summaryrefslogtreecommitdiff
path: root/layout/svg
diff options
context:
space:
mode:
authorMatt A. Tobin <mattatobin@localhost.localdomain>2018-02-02 04:16:08 -0500
committerMatt A. Tobin <mattatobin@localhost.localdomain>2018-02-02 04:16:08 -0500
commitad18d877ddd2a44d98fa12ccd3dbbcf4d0ac4299 (patch)
tree10027f336435511475e392454359edea8e25895d /layout/svg
parent15477ed9af4859dacb069040b5d4de600803d3bc (diff)
downloaduxp-ad18d877ddd2a44d98fa12ccd3dbbcf4d0ac4299.tar.gz
Add m-esr52 at 52.6.0
Diffstat (limited to 'layout/svg')
-rw-r--r--layout/svg/AutoReferenceLimiter.h127
-rw-r--r--layout/svg/SVGContextPaint.cpp266
-rw-r--r--layout/svg/SVGContextPaint.h197
-rw-r--r--layout/svg/SVGFEContainerFrame.cpp108
-rw-r--r--layout/svg/SVGFEImageFrame.cpp168
-rw-r--r--layout/svg/SVGFELeafFrame.cpp107
-rw-r--r--layout/svg/SVGFEUnstyledLeafFrame.cpp87
-rw-r--r--layout/svg/SVGImageContext.h88
-rw-r--r--layout/svg/SVGTextFrame.cpp5653
-rw-r--r--layout/svg/SVGTextFrame.h605
-rw-r--r--layout/svg/SVGViewFrame.cpp127
-rw-r--r--layout/svg/crashtests/1016145.svg5
-rw-r--r--layout/svg/crashtests/1028512.svg15
-rw-r--r--layout/svg/crashtests/1140080-1.svg11
-rw-r--r--layout/svg/crashtests/1149542-1.svg9
-rw-r--r--layout/svg/crashtests/1156581-1.svg12
-rw-r--r--layout/svg/crashtests/1182496-1.html21
-rw-r--r--layout/svg/crashtests/1209525-1.svg7
-rw-r--r--layout/svg/crashtests/1223281-1.svg24
-rw-r--r--layout/svg/crashtests/220165-1.svg21
-rw-r--r--layout/svg/crashtests/267650-1.svg4
-rw-r--r--layout/svg/crashtests/294022-1.svg17
-rw-r--r--layout/svg/crashtests/307314-1.svg9
-rw-r--r--layout/svg/crashtests/308615-1.svg10
-rw-r--r--layout/svg/crashtests/308917-1.svg35
-rw-r--r--layout/svg/crashtests/310436-1.svg28
-rw-r--r--layout/svg/crashtests/310638.svg35
-rw-r--r--layout/svg/crashtests/313737-1.xml16
-rw-r--r--layout/svg/crashtests/314244-1.xul26
-rw-r--r--layout/svg/crashtests/322185-1.svg6
-rw-r--r--layout/svg/crashtests/322215-1.svg31
-rw-r--r--layout/svg/crashtests/323704-1.svg12
-rw-r--r--layout/svg/crashtests/325427-1.svg20
-rw-r--r--layout/svg/crashtests/326495-1.svg16
-rw-r--r--layout/svg/crashtests/326974-1.svg21
-rw-r--r--layout/svg/crashtests/327706-1.svg9
-rw-r--r--layout/svg/crashtests/327709-1.svg17
-rw-r--r--layout/svg/crashtests/327711-1.svg19
-rw-r--r--layout/svg/crashtests/328137-1.svg24
-rw-r--r--layout/svg/crashtests/329848-1.svg1
-rw-r--r--layout/svg/crashtests/337408-1.xul21
-rw-r--r--layout/svg/crashtests/338301-1.xhtml13
-rw-r--r--layout/svg/crashtests/338312-1.xhtml28
-rw-r--r--layout/svg/crashtests/340083-1.svg9
-rw-r--r--layout/svg/crashtests/340945-1.svg2
-rw-r--r--layout/svg/crashtests/342923-1.html23
-rw-r--r--layout/svg/crashtests/343221-1.xhtml20
-rw-r--r--layout/svg/crashtests/344749-1.svg11
-rw-r--r--layout/svg/crashtests/344887-1.svg18
-rw-r--r--layout/svg/crashtests/344892-1.svg5
-rw-r--r--layout/svg/crashtests/344898-1.svg19
-rw-r--r--layout/svg/crashtests/344904-1.svg19
-rw-r--r--layout/svg/crashtests/345418-1.svg4
-rw-r--r--layout/svg/crashtests/348982-1.xhtml20
-rw-r--r--layout/svg/crashtests/354777-1.xhtml28
-rw-r--r--layout/svg/crashtests/359516-1.svg36
-rw-r--r--layout/svg/crashtests/361015-1.svg33
-rw-r--r--layout/svg/crashtests/361587-1.svg31
-rw-r--r--layout/svg/crashtests/363611-1.xhtml21
-rw-r--r--layout/svg/crashtests/364688-1.svg34
-rw-r--r--layout/svg/crashtests/366956-1.svg61
-rw-r--r--layout/svg/crashtests/366956-2.svg61
-rw-r--r--layout/svg/crashtests/367111-1.svg29
-rw-r--r--layout/svg/crashtests/367368-1.xhtml12
-rw-r--r--layout/svg/crashtests/369233-1.svg33
-rw-r--r--layout/svg/crashtests/369438-1.svg24
-rw-r--r--layout/svg/crashtests/369438-2.svg27
-rw-r--r--layout/svg/crashtests/371463-1.xhtml8
-rw-r--r--layout/svg/crashtests/371563-1.xhtml32
-rw-r--r--layout/svg/crashtests/375775-1.svg23
-rw-r--r--layout/svg/crashtests/378716.svg4
-rw-r--r--layout/svg/crashtests/380691-1.svg4
-rw-r--r--layout/svg/crashtests/384391-1.xhtml20
-rw-r--r--layout/svg/crashtests/384499-1.svg20
-rw-r--r--layout/svg/crashtests/384637-1.svg9
-rw-r--r--layout/svg/crashtests/384728-1.svg21
-rw-r--r--layout/svg/crashtests/385246-1.svg9
-rw-r--r--layout/svg/crashtests/385246-2.svg15
-rw-r--r--layout/svg/crashtests/385552-1.svg4
-rw-r--r--layout/svg/crashtests/385552-2.svg4
-rw-r--r--layout/svg/crashtests/385840-1.svg20
-rw-r--r--layout/svg/crashtests/385852-1.svg34
-rw-r--r--layout/svg/crashtests/386475-1.xhtml24
-rw-r--r--layout/svg/crashtests/386566-1.svg21
-rw-r--r--layout/svg/crashtests/386690-1.svg3
-rw-r--r--layout/svg/crashtests/387290-1.svg26
-rw-r--r--layout/svg/crashtests/402408-1.svg32
-rw-r--r--layout/svg/crashtests/404677-1.xhtml9
-rw-r--r--layout/svg/crashtests/409565-1.xhtml3
-rw-r--r--layout/svg/crashtests/409573-1.svg17
-rw-r--r--layout/svg/crashtests/420697-1.svg7
-rw-r--r--layout/svg/crashtests/420697-2.svg6
-rw-r--r--layout/svg/crashtests/429774-1.svg29
-rw-r--r--layout/svg/crashtests/441368-1.svg31
-rw-r--r--layout/svg/crashtests/453754-1.svg7
-rw-r--r--layout/svg/crashtests/455314-1.xhtml16
-rw-r--r--layout/svg/crashtests/458453.html24
-rw-r--r--layout/svg/crashtests/459666-1.html7
-rw-r--r--layout/svg/crashtests/459883.xhtml13
-rw-r--r--layout/svg/crashtests/461289-1.svg18
-rw-r--r--layout/svg/crashtests/464374-1.svg15
-rw-r--r--layout/svg/crashtests/466585-1.svg17
-rw-r--r--layout/svg/crashtests/467323-1.svg10
-rw-r--r--layout/svg/crashtests/467498-1.svg12
-rw-r--r--layout/svg/crashtests/470124-1.svg7
-rw-r--r--layout/svg/crashtests/472782-1.svg3
-rw-r--r--layout/svg/crashtests/474700-1.svg1
-rw-r--r--layout/svg/crashtests/475181-1.svg1
-rw-r--r--layout/svg/crashtests/475193-1.html21
-rw-r--r--layout/svg/crashtests/475302-1.svg11
-rw-r--r--layout/svg/crashtests/477935-1.html11
-rw-r--r--layout/svg/crashtests/478128-1.svg15
-rw-r--r--layout/svg/crashtests/478511-1.svg9
-rw-r--r--layout/svg/crashtests/483439-1.svg17
-rw-r--r--layout/svg/crashtests/492186-1.svg6
-rw-r--r--layout/svg/crashtests/508247-1.svg10
-rw-r--r--layout/svg/crashtests/512890-1.svg4
-rw-r--r--layout/svg/crashtests/515288-1.html5
-rw-r--r--layout/svg/crashtests/522394-1.svg12
-rw-r--r--layout/svg/crashtests/522394-2.svg12
-rw-r--r--layout/svg/crashtests/522394-3.svg12
-rw-r--r--layout/svg/crashtests/566216-1.svg19
-rw-r--r--layout/svg/crashtests/587336-1.html9
-rw-r--r--layout/svg/crashtests/590291-1.svg9
-rw-r--r--layout/svg/crashtests/601999-1.html5
-rw-r--r--layout/svg/crashtests/605626-1.svg3
-rw-r--r--layout/svg/crashtests/610594-1.html6
-rw-r--r--layout/svg/crashtests/610954-1.html1
-rw-r--r--layout/svg/crashtests/612662-1.svg1
-rw-r--r--layout/svg/crashtests/612662-2.svg3
-rw-r--r--layout/svg/crashtests/612736-1.svg19
-rw-r--r--layout/svg/crashtests/612736-2.svg8
-rw-r--r--layout/svg/crashtests/614367-1.svg8
-rw-r--r--layout/svg/crashtests/620034-1.html15
-rw-r--r--layout/svg/crashtests/621598-1.svg16
-rw-r--r--layout/svg/crashtests/648819-1.html6
-rw-r--r--layout/svg/crashtests/655025-1.svg6
-rw-r--r--layout/svg/crashtests/655025-2.svg6
-rw-r--r--layout/svg/crashtests/655025-3.svg9
-rw-r--r--layout/svg/crashtests/657077-1.svg19
-rw-r--r--layout/svg/crashtests/669025-1.svg8
-rw-r--r--layout/svg/crashtests/669025-2.svg8
-rw-r--r--layout/svg/crashtests/682411-1.svg5
-rw-r--r--layout/svg/crashtests/692203-1.svg4
-rw-r--r--layout/svg/crashtests/692203-2.svg4
-rw-r--r--layout/svg/crashtests/693424-1.svg6
-rw-r--r--layout/svg/crashtests/709920-1.svg23
-rw-r--r--layout/svg/crashtests/709920-2.svg23
-rw-r--r--layout/svg/crashtests/713413-1.svg12
-rw-r--r--layout/svg/crashtests/722003-1.svg13
-rw-r--r--layout/svg/crashtests/725918-1.svg4
-rw-r--r--layout/svg/crashtests/732836-1.svg17
-rw-r--r--layout/svg/crashtests/740627-1.svg6
-rw-r--r--layout/svg/crashtests/740627-2.svg6
-rw-r--r--layout/svg/crashtests/757704-1.svg17
-rw-r--r--layout/svg/crashtests/757718-1.svg18
-rw-r--r--layout/svg/crashtests/757751-1.svg8
-rw-r--r--layout/svg/crashtests/767056-1.svg21
-rw-r--r--layout/svg/crashtests/767535-1.xhtml22
-rw-r--r--layout/svg/crashtests/768087-1.html4
-rw-r--r--layout/svg/crashtests/768351.svg2
-rw-r--r--layout/svg/crashtests/778492-1.svg4
-rw-r--r--layout/svg/crashtests/779971-1.svg14
-rw-r--r--layout/svg/crashtests/780764-1.svg18
-rw-r--r--layout/svg/crashtests/780963-1.html27
-rw-r--r--layout/svg/crashtests/782141-1.svg16
-rw-r--r--layout/svg/crashtests/784061-1.svg16
-rw-r--r--layout/svg/crashtests/788831-1.svg5
-rw-r--r--layout/svg/crashtests/789390-1.html1
-rw-r--r--layout/svg/crashtests/790072.svg1
-rw-r--r--layout/svg/crashtests/791826-1.svg14
-rw-r--r--layout/svg/crashtests/803562-1.svg18
-rw-r--r--layout/svg/crashtests/808318-1.svg2
-rw-r--r--layout/svg/crashtests/813420-1.svg14
-rw-r--r--layout/svg/crashtests/841163-1.svg29
-rw-r--r--layout/svg/crashtests/841812-1.svg11
-rw-r--r--layout/svg/crashtests/842009-1.svg5
-rw-r--r--layout/svg/crashtests/842630-1.svg1
-rw-r--r--layout/svg/crashtests/842909-1.svg11
-rw-r--r--layout/svg/crashtests/843072-1.svg11
-rw-r--r--layout/svg/crashtests/843917-1.svg19
-rw-r--r--layout/svg/crashtests/847139-1.svg13
-rw-r--r--layout/svg/crashtests/849688-1.svg11
-rw-r--r--layout/svg/crashtests/849688-2.svg11
-rw-r--r--layout/svg/crashtests/860378-1.svg24
-rw-r--r--layout/svg/crashtests/868904-1.svg17
-rw-r--r--layout/svg/crashtests/873806-1.svg10
-rw-r--r--layout/svg/crashtests/876831-1.svg18
-rw-r--r--layout/svg/crashtests/877029-1.svg10
-rw-r--r--layout/svg/crashtests/880925-1.svg26
-rw-r--r--layout/svg/crashtests/881031-1.svg15
-rw-r--r--layout/svg/crashtests/885608-1.svg13
-rw-r--r--layout/svg/crashtests/890782-1.svg16
-rw-r--r--layout/svg/crashtests/890783-1.svg19
-rw-r--r--layout/svg/crashtests/893510-1.svg5
-rw-r--r--layout/svg/crashtests/895311-1.svg17
-rw-r--r--layout/svg/crashtests/897342-1.svg1
-rw-r--r--layout/svg/crashtests/898909-1.svg11
-rw-r--r--layout/svg/crashtests/898951-1.svg3
-rw-r--r--layout/svg/crashtests/913990.html5
-rw-r--r--layout/svg/crashtests/919371-1.xhtml5
-rw-r--r--layout/svg/crashtests/950324-1.svg3
-rw-r--r--layout/svg/crashtests/952270-1.svg9
-rw-r--r--layout/svg/crashtests/963086-1.svg18
-rw-r--r--layout/svg/crashtests/974746-1.svg9
-rw-r--r--layout/svg/crashtests/975773-1.svg10
-rw-r--r--layout/svg/crashtests/979407-1.svg4
-rw-r--r--layout/svg/crashtests/979407-2.svg4
-rw-r--r--layout/svg/crashtests/993443.svg4
-rw-r--r--layout/svg/crashtests/crashtests.list199
-rw-r--r--layout/svg/crashtests/extref-test-1-resource.xhtml24
-rw-r--r--layout/svg/crashtests/extref-test-1.xhtml11
-rw-r--r--layout/svg/moz.build76
-rw-r--r--layout/svg/nsCSSClipPathInstance.cpp377
-rw-r--r--layout/svg/nsCSSClipPathInstance.h64
-rw-r--r--layout/svg/nsCSSFilterInstance.cpp421
-rw-r--r--layout/svg/nsCSSFilterInstance.h144
-rw-r--r--layout/svg/nsFilterInstance.cpp627
-rw-r--r--layout/svg/nsFilterInstance.h390
-rw-r--r--layout/svg/nsISVGChildFrame.h154
-rw-r--r--layout/svg/nsISVGSVGFrame.h26
-rw-r--r--layout/svg/nsSVGAFrame.cpp147
-rw-r--r--layout/svg/nsSVGClipPathFrame.cpp511
-rw-r--r--layout/svg/nsSVGClipPathFrame.h160
-rw-r--r--layout/svg/nsSVGContainerFrame.cpp445
-rw-r--r--layout/svg/nsSVGContainerFrame.h155
-rw-r--r--layout/svg/nsSVGEffects.cpp1001
-rw-r--r--layout/svg/nsSVGEffects.h636
-rw-r--r--layout/svg/nsSVGFilterFrame.cpp212
-rw-r--r--layout/svg/nsSVGFilterFrame.h99
-rw-r--r--layout/svg/nsSVGFilterInstance.cpp453
-rw-r--r--layout/svg/nsSVGFilterInstance.h285
-rw-r--r--layout/svg/nsSVGFilterPaintCallback.h35
-rw-r--r--layout/svg/nsSVGForeignObjectFrame.cpp592
-rw-r--r--layout/svg/nsSVGForeignObjectFrame.h105
-rw-r--r--layout/svg/nsSVGGFrame.cpp98
-rw-r--r--layout/svg/nsSVGGFrame.h59
-rw-r--r--layout/svg/nsSVGGenericContainerFrame.cpp54
-rw-r--r--layout/svg/nsSVGGenericContainerFrame.h55
-rw-r--r--layout/svg/nsSVGGradientFrame.cpp678
-rw-r--r--layout/svg/nsSVGGradientFrame.h213
-rw-r--r--layout/svg/nsSVGImageFrame.cpp667
-rw-r--r--layout/svg/nsSVGInnerSVGFrame.cpp322
-rw-r--r--layout/svg/nsSVGInnerSVGFrame.h77
-rw-r--r--layout/svg/nsSVGIntegrationUtils.cpp1142
-rw-r--r--layout/svg/nsSVGIntegrationUtils.h231
-rw-r--r--layout/svg/nsSVGMarkerFrame.cpp278
-rw-r--r--layout/svg/nsSVGMarkerFrame.h174
-rw-r--r--layout/svg/nsSVGMaskFrame.cpp403
-rw-r--r--layout/svg/nsSVGMaskFrame.h131
-rw-r--r--layout/svg/nsSVGMaskFrameNEON.cpp73
-rw-r--r--layout/svg/nsSVGMaskFrameNEON.h19
-rw-r--r--layout/svg/nsSVGOuterSVGFrame.cpp999
-rw-r--r--layout/svg/nsSVGOuterSVGFrame.h277
-rw-r--r--layout/svg/nsSVGPaintServerFrame.h70
-rw-r--r--layout/svg/nsSVGPathGeometryFrame.cpp928
-rw-r--r--layout/svg/nsSVGPathGeometryFrame.h147
-rw-r--r--layout/svg/nsSVGPatternFrame.cpp748
-rw-r--r--layout/svg/nsSVGPatternFrame.h154
-rw-r--r--layout/svg/nsSVGStopFrame.cpp116
-rw-r--r--layout/svg/nsSVGSwitchFrame.cpp268
-rw-r--r--layout/svg/nsSVGUseFrame.cpp259
-rw-r--r--layout/svg/nsSVGUtils.cpp1829
-rw-r--r--layout/svg/nsSVGUtils.h600
-rw-r--r--layout/svg/resources/content/svgBindings.xml15
-rw-r--r--layout/svg/svg.css106
-rw-r--r--layout/svg/tests/example.xml227
-rw-r--r--layout/svg/tests/svg.css33
268 files changed, 29133 insertions, 0 deletions
diff --git a/layout/svg/AutoReferenceLimiter.h b/layout/svg/AutoReferenceLimiter.h
new file mode 100644
index 0000000000..5f822ba135
--- /dev/null
+++ b/layout/svg/AutoReferenceLimiter.h
@@ -0,0 +1,127 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef NS_AUTOREFERENCELIMITER_H
+#define NS_AUTOREFERENCELIMITER_H
+
+#include "mozilla/Assertions.h"
+#include "mozilla/Attributes.h"
+#include "mozilla/ReentrancyGuard.h"
+#include "nsDebug.h"
+
+namespace mozilla {
+
+/**
+ * This helper allows us to handle two related issues in SVG content: reference
+ * loops, and reference chains that we deem to be too long.
+ *
+ * SVG content may contain reference loops where an SVG effect (a clipPath,
+ * say) may reference itself either directly or, perhaps more likely,
+ * indirectly via a reference chain to other elements that eventually leads
+ * back to itself. This helper class allows us to detect and immediately break
+ * such reference loops when applying an effect so that we can prevent
+ * reference loops causing us to recurse until we run out of stack space and
+ * crash.
+ *
+ * SVG also allows for (non-loop) reference chains of arbitrary length, the
+ * length depending entirely on the SVG content. Some SVG authoring tools have
+ * been known to create absurdly long reference chains. (For example, bug
+ * 1253590 details a case where Adobe Illustrator created an SVG with a chain
+ * of 5000 clip paths which could cause us to run out of stack space and
+ * crash.) This helper class also allows us to limit the number of times we
+ * recurse into a function, thereby allowing us to limit the length ofreference
+ * chains.
+ *
+ * Consumers that need to handle the reference loop case should add a member
+ * variable (mReferencing, say) to the class that represents and applies the
+ * SVG effect in question (typically an nsIFrame sub-class), initialize that
+ * member to AutoReferenceLimiter::notReferencing in the class' constructor
+ * (and never touch that variable again), and then add something like the
+ * following at the top of the method(s) that may recurse to follow references
+ * when applying an effect:
+ *
+ * AutoReferenceLimiter refLoopDetector(&mInUse, 1); // only one ref allowed
+ * if (!refLoopDetector.Reference()) {
+ * return; // reference loop
+ * }
+ *
+ * Consumers that need to limit reference chain lengths should add something
+ * like the following code at the top of the method(s) that may recurse to
+ * follow references when applying an effect:
+ *
+ * static int16_t sChainLengthCounter = AutoReferenceLimiter::notReferencing;
+ *
+ * AutoReferenceLimiter refChainLengthLimiter(&sChainLengthCounter, MAX_LEN);
+ * if (!refChainLengthLimiter.Reference()) {
+ * return; // reference chain too long
+ * }
+ */
+class MOZ_RAII AutoReferenceLimiter
+{
+public:
+ static const int16_t notReferencing = -2;
+
+ AutoReferenceLimiter(int16_t* aRefCounter, int16_t aMaxReferenceCount)
+ {
+ MOZ_ASSERT(aMaxReferenceCount > 0 &&
+ aRefCounter &&
+ (*aRefCounter == notReferencing ||
+ (*aRefCounter >= 0 && *aRefCounter < aMaxReferenceCount)));
+
+ if (*aRefCounter == notReferencing) {
+ // initialize
+ *aRefCounter = aMaxReferenceCount;
+ }
+ mRefCounter = aRefCounter;
+ mMaxReferenceCount = aMaxReferenceCount;
+ }
+
+ ~AutoReferenceLimiter() {
+ // If we fail this assert then there were more destructor calls than
+ // Reference() calls (a consumer forgot to to call Reference()), or else
+ // someone messed with the variable pointed to by mRefCounter.
+ MOZ_ASSERT(*mRefCounter < mMaxReferenceCount);
+
+ (*mRefCounter)++;
+
+ if (*mRefCounter == mMaxReferenceCount) {
+ *mRefCounter = notReferencing; // reset ready for use next time
+ }
+ }
+
+ /**
+ * Returns true on success (no reference loop/reference chain length is
+ * within the specified limits), else returns false on failure (there is a
+ * reference loop/the reference chain has exceeded the specified limits).
+ */
+ MOZ_MUST_USE bool Reference() {
+ // If we fail this assertion then either a consumer failed to break a
+ // reference loop/chain, or else they called Reference() more than once
+ MOZ_ASSERT(*mRefCounter >= 0);
+
+ (*mRefCounter)--;
+
+ if (*mRefCounter < 0) {
+ // TODO: This is an issue with the document, not with Mozilla code. We
+ // should stop using NS_WARNING and send a message to the console
+ // instead (but only once per document, not over and over as we repaint).
+ if (mMaxReferenceCount == 1) {
+ NS_WARNING("Reference loop detected!");
+ } else {
+ NS_WARNING("Reference chain length limit exceeded!");
+ }
+ return false;
+ }
+ return true;
+ }
+
+private:
+ int16_t* mRefCounter;
+ int16_t mMaxReferenceCount;
+};
+
+} // namespace mozilla
+
+#endif // NS_AUTOREFERENCELIMITER_H
diff --git a/layout/svg/SVGContextPaint.cpp b/layout/svg/SVGContextPaint.cpp
new file mode 100644
index 0000000000..23b9b99dcf
--- /dev/null
+++ b/layout/svg/SVGContextPaint.cpp
@@ -0,0 +1,266 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "SVGContextPaint.h"
+
+#include "gfxContext.h"
+#include "mozilla/gfx/2D.h"
+#include "nsIDocument.h"
+#include "nsSVGPaintServerFrame.h"
+#include "nsSVGEffects.h"
+#include "nsSVGPaintServerFrame.h"
+
+using namespace mozilla::gfx;
+
+namespace mozilla {
+
+/**
+ * Stores in |aTargetPaint| information on how to reconstruct the current
+ * fill or stroke pattern. Will also set the paint opacity to transparent if
+ * the paint is set to "none".
+ * @param aOuterContextPaint pattern information from the outer text context
+ * @param aTargetPaint where to store the current pattern information
+ * @param aFillOrStroke member pointer to the paint we are setting up
+ * @param aProperty the frame property descriptor of the fill or stroke paint
+ * server frame
+ */
+static void
+SetupInheritablePaint(const DrawTarget* aDrawTarget,
+ const gfxMatrix& aContextMatrix,
+ nsIFrame* aFrame,
+ float& aOpacity,
+ SVGContextPaint* aOuterContextPaint,
+ SVGContextPaintImpl::Paint& aTargetPaint,
+ nsStyleSVGPaint nsStyleSVG::*aFillOrStroke,
+ nsSVGEffects::PaintingPropertyDescriptor aProperty)
+{
+ const nsStyleSVG *style = aFrame->StyleSVG();
+ nsSVGPaintServerFrame *ps =
+ nsSVGEffects::GetPaintServer(aFrame, aFillOrStroke, aProperty);
+
+ if (ps) {
+ RefPtr<gfxPattern> pattern =
+ ps->GetPaintServerPattern(aFrame, aDrawTarget, aContextMatrix,
+ aFillOrStroke, aOpacity);
+ if (pattern) {
+ aTargetPaint.SetPaintServer(aFrame, aContextMatrix, ps);
+ return;
+ }
+ }
+ if (aOuterContextPaint) {
+ RefPtr<gfxPattern> pattern;
+ switch ((style->*aFillOrStroke).Type()) {
+ case eStyleSVGPaintType_ContextFill:
+ pattern = aOuterContextPaint->GetFillPattern(aDrawTarget, aOpacity,
+ aContextMatrix);
+ break;
+ case eStyleSVGPaintType_ContextStroke:
+ pattern = aOuterContextPaint->GetStrokePattern(aDrawTarget, aOpacity,
+ aContextMatrix);
+ break;
+ default:
+ ;
+ }
+ if (pattern) {
+ aTargetPaint.SetContextPaint(aOuterContextPaint, (style->*aFillOrStroke).Type());
+ return;
+ }
+ }
+ nscolor color =
+ nsSVGUtils::GetFallbackOrPaintColor(aFrame->StyleContext(), aFillOrStroke);
+ aTargetPaint.SetColor(color);
+}
+
+DrawMode
+SVGContextPaintImpl::Init(const DrawTarget* aDrawTarget,
+ const gfxMatrix& aContextMatrix,
+ nsIFrame* aFrame,
+ SVGContextPaint* aOuterContextPaint)
+{
+ DrawMode toDraw = DrawMode(0);
+
+ const nsStyleSVG *style = aFrame->StyleSVG();
+
+ // fill:
+ if (style->mFill.Type() == eStyleSVGPaintType_None) {
+ SetFillOpacity(0.0f);
+ } else {
+ float opacity = nsSVGUtils::GetOpacity(style->FillOpacitySource(),
+ style->mFillOpacity,
+ aOuterContextPaint);
+
+ SetupInheritablePaint(aDrawTarget, aContextMatrix, aFrame,
+ opacity, aOuterContextPaint,
+ mFillPaint, &nsStyleSVG::mFill,
+ nsSVGEffects::FillProperty());
+
+ SetFillOpacity(opacity);
+
+ toDraw |= DrawMode::GLYPH_FILL;
+ }
+
+ // stroke:
+ if (style->mStroke.Type() == eStyleSVGPaintType_None) {
+ SetStrokeOpacity(0.0f);
+ } else {
+ float opacity = nsSVGUtils::GetOpacity(style->StrokeOpacitySource(),
+ style->mStrokeOpacity,
+ aOuterContextPaint);
+
+ SetupInheritablePaint(aDrawTarget, aContextMatrix, aFrame,
+ opacity, aOuterContextPaint,
+ mStrokePaint, &nsStyleSVG::mStroke,
+ nsSVGEffects::StrokeProperty());
+
+ SetStrokeOpacity(opacity);
+
+ toDraw |= DrawMode::GLYPH_STROKE;
+ }
+
+ return toDraw;
+}
+
+void
+SVGContextPaint::InitStrokeGeometry(gfxContext* aContext,
+ float devUnitsPerSVGUnit)
+{
+ mStrokeWidth = aContext->CurrentLineWidth() / devUnitsPerSVGUnit;
+ aContext->CurrentDash(mDashes, &mDashOffset);
+ for (uint32_t i = 0; i < mDashes.Length(); i++) {
+ mDashes[i] /= devUnitsPerSVGUnit;
+ }
+ mDashOffset /= devUnitsPerSVGUnit;
+}
+
+/* static */ SVGContextPaint*
+SVGContextPaint::GetContextPaint(nsIContent* aContent)
+{
+ nsIDocument* ownerDoc = aContent->OwnerDoc();
+
+ if (!ownerDoc->IsBeingUsedAsImage()) {
+ return nullptr;
+ }
+
+ return static_cast<SVGContextPaint*>(
+ ownerDoc->GetProperty(nsGkAtoms::svgContextPaint));
+}
+
+already_AddRefed<gfxPattern>
+SVGContextPaintImpl::GetFillPattern(const DrawTarget* aDrawTarget,
+ float aOpacity,
+ const gfxMatrix& aCTM)
+{
+ return mFillPaint.GetPattern(aDrawTarget, aOpacity, &nsStyleSVG::mFill, aCTM);
+}
+
+already_AddRefed<gfxPattern>
+SVGContextPaintImpl::GetStrokePattern(const DrawTarget* aDrawTarget,
+ float aOpacity,
+ const gfxMatrix& aCTM)
+{
+ return mStrokePaint.GetPattern(aDrawTarget, aOpacity, &nsStyleSVG::mStroke, aCTM);
+}
+
+already_AddRefed<gfxPattern>
+SVGContextPaintImpl::Paint::GetPattern(const DrawTarget* aDrawTarget,
+ float aOpacity,
+ nsStyleSVGPaint nsStyleSVG::*aFillOrStroke,
+ const gfxMatrix& aCTM)
+{
+ RefPtr<gfxPattern> pattern;
+ if (mPatternCache.Get(aOpacity, getter_AddRefs(pattern))) {
+ // Set the pattern matrix just in case it was messed with by a previous
+ // caller. We should get the same matrix each time a pattern is constructed
+ // so this should be fine.
+ pattern->SetMatrix(aCTM * mPatternMatrix);
+ return pattern.forget();
+ }
+
+ switch (mPaintType) {
+ case eStyleSVGPaintType_None:
+ pattern = new gfxPattern(Color());
+ mPatternMatrix = gfxMatrix();
+ break;
+ case eStyleSVGPaintType_Color: {
+ Color color = Color::FromABGR(mPaintDefinition.mColor);
+ color.a *= aOpacity;
+ pattern = new gfxPattern(color);
+ mPatternMatrix = gfxMatrix();
+ break;
+ }
+ case eStyleSVGPaintType_Server:
+ pattern = mPaintDefinition.mPaintServerFrame->GetPaintServerPattern(mFrame,
+ aDrawTarget,
+ mContextMatrix,
+ aFillOrStroke,
+ aOpacity);
+ {
+ // m maps original-user-space to pattern space
+ gfxMatrix m = pattern->GetMatrix();
+ gfxMatrix deviceToOriginalUserSpace = mContextMatrix;
+ if (!deviceToOriginalUserSpace.Invert()) {
+ return nullptr;
+ }
+ // mPatternMatrix maps device space to pattern space via original user space
+ mPatternMatrix = deviceToOriginalUserSpace * m;
+ }
+ pattern->SetMatrix(aCTM * mPatternMatrix);
+ break;
+ case eStyleSVGPaintType_ContextFill:
+ pattern = mPaintDefinition.mContextPaint->GetFillPattern(aDrawTarget,
+ aOpacity, aCTM);
+ // Don't cache this. mContextPaint will have cached it anyway. If we
+ // cache it, we'll have to compute mPatternMatrix, which is annoying.
+ return pattern.forget();
+ case eStyleSVGPaintType_ContextStroke:
+ pattern = mPaintDefinition.mContextPaint->GetStrokePattern(aDrawTarget,
+ aOpacity, aCTM);
+ // Don't cache this. mContextPaint will have cached it anyway. If we
+ // cache it, we'll have to compute mPatternMatrix, which is annoying.
+ return pattern.forget();
+ default:
+ MOZ_ASSERT(false, "invalid paint type");
+ return nullptr;
+ }
+
+ mPatternCache.Put(aOpacity, pattern);
+ return pattern.forget();
+}
+
+AutoSetRestoreSVGContextPaint::AutoSetRestoreSVGContextPaint(
+ SVGContextPaint* aContextPaint,
+ nsIDocument* aSVGDocument)
+ : mSVGDocument(aSVGDocument)
+ , mOuterContextPaint(aSVGDocument->GetProperty(nsGkAtoms::svgContextPaint))
+{
+ // The way that we supply context paint is to temporarily set the context
+ // paint on the owner document of the SVG that we're painting while it's
+ // being painted.
+
+ MOZ_ASSERT(aContextPaint);
+ MOZ_ASSERT(aSVGDocument->IsBeingUsedAsImage(),
+ "SVGContextPaint::GetContextPaint assumes this");
+
+ if (mOuterContextPaint) {
+ mSVGDocument->UnsetProperty(nsGkAtoms::svgContextPaint);
+ }
+
+ DebugOnly<nsresult> res =
+ mSVGDocument->SetProperty(nsGkAtoms::svgContextPaint, aContextPaint);
+
+ NS_WARNING_ASSERTION(NS_SUCCEEDED(res), "Failed to set context paint");
+}
+
+AutoSetRestoreSVGContextPaint::~AutoSetRestoreSVGContextPaint()
+{
+ mSVGDocument->UnsetProperty(nsGkAtoms::svgContextPaint);
+ if (mOuterContextPaint) {
+ DebugOnly<nsresult> res =
+ mSVGDocument->SetProperty(nsGkAtoms::svgContextPaint, mOuterContextPaint);
+
+ NS_WARNING_ASSERTION(NS_SUCCEEDED(res), "Failed to restore context paint");
+ }
+}
+
+} // namespace mozilla
diff --git a/layout/svg/SVGContextPaint.h b/layout/svg/SVGContextPaint.h
new file mode 100644
index 0000000000..3c77647443
--- /dev/null
+++ b/layout/svg/SVGContextPaint.h
@@ -0,0 +1,197 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef MOZILLA_SVGCONTEXTPAINT_H_
+#define MOZILLA_SVGCONTEXTPAINT_H_
+
+#include "DrawMode.h"
+#include "gfxMatrix.h"
+#include "gfxPattern.h"
+#include "gfxTypes.h"
+#include "mozilla/AlreadyAddRefed.h"
+#include "mozilla/gfx/2D.h"
+#include "nsStyleStruct.h"
+#include "nsTArray.h"
+
+class gfxContext;
+class nsIDocument;
+class nsSVGPaintServerFrame;
+
+namespace mozilla {
+
+/**
+ * This class is used to pass information about a context element through to
+ * SVG painting code in order to resolve the 'context-fill' and related
+ * keywords. See:
+ *
+ * https://www.w3.org/TR/SVG2/painting.html#context-paint
+ *
+ * This feature allows the color in an SVG-in-OpenType glyph to come from the
+ * computed style for the text that is being drawn, for example, or for color
+ * in an SVG embedded by an <img> element to come from the embedding <img>
+ * element.
+ */
+class SVGContextPaint
+{
+protected:
+ typedef mozilla::gfx::DrawTarget DrawTarget;
+
+ SVGContextPaint() {}
+
+public:
+ virtual ~SVGContextPaint() {}
+
+ virtual already_AddRefed<gfxPattern> GetFillPattern(const DrawTarget* aDrawTarget,
+ float aOpacity,
+ const gfxMatrix& aCTM) = 0;
+ virtual already_AddRefed<gfxPattern> GetStrokePattern(const DrawTarget* aDrawTarget,
+ float aOpacity,
+ const gfxMatrix& aCTM) = 0;
+ virtual float GetFillOpacity() const = 0;
+ virtual float GetStrokeOpacity() const = 0;
+
+ already_AddRefed<gfxPattern> GetFillPattern(const DrawTarget* aDrawTarget,
+ const gfxMatrix& aCTM) {
+ return GetFillPattern(aDrawTarget, GetFillOpacity(), aCTM);
+ }
+
+ already_AddRefed<gfxPattern> GetStrokePattern(const DrawTarget* aDrawTarget,
+ const gfxMatrix& aCTM) {
+ return GetStrokePattern(aDrawTarget, GetStrokeOpacity(), aCTM);
+ }
+
+ static SVGContextPaint* GetContextPaint(nsIContent* aContent);
+
+ // XXX This gets the geometry params from the gfxContext. We should get that
+ // information from the actual paint context!
+ void InitStrokeGeometry(gfxContext *aContext,
+ float devUnitsPerSVGUnit);
+
+ FallibleTArray<gfxFloat>& GetStrokeDashArray() {
+ return mDashes;
+ }
+
+ gfxFloat GetStrokeDashOffset() {
+ return mDashOffset;
+ }
+
+ gfxFloat GetStrokeWidth() {
+ return mStrokeWidth;
+ }
+
+private:
+ // Member-vars are initialized in InitStrokeGeometry.
+ FallibleTArray<gfxFloat> mDashes;
+ MOZ_INIT_OUTSIDE_CTOR gfxFloat mDashOffset;
+ MOZ_INIT_OUTSIDE_CTOR gfxFloat mStrokeWidth;
+};
+
+/**
+ * RAII class used to temporarily set and remove an SVGContextPaint while a
+ * piece of SVG is being painted. The context paint is set on the SVG's owner
+ * document, as expected by SVGContextPaint::GetContextPaint. Any pre-existing
+ * context paint is restored after this class removes the context paint that it
+ * set.
+ */
+class MOZ_RAII AutoSetRestoreSVGContextPaint
+{
+public:
+ AutoSetRestoreSVGContextPaint(SVGContextPaint* aContextPaint,
+ nsIDocument* aSVGDocument);
+ ~AutoSetRestoreSVGContextPaint();
+private:
+ nsIDocument* mSVGDocument;
+ // The context paint that needs to be restored by our dtor after it removes
+ // aContextPaint:
+ void* mOuterContextPaint;
+};
+
+
+/**
+ * This class should be flattened into SVGContextPaint once we get rid of the
+ * other sub-class (SimpleTextContextPaint).
+ */
+struct SVGContextPaintImpl : public SVGContextPaint
+{
+protected:
+ typedef mozilla::gfx::DrawTarget DrawTarget;
+public:
+ DrawMode Init(const DrawTarget* aDrawTarget,
+ const gfxMatrix& aContextMatrix,
+ nsIFrame* aFrame,
+ SVGContextPaint* aOuterContextPaint);
+
+ already_AddRefed<gfxPattern> GetFillPattern(const DrawTarget* aDrawTarget,
+ float aOpacity,
+ const gfxMatrix& aCTM) override;
+ already_AddRefed<gfxPattern> GetStrokePattern(const DrawTarget* aDrawTarget,
+ float aOpacity,
+ const gfxMatrix& aCTM) override;
+
+ void SetFillOpacity(float aOpacity) { mFillOpacity = aOpacity; }
+ float GetFillOpacity() const override { return mFillOpacity; }
+
+ void SetStrokeOpacity(float aOpacity) { mStrokeOpacity = aOpacity; }
+ float GetStrokeOpacity() const override { return mStrokeOpacity; }
+
+ struct Paint {
+ Paint() : mPaintType(eStyleSVGPaintType_None) {}
+
+ void SetPaintServer(nsIFrame* aFrame,
+ const gfxMatrix& aContextMatrix,
+ nsSVGPaintServerFrame* aPaintServerFrame) {
+ mPaintType = eStyleSVGPaintType_Server;
+ mPaintDefinition.mPaintServerFrame = aPaintServerFrame;
+ mFrame = aFrame;
+ mContextMatrix = aContextMatrix;
+ }
+
+ void SetColor(const nscolor &aColor) {
+ mPaintType = eStyleSVGPaintType_Color;
+ mPaintDefinition.mColor = aColor;
+ }
+
+ void SetContextPaint(SVGContextPaint* aContextPaint,
+ nsStyleSVGPaintType aPaintType) {
+ NS_ASSERTION(aPaintType == eStyleSVGPaintType_ContextFill ||
+ aPaintType == eStyleSVGPaintType_ContextStroke,
+ "Invalid context paint type");
+ mPaintType = aPaintType;
+ mPaintDefinition.mContextPaint = aContextPaint;
+ }
+
+ union {
+ nsSVGPaintServerFrame* mPaintServerFrame;
+ SVGContextPaint* mContextPaint;
+ nscolor mColor;
+ } mPaintDefinition;
+
+ // Initialized (if needed) in SetPaintServer():
+ MOZ_INIT_OUTSIDE_CTOR nsIFrame* mFrame;
+ // CTM defining the user space for the pattern we will use.
+ gfxMatrix mContextMatrix;
+ nsStyleSVGPaintType mPaintType;
+
+ // Device-space-to-pattern-space
+ gfxMatrix mPatternMatrix;
+ nsRefPtrHashtable<nsFloatHashKey, gfxPattern> mPatternCache;
+
+ already_AddRefed<gfxPattern> GetPattern(const DrawTarget* aDrawTarget,
+ float aOpacity,
+ nsStyleSVGPaint nsStyleSVG::*aFillOrStroke,
+ const gfxMatrix& aCTM);
+ };
+
+ Paint mFillPaint;
+ Paint mStrokePaint;
+
+ float mFillOpacity;
+ float mStrokeOpacity;
+};
+
+} // namespace mozilla
+
+#endif // MOZILLA_SVGCONTEXTPAINT_H_
+
diff --git a/layout/svg/SVGFEContainerFrame.cpp b/layout/svg/SVGFEContainerFrame.cpp
new file mode 100644
index 0000000000..ef86470f27
--- /dev/null
+++ b/layout/svg/SVGFEContainerFrame.cpp
@@ -0,0 +1,108 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+// Keep in (case-insensitive) order:
+#include "nsContainerFrame.h"
+#include "nsGkAtoms.h"
+#include "nsIFrame.h"
+#include "nsLiteralString.h"
+#include "nsSVGEffects.h"
+#include "nsSVGFilters.h"
+
+/*
+ * This frame is used by filter primitive elements that
+ * have special child elements that provide parameters.
+ */
+class SVGFEContainerFrame : public nsContainerFrame
+{
+ friend nsIFrame*
+ NS_NewSVGFEContainerFrame(nsIPresShell* aPresShell, nsStyleContext* aContext);
+protected:
+ explicit SVGFEContainerFrame(nsStyleContext* aContext)
+ : nsContainerFrame(aContext)
+ {
+ AddStateBits(NS_FRAME_SVG_LAYOUT | NS_FRAME_IS_NONDISPLAY);
+ }
+
+public:
+ NS_DECL_FRAMEARENA_HELPERS
+
+ virtual bool IsFrameOfType(uint32_t aFlags) const override
+ {
+ return nsContainerFrame::IsFrameOfType(
+ aFlags & ~(nsIFrame::eSVG | nsIFrame::eSVGContainer));
+ }
+
+#ifdef DEBUG_FRAME_DUMP
+ virtual nsresult GetFrameName(nsAString& aResult) const override
+ {
+ return MakeFrameName(NS_LITERAL_STRING("SVGFEContainer"), aResult);
+ }
+#endif
+
+#ifdef DEBUG
+ virtual void Init(nsIContent* aContent,
+ nsContainerFrame* aParent,
+ nsIFrame* aPrevInFlow) override;
+#endif
+ /**
+ * Get the "type" of the frame
+ *
+ * @see nsGkAtoms::svgFEContainerFrame
+ */
+ virtual nsIAtom* GetType() const override;
+
+ virtual nsresult AttributeChanged(int32_t aNameSpaceID,
+ nsIAtom* aAttribute,
+ int32_t aModType) override;
+
+ virtual bool ComputeCustomOverflow(nsOverflowAreas& aOverflowAreas) override {
+ // We don't maintain a visual overflow rect
+ return false;
+ }
+};
+
+nsIFrame*
+NS_NewSVGFEContainerFrame(nsIPresShell* aPresShell, nsStyleContext* aContext)
+{
+ return new (aPresShell) SVGFEContainerFrame(aContext);
+}
+
+NS_IMPL_FRAMEARENA_HELPERS(SVGFEContainerFrame)
+
+#ifdef DEBUG
+void
+SVGFEContainerFrame::Init(nsIContent* aContent,
+ nsContainerFrame* aParent,
+ nsIFrame* aPrevInFlow)
+{
+ NS_ASSERTION(aContent->IsNodeOfType(nsINode::eFILTER),
+ "Trying to construct an SVGFEContainerFrame for a "
+ "content element that doesn't support the right interfaces");
+
+ nsContainerFrame::Init(aContent, aParent, aPrevInFlow);
+}
+#endif /* DEBUG */
+
+nsIAtom *
+SVGFEContainerFrame::GetType() const
+{
+ return nsGkAtoms::svgFEContainerFrame;
+}
+
+nsresult
+SVGFEContainerFrame::AttributeChanged(int32_t aNameSpaceID,
+ nsIAtom* aAttribute,
+ int32_t aModType)
+{
+ nsSVGFE *element = static_cast<nsSVGFE*>(mContent);
+ if (element->AttributeAffectsRendering(aNameSpaceID, aAttribute)) {
+ MOZ_ASSERT(GetParent()->GetType() == nsGkAtoms::svgFilterFrame,
+ "Observers observe the filter, so that's what we must invalidate");
+ nsSVGEffects::InvalidateDirectRenderingObservers(GetParent());
+ }
+
+ return nsContainerFrame::AttributeChanged(aNameSpaceID, aAttribute, aModType);
+}
diff --git a/layout/svg/SVGFEImageFrame.cpp b/layout/svg/SVGFEImageFrame.cpp
new file mode 100644
index 0000000000..185096a93c
--- /dev/null
+++ b/layout/svg/SVGFEImageFrame.cpp
@@ -0,0 +1,168 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+// Keep in (case-insensitive) order:
+#include "nsContainerFrame.h"
+#include "nsContentUtils.h"
+#include "nsFrame.h"
+#include "nsGkAtoms.h"
+#include "nsLiteralString.h"
+#include "nsSVGEffects.h"
+#include "nsSVGFilters.h"
+#include "mozilla/dom/SVGFEImageElement.h"
+
+using namespace mozilla;
+using namespace mozilla::dom;
+
+class SVGFEImageFrame : public nsFrame
+{
+ friend nsIFrame*
+ NS_NewSVGFEImageFrame(nsIPresShell* aPresShell, nsStyleContext* aContext);
+protected:
+ explicit SVGFEImageFrame(nsStyleContext* aContext)
+ : nsFrame(aContext)
+ {
+ AddStateBits(NS_FRAME_SVG_LAYOUT | NS_FRAME_IS_NONDISPLAY);
+
+ // This frame isn't actually displayed, but it contains an image and we want
+ // to use the nsImageLoadingContent machinery for managing images, which
+ // requires visibility tracking, so we enable visibility tracking and
+ // forcibly mark it visible below.
+ EnableVisibilityTracking();
+ }
+
+public:
+ NS_DECL_FRAMEARENA_HELPERS
+
+ virtual void Init(nsIContent* aContent,
+ nsContainerFrame* aParent,
+ nsIFrame* aPrevInFlow) override;
+ virtual void DestroyFrom(nsIFrame* aDestructRoot) override;
+
+ virtual bool IsFrameOfType(uint32_t aFlags) const override
+ {
+ return nsFrame::IsFrameOfType(aFlags & ~(nsIFrame::eSVG));
+ }
+
+#ifdef DEBUG_FRAME_DUMP
+ virtual nsresult GetFrameName(nsAString& aResult) const override
+ {
+ return MakeFrameName(NS_LITERAL_STRING("SVGFEImage"), aResult);
+ }
+#endif
+
+ /**
+ * Get the "type" of the frame
+ *
+ * @see nsGkAtoms::svgFEImageFrame
+ */
+ virtual nsIAtom* GetType() const override;
+
+ virtual nsresult AttributeChanged(int32_t aNameSpaceID,
+ nsIAtom* aAttribute,
+ int32_t aModType) override;
+
+ void OnVisibilityChange(Visibility aNewVisibility,
+ Maybe<OnNonvisible> aNonvisibleAction = Nothing()) override;
+
+ virtual bool ComputeCustomOverflow(nsOverflowAreas& aOverflowAreas) override {
+ // We don't maintain a visual overflow rect
+ return false;
+ }
+};
+
+nsIFrame*
+NS_NewSVGFEImageFrame(nsIPresShell* aPresShell, nsStyleContext* aContext)
+{
+ return new (aPresShell) SVGFEImageFrame(aContext);
+}
+
+NS_IMPL_FRAMEARENA_HELPERS(SVGFEImageFrame)
+
+/* virtual */ void
+SVGFEImageFrame::DestroyFrom(nsIFrame* aDestructRoot)
+{
+ DecApproximateVisibleCount();
+
+ nsCOMPtr<nsIImageLoadingContent> imageLoader =
+ do_QueryInterface(nsFrame::mContent);
+ if (imageLoader) {
+ imageLoader->FrameDestroyed(this);
+ }
+
+ nsFrame::DestroyFrom(aDestructRoot);
+}
+
+void
+SVGFEImageFrame::Init(nsIContent* aContent,
+ nsContainerFrame* aParent,
+ nsIFrame* aPrevInFlow)
+{
+ NS_ASSERTION(aContent->IsSVGElement(nsGkAtoms::feImage),
+ "Trying to construct an SVGFEImageFrame for a "
+ "content element that doesn't support the right interfaces");
+
+ nsFrame::Init(aContent, aParent, aPrevInFlow);
+
+ // We assume that feImage's are always visible.
+ IncApproximateVisibleCount();
+
+ nsCOMPtr<nsIImageLoadingContent> imageLoader =
+ do_QueryInterface(nsFrame::mContent);
+ if (imageLoader) {
+ imageLoader->FrameCreated(this);
+ }
+}
+
+nsIAtom *
+SVGFEImageFrame::GetType() const
+{
+ return nsGkAtoms::svgFEImageFrame;
+}
+
+nsresult
+SVGFEImageFrame::AttributeChanged(int32_t aNameSpaceID,
+ nsIAtom* aAttribute,
+ int32_t aModType)
+{
+ SVGFEImageElement *element = static_cast<SVGFEImageElement*>(mContent);
+ if (element->AttributeAffectsRendering(aNameSpaceID, aAttribute)) {
+ MOZ_ASSERT(GetParent()->GetType() == nsGkAtoms::svgFilterFrame,
+ "Observers observe the filter, so that's what we must invalidate");
+ nsSVGEffects::InvalidateDirectRenderingObservers(GetParent());
+ }
+ if ((aNameSpaceID == kNameSpaceID_XLink ||
+ aNameSpaceID == kNameSpaceID_None) &&
+ aAttribute == nsGkAtoms::href) {
+ bool hrefIsSet =
+ element->mStringAttributes[SVGFEImageElement::HREF].IsExplicitlySet() ||
+ element->mStringAttributes[SVGFEImageElement::XLINK_HREF]
+ .IsExplicitlySet();
+ if (hrefIsSet) {
+ element->LoadSVGImage(true, true);
+ } else {
+ element->CancelImageRequests(true);
+ }
+ }
+
+ return nsFrame::AttributeChanged(aNameSpaceID, aAttribute, aModType);
+}
+
+void
+SVGFEImageFrame::OnVisibilityChange(Visibility aNewVisibility,
+ Maybe<OnNonvisible> aNonvisibleAction)
+{
+ nsCOMPtr<nsIImageLoadingContent> imageLoader =
+ do_QueryInterface(nsFrame::mContent);
+ if (!imageLoader) {
+ MOZ_ASSERT_UNREACHABLE("Should have an nsIImageLoadingContent");
+ nsFrame::OnVisibilityChange(aNewVisibility, aNonvisibleAction);
+ return;
+ }
+
+ imageLoader->OnVisibilityChange(aNewVisibility, aNonvisibleAction);
+
+ nsFrame::OnVisibilityChange(aNewVisibility, aNonvisibleAction);
+}
diff --git a/layout/svg/SVGFELeafFrame.cpp b/layout/svg/SVGFELeafFrame.cpp
new file mode 100644
index 0000000000..d2a5fd891e
--- /dev/null
+++ b/layout/svg/SVGFELeafFrame.cpp
@@ -0,0 +1,107 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+// Keep in (case-insensitive) order:
+#include "nsContainerFrame.h"
+#include "nsFrame.h"
+#include "nsGkAtoms.h"
+#include "nsSVGEffects.h"
+#include "nsSVGFilters.h"
+
+/*
+ * This frame is used by filter primitive elements that don't
+ * have special child elements that provide parameters.
+ */
+class SVGFELeafFrame : public nsFrame
+{
+ friend nsIFrame*
+ NS_NewSVGFELeafFrame(nsIPresShell* aPresShell, nsStyleContext* aContext);
+protected:
+ explicit SVGFELeafFrame(nsStyleContext* aContext)
+ : nsFrame(aContext)
+ {
+ AddStateBits(NS_FRAME_SVG_LAYOUT | NS_FRAME_IS_NONDISPLAY);
+ }
+
+public:
+ NS_DECL_FRAMEARENA_HELPERS
+
+#ifdef DEBUG
+ virtual void Init(nsIContent* aContent,
+ nsContainerFrame* aParent,
+ nsIFrame* aPrevInFlow) override;
+#endif
+
+ virtual bool IsFrameOfType(uint32_t aFlags) const override
+ {
+ return nsFrame::IsFrameOfType(aFlags & ~(nsIFrame::eSVG));
+ }
+
+#ifdef DEBUG_FRAME_DUMP
+ virtual nsresult GetFrameName(nsAString& aResult) const override
+ {
+ return MakeFrameName(NS_LITERAL_STRING("SVGFELeaf"), aResult);
+ }
+#endif
+
+ /**
+ * Get the "type" of the frame
+ *
+ * @see nsGkAtoms::svgFELeafFrame
+ */
+ virtual nsIAtom* GetType() const override;
+
+ virtual nsresult AttributeChanged(int32_t aNameSpaceID,
+ nsIAtom* aAttribute,
+ int32_t aModType) override;
+
+ virtual bool ComputeCustomOverflow(nsOverflowAreas& aOverflowAreas) override {
+ // We don't maintain a visual overflow rect
+ return false;
+ }
+};
+
+nsIFrame*
+NS_NewSVGFELeafFrame(nsIPresShell* aPresShell, nsStyleContext* aContext)
+{
+ return new (aPresShell) SVGFELeafFrame(aContext);
+}
+
+NS_IMPL_FRAMEARENA_HELPERS(SVGFELeafFrame)
+
+#ifdef DEBUG
+void
+SVGFELeafFrame::Init(nsIContent* aContent,
+ nsContainerFrame* aParent,
+ nsIFrame* aPrevInFlow)
+{
+ NS_ASSERTION(aContent->IsNodeOfType(nsINode::eFILTER),
+ "Trying to construct an SVGFELeafFrame for a "
+ "content element that doesn't support the right interfaces");
+
+ nsFrame::Init(aContent, aParent, aPrevInFlow);
+}
+#endif /* DEBUG */
+
+nsIAtom *
+SVGFELeafFrame::GetType() const
+{
+ return nsGkAtoms::svgFELeafFrame;
+}
+
+nsresult
+SVGFELeafFrame::AttributeChanged(int32_t aNameSpaceID,
+ nsIAtom* aAttribute,
+ int32_t aModType)
+{
+ nsSVGFE *element = static_cast<nsSVGFE*>(mContent);
+ if (element->AttributeAffectsRendering(aNameSpaceID, aAttribute)) {
+ MOZ_ASSERT(GetParent()->GetType() == nsGkAtoms::svgFilterFrame,
+ "Observers observe the filter, so that's what we must invalidate");
+ nsSVGEffects::InvalidateDirectRenderingObservers(GetParent());
+ }
+
+ return nsFrame::AttributeChanged(aNameSpaceID, aAttribute, aModType);
+}
diff --git a/layout/svg/SVGFEUnstyledLeafFrame.cpp b/layout/svg/SVGFEUnstyledLeafFrame.cpp
new file mode 100644
index 0000000000..083c0f27d1
--- /dev/null
+++ b/layout/svg/SVGFEUnstyledLeafFrame.cpp
@@ -0,0 +1,87 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+// Keep in (case-insensitive) order:
+#include "nsContainerFrame.h"
+#include "nsFrame.h"
+#include "nsGkAtoms.h"
+#include "nsSVGEffects.h"
+#include "nsSVGFilters.h"
+
+class SVGFEUnstyledLeafFrame : public nsFrame
+{
+ friend nsIFrame*
+ NS_NewSVGFEUnstyledLeafFrame(nsIPresShell* aPresShell, nsStyleContext* aContext);
+protected:
+ explicit SVGFEUnstyledLeafFrame(nsStyleContext* aContext)
+ : nsFrame(aContext)
+ {
+ AddStateBits(NS_FRAME_SVG_LAYOUT | NS_FRAME_IS_NONDISPLAY);
+ }
+
+public:
+ NS_DECL_FRAMEARENA_HELPERS
+
+ virtual void BuildDisplayList(nsDisplayListBuilder* aBuilder,
+ const nsRect& aDirtyRect,
+ const nsDisplayListSet& aLists) override {}
+
+ virtual bool IsFrameOfType(uint32_t aFlags) const override
+ {
+ return nsFrame::IsFrameOfType(aFlags & ~(nsIFrame::eSVG));
+ }
+
+#ifdef DEBUG_FRAME_DUMP
+ virtual nsresult GetFrameName(nsAString& aResult) const override
+ {
+ return MakeFrameName(NS_LITERAL_STRING("SVGFEUnstyledLeaf"), aResult);
+ }
+#endif
+
+ /**
+ * Get the "type" of the frame
+ *
+ * @see nsGkAtoms::svgFEUnstyledLeafFrame
+ */
+ virtual nsIAtom* GetType() const override;
+
+ virtual nsresult AttributeChanged(int32_t aNameSpaceID,
+ nsIAtom* aAttribute,
+ int32_t aModType) override;
+
+ virtual bool ComputeCustomOverflow(nsOverflowAreas& aOverflowAreas) override {
+ // We don't maintain a visual overflow rect
+ return false;
+ }
+};
+
+nsIFrame*
+NS_NewSVGFEUnstyledLeafFrame(nsIPresShell* aPresShell, nsStyleContext* aContext)
+{
+ return new (aPresShell) SVGFEUnstyledLeafFrame(aContext);
+}
+
+NS_IMPL_FRAMEARENA_HELPERS(SVGFEUnstyledLeafFrame)
+
+nsIAtom *
+SVGFEUnstyledLeafFrame::GetType() const
+{
+ return nsGkAtoms::svgFEUnstyledLeafFrame;
+}
+
+nsresult
+SVGFEUnstyledLeafFrame::AttributeChanged(int32_t aNameSpaceID,
+ nsIAtom* aAttribute,
+ int32_t aModType)
+{
+ SVGFEUnstyledElement *element = static_cast<SVGFEUnstyledElement*>(mContent);
+ if (element->AttributeAffectsRendering(aNameSpaceID, aAttribute)) {
+ MOZ_ASSERT(GetParent()->GetParent()->GetType() == nsGkAtoms::svgFilterFrame,
+ "Observers observe the filter, so that's what we must invalidate");
+ nsSVGEffects::InvalidateDirectRenderingObservers(GetParent()->GetParent());
+ }
+
+ return nsFrame::AttributeChanged(aNameSpaceID, aAttribute, aModType);
+}
diff --git a/layout/svg/SVGImageContext.h b/layout/svg/SVGImageContext.h
new file mode 100644
index 0000000000..f1e1b94d27
--- /dev/null
+++ b/layout/svg/SVGImageContext.h
@@ -0,0 +1,88 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef MOZILLA_SVGCONTEXT_H_
+#define MOZILLA_SVGCONTEXT_H_
+
+#include "mozilla/Maybe.h"
+#include "SVGPreserveAspectRatio.h"
+#include "Units.h"
+
+namespace mozilla {
+
+// SVG image-specific rendering context. For imgIContainer::Draw.
+// Used to pass information such as
+// - viewport information from CSS, and
+// - overridden attributes from an SVG <image> element
+// to the image's internal SVG document when it's drawn.
+class SVGImageContext
+{
+public:
+ SVGImageContext()
+ : mGlobalOpacity(1.0)
+ { }
+
+ // Note: 'aIsPaintingSVGImageElement' should be used to indicate whether
+ // the SVG image in question is being painted for an SVG <image> element.
+ SVGImageContext(CSSIntSize aViewportSize,
+ Maybe<SVGPreserveAspectRatio> aPreserveAspectRatio,
+ gfxFloat aOpacity = 1.0,
+ bool aIsPaintingSVGImageElement = false)
+ : mViewportSize(aViewportSize)
+ , mPreserveAspectRatio(aPreserveAspectRatio)
+ , mGlobalOpacity(aOpacity)
+ , mIsPaintingSVGImageElement(aIsPaintingSVGImageElement)
+ { }
+
+ const CSSIntSize& GetViewportSize() const {
+ return mViewportSize;
+ }
+
+ const Maybe<SVGPreserveAspectRatio>& GetPreserveAspectRatio() const {
+ return mPreserveAspectRatio;
+ }
+
+ gfxFloat GetGlobalOpacity() const {
+ return mGlobalOpacity;
+ }
+
+ bool IsPaintingForSVGImageElement() const {
+ return mIsPaintingSVGImageElement;
+ }
+
+ bool operator==(const SVGImageContext& aOther) const {
+ return mViewportSize == aOther.mViewportSize &&
+ mPreserveAspectRatio == aOther.mPreserveAspectRatio &&
+ mGlobalOpacity == aOther.mGlobalOpacity &&
+ mIsPaintingSVGImageElement == aOther.mIsPaintingSVGImageElement;
+ }
+
+ bool operator!=(const SVGImageContext& aOther) const {
+ return !(*this == aOther);
+ }
+
+ uint32_t Hash() const {
+ return HashGeneric(mViewportSize.width,
+ mViewportSize.height,
+ mPreserveAspectRatio.map(HashPAR).valueOr(0),
+ HashBytes(&mGlobalOpacity, sizeof(gfxFloat)),
+ mIsPaintingSVGImageElement);
+ }
+
+private:
+ static uint32_t HashPAR(const SVGPreserveAspectRatio& aPAR) {
+ return aPAR.Hash();
+ }
+
+ // NOTE: When adding new member-vars, remember to update Hash() & operator==.
+ CSSIntSize mViewportSize;
+ Maybe<SVGPreserveAspectRatio> mPreserveAspectRatio;
+ gfxFloat mGlobalOpacity;
+ bool mIsPaintingSVGImageElement;
+};
+
+} // namespace mozilla
+
+#endif // MOZILLA_SVGCONTEXT_H_
diff --git a/layout/svg/SVGTextFrame.cpp b/layout/svg/SVGTextFrame.cpp
new file mode 100644
index 0000000000..e681260682
--- /dev/null
+++ b/layout/svg/SVGTextFrame.cpp
@@ -0,0 +1,5653 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+// Main header first:
+#include "SVGTextFrame.h"
+
+// Keep others in (case-insensitive) order:
+#include "DOMSVGPoint.h"
+#include "gfx2DGlue.h"
+#include "gfxFont.h"
+#include "gfxSkipChars.h"
+#include "gfxTypes.h"
+#include "gfxUtils.h"
+#include "LookAndFeel.h"
+#include "mozilla/gfx/2D.h"
+#include "mozilla/gfx/PatternHelpers.h"
+#include "mozilla/Likely.h"
+#include "nsAlgorithm.h"
+#include "nsBlockFrame.h"
+#include "nsCaret.h"
+#include "nsContentUtils.h"
+#include "nsGkAtoms.h"
+#include "nsIDOMSVGLength.h"
+#include "nsISelection.h"
+#include "nsQuickSort.h"
+#include "nsRenderingContext.h"
+#include "nsSVGEffects.h"
+#include "nsSVGOuterSVGFrame.h"
+#include "nsSVGPaintServerFrame.h"
+#include "mozilla/dom/SVGRect.h"
+#include "nsSVGIntegrationUtils.h"
+#include "nsSVGUtils.h"
+#include "nsTArray.h"
+#include "nsTextFrame.h"
+#include "nsTextNode.h"
+#include "SVGAnimatedNumberList.h"
+#include "SVGContentUtils.h"
+#include "SVGLengthList.h"
+#include "SVGNumberList.h"
+#include "SVGPathElement.h"
+#include "SVGTextPathElement.h"
+#include "nsLayoutUtils.h"
+#include "nsFrameSelection.h"
+#include "nsStyleStructInlines.h"
+#include <algorithm>
+#include <cmath>
+#include <limits>
+
+using namespace mozilla;
+using namespace mozilla::dom;
+using namespace mozilla::gfx;
+using namespace mozilla::image;
+
+// ============================================================================
+// Utility functions
+
+/**
+ * Using the specified gfxSkipCharsIterator, converts an offset and length
+ * in original char indexes to skipped char indexes.
+ *
+ * @param aIterator The gfxSkipCharsIterator to use for the conversion.
+ * @param aOriginalOffset The original offset.
+ * @param aOriginalLength The original length.
+ */
+static gfxTextRun::Range
+ConvertOriginalToSkipped(gfxSkipCharsIterator& aIterator,
+ uint32_t aOriginalOffset, uint32_t aOriginalLength)
+{
+ uint32_t start = aIterator.ConvertOriginalToSkipped(aOriginalOffset);
+ aIterator.AdvanceOriginal(aOriginalLength);
+ return gfxTextRun::Range(start, aIterator.GetSkippedOffset());
+}
+
+/**
+ * Converts an nsPoint from app units to user space units using the specified
+ * nsPresContext and returns it as a gfxPoint.
+ */
+static gfxPoint
+AppUnitsToGfxUnits(const nsPoint& aPoint, const nsPresContext* aContext)
+{
+ return gfxPoint(aContext->AppUnitsToGfxUnits(aPoint.x),
+ aContext->AppUnitsToGfxUnits(aPoint.y));
+}
+
+/**
+ * Converts a gfxRect that is in app units to CSS pixels using the specified
+ * nsPresContext and returns it as a gfxRect.
+ */
+static gfxRect
+AppUnitsToFloatCSSPixels(const gfxRect& aRect, const nsPresContext* aContext)
+{
+ return gfxRect(aContext->AppUnitsToFloatCSSPixels(aRect.x),
+ aContext->AppUnitsToFloatCSSPixels(aRect.y),
+ aContext->AppUnitsToFloatCSSPixels(aRect.width),
+ aContext->AppUnitsToFloatCSSPixels(aRect.height));
+}
+
+/**
+ * Scales a gfxRect around a given point.
+ *
+ * @param aRect The rectangle to scale.
+ * @param aPoint The point around which to scale.
+ * @param aScale The scale amount.
+ */
+static void
+ScaleAround(gfxRect& aRect, const gfxPoint& aPoint, double aScale)
+{
+ aRect.x = aPoint.x - aScale * (aPoint.x - aRect.x);
+ aRect.y = aPoint.y - aScale * (aPoint.y - aRect.y);
+ aRect.width *= aScale;
+ aRect.height *= aScale;
+}
+
+/**
+ * Returns whether a gfxPoint lies within a gfxRect.
+ */
+static bool
+Inside(const gfxRect& aRect, const gfxPoint& aPoint)
+{
+ return aPoint.x >= aRect.x &&
+ aPoint.x < aRect.XMost() &&
+ aPoint.y >= aRect.y &&
+ aPoint.y < aRect.YMost();
+}
+
+/**
+ * Gets the measured ascent and descent of the text in the given nsTextFrame
+ * in app units.
+ *
+ * @param aFrame The text frame.
+ * @param aAscent The ascent in app units (output).
+ * @param aDescent The descent in app units (output).
+ */
+static void
+GetAscentAndDescentInAppUnits(nsTextFrame* aFrame,
+ gfxFloat& aAscent, gfxFloat& aDescent)
+{
+ gfxSkipCharsIterator it = aFrame->EnsureTextRun(nsTextFrame::eInflated);
+ gfxTextRun* textRun = aFrame->GetTextRun(nsTextFrame::eInflated);
+
+ gfxTextRun::Range range = ConvertOriginalToSkipped(
+ it, aFrame->GetContentOffset(), aFrame->GetContentLength());
+
+ gfxTextRun::Metrics metrics =
+ textRun->MeasureText(range, gfxFont::LOOSE_INK_EXTENTS, nullptr, nullptr);
+
+ aAscent = metrics.mAscent;
+ aDescent = metrics.mDescent;
+}
+
+/**
+ * Updates an interval by intersecting it with another interval.
+ * The intervals are specified using a start index and a length.
+ */
+static void
+IntersectInterval(uint32_t& aStart, uint32_t& aLength,
+ uint32_t aStartOther, uint32_t aLengthOther)
+{
+ uint32_t aEnd = aStart + aLength;
+ uint32_t aEndOther = aStartOther + aLengthOther;
+
+ if (aStartOther >= aEnd || aStart >= aEndOther) {
+ aLength = 0;
+ } else {
+ if (aStartOther >= aStart)
+ aStart = aStartOther;
+ aLength = std::min(aEnd, aEndOther) - aStart;
+ }
+}
+
+/**
+ * Intersects an interval as IntersectInterval does but by taking
+ * the offset and length of the other interval from a
+ * nsTextFrame::TrimmedOffsets object.
+ */
+static void
+TrimOffsets(uint32_t& aStart, uint32_t& aLength,
+ const nsTextFrame::TrimmedOffsets& aTrimmedOffsets)
+{
+ IntersectInterval(aStart, aLength,
+ aTrimmedOffsets.mStart, aTrimmedOffsets.mLength);
+}
+
+/**
+ * Returns the closest ancestor-or-self node that is not an SVG <a>
+ * element.
+ */
+static nsIContent*
+GetFirstNonAAncestor(nsIContent* aContent)
+{
+ while (aContent && aContent->IsSVGElement(nsGkAtoms::a)) {
+ aContent = aContent->GetParent();
+ }
+ return aContent;
+}
+
+/**
+ * Returns whether the given node is a text content element[1], taking into
+ * account whether it has a valid parent.
+ *
+ * For example, in:
+ *
+ * <svg xmlns="http://www.w3.org/2000/svg">
+ * <text><a/><text/></text>
+ * <tspan/>
+ * </svg>
+ *
+ * true would be returned for the outer <text> element and the <a> element,
+ * and false for the inner <text> element (since a <text> is not allowed
+ * to be a child of another <text>) and the <tspan> element (because it
+ * must be inside a <text> subtree).
+ *
+ * Note that we don't support the <tref> element yet and this function
+ * returns false for it.
+ *
+ * [1] https://svgwg.org/svg2-draft/intro.html#TermTextContentElement
+ */
+static bool
+IsTextContentElement(nsIContent* aContent)
+{
+ if (aContent->IsSVGElement(nsGkAtoms::text)) {
+ nsIContent* parent = GetFirstNonAAncestor(aContent->GetParent());
+ return !parent || !IsTextContentElement(parent);
+ }
+
+ if (aContent->IsSVGElement(nsGkAtoms::textPath)) {
+ nsIContent* parent = GetFirstNonAAncestor(aContent->GetParent());
+ return parent && parent->IsSVGElement(nsGkAtoms::text);
+ }
+
+ if (aContent->IsAnyOfSVGElements(nsGkAtoms::a,
+ nsGkAtoms::tspan)) {
+ return true;
+ }
+
+ return false;
+}
+
+/**
+ * Returns whether the specified frame is an nsTextFrame that has some text
+ * content.
+ */
+static bool
+IsNonEmptyTextFrame(nsIFrame* aFrame)
+{
+ nsTextFrame* textFrame = do_QueryFrame(aFrame);
+ if (!textFrame) {
+ return false;
+ }
+
+ return textFrame->GetContentLength() != 0;
+}
+
+/**
+ * Takes an nsIFrame and if it is a text frame that has some text content,
+ * returns it as an nsTextFrame and its corresponding nsTextNode.
+ *
+ * @param aFrame The frame to look at.
+ * @param aTextFrame aFrame as an nsTextFrame (output).
+ * @param aTextNode The nsTextNode content of aFrame (output).
+ * @return true if aFrame is a non-empty text frame, false otherwise.
+ */
+static bool
+GetNonEmptyTextFrameAndNode(nsIFrame* aFrame,
+ nsTextFrame*& aTextFrame,
+ nsTextNode*& aTextNode)
+{
+ nsTextFrame* text = do_QueryFrame(aFrame);
+ bool isNonEmptyTextFrame = text && text->GetContentLength() != 0;
+
+ if (isNonEmptyTextFrame) {
+ nsIContent* content = text->GetContent();
+ NS_ASSERTION(content && content->IsNodeOfType(nsINode::eTEXT),
+ "unexpected content type for nsTextFrame");
+
+ nsTextNode* node = static_cast<nsTextNode*>(content);
+ MOZ_ASSERT(node->TextLength() != 0,
+ "frame's GetContentLength() should be 0 if the text node "
+ "has no content");
+
+ aTextFrame = text;
+ aTextNode = node;
+ }
+
+ MOZ_ASSERT(IsNonEmptyTextFrame(aFrame) == isNonEmptyTextFrame,
+ "our logic should agree with IsNonEmptyTextFrame");
+ return isNonEmptyTextFrame;
+}
+
+/**
+ * Returns whether the specified atom is for one of the five
+ * glyph positioning attributes that can appear on SVG text
+ * elements -- x, y, dx, dy or rotate.
+ */
+static bool
+IsGlyphPositioningAttribute(nsIAtom* aAttribute)
+{
+ return aAttribute == nsGkAtoms::x ||
+ aAttribute == nsGkAtoms::y ||
+ aAttribute == nsGkAtoms::dx ||
+ aAttribute == nsGkAtoms::dy ||
+ aAttribute == nsGkAtoms::rotate;
+}
+
+/**
+ * Returns the position in app units of a given baseline (using an
+ * SVG dominant-baseline property value) for a given nsTextFrame.
+ *
+ * @param aFrame The text frame to inspect.
+ * @param aTextRun The text run of aFrame.
+ * @param aDominantBaseline The dominant-baseline value to use.
+ */
+static nscoord
+GetBaselinePosition(nsTextFrame* aFrame,
+ gfxTextRun* aTextRun,
+ uint8_t aDominantBaseline,
+ float aFontSizeScaleFactor)
+{
+ WritingMode writingMode = aFrame->GetWritingMode();
+ gfxTextRun::Metrics metrics =
+ aTextRun->MeasureText(gfxFont::LOOSE_INK_EXTENTS, nullptr);
+
+ switch (aDominantBaseline) {
+ case NS_STYLE_DOMINANT_BASELINE_HANGING:
+ case NS_STYLE_DOMINANT_BASELINE_TEXT_BEFORE_EDGE:
+ return writingMode.IsVerticalRL()
+ ? metrics.mAscent + metrics.mDescent : 0;
+
+ case NS_STYLE_DOMINANT_BASELINE_USE_SCRIPT:
+ case NS_STYLE_DOMINANT_BASELINE_NO_CHANGE:
+ case NS_STYLE_DOMINANT_BASELINE_RESET_SIZE:
+ // These three should not simply map to 'baseline', but we don't
+ // support the complex baseline model that SVG 1.1 has and which
+ // css3-linebox now defines.
+ // (fall through)
+
+ case NS_STYLE_DOMINANT_BASELINE_AUTO:
+ case NS_STYLE_DOMINANT_BASELINE_ALPHABETIC:
+ return writingMode.IsVerticalRL()
+ ? metrics.mAscent + metrics.mDescent -
+ aFrame->GetLogicalBaseline(writingMode)
+ : aFrame->GetLogicalBaseline(writingMode);
+
+ case NS_STYLE_DOMINANT_BASELINE_MIDDLE:
+ return aFrame->GetLogicalBaseline(writingMode) -
+ SVGContentUtils::GetFontXHeight(aFrame) / 2.0 *
+ aFrame->PresContext()->AppUnitsPerCSSPixel() * aFontSizeScaleFactor;
+
+ case NS_STYLE_DOMINANT_BASELINE_TEXT_AFTER_EDGE:
+ case NS_STYLE_DOMINANT_BASELINE_IDEOGRAPHIC:
+ return writingMode.IsVerticalLR()
+ ? 0 : metrics.mAscent + metrics.mDescent;
+
+ case NS_STYLE_DOMINANT_BASELINE_CENTRAL:
+ case NS_STYLE_DOMINANT_BASELINE_MATHEMATICAL:
+ return (metrics.mAscent + metrics.mDescent) / 2.0;
+ }
+
+ NS_NOTREACHED("unexpected dominant-baseline value");
+ return aFrame->GetLogicalBaseline(writingMode);
+}
+
+/**
+ * For a given text run, returns the range of skipped characters that comprise
+ * the ligature group and/or cluster that includes the character represented
+ * by the specified gfxSkipCharsIterator.
+ *
+ * @param aTextRun The text run to use for determining whether a given character
+ * is part of a ligature or cluster.
+ * @param aIterator The gfxSkipCharsIterator to use for the current position
+ * in the text run.
+ */
+static gfxTextRun::Range
+ClusterRange(gfxTextRun* aTextRun, const gfxSkipCharsIterator& aIterator)
+{
+ uint32_t start = aIterator.GetSkippedOffset();
+ uint32_t end = start + 1;
+ while (end < aTextRun->GetLength() &&
+ (!aTextRun->IsLigatureGroupStart(end) ||
+ !aTextRun->IsClusterStart(end))) {
+ end++;
+ }
+ return gfxTextRun::Range(start, end);
+}
+
+/**
+ * Truncates an array to be at most the length of another array.
+ *
+ * @param aArrayToTruncate The array to truncate.
+ * @param aReferenceArray The array whose length will be used to truncate
+ * aArrayToTruncate to.
+ */
+template<typename T, typename U>
+static void
+TruncateTo(nsTArray<T>& aArrayToTruncate, const nsTArray<U>& aReferenceArray)
+{
+ uint32_t length = aReferenceArray.Length();
+ if (aArrayToTruncate.Length() > length) {
+ aArrayToTruncate.TruncateLength(length);
+ }
+}
+
+/**
+ * Asserts that the anonymous block child of the SVGTextFrame has been
+ * reflowed (or does not exist). Returns null if the child has not been
+ * reflowed, and the frame otherwise.
+ *
+ * We check whether the kid has been reflowed and not the frame itself
+ * since we sometimes need to call this function during reflow, after the
+ * kid has been reflowed but before we have cleared the dirty bits on the
+ * frame itself.
+ */
+static SVGTextFrame*
+FrameIfAnonymousChildReflowed(SVGTextFrame* aFrame)
+{
+ NS_PRECONDITION(aFrame, "aFrame must not be null");
+ nsIFrame* kid = aFrame->PrincipalChildList().FirstChild();
+ if (NS_SUBTREE_DIRTY(kid)) {
+ MOZ_ASSERT(false, "should have already reflowed the anonymous block child");
+ return nullptr;
+ }
+ return aFrame;
+}
+
+static double
+GetContextScale(const gfxMatrix& aMatrix)
+{
+ // The context scale is the ratio of the length of the transformed
+ // diagonal vector (1,1) to the length of the untransformed diagonal
+ // (which is sqrt(2)).
+ gfxPoint p = aMatrix.Transform(gfxPoint(1, 1)) -
+ aMatrix.Transform(gfxPoint(0, 0));
+ return SVGContentUtils::ComputeNormalizedHypotenuse(p.x, p.y);
+}
+
+// ============================================================================
+// Utility classes
+
+namespace mozilla {
+
+// ----------------------------------------------------------------------------
+// TextRenderedRun
+
+/**
+ * A run of text within a single nsTextFrame whose glyphs can all be painted
+ * with a single call to nsTextFrame::PaintText. A text rendered run can
+ * be created for a sequence of two or more consecutive glyphs as long as:
+ *
+ * - Only the first glyph has (or none of the glyphs have) been positioned
+ * with SVG text positioning attributes
+ * - All of the glyphs have zero rotation
+ * - The glyphs are not on a text path
+ * - The glyphs correspond to content within the one nsTextFrame
+ *
+ * A TextRenderedRunIterator produces TextRenderedRuns required for painting a
+ * whole SVGTextFrame.
+ */
+struct TextRenderedRun
+{
+ typedef gfxTextRun::Range Range;
+
+ /**
+ * Constructs a TextRenderedRun that is uninitialized except for mFrame
+ * being null.
+ */
+ TextRenderedRun()
+ : mFrame(nullptr)
+ {
+ }
+
+ /**
+ * Constructs a TextRenderedRun with all of the information required to
+ * paint it. See the comments documenting the member variables below
+ * for descriptions of the arguments.
+ */
+ TextRenderedRun(nsTextFrame* aFrame, const gfxPoint& aPosition,
+ float aLengthAdjustScaleFactor, double aRotate,
+ float aFontSizeScaleFactor, nscoord aBaseline,
+ uint32_t aTextFrameContentOffset,
+ uint32_t aTextFrameContentLength,
+ uint32_t aTextElementCharIndex)
+ : mFrame(aFrame),
+ mPosition(aPosition),
+ mLengthAdjustScaleFactor(aLengthAdjustScaleFactor),
+ mRotate(static_cast<float>(aRotate)),
+ mFontSizeScaleFactor(aFontSizeScaleFactor),
+ mBaseline(aBaseline),
+ mTextFrameContentOffset(aTextFrameContentOffset),
+ mTextFrameContentLength(aTextFrameContentLength),
+ mTextElementCharIndex(aTextElementCharIndex)
+ {
+ }
+
+ /**
+ * Returns the text run for the text frame that this rendered run is part of.
+ */
+ gfxTextRun* GetTextRun() const
+ {
+ mFrame->EnsureTextRun(nsTextFrame::eInflated);
+ return mFrame->GetTextRun(nsTextFrame::eInflated);
+ }
+
+ /**
+ * Returns whether this rendered run is RTL.
+ */
+ bool IsRightToLeft() const
+ {
+ return GetTextRun()->IsRightToLeft();
+ }
+
+ /**
+ * Returns whether this rendered run is vertical.
+ */
+ bool IsVertical() const
+ {
+ return GetTextRun()->IsVertical();
+ }
+
+ /**
+ * Returns the transform that converts from a <text> element's user space into
+ * the coordinate space that rendered runs can be painted directly in.
+ *
+ * The difference between this method and GetTransformFromRunUserSpaceToUserSpace
+ * is that when calling in to nsTextFrame::PaintText, it will already take
+ * into account any left clip edge (that is, it doesn't just apply a visual
+ * clip to the rendered text, it shifts the glyphs over so that they are
+ * painted with their left edge at the x coordinate passed in to it).
+ * Thus we need to account for this in our transform.
+ *
+ *
+ * Assume that we have <text x="100" y="100" rotate="0 0 1 0 0 1">abcdef</text>.
+ * This would result in four text rendered runs:
+ *
+ * - one for "ab"
+ * - one for "c"
+ * - one for "de"
+ * - one for "f"
+ *
+ * Assume now that we are painting the third TextRenderedRun. It will have
+ * a left clip edge that is the sum of the advances of "abc", and it will
+ * have a right clip edge that is the advance of "f". In
+ * SVGTextFrame::PaintSVG(), we pass in nsPoint() (i.e., the origin)
+ * as the point at which to paint the text frame, and we pass in the
+ * clip edge values. The nsTextFrame will paint the substring of its
+ * text such that the top-left corner of the "d"'s glyph cell will be at
+ * (0, 0) in the current coordinate system.
+ *
+ * Thus, GetTransformFromUserSpaceForPainting must return a transform from
+ * whatever user space the <text> element is in to a coordinate space in
+ * device pixels (as that's what nsTextFrame works in) where the origin is at
+ * the same position as our user space mPositions[i].mPosition value for
+ * the "d" glyph, which will be (100 + userSpaceAdvance("abc"), 100).
+ * The translation required to do this (ignoring the scale to get from
+ * user space to device pixels, and ignoring the
+ * (100 + userSpaceAdvance("abc"), 100) translation) is:
+ *
+ * (-leftEdge, -baseline)
+ *
+ * where baseline is the distance between the baseline of the text and the top
+ * edge of the nsTextFrame. We translate by -leftEdge horizontally because
+ * the nsTextFrame will already shift the glyphs over by that amount and start
+ * painting glyphs at x = 0. We translate by -baseline vertically so that
+ * painting the top edges of the glyphs at y = 0 will result in their
+ * baselines being at our desired y position.
+ *
+ *
+ * Now for an example with RTL text. Assume our content is now
+ * <text x="100" y="100" rotate="0 0 1 0 0 1">WERBEH</text>. We'd have
+ * the following text rendered runs:
+ *
+ * - one for "EH"
+ * - one for "B"
+ * - one for "ER"
+ * - one for "W"
+ *
+ * Again, we are painting the third TextRenderedRun. The left clip edge
+ * is the advance of the "W" and the right clip edge is the sum of the
+ * advances of "BEH". Our translation to get the rendered "ER" glyphs
+ * in the right place this time is:
+ *
+ * (-frameWidth + rightEdge, -baseline)
+ *
+ * which is equivalent to:
+ *
+ * (-(leftEdge + advance("ER")), -baseline)
+ *
+ * The reason we have to shift left additionally by the width of the run
+ * of glyphs we are painting is that although the nsTextFrame is RTL,
+ * we still supply the top-left corner to paint the frame at when calling
+ * nsTextFrame::PaintText, even though our user space positions for each
+ * glyph in mPositions specifies the origin of each glyph, which for RTL
+ * glyphs is at the right edge of the glyph cell.
+ *
+ *
+ * For any other use of an nsTextFrame in the context of a particular run
+ * (such as hit testing, or getting its rectangle),
+ * GetTransformFromRunUserSpaceToUserSpace should be used.
+ *
+ * @param aContext The context to use for unit conversions.
+ * @param aItem The nsCharClipDisplayItem that holds the amount of clipping
+ * from the left and right edges of the text frame for this rendered run.
+ * An appropriate nsCharClipDisplayItem can be obtained by constructing an
+ * SVGCharClipDisplayItem for the TextRenderedRun.
+ */
+ gfxMatrix GetTransformFromUserSpaceForPainting(
+ nsPresContext* aContext,
+ const nsCharClipDisplayItem& aItem) const;
+
+ /**
+ * Returns the transform that converts from "run user space" to a <text>
+ * element's user space. Run user space is a coordinate system that has the
+ * same size as the <text>'s user space but rotated and translated such that
+ * (0,0) is the top-left of the rectangle that bounds the text.
+ *
+ * @param aContext The context to use for unit conversions.
+ */
+ gfxMatrix GetTransformFromRunUserSpaceToUserSpace(nsPresContext* aContext) const;
+
+ /**
+ * Returns the transform that converts from "run user space" to float pixels
+ * relative to the nsTextFrame that this rendered run is a part of.
+ *
+ * @param aContext The context to use for unit conversions.
+ */
+ gfxMatrix GetTransformFromRunUserSpaceToFrameUserSpace(nsPresContext* aContext) const;
+
+ /**
+ * Flag values used for the aFlags arguments of GetRunUserSpaceRect,
+ * GetFrameUserSpaceRect and GetUserSpaceRect.
+ */
+ enum {
+ // Includes the fill geometry of the text in the returned rectangle.
+ eIncludeFill = 1,
+ // Includes the stroke geometry of the text in the returned rectangle.
+ eIncludeStroke = 2,
+ // Includes any text shadow in the returned rectangle.
+ eIncludeTextShadow = 4,
+ // Don't include any horizontal glyph overflow in the returned rectangle.
+ eNoHorizontalOverflow = 8
+ };
+
+ /**
+ * Returns a rectangle that bounds the fill and/or stroke of the rendered run
+ * in run user space.
+ *
+ * @param aContext The context to use for unit conversions.
+ * @param aFlags A combination of the flags above (eIncludeFill and
+ * eIncludeStroke) indicating what parts of the text to include in
+ * the rectangle.
+ */
+ SVGBBox GetRunUserSpaceRect(nsPresContext* aContext, uint32_t aFlags) const;
+
+ /**
+ * Returns a rectangle that covers the fill and/or stroke of the rendered run
+ * in "frame user space".
+ *
+ * Frame user space is a coordinate space of the same scale as the <text>
+ * element's user space, but with its rotation set to the rotation of
+ * the glyphs within this rendered run and its origin set to the position
+ * such that placing the nsTextFrame there would result in the glyphs in
+ * this rendered run being at their correct positions.
+ *
+ * For example, say we have <text x="100 150" y="100">ab</text>. Assume
+ * the advance of both the "a" and the "b" is 12 user units, and the
+ * ascent of the text is 8 user units and its descent is 6 user units,
+ * and that we are not measuing the stroke of the text, so that we stay
+ * entirely within the glyph cells.
+ *
+ * There will be two text rendered runs, one for "a" and one for "b".
+ *
+ * The frame user space for the "a" run will have its origin at
+ * (100, 100 - 8) in the <text> element's user space and will have its
+ * axes aligned with the user space (since there is no rotate="" or
+ * text path involve) and with its scale the same as the user space.
+ * The rect returned by this method will be (0, 0, 12, 14), since the "a"
+ * glyph is right at the left of the nsTextFrame.
+ *
+ * The frame user space for the "b" run will have its origin at
+ * (150 - 12, 100 - 8), and scale/rotation the same as above. The rect
+ * returned by this method will be (12, 0, 12, 14), since we are
+ * advance("a") horizontally in to the text frame.
+ *
+ * @param aContext The context to use for unit conversions.
+ * @param aFlags A combination of the flags above (eIncludeFill and
+ * eIncludeStroke) indicating what parts of the text to include in
+ * the rectangle.
+ */
+ SVGBBox GetFrameUserSpaceRect(nsPresContext* aContext, uint32_t aFlags) const;
+
+ /**
+ * Returns a rectangle that covers the fill and/or stroke of the rendered run
+ * in the <text> element's user space.
+ *
+ * @param aContext The context to use for unit conversions.
+ * @param aFlags A combination of the flags above indicating what parts of the
+ * text to include in the rectangle.
+ * @param aAdditionalTransform An additional transform to apply to the
+ * frame user space rectangle before its bounds are transformed into
+ * user space.
+ */
+ SVGBBox GetUserSpaceRect(nsPresContext* aContext, uint32_t aFlags,
+ const gfxMatrix* aAdditionalTransform = nullptr) const;
+
+ /**
+ * Gets the app unit amounts to clip from the left and right edges of
+ * the nsTextFrame in order to paint just this rendered run.
+ *
+ * Note that if clip edge amounts land in the middle of a glyph, the
+ * glyph won't be painted at all. The clip edges are thus more of
+ * a selection mechanism for which glyphs will be painted, rather
+ * than a geometric clip.
+ */
+ void GetClipEdges(nscoord& aVisIStartEdge, nscoord& aVisIEndEdge) const;
+
+ /**
+ * Returns the advance width of the whole rendered run.
+ */
+ nscoord GetAdvanceWidth() const;
+
+ /**
+ * Returns the index of the character into this rendered run whose
+ * glyph cell contains the given point, or -1 if there is no such
+ * character. This does not hit test against any overflow.
+ *
+ * @param aContext The context to use for unit conversions.
+ * @param aPoint The point in the user space of the <text> element.
+ */
+ int32_t GetCharNumAtPosition(nsPresContext* aContext,
+ const gfxPoint& aPoint) const;
+
+ /**
+ * The text frame that this rendered run lies within.
+ */
+ nsTextFrame* mFrame;
+
+ /**
+ * The point in user space that the text is positioned at.
+ *
+ * For a horizontal run:
+ * The x coordinate is the left edge of a LTR run of text or the right edge of
+ * an RTL run. The y coordinate is the baseline of the text.
+ * For a vertical run:
+ * The x coordinate is the baseline of the text.
+ * The y coordinate is the top edge of a LTR run, or bottom of RTL.
+ */
+ gfxPoint mPosition;
+
+ /**
+ * The horizontal scale factor to apply when painting glyphs to take
+ * into account textLength="".
+ */
+ float mLengthAdjustScaleFactor;
+
+ /**
+ * The rotation in radians in the user coordinate system that the text has.
+ */
+ float mRotate;
+
+ /**
+ * The scale factor that was used to transform the text run's original font
+ * size into a sane range for painting and measurement.
+ */
+ double mFontSizeScaleFactor;
+
+ /**
+ * The baseline in app units of this text run. The measurement is from the
+ * top of the text frame. (From the left edge if vertical.)
+ */
+ nscoord mBaseline;
+
+ /**
+ * The offset and length in mFrame's content nsTextNode that corresponds to
+ * this text rendered run. These are original char indexes.
+ */
+ uint32_t mTextFrameContentOffset;
+ uint32_t mTextFrameContentLength;
+
+ /**
+ * The character index in the whole SVG <text> element that this text rendered
+ * run begins at.
+ */
+ uint32_t mTextElementCharIndex;
+};
+
+gfxMatrix
+TextRenderedRun::GetTransformFromUserSpaceForPainting(
+ nsPresContext* aContext,
+ const nsCharClipDisplayItem& aItem) const
+{
+ // We transform to device pixels positioned such that painting the text frame
+ // at (0,0) with aItem will result in the text being in the right place.
+
+ gfxMatrix m;
+ if (!mFrame) {
+ return m;
+ }
+
+ float cssPxPerDevPx = aContext->
+ AppUnitsToFloatCSSPixels(aContext->AppUnitsPerDevPixel());
+
+ // Glyph position in user space.
+ m.Translate(mPosition / cssPxPerDevPx);
+
+ // Take into account any font size scaling and scaling due to textLength="".
+ m.Scale(1.0 / mFontSizeScaleFactor, 1.0 / mFontSizeScaleFactor);
+
+ // Rotation due to rotate="" or a <textPath>.
+ m.Rotate(mRotate);
+
+ m.Scale(mLengthAdjustScaleFactor, 1.0);
+
+ // Translation to get the text frame in the right place.
+ nsPoint t;
+ if (IsVertical()) {
+ t = nsPoint(-mBaseline,
+ IsRightToLeft()
+ ? -mFrame->GetRect().height + aItem.mVisIEndEdge
+ : -aItem.mVisIStartEdge);
+ } else {
+ t = nsPoint(IsRightToLeft()
+ ? -mFrame->GetRect().width + aItem.mVisIEndEdge
+ : -aItem.mVisIStartEdge,
+ -mBaseline);
+ }
+ m.Translate(AppUnitsToGfxUnits(t, aContext));
+
+ return m;
+}
+
+gfxMatrix
+TextRenderedRun::GetTransformFromRunUserSpaceToUserSpace(
+ nsPresContext* aContext) const
+{
+ gfxMatrix m;
+ if (!mFrame) {
+ return m;
+ }
+
+ float cssPxPerDevPx = aContext->
+ AppUnitsToFloatCSSPixels(aContext->AppUnitsPerDevPixel());
+
+ nscoord start, end;
+ GetClipEdges(start, end);
+
+ // Glyph position in user space.
+ m.Translate(mPosition);
+
+ // Rotation due to rotate="" or a <textPath>.
+ m.Rotate(mRotate);
+
+ // Scale due to textLength="".
+ m.Scale(mLengthAdjustScaleFactor, 1.0);
+
+ // Translation to get the text frame in the right place.
+ nsPoint t;
+ if (IsVertical()) {
+ t = nsPoint(-mBaseline,
+ IsRightToLeft()
+ ? -mFrame->GetRect().height + start + end
+ : 0);
+ } else {
+ t = nsPoint(IsRightToLeft()
+ ? -mFrame->GetRect().width + start + end
+ : 0,
+ -mBaseline);
+ }
+ m.Translate(AppUnitsToGfxUnits(t, aContext) *
+ cssPxPerDevPx / mFontSizeScaleFactor);
+
+ return m;
+}
+
+gfxMatrix
+TextRenderedRun::GetTransformFromRunUserSpaceToFrameUserSpace(
+ nsPresContext* aContext) const
+{
+ gfxMatrix m;
+ if (!mFrame) {
+ return m;
+ }
+
+ nscoord start, end;
+ GetClipEdges(start, end);
+
+ // Translate by the horizontal distance into the text frame this
+ // rendered run is.
+ gfxFloat appPerCssPx = aContext->AppUnitsPerCSSPixel();
+ gfxPoint t = IsVertical() ? gfxPoint(0, start / appPerCssPx)
+ : gfxPoint(start / appPerCssPx, 0);
+ return m.Translate(t);
+}
+
+SVGBBox
+TextRenderedRun::GetRunUserSpaceRect(nsPresContext* aContext,
+ uint32_t aFlags) const
+{
+ SVGBBox r;
+ if (!mFrame) {
+ return r;
+ }
+
+ // Determine the amount of overflow above and below the frame's mRect.
+ //
+ // We need to call GetVisualOverflowRectRelativeToSelf because this includes
+ // overflowing decorations, which the MeasureText call below does not. We
+ // assume here the decorations only overflow above and below the frame, never
+ // horizontally.
+ nsRect self = mFrame->GetVisualOverflowRectRelativeToSelf();
+ nsRect rect = mFrame->GetRect();
+ bool vertical = IsVertical();
+ nscoord above = vertical ? -self.x : -self.y;
+ nscoord below = vertical ? self.XMost() - rect.width
+ : self.YMost() - rect.height;
+
+ gfxSkipCharsIterator it = mFrame->EnsureTextRun(nsTextFrame::eInflated);
+ gfxTextRun* textRun = mFrame->GetTextRun(nsTextFrame::eInflated);
+
+ // Get the content range for this rendered run.
+ Range range = ConvertOriginalToSkipped(it, mTextFrameContentOffset,
+ mTextFrameContentLength);
+ if (range.Length() == 0) {
+ return r;
+ }
+
+ // Measure that range.
+ gfxTextRun::Metrics metrics =
+ textRun->MeasureText(range, gfxFont::LOOSE_INK_EXTENTS, nullptr, nullptr);
+ // Make sure it includes the font-box.
+ gfxRect fontBox(0, -metrics.mAscent,
+ metrics.mAdvanceWidth, metrics.mAscent + metrics.mDescent);
+ metrics.mBoundingBox.UnionRect(metrics.mBoundingBox, fontBox);
+
+ // Determine the rectangle that covers the rendered run's fill,
+ // taking into account the measured vertical overflow due to
+ // decorations.
+ nscoord baseline = metrics.mBoundingBox.y + metrics.mAscent;
+ gfxFloat x, width;
+ if (aFlags & eNoHorizontalOverflow) {
+ x = 0.0;
+ width = textRun->GetAdvanceWidth(range, nullptr);
+ } else {
+ x = metrics.mBoundingBox.x;
+ width = metrics.mBoundingBox.width;
+ }
+ nsRect fillInAppUnits(x, baseline - above,
+ width, metrics.mBoundingBox.height + above + below);
+ if (textRun->IsVertical()) {
+ // Swap line-relative textMetrics dimensions to physical coordinates.
+ Swap(fillInAppUnits.x, fillInAppUnits.y);
+ Swap(fillInAppUnits.width, fillInAppUnits.height);
+ }
+
+ // Account for text-shadow.
+ if (aFlags & eIncludeTextShadow) {
+ fillInAppUnits =
+ nsLayoutUtils::GetTextShadowRectsUnion(fillInAppUnits, mFrame);
+ }
+
+ // Convert the app units rectangle to user units.
+ gfxRect fill = AppUnitsToFloatCSSPixels(gfxRect(fillInAppUnits.x,
+ fillInAppUnits.y,
+ fillInAppUnits.width,
+ fillInAppUnits.height),
+ aContext);
+
+ // Scale the rectangle up due to any mFontSizeScaleFactor. We scale
+ // it around the text's origin.
+ ScaleAround(fill,
+ textRun->IsVertical()
+ ? gfxPoint(aContext->AppUnitsToFloatCSSPixels(baseline), 0.0)
+ : gfxPoint(0.0, aContext->AppUnitsToFloatCSSPixels(baseline)),
+ 1.0 / mFontSizeScaleFactor);
+
+ // Include the fill if requested.
+ if (aFlags & eIncludeFill) {
+ r = fill;
+ }
+
+ // Include the stroke if requested.
+ if ((aFlags & eIncludeStroke) &&
+ !fill.IsEmpty() &&
+ nsSVGUtils::GetStrokeWidth(mFrame) > 0) {
+ r.UnionEdges(nsSVGUtils::PathExtentsToMaxStrokeExtents(fill, mFrame,
+ gfxMatrix()));
+ }
+
+ return r;
+}
+
+SVGBBox
+TextRenderedRun::GetFrameUserSpaceRect(nsPresContext* aContext,
+ uint32_t aFlags) const
+{
+ SVGBBox r = GetRunUserSpaceRect(aContext, aFlags);
+ if (r.IsEmpty()) {
+ return r;
+ }
+ gfxMatrix m = GetTransformFromRunUserSpaceToFrameUserSpace(aContext);
+ return m.TransformBounds(r.ToThebesRect());
+}
+
+SVGBBox
+TextRenderedRun::GetUserSpaceRect(nsPresContext* aContext,
+ uint32_t aFlags,
+ const gfxMatrix* aAdditionalTransform) const
+{
+ SVGBBox r = GetRunUserSpaceRect(aContext, aFlags);
+ if (r.IsEmpty()) {
+ return r;
+ }
+ gfxMatrix m = GetTransformFromRunUserSpaceToUserSpace(aContext);
+ if (aAdditionalTransform) {
+ m *= *aAdditionalTransform;
+ }
+ return m.TransformBounds(r.ToThebesRect());
+}
+
+void
+TextRenderedRun::GetClipEdges(nscoord& aVisIStartEdge,
+ nscoord& aVisIEndEdge) const
+{
+ uint32_t contentLength = mFrame->GetContentLength();
+ if (mTextFrameContentOffset == 0 &&
+ mTextFrameContentLength == contentLength) {
+ // If the rendered run covers the entire content, we know we don't need
+ // to clip without having to measure anything.
+ aVisIStartEdge = 0;
+ aVisIEndEdge = 0;
+ return;
+ }
+
+ gfxSkipCharsIterator it = mFrame->EnsureTextRun(nsTextFrame::eInflated);
+ gfxTextRun* textRun = mFrame->GetTextRun(nsTextFrame::eInflated);
+
+ // Get the covered content offset/length for this rendered run in skipped
+ // characters, since that is what GetAdvanceWidth expects.
+ Range runRange = ConvertOriginalToSkipped(it, mTextFrameContentOffset,
+ mTextFrameContentLength);
+
+ // Get the offset/length of the whole nsTextFrame.
+ uint32_t frameOffset = mFrame->GetContentOffset();
+ uint32_t frameLength = mFrame->GetContentLength();
+
+ // Trim the whole-nsTextFrame offset/length to remove any leading/trailing
+ // white space, as the nsTextFrame when painting does not include them when
+ // interpreting clip edges.
+ nsTextFrame::TrimmedOffsets trimmedOffsets =
+ mFrame->GetTrimmedOffsets(mFrame->GetContent()->GetText(), true);
+ TrimOffsets(frameOffset, frameLength, trimmedOffsets);
+
+ // Convert the trimmed whole-nsTextFrame offset/length into skipped
+ // characters.
+ Range frameRange = ConvertOriginalToSkipped(it, frameOffset, frameLength);
+
+ // Measure the advance width in the text run between the start of
+ // frame's content and the start of the rendered run's content,
+ nscoord startEdge = textRun->
+ GetAdvanceWidth(Range(frameRange.start, runRange.start), nullptr);
+
+ // and between the end of the rendered run's content and the end
+ // of the frame's content.
+ nscoord endEdge = textRun->
+ GetAdvanceWidth(Range(runRange.end, frameRange.end), nullptr);
+
+ if (textRun->IsRightToLeft()) {
+ aVisIStartEdge = endEdge;
+ aVisIEndEdge = startEdge;
+ } else {
+ aVisIStartEdge = startEdge;
+ aVisIEndEdge = endEdge;
+ }
+}
+
+nscoord
+TextRenderedRun::GetAdvanceWidth() const
+{
+ gfxSkipCharsIterator it = mFrame->EnsureTextRun(nsTextFrame::eInflated);
+ gfxTextRun* textRun = mFrame->GetTextRun(nsTextFrame::eInflated);
+
+ Range range = ConvertOriginalToSkipped(it, mTextFrameContentOffset,
+ mTextFrameContentLength);
+
+ return textRun->GetAdvanceWidth(range, nullptr);
+}
+
+int32_t
+TextRenderedRun::GetCharNumAtPosition(nsPresContext* aContext,
+ const gfxPoint& aPoint) const
+{
+ if (mTextFrameContentLength == 0) {
+ return -1;
+ }
+
+ float cssPxPerDevPx = aContext->
+ AppUnitsToFloatCSSPixels(aContext->AppUnitsPerDevPixel());
+
+ // Convert the point from user space into run user space, and take
+ // into account any mFontSizeScaleFactor.
+ gfxMatrix m = GetTransformFromRunUserSpaceToUserSpace(aContext);
+ if (!m.Invert()) {
+ return -1;
+ }
+ gfxPoint p = m.Transform(aPoint) / cssPxPerDevPx * mFontSizeScaleFactor;
+
+ // First check that the point lies vertically between the top and bottom
+ // edges of the text.
+ gfxFloat ascent, descent;
+ GetAscentAndDescentInAppUnits(mFrame, ascent, descent);
+
+ WritingMode writingMode = mFrame->GetWritingMode();
+ if (writingMode.IsVertical()) {
+ gfxFloat leftEdge =
+ mFrame->GetLogicalBaseline(writingMode) -
+ (writingMode.IsVerticalRL() ? ascent : descent);
+ gfxFloat rightEdge = leftEdge + ascent + descent;
+ if (p.x < aContext->AppUnitsToGfxUnits(leftEdge) ||
+ p.x > aContext->AppUnitsToGfxUnits(rightEdge)) {
+ return -1;
+ }
+ } else {
+ gfxFloat topEdge = mFrame->GetLogicalBaseline(writingMode) - ascent;
+ gfxFloat bottomEdge = topEdge + ascent + descent;
+ if (p.y < aContext->AppUnitsToGfxUnits(topEdge) ||
+ p.y > aContext->AppUnitsToGfxUnits(bottomEdge)) {
+ return -1;
+ }
+ }
+
+ gfxSkipCharsIterator it = mFrame->EnsureTextRun(nsTextFrame::eInflated);
+ gfxTextRun* textRun = mFrame->GetTextRun(nsTextFrame::eInflated);
+
+ // Next check that the point lies horizontally within the left and right
+ // edges of the text.
+ Range range = ConvertOriginalToSkipped(it, mTextFrameContentOffset,
+ mTextFrameContentLength);
+ gfxFloat runAdvance =
+ aContext->AppUnitsToGfxUnits(textRun->GetAdvanceWidth(range, nullptr));
+
+ gfxFloat pos = writingMode.IsVertical() ? p.y : p.x;
+ if (pos < 0 || pos >= runAdvance) {
+ return -1;
+ }
+
+ // Finally, measure progressively smaller portions of the rendered run to
+ // find which glyph it lies within. This will need to change once we
+ // support letter-spacing and word-spacing.
+ bool rtl = textRun->IsRightToLeft();
+ for (int32_t i = mTextFrameContentLength - 1; i >= 0; i--) {
+ range = ConvertOriginalToSkipped(it, mTextFrameContentOffset, i);
+ gfxFloat advance =
+ aContext->AppUnitsToGfxUnits(textRun->GetAdvanceWidth(range, nullptr));
+ if ((rtl && pos < runAdvance - advance) ||
+ (!rtl && pos >= advance)) {
+ return i;
+ }
+ }
+ return -1;
+}
+
+// ----------------------------------------------------------------------------
+// TextNodeIterator
+
+enum SubtreePosition
+{
+ eBeforeSubtree,
+ eWithinSubtree,
+ eAfterSubtree
+};
+
+/**
+ * An iterator class for nsTextNodes that are descendants of a given node, the
+ * root. Nodes are iterated in document order. An optional subtree can be
+ * specified, in which case the iterator will track whether the current state of
+ * the traversal over the tree is within that subtree or is past that subtree.
+ */
+class TextNodeIterator
+{
+public:
+ /**
+ * Constructs a TextNodeIterator with the specified root node and optional
+ * subtree.
+ */
+ explicit TextNodeIterator(nsIContent* aRoot, nsIContent* aSubtree = nullptr)
+ : mRoot(aRoot),
+ mSubtree(aSubtree == aRoot ? nullptr : aSubtree),
+ mCurrent(aRoot),
+ mSubtreePosition(mSubtree ? eBeforeSubtree : eWithinSubtree)
+ {
+ NS_ASSERTION(aRoot, "expected non-null root");
+ if (!aRoot->IsNodeOfType(nsINode::eTEXT)) {
+ Next();
+ }
+ }
+
+ /**
+ * Returns the current nsTextNode, or null if the iterator has finished.
+ */
+ nsTextNode* Current() const
+ {
+ return static_cast<nsTextNode*>(mCurrent);
+ }
+
+ /**
+ * Advances to the next nsTextNode and returns it, or null if the end of
+ * iteration has been reached.
+ */
+ nsTextNode* Next();
+
+ /**
+ * Returns whether the iterator is currently within the subtree rooted
+ * at mSubtree. Returns true if we are not tracking a subtree (we consider
+ * that we're always within the subtree).
+ */
+ bool IsWithinSubtree() const
+ {
+ return mSubtreePosition == eWithinSubtree;
+ }
+
+ /**
+ * Returns whether the iterator is past the subtree rooted at mSubtree.
+ * Returns false if we are not tracking a subtree.
+ */
+ bool IsAfterSubtree() const
+ {
+ return mSubtreePosition == eAfterSubtree;
+ }
+
+private:
+ /**
+ * The root under which all nsTextNodes will be iterated over.
+ */
+ nsIContent* mRoot;
+
+ /**
+ * The node rooting the subtree to track.
+ */
+ nsIContent* mSubtree;
+
+ /**
+ * The current node during iteration.
+ */
+ nsIContent* mCurrent;
+
+ /**
+ * The current iterator position relative to mSubtree.
+ */
+ SubtreePosition mSubtreePosition;
+};
+
+nsTextNode*
+TextNodeIterator::Next()
+{
+ // Starting from mCurrent, we do a non-recursive traversal to the next
+ // nsTextNode beneath mRoot, updating mSubtreePosition appropriately if we
+ // encounter mSubtree.
+ if (mCurrent) {
+ do {
+ nsIContent* next = IsTextContentElement(mCurrent) ?
+ mCurrent->GetFirstChild() :
+ nullptr;
+ if (next) {
+ mCurrent = next;
+ if (mCurrent == mSubtree) {
+ mSubtreePosition = eWithinSubtree;
+ }
+ } else {
+ for (;;) {
+ if (mCurrent == mRoot) {
+ mCurrent = nullptr;
+ break;
+ }
+ if (mCurrent == mSubtree) {
+ mSubtreePosition = eAfterSubtree;
+ }
+ next = mCurrent->GetNextSibling();
+ if (next) {
+ mCurrent = next;
+ if (mCurrent == mSubtree) {
+ mSubtreePosition = eWithinSubtree;
+ }
+ break;
+ }
+ if (mCurrent == mSubtree) {
+ mSubtreePosition = eAfterSubtree;
+ }
+ mCurrent = mCurrent->GetParent();
+ }
+ }
+ } while (mCurrent && !mCurrent->IsNodeOfType(nsINode::eTEXT));
+ }
+
+ return static_cast<nsTextNode*>(mCurrent);
+}
+
+// ----------------------------------------------------------------------------
+// TextNodeCorrespondenceRecorder
+
+/**
+ * TextNodeCorrespondence is used as the value of a frame property that
+ * is stored on all its descendant nsTextFrames. It stores the number of DOM
+ * characters between it and the previous nsTextFrame that did not have an
+ * nsTextFrame created for them, due to either not being in a correctly
+ * parented text content element, or because they were display:none.
+ * These are called "undisplayed characters".
+ *
+ * See also TextNodeCorrespondenceRecorder below, which is what sets the
+ * frame property.
+ */
+struct TextNodeCorrespondence
+{
+ explicit TextNodeCorrespondence(uint32_t aUndisplayedCharacters)
+ : mUndisplayedCharacters(aUndisplayedCharacters)
+ {
+ }
+
+ uint32_t mUndisplayedCharacters;
+};
+
+NS_DECLARE_FRAME_PROPERTY_DELETABLE(TextNodeCorrespondenceProperty,
+ TextNodeCorrespondence)
+
+/**
+ * Returns the number of undisplayed characters before the specified
+ * nsTextFrame.
+ */
+static uint32_t
+GetUndisplayedCharactersBeforeFrame(nsTextFrame* aFrame)
+{
+ void* value = aFrame->Properties().Get(TextNodeCorrespondenceProperty());
+ TextNodeCorrespondence* correspondence =
+ static_cast<TextNodeCorrespondence*>(value);
+ if (!correspondence) {
+ NS_NOTREACHED("expected a TextNodeCorrespondenceProperty on nsTextFrame "
+ "used for SVG text");
+ return 0;
+ }
+ return correspondence->mUndisplayedCharacters;
+}
+
+/**
+ * Traverses the nsTextFrames for an SVGTextFrame and records a
+ * TextNodeCorrespondenceProperty on each for the number of undisplayed DOM
+ * characters between each frame. This is done by iterating simultaneously
+ * over the nsTextNodes and nsTextFrames and noting when nsTextNodes (or
+ * parts of them) are skipped when finding the next nsTextFrame.
+ */
+class TextNodeCorrespondenceRecorder
+{
+public:
+ /**
+ * Entry point for the TextNodeCorrespondenceProperty recording.
+ */
+ static void RecordCorrespondence(SVGTextFrame* aRoot);
+
+private:
+ explicit TextNodeCorrespondenceRecorder(SVGTextFrame* aRoot)
+ : mNodeIterator(aRoot->GetContent()),
+ mPreviousNode(nullptr),
+ mNodeCharIndex(0)
+ {
+ }
+
+ void Record(SVGTextFrame* aRoot);
+ void TraverseAndRecord(nsIFrame* aFrame);
+
+ /**
+ * Returns the next non-empty nsTextNode.
+ */
+ nsTextNode* NextNode();
+
+ /**
+ * The iterator over the nsTextNodes that we use as we simultaneously
+ * iterate over the nsTextFrames.
+ */
+ TextNodeIterator mNodeIterator;
+
+ /**
+ * The previous nsTextNode we iterated over.
+ */
+ nsTextNode* mPreviousNode;
+
+ /**
+ * The index into the current nsTextNode's character content.
+ */
+ uint32_t mNodeCharIndex;
+};
+
+/* static */ void
+TextNodeCorrespondenceRecorder::RecordCorrespondence(SVGTextFrame* aRoot)
+{
+ TextNodeCorrespondenceRecorder recorder(aRoot);
+ recorder.Record(aRoot);
+}
+
+void
+TextNodeCorrespondenceRecorder::Record(SVGTextFrame* aRoot)
+{
+ if (!mNodeIterator.Current()) {
+ // If there are no nsTextNodes then there is nothing to do.
+ return;
+ }
+
+ // Traverse over all the nsTextFrames and record the number of undisplayed
+ // characters.
+ TraverseAndRecord(aRoot);
+
+ // Find how many undisplayed characters there are after the final nsTextFrame.
+ uint32_t undisplayed = 0;
+ if (mNodeIterator.Current()) {
+ if (mPreviousNode && mPreviousNode->TextLength() != mNodeCharIndex) {
+ // The last nsTextFrame ended part way through an nsTextNode. The
+ // remaining characters count as undisplayed.
+ NS_ASSERTION(mNodeCharIndex < mPreviousNode->TextLength(),
+ "incorrect tracking of undisplayed characters in "
+ "text nodes");
+ undisplayed += mPreviousNode->TextLength() - mNodeCharIndex;
+ }
+ // All the remaining nsTextNodes that we iterate must also be undisplayed.
+ for (nsTextNode* textNode = mNodeIterator.Current();
+ textNode;
+ textNode = NextNode()) {
+ undisplayed += textNode->TextLength();
+ }
+ }
+
+ // Record the trailing number of undisplayed characters on the
+ // SVGTextFrame.
+ aRoot->mTrailingUndisplayedCharacters = undisplayed;
+}
+
+nsTextNode*
+TextNodeCorrespondenceRecorder::NextNode()
+{
+ mPreviousNode = mNodeIterator.Current();
+ nsTextNode* next;
+ do {
+ next = mNodeIterator.Next();
+ } while (next && next->TextLength() == 0);
+ return next;
+}
+
+void
+TextNodeCorrespondenceRecorder::TraverseAndRecord(nsIFrame* aFrame)
+{
+ // Recursively iterate over the frame tree, for frames that correspond
+ // to text content elements.
+ if (IsTextContentElement(aFrame->GetContent())) {
+ for (nsIFrame* f : aFrame->PrincipalChildList()) {
+ TraverseAndRecord(f);
+ }
+ return;
+ }
+
+ nsTextFrame* frame; // The current text frame.
+ nsTextNode* node; // The text node for the current text frame.
+ if (!GetNonEmptyTextFrameAndNode(aFrame, frame, node)) {
+ // If this isn't an nsTextFrame, or is empty, nothing to do.
+ return;
+ }
+
+ NS_ASSERTION(frame->GetContentOffset() >= 0,
+ "don't know how to handle negative content indexes");
+
+ uint32_t undisplayed = 0;
+ if (!mPreviousNode) {
+ // Must be the very first text frame.
+ NS_ASSERTION(mNodeCharIndex == 0, "incorrect tracking of undisplayed "
+ "characters in text nodes");
+ if (!mNodeIterator.Current()) {
+ NS_NOTREACHED("incorrect tracking of correspondence between text frames "
+ "and text nodes");
+ } else {
+ // Each whole nsTextNode we find before we get to the text node for the
+ // first text frame must be undisplayed.
+ while (mNodeIterator.Current() != node) {
+ undisplayed += mNodeIterator.Current()->TextLength();
+ NextNode();
+ }
+ // If the first text frame starts at a non-zero content offset, then those
+ // earlier characters are also undisplayed.
+ undisplayed += frame->GetContentOffset();
+ NextNode();
+ }
+ } else if (mPreviousNode == node) {
+ // Same text node as last time.
+ if (static_cast<uint32_t>(frame->GetContentOffset()) != mNodeCharIndex) {
+ // We have some characters in the middle of the text node
+ // that are undisplayed.
+ NS_ASSERTION(mNodeCharIndex <
+ static_cast<uint32_t>(frame->GetContentOffset()),
+ "incorrect tracking of undisplayed characters in "
+ "text nodes");
+ undisplayed = frame->GetContentOffset() - mNodeCharIndex;
+ }
+ } else {
+ // Different text node from last time.
+ if (mPreviousNode->TextLength() != mNodeCharIndex) {
+ NS_ASSERTION(mNodeCharIndex < mPreviousNode->TextLength(),
+ "incorrect tracking of undisplayed characters in "
+ "text nodes");
+ // Any trailing characters at the end of the previous nsTextNode are
+ // undisplayed.
+ undisplayed = mPreviousNode->TextLength() - mNodeCharIndex;
+ }
+ // Each whole nsTextNode we find before we get to the text node for
+ // the current text frame must be undisplayed.
+ while (mNodeIterator.Current() != node) {
+ undisplayed += mNodeIterator.Current()->TextLength();
+ NextNode();
+ }
+ // If the current text frame starts at a non-zero content offset, then those
+ // earlier characters are also undisplayed.
+ undisplayed += frame->GetContentOffset();
+ NextNode();
+ }
+
+ // Set the frame property.
+ frame->Properties().Set(TextNodeCorrespondenceProperty(),
+ new TextNodeCorrespondence(undisplayed));
+
+ // Remember how far into the current nsTextNode we are.
+ mNodeCharIndex = frame->GetContentEnd();
+}
+
+// ----------------------------------------------------------------------------
+// TextFrameIterator
+
+/**
+ * An iterator class for nsTextFrames that are descendants of an
+ * SVGTextFrame. The iterator can optionally track whether the
+ * current nsTextFrame is for a descendant of, or past, a given subtree
+ * content node or frame. (This functionality is used for example by the SVG
+ * DOM text methods to get only the nsTextFrames for a particular <tspan>.)
+ *
+ * TextFrameIterator also tracks and exposes other information about the
+ * current nsTextFrame:
+ *
+ * * how many undisplayed characters came just before it
+ * * its position (in app units) relative to the SVGTextFrame's anonymous
+ * block frame
+ * * what nsInlineFrame corresponding to a <textPath> element it is a
+ * descendant of
+ * * what computed dominant-baseline value applies to it
+ *
+ * Note that any text frames that are empty -- whose ContentLength() is 0 --
+ * will be skipped over.
+ */
+class TextFrameIterator
+{
+public:
+ /**
+ * Constructs a TextFrameIterator for the specified SVGTextFrame
+ * with an optional frame subtree to restrict iterated text frames to.
+ */
+ explicit TextFrameIterator(SVGTextFrame* aRoot, nsIFrame* aSubtree = nullptr)
+ : mRootFrame(aRoot),
+ mSubtree(aSubtree),
+ mCurrentFrame(aRoot),
+ mCurrentPosition(),
+ mSubtreePosition(mSubtree ? eBeforeSubtree : eWithinSubtree)
+ {
+ Init();
+ }
+
+ /**
+ * Constructs a TextFrameIterator for the specified SVGTextFrame
+ * with an optional frame content subtree to restrict iterated text frames to.
+ */
+ TextFrameIterator(SVGTextFrame* aRoot, nsIContent* aSubtree)
+ : mRootFrame(aRoot),
+ mSubtree(aRoot && aSubtree && aSubtree != aRoot->GetContent() ?
+ aSubtree->GetPrimaryFrame() :
+ nullptr),
+ mCurrentFrame(aRoot),
+ mCurrentPosition(),
+ mSubtreePosition(mSubtree ? eBeforeSubtree : eWithinSubtree)
+ {
+ Init();
+ }
+
+ /**
+ * Returns the root SVGTextFrame this TextFrameIterator is iterating over.
+ */
+ SVGTextFrame* Root() const
+ {
+ return mRootFrame;
+ }
+
+ /**
+ * Returns the current nsTextFrame.
+ */
+ nsTextFrame* Current() const
+ {
+ return do_QueryFrame(mCurrentFrame);
+ }
+
+ /**
+ * Returns the number of undisplayed characters in the DOM just before the
+ * current frame.
+ */
+ uint32_t UndisplayedCharacters() const;
+
+ /**
+ * Returns the current frame's position, in app units, relative to the
+ * root SVGTextFrame's anonymous block frame.
+ */
+ nsPoint Position() const
+ {
+ return mCurrentPosition;
+ }
+
+ /**
+ * Advances to the next nsTextFrame and returns it.
+ */
+ nsTextFrame* Next();
+
+ /**
+ * Returns whether the iterator is within the subtree.
+ */
+ bool IsWithinSubtree() const
+ {
+ return mSubtreePosition == eWithinSubtree;
+ }
+
+ /**
+ * Returns whether the iterator is past the subtree.
+ */
+ bool IsAfterSubtree() const
+ {
+ return mSubtreePosition == eAfterSubtree;
+ }
+
+ /**
+ * Returns the frame corresponding to the <textPath> element, if we
+ * are inside one.
+ */
+ nsIFrame* TextPathFrame() const
+ {
+ return mTextPathFrames.IsEmpty() ?
+ nullptr :
+ mTextPathFrames.ElementAt(mTextPathFrames.Length() - 1);
+ }
+
+ /**
+ * Returns the current frame's computed dominant-baseline value.
+ */
+ uint8_t DominantBaseline() const
+ {
+ return mBaselines.ElementAt(mBaselines.Length() - 1);
+ }
+
+ /**
+ * Finishes the iterator.
+ */
+ void Close()
+ {
+ mCurrentFrame = nullptr;
+ }
+
+private:
+ /**
+ * Initializes the iterator and advances to the first item.
+ */
+ void Init()
+ {
+ if (!mRootFrame) {
+ return;
+ }
+
+ mBaselines.AppendElement(mRootFrame->StyleSVGReset()->mDominantBaseline);
+ Next();
+ }
+
+ /**
+ * Pushes the specified frame's computed dominant-baseline value.
+ * If the value of the property is "auto", then the parent frame's
+ * computed value is used.
+ */
+ void PushBaseline(nsIFrame* aNextFrame);
+
+ /**
+ * Pops the current dominant-baseline off the stack.
+ */
+ void PopBaseline();
+
+ /**
+ * The root frame we are iterating through.
+ */
+ SVGTextFrame* mRootFrame;
+
+ /**
+ * The frame for the subtree we are also interested in tracking.
+ */
+ nsIFrame* mSubtree;
+
+ /**
+ * The current value of the iterator.
+ */
+ nsIFrame* mCurrentFrame;
+
+ /**
+ * The position, in app units, of the current frame relative to mRootFrame.
+ */
+ nsPoint mCurrentPosition;
+
+ /**
+ * Stack of frames corresponding to <textPath> elements that are in scope
+ * for the current frame.
+ */
+ AutoTArray<nsIFrame*, 1> mTextPathFrames;
+
+ /**
+ * Stack of dominant-baseline values to record as we traverse through the
+ * frame tree.
+ */
+ AutoTArray<uint8_t, 8> mBaselines;
+
+ /**
+ * The iterator's current position relative to mSubtree.
+ */
+ SubtreePosition mSubtreePosition;
+};
+
+uint32_t
+TextFrameIterator::UndisplayedCharacters() const
+{
+ MOZ_ASSERT(!(mRootFrame->PrincipalChildList().FirstChild() &&
+ NS_SUBTREE_DIRTY(mRootFrame->PrincipalChildList().FirstChild())),
+ "should have already reflowed the anonymous block child");
+
+ if (!mCurrentFrame) {
+ return mRootFrame->mTrailingUndisplayedCharacters;
+ }
+
+ nsTextFrame* frame = do_QueryFrame(mCurrentFrame);
+ return GetUndisplayedCharactersBeforeFrame(frame);
+}
+
+nsTextFrame*
+TextFrameIterator::Next()
+{
+ // Starting from mCurrentFrame, we do a non-recursive traversal to the next
+ // nsTextFrame beneath mRoot, updating mSubtreePosition appropriately if we
+ // encounter mSubtree.
+ if (mCurrentFrame) {
+ do {
+ nsIFrame* next = IsTextContentElement(mCurrentFrame->GetContent()) ?
+ mCurrentFrame->PrincipalChildList().FirstChild() :
+ nullptr;
+ if (next) {
+ // Descend into this frame, and accumulate its position.
+ mCurrentPosition += next->GetPosition();
+ if (next->GetContent()->IsSVGElement(nsGkAtoms::textPath)) {
+ // Record this <textPath> frame.
+ mTextPathFrames.AppendElement(next);
+ }
+ // Record the frame's baseline.
+ PushBaseline(next);
+ mCurrentFrame = next;
+ if (mCurrentFrame == mSubtree) {
+ // If the current frame is mSubtree, we have now moved into it.
+ mSubtreePosition = eWithinSubtree;
+ }
+ } else {
+ for (;;) {
+ // We want to move past the current frame.
+ if (mCurrentFrame == mRootFrame) {
+ // If we've reached the root frame, we're finished.
+ mCurrentFrame = nullptr;
+ break;
+ }
+ // Remove the current frame's position.
+ mCurrentPosition -= mCurrentFrame->GetPosition();
+ if (mCurrentFrame->GetContent()->IsSVGElement(nsGkAtoms::textPath)) {
+ // Pop off the <textPath> frame if this is a <textPath>.
+ mTextPathFrames.TruncateLength(mTextPathFrames.Length() - 1);
+ }
+ // Pop off the current baseline.
+ PopBaseline();
+ if (mCurrentFrame == mSubtree) {
+ // If this was mSubtree, we have now moved past it.
+ mSubtreePosition = eAfterSubtree;
+ }
+ next = mCurrentFrame->GetNextSibling();
+ if (next) {
+ // Moving to the next sibling.
+ mCurrentPosition += next->GetPosition();
+ if (next->GetContent()->IsSVGElement(nsGkAtoms::textPath)) {
+ // Record this <textPath> frame.
+ mTextPathFrames.AppendElement(next);
+ }
+ // Record the frame's baseline.
+ PushBaseline(next);
+ mCurrentFrame = next;
+ if (mCurrentFrame == mSubtree) {
+ // If the current frame is mSubtree, we have now moved into it.
+ mSubtreePosition = eWithinSubtree;
+ }
+ break;
+ }
+ if (mCurrentFrame == mSubtree) {
+ // If there is no next sibling frame, and the current frame is
+ // mSubtree, we have now moved past it.
+ mSubtreePosition = eAfterSubtree;
+ }
+ // Ascend out of this frame.
+ mCurrentFrame = mCurrentFrame->GetParent();
+ }
+ }
+ } while (mCurrentFrame &&
+ !IsNonEmptyTextFrame(mCurrentFrame));
+ }
+
+ return Current();
+}
+
+void
+TextFrameIterator::PushBaseline(nsIFrame* aNextFrame)
+{
+ uint8_t baseline = aNextFrame->StyleSVGReset()->mDominantBaseline;
+ if (baseline == NS_STYLE_DOMINANT_BASELINE_AUTO) {
+ baseline = mBaselines.LastElement();
+ }
+ mBaselines.AppendElement(baseline);
+}
+
+void
+TextFrameIterator::PopBaseline()
+{
+ NS_ASSERTION(!mBaselines.IsEmpty(), "popped too many baselines");
+ mBaselines.TruncateLength(mBaselines.Length() - 1);
+}
+
+// -----------------------------------------------------------------------------
+// TextRenderedRunIterator
+
+/**
+ * Iterator for TextRenderedRun objects for the SVGTextFrame.
+ */
+class TextRenderedRunIterator
+{
+public:
+ /**
+ * Values for the aFilter argument of the constructor, to indicate which frames
+ * we should be limited to iterating TextRenderedRun objects for.
+ */
+ enum RenderedRunFilter {
+ // Iterate TextRenderedRuns for all nsTextFrames.
+ eAllFrames,
+ // Iterate only TextRenderedRuns for nsTextFrames that are
+ // visibility:visible.
+ eVisibleFrames
+ };
+
+ /**
+ * Constructs a TextRenderedRunIterator with an optional frame subtree to
+ * restrict iterated rendered runs to.
+ *
+ * @param aSVGTextFrame The SVGTextFrame whose rendered runs to iterate
+ * through.
+ * @param aFilter Indicates whether to iterate rendered runs for non-visible
+ * nsTextFrames.
+ * @param aSubtree An optional frame subtree to restrict iterated rendered
+ * runs to.
+ */
+ explicit TextRenderedRunIterator(SVGTextFrame* aSVGTextFrame,
+ RenderedRunFilter aFilter = eAllFrames,
+ nsIFrame* aSubtree = nullptr)
+ : mFrameIterator(FrameIfAnonymousChildReflowed(aSVGTextFrame), aSubtree),
+ mFilter(aFilter),
+ mTextElementCharIndex(0),
+ mFrameStartTextElementCharIndex(0),
+ mFontSizeScaleFactor(aSVGTextFrame->mFontSizeScaleFactor),
+ mCurrent(First())
+ {
+ }
+
+ /**
+ * Constructs a TextRenderedRunIterator with a content subtree to restrict
+ * iterated rendered runs to.
+ *
+ * @param aSVGTextFrame The SVGTextFrame whose rendered runs to iterate
+ * through.
+ * @param aFilter Indicates whether to iterate rendered runs for non-visible
+ * nsTextFrames.
+ * @param aSubtree A content subtree to restrict iterated rendered runs to.
+ */
+ TextRenderedRunIterator(SVGTextFrame* aSVGTextFrame,
+ RenderedRunFilter aFilter,
+ nsIContent* aSubtree)
+ : mFrameIterator(FrameIfAnonymousChildReflowed(aSVGTextFrame), aSubtree),
+ mFilter(aFilter),
+ mTextElementCharIndex(0),
+ mFrameStartTextElementCharIndex(0),
+ mFontSizeScaleFactor(aSVGTextFrame->mFontSizeScaleFactor),
+ mCurrent(First())
+ {
+ }
+
+ /**
+ * Returns the current TextRenderedRun.
+ */
+ TextRenderedRun Current() const
+ {
+ return mCurrent;
+ }
+
+ /**
+ * Advances to the next TextRenderedRun and returns it.
+ */
+ TextRenderedRun Next();
+
+private:
+ /**
+ * Returns the root SVGTextFrame this iterator is for.
+ */
+ SVGTextFrame* Root() const
+ {
+ return mFrameIterator.Root();
+ }
+
+ /**
+ * Advances to the first TextRenderedRun and returns it.
+ */
+ TextRenderedRun First();
+
+ /**
+ * The frame iterator to use.
+ */
+ TextFrameIterator mFrameIterator;
+
+ /**
+ * The filter indicating which TextRenderedRuns to return.
+ */
+ RenderedRunFilter mFilter;
+
+ /**
+ * The character index across the entire <text> element we are currently
+ * up to.
+ */
+ uint32_t mTextElementCharIndex;
+
+ /**
+ * The character index across the entire <text> for the start of the current
+ * frame.
+ */
+ uint32_t mFrameStartTextElementCharIndex;
+
+ /**
+ * The font-size scale factor we used when constructing the nsTextFrames.
+ */
+ double mFontSizeScaleFactor;
+
+ /**
+ * The current TextRenderedRun.
+ */
+ TextRenderedRun mCurrent;
+};
+
+TextRenderedRun
+TextRenderedRunIterator::Next()
+{
+ if (!mFrameIterator.Current()) {
+ // If there are no more frames, then there are no more rendered runs to
+ // return.
+ mCurrent = TextRenderedRun();
+ return mCurrent;
+ }
+
+ // The values we will use to initialize the TextRenderedRun with.
+ nsTextFrame* frame;
+ gfxPoint pt;
+ double rotate;
+ nscoord baseline;
+ uint32_t offset, length;
+ uint32_t charIndex;
+
+ // We loop, because we want to skip over rendered runs that either aren't
+ // within our subtree of interest, because they don't match the filter,
+ // or because they are hidden due to having fallen off the end of a
+ // <textPath>.
+ for (;;) {
+ if (mFrameIterator.IsAfterSubtree()) {
+ mCurrent = TextRenderedRun();
+ return mCurrent;
+ }
+
+ frame = mFrameIterator.Current();
+
+ charIndex = mTextElementCharIndex;
+
+ // Find the end of the rendered run, by looking through the
+ // SVGTextFrame's positions array until we find one that is recorded
+ // as a run boundary.
+ uint32_t runStart, runEnd; // XXX Replace runStart with mTextElementCharIndex.
+ runStart = mTextElementCharIndex;
+ runEnd = runStart + 1;
+ while (runEnd < Root()->mPositions.Length() &&
+ !Root()->mPositions[runEnd].mRunBoundary) {
+ runEnd++;
+ }
+
+ // Convert the global run start/end indexes into an offset/length into the
+ // current frame's nsTextNode.
+ offset = frame->GetContentOffset() + runStart -
+ mFrameStartTextElementCharIndex;
+ length = runEnd - runStart;
+
+ // If the end of the frame's content comes before the run boundary we found
+ // in SVGTextFrame's position array, we need to shorten the rendered run.
+ uint32_t contentEnd = frame->GetContentEnd();
+ if (offset + length > contentEnd) {
+ length = contentEnd - offset;
+ }
+
+ NS_ASSERTION(offset >= uint32_t(frame->GetContentOffset()), "invalid offset");
+ NS_ASSERTION(offset + length <= contentEnd, "invalid offset or length");
+
+ // Get the frame's baseline position.
+ frame->EnsureTextRun(nsTextFrame::eInflated);
+ baseline = GetBaselinePosition(frame,
+ frame->GetTextRun(nsTextFrame::eInflated),
+ mFrameIterator.DominantBaseline(),
+ mFontSizeScaleFactor);
+
+ // Trim the offset/length to remove any leading/trailing white space.
+ uint32_t untrimmedOffset = offset;
+ uint32_t untrimmedLength = length;
+ nsTextFrame::TrimmedOffsets trimmedOffsets =
+ frame->GetTrimmedOffsets(frame->GetContent()->GetText(), true);
+ TrimOffsets(offset, length, trimmedOffsets);
+ charIndex += offset - untrimmedOffset;
+
+ // Get the position and rotation of the character that begins this
+ // rendered run.
+ pt = Root()->mPositions[charIndex].mPosition;
+ rotate = Root()->mPositions[charIndex].mAngle;
+
+ // Determine if we should skip this rendered run.
+ bool skip = !mFrameIterator.IsWithinSubtree() ||
+ Root()->mPositions[mTextElementCharIndex].mHidden;
+ if (mFilter == eVisibleFrames) {
+ skip = skip || !frame->StyleVisibility()->IsVisible();
+ }
+
+ // Update our global character index to move past the characters
+ // corresponding to this rendered run.
+ mTextElementCharIndex += untrimmedLength;
+
+ // If we have moved past the end of the current frame's content, we need to
+ // advance to the next frame.
+ if (offset + untrimmedLength >= contentEnd) {
+ mFrameIterator.Next();
+ mTextElementCharIndex += mFrameIterator.UndisplayedCharacters();
+ mFrameStartTextElementCharIndex = mTextElementCharIndex;
+ }
+
+ if (!mFrameIterator.Current()) {
+ if (skip) {
+ // That was the last frame, and we skipped this rendered run. So we
+ // have no rendered run to return.
+ mCurrent = TextRenderedRun();
+ return mCurrent;
+ }
+ break;
+ }
+
+ if (length && !skip) {
+ // Only return a rendered run if it didn't get collapsed away entirely
+ // (due to it being all white space) and if we don't want to skip it.
+ break;
+ }
+ }
+
+ mCurrent = TextRenderedRun(frame, pt, Root()->mLengthAdjustScaleFactor,
+ rotate, mFontSizeScaleFactor, baseline,
+ offset, length, charIndex);
+ return mCurrent;
+}
+
+TextRenderedRun
+TextRenderedRunIterator::First()
+{
+ if (!mFrameIterator.Current()) {
+ return TextRenderedRun();
+ }
+
+ if (Root()->mPositions.IsEmpty()) {
+ mFrameIterator.Close();
+ return TextRenderedRun();
+ }
+
+ // Get the character index for the start of this rendered run, by skipping
+ // any undisplayed characters.
+ mTextElementCharIndex = mFrameIterator.UndisplayedCharacters();
+ mFrameStartTextElementCharIndex = mTextElementCharIndex;
+
+ return Next();
+}
+
+// -----------------------------------------------------------------------------
+// CharIterator
+
+/**
+ * Iterator for characters within an SVGTextFrame.
+ */
+class CharIterator
+{
+ typedef gfxTextRun::Range Range;
+
+public:
+ /**
+ * Values for the aFilter argument of the constructor, to indicate which
+ * characters we should be iterating over.
+ */
+ enum CharacterFilter {
+ // Iterate over all original characters from the DOM that are within valid
+ // text content elements.
+ eOriginal,
+ // Iterate only over characters that are addressable by the positioning
+ // attributes x="", y="", etc. This includes all characters after
+ // collapsing white space as required by the value of 'white-space'.
+ eAddressable,
+ // Iterate only over characters that are the first of clusters or ligature
+ // groups.
+ eClusterAndLigatureGroupStart,
+ // Iterate only over characters that are part of a cluster or ligature
+ // group but not the first character.
+ eClusterOrLigatureGroupMiddle
+ };
+
+ /**
+ * Constructs a CharIterator.
+ *
+ * @param aSVGTextFrame The SVGTextFrame whose characters to iterate
+ * through.
+ * @param aFilter Indicates which characters to iterate over.
+ * @param aSubtree A content subtree to track whether the current character
+ * is within.
+ */
+ CharIterator(SVGTextFrame* aSVGTextFrame,
+ CharacterFilter aFilter,
+ nsIContent* aSubtree = nullptr);
+
+ /**
+ * Returns whether the iterator is finished.
+ */
+ bool AtEnd() const
+ {
+ return !mFrameIterator.Current();
+ }
+
+ /**
+ * Advances to the next matching character. Returns true if there was a
+ * character to advance to, and false otherwise.
+ */
+ bool Next();
+
+ /**
+ * Advances ahead aCount matching characters. Returns true if there were
+ * enough characters to advance past, and false otherwise.
+ */
+ bool Next(uint32_t aCount);
+
+ /**
+ * Advances ahead up to aCount matching characters.
+ */
+ void NextWithinSubtree(uint32_t aCount);
+
+ /**
+ * Advances to the character with the specified index. The index is in the
+ * space of original characters (i.e., all DOM characters under the <text>
+ * that are within valid text content elements).
+ */
+ bool AdvanceToCharacter(uint32_t aTextElementCharIndex);
+
+ /**
+ * Advances to the first matching character after the current nsTextFrame.
+ */
+ bool AdvancePastCurrentFrame();
+
+ /**
+ * Advances to the first matching character after the frames within
+ * the current <textPath>.
+ */
+ bool AdvancePastCurrentTextPathFrame();
+
+ /**
+ * Advances to the first matching character of the subtree. Returns true
+ * if we successfully advance to the subtree, or if we are already within
+ * the subtree. Returns false if we are past the subtree.
+ */
+ bool AdvanceToSubtree();
+
+ /**
+ * Returns the nsTextFrame for the current character.
+ */
+ nsTextFrame* TextFrame() const
+ {
+ return mFrameIterator.Current();
+ }
+
+ /**
+ * Returns whether the iterator is within the subtree.
+ */
+ bool IsWithinSubtree() const
+ {
+ return mFrameIterator.IsWithinSubtree();
+ }
+
+ /**
+ * Returns whether the iterator is past the subtree.
+ */
+ bool IsAfterSubtree() const
+ {
+ return mFrameIterator.IsAfterSubtree();
+ }
+
+ /**
+ * Returns whether the current character is a skipped character.
+ */
+ bool IsOriginalCharSkipped() const
+ {
+ return mSkipCharsIterator.IsOriginalCharSkipped();
+ }
+
+ /**
+ * Returns whether the current character is the start of a cluster and
+ * ligature group.
+ */
+ bool IsClusterAndLigatureGroupStart() const;
+
+ /**
+ * Returns whether the current character is trimmed away when painting,
+ * due to it being leading/trailing white space.
+ */
+ bool IsOriginalCharTrimmed() const;
+
+ /**
+ * Returns whether the current character is unaddressable from the SVG glyph
+ * positioning attributes.
+ */
+ bool IsOriginalCharUnaddressable() const
+ {
+ return IsOriginalCharSkipped() || IsOriginalCharTrimmed();
+ }
+
+ /**
+ * Returns the text run for the current character.
+ */
+ gfxTextRun* TextRun() const
+ {
+ return mTextRun;
+ }
+
+ /**
+ * Returns the current character index.
+ */
+ uint32_t TextElementCharIndex() const
+ {
+ return mTextElementCharIndex;
+ }
+
+ /**
+ * Returns the character index for the start of the cluster/ligature group it
+ * is part of.
+ */
+ uint32_t GlyphStartTextElementCharIndex() const
+ {
+ return mGlyphStartTextElementCharIndex;
+ }
+
+ /**
+ * Returns the number of undisplayed characters between the beginning of
+ * the glyph and the current character.
+ */
+ uint32_t GlyphUndisplayedCharacters() const
+ {
+ return mGlyphUndisplayedCharacters;
+ }
+
+ /**
+ * Gets the original character offsets within the nsTextNode for the
+ * cluster/ligature group the current character is a part of.
+ *
+ * @param aOriginalOffset The offset of the start of the cluster/ligature
+ * group (output).
+ * @param aOriginalLength The length of cluster/ligature group (output).
+ */
+ void GetOriginalGlyphOffsets(uint32_t& aOriginalOffset,
+ uint32_t& aOriginalLength) const;
+
+ /**
+ * Gets the advance, in user units, of the glyph the current character is
+ * part of.
+ *
+ * @param aContext The context to use for unit conversions.
+ */
+ gfxFloat GetGlyphAdvance(nsPresContext* aContext) const;
+
+ /**
+ * Gets the advance, in user units, of the current character. If the
+ * character is a part of ligature, then the advance returned will be
+ * a fraction of the ligature glyph's advance.
+ *
+ * @param aContext The context to use for unit conversions.
+ */
+ gfxFloat GetAdvance(nsPresContext* aContext) const;
+
+ /**
+ * Gets the specified partial advance of the glyph the current character is
+ * part of. The partial advance is measured from the first character
+ * corresponding to the glyph until the specified part length.
+ *
+ * The part length value does not include any undisplayed characters in the
+ * middle of the cluster/ligature group. For example, if you have:
+ *
+ * <text>f<tspan display="none">x</tspan>i</text>
+ *
+ * and the "f" and "i" are ligaturized, then calling GetGlyphPartialAdvance
+ * with aPartLength values will have the following results:
+ *
+ * 0 => 0
+ * 1 => adv("fi") / 2
+ * 2 => adv("fi")
+ *
+ * @param aPartLength The number of characters in the cluster/ligature group
+ * to measure.
+ * @param aContext The context to use for unit conversions.
+ */
+ gfxFloat GetGlyphPartialAdvance(uint32_t aPartLength,
+ nsPresContext* aContext) const;
+
+ /**
+ * Returns the frame corresponding to the <textPath> that the current
+ * character is within.
+ */
+ nsIFrame* TextPathFrame() const
+ {
+ return mFrameIterator.TextPathFrame();
+ }
+
+private:
+ /**
+ * Advances to the next character without checking it against the filter.
+ * Returns true if there was a next character to advance to, or false
+ * otherwise.
+ */
+ bool NextCharacter();
+
+ /**
+ * Returns whether the current character matches the filter.
+ */
+ bool MatchesFilter() const;
+
+ /**
+ * If this is the start of a glyph, record it.
+ */
+ void UpdateGlyphStartTextElementCharIndex() {
+ if (!IsOriginalCharSkipped() && IsClusterAndLigatureGroupStart()) {
+ mGlyphStartTextElementCharIndex = mTextElementCharIndex;
+ mGlyphUndisplayedCharacters = 0;
+ }
+ }
+
+ /**
+ * The filter to use.
+ */
+ CharacterFilter mFilter;
+
+ /**
+ * The iterator for text frames.
+ */
+ TextFrameIterator mFrameIterator;
+
+ /**
+ * A gfxSkipCharsIterator for the text frame the current character is
+ * a part of.
+ */
+ gfxSkipCharsIterator mSkipCharsIterator;
+
+ // Cache for information computed by IsOriginalCharTrimmed.
+ mutable nsTextFrame* mFrameForTrimCheck;
+ mutable uint32_t mTrimmedOffset;
+ mutable uint32_t mTrimmedLength;
+
+ /**
+ * The text run the current character is a part of.
+ */
+ gfxTextRun* mTextRun;
+
+ /**
+ * The current character's index.
+ */
+ uint32_t mTextElementCharIndex;
+
+ /**
+ * The index of the character that starts the cluster/ligature group the
+ * current character is a part of.
+ */
+ uint32_t mGlyphStartTextElementCharIndex;
+
+ /**
+ * If we are iterating in mode eClusterOrLigatureGroupMiddle, then
+ * this tracks how many undisplayed characters were encountered
+ * between the start of this glyph (at mGlyphStartTextElementCharIndex)
+ * and the current character (at mTextElementCharIndex).
+ */
+ uint32_t mGlyphUndisplayedCharacters;
+
+ /**
+ * The scale factor to apply to glyph advances returned by
+ * GetGlyphAdvance etc. to take into account textLength="".
+ */
+ float mLengthAdjustScaleFactor;
+};
+
+CharIterator::CharIterator(SVGTextFrame* aSVGTextFrame,
+ CharIterator::CharacterFilter aFilter,
+ nsIContent* aSubtree)
+ : mFilter(aFilter),
+ mFrameIterator(FrameIfAnonymousChildReflowed(aSVGTextFrame), aSubtree),
+ mFrameForTrimCheck(nullptr),
+ mTrimmedOffset(0),
+ mTrimmedLength(0),
+ mTextElementCharIndex(0),
+ mGlyphStartTextElementCharIndex(0),
+ mLengthAdjustScaleFactor(aSVGTextFrame->mLengthAdjustScaleFactor)
+{
+ if (!AtEnd()) {
+ mSkipCharsIterator = TextFrame()->EnsureTextRun(nsTextFrame::eInflated);
+ mTextRun = TextFrame()->GetTextRun(nsTextFrame::eInflated);
+ mTextElementCharIndex = mFrameIterator.UndisplayedCharacters();
+ UpdateGlyphStartTextElementCharIndex();
+ if (!MatchesFilter()) {
+ Next();
+ }
+ }
+}
+
+bool
+CharIterator::Next()
+{
+ while (NextCharacter()) {
+ if (MatchesFilter()) {
+ return true;
+ }
+ }
+ return false;
+}
+
+bool
+CharIterator::Next(uint32_t aCount)
+{
+ if (aCount == 0 && AtEnd()) {
+ return false;
+ }
+ while (aCount) {
+ if (!Next()) {
+ return false;
+ }
+ aCount--;
+ }
+ return true;
+}
+
+void
+CharIterator::NextWithinSubtree(uint32_t aCount)
+{
+ while (IsWithinSubtree() && aCount) {
+ --aCount;
+ if (!Next()) {
+ return;
+ }
+ }
+}
+
+bool
+CharIterator::AdvanceToCharacter(uint32_t aTextElementCharIndex)
+{
+ while (mTextElementCharIndex < aTextElementCharIndex) {
+ if (!Next()) {
+ return false;
+ }
+ }
+ return true;
+}
+
+bool
+CharIterator::AdvancePastCurrentFrame()
+{
+ // XXX Can do this better than one character at a time if it matters.
+ nsTextFrame* currentFrame = TextFrame();
+ do {
+ if (!Next()) {
+ return false;
+ }
+ } while (TextFrame() == currentFrame);
+ return true;
+}
+
+bool
+CharIterator::AdvancePastCurrentTextPathFrame()
+{
+ nsIFrame* currentTextPathFrame = TextPathFrame();
+ NS_ASSERTION(currentTextPathFrame,
+ "expected AdvancePastCurrentTextPathFrame to be called only "
+ "within a text path frame");
+ do {
+ if (!AdvancePastCurrentFrame()) {
+ return false;
+ }
+ } while (TextPathFrame() == currentTextPathFrame);
+ return true;
+}
+
+bool
+CharIterator::AdvanceToSubtree()
+{
+ while (!IsWithinSubtree()) {
+ if (IsAfterSubtree()) {
+ return false;
+ }
+ if (!AdvancePastCurrentFrame()) {
+ return false;
+ }
+ }
+ return true;
+}
+
+bool
+CharIterator::IsClusterAndLigatureGroupStart() const
+{
+ return mTextRun->IsLigatureGroupStart(mSkipCharsIterator.GetSkippedOffset()) &&
+ mTextRun->IsClusterStart(mSkipCharsIterator.GetSkippedOffset());
+}
+
+bool
+CharIterator::IsOriginalCharTrimmed() const
+{
+ if (mFrameForTrimCheck != TextFrame()) {
+ // Since we do a lot of trim checking, we cache the trimmed offsets and
+ // lengths while we are in the same frame.
+ mFrameForTrimCheck = TextFrame();
+ uint32_t offset = mFrameForTrimCheck->GetContentOffset();
+ uint32_t length = mFrameForTrimCheck->GetContentLength();
+ nsIContent* content = mFrameForTrimCheck->GetContent();
+ nsTextFrame::TrimmedOffsets trim =
+ mFrameForTrimCheck->GetTrimmedOffsets(content->GetText(), true);
+ TrimOffsets(offset, length, trim);
+ mTrimmedOffset = offset;
+ mTrimmedLength = length;
+ }
+
+ // A character is trimmed if it is outside the mTrimmedOffset/mTrimmedLength
+ // range and it is not a significant newline character.
+ uint32_t index = mSkipCharsIterator.GetOriginalOffset();
+ return !((index >= mTrimmedOffset &&
+ index < mTrimmedOffset + mTrimmedLength) ||
+ (index >= mTrimmedOffset + mTrimmedLength &&
+ mFrameForTrimCheck->StyleText()->
+ NewlineIsSignificant(mFrameForTrimCheck) &&
+ mFrameForTrimCheck->GetContent()->GetText()->CharAt(index) == '\n'));
+}
+
+void
+CharIterator::GetOriginalGlyphOffsets(uint32_t& aOriginalOffset,
+ uint32_t& aOriginalLength) const
+{
+ gfxSkipCharsIterator it = TextFrame()->EnsureTextRun(nsTextFrame::eInflated);
+ it.SetOriginalOffset(mSkipCharsIterator.GetOriginalOffset() -
+ (mTextElementCharIndex -
+ mGlyphStartTextElementCharIndex -
+ mGlyphUndisplayedCharacters));
+
+ while (it.GetSkippedOffset() > 0 &&
+ (!mTextRun->IsClusterStart(it.GetSkippedOffset()) ||
+ !mTextRun->IsLigatureGroupStart(it.GetSkippedOffset()))) {
+ it.AdvanceSkipped(-1);
+ }
+
+ aOriginalOffset = it.GetOriginalOffset();
+
+ // Find the end of the cluster/ligature group.
+ it.SetOriginalOffset(mSkipCharsIterator.GetOriginalOffset());
+ do {
+ it.AdvanceSkipped(1);
+ } while (it.GetSkippedOffset() < mTextRun->GetLength() &&
+ (!mTextRun->IsClusterStart(it.GetSkippedOffset()) ||
+ !mTextRun->IsLigatureGroupStart(it.GetSkippedOffset())));
+
+ aOriginalLength = it.GetOriginalOffset() - aOriginalOffset;
+}
+
+gfxFloat
+CharIterator::GetGlyphAdvance(nsPresContext* aContext) const
+{
+ uint32_t offset, length;
+ GetOriginalGlyphOffsets(offset, length);
+
+ gfxSkipCharsIterator it = TextFrame()->EnsureTextRun(nsTextFrame::eInflated);
+ Range range = ConvertOriginalToSkipped(it, offset, length);
+
+ float cssPxPerDevPx = aContext->
+ AppUnitsToFloatCSSPixels(aContext->AppUnitsPerDevPixel());
+
+ gfxFloat advance = mTextRun->GetAdvanceWidth(range, nullptr);
+ return aContext->AppUnitsToGfxUnits(advance) *
+ mLengthAdjustScaleFactor * cssPxPerDevPx;
+}
+
+gfxFloat
+CharIterator::GetAdvance(nsPresContext* aContext) const
+{
+ float cssPxPerDevPx = aContext->
+ AppUnitsToFloatCSSPixels(aContext->AppUnitsPerDevPixel());
+
+ uint32_t offset = mSkipCharsIterator.GetSkippedOffset();
+ gfxFloat advance = mTextRun->
+ GetAdvanceWidth(Range(offset, offset + 1), nullptr);
+ return aContext->AppUnitsToGfxUnits(advance) *
+ mLengthAdjustScaleFactor * cssPxPerDevPx;
+}
+
+gfxFloat
+CharIterator::GetGlyphPartialAdvance(uint32_t aPartLength,
+ nsPresContext* aContext) const
+{
+ uint32_t offset, length;
+ GetOriginalGlyphOffsets(offset, length);
+
+ NS_ASSERTION(aPartLength <= length, "invalid aPartLength value");
+ length = aPartLength;
+
+ gfxSkipCharsIterator it = TextFrame()->EnsureTextRun(nsTextFrame::eInflated);
+ Range range = ConvertOriginalToSkipped(it, offset, length);
+
+ float cssPxPerDevPx = aContext->
+ AppUnitsToFloatCSSPixels(aContext->AppUnitsPerDevPixel());
+
+ gfxFloat advance = mTextRun->GetAdvanceWidth(range, nullptr);
+ return aContext->AppUnitsToGfxUnits(advance) *
+ mLengthAdjustScaleFactor * cssPxPerDevPx;
+}
+
+bool
+CharIterator::NextCharacter()
+{
+ if (AtEnd()) {
+ return false;
+ }
+
+ mTextElementCharIndex++;
+
+ // Advance within the current text run.
+ mSkipCharsIterator.AdvanceOriginal(1);
+ if (mSkipCharsIterator.GetOriginalOffset() < TextFrame()->GetContentEnd()) {
+ // We're still within the part of the text run for the current text frame.
+ UpdateGlyphStartTextElementCharIndex();
+ return true;
+ }
+
+ // Advance to the next frame.
+ mFrameIterator.Next();
+
+ // Skip any undisplayed characters.
+ uint32_t undisplayed = mFrameIterator.UndisplayedCharacters();
+ mGlyphUndisplayedCharacters += undisplayed;
+ mTextElementCharIndex += undisplayed;
+ if (!TextFrame()) {
+ // We're at the end.
+ mSkipCharsIterator = gfxSkipCharsIterator();
+ return false;
+ }
+
+ mSkipCharsIterator = TextFrame()->EnsureTextRun(nsTextFrame::eInflated);
+ mTextRun = TextFrame()->GetTextRun(nsTextFrame::eInflated);
+ UpdateGlyphStartTextElementCharIndex();
+ return true;
+}
+
+bool
+CharIterator::MatchesFilter() const
+{
+ if (mFilter == eOriginal) {
+ return true;
+ }
+
+ if (IsOriginalCharSkipped()) {
+ return false;
+ }
+
+ if (mFilter == eAddressable) {
+ return !IsOriginalCharUnaddressable();
+ }
+
+ return (mFilter == eClusterAndLigatureGroupStart) ==
+ IsClusterAndLigatureGroupStart();
+}
+
+// -----------------------------------------------------------------------------
+// nsCharClipDisplayItem
+
+/**
+ * An nsCharClipDisplayItem that obtains its left and right clip edges from a
+ * TextRenderedRun object.
+ */
+class SVGCharClipDisplayItem : public nsCharClipDisplayItem {
+public:
+ explicit SVGCharClipDisplayItem(const TextRenderedRun& aRun)
+ : nsCharClipDisplayItem(aRun.mFrame)
+ {
+ aRun.GetClipEdges(mVisIStartEdge, mVisIEndEdge);
+ }
+
+ NS_DISPLAY_DECL_NAME("SVGText", TYPE_TEXT)
+};
+
+// -----------------------------------------------------------------------------
+// SVGTextDrawPathCallbacks
+
+/**
+ * Text frame draw callback class that paints the text and text decoration parts
+ * of an nsTextFrame using SVG painting properties, and selection backgrounds
+ * and decorations as they would normally.
+ *
+ * An instance of this class is passed to nsTextFrame::PaintText if painting
+ * cannot be done directly (e.g. if we are using an SVG pattern fill, stroking
+ * the text, etc.).
+ */
+class SVGTextDrawPathCallbacks : public nsTextFrame::DrawPathCallbacks
+{
+public:
+ /**
+ * Constructs an SVGTextDrawPathCallbacks.
+ *
+ * @param aContext The context to use for painting.
+ * @param aFrame The nsTextFrame to paint.
+ * @param aCanvasTM The transformation matrix to set when painting; this
+ * should be the FOR_OUTERSVG_TM canvas TM of the text, so that
+ * paint servers are painted correctly.
+ * @param aShouldPaintSVGGlyphs Whether SVG glyphs should be painted.
+ */
+ SVGTextDrawPathCallbacks(nsRenderingContext* aContext,
+ nsTextFrame* aFrame,
+ const gfxMatrix& aCanvasTM,
+ bool aShouldPaintSVGGlyphs)
+ : DrawPathCallbacks(aShouldPaintSVGGlyphs),
+ gfx(aContext->ThebesContext()),
+ mFrame(aFrame),
+ mCanvasTM(aCanvasTM)
+ {
+ }
+
+ void NotifySelectionBackgroundNeedsFill(const Rect& aBackgroundRect,
+ nscolor aColor,
+ DrawTarget& aDrawTarget) override;
+ void PaintDecorationLine(Rect aPath, nscolor aColor) override;
+ void PaintSelectionDecorationLine(Rect aPath, nscolor aColor) override;
+ void NotifyBeforeText(nscolor aColor) override;
+ void NotifyGlyphPathEmitted() override;
+ void NotifyAfterText() override;
+
+private:
+ void SetupContext();
+
+ bool IsClipPathChild() const {
+ return nsLayoutUtils::GetClosestFrameOfType
+ (mFrame->GetParent(), nsGkAtoms::svgTextFrame)->GetStateBits() &
+ NS_STATE_SVG_CLIPPATH_CHILD;
+ }
+
+ /**
+ * Paints a piece of text geometry. This is called when glyphs
+ * or text decorations have been emitted to the gfxContext.
+ */
+ void HandleTextGeometry();
+
+ /**
+ * Sets the gfxContext paint to the appropriate color or pattern
+ * for filling text geometry.
+ */
+ void MakeFillPattern(GeneralPattern* aOutPattern);
+
+ /**
+ * Fills and strokes a piece of text geometry, using group opacity
+ * if the selection style requires it.
+ */
+ void FillAndStrokeGeometry();
+
+ /**
+ * Fills a piece of text geometry.
+ */
+ void FillGeometry();
+
+ /**
+ * Strokes a piece of text geometry.
+ */
+ void StrokeGeometry();
+
+ gfxContext* gfx;
+ nsTextFrame* mFrame;
+ const gfxMatrix& mCanvasTM;
+
+ /**
+ * The color that we were last told from one of the path callback functions.
+ * This color can be the special NS_SAME_AS_FOREGROUND_COLOR,
+ * NS_40PERCENT_FOREGROUND_COLOR and NS_TRANSPARENT colors when we are
+ * painting selections or IME decorations.
+ */
+ nscolor mColor;
+};
+
+void
+SVGTextDrawPathCallbacks::NotifySelectionBackgroundNeedsFill(
+ const Rect& aBackgroundRect,
+ nscolor aColor,
+ DrawTarget& aDrawTarget)
+{
+ if (IsClipPathChild()) {
+ // Don't paint selection backgrounds when in a clip path.
+ return;
+ }
+
+ mColor = aColor; // currently needed by MakeFillPattern
+
+ GeneralPattern fillPattern;
+ MakeFillPattern(&fillPattern);
+ if (fillPattern.GetPattern()) {
+ DrawOptions drawOptions(aColor == NS_40PERCENT_FOREGROUND_COLOR ? 0.4 : 1.0);
+ aDrawTarget.FillRect(aBackgroundRect, fillPattern, drawOptions);
+ }
+}
+
+void
+SVGTextDrawPathCallbacks::NotifyBeforeText(nscolor aColor)
+{
+ mColor = aColor;
+ SetupContext();
+ gfx->NewPath();
+}
+
+void
+SVGTextDrawPathCallbacks::NotifyGlyphPathEmitted()
+{
+ HandleTextGeometry();
+ gfx->NewPath();
+}
+
+void
+SVGTextDrawPathCallbacks::NotifyAfterText()
+{
+ gfx->Restore();
+}
+
+void
+SVGTextDrawPathCallbacks::PaintDecorationLine(Rect aPath, nscolor aColor)
+{
+ mColor = aColor;
+ AntialiasMode aaMode =
+ nsSVGUtils::ToAntialiasMode(mFrame->StyleText()->mTextRendering);
+
+ gfx->Save();
+ gfx->NewPath();
+ gfx->SetAntialiasMode(aaMode);
+ gfx->Rectangle(ThebesRect(aPath));
+ HandleTextGeometry();
+ gfx->NewPath();
+ gfx->Restore();
+}
+
+void
+SVGTextDrawPathCallbacks::PaintSelectionDecorationLine(Rect aPath,
+ nscolor aColor)
+{
+ if (IsClipPathChild()) {
+ // Don't paint selection decorations when in a clip path.
+ return;
+ }
+
+ mColor = aColor;
+
+ gfx->Save();
+ gfx->NewPath();
+ gfx->Rectangle(ThebesRect(aPath));
+ FillAndStrokeGeometry();
+ gfx->Restore();
+}
+
+void
+SVGTextDrawPathCallbacks::SetupContext()
+{
+ gfx->Save();
+
+ // XXX This is copied from nsSVGGlyphFrame::Render, but cairo doesn't actually
+ // seem to do anything with the antialias mode. So we can perhaps remove it,
+ // or make SetAntialiasMode set cairo text antialiasing too.
+ switch (mFrame->StyleText()->mTextRendering) {
+ case NS_STYLE_TEXT_RENDERING_OPTIMIZESPEED:
+ gfx->SetAntialiasMode(AntialiasMode::NONE);
+ break;
+ default:
+ gfx->SetAntialiasMode(AntialiasMode::SUBPIXEL);
+ break;
+ }
+}
+
+void
+SVGTextDrawPathCallbacks::HandleTextGeometry()
+{
+ if (IsClipPathChild()) {
+ RefPtr<Path> path = gfx->GetPath();
+ ColorPattern white(Color(1.f, 1.f, 1.f, 1.f)); // for masking, so no ToDeviceColor
+ gfx->GetDrawTarget()->Fill(path, white);
+ } else {
+ // Normal painting.
+ gfxContextMatrixAutoSaveRestore saveMatrix(gfx);
+ gfx->SetMatrix(mCanvasTM);
+
+ FillAndStrokeGeometry();
+ }
+}
+
+void
+SVGTextDrawPathCallbacks::MakeFillPattern(GeneralPattern* aOutPattern)
+{
+ if (mColor == NS_SAME_AS_FOREGROUND_COLOR ||
+ mColor == NS_40PERCENT_FOREGROUND_COLOR) {
+ nsSVGUtils::MakeFillPatternFor(mFrame, gfx, aOutPattern);
+ return;
+ }
+
+ if (mColor == NS_TRANSPARENT) {
+ return;
+ }
+
+ aOutPattern->InitColorPattern(ToDeviceColor(mColor));
+}
+
+void
+SVGTextDrawPathCallbacks::FillAndStrokeGeometry()
+{
+ bool pushedGroup = false;
+ if (mColor == NS_40PERCENT_FOREGROUND_COLOR) {
+ pushedGroup = true;
+ gfx->PushGroupForBlendBack(gfxContentType::COLOR_ALPHA, 0.4f);
+ }
+
+ uint32_t paintOrder = mFrame->StyleSVG()->mPaintOrder;
+ if (paintOrder == NS_STYLE_PAINT_ORDER_NORMAL) {
+ FillGeometry();
+ StrokeGeometry();
+ } else {
+ while (paintOrder) {
+ uint32_t component =
+ paintOrder & ((1 << NS_STYLE_PAINT_ORDER_BITWIDTH) - 1);
+ switch (component) {
+ case NS_STYLE_PAINT_ORDER_FILL:
+ FillGeometry();
+ break;
+ case NS_STYLE_PAINT_ORDER_STROKE:
+ StrokeGeometry();
+ break;
+ }
+ paintOrder >>= NS_STYLE_PAINT_ORDER_BITWIDTH;
+ }
+ }
+
+ if (pushedGroup) {
+ gfx->PopGroupAndBlend();
+ }
+}
+
+void
+SVGTextDrawPathCallbacks::FillGeometry()
+{
+ GeneralPattern fillPattern;
+ MakeFillPattern(&fillPattern);
+ if (fillPattern.GetPattern()) {
+ RefPtr<Path> path = gfx->GetPath();
+ FillRule fillRule = nsSVGUtils::ToFillRule(IsClipPathChild() ?
+ mFrame->StyleSVG()->mClipRule :
+ mFrame->StyleSVG()->mFillRule);
+ if (fillRule != path->GetFillRule()) {
+ RefPtr<PathBuilder> builder = path->CopyToBuilder(fillRule);
+ path = builder->Finish();
+ }
+ gfx->GetDrawTarget()->Fill(path, fillPattern);
+ }
+}
+
+void
+SVGTextDrawPathCallbacks::StrokeGeometry()
+{
+ // We don't paint the stroke when we are filling with a selection color.
+ if (mColor == NS_SAME_AS_FOREGROUND_COLOR ||
+ mColor == NS_40PERCENT_FOREGROUND_COLOR) {
+ if (nsSVGUtils::HasStroke(mFrame, /*aContextPaint*/ nullptr)) {
+ GeneralPattern strokePattern;
+ nsSVGUtils::MakeStrokePatternFor(mFrame, gfx, &strokePattern, /*aContextPaint*/ nullptr);
+ if (strokePattern.GetPattern()) {
+ if (!mFrame->GetParent()->GetContent()->IsSVGElement()) {
+ // The cast that follows would be unsafe
+ MOZ_ASSERT(false, "Our nsTextFrame's parent's content should be SVG");
+ return;
+ }
+ nsSVGElement* svgOwner =
+ static_cast<nsSVGElement*>(mFrame->GetParent()->GetContent());
+
+ // Apply any stroke-specific transform
+ gfxMatrix outerSVGToUser;
+ if (nsSVGUtils::GetNonScalingStrokeTransform(mFrame, &outerSVGToUser) &&
+ outerSVGToUser.Invert()) {
+ gfx->Multiply(outerSVGToUser);
+ }
+
+ RefPtr<Path> path = gfx->GetPath();
+ SVGContentUtils::AutoStrokeOptions strokeOptions;
+ SVGContentUtils::GetStrokeOptions(&strokeOptions, svgOwner,
+ mFrame->StyleContext(),
+ /*aContextPaint*/ nullptr);
+ DrawOptions drawOptions;
+ drawOptions.mAntialiasMode =
+ nsSVGUtils::ToAntialiasMode(mFrame->StyleText()->mTextRendering);
+ gfx->GetDrawTarget()->Stroke(path, strokePattern, strokeOptions);
+ }
+ }
+ }
+}
+
+} // namespace mozilla
+
+
+// ============================================================================
+// SVGTextFrame
+
+// ----------------------------------------------------------------------------
+// Display list item
+
+class nsDisplaySVGText : public nsDisplayItem {
+public:
+ nsDisplaySVGText(nsDisplayListBuilder* aBuilder,
+ SVGTextFrame* aFrame)
+ : nsDisplayItem(aBuilder, aFrame),
+ mDisableSubpixelAA(false)
+ {
+ MOZ_COUNT_CTOR(nsDisplaySVGText);
+ MOZ_ASSERT(aFrame, "Must have a frame!");
+ }
+#ifdef NS_BUILD_REFCNT_LOGGING
+ virtual ~nsDisplaySVGText() {
+ MOZ_COUNT_DTOR(nsDisplaySVGText);
+ }
+#endif
+
+ NS_DISPLAY_DECL_NAME("nsDisplaySVGText", TYPE_SVG_TEXT)
+
+ virtual void DisableComponentAlpha() override {
+ mDisableSubpixelAA = true;
+ }
+ virtual void HitTest(nsDisplayListBuilder* aBuilder, const nsRect& aRect,
+ HitTestState* aState,
+ nsTArray<nsIFrame*> *aOutFrames) override;
+ virtual void Paint(nsDisplayListBuilder* aBuilder,
+ nsRenderingContext* aCtx) override;
+ nsDisplayItemGeometry* AllocateGeometry(nsDisplayListBuilder* aBuilder) override
+ {
+ return new nsDisplayItemGenericImageGeometry(this, aBuilder);
+ }
+ virtual nsRect GetComponentAlphaBounds(nsDisplayListBuilder* aBuilder) override {
+ bool snap;
+ return GetBounds(aBuilder, &snap);
+ }
+private:
+ bool mDisableSubpixelAA;
+};
+
+void
+nsDisplaySVGText::HitTest(nsDisplayListBuilder* aBuilder, const nsRect& aRect,
+ HitTestState* aState, nsTArray<nsIFrame*> *aOutFrames)
+{
+ SVGTextFrame *frame = static_cast<SVGTextFrame*>(mFrame);
+ nsPoint pointRelativeToReferenceFrame = aRect.Center();
+ // ToReferenceFrame() includes frame->GetPosition(), our user space position.
+ nsPoint userSpacePtInAppUnits = pointRelativeToReferenceFrame -
+ (ToReferenceFrame() - frame->GetPosition());
+
+ gfxPoint userSpacePt =
+ gfxPoint(userSpacePtInAppUnits.x, userSpacePtInAppUnits.y) /
+ frame->PresContext()->AppUnitsPerCSSPixel();
+
+ nsIFrame* target = frame->GetFrameForPoint(userSpacePt);
+ if (target) {
+ aOutFrames->AppendElement(target);
+ }
+}
+
+void
+nsDisplaySVGText::Paint(nsDisplayListBuilder* aBuilder,
+ nsRenderingContext* aCtx)
+{
+ DrawTargetAutoDisableSubpixelAntialiasing
+ disable(aCtx->GetDrawTarget(), mDisableSubpixelAA);
+
+ uint32_t appUnitsPerDevPixel = mFrame->PresContext()->AppUnitsPerDevPixel();
+
+ // ToReferenceFrame includes our mRect offset, but painting takes
+ // account of that too. To avoid double counting, we subtract that
+ // here.
+ nsPoint offset = ToReferenceFrame() - mFrame->GetPosition();
+
+ gfxPoint devPixelOffset =
+ nsLayoutUtils::PointToGfxPoint(offset, appUnitsPerDevPixel);
+
+ gfxMatrix tm = nsSVGIntegrationUtils::GetCSSPxToDevPxMatrix(mFrame) *
+ gfxMatrix::Translation(devPixelOffset);
+
+ gfxContext* ctx = aCtx->ThebesContext();
+ ctx->Save();
+ DrawResult result = static_cast<SVGTextFrame*>(mFrame)->PaintSVG(*ctx, tm);
+ nsDisplayItemGenericImageGeometry::UpdateDrawResult(this, result);
+ ctx->Restore();
+}
+
+// ---------------------------------------------------------------------
+// nsQueryFrame methods
+
+NS_QUERYFRAME_HEAD(SVGTextFrame)
+ NS_QUERYFRAME_ENTRY(SVGTextFrame)
+NS_QUERYFRAME_TAIL_INHERITING(nsSVGDisplayContainerFrame)
+
+// ---------------------------------------------------------------------
+// Implementation
+
+nsIFrame*
+NS_NewSVGTextFrame(nsIPresShell* aPresShell, nsStyleContext* aContext)
+{
+ return new (aPresShell) SVGTextFrame(aContext);
+}
+
+NS_IMPL_FRAMEARENA_HELPERS(SVGTextFrame)
+
+// ---------------------------------------------------------------------
+// nsIFrame methods
+
+void
+SVGTextFrame::Init(nsIContent* aContent,
+ nsContainerFrame* aParent,
+ nsIFrame* aPrevInFlow)
+{
+ NS_ASSERTION(aContent->IsSVGElement(nsGkAtoms::text), "Content is not an SVG text");
+
+ nsSVGDisplayContainerFrame::Init(aContent, aParent, aPrevInFlow);
+ AddStateBits((aParent->GetStateBits() & NS_STATE_SVG_CLIPPATH_CHILD) |
+ NS_FRAME_SVG_LAYOUT | NS_FRAME_IS_SVG_TEXT);
+
+ mMutationObserver = new MutationObserver(this);
+}
+
+void
+SVGTextFrame::BuildDisplayList(nsDisplayListBuilder* aBuilder,
+ const nsRect& aDirtyRect,
+ const nsDisplayListSet& aLists)
+{
+ if (NS_SUBTREE_DIRTY(this)) {
+ // We can sometimes be asked to paint before reflow happens and we
+ // have updated mPositions, etc. In this case, we just avoid
+ // painting.
+ return;
+ }
+ if (!IsVisibleForPainting(aBuilder) &&
+ aBuilder->IsForPainting()) {
+ return;
+ }
+ DisplayOutline(aBuilder, aLists);
+ aLists.Content()->AppendNewToTop(
+ new (aBuilder) nsDisplaySVGText(aBuilder, this));
+}
+
+nsresult
+SVGTextFrame::AttributeChanged(int32_t aNameSpaceID,
+ nsIAtom* aAttribute,
+ int32_t aModType)
+{
+ if (aNameSpaceID != kNameSpaceID_None)
+ return NS_OK;
+
+ if (aAttribute == nsGkAtoms::transform) {
+ // We don't invalidate for transform changes (the layers code does that).
+ // Also note that SVGTransformableElement::GetAttributeChangeHint will
+ // return nsChangeHint_UpdateOverflow for "transform" attribute changes
+ // and cause DoApplyRenderingChangeToTree to make the SchedulePaint call.
+
+ if (!(mState & NS_FRAME_FIRST_REFLOW) &&
+ mCanvasTM && mCanvasTM->IsSingular()) {
+ // We won't have calculated the glyph positions correctly.
+ NotifyGlyphMetricsChange();
+ }
+ mCanvasTM = nullptr;
+ } else if (IsGlyphPositioningAttribute(aAttribute) ||
+ aAttribute == nsGkAtoms::textLength ||
+ aAttribute == nsGkAtoms::lengthAdjust) {
+ NotifyGlyphMetricsChange();
+ }
+
+ return NS_OK;
+}
+
+nsIAtom *
+SVGTextFrame::GetType() const
+{
+ return nsGkAtoms::svgTextFrame;
+}
+
+void
+SVGTextFrame::DidSetStyleContext(nsStyleContext* aOldStyleContext)
+{
+ if (mState & NS_FRAME_IS_NONDISPLAY) {
+ // We need this DidSetStyleContext override to handle cases like this:
+ //
+ // <defs>
+ // <g>
+ // <mask>
+ // <text>...</text>
+ // </mask>
+ // </g>
+ // </defs>
+ //
+ // where the <text> is non-display, and a style change occurs on the <defs>,
+ // the <g>, the <mask>, or the <text> itself. If the style change happened
+ // on the parent of the <defs>, then in
+ // nsSVGDisplayContainerFrame::ReflowSVG, we would find the non-display
+ // <defs> container and then call ReflowSVGNonDisplayText on it. If we do
+ // not actually reflow the parent of the <defs>, then without this
+ // DidSetStyleContext we would (a) not cause the <text>'s anonymous block
+ // child to be reflowed when it is next painted, and (b) not cause the
+ // <text> to be repainted anyway since the user of the <mask> would not
+ // know it needs to be repainted.
+ ScheduleReflowSVGNonDisplayText(nsIPresShell::eStyleChange);
+ }
+}
+
+void
+SVGTextFrame::ReflowSVGNonDisplayText()
+{
+ MOZ_ASSERT(nsSVGUtils::AnyOuterSVGIsCallingReflowSVG(this),
+ "only call ReflowSVGNonDisplayText when an outer SVG frame is "
+ "under ReflowSVG");
+ MOZ_ASSERT(mState & NS_FRAME_IS_NONDISPLAY,
+ "only call ReflowSVGNonDisplayText if the frame is "
+ "NS_FRAME_IS_NONDISPLAY");
+
+ // We had a style change, so we mark this frame as dirty so that the next
+ // time it is painted, we reflow the anonymous block frame.
+ AddStateBits(NS_FRAME_IS_DIRTY);
+
+ // We also need to call InvalidateRenderingObservers, so that if the <text>
+ // element is within a <mask>, say, the element referencing the <mask> will
+ // be updated, which will then cause this SVGTextFrame to be painted and
+ // in doing so cause the anonymous block frame to be reflowed.
+ nsLayoutUtils::PostRestyleEvent(
+ mContent->AsElement(), nsRestyleHint(0),
+ nsChangeHint_InvalidateRenderingObservers);
+
+ // Finally, we need to actually reflow the anonymous block frame and update
+ // mPositions, in case we are being reflowed immediately after a DOM
+ // mutation that needs frame reconstruction.
+ MaybeReflowAnonymousBlockChild();
+ UpdateGlyphPositioning();
+}
+
+void
+SVGTextFrame::ScheduleReflowSVGNonDisplayText(nsIPresShell::IntrinsicDirty aReason)
+{
+ MOZ_ASSERT(!nsSVGUtils::OuterSVGIsCallingReflowSVG(this),
+ "do not call ScheduleReflowSVGNonDisplayText when the outer SVG "
+ "frame is under ReflowSVG");
+ MOZ_ASSERT(!(mState & NS_STATE_SVG_TEXT_IN_REFLOW),
+ "do not call ScheduleReflowSVGNonDisplayText while reflowing the "
+ "anonymous block child");
+
+ // We need to find an ancestor frame that we can call FrameNeedsReflow
+ // on that will cause the document to be marked as needing relayout,
+ // and for that ancestor (or some further ancestor) to be marked as
+ // a root to reflow. We choose the closest ancestor frame that is not
+ // NS_FRAME_IS_NONDISPLAY and which is either an outer SVG frame or a
+ // non-SVG frame. (We don't consider displayed SVG frame ancestors toerh
+ // than nsSVGOuterSVGFrame, since calling FrameNeedsReflow on those other
+ // SVG frames would do a bunch of unnecessary work on the SVG frames up to
+ // the nsSVGOuterSVGFrame.)
+
+ nsIFrame* f = this;
+ while (f) {
+ if (!(f->GetStateBits() & NS_FRAME_IS_NONDISPLAY)) {
+ if (NS_SUBTREE_DIRTY(f)) {
+ // This is a displayed frame, so if it is already dirty, we will be reflowed
+ // soon anyway. No need to call FrameNeedsReflow again, then.
+ return;
+ }
+ if (!f->IsFrameOfType(eSVG) ||
+ (f->GetStateBits() & NS_STATE_IS_OUTER_SVG)) {
+ break;
+ }
+ f->AddStateBits(NS_FRAME_HAS_DIRTY_CHILDREN);
+ }
+ f = f->GetParent();
+ }
+
+ MOZ_ASSERT(f, "should have found an ancestor frame to reflow");
+
+ PresContext()->PresShell()->FrameNeedsReflow(f, aReason, NS_FRAME_IS_DIRTY);
+}
+
+NS_IMPL_ISUPPORTS(SVGTextFrame::MutationObserver, nsIMutationObserver)
+
+void
+SVGTextFrame::MutationObserver::ContentAppended(nsIDocument* aDocument,
+ nsIContent* aContainer,
+ nsIContent* aFirstNewContent,
+ int32_t aNewIndexInContainer)
+{
+ mFrame->NotifyGlyphMetricsChange();
+}
+
+void
+SVGTextFrame::MutationObserver::ContentInserted(
+ nsIDocument* aDocument,
+ nsIContent* aContainer,
+ nsIContent* aChild,
+ int32_t aIndexInContainer)
+{
+ mFrame->NotifyGlyphMetricsChange();
+}
+
+void
+SVGTextFrame::MutationObserver::ContentRemoved(
+ nsIDocument *aDocument,
+ nsIContent* aContainer,
+ nsIContent* aChild,
+ int32_t aIndexInContainer,
+ nsIContent* aPreviousSibling)
+{
+ mFrame->NotifyGlyphMetricsChange();
+}
+
+void
+SVGTextFrame::MutationObserver::CharacterDataChanged(
+ nsIDocument* aDocument,
+ nsIContent* aContent,
+ CharacterDataChangeInfo* aInfo)
+{
+ mFrame->NotifyGlyphMetricsChange();
+}
+
+void
+SVGTextFrame::MutationObserver::AttributeChanged(
+ nsIDocument* aDocument,
+ mozilla::dom::Element* aElement,
+ int32_t aNameSpaceID,
+ nsIAtom* aAttribute,
+ int32_t aModType,
+ const nsAttrValue* aOldValue)
+{
+ if (!aElement->IsSVGElement()) {
+ return;
+ }
+
+ // Attribute changes on this element will be handled by
+ // SVGTextFrame::AttributeChanged.
+ if (aElement == mFrame->GetContent()) {
+ return;
+ }
+
+ mFrame->HandleAttributeChangeInDescendant(aElement, aNameSpaceID, aAttribute);
+}
+
+void
+SVGTextFrame::HandleAttributeChangeInDescendant(Element* aElement,
+ int32_t aNameSpaceID,
+ nsIAtom* aAttribute)
+{
+ if (aElement->IsSVGElement(nsGkAtoms::textPath)) {
+ if (aNameSpaceID == kNameSpaceID_None &&
+ aAttribute == nsGkAtoms::startOffset) {
+ NotifyGlyphMetricsChange();
+ } else if ((aNameSpaceID == kNameSpaceID_XLink ||
+ aNameSpaceID == kNameSpaceID_None) &&
+ aAttribute == nsGkAtoms::href) {
+ // Blow away our reference, if any
+ nsIFrame* childElementFrame = aElement->GetPrimaryFrame();
+ if (childElementFrame) {
+ childElementFrame->Properties().Delete(
+ nsSVGEffects::HrefAsTextPathProperty());
+ NotifyGlyphMetricsChange();
+ }
+ }
+ } else {
+ if (aNameSpaceID == kNameSpaceID_None &&
+ IsGlyphPositioningAttribute(aAttribute)) {
+ NotifyGlyphMetricsChange();
+ }
+ }
+}
+
+void
+SVGTextFrame::FindCloserFrameForSelection(
+ nsPoint aPoint,
+ nsIFrame::FrameWithDistance* aCurrentBestFrame)
+{
+ if (GetStateBits() & NS_FRAME_IS_NONDISPLAY) {
+ return;
+ }
+
+ UpdateGlyphPositioning();
+
+ nsPresContext* presContext = PresContext();
+
+ // Find the frame that has the closest rendered run rect to aPoint.
+ TextRenderedRunIterator it(this);
+ for (TextRenderedRun run = it.Current(); run.mFrame; run = it.Next()) {
+ uint32_t flags = TextRenderedRun::eIncludeFill |
+ TextRenderedRun::eIncludeStroke |
+ TextRenderedRun::eNoHorizontalOverflow;
+ SVGBBox userRect = run.GetUserSpaceRect(presContext, flags);
+ float devPxPerCSSPx = presContext->CSSPixelsToDevPixels(1.f);
+ userRect.Scale(devPxPerCSSPx);
+
+ if (!userRect.IsEmpty()) {
+ gfxMatrix m;
+ if (!NS_SVGDisplayListHitTestingEnabled()) {
+ m = GetCanvasTM();
+ }
+ nsRect rect = nsSVGUtils::ToCanvasBounds(userRect.ToThebesRect(), m,
+ presContext);
+
+ if (nsLayoutUtils::PointIsCloserToRect(aPoint, rect,
+ aCurrentBestFrame->mXDistance,
+ aCurrentBestFrame->mYDistance)) {
+ aCurrentBestFrame->mFrame = run.mFrame;
+ }
+ }
+ }
+}
+
+//----------------------------------------------------------------------
+// nsISVGChildFrame methods
+
+void
+SVGTextFrame::NotifySVGChanged(uint32_t aFlags)
+{
+ MOZ_ASSERT(aFlags & (TRANSFORM_CHANGED | COORD_CONTEXT_CHANGED),
+ "Invalidation logic may need adjusting");
+
+ bool needNewBounds = false;
+ bool needGlyphMetricsUpdate = false;
+ bool needNewCanvasTM = false;
+
+ if ((aFlags & COORD_CONTEXT_CHANGED) &&
+ (mState & NS_STATE_SVG_POSITIONING_MAY_USE_PERCENTAGES)) {
+ needGlyphMetricsUpdate = true;
+ }
+
+ if (aFlags & TRANSFORM_CHANGED) {
+ needNewCanvasTM = true;
+ if (mCanvasTM && mCanvasTM->IsSingular()) {
+ // We won't have calculated the glyph positions correctly.
+ needNewBounds = true;
+ needGlyphMetricsUpdate = true;
+ }
+ if (StyleSVGReset()->HasNonScalingStroke()) {
+ // Stroke currently contributes to our mRect, and our stroke depends on
+ // the transform to our outer-<svg> if |vector-effect:non-scaling-stroke|.
+ needNewBounds = true;
+ }
+ }
+
+ // If the scale at which we computed our mFontSizeScaleFactor has changed by
+ // at least a factor of two, reflow the text. This avoids reflowing text
+ // at every tick of a transform animation, but ensures our glyph metrics
+ // do not get too far out of sync with the final font size on the screen.
+ if (needNewCanvasTM && mLastContextScale != 0.0f) {
+ mCanvasTM = nullptr;
+ // If we are a non-display frame, then we don't want to call
+ // GetCanvasTM(), since the context scale does not use it.
+ gfxMatrix newTM =
+ (mState & NS_FRAME_IS_NONDISPLAY) ? gfxMatrix() :
+ GetCanvasTM();
+ // Compare the old and new context scales.
+ float scale = GetContextScale(newTM);
+ float change = scale / mLastContextScale;
+ if (change >= 2.0f || change <= 0.5f) {
+ needNewBounds = true;
+ needGlyphMetricsUpdate = true;
+ }
+ }
+
+ if (needNewBounds) {
+ // Ancestor changes can't affect how we render from the perspective of
+ // any rendering observers that we may have, so we don't need to
+ // invalidate them. We also don't need to invalidate ourself, since our
+ // changed ancestor will have invalidated its entire area, which includes
+ // our area.
+ ScheduleReflowSVG();
+ }
+
+ if (needGlyphMetricsUpdate) {
+ // If we are positioned using percentage values we need to update our
+ // position whenever our viewport's dimensions change. But only do this if
+ // we have been reflowed once, otherwise the glyph positioning will be
+ // wrong. (We need to wait until bidi reordering has been done.)
+ if (!(mState & NS_FRAME_FIRST_REFLOW)) {
+ NotifyGlyphMetricsChange();
+ }
+ }
+}
+
+/**
+ * Gets the offset into a DOM node that the specified caret is positioned at.
+ */
+static int32_t
+GetCaretOffset(nsCaret* aCaret)
+{
+ nsCOMPtr<nsISelection> selection = aCaret->GetSelection();
+ if (!selection) {
+ return -1;
+ }
+
+ int32_t offset = -1;
+ selection->GetAnchorOffset(&offset);
+ return offset;
+}
+
+/**
+ * Returns whether the caret should be painted for a given TextRenderedRun
+ * by checking whether the caret is in the range covered by the rendered run.
+ *
+ * @param aThisRun The TextRenderedRun to be painted.
+ * @param aCaret The caret.
+ */
+static bool
+ShouldPaintCaret(const TextRenderedRun& aThisRun, nsCaret* aCaret)
+{
+ int32_t caretOffset = GetCaretOffset(aCaret);
+
+ if (caretOffset < 0) {
+ return false;
+ }
+
+ if (uint32_t(caretOffset) >= aThisRun.mTextFrameContentOffset &&
+ uint32_t(caretOffset) < aThisRun.mTextFrameContentOffset +
+ aThisRun.mTextFrameContentLength) {
+ return true;
+ }
+
+ return false;
+}
+
+DrawResult
+SVGTextFrame::PaintSVG(gfxContext& aContext,
+ const gfxMatrix& aTransform,
+ const nsIntRect *aDirtyRect)
+{
+ DrawTarget& aDrawTarget = *aContext.GetDrawTarget();
+
+ nsIFrame* kid = PrincipalChildList().FirstChild();
+ if (!kid)
+ return DrawResult::SUCCESS;
+
+ nsPresContext* presContext = PresContext();
+
+ gfxMatrix initialMatrix = aContext.CurrentMatrix();
+
+ if (mState & NS_FRAME_IS_NONDISPLAY) {
+ // If we are in a canvas DrawWindow call that used the
+ // DRAWWINDOW_DO_NOT_FLUSH flag, then we may still have out
+ // of date frames. Just don't paint anything if they are
+ // dirty.
+ if (presContext->PresShell()->InDrawWindowNotFlushing() &&
+ NS_SUBTREE_DIRTY(this)) {
+ return DrawResult::SUCCESS;
+ }
+ // Text frames inside <clipPath>, <mask>, etc. will never have had
+ // ReflowSVG called on them, so call UpdateGlyphPositioning to do this now.
+ UpdateGlyphPositioning();
+ } else if (NS_SUBTREE_DIRTY(this)) {
+ // If we are asked to paint before reflow has recomputed mPositions etc.
+ // directly via PaintSVG, rather than via a display list, then we need
+ // to bail out here too.
+ return DrawResult::SUCCESS;
+ }
+
+ if (aTransform.IsSingular()) {
+ NS_WARNING("Can't render text element!");
+ return DrawResult::BAD_ARGS;
+ }
+
+ gfxMatrix matrixForPaintServers = aTransform * initialMatrix;
+
+ // Check if we need to draw anything.
+ if (aDirtyRect) {
+ NS_ASSERTION(!NS_SVGDisplayListPaintingEnabled() ||
+ (mState & NS_FRAME_IS_NONDISPLAY),
+ "Display lists handle dirty rect intersection test");
+ nsRect dirtyRect(aDirtyRect->x, aDirtyRect->y,
+ aDirtyRect->width, aDirtyRect->height);
+
+ gfxFloat appUnitsPerDevPixel = presContext->AppUnitsPerDevPixel();
+ gfxRect frameRect(mRect.x / appUnitsPerDevPixel,
+ mRect.y / appUnitsPerDevPixel,
+ mRect.width / appUnitsPerDevPixel,
+ mRect.height / appUnitsPerDevPixel);
+
+ nsRect canvasRect = nsLayoutUtils::RoundGfxRectToAppRect(
+ GetCanvasTM().TransformBounds(frameRect), 1);
+ if (!canvasRect.Intersects(dirtyRect)) {
+ return DrawResult::SUCCESS;
+ }
+ }
+
+ // SVG frames' PaintSVG methods paint in CSS px, but normally frames paint in
+ // dev pixels. Here we multiply a CSS-px-to-dev-pixel factor onto aTransform
+ // so our non-SVG nsTextFrame children paint correctly.
+ auto auPerDevPx = presContext->AppUnitsPerDevPixel();
+ float cssPxPerDevPx = presContext->AppUnitsToFloatCSSPixels(auPerDevPx);
+ gfxMatrix canvasTMForChildren = aTransform;
+ canvasTMForChildren.Scale(cssPxPerDevPx, cssPxPerDevPx);
+ initialMatrix.Scale(1 / cssPxPerDevPx, 1 / cssPxPerDevPx);
+
+ gfxContextAutoSaveRestore save(&aContext);
+ aContext.NewPath();
+ aContext.Multiply(canvasTMForChildren);
+ gfxMatrix currentMatrix = aContext.CurrentMatrix();
+
+ RefPtr<nsCaret> caret = presContext->PresShell()->GetCaret();
+ nsRect caretRect;
+ nsIFrame* caretFrame = caret->GetPaintGeometry(&caretRect);
+
+ TextRenderedRunIterator it(this, TextRenderedRunIterator::eVisibleFrames);
+ TextRenderedRun run = it.Current();
+
+ SVGContextPaint* outerContextPaint =
+ SVGContextPaint::GetContextPaint(mContent);
+
+ nsRenderingContext rendCtx(&aContext);
+
+ while (run.mFrame) {
+ nsTextFrame* frame = run.mFrame;
+
+ // Determine how much of the left and right edges of the text frame we
+ // need to ignore.
+ SVGCharClipDisplayItem item(run);
+
+ // Set up the fill and stroke so that SVG glyphs can get painted correctly
+ // when they use context-fill etc.
+ aContext.SetMatrix(initialMatrix);
+
+ SVGContextPaintImpl contextPaint;
+ DrawMode drawMode = contextPaint.Init(&aDrawTarget,
+ aContext.CurrentMatrix(),
+ frame, outerContextPaint);
+
+ if (drawMode & DrawMode::GLYPH_STROKE) {
+ // This may change the gfxContext's transform (for non-scaling stroke),
+ // in which case this needs to happen before we call SetMatrix() below.
+ nsSVGUtils::SetupCairoStrokeGeometry(frame, &aContext, outerContextPaint);
+ }
+
+ // Set up the transform for painting the text frame for the substring
+ // indicated by the run.
+ gfxMatrix runTransform =
+ run.GetTransformFromUserSpaceForPainting(presContext, item) *
+ currentMatrix;
+ aContext.SetMatrix(runTransform);
+
+ if (drawMode != DrawMode(0)) {
+ bool paintSVGGlyphs;
+ nsTextFrame::PaintTextParams params(rendCtx.ThebesContext());
+ params.framePt = gfxPoint();
+ params.dirtyRect = LayoutDevicePixel::
+ FromAppUnits(frame->GetVisualOverflowRect(), auPerDevPx);
+ params.contextPaint = &contextPaint;
+ if (ShouldRenderAsPath(frame, paintSVGGlyphs)) {
+ SVGTextDrawPathCallbacks callbacks(&rendCtx, frame,
+ matrixForPaintServers,
+ paintSVGGlyphs);
+ params.callbacks = &callbacks;
+ frame->PaintText(params, item);
+ } else {
+ frame->PaintText(params, item);
+ }
+ }
+
+ if (frame == caretFrame && ShouldPaintCaret(run, caret)) {
+ // XXX Should we be looking at the fill/stroke colours to paint the
+ // caret with, rather than using the color property?
+ caret->PaintCaret(aDrawTarget, frame, nsPoint());
+ aContext.NewPath();
+ }
+
+ run = it.Next();
+ }
+
+ return DrawResult::SUCCESS;
+}
+
+nsIFrame*
+SVGTextFrame::GetFrameForPoint(const gfxPoint& aPoint)
+{
+ NS_ASSERTION(PrincipalChildList().FirstChild(), "must have a child frame");
+
+ if (mState & NS_FRAME_IS_NONDISPLAY) {
+ // Text frames inside <clipPath> will never have had ReflowSVG called on
+ // them, so call UpdateGlyphPositioning to do this now. (Text frames
+ // inside <mask> and other non-display containers will never need to
+ // be hit tested.)
+ UpdateGlyphPositioning();
+ } else {
+ NS_ASSERTION(!NS_SUBTREE_DIRTY(this), "reflow should have happened");
+ }
+
+ // Hit-testing any clip-path will typically be a lot quicker than the
+ // hit-testing of our text frames in the loop below, so we do the former up
+ // front to avoid unnecessarily wasting cycles on the latter.
+ if (!nsSVGUtils::HitTestClip(this, aPoint)) {
+ return nullptr;
+ }
+
+ nsPresContext* presContext = PresContext();
+
+ // Ideally we'd iterate backwards so that we can just return the first frame
+ // that is under aPoint. In practice this will rarely matter though since it
+ // is rare for text in/under an SVG <text> element to overlap (i.e. the first
+ // text frame that is hit will likely be the only text frame that is hit).
+
+ TextRenderedRunIterator it(this);
+ nsIFrame* hit = nullptr;
+ for (TextRenderedRun run = it.Current(); run.mFrame; run = it.Next()) {
+ uint16_t hitTestFlags = nsSVGUtils::GetGeometryHitTestFlags(run.mFrame);
+ if (!(hitTestFlags & (SVG_HIT_TEST_FILL | SVG_HIT_TEST_STROKE))) {
+ continue;
+ }
+
+ gfxMatrix m = run.GetTransformFromRunUserSpaceToUserSpace(presContext);
+ if (!m.Invert()) {
+ return nullptr;
+ }
+
+ gfxPoint pointInRunUserSpace = m.Transform(aPoint);
+ gfxRect frameRect =
+ run.GetRunUserSpaceRect(presContext, TextRenderedRun::eIncludeFill |
+ TextRenderedRun::eIncludeStroke).ToThebesRect();
+
+ if (Inside(frameRect, pointInRunUserSpace)) {
+ hit = run.mFrame;
+ }
+ }
+ return hit;
+}
+
+nsRect
+SVGTextFrame::GetCoveredRegion()
+{
+ return nsSVGUtils::TransformFrameRectToOuterSVG(
+ mRect, GetCanvasTM(), PresContext());
+}
+
+void
+SVGTextFrame::ReflowSVG()
+{
+ NS_ASSERTION(nsSVGUtils::OuterSVGIsCallingReflowSVG(this),
+ "This call is probaby a wasteful mistake");
+
+ MOZ_ASSERT(!(GetStateBits() & NS_FRAME_IS_NONDISPLAY),
+ "ReflowSVG mechanism not designed for this");
+
+ if (!nsSVGUtils::NeedsReflowSVG(this)) {
+ NS_ASSERTION(!(mState & NS_STATE_SVG_POSITIONING_DIRTY), "How did this happen?");
+ return;
+ }
+
+ MaybeReflowAnonymousBlockChild();
+ UpdateGlyphPositioning();
+
+ nsPresContext* presContext = PresContext();
+
+ SVGBBox r;
+ TextRenderedRunIterator it(this, TextRenderedRunIterator::eAllFrames);
+ for (TextRenderedRun run = it.Current(); run.mFrame; run = it.Next()) {
+ uint32_t runFlags = 0;
+ if (run.mFrame->StyleSVG()->mFill.Type() != eStyleSVGPaintType_None) {
+ runFlags |= TextRenderedRun::eIncludeFill |
+ TextRenderedRun::eIncludeTextShadow;
+ }
+ if (nsSVGUtils::HasStroke(run.mFrame)) {
+ runFlags |= TextRenderedRun::eIncludeFill |
+ TextRenderedRun::eIncludeTextShadow;
+ }
+ // Our "visual" overflow rect needs to be valid for building display lists
+ // for hit testing, which means that for certain values of 'pointer-events'
+ // it needs to include the geometry of the fill or stroke even when the fill/
+ // stroke don't actually render (e.g. when stroke="none" or
+ // stroke-opacity="0"). GetGeometryHitTestFlags accounts for 'pointer-events'.
+ // The text-shadow is not part of the hit-test area.
+ uint16_t hitTestFlags = nsSVGUtils::GetGeometryHitTestFlags(run.mFrame);
+ if (hitTestFlags & SVG_HIT_TEST_FILL) {
+ runFlags |= TextRenderedRun::eIncludeFill;
+ }
+ if (hitTestFlags & SVG_HIT_TEST_STROKE) {
+ runFlags |= TextRenderedRun::eIncludeStroke;
+ }
+
+ if (runFlags) {
+ r.UnionEdges(run.GetUserSpaceRect(presContext, runFlags));
+ }
+ }
+
+ if (r.IsEmpty()) {
+ mRect.SetEmpty();
+ } else {
+ mRect =
+ nsLayoutUtils::RoundGfxRectToAppRect(r.ToThebesRect(), presContext->AppUnitsPerCSSPixel());
+
+ // Due to rounding issues when we have a transform applied, we sometimes
+ // don't include an additional row of pixels. For now, just inflate our
+ // covered region.
+ mRect.Inflate(presContext->AppUnitsPerDevPixel());
+ }
+
+ if (mState & NS_FRAME_FIRST_REFLOW) {
+ // Make sure we have our filter property (if any) before calling
+ // FinishAndStoreOverflow (subsequent filter changes are handled off
+ // nsChangeHint_UpdateEffects):
+ nsSVGEffects::UpdateEffects(this);
+ }
+
+ // Now unset the various reflow bits. Do this before calling
+ // FinishAndStoreOverflow since FinishAndStoreOverflow can require glyph
+ // positions (to resolve transform-origin).
+ mState &= ~(NS_FRAME_FIRST_REFLOW | NS_FRAME_IS_DIRTY |
+ NS_FRAME_HAS_DIRTY_CHILDREN);
+
+ nsRect overflow = nsRect(nsPoint(0,0), mRect.Size());
+ nsOverflowAreas overflowAreas(overflow, overflow);
+ FinishAndStoreOverflow(overflowAreas, mRect.Size());
+
+ // XXX nsSVGContainerFrame::ReflowSVG only looks at its nsISVGChildFrame
+ // children, and calls ConsiderChildOverflow on them. Does it matter
+ // that ConsiderChildOverflow won't be called on our children?
+ nsSVGDisplayContainerFrame::ReflowSVG();
+}
+
+/**
+ * Converts nsSVGUtils::eBBox* flags into TextRenderedRun flags appropriate
+ * for the specified rendered run.
+ */
+static uint32_t
+TextRenderedRunFlagsForBBoxContribution(const TextRenderedRun& aRun,
+ uint32_t aBBoxFlags)
+{
+ uint32_t flags = 0;
+ if ((aBBoxFlags & nsSVGUtils::eBBoxIncludeFillGeometry) ||
+ ((aBBoxFlags & nsSVGUtils::eBBoxIncludeFill) &&
+ aRun.mFrame->StyleSVG()->mFill.Type() != eStyleSVGPaintType_None)) {
+ flags |= TextRenderedRun::eIncludeFill;
+ }
+ if ((aBBoxFlags & nsSVGUtils::eBBoxIncludeStrokeGeometry) ||
+ ((aBBoxFlags & nsSVGUtils::eBBoxIncludeStroke) &&
+ nsSVGUtils::HasStroke(aRun.mFrame))) {
+ flags |= TextRenderedRun::eIncludeStroke;
+ }
+ return flags;
+}
+
+SVGBBox
+SVGTextFrame::GetBBoxContribution(const gfx::Matrix &aToBBoxUserspace,
+ uint32_t aFlags)
+{
+ NS_ASSERTION(PrincipalChildList().FirstChild(), "must have a child frame");
+ SVGBBox bbox;
+ nsIFrame* kid = PrincipalChildList().FirstChild();
+ if (kid && NS_SUBTREE_DIRTY(kid)) {
+ // Return an empty bbox if our kid's subtree is dirty. This may be called
+ // in that situation, e.g. when we're building a display list after an
+ // interrupted reflow. This can also be called during reflow before we've
+ // been reflowed, e.g. if an earlier sibling is calling FinishAndStoreOverflow and
+ // needs our parent's perspective matrix, which depends on the SVG bbox
+ // contribution of this frame. In the latter situation, when all siblings have
+ // been reflowed, the parent will compute its perspective and rerun
+ // FinishAndStoreOverflow for all its children.
+ return bbox;
+ }
+
+ UpdateGlyphPositioning();
+
+ nsPresContext* presContext = PresContext();
+
+ TextRenderedRunIterator it(this);
+ for (TextRenderedRun run = it.Current(); run.mFrame; run = it.Next()) {
+ uint32_t flags = TextRenderedRunFlagsForBBoxContribution(run, aFlags);
+ gfxMatrix m = ThebesMatrix(aToBBoxUserspace);
+ SVGBBox bboxForRun =
+ run.GetUserSpaceRect(presContext, flags, &m);
+ bbox.UnionEdges(bboxForRun);
+ }
+
+ return bbox;
+}
+
+//----------------------------------------------------------------------
+// nsSVGContainerFrame methods
+
+gfxMatrix
+SVGTextFrame::GetCanvasTM()
+{
+ if (!mCanvasTM) {
+ NS_ASSERTION(GetParent(), "null parent");
+ NS_ASSERTION(!(GetStateBits() & NS_FRAME_IS_NONDISPLAY),
+ "should not call GetCanvasTM() when we are non-display");
+
+ nsSVGContainerFrame *parent = static_cast<nsSVGContainerFrame*>(GetParent());
+ dom::SVGTextContentElement *content = static_cast<dom::SVGTextContentElement*>(mContent);
+
+ gfxMatrix tm = content->PrependLocalTransformsTo(parent->GetCanvasTM());
+
+ mCanvasTM = new gfxMatrix(tm);
+ }
+ return *mCanvasTM;
+}
+
+//----------------------------------------------------------------------
+// SVGTextFrame SVG DOM methods
+
+/**
+ * Returns whether the specified node has any non-empty nsTextNodes
+ * beneath it.
+ */
+static bool
+HasTextContent(nsIContent* aContent)
+{
+ NS_ASSERTION(aContent, "expected non-null aContent");
+
+ TextNodeIterator it(aContent);
+ for (nsTextNode* text = it.Current(); text; text = it.Next()) {
+ if (text->TextLength() != 0) {
+ return true;
+ }
+ }
+ return false;
+}
+
+/**
+ * Returns the number of DOM characters beneath the specified node.
+ */
+static uint32_t
+GetTextContentLength(nsIContent* aContent)
+{
+ NS_ASSERTION(aContent, "expected non-null aContent");
+
+ uint32_t length = 0;
+ TextNodeIterator it(aContent);
+ for (nsTextNode* text = it.Current(); text; text = it.Next()) {
+ length += text->TextLength();
+ }
+ return length;
+}
+
+int32_t
+SVGTextFrame::ConvertTextElementCharIndexToAddressableIndex(
+ int32_t aIndex,
+ nsIContent* aContent)
+{
+ CharIterator it(this, CharIterator::eOriginal, aContent);
+ if (!it.AdvanceToSubtree()) {
+ return -1;
+ }
+ int32_t result = 0;
+ int32_t textElementCharIndex;
+ while (!it.AtEnd() &&
+ it.IsWithinSubtree()) {
+ bool addressable = !it.IsOriginalCharUnaddressable();
+ textElementCharIndex = it.TextElementCharIndex();
+ it.Next();
+ uint32_t delta = it.TextElementCharIndex() - textElementCharIndex;
+ aIndex -= delta;
+ if (addressable) {
+ if (aIndex < 0) {
+ return result;
+ }
+ result += delta;
+ }
+ }
+ return -1;
+}
+
+/**
+ * Implements the SVG DOM GetNumberOfChars method for the specified
+ * text content element.
+ */
+uint32_t
+SVGTextFrame::GetNumberOfChars(nsIContent* aContent)
+{
+ UpdateGlyphPositioning();
+
+ uint32_t n = 0;
+ CharIterator it(this, CharIterator::eAddressable, aContent);
+ if (it.AdvanceToSubtree()) {
+ while (!it.AtEnd() && it.IsWithinSubtree()) {
+ n++;
+ it.Next();
+ }
+ }
+ return n;
+}
+
+/**
+ * Implements the SVG DOM GetComputedTextLength method for the specified
+ * text child element.
+ */
+float
+SVGTextFrame::GetComputedTextLength(nsIContent* aContent)
+{
+ UpdateGlyphPositioning();
+
+ float cssPxPerDevPx = PresContext()->
+ AppUnitsToFloatCSSPixels(PresContext()->AppUnitsPerDevPixel());
+
+ nscoord length = 0;
+ TextRenderedRunIterator it(this, TextRenderedRunIterator::eAllFrames,
+ aContent);
+ for (TextRenderedRun run = it.Current(); run.mFrame; run = it.Next()) {
+ length += run.GetAdvanceWidth();
+ }
+
+ return PresContext()->AppUnitsToGfxUnits(length) *
+ cssPxPerDevPx * mLengthAdjustScaleFactor / mFontSizeScaleFactor;
+}
+
+/**
+ * Implements the SVG DOM SelectSubString method for the specified
+ * text content element.
+ */
+nsresult
+SVGTextFrame::SelectSubString(nsIContent* aContent,
+ uint32_t charnum, uint32_t nchars)
+{
+ UpdateGlyphPositioning();
+
+ // Convert charnum/nchars from addressable characters relative to
+ // aContent to global character indices.
+ CharIterator chit(this, CharIterator::eAddressable, aContent);
+ if (!chit.AdvanceToSubtree() ||
+ !chit.Next(charnum) ||
+ chit.IsAfterSubtree()) {
+ return NS_ERROR_DOM_INDEX_SIZE_ERR;
+ }
+ charnum = chit.TextElementCharIndex();
+ nsIContent* content = chit.TextFrame()->GetContent();
+ chit.NextWithinSubtree(nchars);
+ nchars = chit.TextElementCharIndex() - charnum;
+
+ RefPtr<nsFrameSelection> frameSelection = GetFrameSelection();
+
+ frameSelection->HandleClick(content, charnum, charnum + nchars,
+ false, false, CARET_ASSOCIATE_BEFORE);
+ return NS_OK;
+}
+
+/**
+ * Implements the SVG DOM GetSubStringLength method for the specified
+ * text content element.
+ */
+nsresult
+SVGTextFrame::GetSubStringLength(nsIContent* aContent,
+ uint32_t charnum, uint32_t nchars,
+ float* aResult)
+{
+ UpdateGlyphPositioning();
+
+ // Convert charnum/nchars from addressable characters relative to
+ // aContent to global character indices.
+ CharIterator chit(this, CharIterator::eAddressable, aContent);
+ if (!chit.AdvanceToSubtree() ||
+ !chit.Next(charnum) ||
+ chit.IsAfterSubtree()) {
+ return NS_ERROR_DOM_INDEX_SIZE_ERR;
+ }
+
+ if (nchars == 0) {
+ *aResult = 0.0f;
+ return NS_OK;
+ }
+
+ charnum = chit.TextElementCharIndex();
+ chit.NextWithinSubtree(nchars);
+ nchars = chit.TextElementCharIndex() - charnum;
+
+ // Find each rendered run that intersects with the range defined
+ // by charnum/nchars.
+ nscoord textLength = 0;
+ TextRenderedRunIterator runIter(this, TextRenderedRunIterator::eAllFrames);
+ TextRenderedRun run = runIter.Current();
+ while (run.mFrame) {
+ // If this rendered run is past the substring we are interested in, we
+ // are done.
+ uint32_t offset = run.mTextElementCharIndex;
+ if (offset >= charnum + nchars) {
+ break;
+ }
+
+ // Intersect the substring we are interested in with the range covered by
+ // the rendered run.
+ uint32_t length = run.mTextFrameContentLength;
+ IntersectInterval(offset, length, charnum, nchars);
+
+ if (length != 0) {
+ // Convert offset into an index into the frame.
+ offset += run.mTextFrameContentOffset - run.mTextElementCharIndex;
+
+ gfxSkipCharsIterator skipCharsIter =
+ run.mFrame->EnsureTextRun(nsTextFrame::eInflated);
+ gfxTextRun* textRun = run.mFrame->GetTextRun(nsTextFrame::eInflated);
+ Range range = ConvertOriginalToSkipped(skipCharsIter, offset, length);
+
+ // Accumulate the advance.
+ textLength += textRun->GetAdvanceWidth(range, nullptr);
+ }
+
+ run = runIter.Next();
+ }
+
+ nsPresContext* presContext = PresContext();
+ float cssPxPerDevPx = presContext->
+ AppUnitsToFloatCSSPixels(presContext->AppUnitsPerDevPixel());
+
+ *aResult = presContext->AppUnitsToGfxUnits(textLength) *
+ cssPxPerDevPx / mFontSizeScaleFactor;
+ return NS_OK;
+}
+
+/**
+ * Implements the SVG DOM GetCharNumAtPosition method for the specified
+ * text content element.
+ */
+int32_t
+SVGTextFrame::GetCharNumAtPosition(nsIContent* aContent,
+ mozilla::nsISVGPoint* aPoint)
+{
+ UpdateGlyphPositioning();
+
+ nsPresContext* context = PresContext();
+
+ gfxPoint p(aPoint->X(), aPoint->Y());
+
+ int32_t result = -1;
+
+ TextRenderedRunIterator it(this, TextRenderedRunIterator::eAllFrames, aContent);
+ for (TextRenderedRun run = it.Current(); run.mFrame; run = it.Next()) {
+ // Hit test this rendered run. Later runs will override earlier ones.
+ int32_t index = run.GetCharNumAtPosition(context, p);
+ if (index != -1) {
+ result = index + run.mTextElementCharIndex;
+ }
+ }
+
+ if (result == -1) {
+ return result;
+ }
+
+ return ConvertTextElementCharIndexToAddressableIndex(result, aContent);
+}
+
+/**
+ * Implements the SVG DOM GetStartPositionOfChar method for the specified
+ * text content element.
+ */
+nsresult
+SVGTextFrame::GetStartPositionOfChar(nsIContent* aContent,
+ uint32_t aCharNum,
+ mozilla::nsISVGPoint** aResult)
+{
+ UpdateGlyphPositioning();
+
+ CharIterator it(this, CharIterator::eAddressable, aContent);
+ if (!it.AdvanceToSubtree() ||
+ !it.Next(aCharNum)) {
+ return NS_ERROR_DOM_INDEX_SIZE_ERR;
+ }
+
+ // We need to return the start position of the whole glyph.
+ uint32_t startIndex = it.GlyphStartTextElementCharIndex();
+
+ NS_ADDREF(*aResult =
+ new DOMSVGPoint(ToPoint(mPositions[startIndex].mPosition)));
+ return NS_OK;
+}
+
+/**
+ * Implements the SVG DOM GetEndPositionOfChar method for the specified
+ * text content element.
+ */
+nsresult
+SVGTextFrame::GetEndPositionOfChar(nsIContent* aContent,
+ uint32_t aCharNum,
+ mozilla::nsISVGPoint** aResult)
+{
+ UpdateGlyphPositioning();
+
+ CharIterator it(this, CharIterator::eAddressable, aContent);
+ if (!it.AdvanceToSubtree() ||
+ !it.Next(aCharNum)) {
+ return NS_ERROR_DOM_INDEX_SIZE_ERR;
+ }
+
+ // We need to return the end position of the whole glyph.
+ uint32_t startIndex = it.GlyphStartTextElementCharIndex();
+
+ // Get the advance of the glyph.
+ gfxFloat advance = it.GetGlyphAdvance(PresContext());
+ if (it.TextRun()->IsRightToLeft()) {
+ advance = -advance;
+ }
+
+ // The end position is the start position plus the advance in the direction
+ // of the glyph's rotation.
+ Matrix m =
+ Matrix::Rotation(mPositions[startIndex].mAngle) *
+ Matrix::Translation(ToPoint(mPositions[startIndex].mPosition));
+ Point p = m.TransformPoint(Point(advance / mFontSizeScaleFactor, 0));
+
+ NS_ADDREF(*aResult = new DOMSVGPoint(p));
+ return NS_OK;
+}
+
+/**
+ * Implements the SVG DOM GetExtentOfChar method for the specified
+ * text content element.
+ */
+nsresult
+SVGTextFrame::GetExtentOfChar(nsIContent* aContent,
+ uint32_t aCharNum,
+ dom::SVGIRect** aResult)
+{
+ UpdateGlyphPositioning();
+
+ CharIterator it(this, CharIterator::eAddressable, aContent);
+ if (!it.AdvanceToSubtree() ||
+ !it.Next(aCharNum)) {
+ return NS_ERROR_DOM_INDEX_SIZE_ERR;
+ }
+
+ nsPresContext* presContext = PresContext();
+
+ float cssPxPerDevPx = presContext->
+ AppUnitsToFloatCSSPixels(presContext->AppUnitsPerDevPixel());
+
+ // We need to return the extent of the whole glyph.
+ uint32_t startIndex = it.GlyphStartTextElementCharIndex();
+
+ // The ascent and descent gives the height of the glyph.
+ gfxFloat ascent, descent;
+ GetAscentAndDescentInAppUnits(it.TextFrame(), ascent, descent);
+
+ // Get the advance of the glyph.
+ gfxFloat advance = it.GetGlyphAdvance(presContext);
+ gfxFloat x = it.TextRun()->IsRightToLeft() ? -advance : 0.0;
+
+ // The horizontal extent is the origin of the glyph plus the advance
+ // in the direction of the glyph's rotation.
+ gfxMatrix m;
+ m.Translate(mPositions[startIndex].mPosition);
+ m.Rotate(mPositions[startIndex].mAngle);
+ m.Scale(1 / mFontSizeScaleFactor, 1 / mFontSizeScaleFactor);
+
+ gfxRect glyphRect;
+ if (it.TextRun()->IsVertical()) {
+ glyphRect =
+ gfxRect(-presContext->AppUnitsToGfxUnits(descent) * cssPxPerDevPx, x,
+ presContext->AppUnitsToGfxUnits(ascent + descent) * cssPxPerDevPx,
+ advance);
+ } else {
+ glyphRect =
+ gfxRect(x, -presContext->AppUnitsToGfxUnits(ascent) * cssPxPerDevPx,
+ advance,
+ presContext->AppUnitsToGfxUnits(ascent + descent) * cssPxPerDevPx);
+ }
+
+ // Transform the glyph's rect into user space.
+ gfxRect r = m.TransformBounds(glyphRect);
+
+ NS_ADDREF(*aResult = new dom::SVGRect(aContent, r.x, r.y, r.width, r.height));
+ return NS_OK;
+}
+
+/**
+ * Implements the SVG DOM GetRotationOfChar method for the specified
+ * text content element.
+ */
+nsresult
+SVGTextFrame::GetRotationOfChar(nsIContent* aContent,
+ uint32_t aCharNum,
+ float* aResult)
+{
+ UpdateGlyphPositioning();
+
+ CharIterator it(this, CharIterator::eAddressable, aContent);
+ if (!it.AdvanceToSubtree() ||
+ !it.Next(aCharNum)) {
+ return NS_ERROR_DOM_INDEX_SIZE_ERR;
+ }
+
+ *aResult = mPositions[it.TextElementCharIndex()].mAngle * 180.0 / M_PI;
+ return NS_OK;
+}
+
+//----------------------------------------------------------------------
+// SVGTextFrame text layout methods
+
+/**
+ * Given the character position array before values have been filled in
+ * to any unspecified positions, and an array of dx/dy values, returns whether
+ * a character at a given index should start a new rendered run.
+ *
+ * @param aPositions The array of character positions before unspecified
+ * positions have been filled in and dx/dy values have been added to them.
+ * @param aDeltas The array of dx/dy values.
+ * @param aIndex The character index in question.
+ */
+static bool
+ShouldStartRunAtIndex(const nsTArray<CharPosition>& aPositions,
+ const nsTArray<gfxPoint>& aDeltas,
+ uint32_t aIndex)
+{
+ if (aIndex == 0) {
+ return true;
+ }
+
+ if (aIndex < aPositions.Length()) {
+ // If an explicit x or y value was given, start a new run.
+ if (aPositions[aIndex].IsXSpecified() ||
+ aPositions[aIndex].IsYSpecified()) {
+ return true;
+ }
+
+ // If a non-zero rotation was given, or the previous character had a non-
+ // zero rotation, start a new run.
+ if ((aPositions[aIndex].IsAngleSpecified() &&
+ aPositions[aIndex].mAngle != 0.0f) ||
+ (aPositions[aIndex - 1].IsAngleSpecified() &&
+ (aPositions[aIndex - 1].mAngle != 0.0f))) {
+ return true;
+ }
+ }
+
+ if (aIndex < aDeltas.Length()) {
+ // If a non-zero dx or dy value was given, start a new run.
+ if (aDeltas[aIndex].x != 0.0 ||
+ aDeltas[aIndex].y != 0.0) {
+ return true;
+ }
+ }
+
+ return false;
+}
+
+bool
+SVGTextFrame::ResolvePositionsForNode(nsIContent* aContent,
+ uint32_t& aIndex,
+ bool aInTextPath,
+ bool& aForceStartOfChunk,
+ nsTArray<gfxPoint>& aDeltas)
+{
+ if (aContent->IsNodeOfType(nsINode::eTEXT)) {
+ // We found a text node.
+ uint32_t length = static_cast<nsTextNode*>(aContent)->TextLength();
+ if (length) {
+ uint32_t end = aIndex + length;
+ if (MOZ_UNLIKELY(end > mPositions.Length())) {
+ MOZ_ASSERT_UNREACHABLE("length of mPositions does not match characters "
+ "found by iterating content");
+ return false;
+ }
+ if (aForceStartOfChunk) {
+ // Note this character as starting a new anchored chunk.
+ mPositions[aIndex].mStartOfChunk = true;
+ aForceStartOfChunk = false;
+ }
+ while (aIndex < end) {
+ // Record whether each of these characters should start a new rendered
+ // run. That is always the case for characters on a text path.
+ //
+ // Run boundaries due to rotate="" values are handled in
+ // DoGlyphPositioning.
+ if (aInTextPath || ShouldStartRunAtIndex(mPositions, aDeltas, aIndex)) {
+ mPositions[aIndex].mRunBoundary = true;
+ }
+ aIndex++;
+ }
+ }
+ return true;
+ }
+
+ // Skip past elements that aren't text content elements.
+ if (!IsTextContentElement(aContent)) {
+ return true;
+ }
+
+ if (aContent->IsSVGElement(nsGkAtoms::textPath)) {
+ // <textPath> elements are as if they are specified with x="0" y="0", but
+ // only if they actually have some text content.
+ if (HasTextContent(aContent)) {
+ if (MOZ_UNLIKELY(aIndex >= mPositions.Length())) {
+ MOZ_ASSERT_UNREACHABLE("length of mPositions does not match characters "
+ "found by iterating content");
+ return false;
+ }
+ mPositions[aIndex].mPosition = gfxPoint();
+ mPositions[aIndex].mStartOfChunk = true;
+ }
+ } else if (!aContent->IsSVGElement(nsGkAtoms::a)) {
+ // We have a text content element that can have x/y/dx/dy/rotate attributes.
+ nsSVGElement* element = static_cast<nsSVGElement*>(aContent);
+
+ // Get x, y, dx, dy.
+ SVGUserUnitList x, y, dx, dy;
+ element->GetAnimatedLengthListValues(&x, &y, &dx, &dy, nullptr);
+
+ // Get rotate.
+ const SVGNumberList* rotate = nullptr;
+ SVGAnimatedNumberList* animatedRotate =
+ element->GetAnimatedNumberList(nsGkAtoms::rotate);
+ if (animatedRotate) {
+ rotate = &animatedRotate->GetAnimValue();
+ }
+
+ bool percentages = false;
+ uint32_t count = GetTextContentLength(aContent);
+
+ if (MOZ_UNLIKELY(aIndex + count > mPositions.Length())) {
+ MOZ_ASSERT_UNREACHABLE("length of mPositions does not match characters "
+ "found by iterating content");
+ return false;
+ }
+
+ // New text anchoring chunks start at each character assigned a position
+ // with x="" or y="", or if we forced one with aForceStartOfChunk due to
+ // being just after a <textPath>.
+ uint32_t newChunkCount = std::max(x.Length(), y.Length());
+ if (!newChunkCount && aForceStartOfChunk) {
+ newChunkCount = 1;
+ }
+ for (uint32_t i = 0, j = 0; i < newChunkCount && j < count; j++) {
+ if (!mPositions[aIndex + j].mUnaddressable) {
+ mPositions[aIndex + j].mStartOfChunk = true;
+ i++;
+ }
+ }
+
+ // Copy dx="" and dy="" values into aDeltas.
+ if (!dx.IsEmpty() || !dy.IsEmpty()) {
+ // Any unspecified deltas when we grow the array just get left as 0s.
+ aDeltas.EnsureLengthAtLeast(aIndex + count);
+ for (uint32_t i = 0, j = 0; i < dx.Length() && j < count; j++) {
+ if (!mPositions[aIndex + j].mUnaddressable) {
+ aDeltas[aIndex + j].x = dx[i];
+ percentages = percentages || dx.HasPercentageValueAt(i);
+ i++;
+ }
+ }
+ for (uint32_t i = 0, j = 0; i < dy.Length() && j < count; j++) {
+ if (!mPositions[aIndex + j].mUnaddressable) {
+ aDeltas[aIndex + j].y = dy[i];
+ percentages = percentages || dy.HasPercentageValueAt(i);
+ i++;
+ }
+ }
+ }
+
+ // Copy x="" and y="" values.
+ for (uint32_t i = 0, j = 0; i < x.Length() && j < count; j++) {
+ if (!mPositions[aIndex + j].mUnaddressable) {
+ mPositions[aIndex + j].mPosition.x = x[i];
+ percentages = percentages || x.HasPercentageValueAt(i);
+ i++;
+ }
+ }
+ for (uint32_t i = 0, j = 0; i < y.Length() && j < count; j++) {
+ if (!mPositions[aIndex + j].mUnaddressable) {
+ mPositions[aIndex + j].mPosition.y = y[i];
+ percentages = percentages || y.HasPercentageValueAt(i);
+ i++;
+ }
+ }
+
+ // Copy rotate="" values.
+ if (rotate && !rotate->IsEmpty()) {
+ uint32_t i = 0, j = 0;
+ while (i < rotate->Length() && j < count) {
+ if (!mPositions[aIndex + j].mUnaddressable) {
+ mPositions[aIndex + j].mAngle = M_PI * (*rotate)[i] / 180.0;
+ i++;
+ }
+ j++;
+ }
+ // Propagate final rotate="" value to the end of this element.
+ while (j < count) {
+ mPositions[aIndex + j].mAngle = mPositions[aIndex + j - 1].mAngle;
+ j++;
+ }
+ }
+
+ if (percentages) {
+ AddStateBits(NS_STATE_SVG_POSITIONING_MAY_USE_PERCENTAGES);
+ }
+ }
+
+ // Recurse to children.
+ bool inTextPath = aInTextPath || aContent->IsSVGElement(nsGkAtoms::textPath);
+ for (nsIContent* child = aContent->GetFirstChild();
+ child;
+ child = child->GetNextSibling()) {
+ bool ok = ResolvePositionsForNode(child, aIndex, inTextPath,
+ aForceStartOfChunk, aDeltas);
+ if (!ok) {
+ return false;
+ }
+ }
+
+ if (aContent->IsSVGElement(nsGkAtoms::textPath)) {
+ // Force a new anchored chunk just after a <textPath>.
+ aForceStartOfChunk = true;
+ }
+
+ return true;
+}
+
+bool
+SVGTextFrame::ResolvePositions(nsTArray<gfxPoint>& aDeltas,
+ bool aRunPerGlyph)
+{
+ NS_ASSERTION(mPositions.IsEmpty(), "expected mPositions to be empty");
+ RemoveStateBits(NS_STATE_SVG_POSITIONING_MAY_USE_PERCENTAGES);
+
+ CharIterator it(this, CharIterator::eOriginal);
+ if (it.AtEnd()) {
+ return false;
+ }
+
+ // We assume the first character position is (0,0) unless we later see
+ // otherwise, and note it as unaddressable if it is.
+ bool firstCharUnaddressable = it.IsOriginalCharUnaddressable();
+ mPositions.AppendElement(CharPosition::Unspecified(firstCharUnaddressable));
+
+ // Fill in unspecified positions for all remaining characters, noting
+ // them as unaddressable if they are.
+ uint32_t index = 0;
+ while (it.Next()) {
+ while (++index < it.TextElementCharIndex()) {
+ mPositions.AppendElement(CharPosition::Unspecified(false));
+ }
+ mPositions.AppendElement(CharPosition::Unspecified(
+ it.IsOriginalCharUnaddressable()));
+ }
+ while (++index < it.TextElementCharIndex()) {
+ mPositions.AppendElement(CharPosition::Unspecified(false));
+ }
+
+ // Recurse over the content and fill in character positions as we go.
+ bool forceStartOfChunk = false;
+ index = 0;
+ bool ok = ResolvePositionsForNode(mContent, index, aRunPerGlyph,
+ forceStartOfChunk, aDeltas);
+ return ok && index > 0;
+}
+
+void
+SVGTextFrame::DetermineCharPositions(nsTArray<nsPoint>& aPositions)
+{
+ NS_ASSERTION(aPositions.IsEmpty(), "expected aPositions to be empty");
+
+ nsPoint position, lastPosition;
+
+ TextFrameIterator frit(this);
+ for (nsTextFrame* frame = frit.Current(); frame; frame = frit.Next()) {
+ gfxSkipCharsIterator it = frame->EnsureTextRun(nsTextFrame::eInflated);
+ gfxTextRun* textRun = frame->GetTextRun(nsTextFrame::eInflated);
+
+ // Reset the position to the new frame's position.
+ position = frit.Position();
+ if (textRun->IsVertical()) {
+ if (textRun->IsRightToLeft()) {
+ position.y += frame->GetRect().height;
+ }
+ position.x += GetBaselinePosition(frame, textRun,
+ frit.DominantBaseline(),
+ mFontSizeScaleFactor);
+ } else {
+ if (textRun->IsRightToLeft()) {
+ position.x += frame->GetRect().width;
+ }
+ position.y += GetBaselinePosition(frame, textRun,
+ frit.DominantBaseline(),
+ mFontSizeScaleFactor);
+ }
+
+ // Any characters not in a frame, e.g. when display:none.
+ for (uint32_t i = 0; i < frit.UndisplayedCharacters(); i++) {
+ aPositions.AppendElement(position);
+ }
+
+ // Any white space characters trimmed at the start of the line of text.
+ nsTextFrame::TrimmedOffsets trimmedOffsets =
+ frame->GetTrimmedOffsets(frame->GetContent()->GetText(), true);
+ while (it.GetOriginalOffset() < trimmedOffsets.mStart) {
+ aPositions.AppendElement(position);
+ it.AdvanceOriginal(1);
+ }
+
+ // If a ligature was started in the previous frame, we should record
+ // the ligature's start position, not any partial position.
+ while (it.GetOriginalOffset() < frame->GetContentEnd() &&
+ !it.IsOriginalCharSkipped() &&
+ (!textRun->IsLigatureGroupStart(it.GetSkippedOffset()) ||
+ !textRun->IsClusterStart(it.GetSkippedOffset()))) {
+ uint32_t offset = it.GetSkippedOffset();
+ nscoord advance = textRun->
+ GetAdvanceWidth(Range(offset, offset + 1), nullptr);
+ (textRun->IsVertical() ? position.y : position.x) +=
+ textRun->IsRightToLeft() ? -advance : advance;
+ aPositions.AppendElement(lastPosition);
+ it.AdvanceOriginal(1);
+ }
+
+ // The meat of the text frame.
+ while (it.GetOriginalOffset() < frame->GetContentEnd()) {
+ aPositions.AppendElement(position);
+ if (!it.IsOriginalCharSkipped() &&
+ textRun->IsLigatureGroupStart(it.GetSkippedOffset()) &&
+ textRun->IsClusterStart(it.GetSkippedOffset())) {
+ // A real visible character.
+ nscoord advance = textRun->
+ GetAdvanceWidth(ClusterRange(textRun, it), nullptr);
+ (textRun->IsVertical() ? position.y : position.x) +=
+ textRun->IsRightToLeft() ? -advance : advance;
+ lastPosition = position;
+ }
+ it.AdvanceOriginal(1);
+ }
+ }
+
+ // Finally any characters at the end that are not in a frame.
+ for (uint32_t i = 0; i < frit.UndisplayedCharacters(); i++) {
+ aPositions.AppendElement(position);
+ }
+}
+
+/**
+ * Physical text-anchor values.
+ */
+enum TextAnchorSide {
+ eAnchorLeft,
+ eAnchorMiddle,
+ eAnchorRight
+};
+
+/**
+ * Converts a logical text-anchor value to its physical value, based on whether
+ * it is for an RTL frame.
+ */
+static TextAnchorSide
+ConvertLogicalTextAnchorToPhysical(uint8_t aTextAnchor, bool aIsRightToLeft)
+{
+ NS_ASSERTION(aTextAnchor <= 3, "unexpected value for aTextAnchor");
+ if (!aIsRightToLeft)
+ return TextAnchorSide(aTextAnchor);
+ return TextAnchorSide(2 - aTextAnchor);
+}
+
+/**
+ * Shifts the recorded character positions for an anchored chunk.
+ *
+ * @param aCharPositions The recorded character positions.
+ * @param aChunkStart The character index the starts the anchored chunk. This
+ * character's initial position is the anchor point.
+ * @param aChunkEnd The character index just after the end of the anchored
+ * chunk.
+ * @param aVisIStartEdge The left/top-most edge of any of the glyphs within the
+ * anchored chunk.
+ * @param aVisIEndEdge The right/bottom-most edge of any of the glyphs within
+ * the anchored chunk.
+ * @param aAnchorSide The direction to anchor.
+ */
+static void
+ShiftAnchoredChunk(nsTArray<mozilla::CharPosition>& aCharPositions,
+ uint32_t aChunkStart,
+ uint32_t aChunkEnd,
+ gfxFloat aVisIStartEdge,
+ gfxFloat aVisIEndEdge,
+ TextAnchorSide aAnchorSide,
+ bool aVertical)
+{
+ NS_ASSERTION(aVisIStartEdge <= aVisIEndEdge,
+ "unexpected anchored chunk edges");
+ NS_ASSERTION(aChunkStart < aChunkEnd,
+ "unexpected values for aChunkStart and aChunkEnd");
+
+ gfxFloat shift = aVertical ? aCharPositions[aChunkStart].mPosition.y
+ : aCharPositions[aChunkStart].mPosition.x;
+ switch (aAnchorSide) {
+ case eAnchorLeft:
+ shift -= aVisIStartEdge;
+ break;
+ case eAnchorMiddle:
+ shift -= (aVisIStartEdge + aVisIEndEdge) / 2;
+ break;
+ case eAnchorRight:
+ shift -= aVisIEndEdge;
+ break;
+ default:
+ NS_NOTREACHED("unexpected value for aAnchorSide");
+ }
+
+ if (shift != 0.0) {
+ if (aVertical) {
+ for (uint32_t i = aChunkStart; i < aChunkEnd; i++) {
+ aCharPositions[i].mPosition.y += shift;
+ }
+ } else {
+ for (uint32_t i = aChunkStart; i < aChunkEnd; i++) {
+ aCharPositions[i].mPosition.x += shift;
+ }
+ }
+ }
+}
+
+void
+SVGTextFrame::AdjustChunksForLineBreaks()
+{
+ nsBlockFrame* block = nsLayoutUtils::GetAsBlock(PrincipalChildList().FirstChild());
+ NS_ASSERTION(block, "expected block frame");
+
+ nsBlockFrame::LineIterator line = block->LinesBegin();
+
+ CharIterator it(this, CharIterator::eOriginal);
+ while (!it.AtEnd() && line != block->LinesEnd()) {
+ if (it.TextFrame() == line->mFirstChild) {
+ mPositions[it.TextElementCharIndex()].mStartOfChunk = true;
+ line++;
+ }
+ it.AdvancePastCurrentFrame();
+ }
+}
+
+void
+SVGTextFrame::AdjustPositionsForClusters()
+{
+ nsPresContext* presContext = PresContext();
+
+ CharIterator it(this, CharIterator::eClusterOrLigatureGroupMiddle);
+ while (!it.AtEnd()) {
+ // Find the start of the cluster/ligature group.
+ uint32_t charIndex = it.TextElementCharIndex();
+ uint32_t startIndex = it.GlyphStartTextElementCharIndex();
+
+ mPositions[charIndex].mClusterOrLigatureGroupMiddle = true;
+
+ // Don't allow different rotations on ligature parts.
+ bool rotationAdjusted = false;
+ double angle = mPositions[startIndex].mAngle;
+ if (mPositions[charIndex].mAngle != angle) {
+ mPositions[charIndex].mAngle = angle;
+ rotationAdjusted = true;
+ }
+
+ // Find out the partial glyph advance for this character and update
+ // the character position.
+ uint32_t partLength =
+ charIndex - startIndex - it.GlyphUndisplayedCharacters();
+ gfxFloat advance =
+ it.GetGlyphPartialAdvance(partLength, presContext) / mFontSizeScaleFactor;
+ gfxPoint direction = gfxPoint(cos(angle), sin(angle)) *
+ (it.TextRun()->IsRightToLeft() ? -1.0 : 1.0);
+ if (it.TextRun()->IsVertical()) {
+ Swap(direction.x, direction.y);
+ }
+ mPositions[charIndex].mPosition = mPositions[startIndex].mPosition +
+ direction * advance;
+
+ // Ensure any runs that would end in the middle of a ligature now end just
+ // after the ligature.
+ if (mPositions[charIndex].mRunBoundary) {
+ mPositions[charIndex].mRunBoundary = false;
+ if (charIndex + 1 < mPositions.Length()) {
+ mPositions[charIndex + 1].mRunBoundary = true;
+ }
+ } else if (rotationAdjusted) {
+ if (charIndex + 1 < mPositions.Length()) {
+ mPositions[charIndex + 1].mRunBoundary = true;
+ }
+ }
+
+ // Ensure any anchored chunks that would begin in the middle of a ligature
+ // now begin just after the ligature.
+ if (mPositions[charIndex].mStartOfChunk) {
+ mPositions[charIndex].mStartOfChunk = false;
+ if (charIndex + 1 < mPositions.Length()) {
+ mPositions[charIndex + 1].mStartOfChunk = true;
+ }
+ }
+
+ it.Next();
+ }
+}
+
+SVGPathElement*
+SVGTextFrame::GetTextPathPathElement(nsIFrame* aTextPathFrame)
+{
+ nsSVGTextPathProperty *property =
+ aTextPathFrame->Properties().Get(nsSVGEffects::HrefAsTextPathProperty());
+
+ if (!property) {
+ nsIContent* content = aTextPathFrame->GetContent();
+ dom::SVGTextPathElement* tp = static_cast<dom::SVGTextPathElement*>(content);
+ nsAutoString href;
+ if (tp->mStringAttributes[dom::SVGTextPathElement::HREF].IsExplicitlySet()) {
+ tp->mStringAttributes[dom::SVGTextPathElement::HREF]
+ .GetAnimValue(href, tp);
+ } else {
+ tp->mStringAttributes[dom::SVGTextPathElement::XLINK_HREF]
+ .GetAnimValue(href, tp);
+ }
+
+ if (href.IsEmpty()) {
+ return nullptr; // no URL
+ }
+
+ nsCOMPtr<nsIURI> targetURI;
+ nsCOMPtr<nsIURI> base = content->GetBaseURI();
+ nsContentUtils::NewURIWithDocumentCharset(getter_AddRefs(targetURI), href,
+ content->GetUncomposedDoc(), base);
+
+ property = nsSVGEffects::GetTextPathProperty(
+ targetURI,
+ aTextPathFrame,
+ nsSVGEffects::HrefAsTextPathProperty());
+ if (!property)
+ return nullptr;
+ }
+
+ Element* element = property->GetReferencedElement();
+ return (element && element->IsSVGElement(nsGkAtoms::path)) ?
+ static_cast<SVGPathElement*>(element) : nullptr;
+}
+
+already_AddRefed<Path>
+SVGTextFrame::GetTextPath(nsIFrame* aTextPathFrame)
+{
+ SVGPathElement* element = GetTextPathPathElement(aTextPathFrame);
+ if (!element) {
+ return nullptr;
+ }
+
+ RefPtr<Path> path = element->GetOrBuildPathForMeasuring();
+ if (!path) {
+ return nullptr;
+ }
+
+ gfxMatrix matrix = element->PrependLocalTransformsTo(gfxMatrix());
+ if (!matrix.IsIdentity()) {
+ RefPtr<PathBuilder> builder =
+ path->TransformedCopyToBuilder(ToMatrix(matrix));
+ path = builder->Finish();
+ }
+
+ return path.forget();
+}
+
+gfxFloat
+SVGTextFrame::GetOffsetScale(nsIFrame* aTextPathFrame)
+{
+ SVGPathElement* pathElement = GetTextPathPathElement(aTextPathFrame);
+ if (!pathElement)
+ return 1.0;
+
+ return pathElement->GetPathLengthScale(dom::SVGPathElement::eForTextPath);
+}
+
+gfxFloat
+SVGTextFrame::GetStartOffset(nsIFrame* aTextPathFrame)
+{
+ dom::SVGTextPathElement *tp =
+ static_cast<dom::SVGTextPathElement*>(aTextPathFrame->GetContent());
+ nsSVGLength2 *length =
+ &tp->mLengthAttributes[dom::SVGTextPathElement::STARTOFFSET];
+
+ if (length->IsPercentage()) {
+ RefPtr<Path> data = GetTextPath(aTextPathFrame);
+ return data ?
+ length->GetAnimValInSpecifiedUnits() * data->ComputeLength() / 100.0 :
+ 0.0;
+ }
+ return length->GetAnimValue(tp) * GetOffsetScale(aTextPathFrame);
+}
+
+void
+SVGTextFrame::DoTextPathLayout()
+{
+ nsPresContext* context = PresContext();
+
+ CharIterator it(this, CharIterator::eClusterAndLigatureGroupStart);
+ while (!it.AtEnd()) {
+ nsIFrame* textPathFrame = it.TextPathFrame();
+ if (!textPathFrame) {
+ // Skip past this frame if we're not in a text path.
+ it.AdvancePastCurrentFrame();
+ continue;
+ }
+
+ // Get the path itself.
+ RefPtr<Path> path = GetTextPath(textPathFrame);
+ if (!path) {
+ it.AdvancePastCurrentTextPathFrame();
+ continue;
+ }
+
+ nsIContent* textPath = textPathFrame->GetContent();
+
+ gfxFloat offset = GetStartOffset(textPathFrame);
+ Float pathLength = path->ComputeLength();
+
+ // Loop for each text frame in the text path.
+ do {
+ uint32_t i = it.TextElementCharIndex();
+ gfxFloat halfAdvance =
+ it.GetGlyphAdvance(context) / mFontSizeScaleFactor / 2.0;
+ gfxFloat sign = it.TextRun()->IsRightToLeft() ? -1.0 : 1.0;
+ bool vertical = it.TextRun()->IsVertical();
+ gfxFloat midx = (vertical ? mPositions[i].mPosition.y
+ : mPositions[i].mPosition.x) +
+ sign * halfAdvance + offset;
+
+ // Hide the character if it falls off the end of the path.
+ mPositions[i].mHidden = midx < 0 || midx > pathLength;
+
+ // Position the character on the path at the right angle.
+ Point tangent; // Unit vector tangent to the point we find.
+ Point pt = path->ComputePointAtLength(Float(midx), &tangent);
+ Float rotation = vertical ? atan2f(-tangent.x, tangent.y)
+ : atan2f(tangent.y, tangent.x);
+ Point normal(-tangent.y, tangent.x); // Unit vector normal to the point.
+ Point offsetFromPath = normal * (vertical ? -mPositions[i].mPosition.x
+ : mPositions[i].mPosition.y);
+ pt += offsetFromPath;
+ Point direction = tangent * sign;
+ mPositions[i].mPosition = ThebesPoint(pt) - ThebesPoint(direction) * halfAdvance;
+ mPositions[i].mAngle += rotation;
+
+ // Position any characters for a partial ligature.
+ for (uint32_t j = i + 1;
+ j < mPositions.Length() && mPositions[j].mClusterOrLigatureGroupMiddle;
+ j++) {
+ gfxPoint partialAdvance =
+ ThebesPoint(direction) * it.GetGlyphPartialAdvance(j - i, context) /
+ mFontSizeScaleFactor;
+ mPositions[j].mPosition = mPositions[i].mPosition + partialAdvance;
+ mPositions[j].mAngle = mPositions[i].mAngle;
+ mPositions[j].mHidden = mPositions[i].mHidden;
+ }
+ it.Next();
+ } while (it.TextPathFrame() &&
+ it.TextPathFrame()->GetContent() == textPath);
+ }
+}
+
+void
+SVGTextFrame::DoAnchoring()
+{
+ nsPresContext* presContext = PresContext();
+
+ CharIterator it(this, CharIterator::eOriginal);
+
+ // Don't need to worry about skipped or trimmed characters.
+ while (!it.AtEnd() &&
+ (it.IsOriginalCharSkipped() || it.IsOriginalCharTrimmed())) {
+ it.Next();
+ }
+
+ bool vertical = GetWritingMode().IsVertical();
+ uint32_t start = it.TextElementCharIndex();
+ while (start < mPositions.Length()) {
+ it.AdvanceToCharacter(start);
+ nsTextFrame* chunkFrame = it.TextFrame();
+
+ // Measure characters in this chunk to find the left-most and right-most
+ // edges of all glyphs within the chunk.
+ uint32_t index = it.TextElementCharIndex();
+ uint32_t end = start;
+ gfxFloat left = std::numeric_limits<gfxFloat>::infinity();
+ gfxFloat right = -std::numeric_limits<gfxFloat>::infinity();
+ do {
+ if (!it.IsOriginalCharSkipped() && !it.IsOriginalCharTrimmed()) {
+ gfxFloat advance = it.GetAdvance(presContext) / mFontSizeScaleFactor;
+ gfxFloat pos =
+ it.TextRun()->IsVertical() ? mPositions[index].mPosition.y
+ : mPositions[index].mPosition.x;
+ if (it.TextRun()->IsRightToLeft()) {
+ left = std::min(left, pos - advance);
+ right = std::max(right, pos);
+ } else {
+ left = std::min(left, pos);
+ right = std::max(right, pos + advance);
+ }
+ }
+ it.Next();
+ index = end = it.TextElementCharIndex();
+ } while (!it.AtEnd() && !mPositions[end].mStartOfChunk);
+
+ if (left != std::numeric_limits<gfxFloat>::infinity()) {
+ bool isRTL =
+ chunkFrame->StyleVisibility()->mDirection == NS_STYLE_DIRECTION_RTL;
+ TextAnchorSide anchor =
+ ConvertLogicalTextAnchorToPhysical(chunkFrame->StyleSVG()->mTextAnchor,
+ isRTL);
+
+ ShiftAnchoredChunk(mPositions, start, end, left, right, anchor,
+ vertical);
+ }
+
+ start = it.TextElementCharIndex();
+ }
+}
+
+void
+SVGTextFrame::DoGlyphPositioning()
+{
+ mPositions.Clear();
+ RemoveStateBits(NS_STATE_SVG_POSITIONING_DIRTY);
+
+ nsIFrame* kid = PrincipalChildList().FirstChild();
+ if (kid && NS_SUBTREE_DIRTY(kid)) {
+ MOZ_ASSERT(false, "should have already reflowed the kid");
+ return;
+ }
+
+ // Determine the positions of each character in app units.
+ nsTArray<nsPoint> charPositions;
+ DetermineCharPositions(charPositions);
+
+ if (charPositions.IsEmpty()) {
+ // No characters, so nothing to do.
+ return;
+ }
+
+ // If the textLength="" attribute was specified, then we need ResolvePositions
+ // to record that a new run starts with each glyph.
+ SVGTextContentElement* element = static_cast<SVGTextContentElement*>(mContent);
+ nsSVGLength2* textLengthAttr =
+ element->GetAnimatedLength(nsGkAtoms::textLength);
+ bool adjustingTextLength = textLengthAttr->IsExplicitlySet();
+ float expectedTextLength = textLengthAttr->GetAnimValue(element);
+
+ if (adjustingTextLength && expectedTextLength < 0.0f) {
+ // If textLength="" is less than zero, ignore it.
+ adjustingTextLength = false;
+ }
+
+ // Get the x, y, dx, dy, rotate values for the subtree.
+ nsTArray<gfxPoint> deltas;
+ if (!ResolvePositions(deltas, adjustingTextLength)) {
+ // If ResolvePositions returned false, it means either there were some
+ // characters in the DOM but none of them are displayed, or there was
+ // an error in processing mPositions. Clear out mPositions so that we don't
+ // attempt to do any painting later.
+ mPositions.Clear();
+ return;
+ }
+
+ // XXX We might be able to do less work when there is at most a single
+ // x/y/dx/dy position.
+
+ // Truncate the positioning arrays to the actual number of characters present.
+ TruncateTo(deltas, charPositions);
+ TruncateTo(mPositions, charPositions);
+
+ // Fill in an unspecified character position at index 0.
+ if (!mPositions[0].IsXSpecified()) {
+ mPositions[0].mPosition.x = 0.0;
+ }
+ if (!mPositions[0].IsYSpecified()) {
+ mPositions[0].mPosition.y = 0.0;
+ }
+ if (!mPositions[0].IsAngleSpecified()) {
+ mPositions[0].mAngle = 0.0;
+ }
+
+ nsPresContext* presContext = PresContext();
+ bool vertical = GetWritingMode().IsVertical();
+
+ float cssPxPerDevPx = presContext->
+ AppUnitsToFloatCSSPixels(presContext->AppUnitsPerDevPixel());
+ double factor = cssPxPerDevPx / mFontSizeScaleFactor;
+
+ // Determine how much to compress or expand glyph positions due to
+ // textLength="" and lengthAdjust="".
+ double adjustment = 0.0;
+ mLengthAdjustScaleFactor = 1.0f;
+ if (adjustingTextLength) {
+ nscoord frameLength = vertical ? PrincipalChildList().FirstChild()->GetRect().height
+ : PrincipalChildList().FirstChild()->GetRect().width;
+ float actualTextLength =
+ static_cast<float>(presContext->AppUnitsToGfxUnits(frameLength) * factor);
+
+ RefPtr<SVGAnimatedEnumeration> lengthAdjustEnum = element->LengthAdjust();
+ uint16_t lengthAdjust = lengthAdjustEnum->AnimVal();
+ switch (lengthAdjust) {
+ case SVG_LENGTHADJUST_SPACINGANDGLYPHS:
+ // Scale the glyphs and their positions.
+ if (actualTextLength > 0) {
+ mLengthAdjustScaleFactor = expectedTextLength / actualTextLength;
+ }
+ break;
+
+ default:
+ MOZ_ASSERT(lengthAdjust == SVG_LENGTHADJUST_SPACING);
+ // Just add space between each glyph.
+ int32_t adjustableSpaces = 0;
+ for (uint32_t i = 1; i < mPositions.Length(); i++) {
+ if (!mPositions[i].mUnaddressable) {
+ adjustableSpaces++;
+ }
+ }
+ if (adjustableSpaces) {
+ adjustment = (expectedTextLength - actualTextLength) / adjustableSpaces;
+ }
+ break;
+ }
+ }
+
+ // Fill in any unspecified character positions based on the positions recorded
+ // in charPositions, and also add in the dx/dy values.
+ if (!deltas.IsEmpty()) {
+ mPositions[0].mPosition += deltas[0];
+ }
+
+ gfxFloat xLengthAdjustFactor = vertical ? 1.0 : mLengthAdjustScaleFactor;
+ gfxFloat yLengthAdjustFactor = vertical ? mLengthAdjustScaleFactor : 1.0;
+ for (uint32_t i = 1; i < mPositions.Length(); i++) {
+ // Fill in unspecified x position.
+ if (!mPositions[i].IsXSpecified()) {
+ nscoord d = charPositions[i].x - charPositions[i - 1].x;
+ mPositions[i].mPosition.x =
+ mPositions[i - 1].mPosition.x +
+ presContext->AppUnitsToGfxUnits(d) * factor * xLengthAdjustFactor;
+ if (!vertical && !mPositions[i].mUnaddressable) {
+ mPositions[i].mPosition.x += adjustment;
+ }
+ }
+ // Fill in unspecified y position.
+ if (!mPositions[i].IsYSpecified()) {
+ nscoord d = charPositions[i].y - charPositions[i - 1].y;
+ mPositions[i].mPosition.y =
+ mPositions[i - 1].mPosition.y +
+ presContext->AppUnitsToGfxUnits(d) * factor * yLengthAdjustFactor;
+ if (vertical && !mPositions[i].mUnaddressable) {
+ mPositions[i].mPosition.y += adjustment;
+ }
+ }
+ // Add in dx/dy.
+ if (i < deltas.Length()) {
+ mPositions[i].mPosition += deltas[i];
+ }
+ // Fill in unspecified rotation values.
+ if (!mPositions[i].IsAngleSpecified()) {
+ mPositions[i].mAngle = 0.0f;
+ }
+ }
+
+ MOZ_ASSERT(mPositions.Length() == charPositions.Length());
+
+ AdjustChunksForLineBreaks();
+ AdjustPositionsForClusters();
+ DoAnchoring();
+ DoTextPathLayout();
+}
+
+bool
+SVGTextFrame::ShouldRenderAsPath(nsTextFrame* aFrame,
+ bool& aShouldPaintSVGGlyphs)
+{
+ // Rendering to a clip path.
+ if (aFrame->GetParent()->GetParent()->GetStateBits() & NS_STATE_SVG_CLIPPATH_CHILD) {
+ aShouldPaintSVGGlyphs = false;
+ return true;
+ }
+
+ aShouldPaintSVGGlyphs = true;
+
+ const nsStyleSVG* style = aFrame->StyleSVG();
+
+ // Fill is a non-solid paint, has a non-default fill-rule or has
+ // non-1 opacity.
+ if (!(style->mFill.Type() == eStyleSVGPaintType_None ||
+ (style->mFill.Type() == eStyleSVGPaintType_Color &&
+ style->mFillOpacity == 1))) {
+ return true;
+ }
+
+ // Text has a stroke.
+ if (style->HasStroke() &&
+ SVGContentUtils::CoordToFloat(static_cast<nsSVGElement*>(mContent),
+ style->mStrokeWidth) > 0) {
+ return true;
+ }
+
+ return false;
+}
+
+void
+SVGTextFrame::ScheduleReflowSVG()
+{
+ if (mState & NS_FRAME_IS_NONDISPLAY) {
+ ScheduleReflowSVGNonDisplayText(nsIPresShell::eStyleChange);
+ } else {
+ nsSVGUtils::ScheduleReflowSVG(this);
+ }
+}
+
+void
+SVGTextFrame::NotifyGlyphMetricsChange()
+{
+ AddStateBits(NS_STATE_SVG_POSITIONING_DIRTY);
+ nsLayoutUtils::PostRestyleEvent(
+ mContent->AsElement(), nsRestyleHint(0),
+ nsChangeHint_InvalidateRenderingObservers);
+ ScheduleReflowSVG();
+}
+
+void
+SVGTextFrame::UpdateGlyphPositioning()
+{
+ nsIFrame* kid = PrincipalChildList().FirstChild();
+ if (!kid) {
+ return;
+ }
+
+ if (mState & NS_STATE_SVG_POSITIONING_DIRTY) {
+ DoGlyphPositioning();
+ }
+}
+
+void
+SVGTextFrame::MaybeReflowAnonymousBlockChild()
+{
+ nsIFrame* kid = PrincipalChildList().FirstChild();
+ if (!kid)
+ return;
+
+ NS_ASSERTION(!(kid->GetStateBits() & NS_FRAME_IN_REFLOW),
+ "should not be in reflow when about to reflow again");
+
+ if (NS_SUBTREE_DIRTY(this)) {
+ if (mState & NS_FRAME_IS_DIRTY) {
+ // If we require a full reflow, ensure our kid is marked fully dirty.
+ // (Note that our anonymous nsBlockFrame is not an nsISVGChildFrame, so
+ // even when we are called via our ReflowSVG this will not be done for us
+ // by nsSVGDisplayContainerFrame::ReflowSVG.)
+ kid->AddStateBits(NS_FRAME_IS_DIRTY);
+ }
+ MOZ_ASSERT(nsSVGUtils::AnyOuterSVGIsCallingReflowSVG(this),
+ "should be under ReflowSVG");
+ nsPresContext::InterruptPreventer noInterrupts(PresContext());
+ DoReflow();
+ }
+}
+
+void
+SVGTextFrame::DoReflow()
+{
+ // Since we are going to reflow the anonymous block frame, we will
+ // need to update mPositions.
+ AddStateBits(NS_STATE_SVG_POSITIONING_DIRTY);
+
+ if (mState & NS_FRAME_IS_NONDISPLAY) {
+ // Normally, these dirty flags would be cleared in ReflowSVG(), but that
+ // doesn't get called for non-display frames. We don't want to reflow our
+ // descendants every time SVGTextFrame::PaintSVG makes sure that we have
+ // valid positions by calling UpdateGlyphPositioning(), so we need to clear
+ // these dirty bits. Note that this also breaks an invalidation loop where
+ // our descendants invalidate as they reflow, which invalidates rendering
+ // observers, which reschedules the frame that is currently painting by
+ // referencing us to paint again. See bug 839958 comment 7. Hopefully we
+ // will break that loop more convincingly at some point.
+ mState &= ~(NS_FRAME_IS_DIRTY | NS_FRAME_HAS_DIRTY_CHILDREN);
+ }
+
+ nsPresContext *presContext = PresContext();
+ nsIFrame* kid = PrincipalChildList().FirstChild();
+ if (!kid)
+ return;
+
+ nsRenderingContext renderingContext(
+ presContext->PresShell()->CreateReferenceRenderingContext());
+
+ if (UpdateFontSizeScaleFactor()) {
+ // If the font size scale factor changed, we need the block to report
+ // an updated preferred width.
+ kid->MarkIntrinsicISizesDirty();
+ }
+
+ mState |= NS_STATE_SVG_TEXT_IN_REFLOW;
+
+ nscoord inlineSize = kid->GetPrefISize(&renderingContext);
+ WritingMode wm = kid->GetWritingMode();
+ ReflowInput reflowInput(presContext, kid,
+ &renderingContext,
+ LogicalSize(wm, inlineSize,
+ NS_UNCONSTRAINEDSIZE));
+ ReflowOutput desiredSize(reflowInput);
+ nsReflowStatus status;
+
+ NS_ASSERTION(reflowInput.ComputedPhysicalBorderPadding() == nsMargin(0, 0, 0, 0) &&
+ reflowInput.ComputedPhysicalMargin() == nsMargin(0, 0, 0, 0),
+ "style system should ensure that :-moz-svg-text "
+ "does not get styled");
+
+ kid->Reflow(presContext, desiredSize, reflowInput, status);
+ kid->DidReflow(presContext, &reflowInput, nsDidReflowStatus::FINISHED);
+ kid->SetSize(wm, desiredSize.Size(wm));
+
+ mState &= ~NS_STATE_SVG_TEXT_IN_REFLOW;
+
+ TextNodeCorrespondenceRecorder::RecordCorrespondence(this);
+}
+
+// Usable font size range in devpixels / user-units
+#define CLAMP_MIN_SIZE 8.0
+#define CLAMP_MAX_SIZE 200.0
+#define PRECISE_SIZE 200.0
+
+bool
+SVGTextFrame::UpdateFontSizeScaleFactor()
+{
+ double oldFontSizeScaleFactor = mFontSizeScaleFactor;
+
+ nsPresContext* presContext = PresContext();
+
+ bool geometricPrecision = false;
+ nscoord min = nscoord_MAX,
+ max = nscoord_MIN;
+
+ // Find the minimum and maximum font sizes used over all the
+ // nsTextFrames.
+ TextFrameIterator it(this);
+ nsTextFrame* f = it.Current();
+ while (f) {
+ if (!geometricPrecision) {
+ // Unfortunately we can't treat text-rendering:geometricPrecision
+ // separately for each text frame.
+ geometricPrecision = f->StyleText()->mTextRendering ==
+ NS_STYLE_TEXT_RENDERING_GEOMETRICPRECISION;
+ }
+ nscoord size = f->StyleFont()->mFont.size;
+ if (size) {
+ min = std::min(min, size);
+ max = std::max(max, size);
+ }
+ f = it.Next();
+ }
+
+ if (min == nscoord_MAX) {
+ // No text, so no need for scaling.
+ mFontSizeScaleFactor = 1.0;
+ return mFontSizeScaleFactor != oldFontSizeScaleFactor;
+ }
+
+ double minSize = presContext->AppUnitsToFloatCSSPixels(min);
+
+ if (geometricPrecision) {
+ // We want to ensure minSize is scaled to PRECISE_SIZE.
+ mFontSizeScaleFactor = PRECISE_SIZE / minSize;
+ return mFontSizeScaleFactor != oldFontSizeScaleFactor;
+ }
+
+ // When we are non-display, we could be painted in different coordinate
+ // spaces, and we don't want to have to reflow for each of these. We
+ // just assume that the context scale is 1.0 for them all, so we don't
+ // get stuck with a font size scale factor based on whichever referencing
+ // frame happens to reflow first.
+ double contextScale = 1.0;
+ if (!(mState & NS_FRAME_IS_NONDISPLAY)) {
+ gfxMatrix m(GetCanvasTM());
+ if (!m.IsSingular()) {
+ contextScale = GetContextScale(m);
+ }
+ }
+ mLastContextScale = contextScale;
+
+ double maxSize = presContext->AppUnitsToFloatCSSPixels(max);
+
+ // But we want to ignore any scaling required due to HiDPI displays, since
+ // regular CSS text frames will still create text runs using the font size
+ // in CSS pixels, and we want SVG text to have the same rendering as HTML
+ // text for regular font sizes.
+ float cssPxPerDevPx =
+ presContext->AppUnitsToFloatCSSPixels(presContext->AppUnitsPerDevPixel());
+ contextScale *= cssPxPerDevPx;
+
+ double minTextRunSize = minSize * contextScale;
+ double maxTextRunSize = maxSize * contextScale;
+
+ if (minTextRunSize >= CLAMP_MIN_SIZE &&
+ maxTextRunSize <= CLAMP_MAX_SIZE) {
+ // We are already in the ideal font size range for all text frames,
+ // so we only have to take into account the contextScale.
+ mFontSizeScaleFactor = contextScale;
+ } else if (maxSize / minSize > CLAMP_MAX_SIZE / CLAMP_MIN_SIZE) {
+ // We can't scale the font sizes so that all of the text frames lie
+ // within our ideal font size range, so we treat the minimum as more
+ // important and just scale so that minSize = CLAMP_MIN_SIZE.
+ mFontSizeScaleFactor = CLAMP_MIN_SIZE / minTextRunSize;
+ } else if (minTextRunSize < CLAMP_MIN_SIZE) {
+ mFontSizeScaleFactor = CLAMP_MIN_SIZE / minTextRunSize;
+ } else {
+ mFontSizeScaleFactor = CLAMP_MAX_SIZE / maxTextRunSize;
+ }
+
+ return mFontSizeScaleFactor != oldFontSizeScaleFactor;
+}
+
+double
+SVGTextFrame::GetFontSizeScaleFactor() const
+{
+ return mFontSizeScaleFactor;
+}
+
+/**
+ * Take aPoint, which is in the <text> element's user space, and convert
+ * it to the appropriate frame user space of aChildFrame according to
+ * which rendered run the point hits.
+ */
+Point
+SVGTextFrame::TransformFramePointToTextChild(const Point& aPoint,
+ nsIFrame* aChildFrame)
+{
+ NS_ASSERTION(aChildFrame &&
+ nsLayoutUtils::GetClosestFrameOfType
+ (aChildFrame->GetParent(), nsGkAtoms::svgTextFrame) == this,
+ "aChildFrame must be a descendant of this frame");
+
+ UpdateGlyphPositioning();
+
+ nsPresContext* presContext = PresContext();
+
+ // Add in the mRect offset to aPoint, as that will have been taken into
+ // account when transforming the point from the ancestor frame down
+ // to this one.
+ float cssPxPerDevPx = presContext->
+ AppUnitsToFloatCSSPixels(presContext->AppUnitsPerDevPixel());
+ float factor = presContext->AppUnitsPerCSSPixel();
+ Point framePosition(NSAppUnitsToFloatPixels(mRect.x, factor),
+ NSAppUnitsToFloatPixels(mRect.y, factor));
+ Point pointInUserSpace = aPoint * cssPxPerDevPx + framePosition;
+
+ // Find the closest rendered run for the text frames beneath aChildFrame.
+ TextRenderedRunIterator it(this, TextRenderedRunIterator::eAllFrames,
+ aChildFrame);
+ TextRenderedRun hit;
+ gfxPoint pointInRun;
+ nscoord dx = nscoord_MAX;
+ nscoord dy = nscoord_MAX;
+ for (TextRenderedRun run = it.Current(); run.mFrame; run = it.Next()) {
+ uint32_t flags = TextRenderedRun::eIncludeFill |
+ TextRenderedRun::eIncludeStroke |
+ TextRenderedRun::eNoHorizontalOverflow;
+ gfxRect runRect = run.GetRunUserSpaceRect(presContext, flags).ToThebesRect();
+
+ gfxMatrix m = run.GetTransformFromRunUserSpaceToUserSpace(presContext);
+ if (!m.Invert()) {
+ return aPoint;
+ }
+ gfxPoint pointInRunUserSpace = m.Transform(ThebesPoint(pointInUserSpace));
+
+ if (Inside(runRect, pointInRunUserSpace)) {
+ // The point was inside the rendered run's rect, so we choose it.
+ dx = 0;
+ dy = 0;
+ pointInRun = pointInRunUserSpace;
+ hit = run;
+ } else if (nsLayoutUtils::PointIsCloserToRect(pointInRunUserSpace,
+ runRect, dx, dy)) {
+ // The point was closer to this rendered run's rect than any others
+ // we've seen so far.
+ pointInRun.x = clamped(pointInRunUserSpace.x,
+ runRect.X(), runRect.XMost());
+ pointInRun.y = clamped(pointInRunUserSpace.y,
+ runRect.Y(), runRect.YMost());
+ hit = run;
+ }
+ }
+
+ if (!hit.mFrame) {
+ // We didn't find any rendered runs for the frame.
+ return aPoint;
+ }
+
+ // Return the point in user units relative to the nsTextFrame,
+ // but taking into account mFontSizeScaleFactor.
+ gfxMatrix m = hit.GetTransformFromRunUserSpaceToFrameUserSpace(presContext);
+ m.Scale(mFontSizeScaleFactor, mFontSizeScaleFactor);
+ return ToPoint(m.Transform(pointInRun) / cssPxPerDevPx);
+}
+
+/**
+ * For each rendered run for frames beneath aChildFrame, convert aRect
+ * into the run's frame user space and intersect it with the run's
+ * frame user space rectangle. For each of these intersections,
+ * then translate them up into aChildFrame's coordinate space
+ * and union them all together.
+ */
+gfxRect
+SVGTextFrame::TransformFrameRectToTextChild(const gfxRect& aRect,
+ nsIFrame* aChildFrame)
+{
+ NS_ASSERTION(aChildFrame &&
+ nsLayoutUtils::GetClosestFrameOfType
+ (aChildFrame->GetParent(), nsGkAtoms::svgTextFrame) == this,
+ "aChildFrame must be a descendant of this frame");
+
+ UpdateGlyphPositioning();
+
+ nsPresContext* presContext = PresContext();
+
+ // Add in the mRect offset to aRect, as that will have been taken into
+ // account when transforming the rect from the ancestor frame down
+ // to this one.
+ float cssPxPerDevPx = presContext->
+ AppUnitsToFloatCSSPixels(presContext->AppUnitsPerDevPixel());
+ float factor = presContext->AppUnitsPerCSSPixel();
+ gfxPoint framePosition(NSAppUnitsToFloatPixels(mRect.x, factor),
+ NSAppUnitsToFloatPixels(mRect.y, factor));
+ gfxRect incomingRectInUserSpace(aRect.x * cssPxPerDevPx + framePosition.x,
+ aRect.y * cssPxPerDevPx + framePosition.y,
+ aRect.width * cssPxPerDevPx,
+ aRect.height * cssPxPerDevPx);
+
+ // Find each rendered run for text frames beneath aChildFrame.
+ gfxRect result;
+ TextRenderedRunIterator it(this, TextRenderedRunIterator::eAllFrames,
+ aChildFrame);
+ for (TextRenderedRun run = it.Current(); run.mFrame; run = it.Next()) {
+ // Convert the incoming rect into frame user space.
+ gfxMatrix userSpaceToRunUserSpace =
+ run.GetTransformFromRunUserSpaceToUserSpace(presContext);
+ if (!userSpaceToRunUserSpace.Invert()) {
+ return result;
+ }
+ gfxMatrix m = run.GetTransformFromRunUserSpaceToFrameUserSpace(presContext) *
+ userSpaceToRunUserSpace;
+ gfxRect incomingRectInFrameUserSpace =
+ m.TransformBounds(incomingRectInUserSpace);
+
+ // Intersect it with this run's rectangle.
+ uint32_t flags = TextRenderedRun::eIncludeFill |
+ TextRenderedRun::eIncludeStroke;
+ SVGBBox runRectInFrameUserSpace = run.GetFrameUserSpaceRect(presContext, flags);
+ if (runRectInFrameUserSpace.IsEmpty()) {
+ continue;
+ }
+ gfxRect runIntersectionInFrameUserSpace =
+ incomingRectInFrameUserSpace.Intersect(runRectInFrameUserSpace.ToThebesRect());
+
+ if (!runIntersectionInFrameUserSpace.IsEmpty()) {
+ // Take the font size scale into account.
+ runIntersectionInFrameUserSpace.x *= mFontSizeScaleFactor;
+ runIntersectionInFrameUserSpace.y *= mFontSizeScaleFactor;
+ runIntersectionInFrameUserSpace.width *= mFontSizeScaleFactor;
+ runIntersectionInFrameUserSpace.height *= mFontSizeScaleFactor;
+
+ // Convert it into the coordinate space of aChildFrame.
+ nsPoint offset = run.mFrame->GetOffsetTo(aChildFrame);
+ gfxRect runIntersection =
+ runIntersectionInFrameUserSpace +
+ gfxPoint(NSAppUnitsToFloatPixels(offset.x, factor),
+ NSAppUnitsToFloatPixels(offset.y, factor));
+
+ // Union it into the result.
+ result.UnionRect(result, runIntersection);
+ }
+ }
+
+ return result;
+}
+
+/**
+ * For each rendered run beneath aChildFrame, translate aRect from
+ * aChildFrame to the run's text frame, transform it then into
+ * the run's frame user space, intersect it with the run's
+ * frame user space rect, then transform it up to user space.
+ * The result is the union of all of these.
+ */
+gfxRect
+SVGTextFrame::TransformFrameRectFromTextChild(const nsRect& aRect,
+ nsIFrame* aChildFrame)
+{
+ NS_ASSERTION(aChildFrame &&
+ nsLayoutUtils::GetClosestFrameOfType
+ (aChildFrame->GetParent(), nsGkAtoms::svgTextFrame) == this,
+ "aChildFrame must be a descendant of this frame");
+
+ UpdateGlyphPositioning();
+
+ nsPresContext* presContext = PresContext();
+
+ gfxRect result;
+ TextRenderedRunIterator it(this, TextRenderedRunIterator::eAllFrames,
+ aChildFrame);
+ for (TextRenderedRun run = it.Current(); run.mFrame; run = it.Next()) {
+ // First, translate aRect from aChildFrame to this run's frame.
+ nsRect rectInTextFrame = aRect + aChildFrame->GetOffsetTo(run.mFrame);
+
+ // Scale it into frame user space.
+ gfxRect rectInFrameUserSpace =
+ AppUnitsToFloatCSSPixels(gfxRect(rectInTextFrame.x,
+ rectInTextFrame.y,
+ rectInTextFrame.width,
+ rectInTextFrame.height), presContext);
+
+ // Intersect it with the run.
+ uint32_t flags = TextRenderedRun::eIncludeFill |
+ TextRenderedRun::eIncludeStroke;
+
+ if (rectInFrameUserSpace.IntersectRect(rectInFrameUserSpace,
+ run.GetFrameUserSpaceRect(presContext, flags).ToThebesRect())) {
+ // Transform it up to user space of the <text>, also taking into
+ // account the font size scale.
+ gfxMatrix m = run.GetTransformFromRunUserSpaceToUserSpace(presContext);
+ m.Scale(mFontSizeScaleFactor, mFontSizeScaleFactor);
+ gfxRect rectInUserSpace = m.Transform(rectInFrameUserSpace);
+
+ // Union it into the result.
+ result.UnionRect(result, rectInUserSpace);
+ }
+ }
+
+ // Subtract the mRect offset from the result, as our user space for
+ // this frame is relative to the top-left of mRect.
+ float factor = presContext->AppUnitsPerCSSPixel();
+ gfxPoint framePosition(NSAppUnitsToFloatPixels(mRect.x, factor),
+ NSAppUnitsToFloatPixels(mRect.y, factor));
+
+ return result - framePosition;
+}
diff --git a/layout/svg/SVGTextFrame.h b/layout/svg/SVGTextFrame.h
new file mode 100644
index 0000000000..9c672c6a53
--- /dev/null
+++ b/layout/svg/SVGTextFrame.h
@@ -0,0 +1,605 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef MOZILLA_SVGTEXTFRAME_H
+#define MOZILLA_SVGTEXTFRAME_H
+
+#include "mozilla/Attributes.h"
+#include "mozilla/RefPtr.h"
+#include "mozilla/gfx/2D.h"
+#include "gfxMatrix.h"
+#include "gfxRect.h"
+#include "gfxTextRun.h"
+#include "nsAutoPtr.h"
+#include "nsIContent.h" // for GetContent
+#include "nsStubMutationObserver.h"
+#include "nsSVGContainerFrame.h"
+
+class gfxContext;
+class nsDisplaySVGText;
+class SVGTextFrame;
+class nsTextFrame;
+
+namespace mozilla {
+
+class CharIterator;
+class nsISVGPoint;
+class TextFrameIterator;
+class TextNodeCorrespondenceRecorder;
+struct TextRenderedRun;
+class TextRenderedRunIterator;
+
+namespace dom {
+class SVGIRect;
+class SVGPathElement;
+} // namespace dom
+
+/**
+ * Information about the positioning for a single character in an SVG <text>
+ * element.
+ *
+ * During SVG text layout, we use infinity values to represent positions and
+ * rotations that are not explicitly specified with x/y/rotate attributes.
+ */
+struct CharPosition
+{
+ CharPosition()
+ : mAngle(0),
+ mHidden(false),
+ mUnaddressable(false),
+ mClusterOrLigatureGroupMiddle(false),
+ mRunBoundary(false),
+ mStartOfChunk(false)
+ {
+ }
+
+ CharPosition(gfxPoint aPosition, double aAngle)
+ : mPosition(aPosition),
+ mAngle(aAngle),
+ mHidden(false),
+ mUnaddressable(false),
+ mClusterOrLigatureGroupMiddle(false),
+ mRunBoundary(false),
+ mStartOfChunk(false)
+ {
+ }
+
+ static CharPosition Unspecified(bool aUnaddressable)
+ {
+ CharPosition cp(UnspecifiedPoint(), UnspecifiedAngle());
+ cp.mUnaddressable = aUnaddressable;
+ return cp;
+ }
+
+ bool IsAngleSpecified() const
+ {
+ return mAngle != UnspecifiedAngle();
+ }
+
+ bool IsXSpecified() const
+ {
+ return mPosition.x != UnspecifiedCoord();
+ }
+
+ bool IsYSpecified() const
+ {
+ return mPosition.y != UnspecifiedCoord();
+ }
+
+ gfxPoint mPosition;
+ double mAngle;
+
+ // not displayed due to falling off the end of a <textPath>
+ bool mHidden;
+
+ // skipped in positioning attributes due to being collapsed-away white space
+ bool mUnaddressable;
+
+ // a preceding character is what positioning attributes address
+ bool mClusterOrLigatureGroupMiddle;
+
+ // rendering is split here since an explicit position or rotation was given
+ bool mRunBoundary;
+
+ // an anchored chunk begins here
+ bool mStartOfChunk;
+
+private:
+ static gfxFloat UnspecifiedCoord()
+ {
+ return std::numeric_limits<gfxFloat>::infinity();
+ }
+
+ static double UnspecifiedAngle()
+ {
+ return std::numeric_limits<double>::infinity();
+ }
+
+ static gfxPoint UnspecifiedPoint()
+ {
+ return gfxPoint(UnspecifiedCoord(), UnspecifiedCoord());
+ }
+};
+
+/**
+ * A runnable to mark glyph positions as needing to be recomputed
+ * and to invalid the bounds of the SVGTextFrame frame.
+ */
+class GlyphMetricsUpdater : public Runnable {
+public:
+ NS_DECL_NSIRUNNABLE
+ explicit GlyphMetricsUpdater(SVGTextFrame* aFrame) : mFrame(aFrame) { }
+ static void Run(SVGTextFrame* aFrame);
+ void Revoke() { mFrame = nullptr; }
+private:
+ SVGTextFrame* mFrame;
+};
+
+} // namespace mozilla
+
+/**
+ * Frame class for SVG <text> elements.
+ *
+ * An SVGTextFrame manages SVG text layout, painting and interaction for
+ * all descendent text content elements. The frame tree will look like this:
+ *
+ * SVGTextFrame -- for <text>
+ * <anonymous block frame>
+ * ns{Block,Inline,Text}Frames -- for text nodes, <tspan>s, <a>s, etc.
+ *
+ * SVG text layout is done by:
+ *
+ * 1. Reflowing the anonymous block frame.
+ * 2. Inspecting the (app unit) positions of the glyph for each character in
+ * the nsTextFrames underneath the anonymous block frame.
+ * 3. Determining the (user unit) positions for each character in the <text>
+ * using the x/y/dx/dy/rotate attributes on all the text content elements,
+ * and using the step 2 results to fill in any gaps.
+ * 4. Applying any other SVG specific text layout (anchoring and text paths)
+ * to the positions computed in step 3.
+ *
+ * Rendering of the text is done by splitting up each nsTextFrame into ranges
+ * that can be contiguously painted. (For example <text x="10 20">abcd</text>
+ * would have two contiguous ranges: one for the "a" and one for the "bcd".)
+ * Each range is called a "text rendered run", represented by a TextRenderedRun
+ * object. The TextRenderedRunIterator class performs that splitting and
+ * returns a TextRenderedRun for each bit of text to be painted separately.
+ *
+ * Each rendered run is painted by calling nsTextFrame::PaintText. If the text
+ * formatting is simple enough (solid fill, no stroking, etc.), PaintText will
+ * itself do the painting. Otherwise, a DrawPathCallback is passed to
+ * PaintText so that we can fill the text geometry with SVG paint servers.
+ */
+class SVGTextFrame final : public nsSVGDisplayContainerFrame
+{
+ friend nsIFrame*
+ NS_NewSVGTextFrame(nsIPresShell* aPresShell, nsStyleContext* aContext);
+
+ friend class mozilla::CharIterator;
+ friend class mozilla::GlyphMetricsUpdater;
+ friend class mozilla::TextFrameIterator;
+ friend class mozilla::TextNodeCorrespondenceRecorder;
+ friend struct mozilla::TextRenderedRun;
+ friend class mozilla::TextRenderedRunIterator;
+ friend class MutationObserver;
+ friend class nsDisplaySVGText;
+
+ typedef gfxTextRun::Range Range;
+ typedef mozilla::gfx::DrawTarget DrawTarget;
+ typedef mozilla::gfx::Path Path;
+ typedef mozilla::gfx::Point Point;
+ typedef mozilla::image::DrawResult DrawResult;
+
+protected:
+ explicit SVGTextFrame(nsStyleContext* aContext)
+ : nsSVGDisplayContainerFrame(aContext)
+ , mFontSizeScaleFactor(1.0f)
+ , mLastContextScale(1.0f)
+ , mLengthAdjustScaleFactor(1.0f)
+ {
+ AddStateBits(NS_STATE_SVG_POSITIONING_DIRTY);
+ }
+
+ ~SVGTextFrame() {}
+
+public:
+ NS_DECL_QUERYFRAME_TARGET(SVGTextFrame)
+ NS_DECL_QUERYFRAME
+ NS_DECL_FRAMEARENA_HELPERS
+
+ // nsIFrame:
+ virtual void Init(nsIContent* aContent,
+ nsContainerFrame* aParent,
+ nsIFrame* aPrevInFlow) override;
+
+ virtual nsresult AttributeChanged(int32_t aNamespaceID,
+ nsIAtom* aAttribute,
+ int32_t aModType) override;
+
+ virtual nsContainerFrame* GetContentInsertionFrame() override
+ {
+ return PrincipalChildList().FirstChild()->GetContentInsertionFrame();
+ }
+
+ virtual void BuildDisplayList(nsDisplayListBuilder* aBuilder,
+ const nsRect& aDirtyRect,
+ const nsDisplayListSet& aLists) override;
+
+ /**
+ * Get the "type" of the frame
+ *
+ * @see nsGkAtoms::svgTextFrame
+ */
+ virtual nsIAtom* GetType() const override;
+
+#ifdef DEBUG_FRAME_DUMP
+ virtual nsresult GetFrameName(nsAString& aResult) const override
+ {
+ return MakeFrameName(NS_LITERAL_STRING("SVGText"), aResult);
+ }
+#endif
+
+ virtual void DidSetStyleContext(nsStyleContext* aOldStyleContext) override;
+
+ /**
+ * Finds the nsTextFrame for the closest rendered run to the specified point.
+ */
+ virtual void FindCloserFrameForSelection(nsPoint aPoint,
+ FrameWithDistance* aCurrentBestFrame) override;
+
+
+
+ // nsISVGChildFrame interface:
+ virtual void NotifySVGChanged(uint32_t aFlags) override;
+ virtual DrawResult PaintSVG(gfxContext& aContext,
+ const gfxMatrix& aTransform,
+ const nsIntRect* aDirtyRect = nullptr) override;
+ virtual nsIFrame* GetFrameForPoint(const gfxPoint& aPoint) override;
+ virtual void ReflowSVG() override;
+ virtual nsRect GetCoveredRegion() override;
+ virtual SVGBBox GetBBoxContribution(const Matrix& aToBBoxUserspace,
+ uint32_t aFlags) override;
+
+ // nsSVGContainerFrame methods:
+ virtual gfxMatrix GetCanvasTM() override;
+
+ // SVG DOM text methods:
+ uint32_t GetNumberOfChars(nsIContent* aContent);
+ float GetComputedTextLength(nsIContent* aContent);
+ nsresult SelectSubString(nsIContent* aContent, uint32_t charnum, uint32_t nchars);
+ nsresult GetSubStringLength(nsIContent* aContent, uint32_t charnum,
+ uint32_t nchars, float* aResult);
+ int32_t GetCharNumAtPosition(nsIContent* aContent, mozilla::nsISVGPoint* point);
+
+ nsresult GetStartPositionOfChar(nsIContent* aContent, uint32_t aCharNum,
+ mozilla::nsISVGPoint** aResult);
+ nsresult GetEndPositionOfChar(nsIContent* aContent, uint32_t aCharNum,
+ mozilla::nsISVGPoint** aResult);
+ nsresult GetExtentOfChar(nsIContent* aContent, uint32_t aCharNum,
+ mozilla::dom::SVGIRect** aResult);
+ nsresult GetRotationOfChar(nsIContent* aContent, uint32_t aCharNum,
+ float* aResult);
+
+ // SVGTextFrame methods:
+
+ /**
+ * Handles a base or animated attribute value change to a descendant
+ * text content element.
+ */
+ void HandleAttributeChangeInDescendant(mozilla::dom::Element* aElement,
+ int32_t aNameSpaceID,
+ nsIAtom* aAttribute);
+
+ /**
+ * Schedules mPositions to be recomputed and the covered region to be
+ * updated.
+ */
+ void NotifyGlyphMetricsChange();
+
+ /**
+ * Calls ScheduleReflowSVGNonDisplayText if this is a non-display frame,
+ * and nsSVGUtils::ScheduleReflowSVG otherwise.
+ */
+ void ScheduleReflowSVG();
+
+ /**
+ * Reflows the anonymous block frame of this non-display SVGTextFrame.
+ *
+ * When we are under nsSVGDisplayContainerFrame::ReflowSVG, we need to
+ * reflow any SVGTextFrame frames in the subtree in case they are
+ * being observed (by being for example in a <mask>) and the change
+ * that caused the reflow would not already have caused a reflow.
+ *
+ * Note that displayed SVGTextFrames are reflowed as needed, when PaintSVG
+ * is called or some SVG DOM method is called on the element.
+ */
+ void ReflowSVGNonDisplayText();
+
+ /**
+ * This is a function that behaves similarly to nsSVGUtils::ScheduleReflowSVG,
+ * but which will skip over any ancestor non-display container frames on the
+ * way to the nsSVGOuterSVGFrame. It exists for the situation where a
+ * non-display <text> element has changed and needs to ensure ReflowSVG will
+ * be called on its closest display container frame, so that
+ * nsSVGDisplayContainerFrame::ReflowSVG will call ReflowSVGNonDisplayText on
+ * it.
+ *
+ * We have to do this in two cases: in response to a style change on a
+ * non-display <text>, where aReason will be eStyleChange (the common case),
+ * and also in response to glyphs changes on non-display <text> (i.e.,
+ * animated SVG-in-OpenType glyphs), in which case aReason will be eResize,
+ * since layout doesn't need to be recomputed.
+ */
+ void ScheduleReflowSVGNonDisplayText(nsIPresShell::IntrinsicDirty aReason);
+
+ /**
+ * Updates the mFontSizeScaleFactor value by looking at the range of
+ * font-sizes used within the <text>.
+ *
+ * @return Whether mFontSizeScaleFactor changed.
+ */
+ bool UpdateFontSizeScaleFactor();
+
+ double GetFontSizeScaleFactor() const;
+
+ /**
+ * Takes a point from the <text> element's user space and
+ * converts it to the appropriate frame user space of aChildFrame,
+ * according to which rendered run the point hits.
+ */
+ Point TransformFramePointToTextChild(const Point& aPoint,
+ nsIFrame* aChildFrame);
+
+ /**
+ * Takes a rectangle, aRect, in the <text> element's user space, and
+ * returns a rectangle in aChildFrame's frame user space that
+ * covers intersections of aRect with each rendered run for text frames
+ * within aChildFrame.
+ */
+ gfxRect TransformFrameRectToTextChild(const gfxRect& aRect,
+ nsIFrame* aChildFrame);
+
+ /**
+ * Takes an app unit rectangle in the coordinate space of a given descendant
+ * frame of this frame, and returns a rectangle in the <text> element's user
+ * space that covers all parts of rendered runs that intersect with the
+ * rectangle.
+ */
+ gfxRect TransformFrameRectFromTextChild(const nsRect& aRect,
+ nsIFrame* aChildFrame);
+
+private:
+ /**
+ * Mutation observer used to watch for text positioning attribute changes
+ * on descendent text content elements (like <tspan>s).
+ */
+ class MutationObserver final : public nsStubMutationObserver {
+ public:
+ explicit MutationObserver(SVGTextFrame* aFrame)
+ : mFrame(aFrame)
+ {
+ MOZ_ASSERT(mFrame, "MutationObserver needs a non-null frame");
+ mFrame->GetContent()->AddMutationObserver(this);
+ }
+
+ // nsISupports
+ NS_DECL_ISUPPORTS
+
+ // nsIMutationObserver
+ NS_DECL_NSIMUTATIONOBSERVER_CONTENTAPPENDED
+ NS_DECL_NSIMUTATIONOBSERVER_CONTENTINSERTED
+ NS_DECL_NSIMUTATIONOBSERVER_CONTENTREMOVED
+ NS_DECL_NSIMUTATIONOBSERVER_CHARACTERDATACHANGED
+ NS_DECL_NSIMUTATIONOBSERVER_ATTRIBUTECHANGED
+
+ private:
+ ~MutationObserver()
+ {
+ mFrame->GetContent()->RemoveMutationObserver(this);
+ }
+
+ SVGTextFrame* const mFrame;
+ };
+
+ /**
+ * Reflows the anonymous block child if it is dirty or has dirty
+ * children, or if the SVGTextFrame itself is dirty.
+ */
+ void MaybeReflowAnonymousBlockChild();
+
+ /**
+ * Performs the actual work of reflowing the anonymous block child.
+ */
+ void DoReflow();
+
+ /**
+ * Recomputes mPositions by calling DoGlyphPositioning if this information
+ * is out of date.
+ */
+ void UpdateGlyphPositioning();
+
+ /**
+ * Populates mPositions with positioning information for each character
+ * within the <text>.
+ */
+ void DoGlyphPositioning();
+
+ /**
+ * Converts the specified index into mPositions to an addressable
+ * character index (as can be used with the SVG DOM text methods)
+ * relative to the specified text child content element.
+ *
+ * @param aIndex The global character index.
+ * @param aContent The descendant text child content element that
+ * the returned addressable index will be relative to; null
+ * means the same as the <text> element.
+ * @return The addressable index, or -1 if the index cannot be
+ * represented as an addressable index relative to aContent.
+ */
+ int32_t
+ ConvertTextElementCharIndexToAddressableIndex(int32_t aIndex,
+ nsIContent* aContent);
+
+ /**
+ * Recursive helper for ResolvePositions below.
+ *
+ * @param aContent The current node.
+ * @param aIndex (in/out) The current character index.
+ * @param aInTextPath Whether we are currently under a <textPath> element.
+ * @param aForceStartOfChunk (in/out) Whether the next character we find
+ * should start a new anchored chunk.
+ * @param aDeltas (in/out) Receives the resolved dx/dy values for each
+ * character.
+ * @return false if we discover that mPositions did not have enough
+ * elements; true otherwise.
+ */
+ bool ResolvePositionsForNode(nsIContent* aContent, uint32_t& aIndex,
+ bool aInTextPath, bool& aForceStartOfChunk,
+ nsTArray<gfxPoint>& aDeltas);
+
+ /**
+ * Initializes mPositions with character position information based on
+ * x/y/rotate attributes, leaving unspecified values in the array if a position
+ * was not given for that character. Also fills aDeltas with values based on
+ * dx/dy attributes.
+ *
+ * @param aDeltas (in/out) Receives the resolved dx/dy values for each
+ * character.
+ * @param aRunPerGlyph Whether mPositions should record that a new run begins
+ * at each glyph.
+ * @return false if we did not record any positions (due to having no
+ * displayed characters) or if we discover that mPositions did not have
+ * enough elements; true otherwise.
+ */
+ bool ResolvePositions(nsTArray<gfxPoint>& aDeltas, bool aRunPerGlyph);
+
+ /**
+ * Determines the position, in app units, of each character in the <text> as
+ * laid out by reflow, and appends them to aPositions. Any characters that
+ * are undisplayed or trimmed away just get the last position.
+ */
+ void DetermineCharPositions(nsTArray<nsPoint>& aPositions);
+
+ /**
+ * Sets mStartOfChunk to true for each character in mPositions that starts a
+ * line of text.
+ */
+ void AdjustChunksForLineBreaks();
+
+ /**
+ * Adjusts recorded character positions in mPositions to account for glyph
+ * boundaries. Four things are done:
+ *
+ * 1. mClusterOrLigatureGroupMiddle is set to true for all such characters.
+ *
+ * 2. Any run and anchored chunk boundaries that begin in the middle of a
+ * cluster/ligature group get moved to the start of the next
+ * cluster/ligature group.
+ *
+ * 3. The position of any character in the middle of a cluster/ligature
+ * group is updated to take into account partial ligatures and any
+ * rotation the glyph as a whole has. (The values that come out of
+ * DetermineCharPositions which then get written into mPositions in
+ * ResolvePositions store the same position value for each part of the
+ * ligature.)
+ *
+ * 4. The rotation of any character in the middle of a cluster/ligature
+ * group is set to the rotation of the first character.
+ */
+ void AdjustPositionsForClusters();
+
+ /**
+ * Updates the character positions stored in mPositions to account for
+ * text anchoring.
+ */
+ void DoAnchoring();
+
+ /**
+ * Updates character positions in mPositions for those characters inside a
+ * <textPath>.
+ */
+ void DoTextPathLayout();
+
+ /**
+ * Returns whether we need to render the text using
+ * nsTextFrame::DrawPathCallbacks rather than directly painting
+ * the text frames.
+ *
+ * @param aShouldPaintSVGGlyphs (out) Whether SVG glyphs in the text
+ * should be painted.
+ */
+ bool ShouldRenderAsPath(nsTextFrame* aFrame, bool& aShouldPaintSVGGlyphs);
+
+ // Methods to get information for a <textPath> frame.
+ mozilla::dom::SVGPathElement*
+ GetTextPathPathElement(nsIFrame* aTextPathFrame);
+ already_AddRefed<Path> GetTextPath(nsIFrame* aTextPathFrame);
+ gfxFloat GetOffsetScale(nsIFrame* aTextPathFrame);
+ gfxFloat GetStartOffset(nsIFrame* aTextPathFrame);
+
+ /**
+ * The MutationObserver we have registered for the <text> element subtree.
+ */
+ RefPtr<MutationObserver> mMutationObserver;
+
+ /**
+ * Cached canvasTM value.
+ */
+ nsAutoPtr<gfxMatrix> mCanvasTM;
+
+ /**
+ * The number of characters in the DOM after the final nsTextFrame. For
+ * example, with
+ *
+ * <text>abcd<tspan display="none">ef</tspan></text>
+ *
+ * mTrailingUndisplayedCharacters would be 2.
+ */
+ uint32_t mTrailingUndisplayedCharacters;
+
+ /**
+ * Computed position information for each DOM character within the <text>.
+ */
+ nsTArray<mozilla::CharPosition> mPositions;
+
+ /**
+ * mFontSizeScaleFactor is used to cause the nsTextFrames to create text
+ * runs with a font size different from the actual font-size property value.
+ * This is used so that, for example with:
+ *
+ * <svg>
+ * <g transform="scale(2)">
+ * <text font-size="10">abc</text>
+ * </g>
+ * </svg>
+ *
+ * a font size of 20 would be used. It's preferable to use a font size that
+ * is identical or close to the size that the text will appear on the screen,
+ * because at very small or large font sizes, text metrics will be computed
+ * differently due to the limited precision that text runs have.
+ *
+ * mFontSizeScaleFactor is the amount the actual font-size property value
+ * should be multiplied by to cause the text run font size to (a) be within a
+ * "reasonable" range, and (b) be close to the actual size to be painted on
+ * screen. (The "reasonable" range as determined by some #defines in
+ * SVGTextFrame.cpp is 8..200.)
+ */
+ float mFontSizeScaleFactor;
+
+ /**
+ * The scale of the context that we last used to compute mFontSizeScaleFactor.
+ * We record this so that we can tell when our scale transform has changed
+ * enough to warrant reflowing the text.
+ */
+ float mLastContextScale;
+
+ /**
+ * The amount that we need to scale each rendered run to account for
+ * lengthAdjust="spacingAndGlyphs".
+ */
+ float mLengthAdjustScaleFactor;
+};
+
+#endif
diff --git a/layout/svg/SVGViewFrame.cpp b/layout/svg/SVGViewFrame.cpp
new file mode 100644
index 0000000000..7ffa4824f7
--- /dev/null
+++ b/layout/svg/SVGViewFrame.cpp
@@ -0,0 +1,127 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+// Keep in (case-insensitive) order:
+#include "nsFrame.h"
+#include "nsGkAtoms.h"
+#include "nsSVGOuterSVGFrame.h"
+#include "mozilla/dom/SVGSVGElement.h"
+#include "mozilla/dom/SVGViewElement.h"
+
+using namespace mozilla::dom;
+
+/**
+ * While views are not directly rendered in SVG they can be linked to
+ * and thereby override attributes of an <svg> element via a fragment
+ * identifier. The SVGViewFrame class passes on any attribute changes
+ * the view receives to the overridden <svg> element (if there is one).
+ **/
+class SVGViewFrame : public nsFrame
+{
+ friend nsIFrame*
+ NS_NewSVGViewFrame(nsIPresShell* aPresShell, nsStyleContext* aContext);
+protected:
+ explicit SVGViewFrame(nsStyleContext* aContext)
+ : nsFrame(aContext)
+ {
+ AddStateBits(NS_FRAME_IS_NONDISPLAY);
+ }
+
+public:
+ NS_DECL_FRAMEARENA_HELPERS
+
+#ifdef DEBUG
+ virtual void Init(nsIContent* aContent,
+ nsContainerFrame* aParent,
+ nsIFrame* aPrevInFlow) override;
+#endif
+
+ virtual bool IsFrameOfType(uint32_t aFlags) const override
+ {
+ return nsFrame::IsFrameOfType(aFlags & ~(nsIFrame::eSVG));
+ }
+
+#ifdef DEBUG_FRAME_DUMP
+ virtual nsresult GetFrameName(nsAString& aResult) const override
+ {
+ return MakeFrameName(NS_LITERAL_STRING("SVGView"), aResult);
+ }
+#endif
+
+ /**
+ * Get the "type" of the frame
+ *
+ * @see nsGkAtoms::svgFELeafFrame
+ */
+ virtual nsIAtom* GetType() const override;
+
+ virtual nsresult AttributeChanged(int32_t aNameSpaceID,
+ nsIAtom* aAttribute,
+ int32_t aModType) override;
+
+ virtual bool ComputeCustomOverflow(nsOverflowAreas& aOverflowAreas) override {
+ // We don't maintain a visual overflow rect
+ return false;
+ }
+};
+
+nsIFrame*
+NS_NewSVGViewFrame(nsIPresShell* aPresShell, nsStyleContext* aContext)
+{
+ return new (aPresShell) SVGViewFrame(aContext);
+}
+
+NS_IMPL_FRAMEARENA_HELPERS(SVGViewFrame)
+
+#ifdef DEBUG
+void
+SVGViewFrame::Init(nsIContent* aContent,
+ nsContainerFrame* aParent,
+ nsIFrame* aPrevInFlow)
+{
+ NS_ASSERTION(aContent->IsSVGElement(nsGkAtoms::view),
+ "Content is not an SVG view");
+
+ nsFrame::Init(aContent, aParent, aPrevInFlow);
+}
+#endif /* DEBUG */
+
+nsIAtom *
+SVGViewFrame::GetType() const
+{
+ return nsGkAtoms::svgViewFrame;
+}
+
+nsresult
+SVGViewFrame::AttributeChanged(int32_t aNameSpaceID,
+ nsIAtom* aAttribute,
+ int32_t aModType)
+{
+ // Ignore zoomAndPan as it does not cause the <svg> element to re-render
+
+ if (aNameSpaceID == kNameSpaceID_None &&
+ (aAttribute == nsGkAtoms::preserveAspectRatio ||
+ aAttribute == nsGkAtoms::viewBox ||
+ aAttribute == nsGkAtoms::viewTarget)) {
+
+ nsSVGOuterSVGFrame *outerSVGFrame = nsSVGUtils::GetOuterSVGFrame(this);
+ NS_ASSERTION(outerSVGFrame->GetContent()->IsSVGElement(nsGkAtoms::svg),
+ "Expecting an <svg> element");
+
+ SVGSVGElement* svgElement =
+ static_cast<SVGSVGElement*>(outerSVGFrame->GetContent());
+
+ nsAutoString viewID;
+ mContent->GetAttr(kNameSpaceID_None, nsGkAtoms::id, viewID);
+
+ if (svgElement->IsOverriddenBy(viewID)) {
+ // We're the view that's providing overrides, so pretend that the frame
+ // we're overriding was updated.
+ outerSVGFrame->AttributeChanged(aNameSpaceID, aAttribute, aModType);
+ }
+ }
+
+ return nsFrame::AttributeChanged(aNameSpaceID, aAttribute, aModType);
+}
diff --git a/layout/svg/crashtests/1016145.svg b/layout/svg/crashtests/1016145.svg
new file mode 100644
index 0000000000..5c362a17e1
--- /dev/null
+++ b/layout/svg/crashtests/1016145.svg
@@ -0,0 +1,5 @@
+<!-- svg mime type but html root -->
+<html xmlns="http://www.w3.org/1999/xhtml">
+ <body style="display: table-column-group;" />
+</html>
+
diff --git a/layout/svg/crashtests/1028512.svg b/layout/svg/crashtests/1028512.svg
new file mode 100644
index 0000000000..0c9458b478
--- /dev/null
+++ b/layout/svg/crashtests/1028512.svg
@@ -0,0 +1,15 @@
+<!-- {lower,upper}-{roman,alpha} in svg -->
+<svg xmlns="http://www.w3.org/2000/svg">
+<script>
+<![CDATA[
+
+function boom()
+{
+ document.documentElement.style.transition = "2s";
+ document.documentElement.style.listStyleType = "lower-roman";
+}
+
+window.addEventListener("load", boom, false);
+
+]]>
+</script></svg>
diff --git a/layout/svg/crashtests/1140080-1.svg b/layout/svg/crashtests/1140080-1.svg
new file mode 100644
index 0000000000..d424562468
--- /dev/null
+++ b/layout/svg/crashtests/1140080-1.svg
@@ -0,0 +1,11 @@
+<svg xmlns="http://www.w3.org/2000/svg">
+<script>
+
+window.addEventListener("load", function() {
+ var stop = document.getElementsByTagName("stop")[0];
+ stop.setAttribute("offset", "0");
+}, false);
+
+</script>
+<stop/>
+</svg>
diff --git a/layout/svg/crashtests/1149542-1.svg b/layout/svg/crashtests/1149542-1.svg
new file mode 100644
index 0000000000..7353f12775
--- /dev/null
+++ b/layout/svg/crashtests/1149542-1.svg
@@ -0,0 +1,9 @@
+<svg xmlns="http://www.w3.org/2000/svg">
+ <style>
+ text { white-space: pre }
+ text::first-letter { color: red; }
+ tspan { display: none }
+ </style>
+ <text textLength="64">
+<tspan>a</tspan>b</text>
+</svg>
diff --git a/layout/svg/crashtests/1156581-1.svg b/layout/svg/crashtests/1156581-1.svg
new file mode 100644
index 0000000000..97e5fb1ca9
--- /dev/null
+++ b/layout/svg/crashtests/1156581-1.svg
@@ -0,0 +1,12 @@
+<svg xmlns="http://www.w3.org/2000/svg" style="filter: url(#a); clip: rect(0px, 4rem, 2px, 2px);">
+ <script>
+ function boom()
+ {
+ document.getElementById("a").style.overflow = "hidden";
+ document.documentElement.style.fontSize = "10px";
+ }
+ window.addEventListener("load", boom, false);
+ </script>
+
+ <set id="a"/>
+</svg>
diff --git a/layout/svg/crashtests/1182496-1.html b/layout/svg/crashtests/1182496-1.html
new file mode 100644
index 0000000000..1d95905a2d
--- /dev/null
+++ b/layout/svg/crashtests/1182496-1.html
@@ -0,0 +1,21 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <script>
+ function tweak(){
+ document.body.innerHTML="fuzz"
+ }
+ </script>
+</head>
+<body onload="tweak()">
+ <svg xmlns="http://www.w3.org/2000/svg">
+ <text>
+ <foreignObject requiredFeatures="foo">
+ <svg style="position: absolute;"/>
+ </foreignObject>
+ </text>
+ </svg>
+</body>
+</html>
+
+
diff --git a/layout/svg/crashtests/1209525-1.svg b/layout/svg/crashtests/1209525-1.svg
new file mode 100644
index 0000000000..21134df33b
--- /dev/null
+++ b/layout/svg/crashtests/1209525-1.svg
@@ -0,0 +1,7 @@
+<svg xmlns="http://www.w3.org/2000/svg"
+ width="0" height="0">
+
+ <rect width="10" height="10" stroke="black"
+ vector-effect="non-scaling-stroke" />
+
+</svg>
diff --git a/layout/svg/crashtests/1223281-1.svg b/layout/svg/crashtests/1223281-1.svg
new file mode 100644
index 0000000000..b548a9a600
--- /dev/null
+++ b/layout/svg/crashtests/1223281-1.svg
@@ -0,0 +1,24 @@
+<svg xmlns="http://www.w3.org/2000/svg">
+<script>
+<![CDATA[
+
+function forceFrameConstruction()
+{
+ document.documentElement.getBoundingClientRect();
+}
+
+function boom()
+{
+ document.documentElement.style.overflow = "scroll";
+ forceFrameConstruction()
+ document.documentElement.style.visibility = "visible";
+}
+
+window.addEventListener("load", boom, false);
+
+]]>
+</script>
+
+<rect style="perspective: 10em;" />
+</svg>
+
diff --git a/layout/svg/crashtests/220165-1.svg b/layout/svg/crashtests/220165-1.svg
new file mode 100644
index 0000000000..0335f78d41
--- /dev/null
+++ b/layout/svg/crashtests/220165-1.svg
@@ -0,0 +1,21 @@
+<?xml version="1.0"?>
+
+<svg xmlns="http://www.w3.org/2000/svg"
+ xmlns:html="http://www.w3.org/1999/xhtml" height="500"
+ xmlns:xul="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ onload="document.documentElement.getBoundingClientRect();
+ document.getElementById('x').textContent = 'New text'">
+
+ <foreignObject x="200" y="180" width="100" height="50" >
+ <html:button id="x">Old long long long text</html:button>
+ </foreignObject>
+
+ <g transform="rotate(10) translate(-100) scale(0.8)">
+ <polygon style="fill:red; fill-opacity:0.5;"
+ points="350, 75 379,161 469,161 397,215
+ 423,301 350,250 277,301 303,215
+ 231,161 321,161" />
+
+ </g>
+
+</svg>
diff --git a/layout/svg/crashtests/267650-1.svg b/layout/svg/crashtests/267650-1.svg
new file mode 100644
index 0000000000..3e9c7ecc01
--- /dev/null
+++ b/layout/svg/crashtests/267650-1.svg
@@ -0,0 +1,4 @@
+<?xml version='1.0'?>
+<svg xmlns='http://www.w3.org/2000/svg'>
+ <text fill='none' stroke='black'>TESTCASE</text>
+</svg>
diff --git a/layout/svg/crashtests/294022-1.svg b/layout/svg/crashtests/294022-1.svg
new file mode 100644
index 0000000000..f30b484c83
--- /dev/null
+++ b/layout/svg/crashtests/294022-1.svg
@@ -0,0 +1,17 @@
+<?xml version="1.0"?>
+<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN"
+ "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
+<svg xmlns="http://www.w3.org/2000/svg"
+ xmlns:xlink="http://www.w3.org/1999/xlink"
+ >
+
+ <g>
+ <clipPath id="action_box_cp">
+ <rect width="100" height="46"/>
+ </clipPath>
+ <text style="clip-path:url(#action_box_cp); " y="10" id="action_boxtext" pointer-events="none" class="TextBoxText">
+ <tspan x="0" dy="0">Action</tspan>
+ </text>
+ </g>
+
+</svg>
diff --git a/layout/svg/crashtests/307314-1.svg b/layout/svg/crashtests/307314-1.svg
new file mode 100644
index 0000000000..5c538df88d
--- /dev/null
+++ b/layout/svg/crashtests/307314-1.svg
@@ -0,0 +1,9 @@
+<svg xmlns="http://www.w3.org/2000/svg" class="reftest-wait" onload="setTimeout(function() { var g = document.getElementById('N1'), h = document.getElementById('N2'); g.appendChild(h); document.documentElement.removeAttribute("class"); }, 20);">
+
+ <text id="N1"/>
+ <text id="N2">
+ <tspan>
+ <textPath/>
+ </tspan>
+ </text>
+</svg>
diff --git a/layout/svg/crashtests/308615-1.svg b/layout/svg/crashtests/308615-1.svg
new file mode 100644
index 0000000000..fe5391de38
--- /dev/null
+++ b/layout/svg/crashtests/308615-1.svg
@@ -0,0 +1,10 @@
+<svg xmlns="http://www.w3.org/2000/svg"
+ xmlns:xlink="http://www.w3.org/1999/xlink">
+
+ <pattern id="pattern1">
+ <rect style="fill:url(#pattern1);"/>
+ </pattern>
+
+ <rect style="fill:url(#pattern1);" />
+
+</svg> \ No newline at end of file
diff --git a/layout/svg/crashtests/308917-1.svg b/layout/svg/crashtests/308917-1.svg
new file mode 100644
index 0000000000..7d0ae44f74
--- /dev/null
+++ b/layout/svg/crashtests/308917-1.svg
@@ -0,0 +1,35 @@
+<?xml version="1.0" encoding="UTF-8"?>
+
+<svg version="1.1" baseProfile="basic" xmlns="http://www.w3.org/2000/svg" width="100%" height="100%" viewBox="0 0 480 360" class="reftest-wait" onload="first();">
+
+<script><![CDATA[
+
+function first()
+{
+ document.getElementById("z").appendChild(document.getElementById("pat1"));
+ setTimeout(second, 30);
+}
+
+function second()
+{
+ document.getElementById("pat4").appendChild(document.getElementById("z"));
+ document.documentElement.removeAttribute("class");
+}
+
+]]></script>
+
+
+ <pattern patternUnits="userSpaceOnUse" id="pat1" x="10" y="10" width="20" height="20">
+ <rect x="5" y="5" width="10" height="10" fill="red" />
+ <rect x="10" y="10" width="10" height="10" fill="green" />
+ </pattern>
+ <rect x="25" y="10" width="430" height="60" stroke="black" fill="url(#pat1)" />
+
+ <pattern patternUnits="userSpaceOnUse" id="pat4" x="0" y="0" width="20" height="10">
+ <rect x="0" y="0" width="10" height="10" fill="red" />
+ <rect x="10" y="0" width="10" height="10" fill="blue" />
+ </pattern>
+ <text font-family="Arial" font-size="40" fill="none" stroke="url(#pat4)" stroke-width="2" x="25" y="275" id="z">Pattern on stroke</text>
+
+</svg>
+
diff --git a/layout/svg/crashtests/310436-1.svg b/layout/svg/crashtests/310436-1.svg
new file mode 100644
index 0000000000..e6dd5680ce
--- /dev/null
+++ b/layout/svg/crashtests/310436-1.svg
@@ -0,0 +1,28 @@
+<svg xmlns="http://www.w3.org/2000/svg" class="reftest-wait"><script><![CDATA[
+
+function init() {
+ var docElt = document.documentElement;
+ var div1 = document.getElementById("div1");
+ var div2 = document.getElementById("div2");
+ var textNode = div2.childNodes[0];
+
+ function first()
+ {
+ docElt.appendChild(div2);
+ div2.appendChild(div1);
+ }
+
+ function second()
+ {
+ div2.appendChild(div1);
+ div1.appendChild(textNode);
+ document.documentElement.removeAttribute("class");
+ }
+
+ first();
+ setTimeout(second, 30);
+}
+
+window.addEventListener("load", init, false);
+
+]]></script><div xmlns="http://www.w3.org/1999/xhtml" id="div1"><div id="div2">A Z</div></div></svg>
diff --git a/layout/svg/crashtests/310638.svg b/layout/svg/crashtests/310638.svg
new file mode 100644
index 0000000000..e5ee30fb2c
--- /dev/null
+++ b/layout/svg/crashtests/310638.svg
@@ -0,0 +1,35 @@
+<svg xmlns="http://www.w3.org/2000/svg"><div xmlns='http://www.w3.org/1999/xhtml' id="div1">
+<div id="div2">bar</div>
+</div>
+<script><![CDATA[
+
+function init()
+{
+ var div2 = document.getElementById("div2");
+ var div1 = document.getElementById("div1");
+ var docElt = document.documentElement;
+ var titleText = document.createTextNode("foo baz");
+
+ docElt.appendChild(div2); div2.appendChild(titleText);
+
+ function second ()
+ {
+ div2.appendChild(div1);
+ removeNode(titleText);
+ removeNode(div2);
+ }
+
+ setTimeout(second, 0);
+}
+
+
+function removeNode(q1) { q1.parentNode.removeChild(q1); }
+
+
+setTimeout(init, 0);
+
+
+]]></script>
+
+
+</svg> \ No newline at end of file
diff --git a/layout/svg/crashtests/313737-1.xml b/layout/svg/crashtests/313737-1.xml
new file mode 100644
index 0000000000..93421f6077
--- /dev/null
+++ b/layout/svg/crashtests/313737-1.xml
@@ -0,0 +1,16 @@
+<?xml version="1.0" encoding="ISO-8859-1" standalone="no"?>
+<!DOCTYPE xhtml PUBLIC "-//W3C//DTD XHTML 1.1 plus MathML 2.0 plus SVG 1.1//EN" "http://www.w3.org/2002/04/xhtml-math-svg/xhtml-math-svg.dtd" [
+]>
+
+<html xmlns="http://www.w3.org/1999/xhtml" xmlns:svg="http://www.w3.org/2000/svg" xmlns:xul="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" xmlns:xlink="http://www.w3.org/1999/xlink">
+ <head>
+ <title>bug 313737</title>
+ </head>
+ <body>
+
+ <svg:svg style="position:fixed;">
+ <input type="text" style="position:absolute;" />
+ </svg:svg>
+
+ </body>
+</html>
diff --git a/layout/svg/crashtests/314244-1.xul b/layout/svg/crashtests/314244-1.xul
new file mode 100644
index 0000000000..e801ca70e8
--- /dev/null
+++ b/layout/svg/crashtests/314244-1.xul
@@ -0,0 +1,26 @@
+<?xml version="1.0"?>
+<?xml-stylesheet href="chrome://global/skin/" type="text/css"?>
+<!-- From https://bugzilla.mozilla.org/show_bug.cgi?id=314244 -->
+<!-- Just checking for lack of crash, nothing more -->
+
+<window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ title="GMail"
+ width="300" height="300"
+ screenX="10" screenY="10">
+
+ <hbox>
+ <svg version="1.0"
+ xmlns="http://www.w3.org/2000/svg"
+ width="100px" height="100px"
+ id="back-button"
+ class="nav-button"
+ style="display: -moz-box;">
+ <rect x="10" y="10" width="80" height="80" fill="blue" />
+ </svg>
+ <spacer flex="1" />
+ </hbox>
+
+ <spacer flex="1" />
+
+</window>
+
diff --git a/layout/svg/crashtests/322185-1.svg b/layout/svg/crashtests/322185-1.svg
new file mode 100644
index 0000000000..38958c0b41
--- /dev/null
+++ b/layout/svg/crashtests/322185-1.svg
@@ -0,0 +1,6 @@
+<?xml version="1.0"?>
+<svg xmlns="http://www.w3.org/2000/svg">
+ <g id="g" style="display: -moz-grid-line; overflow: hidden;">
+ <circle />
+ </g>
+</svg>
diff --git a/layout/svg/crashtests/322215-1.svg b/layout/svg/crashtests/322215-1.svg
new file mode 100644
index 0000000000..f872fbcd8f
--- /dev/null
+++ b/layout/svg/crashtests/322215-1.svg
@@ -0,0 +1,31 @@
+<?xml version="1.0" standalone="no"?>
+<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN"
+"http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
+<!-- This Source Code Form is subject to the terms of the Mozilla Public
+ - License, v. 2.0. If a copy of the MPL was not distributed with this
+ - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
+<!--
+Copyright Georgi Guninski
+-->
+
+
+<svg width="100%" height="100%" version="1.1"
+xmlns="http://www.w3.org/2000/svg">
+
+<defs>
+<filter id="MyFilter" filterUnits="userSpaceOnUse"
+x="0" y="0" width="32769" height="32769">
+
+<feGaussianBlur in="SourceAlpha" stdDeviation="4" result="blur"/>
+
+</filter>
+</defs>
+
+<rect x="1" y="1" width="198" height="118" fill="#cccccc" />
+
+<g filter="url(#MyFilter)">
+<text fill="#FFFFFF" stroke="black" font-size="45"
+x="42" y="42">Feck b1ll</text>
+</g>
+
+</svg>
diff --git a/layout/svg/crashtests/323704-1.svg b/layout/svg/crashtests/323704-1.svg
new file mode 100644
index 0000000000..13b8d52243
--- /dev/null
+++ b/layout/svg/crashtests/323704-1.svg
@@ -0,0 +1,12 @@
+<svg xmlns='http://www.w3.org/2000/svg'
+xmlns:xlink='http://www.w3.org/1999/xlink'>
+
+ <clipPath id='clipPath_0'>
+ <rect x='10' y='10' width='25' height='25' rx='5' ry='5' fill='none'
+clip-path='url(#clipPath_0)'/>
+ </clipPath>
+
+ <rect x='5' y='5' width='35' height='35' fill='red'
+clip-path='url(#clipPath_0)'/>
+
+</svg>
diff --git a/layout/svg/crashtests/325427-1.svg b/layout/svg/crashtests/325427-1.svg
new file mode 100644
index 0000000000..1f1c645251
--- /dev/null
+++ b/layout/svg/crashtests/325427-1.svg
@@ -0,0 +1,20 @@
+<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
+ <defs>
+ <g id="module">
+ <use xlink:href="#module" />
+ <use xlink:href="#module" />
+ <use xlink:href="#module" />
+ <use xlink:href="#module" />
+ <use xlink:href="#baseModule" />
+ <use xlink:href="#baseModule" />
+ <use xlink:href="#extendsModule" />
+ <use xlink:href="#extendsModule" />
+ <use xlink:href="#extendsModule" />
+ <use xlink:href="#extendsModule" />
+ <use xlink:href="#extendsModule" />
+ <use xlink:href="#extendsModule" />
+ </g>
+ <use id="baseModule" xlink:href="#module" />
+ <use id="extendsModule" xlink:href="#module" />
+ </defs>
+</svg>
diff --git a/layout/svg/crashtests/326495-1.svg b/layout/svg/crashtests/326495-1.svg
new file mode 100644
index 0000000000..4748e31578
--- /dev/null
+++ b/layout/svg/crashtests/326495-1.svg
@@ -0,0 +1,16 @@
+<?xml version="1.0"?>
+<svg width="10cm" height="5cm" viewBox="0 0 1000 500"
+ xmlns="http://www.w3.org/2000/svg"
+ version="1.1">
+
+ <script>
+ function init()
+ {
+ document.getElementsByTagName("rect")[0].style.display = "-moz-inline-stack";
+ }
+
+ window.addEventListener("load", init, false);
+ </script>
+
+ <rect requiredFeatures="http://www.w3.org/TR/SVG11/feature#SVG-nonexistent-feature"/>
+</svg>
diff --git a/layout/svg/crashtests/326974-1.svg b/layout/svg/crashtests/326974-1.svg
new file mode 100644
index 0000000000..750165b730
--- /dev/null
+++ b/layout/svg/crashtests/326974-1.svg
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="UTF-8"?>
+
+<svg xmlns="http://www.w3.org/2000/svg">
+
+<script>
+
+function init() { var n2 = document.getElementById("n2");
+ var n3 = document.getElementById("n3");
+
+ n2.appendChild(n3);
+}
+
+window.addEventListener("load", init, false);
+
+</script>
+
+
+<g id="n2"> <text id="n3" />
+</g>
+
+</svg> \ No newline at end of file
diff --git a/layout/svg/crashtests/327706-1.svg b/layout/svg/crashtests/327706-1.svg
new file mode 100644
index 0000000000..9aae909250
--- /dev/null
+++ b/layout/svg/crashtests/327706-1.svg
@@ -0,0 +1,9 @@
+<?xml version="1.0" encoding="Windows-1252"?>
+<svg xmlns="http://www.w3.org/2000/svg">
+
+<script>
+
+document.documentElement.unsuspendRedraw(6)
+
+</script>
+</svg>
diff --git a/layout/svg/crashtests/327709-1.svg b/layout/svg/crashtests/327709-1.svg
new file mode 100644
index 0000000000..ce07114272
--- /dev/null
+++ b/layout/svg/crashtests/327709-1.svg
@@ -0,0 +1,17 @@
+<!-- From https://bugzilla.mozilla.org/show_bug.cgi?id=327709 -->
+<!-- Just checking for absence of assertion, nothing more -->
+<svg xmlns="http://www.w3.org/2000/svg">
+
+<script>
+function init()
+{
+ var apsl = document.getElementById("n126").animatedPathSegList;
+ apsl.appendItem(apsl.__proto__);
+}
+window.addEventListener("load", init, false);
+</script>
+
+<path id="n126" d="M 270 60 L 320 60 L 320 110 Z"/>
+
+</svg>
+
diff --git a/layout/svg/crashtests/327711-1.svg b/layout/svg/crashtests/327711-1.svg
new file mode 100644
index 0000000000..d919b866f6
--- /dev/null
+++ b/layout/svg/crashtests/327711-1.svg
@@ -0,0 +1,19 @@
+<?xml version="1.0" encoding="UTF-8"?>
+
+<svg xmlns="http://www.w3.org/2000/svg">
+
+<script>
+
+function init()
+{
+ document.documentElement.unsuspendRedrawAll();
+ document.getElementsByTagName("text")[0].firstChild.data = "Quux";
+}
+
+window.addEventListener("load", init, false);
+
+</script>
+
+<text x="125" y="30">Foo</text>
+
+</svg> \ No newline at end of file
diff --git a/layout/svg/crashtests/328137-1.svg b/layout/svg/crashtests/328137-1.svg
new file mode 100644
index 0000000000..26190b1eb0
--- /dev/null
+++ b/layout/svg/crashtests/328137-1.svg
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="UTF-8"?>
+
+<svg xmlns="http://www.w3.org/2000/svg">
+
+<script>
+
+
+function init()
+{
+ x = document.getElementsByTagName("stop");
+ x[0].appendChild(x[1]);
+}
+
+
+window.addEventListener("load", init, false);
+
+</script>
+
+<radialGradient>
+ <stop/>
+ <stop/>
+</radialGradient>
+
+</svg>
diff --git a/layout/svg/crashtests/329848-1.svg b/layout/svg/crashtests/329848-1.svg
new file mode 100644
index 0000000000..ac4f0022e1
--- /dev/null
+++ b/layout/svg/crashtests/329848-1.svg
@@ -0,0 +1 @@
+<svg xmlns="http://www.w3.org/2000/svg"> <polygon transform="?" points="100,100 200,100 150,200"/> </svg> \ No newline at end of file
diff --git a/layout/svg/crashtests/337408-1.xul b/layout/svg/crashtests/337408-1.xul
new file mode 100644
index 0000000000..f56c06ec94
--- /dev/null
+++ b/layout/svg/crashtests/337408-1.xul
@@ -0,0 +1,21 @@
+<?xml version="1.0"?>
+<!-- From https://bugzilla.mozilla.org/show_bug.cgi?id=337408 -->
+<!-- Just checking for lack of crash, nothing more -->
+<window id="svg-in-xul-stack"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ xmlns:svg="http://www.w3.org/2000/svg"
+ style="background-color:white;"
+ screenX="20"
+ screenY="20"
+ width="600"
+ height="400">
+
+ <stack>
+ <box flex="1">
+ <label value="foo"/>
+ </box>
+ <svg:svg>
+ <svg:rect width="100%" height="100%" fill="red" fill-opacity="0.5"/>
+ </svg:svg>
+ </stack>
+</window>
diff --git a/layout/svg/crashtests/338301-1.xhtml b/layout/svg/crashtests/338301-1.xhtml
new file mode 100644
index 0000000000..3367eedebb
--- /dev/null
+++ b/layout/svg/crashtests/338301-1.xhtml
@@ -0,0 +1,13 @@
+<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="de">
+<body>
+
+ <svg xmlns="http://www.w3.org/2000/svg">
+ <defs>
+ <linearGradient>
+ <path/>
+ </linearGradient>
+ </defs>
+ </svg>
+
+</body>
+</html>
diff --git a/layout/svg/crashtests/338312-1.xhtml b/layout/svg/crashtests/338312-1.xhtml
new file mode 100644
index 0000000000..5a751a2ae0
--- /dev/null
+++ b/layout/svg/crashtests/338312-1.xhtml
@@ -0,0 +1,28 @@
+<html xmlns="http://www.w3.org/1999/xhtml">
+<head>
+<script>
+
+
+function boom()
+{
+ document.getElementById("foo").appendChild(document.getElementById("bar"));
+}
+
+window.addEventListener("load", boom, false);
+
+</script>
+</head>
+
+<body>
+
+ <div id="foo"></div>
+
+ <svg xmlns="http://www.w3.org/2000/svg">
+ <defs>
+ <linearGradient id="grad1"/>
+ </defs>
+ <rect id="bar" style="fill:url(#grad1);" />
+ </svg>
+
+</body>
+</html>
diff --git a/layout/svg/crashtests/340083-1.svg b/layout/svg/crashtests/340083-1.svg
new file mode 100644
index 0000000000..7f015b6efe
--- /dev/null
+++ b/layout/svg/crashtests/340083-1.svg
@@ -0,0 +1,9 @@
+<?xml version="1.0" encoding="UTF-8"?>
+
+<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="100%" height="100%">
+ <defs>
+ <title>
+ <image x="30" y="0" width="190" height="190" xlink:href="../../../testing/crashtest/images/tree.gif"/>
+ </title>
+ </defs>
+</svg>
diff --git a/layout/svg/crashtests/340945-1.svg b/layout/svg/crashtests/340945-1.svg
new file mode 100644
index 0000000000..01ac66fb33
--- /dev/null
+++ b/layout/svg/crashtests/340945-1.svg
@@ -0,0 +1,2 @@
+<svg xmlns="http://www.w3.org/2000/svg" style="display: table;">
+</svg>
diff --git a/layout/svg/crashtests/342923-1.html b/layout/svg/crashtests/342923-1.html
new file mode 100644
index 0000000000..bed6e89791
--- /dev/null
+++ b/layout/svg/crashtests/342923-1.html
@@ -0,0 +1,23 @@
+<html>
+<head>
+<script>
+
+function boo()
+{
+ var rect = document.createElementNS("http://www.w3.org/2000/svg", "rect");
+ rect.setAttribute("stroke", "blue");
+
+ document.body.appendChild(rect);
+}
+
+</script>
+</head>
+
+<body class="bodytext" onload="boo();">
+
+<div id="c1"></div>
+
+<p>In a debug trunk build from 2006-006-27, loading this page triggers an assertion. (It also triggers a CSS error in the console, but I think that's a known, separate bug.)</p>
+
+</body>
+</html>
diff --git a/layout/svg/crashtests/343221-1.xhtml b/layout/svg/crashtests/343221-1.xhtml
new file mode 100644
index 0000000000..890b161dfe
--- /dev/null
+++ b/layout/svg/crashtests/343221-1.xhtml
@@ -0,0 +1,20 @@
+<html xmlns="http://www.w3.org/1999/xhtml" class="reftest-wait">
+<head>
+<script>
+
+function boo()
+{
+ document.getElementById("c").style.overflow = "hidden";
+ document.documentElement.removeAttribute("class");
+}
+
+</script>
+</head>
+<body onload="setTimeout(boo, 30);">
+
+<svg xmlns="http://www.w3.org/2000/svg">
+ <circle id="c" cx="50" cy="50" r="20" />
+</svg>
+
+</body>
+</html>
diff --git a/layout/svg/crashtests/344749-1.svg b/layout/svg/crashtests/344749-1.svg
new file mode 100644
index 0000000000..1a02d7c180
--- /dev/null
+++ b/layout/svg/crashtests/344749-1.svg
@@ -0,0 +1,11 @@
+<svg xmlns="http://www.w3.org/2000/svg">
+
+
+<circle cx="6cm" cy="2cm" r="100" fill="red" transform="translate(0,50)" />
+<circle cx="6cm" cy="2cm" r="100" fill="blue" transform="translate(70,150)" />
+<circle cx="6cm" cy="2cm" r="100" fill="green" transform="translate(-70,150)" />
+
+<rect id="rect1" fill="url(#pat0)"/>
+
+<pattern patternUnits="userSpaceOnUse" id="pat0" x="10" y="10" width="20" height="20"> <rect x="5" y="5" width="10" height="10" fill="red" /> <rect x="10" y="10" width="10" height="10" fill="green" /> </pattern>
+ </svg>
diff --git a/layout/svg/crashtests/344887-1.svg b/layout/svg/crashtests/344887-1.svg
new file mode 100644
index 0000000000..f0bd21c592
--- /dev/null
+++ b/layout/svg/crashtests/344887-1.svg
@@ -0,0 +1,18 @@
+<svg xmlns="http://www.w3.org/2000/svg" xmlns:html="http://www.w3.org/1999/xhtml" onload="setTimeout(foo, 30);" class="reftest-wait">
+
+<script>
+
+var SVG_NS = "http://www.w3.org/2000/svg";
+
+function foo()
+{
+ var rect = document.createElementNS(SVG_NS, 'rect');
+ rect.setAttribute('opacity', ".3");
+ document.documentElement.appendChild(rect);
+ document.documentElement.removeAttribute("class");
+}
+
+</script>
+
+
+</svg> \ No newline at end of file
diff --git a/layout/svg/crashtests/344892-1.svg b/layout/svg/crashtests/344892-1.svg
new file mode 100644
index 0000000000..a38d7eb40f
--- /dev/null
+++ b/layout/svg/crashtests/344892-1.svg
@@ -0,0 +1,5 @@
+<svg xmlns="http://www.w3.org/2000/svg">
+
+<text stroke-width="50%">foo</text>
+
+</svg> \ No newline at end of file
diff --git a/layout/svg/crashtests/344898-1.svg b/layout/svg/crashtests/344898-1.svg
new file mode 100644
index 0000000000..34c3f45a4e
--- /dev/null
+++ b/layout/svg/crashtests/344898-1.svg
@@ -0,0 +1,19 @@
+<svg xmlns="http://www.w3.org/2000/svg" onload="setTimeout(removeText, 30);" class="reftest-wait">
+
+
+<script>
+
+function removeText()
+{
+ var x = document.getElementById("textPath");
+ x.removeChild(x.firstChild);
+ document.documentElement.removeAttribute("class");
+}
+
+</script>
+
+
+<text x="30" y="30"><textPath id="textPath">Foo</textPath></text>
+
+
+</svg> \ No newline at end of file
diff --git a/layout/svg/crashtests/344904-1.svg b/layout/svg/crashtests/344904-1.svg
new file mode 100644
index 0000000000..a2c8d07647
--- /dev/null
+++ b/layout/svg/crashtests/344904-1.svg
@@ -0,0 +1,19 @@
+<svg xmlns="http://www.w3.org/2000/svg" onload="setTimeout(boom, 30);" class="reftest-wait">
+
+<script>
+
+function boom()
+{
+ document.getElementById("m").setAttribute("stroke-miterlimit", 1);
+ document.documentElement.removeAttribute("class");
+}
+
+</script>
+
+
+<marker>
+ <path id="m" />
+</marker>
+
+
+</svg>
diff --git a/layout/svg/crashtests/345418-1.svg b/layout/svg/crashtests/345418-1.svg
new file mode 100644
index 0000000000..2cf8b331fa
--- /dev/null
+++ b/layout/svg/crashtests/345418-1.svg
@@ -0,0 +1,4 @@
+<svg xmlns="http://www.w3.org/2000/svg">
+
+<tspan>arg</tspan>
+ </svg> \ No newline at end of file
diff --git a/layout/svg/crashtests/348982-1.xhtml b/layout/svg/crashtests/348982-1.xhtml
new file mode 100644
index 0000000000..ad0340689a
--- /dev/null
+++ b/layout/svg/crashtests/348982-1.xhtml
@@ -0,0 +1,20 @@
+<html xmlns="http://www.w3.org/1999/xhtml">
+
+<head>
+<style>
+#div, #svg { display: table; }
+#g { display: inline; }
+</style>
+</head>
+
+<body>
+ <div id="div">
+ <svg id="svg" xmlns="http://www.w3.org/2000/svg">
+ <g id="g">
+ <circle cx="6.5cm" cy="2cm" r="100" style="fill: blue;" />
+ </g>
+ </svg>
+ </div>
+</body>
+
+</html>
diff --git a/layout/svg/crashtests/354777-1.xhtml b/layout/svg/crashtests/354777-1.xhtml
new file mode 100644
index 0000000000..e82baf34c9
--- /dev/null
+++ b/layout/svg/crashtests/354777-1.xhtml
@@ -0,0 +1,28 @@
+<html xmlns="http://www.w3.org/1999/xhtml">
+
+<head>
+<script>
+
+var SVG_NS = "http://www.w3.org/2000/svg";
+
+function boom()
+{
+ var svgElem = document.createElementNS(SVG_NS, "svg");
+ var ellipse = document.createElementNS(SVG_NS, "ellipse");
+
+ svgElem.setAttribute("viewBox", "0 0 30 40");
+ document.body.appendChild(svgElem);
+ document.body.appendChild(ellipse);
+ ellipse.appendChild(svgElem);
+ svgElem.removeAttribute("viewBox");
+}
+
+</script>
+</head>
+
+<body onload="boom()">
+
+</body>
+
+</html>
+
diff --git a/layout/svg/crashtests/359516-1.svg b/layout/svg/crashtests/359516-1.svg
new file mode 100644
index 0000000000..c997eb3a85
--- /dev/null
+++ b/layout/svg/crashtests/359516-1.svg
@@ -0,0 +1,36 @@
+<svg xmlns="http://www.w3.org/2000/svg"
+ xmlns:html="http://www.w3.org/1999/xhtml"
+ class="reftest-wait"
+ onload="setTimeout(doStuff, 30);">
+
+<html:script style="display: none;" type="text/javascript">
+
+function doStuff()
+{
+ var svg = document.documentElement;
+ var ellipse = document.getElementById("ellipse");
+ var filter = document.getElementById("filter");
+
+ document.addEventListener("DOMNodeRemoved", foopy, false);
+ filter.removeChild(filter.firstChild);
+ document.removeEventListener("DOMNodeRemoved", foopy, false);
+
+ function foopy()
+ {
+ document.removeEventListener("DOMNodeRemoved", foopy, false);
+ svg.appendChild(filter);
+ }
+
+ // Needed for the crash, but not for the assertion.
+ svg.appendChild(ellipse);
+
+ document.documentElement.removeAttribute("class");
+}
+
+</html:script>
+
+<ellipse id="ellipse" cx="200" cy="150" rx="70" ry="40" style="filter: url(#filter);"/>
+
+<filter id="filter"> </filter>
+
+</svg>
diff --git a/layout/svg/crashtests/361015-1.svg b/layout/svg/crashtests/361015-1.svg
new file mode 100644
index 0000000000..8ac4bc56f2
--- /dev/null
+++ b/layout/svg/crashtests/361015-1.svg
@@ -0,0 +1,33 @@
+<svg xmlns="http://www.w3.org/2000/svg"
+ xmlns:html="http://www.w3.org/1999/xhtml"
+ class="reftest-wait"
+ onload="setTimeout(boom, 30)">
+
+<html:script>
+<![CDATA[
+
+function boom()
+{
+ var grad = document.getElementById("grad");
+ var g = document.getElementById("g");
+ grad.appendChild(g);
+ g.removeAttribute("transform");
+ document.documentElement.removeAttribute("class");
+}
+
+]]>
+</html:script>
+
+
+ <g id="g" transform="translate(500,0)">
+ <text x="25" y="85">Foo</text>
+ </g>
+
+
+ <linearGradient id="grad" gradientUnits="objectBoundingBox" x1="0" y1="0" x2="1" y2="1">
+ <stop stop-color="blue" offset="0.2"/>
+ <stop stop-color="lime" offset="0.4"/>
+ </linearGradient>
+
+
+</svg>
diff --git a/layout/svg/crashtests/361587-1.svg b/layout/svg/crashtests/361587-1.svg
new file mode 100644
index 0000000000..52bce9eda7
--- /dev/null
+++ b/layout/svg/crashtests/361587-1.svg
@@ -0,0 +1,31 @@
+<svg xmlns="http://www.w3.org/2000/svg"
+ onload="setTimeout(boom, 30);"
+ class="reftest-wait">
+
+<script style="display: none" type="text/javascript">
+<![CDATA[
+
+function boom()
+{
+ var oldGrad = document.getElementById("grad");
+ oldGrad.parentNode.removeChild(oldGrad);
+
+ var newGrad = document.createElementNS("http://www.w3.org/2000/svg", "radialGradient");
+ newGrad.setAttribute("gradientUnits", "userSpaceOnUse");
+ newGrad.setAttribute("id", "grad");
+
+ document.documentElement.appendChild(newGrad);
+
+ document.documentElement.removeAttribute("class");
+}
+
+]]>
+</script>
+
+ <radialGradient id="grad" gradientUnits="userSpaceOnUse" cx="240" cy="210" r="220" fx="240" fy="210">
+ <stop stop-color="yellow" offset="0"/>
+ <stop stop-color="green" offset="1"/>
+ </radialGradient>
+ <rect x="20" y="150" width="440" height="80" fill="url(#grad)" stroke-width="40"/>
+
+</svg>
diff --git a/layout/svg/crashtests/363611-1.xhtml b/layout/svg/crashtests/363611-1.xhtml
new file mode 100644
index 0000000000..6bc386bcdf
--- /dev/null
+++ b/layout/svg/crashtests/363611-1.xhtml
@@ -0,0 +1,21 @@
+<html xmlns="http://www.w3.org/1999/xhtml">
+<head>
+
+<script style="display: none" type="text/javascript">
+
+function boom()
+{
+ var fo = document.createElementNS("http://www.w3.org/2000/svg", 'foreignObject');
+ document.getElementById("innerSVG").appendChild(fo);
+}
+
+</script>
+
+</head>
+
+<body onload="boom();">
+
+<svg xmlns="http://www.w3.org/2000/svg" ><linearGradient><svg id="innerSVG"></svg></linearGradient></svg>
+
+</body>
+</html>
diff --git a/layout/svg/crashtests/364688-1.svg b/layout/svg/crashtests/364688-1.svg
new file mode 100644
index 0000000000..045061cd2f
--- /dev/null
+++ b/layout/svg/crashtests/364688-1.svg
@@ -0,0 +1,34 @@
+<svg xmlns="http://www.w3.org/2000/svg"
+ xmlns:html="http://www.w3.org/1999/xhtml"
+ onload="setTimeout(boom, 30);"
+ class="reftest-wait">
+
+<script>
+function boom()
+{
+ document.getElementById("sss").removeAttribute('value');
+
+ document.documentElement.removeAttribute("class");
+}
+</script>
+
+
+<foreignObject width="500" height="500" y="300">
+
+<div xmlns="http://www.w3.org/1999/xhtml">
+
+<table border="1">
+ <tr>
+ <td>Foo</td>
+ </tr>
+ <tr>
+ <td><input type="text" value="Baz" id="sss" /></td>
+ </tr>
+</table>
+
+</div>
+
+</foreignObject>
+
+
+</svg>
diff --git a/layout/svg/crashtests/366956-1.svg b/layout/svg/crashtests/366956-1.svg
new file mode 100644
index 0000000000..9836c7ea3a
--- /dev/null
+++ b/layout/svg/crashtests/366956-1.svg
@@ -0,0 +1,61 @@
+<svg xmlns="http://www.w3.org/2000/svg" xmlns:html="http://www.w3.org/1999/xhtml" onload="setTimeout(boom1, 50);" class="reftest-wait">
+
+<html:script>
+
+function boom1()
+{
+ document.getElementsByTagName("mi")[0].setAttribute('id', "ffff");
+
+ document.getElementById("fo").appendChild(document.createTextNode(" "));
+
+ setTimeout(boom2, 50);
+}
+
+function boom2()
+{
+ var fodiv = document.getElementById("fodiv");
+ fodiv.parentNode.removeChild(fodiv);
+
+ document.documentElement.removeAttribute("class");
+}
+
+</html:script>
+
+
+ <g>
+ <foreignObject width="500" height="500" transform="scale(.7,.7)" id="fo" y="300">
+
+<div id="fodiv" xmlns="http://www.w3.org/1999/xhtml">
+
+
+
+<p>0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 990 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 990 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 990 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99</p>
+
+
+
+<div><math xmlns="http://www.w3.org/1998/Math/MathML" display="block">
+<mrow>
+ <mi>A</mi>
+</mrow>
+</math></div>
+
+
+<svg xmlns="http://www.w3.org/2000/svg" id="svg" viewbox="0 0 250 250" width="100" height="100">
+ <style type="text/css">
+ circle:hover {fill-opacity:0.9;}
+ </style>
+
+ <g style="fill-opacity:0.7;" transform="scale(.2)">
+ <circle cx="6.5cm" cy="2cm" r="100" style="fill:red; stroke:black; stroke-width:0.1cm" transform="translate(0,50)" />
+ <circle cx="6.5cm" cy="2cm" r="100" style="fill:blue; stroke:black; stroke-width:0.1cm" transform="translate(70,150)" />
+ <circle cx="6.5cm" cy="2cm" r="100" style="fill:green; stroke:black; stroke-width:0.1cm" transform="translate(-70,150)"/>
+ </g>
+</svg>
+
+</div>
+</foreignObject>
+</g>
+
+
+
+</svg>
diff --git a/layout/svg/crashtests/366956-2.svg b/layout/svg/crashtests/366956-2.svg
new file mode 100644
index 0000000000..a2ab21ed55
--- /dev/null
+++ b/layout/svg/crashtests/366956-2.svg
@@ -0,0 +1,61 @@
+<svg xmlns="http://www.w3.org/2000/svg" xmlns:html="http://www.w3.org/1999/xhtml" onload="setTimeout(boom1, 30);" class="reftest-wait">
+
+<html:script>
+
+function boom1()
+{
+ document.getElementsByTagName("mi")[0].setAttribute('id', "ffff");
+
+ document.getElementById("fo").appendChild(document.createTextNode(" "));
+
+ boom2();
+}
+
+function boom2()
+{
+ var fodiv = document.getElementById("fodiv");
+ fodiv.parentNode.removeChild(fodiv);
+
+ document.documentElement.removeAttribute("class");
+}
+
+</html:script>
+
+
+ <g>
+ <foreignObject width="500" height="500" transform="scale(.7,.7)" id="fo" y="300">
+
+<div id="fodiv" xmlns="http://www.w3.org/1999/xhtml">
+
+
+
+<p>0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 990 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 990 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 990 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99</p>
+
+
+
+<div><math xmlns="http://www.w3.org/1998/Math/MathML" display="block">
+<mrow>
+ <mi>A</mi>
+</mrow>
+</math></div>
+
+
+<svg xmlns="http://www.w3.org/2000/svg" id="svg" viewbox="0 0 250 250" width="100" height="100">
+ <style type="text/css">
+ circle:hover {fill-opacity:0.9;}
+ </style>
+
+ <g style="fill-opacity:0.7;" transform="scale(.2)">
+ <circle cx="6.5cm" cy="2cm" r="100" style="fill:red; stroke:black; stroke-width:0.1cm" transform="translate(0,50)" />
+ <circle cx="6.5cm" cy="2cm" r="100" style="fill:blue; stroke:black; stroke-width:0.1cm" transform="translate(70,150)" />
+ <circle cx="6.5cm" cy="2cm" r="100" style="fill:green; stroke:black; stroke-width:0.1cm" transform="translate(-70,150)"/>
+ </g>
+</svg>
+
+</div>
+</foreignObject>
+</g>
+
+
+
+</svg>
diff --git a/layout/svg/crashtests/367111-1.svg b/layout/svg/crashtests/367111-1.svg
new file mode 100644
index 0000000000..dcf6a39bf7
--- /dev/null
+++ b/layout/svg/crashtests/367111-1.svg
@@ -0,0 +1,29 @@
+<svg xmlns="http://www.w3.org/2000/svg"
+ xmlns:html="http://www.w3.org/1999/xhtml"
+ onload="setTimeout(boom, 30);"
+ class="reftest-wait">
+
+<html:script>
+
+function boom()
+{
+ document.getElementById("text").appendChild(document.getElementById("fo"));
+
+ document.documentElement.removeAttribute("class");
+}
+
+</html:script>
+
+<defs>
+ <marker>
+ <text id="text">svg:text</text>
+ </marker>
+</defs>
+
+<foreignObject id="fo">
+ <div xmlns="http://www.w3.org/1999/xhtml">
+ <p>HTML in a foreignObject</p>
+ </div>
+</foreignObject>
+
+</svg>
diff --git a/layout/svg/crashtests/367368-1.xhtml b/layout/svg/crashtests/367368-1.xhtml
new file mode 100644
index 0000000000..b9bcd3241b
--- /dev/null
+++ b/layout/svg/crashtests/367368-1.xhtml
@@ -0,0 +1,12 @@
+<!-- From https://bugzilla.mozilla.org/show_bug.cgi?id=367368 -->
+<!-- Just checking for crash, nothing more -->
+
+<html xmlns="http://www.w3.org/1999/xhtml">
+ <body>
+
+ <svg xmlns="http://www.w3.org/2000/svg">
+ <circle cx="6.5cm" cy="2cm" r="100" style="fill:red; stroke:black;" transform="translate(0,50)" />
+ </svg>
+
+ </body>
+</html>
diff --git a/layout/svg/crashtests/369233-1.svg b/layout/svg/crashtests/369233-1.svg
new file mode 100644
index 0000000000..22f4aacb37
--- /dev/null
+++ b/layout/svg/crashtests/369233-1.svg
@@ -0,0 +1,33 @@
+<svg xmlns="http://www.w3.org/2000/svg"
+ xmlns:html="http://www.w3.org/1999/xhtml"
+ onload="setTimeout(boom, 200);"
+ class="reftest-wait">
+
+<html:script>
+
+function boom()
+{
+ try {
+ document.getElementById("grad2").gradientUnits.baseVal = "y";
+ } catch (e) {
+ }
+
+ document.documentElement.removeAttribute("class");
+}
+
+</html:script>
+
+
+
+<radialGradient id="grad2" gradientUnits="userSpaceOnUse" cx="240" cy="210" r="220" fx="240" fy="210">
+ <stop stop-color="black" offset="0"/>
+ <stop stop-color="yellow" offset="0.2"/>
+ <stop stop-color="red" offset="0.4"/>
+ <stop stop-color="blue" offset="0.6"/>
+ <stop stop-color="white" offset="0.8"/>
+ <stop stop-color="green" offset="1"/>
+</radialGradient>
+
+<rect x="20" y="150" width="440" height="80" fill="url(#grad2)" stroke-width="40"/>
+
+</svg>
diff --git a/layout/svg/crashtests/369438-1.svg b/layout/svg/crashtests/369438-1.svg
new file mode 100644
index 0000000000..78bcb6b54d
--- /dev/null
+++ b/layout/svg/crashtests/369438-1.svg
@@ -0,0 +1,24 @@
+<svg width="100%" height="100%" version="1.1"
+xmlns="http://www.w3.org/2000/svg" xmlns:html="http://www.w3.org/1999/xhtml" onload="setTimeout(boom, 30);" class="reftest-wait"><html:script src="data:text/javascript,"></html:script><html:script>
+
+function boom()
+{
+ var defs = document.getElementById("defs");
+ defs.parentNode.removeChild(defs);
+
+ document.documentElement.removeAttribute("class");
+}
+
+</html:script>
+
+<defs id="defs">
+<filter id="Gaussian_Blur">
+<feGaussianBlur in="SourceGraphic" stdDeviation="3"/>
+</filter>
+</defs>
+
+<ellipse cx="200" cy="150" rx="70" ry="40"
+style="fill:#ff0000;stroke:#000000;
+stroke-width:2;filter:url(#Gaussian_Blur)"/>
+
+</svg>
diff --git a/layout/svg/crashtests/369438-2.svg b/layout/svg/crashtests/369438-2.svg
new file mode 100644
index 0000000000..92eea9ee0f
--- /dev/null
+++ b/layout/svg/crashtests/369438-2.svg
@@ -0,0 +1,27 @@
+<svg width="100%" height="100%" version="1.1"
+xmlns="http://www.w3.org/2000/svg" xmlns:html="http://www.w3.org/1999/xhtml" onload="setTimeout(boom, 30);" class="reftest-wait"><html:script>
+
+function boom()
+{
+ var defs = document.getElementById("defs");
+ var gb = document.getElementById("Gaussian_Blur");
+
+ defs.parentNode.removeChild(defs);
+ gb.removeChild(gb.firstChild); // remove a whitespace text node (!)
+
+ document.documentElement.removeAttribute("class");
+}
+
+</html:script>
+
+<defs id="defs">
+<filter id="Gaussian_Blur">
+<feGaussianBlur in="SourceGraphic" stdDeviation="3"/>
+</filter>
+</defs>
+
+<ellipse cx="200" cy="150" rx="70" ry="40"
+style="fill:#ff0000;stroke:#000000;
+stroke-width:2;filter:url(#Gaussian_Blur)"/>
+
+</svg>
diff --git a/layout/svg/crashtests/371463-1.xhtml b/layout/svg/crashtests/371463-1.xhtml
new file mode 100644
index 0000000000..461fb27ba3
--- /dev/null
+++ b/layout/svg/crashtests/371463-1.xhtml
@@ -0,0 +1,8 @@
+<html xmlns="http://www.w3.org/1999/xhtml"
+ xmlns:svg="http://www.w3.org/2000/svg">
+<body>
+
+<select><svg:svg><svg:foreignObject/></svg:svg></select>
+
+</body>
+</html>
diff --git a/layout/svg/crashtests/371563-1.xhtml b/layout/svg/crashtests/371563-1.xhtml
new file mode 100644
index 0000000000..0ebdc9bfa1
--- /dev/null
+++ b/layout/svg/crashtests/371563-1.xhtml
@@ -0,0 +1,32 @@
+<html xmlns="http://www.w3.org/1999/xhtml" class="reftest-wait">
+<head>
+<script>
+
+function boom()
+{
+ document.getElementById("sdiv").style.overflow = "scroll";
+
+ document.documentElement.removeAttribute("class");
+}
+
+
+</script>
+</head>
+
+<body onload="setTimeout(boom, 30);">
+
+ <div id="sdiv" style="float: left;">
+
+ <svg xmlns="http://www.w3.org/2000/svg" height="400px" width="400px"
+ y="0.0000000" x="0.0000000" version="1.0" >
+ <defs>
+ <marker id="Arrow"/>
+ </defs>
+ <path style="marker-end:url(#Arrow)"
+ d="M 12.500000,200.00000 L 387.50000,200.00000" />
+ </svg>
+
+ </div>
+
+</body>
+</html>
diff --git a/layout/svg/crashtests/375775-1.svg b/layout/svg/crashtests/375775-1.svg
new file mode 100644
index 0000000000..cd17c85a94
--- /dev/null
+++ b/layout/svg/crashtests/375775-1.svg
@@ -0,0 +1,23 @@
+<svg xmlns="http://www.w3.org/2000/svg" onload="setTimeout(boom, 30);" class="reftest-wait">
+
+<script type="text/javascript">
+
+function boom()
+{
+ document.getElementById("filter").style.display = "none";
+ document.getElementById("path").style.display = "none";
+
+ document.documentElement.removeAttribute("class");
+}
+
+</script>
+
+<filter id="filter" filterUnits="userSpaceOnUse" x="0" y="0" width="200" height="120" />
+
+<g filter="url(#filter)">
+ <path id="path"
+ fill="black"
+ d="M60,80 C30,80 30,40 60,40 L140,40 C170,40 170,80 140,80 z" />
+</g>
+
+</svg>
diff --git a/layout/svg/crashtests/378716.svg b/layout/svg/crashtests/378716.svg
new file mode 100644
index 0000000000..b6faa00284
--- /dev/null
+++ b/layout/svg/crashtests/378716.svg
@@ -0,0 +1,4 @@
+<svg width="100%" height="100%" x="0" y="0" viewBox="0 0 1 1"
+ xmlns="http://www.w3.org/2000/svg">
+ <text id="text_1" x="0.5" y="0.5" font-size="0.05" fill="green">Okay Text</text>
+</svg> \ No newline at end of file
diff --git a/layout/svg/crashtests/380691-1.svg b/layout/svg/crashtests/380691-1.svg
new file mode 100644
index 0000000000..ed28552633
--- /dev/null
+++ b/layout/svg/crashtests/380691-1.svg
@@ -0,0 +1,4 @@
+<svg xmlns="http://www.w3.org/2000/svg">
+ <mask id="m"/>
+ <foreignObject mask="url(#m)"/>
+</svg>
diff --git a/layout/svg/crashtests/384391-1.xhtml b/layout/svg/crashtests/384391-1.xhtml
new file mode 100644
index 0000000000..12c657a48c
--- /dev/null
+++ b/layout/svg/crashtests/384391-1.xhtml
@@ -0,0 +1,20 @@
+<html xmlns="http://www.w3.org/1999/xhtml" xmlns:svg="http://www.w3.org/2000/svg" >
+<head>
+<script>
+
+function boom()
+{
+ var circle = document.getElementById("circle");
+ document.removeChild(document.documentElement);
+ document.appendChild(circle);
+}
+
+</script>
+</head>
+
+<body onload="boom()">
+
+<svg:circle id="circle" />
+
+</body>
+</html>
diff --git a/layout/svg/crashtests/384499-1.svg b/layout/svg/crashtests/384499-1.svg
new file mode 100644
index 0000000000..f448910008
--- /dev/null
+++ b/layout/svg/crashtests/384499-1.svg
@@ -0,0 +1,20 @@
+<svg xmlns="http://www.w3.org/2000/svg" xmlns:html="http://www.w3.org/1999/xhtml">
+
+<html:style>
+ #mathy { display: table}
+</html:style>
+
+<foreignObject width="500" height="500" y="50">
+ <div xmlns="http://www.w3.org/1999/xhtml">
+ <p>Foo</p>
+ <div>
+ <math xmlns="http://www.w3.org/1998/Math/MathML" id="mathy" display="block">
+ <mrow>
+ <mi>x</mi>
+ </mrow>
+ </math>
+ </div>
+ </div>
+</foreignObject>
+
+</svg>
diff --git a/layout/svg/crashtests/384637-1.svg b/layout/svg/crashtests/384637-1.svg
new file mode 100644
index 0000000000..263a2d556a
--- /dev/null
+++ b/layout/svg/crashtests/384637-1.svg
@@ -0,0 +1,9 @@
+<svg xmlns="http://www.w3.org/2000/svg" baseProfile="basic" width="100%" height="100%" viewBox="0 0 480 360">
+
+ <mask id="mask1" maskUnits="userSpaceOnUse" x="60" y="50" width="100" height="60">
+ <rect x="60" y="50" width="100" height="60" fill="yellow" mask="url(#mask1)"/>
+ </mask>
+
+ <rect x="60" y="50" width="100" height="60" fill="lime" mask="url(#mask1)"/>
+
+</svg>
diff --git a/layout/svg/crashtests/384728-1.svg b/layout/svg/crashtests/384728-1.svg
new file mode 100644
index 0000000000..ccafc83706
--- /dev/null
+++ b/layout/svg/crashtests/384728-1.svg
@@ -0,0 +1,21 @@
+<svg xmlns:xlink="http://www.w3.org/1999/xlink" xmlns="http://www.w3.org/2000/svg" onload="boom();">
+
+<script>
+
+function boom()
+{
+ document.getElementById("thhh").setAttributeNS("http://www.w3.org/1999/xlink", 'href', '');
+}
+
+</script>
+
+ <defs>
+ <g id="ch" style="counter-reset: c;">
+ <rect x="75" y="0" width="75" height="75" fill="lightgreen" style="counter-increment: c;"/>
+ </g>
+
+ </defs>
+
+ <use id="thhh" x="0" y="0"><use xlink:href="#ch" x="0" y="0"/></use>
+
+</svg>
diff --git a/layout/svg/crashtests/385246-1.svg b/layout/svg/crashtests/385246-1.svg
new file mode 100644
index 0000000000..cddad0c5e4
--- /dev/null
+++ b/layout/svg/crashtests/385246-1.svg
@@ -0,0 +1,9 @@
+<svg xmlns="http://www.w3.org/2000/svg">
+
+<foreignObject x="100" y="100" width="-2" height="500">
+ <div xmlns="http://www.w3.org/1999/xhtml">
+ <p>Foo</p>
+ </div>
+</foreignObject>
+
+</svg>
diff --git a/layout/svg/crashtests/385246-2.svg b/layout/svg/crashtests/385246-2.svg
new file mode 100644
index 0000000000..c392f2fc8a
--- /dev/null
+++ b/layout/svg/crashtests/385246-2.svg
@@ -0,0 +1,15 @@
+<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
+
+<script type="text/javascript" xlink:href="data:text/javascript,"></script>
+
+
+<foreignObject width="-2" height="500" id="fo" x="300">
+ <div xmlns="http://www.w3.org/1999/xhtml">
+ <p>Hi!!!</p>
+ </div>
+</foreignObject>
+
+
+
+
+</svg>
diff --git a/layout/svg/crashtests/385552-1.svg b/layout/svg/crashtests/385552-1.svg
new file mode 100644
index 0000000000..019e249d77
--- /dev/null
+++ b/layout/svg/crashtests/385552-1.svg
@@ -0,0 +1,4 @@
+<?xml version="1.0" encoding="Windows-1252"?>
+<script>
+document.createElementNS("http://www.w3.org/2000/svg", "svg").unsuspendRedrawAll();
+</script>
diff --git a/layout/svg/crashtests/385552-2.svg b/layout/svg/crashtests/385552-2.svg
new file mode 100644
index 0000000000..9a93d657fb
--- /dev/null
+++ b/layout/svg/crashtests/385552-2.svg
@@ -0,0 +1,4 @@
+<?xml version="1.0" encoding="Windows-1252"?>
+<script>
+document.createElementNS("http://www.w3.org/2000/svg", "svg").suspendRedraw(3);
+</script>
diff --git a/layout/svg/crashtests/385840-1.svg b/layout/svg/crashtests/385840-1.svg
new file mode 100644
index 0000000000..cf7ff6949c
--- /dev/null
+++ b/layout/svg/crashtests/385840-1.svg
@@ -0,0 +1,20 @@
+<svg width="100%" height="100%" xmlns="http://www.w3.org/2000/svg" onload="boom();">
+
+<script>
+
+function boom()
+{
+ var SVG_NS = "http://www.w3.org/2000/svg";
+
+ var svgCircle = document.createElementNS(SVG_NS, 'circle');
+ var svgText = document.createElementNS(SVG_NS, 'text');
+ svgText.appendChild(document.createTextNode("foo"));
+ svgCircle.appendChild(svgText);
+
+ document.removeChild(document.documentElement);
+ document.appendChild(svgCircle);
+}
+
+</script>
+
+</svg>
diff --git a/layout/svg/crashtests/385852-1.svg b/layout/svg/crashtests/385852-1.svg
new file mode 100644
index 0000000000..17ad99ca8f
--- /dev/null
+++ b/layout/svg/crashtests/385852-1.svg
@@ -0,0 +1,34 @@
+<svg width="100%" height="100%" xmlns="http://www.w3.org/2000/svg" onload="setTimeout(boom, 30)" class="reftest-wait">
+
+<script>
+
+var originalRoot = document.documentElement;
+var svgCircle;
+
+function boom()
+{
+ var SVG_NS = "http://www.w3.org/2000/svg";
+
+ var svgPolyline = document.createElementNS(SVG_NS, 'polyline');
+ svgCircle = document.createElementNS(SVG_NS, 'circle');
+
+ svgCircle.appendChild(svgPolyline);
+
+ document.removeChild(originalRoot);
+ document.appendChild(svgCircle);
+
+ setTimeout(restore, 30);
+}
+
+function restore()
+{
+ // We have to put it the root element back in the document so that reftest.js
+ // sees the event for the removal of class="reftest-wait"!
+ document.removeChild(svgCircle);
+ document.appendChild(originalRoot);
+ document.documentElement.removeAttribute("class");
+}
+
+</script>
+
+</svg>
diff --git a/layout/svg/crashtests/386475-1.xhtml b/layout/svg/crashtests/386475-1.xhtml
new file mode 100644
index 0000000000..4d1b9a2808
--- /dev/null
+++ b/layout/svg/crashtests/386475-1.xhtml
@@ -0,0 +1,24 @@
+<html xmlns="http://www.w3.org/1999/xhtml"
+ xmlns:svg="http://www.w3.org/2000/svg">
+<head>
+<script>
+function boom()
+{
+ document.body.style.display = "table-header-group";
+ document.getElementById("svg").setAttribute('height', 1);
+}
+</script>
+</head>
+
+<body onload="boom();">
+
+<svg:svg width="100%" height="100%" id="svg">
+ <svg:g>
+ <svg:foreignObject width="8205em" height="100%">
+ <span>hello</span> <span>world</span>
+ </svg:foreignObject>
+ </svg:g>
+</svg:svg>
+
+</body>
+</html>
diff --git a/layout/svg/crashtests/386566-1.svg b/layout/svg/crashtests/386566-1.svg
new file mode 100644
index 0000000000..fd44879c4f
--- /dev/null
+++ b/layout/svg/crashtests/386566-1.svg
@@ -0,0 +1,21 @@
+<svg xmlns="http://www.w3.org/2000/svg" height="5" onload="setTimeout(boom, 30);" class="reftest-wait">
+
+<bindings xmlns="http://www.mozilla.org/xbl"><binding id="foo"><content></content></binding></bindings>
+
+<script>
+function boom()
+{
+ document.getElementById("rr").style.MozBinding = "url('#foo')";
+ document.documentElement.setAttribute('height', "5px");
+ setTimeout(done, 30);
+}
+
+function done()
+{
+ document.documentElement.removeAttribute("class");
+}
+</script>
+
+<rect id="rr" x="50%" width="25%" height="10em" fill="red"/>
+
+</svg>
diff --git a/layout/svg/crashtests/386690-1.svg b/layout/svg/crashtests/386690-1.svg
new file mode 100644
index 0000000000..e206978134
--- /dev/null
+++ b/layout/svg/crashtests/386690-1.svg
@@ -0,0 +1,3 @@
+<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 30 40">
+ <foreignObject width="-2" height="100" />
+</svg> \ No newline at end of file
diff --git a/layout/svg/crashtests/387290-1.svg b/layout/svg/crashtests/387290-1.svg
new file mode 100644
index 0000000000..4ac8463204
--- /dev/null
+++ b/layout/svg/crashtests/387290-1.svg
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- This Source Code Form is subject to the terms of the Mozilla Public
+ - License, v. 2.0. If a copy of the MPL was not distributed with this
+ - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
+<!--
+Copyright Georgi Guninski
+-->
+
+<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN"
+"http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
+
+<svg version="1.1"
+xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"
+>
+<defs>
+<filter id="dafilter" filterUnits="userSpaceOnUse"
+x="0" y="0" width="4194305" height="17">
+<feGaussianBlur in="SourceAlpha" stdDeviation="4" result="blur"/>
+</filter>
+</defs>
+<g>
+
+<rect fill="red" width="256" height="256" filter="url(#dafilter)" transform="scale(262145,9)" />
+</g>
+
+</svg>
diff --git a/layout/svg/crashtests/402408-1.svg b/layout/svg/crashtests/402408-1.svg
new file mode 100644
index 0000000000..f442b2171e
--- /dev/null
+++ b/layout/svg/crashtests/402408-1.svg
@@ -0,0 +1,32 @@
+<svg xmlns="http://www.w3.org/2000/svg"
+ xmlns:xlink="http://www.w3.org/1999/xlink"
+ onload="boom();"
+ class="reftest-wait">
+
+<script>
+
+function boom()
+{
+ var grad1 = document.getElementById("grad1");
+ var grad2 = document.getElementById("grad2");
+
+ grad1.appendChild(grad2);
+
+ setTimeout(function() {
+ grad1.removeChild(grad2);
+ document.documentElement.removeAttribute("class");
+ }, 30);
+}
+
+</script>
+
+<linearGradient id="grad1" gradientUnits="objectBoundingBox" x1="0" y1="0" x2="1" y2="0">
+ <stop id="green" stop-color="#00dd00" offset="0"/>
+ <stop id="blue" stop-color="#0000dd" offset="1"/>
+</linearGradient>
+
+<linearGradient id="grad2" xlink:href="#grad1"/>
+
+<rect x="20" y="20" width="440" height="80" fill="url(#grad2)" />
+
+</svg>
diff --git a/layout/svg/crashtests/404677-1.xhtml b/layout/svg/crashtests/404677-1.xhtml
new file mode 100644
index 0000000000..c1df3869b9
--- /dev/null
+++ b/layout/svg/crashtests/404677-1.xhtml
@@ -0,0 +1,9 @@
+<html xmlns="http://www.w3.org/1999/xhtml" xmlns:svg="http://www.w3.org/2000/svg">
+<head>
+</head>
+<body>
+
+<svg:svg height="-2" width="5" />
+
+</body>
+</html>
diff --git a/layout/svg/crashtests/409565-1.xhtml b/layout/svg/crashtests/409565-1.xhtml
new file mode 100644
index 0000000000..33ab9cefbb
--- /dev/null
+++ b/layout/svg/crashtests/409565-1.xhtml
@@ -0,0 +1,3 @@
+<html xmlns="http://www.w3.org/1999/xhtml" style="white-space: pre;"><body style="width: 24px; height: 24px; -moz-column-width: 200px;">
+
+ <svg xmlns="http://www.w3.org/2000/svg" style="float: left;"></svg></body></html>
diff --git a/layout/svg/crashtests/409573-1.svg b/layout/svg/crashtests/409573-1.svg
new file mode 100644
index 0000000000..bec1469a52
--- /dev/null
+++ b/layout/svg/crashtests/409573-1.svg
@@ -0,0 +1,17 @@
+<svg xmlns="http://www.w3.org/2000/svg"
+ xmlns:xlink="http://www.w3.org/1999/xlink"
+ onload="document.documentElement.style.MozBinding = 'url(#empty)';">
+
+ <bindings xmlns="http://www.mozilla.org/xbl"><binding id="empty"><content></content></binding></bindings>
+
+ <defs>
+
+ <filter id="filter1"/>
+
+ <filter id="filter2">
+ <use filter="url(#filter1)" />
+ </filter>
+
+ </defs>
+
+</svg>
diff --git a/layout/svg/crashtests/420697-1.svg b/layout/svg/crashtests/420697-1.svg
new file mode 100644
index 0000000000..d8b7f38340
--- /dev/null
+++ b/layout/svg/crashtests/420697-1.svg
@@ -0,0 +1,7 @@
+<svg xmlns="http://www.w3.org/2000/svg">
+ <text stroke="black" y="1em"
+ stroke-dashoffset="1%"
+ stroke-dasharray="1px">
+ m
+ </text>
+</svg>
diff --git a/layout/svg/crashtests/420697-2.svg b/layout/svg/crashtests/420697-2.svg
new file mode 100644
index 0000000000..8987693e50
--- /dev/null
+++ b/layout/svg/crashtests/420697-2.svg
@@ -0,0 +1,6 @@
+<svg xmlns="http://www.w3.org/2000/svg">
+ <text stroke="black" y="1em"
+ stroke-dasharray="1%">
+ m
+ </text>
+</svg>
diff --git a/layout/svg/crashtests/429774-1.svg b/layout/svg/crashtests/429774-1.svg
new file mode 100644
index 0000000000..00b726de6a
--- /dev/null
+++ b/layout/svg/crashtests/429774-1.svg
@@ -0,0 +1,29 @@
+<?xml version="1.0"?>
+
+<svg width="7.5cm" height="5cm" viewBox="0 0 200 120"
+ xmlns="http://www.w3.org/2000/svg">
+
+ <defs>
+ <filter id="MyFilter" filterUnits="userSpaceOnUse" x="0" y="0" width="200" height="120">
+
+ <feOffset in="SourceAlpha" result="offset" dx="4" dy="4" y="76"/>
+
+ <feSpecularLighting in="offset" result="specOut"
+ surfaceScale="5" specularConstant=".75" specularExponent="20">
+ <fePointLight x="-5000" y="-10000" z="20000"/>
+ </feSpecularLighting>
+
+ <feComposite in="SourceAlpha" in2="SourceAlpha" result="litPaint"
+ operator="arithmetic" k1="0" k2="1" k3="1" k4="0"/>
+
+ <feMerge>
+ <feMergeNode in="offset"/>
+ <feMergeNode in="litPaint"/>
+ </feMerge>
+
+ </filter>
+ </defs>
+
+ <g filter="url(#MyFilter)"/>
+
+</svg>
diff --git a/layout/svg/crashtests/441368-1.svg b/layout/svg/crashtests/441368-1.svg
new file mode 100644
index 0000000000..d0fee7478b
--- /dev/null
+++ b/layout/svg/crashtests/441368-1.svg
@@ -0,0 +1,31 @@
+<?xml version="1.0" standalone="no"?>
+<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN"
+"http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
+<!-- This Source Code Form is subject to the terms of the Mozilla Public
+ - License, v. 2.0. If a copy of the MPL was not distributed with this
+ - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
+<!--
+Copyright Georgi Guninski
+-->
+
+
+<svg width="100%" height="100%" version="1.1"
+xmlns="http://www.w3.org/2000/svg">
+
+<defs>
+<filter id="MyFilter" filterUnits="userSpaceOnUse"
+x="0" y="0" width="32769" height="32769">
+
+<feGaussianBlur in="SourceAlpha" stdDeviation="2147483648" result="blur"/>
+
+</filter>
+</defs>
+
+<rect x="1" y="1" width="198" height="118" fill="#cccccc" />
+
+<g filter="url(#MyFilter)">
+<text fill="#FFFFFF" stroke="black" font-size="45"
+x="42" y="42">Feck b1ll</text>
+</g>
+
+</svg>
diff --git a/layout/svg/crashtests/453754-1.svg b/layout/svg/crashtests/453754-1.svg
new file mode 100644
index 0000000000..a32d819281
--- /dev/null
+++ b/layout/svg/crashtests/453754-1.svg
@@ -0,0 +1,7 @@
+<svg xmlns="http://www.w3.org/2000/svg">
+
+ <filter id="f" height="-1"/>
+
+ <rect filter="url(#f)" />
+
+</svg>
diff --git a/layout/svg/crashtests/455314-1.xhtml b/layout/svg/crashtests/455314-1.xhtml
new file mode 100644
index 0000000000..01bb33d653
--- /dev/null
+++ b/layout/svg/crashtests/455314-1.xhtml
@@ -0,0 +1,16 @@
+<html xmlns="http://www.w3.org/1999/xhtml"><head>
+<script>
+function doe() {
+document.getElementById('a').appendChild(document.body);
+}
+setTimeout(doe, 100);
+</script>
+</head>
+<body>
+<div style="position: absolute; -moz-appearance: button; filter: url(#b); "></div>
+<pre style="position: absolute;">
+<table id="b"></table>
+</pre>
+</body>
+<div id="a"/>
+</html> \ No newline at end of file
diff --git a/layout/svg/crashtests/458453.html b/layout/svg/crashtests/458453.html
new file mode 100644
index 0000000000..ab72d46dee
--- /dev/null
+++ b/layout/svg/crashtests/458453.html
@@ -0,0 +1,24 @@
+<html class="reftest-wait">
+<head>
+<script type="text/javascript">
+
+var i = 0;
+
+function bouncy()
+{
+ var body = document.body;
+ document.documentElement.removeChild(body);
+ document.documentElement.appendChild(body);
+
+ if (++i < 30)
+ setTimeout(bouncy, 1);
+ else
+ document.documentElement.removeAttribute("class");
+}
+
+</script>
+</head>
+
+<body onload="bouncy();"><span id="a"></span><span style="filter: url(#a);"><span style="filter: url(#a);">B</span></span></body>
+
+</html>
diff --git a/layout/svg/crashtests/459666-1.html b/layout/svg/crashtests/459666-1.html
new file mode 100644
index 0000000000..69074b6028
--- /dev/null
+++ b/layout/svg/crashtests/459666-1.html
@@ -0,0 +1,7 @@
+<!DOCTYPE html>
+<html style="filter: url(#e);">
+<head></head>
+<body onload="document.documentElement.style.counterReset = 'a';">
+<div id="e"></div>
+</body>
+</html>
diff --git a/layout/svg/crashtests/459883.xhtml b/layout/svg/crashtests/459883.xhtml
new file mode 100644
index 0000000000..e125e71d8a
--- /dev/null
+++ b/layout/svg/crashtests/459883.xhtml
@@ -0,0 +1,13 @@
+<html xmlns="http://www.w3.org/1999/xhtml" style="filter: url(#r);" class="reftest-wait"><head>
+<script type="text/javascript">
+
+function boom()
+{
+ document.getElementById("s").setAttribute("style", "display: -moz-box;");
+ document.documentElement.removeAttribute("class");
+}
+
+window.addEventListener("load", function() { setTimeout(boom, 0); }, false);
+
+</script>
+</head><body><ms xmlns="http://www.w3.org/1998/Math/MathML" id="s"><maction id="r"/></ms></body></html>
diff --git a/layout/svg/crashtests/461289-1.svg b/layout/svg/crashtests/461289-1.svg
new file mode 100644
index 0000000000..82a57f81b0
--- /dev/null
+++ b/layout/svg/crashtests/461289-1.svg
@@ -0,0 +1,18 @@
+<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
+
+<script type="text/javascript">
+
+function boom()
+{
+ var f = document.getElementById("filter1");
+ f.appendChild(document.createElementNS("http://www.w3.org/2000/svg", "feImage"));
+ f.appendChild(document.getElementById("rect"));
+}
+
+window.addEventListener("load", boom, false);
+
+</script>
+
+<filter id="filter1"/><rect id="rect" filter="url(#filter1)"/>
+
+</svg>
diff --git a/layout/svg/crashtests/464374-1.svg b/layout/svg/crashtests/464374-1.svg
new file mode 100644
index 0000000000..9844e5187f
--- /dev/null
+++ b/layout/svg/crashtests/464374-1.svg
@@ -0,0 +1,15 @@
+<svg xmlns="http://www.w3.org/2000/svg" onload="boom();">
+
+<script type="text/javascript">
+
+function boom()
+{
+ document.getElementById("b").appendChild(document.createElementNS("http://www.w3.org/1998/Math/MathML", "math"));
+ document.getElementById("defs").setAttribute("filter", "url(#a)");
+}
+
+</script>
+
+<defs id="defs"><filter id="a"/><g id="b"><rect/></g></defs>
+
+</svg>
diff --git a/layout/svg/crashtests/466585-1.svg b/layout/svg/crashtests/466585-1.svg
new file mode 100644
index 0000000000..22ad862e15
--- /dev/null
+++ b/layout/svg/crashtests/466585-1.svg
@@ -0,0 +1,17 @@
+<svg xmlns="http://www.w3.org/2000/svg">
+
+<script type="text/javascript">
+
+window.addEventListener("load", boom, false);
+
+function boom()
+{
+ document.getElementById("rect").setAttribute("filter", "url(#filter)");
+ document.getElementById("defs").setAttribute("fill", "red");
+}
+
+</script>
+
+<defs id="defs"><filter id="filter"/><rect id="rect"/></defs>
+
+</svg>
diff --git a/layout/svg/crashtests/467323-1.svg b/layout/svg/crashtests/467323-1.svg
new file mode 100644
index 0000000000..9d757c349d
--- /dev/null
+++ b/layout/svg/crashtests/467323-1.svg
@@ -0,0 +1,10 @@
+<svg xmlns="http://www.w3.org/2000/svg" width="500" height="500">
+
+<filter id="f1" filterUnits="userSpaceOnUse" primitiveUnits="userSpaceOnUse">
+ <feFlood flood-color="#ff0000" result="flood" x="0" y="0" width="100" height="100"/>
+ <feDisplacementMap style="color-interpolation-filters:sRGB"
+ in="SourceGraphic" in2="flood" scale="100" xChannelSelector="R" yChannelSelector="G"/>
+</filter>
+<g filter="url(#f1)"></g>
+
+</svg>
diff --git a/layout/svg/crashtests/467498-1.svg b/layout/svg/crashtests/467498-1.svg
new file mode 100644
index 0000000000..9839e6c30d
--- /dev/null
+++ b/layout/svg/crashtests/467498-1.svg
@@ -0,0 +1,12 @@
+<!--
+ Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/licenses/publicdomain/
+-->
+<svg width="100%" height="100%" xmlns="http://www.w3.org/2000/svg"
+ xmlns:xlink="http://www.w3.org/1999/xlink">
+ <use id="a" width="100" height="100" xlink:href="#b"/>
+ <use id="b" x="100" y="100" width="100" height="100" xlink:href="#a"/>
+ <script>
+ document.getElementById("a").setAttribute("width", "200");
+ </script>
+</svg>
diff --git a/layout/svg/crashtests/470124-1.svg b/layout/svg/crashtests/470124-1.svg
new file mode 100644
index 0000000000..ba3b8aff40
--- /dev/null
+++ b/layout/svg/crashtests/470124-1.svg
@@ -0,0 +1,7 @@
+<svg xmlns="http://www.w3.org/2000/svg">
+
+<filter id="f7" filterUnits="userSpaceOnUse" primitiveUnits="objectBoundingBox"><feComposite/></filter>
+
+<g filter="url(#f7)"/>
+
+</svg>
diff --git a/layout/svg/crashtests/472782-1.svg b/layout/svg/crashtests/472782-1.svg
new file mode 100644
index 0000000000..7cfeb11a69
--- /dev/null
+++ b/layout/svg/crashtests/472782-1.svg
@@ -0,0 +1,3 @@
+<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" id="r">
+<text><textPath xlink:href="#r">S</textPath> </text>
+</svg>
diff --git a/layout/svg/crashtests/474700-1.svg b/layout/svg/crashtests/474700-1.svg
new file mode 100644
index 0000000000..141a1b3903
--- /dev/null
+++ b/layout/svg/crashtests/474700-1.svg
@@ -0,0 +1 @@
+<svg xmlns="http://www.w3.org/2000/svg"><filter id="f1" height="-2"/><rect width="50" height="100" filter="url(#f1)"/></svg>
diff --git a/layout/svg/crashtests/475181-1.svg b/layout/svg/crashtests/475181-1.svg
new file mode 100644
index 0000000000..ef5a638afb
--- /dev/null
+++ b/layout/svg/crashtests/475181-1.svg
@@ -0,0 +1 @@
+<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"><filter id="filter1"><feImage y="4095"/><feTile/></filter><rect width="100%" height="100%" filter="url(#filter1)"/></svg>
diff --git a/layout/svg/crashtests/475193-1.html b/layout/svg/crashtests/475193-1.html
new file mode 100644
index 0000000000..edc08bcee4
--- /dev/null
+++ b/layout/svg/crashtests/475193-1.html
@@ -0,0 +1,21 @@
+<!DOCTYPE HTML>
+<html><head>
+<meta http-equiv="content-type" content="text/html; charset=UTF-8">
+
+
+<style type="text/css">
+
+.p { marker: url('#c'); }
+
+</style>
+<script type="text/javascript">
+
+function boom()
+{
+ document.getElementById("a").setAttribute("class", "p");
+ document.documentElement.offsetHeight;
+ document.getElementById("b").setAttribute("id", "c");
+}
+
+</script>
+</head><body onload="boom();"><div class="p" id="a">C</div><div id="c"></div></body></html> \ No newline at end of file
diff --git a/layout/svg/crashtests/475302-1.svg b/layout/svg/crashtests/475302-1.svg
new file mode 100644
index 0000000000..4fdaa2213c
--- /dev/null
+++ b/layout/svg/crashtests/475302-1.svg
@@ -0,0 +1,11 @@
+<svg xmlns="http://www.w3.org/2000/svg">
+
+<style type="text/css">
+ tref { filter: url(#filter1); }
+</style>
+
+<filter id="filter1"><feFlood/></filter>
+
+<tref><polyline points="350,75 379,161 469,161 397,215 423,301 350,250 277,301 303,215 231,161 321,161"/></tref>
+
+</svg>
diff --git a/layout/svg/crashtests/477935-1.html b/layout/svg/crashtests/477935-1.html
new file mode 100644
index 0000000000..9c2ac5438a
--- /dev/null
+++ b/layout/svg/crashtests/477935-1.html
@@ -0,0 +1,11 @@
+<!DOCTYPE html>
+<html>
+<head>
+<style type="text/css"> :root { filter: url('#g'); } </style>
+<style type="text/css" id="ccs"> .cc { content: 'X'; } </style>
+</head>
+<body onload="document.getElementById('ccs').disabled = true;">
+<div id="g"></div>
+<div class="cc">5</div>
+</body>
+</html>
diff --git a/layout/svg/crashtests/478128-1.svg b/layout/svg/crashtests/478128-1.svg
new file mode 100644
index 0000000000..a4100026b1
--- /dev/null
+++ b/layout/svg/crashtests/478128-1.svg
@@ -0,0 +1,15 @@
+<svg xmlns="http://www.w3.org/2000/svg">
+ <script type="text/javascript">
+
+ function boom()
+ {
+ document.documentElement.style.MozColumnCount = '15';
+ }
+ window.onload = function() { setTimeout(boom, 20); };
+
+ </script>
+
+ <foreignObject width="50" height="50" filter="url(#f1)"/>
+
+ <filter id="f1"/>
+</svg>
diff --git a/layout/svg/crashtests/478511-1.svg b/layout/svg/crashtests/478511-1.svg
new file mode 100644
index 0000000000..75a4aaa9b2
--- /dev/null
+++ b/layout/svg/crashtests/478511-1.svg
@@ -0,0 +1,9 @@
+<svg xmlns="http://www.w3.org/2000/svg" version="1.0">
+ <defs>
+ <pattern id="pattern"
+ x="0" y="0" width="200" height="200">
+ <circle fill="lime" r="100" cx="100" cy="100"/>
+ </pattern>
+ </defs>
+ <rect width="200" height="200" fill="url(#pattern)"/>
+</svg>
diff --git a/layout/svg/crashtests/483439-1.svg b/layout/svg/crashtests/483439-1.svg
new file mode 100644
index 0000000000..c9e9ebae1c
--- /dev/null
+++ b/layout/svg/crashtests/483439-1.svg
@@ -0,0 +1,17 @@
+<svg xmlns="http://www.w3.org/2000/svg"
+ xmlns:xlink="http://www.w3.org/1999/xlink">
+
+ <defs>
+ <path id="myTextPath"
+ d="M275,20
+ a1,1 0 0,0 100,0
+ "
+ />
+ </defs>
+
+ <svg y="15">
+ <text x="10" y="100" style="stroke: #000000;">
+ <textPath xlink:href="#myTextPath" >Text along a curved path...</textPath>
+ </text>
+ </svg>
+</svg>
diff --git a/layout/svg/crashtests/492186-1.svg b/layout/svg/crashtests/492186-1.svg
new file mode 100644
index 0000000000..7f4b6650ab
--- /dev/null
+++ b/layout/svg/crashtests/492186-1.svg
@@ -0,0 +1,6 @@
+<svg xmlns="http://www.w3.org/2000/svg">
+<altGlyphDef/>
+<script xmlns="http://www.w3.org/1999/xhtml">
+document.documentElement.getBBox();
+</script>
+</svg>
diff --git a/layout/svg/crashtests/508247-1.svg b/layout/svg/crashtests/508247-1.svg
new file mode 100644
index 0000000000..c8b36b905f
--- /dev/null
+++ b/layout/svg/crashtests/508247-1.svg
@@ -0,0 +1,10 @@
+<svg xmlns="http://www.w3.org/2000/svg">
+
+<definition-src>
+<path id="a"/>
+</definition-src>
+
+<script id="script" xmlns="http://www.w3.org/1999/xhtml">
+setTimeout(function() {document.getElementById('a').getCTM()},10);
+</script>
+</svg>
diff --git a/layout/svg/crashtests/512890-1.svg b/layout/svg/crashtests/512890-1.svg
new file mode 100644
index 0000000000..044f693892
--- /dev/null
+++ b/layout/svg/crashtests/512890-1.svg
@@ -0,0 +1,4 @@
+<svg xmlns="http://www.w3.org/2000/svg">
+ <filter id="f" height="1em"/>
+ <rect width="50" height="50" filter="url(#f)"/>
+</svg>
diff --git a/layout/svg/crashtests/515288-1.html b/layout/svg/crashtests/515288-1.html
new file mode 100644
index 0000000000..d78cbbfbec
--- /dev/null
+++ b/layout/svg/crashtests/515288-1.html
@@ -0,0 +1,5 @@
+<script>
+var svgElem = document.createElementNS("http://www.w3.org/2000/svg", "svg");
+document.createElement("script").appendChild(svgElem);
+svgElem.getScreenCTM();
+</script> \ No newline at end of file
diff --git a/layout/svg/crashtests/522394-1.svg b/layout/svg/crashtests/522394-1.svg
new file mode 100644
index 0000000000..f745c47dd2
--- /dev/null
+++ b/layout/svg/crashtests/522394-1.svg
@@ -0,0 +1,12 @@
+<!--
+ Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/licenses/publicdomain/
+-->
+<svg xmlns="http://www.w3.org/2000/svg">
+<defs>
+ <filter id="f1" x="0" y="0" width="-100" height="-100" filterUnits="userSpaceOnUse" primitiveUnits="userSpaceOnUse">
+ <feFlood flood-color="lime" x="0" y="0" width="100%" height="100%"/>
+ </filter>
+</defs>
+<rect x="10" y="10" width="10" height="10" filter="url(#f1)"/>
+</svg>
diff --git a/layout/svg/crashtests/522394-2.svg b/layout/svg/crashtests/522394-2.svg
new file mode 100644
index 0000000000..1b6f1f0892
--- /dev/null
+++ b/layout/svg/crashtests/522394-2.svg
@@ -0,0 +1,12 @@
+<!--
+ Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/licenses/publicdomain/
+-->
+<svg xmlns="http://www.w3.org/2000/svg">
+<defs>
+ <filter id="f1" x="0" y="0" width="100000000" height="100000000" filterUnits="userSpaceOnUse" primitiveUnits="userSpaceOnUse">
+ <feFlood flood-color="lime" x="0" y="0" width="100%" height="100%"/>
+ </filter>
+</defs>
+<rect x="10" y="10" width="10" height="10" filter="url(#f1)"/>
+</svg>
diff --git a/layout/svg/crashtests/522394-3.svg b/layout/svg/crashtests/522394-3.svg
new file mode 100644
index 0000000000..cf3483cfad
--- /dev/null
+++ b/layout/svg/crashtests/522394-3.svg
@@ -0,0 +1,12 @@
+<!--
+ Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/licenses/publicdomain/
+-->
+<svg xmlns="http://www.w3.org/2000/svg">
+<defs>
+ <filter id="f1" x="0" y="0" width="4563402752" height="4563402752" filterUnits="userSpaceOnUse" primitiveUnits="userSpaceOnUse">
+ <feFlood flood-color="lime" x="0" y="0" width="100%" height="100%"/>
+ </filter>
+</defs>
+<rect x="10" y="10" width="10" height="10" filter="url(#f1)"/>
+</svg>
diff --git a/layout/svg/crashtests/566216-1.svg b/layout/svg/crashtests/566216-1.svg
new file mode 100644
index 0000000000..999aaf4f08
--- /dev/null
+++ b/layout/svg/crashtests/566216-1.svg
@@ -0,0 +1,19 @@
+<?xml version="1.0"?>
+
+<svg xmlns="http://www.w3.org/2000/svg"><animate id="y"/><script>
+<![CDATA[
+
+function boom()
+{
+ var r = document.createRange();
+ r.setEnd(document.getElementById('y'), 0);
+ r.extractContents();
+}
+
+window.addEventListener("load", boom, false);
+
+]]>
+</script>
+</svg>
+
+
diff --git a/layout/svg/crashtests/587336-1.html b/layout/svg/crashtests/587336-1.html
new file mode 100644
index 0000000000..811f483dd7
--- /dev/null
+++ b/layout/svg/crashtests/587336-1.html
@@ -0,0 +1,9 @@
+<html>
+<head><script>
+function boom()
+{
+ var b = document.getElementById("b");
+ b.setAttributeNS(null, "style", "filter: url(#a);");
+}
+</script></head>
+<body onload="boom();" id="a"><span id="b">B</span></body></html>
diff --git a/layout/svg/crashtests/590291-1.svg b/layout/svg/crashtests/590291-1.svg
new file mode 100644
index 0000000000..db26fac3a8
--- /dev/null
+++ b/layout/svg/crashtests/590291-1.svg
@@ -0,0 +1,9 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<svg xmlns="http://www.w3.org/2000/svg" version="1.1" width="0cm" height="0cm">
+
+<text id="a">a</text>
+
+<script><![CDATA[
+var x=document.getElementById('a').getExtentOfChar(0);
+]]></script>
+</svg>
diff --git a/layout/svg/crashtests/601999-1.html b/layout/svg/crashtests/601999-1.html
new file mode 100644
index 0000000000..7e8a3d39de
--- /dev/null
+++ b/layout/svg/crashtests/601999-1.html
@@ -0,0 +1,5 @@
+<html class="reftest-wait">
+ <body onload="document.getElementsByTagName('div')[0].id='b';
+ document.documentElement.removeAttribute('class');"
+ ><div style="overflow-x: scroll; filter: url(#b)">abc</div></body>
+</html>
diff --git a/layout/svg/crashtests/605626-1.svg b/layout/svg/crashtests/605626-1.svg
new file mode 100644
index 0000000000..678b011797
--- /dev/null
+++ b/layout/svg/crashtests/605626-1.svg
@@ -0,0 +1,3 @@
+<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
+<image width="10" height="10" xlink:href="data:text/plain,g"/>
+</svg>
diff --git a/layout/svg/crashtests/610594-1.html b/layout/svg/crashtests/610594-1.html
new file mode 100644
index 0000000000..ee48e762cc
--- /dev/null
+++ b/layout/svg/crashtests/610594-1.html
@@ -0,0 +1,6 @@
+<!DOCTYPE html>
+<html>
+<body>
+<svg><path d="M 0 5 a 2 5 -30 104 -5" marker-mid="url(#q)"></path></svg>
+</body>
+</html>
diff --git a/layout/svg/crashtests/610954-1.html b/layout/svg/crashtests/610954-1.html
new file mode 100644
index 0000000000..f5080df8b8
--- /dev/null
+++ b/layout/svg/crashtests/610954-1.html
@@ -0,0 +1 @@
+<!DOCTYPE html><html><body dir=rtl onload="document.getElementById('g').style.filter = 'url(#filter1)';"><span id="g">&#x200E;---</span></body></html>
diff --git a/layout/svg/crashtests/612662-1.svg b/layout/svg/crashtests/612662-1.svg
new file mode 100644
index 0000000000..73dc98ed19
--- /dev/null
+++ b/layout/svg/crashtests/612662-1.svg
@@ -0,0 +1 @@
+<svg xmlns="http://www.w3.org/2000/svg" style="background: url(#a); direction: rtl; margin: -32944px;"></svg>
diff --git a/layout/svg/crashtests/612662-2.svg b/layout/svg/crashtests/612662-2.svg
new file mode 100644
index 0000000000..b46841132f
--- /dev/null
+++ b/layout/svg/crashtests/612662-2.svg
@@ -0,0 +1,3 @@
+<svg xmlns="http://www.w3.org/2000/svg"
+ style="direction: rtl; margin: -32944px;
+ background: url(data:image/svg+xml,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%3E%3C%2Fsvg%3E)"></svg>
diff --git a/layout/svg/crashtests/612736-1.svg b/layout/svg/crashtests/612736-1.svg
new file mode 100644
index 0000000000..cb3044efd0
--- /dev/null
+++ b/layout/svg/crashtests/612736-1.svg
@@ -0,0 +1,19 @@
+<svg xmlns="http://www.w3.org/2000/svg"
+ xmlns:xlink="http://www.w3.org/1999/xlink"
+ class="reftest-wait">
+ <path id="path"/>
+ <text>
+ <textPath xlink:href="#path">f</textPath>
+ <textPath xlink:href="#path">f</textPath>
+ </text>
+
+ <script>
+ function boom()
+ {
+ var path = document.getElementById("path");
+ path.parentNode.removeChild(path);
+ document.documentElement.removeAttribute("class");
+ }
+ window.addEventListener("load", boom, false);
+ </script>
+</svg>
diff --git a/layout/svg/crashtests/612736-2.svg b/layout/svg/crashtests/612736-2.svg
new file mode 100644
index 0000000000..30b8245a9f
--- /dev/null
+++ b/layout/svg/crashtests/612736-2.svg
@@ -0,0 +1,8 @@
+<svg xmlns="http://www.w3.org/2000/svg"
+ xmlns:xlink="http://www.w3.org/1999/xlink">
+ <path id="path" d="M0 100 h50" stroke="black"/>
+ <text>
+ <textPath xlink:href="#path">abc</textPath>
+ <textPath xlink:href="#path">def</textPath>
+ </text>
+</svg>
diff --git a/layout/svg/crashtests/614367-1.svg b/layout/svg/crashtests/614367-1.svg
new file mode 100644
index 0000000000..3af7b491da
--- /dev/null
+++ b/layout/svg/crashtests/614367-1.svg
@@ -0,0 +1,8 @@
+<?xml version="1.0"?>
+
+<svg xmlns="http://www.w3.org/2000/svg">
+ <polygon id="p" transform="?" />
+ <script>
+ document.getElementById("p").transform.baseVal.removeItem(0);
+ </script>
+</svg>
diff --git a/layout/svg/crashtests/620034-1.html b/layout/svg/crashtests/620034-1.html
new file mode 100644
index 0000000000..bfffd3ffac
--- /dev/null
+++ b/layout/svg/crashtests/620034-1.html
@@ -0,0 +1,15 @@
+<!DOCTYPE html>
+<script>
+
+function boom()
+{
+ var f = document.createElementNS("http://www.w3.org/2000/svg", "feFuncB");
+ var tvb = f.tableValues.baseVal;
+ f.setAttribute("tableValues", "3 7 5");
+ f.setAttribute("tableValues", "i");
+ tvb.numberOfItems;
+}
+
+boom();
+
+</script>
diff --git a/layout/svg/crashtests/621598-1.svg b/layout/svg/crashtests/621598-1.svg
new file mode 100644
index 0000000000..dd47967d35
--- /dev/null
+++ b/layout/svg/crashtests/621598-1.svg
@@ -0,0 +1,16 @@
+<svg xmlns="http://www.w3.org/2000/svg">
+ <marker id="m1">
+ <rect/>
+ <marker>
+ <line id="z" marker-end="url(#m1)"/>
+ </marker>
+ </marker>
+ <script>
+ function boom()
+ {
+ document.getElementById("z").getBoundingClientRect();
+ }
+ window.addEventListener("load", boom, false);
+ </script>
+
+</svg>
diff --git a/layout/svg/crashtests/648819-1.html b/layout/svg/crashtests/648819-1.html
new file mode 100644
index 0000000000..727ca3e55f
--- /dev/null
+++ b/layout/svg/crashtests/648819-1.html
@@ -0,0 +1,6 @@
+<!DOCTYPE html>
+<script>
+var p = document.createElementNS("http://www.w3.org/2000/svg", "pattern");
+p.setAttribute("patternTransform", "i");
+p.patternTransform.baseVal.clear();
+</script>
diff --git a/layout/svg/crashtests/655025-1.svg b/layout/svg/crashtests/655025-1.svg
new file mode 100644
index 0000000000..4501bb57fa
--- /dev/null
+++ b/layout/svg/crashtests/655025-1.svg
@@ -0,0 +1,6 @@
+<svg xmlns="http://www.w3.org/2000/svg">
+ <text id="a">a</text>
+ <script>
+ document.getElementById("a").firstChild.nodeValue = "";
+ </script>
+</svg>
diff --git a/layout/svg/crashtests/655025-2.svg b/layout/svg/crashtests/655025-2.svg
new file mode 100644
index 0000000000..601006e831
--- /dev/null
+++ b/layout/svg/crashtests/655025-2.svg
@@ -0,0 +1,6 @@
+<svg xmlns="http://www.w3.org/2000/svg">
+ <text id="a">a</text>
+ <script>
+ document.getElementById("a").appendChild(document.createTextNode(""));
+ </script>
+</svg>
diff --git a/layout/svg/crashtests/655025-3.svg b/layout/svg/crashtests/655025-3.svg
new file mode 100644
index 0000000000..43e06b6fc3
--- /dev/null
+++ b/layout/svg/crashtests/655025-3.svg
@@ -0,0 +1,9 @@
+<svg xmlns="http://www.w3.org/2000/svg">
+ <script>
+ var a = document.createElementNS("http://www.w3.org/2000/svg", "text");
+ a.appendChild(document.createTextNode(""));
+ document.documentElement.appendChild(a);
+ a.getNumberOfChars();
+ document.documentElement.removeChild(a);
+ </script>
+</svg>
diff --git a/layout/svg/crashtests/657077-1.svg b/layout/svg/crashtests/657077-1.svg
new file mode 100644
index 0000000000..b0165bd14a
--- /dev/null
+++ b/layout/svg/crashtests/657077-1.svg
@@ -0,0 +1,19 @@
+<?xml version="1.0" encoding="Windows-1252"?>
+<svg xmlns="http://www.w3.org/2000/svg">
+
+<svg id="a" requiredExtensions="x"/>
+
+<script>
+<![CDATA[
+
+function boom()
+{
+ document.getElementById("a").unsuspendRedrawAll();
+}
+
+window.addEventListener("load", boom, false);
+
+]]>
+</script>
+
+</svg>
diff --git a/layout/svg/crashtests/669025-1.svg b/layout/svg/crashtests/669025-1.svg
new file mode 100644
index 0000000000..eb529da4e0
--- /dev/null
+++ b/layout/svg/crashtests/669025-1.svg
@@ -0,0 +1,8 @@
+<?xml version="1.0"?>
+<svg xmlns="http://www.w3.org/2000/svg">
+ <script type="text/javascript">
+
+ document.createElementNS("http://www.w3.org/2000/svg", "filter").filterResX;
+
+ </script>
+</svg>
diff --git a/layout/svg/crashtests/669025-2.svg b/layout/svg/crashtests/669025-2.svg
new file mode 100644
index 0000000000..ccecebef3c
--- /dev/null
+++ b/layout/svg/crashtests/669025-2.svg
@@ -0,0 +1,8 @@
+<?xml version="1.0"?>
+<svg xmlns="http://www.w3.org/2000/svg">
+ <script type="text/javascript">
+
+ document.createElementNS("http://www.w3.org/2000/svg", "feGaussianBlur").stdDeviationX;
+
+ </script>
+</svg>
diff --git a/layout/svg/crashtests/682411-1.svg b/layout/svg/crashtests/682411-1.svg
new file mode 100644
index 0000000000..92d0c0a725
--- /dev/null
+++ b/layout/svg/crashtests/682411-1.svg
@@ -0,0 +1,5 @@
+<?xml version="1.0" encoding="Windows-1252"?>
+<svg xmlns="http://www.w3.org/2000/svg" style="width: 0pt; padding: 100px;">
+ <filter id="s"/>
+ <g filter="url(#s)"><text>z</text></g>
+</svg>
diff --git a/layout/svg/crashtests/692203-1.svg b/layout/svg/crashtests/692203-1.svg
new file mode 100644
index 0000000000..8427b84268
--- /dev/null
+++ b/layout/svg/crashtests/692203-1.svg
@@ -0,0 +1,4 @@
+<svg xmlns="http://www.w3.org/2000/svg">
+ <marker id="marker" markerWidth="0"/>
+ <path d="M0,0 L100,100 200,200" marker-mid="url(#marker)"/>
+</svg>
diff --git a/layout/svg/crashtests/692203-2.svg b/layout/svg/crashtests/692203-2.svg
new file mode 100644
index 0000000000..b59926dbba
--- /dev/null
+++ b/layout/svg/crashtests/692203-2.svg
@@ -0,0 +1,4 @@
+<svg xmlns="http://www.w3.org/2000/svg">
+ <marker id="marker" markerHeight="0"/>
+ <path d="M0,0 L100,100 200,200" marker-mid="url(#marker)"/>
+</svg>
diff --git a/layout/svg/crashtests/693424-1.svg b/layout/svg/crashtests/693424-1.svg
new file mode 100644
index 0000000000..8485f6b617
--- /dev/null
+++ b/layout/svg/crashtests/693424-1.svg
@@ -0,0 +1,6 @@
+<svg xmlns="http://www.w3.org/2000/svg">
+ <marker id="m">
+ <foreignObject/>
+ </marker>
+ <line marker-end="url(#m)"/>
+</svg>
diff --git a/layout/svg/crashtests/709920-1.svg b/layout/svg/crashtests/709920-1.svg
new file mode 100644
index 0000000000..5f25155307
--- /dev/null
+++ b/layout/svg/crashtests/709920-1.svg
@@ -0,0 +1,23 @@
+<svg xmlns="http://www.w3.org/2000/svg"
+ class="reftest-wait">
+ <!-- Test to be sure that a zero-sized-in-one-dimension viewBox doesn't
+ make us fail assertions. -->
+ <script>
+ document.addEventListener("MozReftestInvalidate", waitAndFinish, false);
+
+ function waitAndFinish() {
+ // Sadly, MozReftestInvalidate fires sooner than PaintPattern here, so
+ // we need to wait a little bit to give PaintPattern a chance to hit
+ // this bug.
+ setTimeout(finish, 100);
+ }
+
+ function finish() {
+ document.documentElement.removeAttribute("class");
+ }
+ </script>
+ <pattern id="test" viewBox="0 0 1 0">
+ <rect/>
+ </pattern>
+ <rect width="200" height="200" fill="url(#test)"/>
+</svg>
diff --git a/layout/svg/crashtests/709920-2.svg b/layout/svg/crashtests/709920-2.svg
new file mode 100644
index 0000000000..58c51111eb
--- /dev/null
+++ b/layout/svg/crashtests/709920-2.svg
@@ -0,0 +1,23 @@
+<svg xmlns="http://www.w3.org/2000/svg"
+ class="reftest-wait">
+ <!-- Test to be sure that a zero-sized-in-one-dimension viewBox doesn't
+ make us fail assertions. -->
+ <script>
+ document.addEventListener("MozReftestInvalidate", waitAndFinish, false);
+
+ function waitAndFinish() {
+ // Sadly, MozReftestInvalidate fires sooner than PaintPattern here, so
+ // we need to wait a little bit to give PaintPattern a chance to hit
+ // this bug.
+ setTimeout(finish, 100);
+ }
+
+ function finish() {
+ document.documentElement.removeAttribute("class");
+ }
+ </script>
+ <pattern id="test" viewBox="0 0 0 1">
+ <rect/>
+ </pattern>
+ <rect width="200" height="200" fill="url(#test)"/>
+</svg>
diff --git a/layout/svg/crashtests/713413-1.svg b/layout/svg/crashtests/713413-1.svg
new file mode 100644
index 0000000000..7131202335
--- /dev/null
+++ b/layout/svg/crashtests/713413-1.svg
@@ -0,0 +1,12 @@
+<?xml version="1.0" encoding="Windows-1252"?>
+<svg xmlns="http://www.w3.org/2000/svg">
+
+<marker id="m"></marker>
+
+<script>
+window.addEventListener("load", function() {
+ document.getElementById("m").appendChild(document.createElementNS("http://www.w3.org/2000/svg", "foreignObject"));
+}, false);
+</script>
+
+</svg>
diff --git a/layout/svg/crashtests/722003-1.svg b/layout/svg/crashtests/722003-1.svg
new file mode 100644
index 0000000000..58e2d57734
--- /dev/null
+++ b/layout/svg/crashtests/722003-1.svg
@@ -0,0 +1,13 @@
+<svg xmlns="http://www.w3.org/2000/svg">
+
+<marker><foreignObject><span id="x" xmlns="http://www.w3.org/1999/xhtml"></span></foreignObject></marker>
+
+<script>
+
+window.addEventListener("load", function() {
+ document.getElementById("x").getClientRects();
+}, false);
+
+</script>
+
+</svg>
diff --git a/layout/svg/crashtests/725918-1.svg b/layout/svg/crashtests/725918-1.svg
new file mode 100644
index 0000000000..5ebdf33f69
--- /dev/null
+++ b/layout/svg/crashtests/725918-1.svg
@@ -0,0 +1,4 @@
+<svg xmlns="http://www.w3.org/2000/svg">
+ <text stroke="url(#p)">t</text>
+ <pattern id="p"/>
+</svg>
diff --git a/layout/svg/crashtests/732836-1.svg b/layout/svg/crashtests/732836-1.svg
new file mode 100644
index 0000000000..a9abb46668
--- /dev/null
+++ b/layout/svg/crashtests/732836-1.svg
@@ -0,0 +1,17 @@
+<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" >
+
+ <symbol id="z">
+ <use xlink:href="data:image/svg+xml,&lt;svg xmlns='http://www.w3.org/2000/svg' id='root' /&gt;#root" />
+ </symbol>
+
+ <use id="a" xlink:href="#z" width="20"/>
+
+ <script>
+ window.addEventListener("load", function() {
+ window.scrollByPages(0);
+ document.getElementById("a").removeAttribute("width");
+ document.elementFromPoint(0, 0);
+ }, false);
+ </script>
+
+</svg>
diff --git a/layout/svg/crashtests/740627-1.svg b/layout/svg/crashtests/740627-1.svg
new file mode 100644
index 0000000000..b74fcd2ddd
--- /dev/null
+++ b/layout/svg/crashtests/740627-1.svg
@@ -0,0 +1,6 @@
+<svg xmlns="http://www.w3.org/2000/svg">
+ <pattern id="test" viewBox="0 0 10 10" height="-65%">
+ <rect/>
+ </pattern>
+ <rect width="100" height="100" fill="url(#test)"/>
+</svg>
diff --git a/layout/svg/crashtests/740627-2.svg b/layout/svg/crashtests/740627-2.svg
new file mode 100644
index 0000000000..6241ddaae5
--- /dev/null
+++ b/layout/svg/crashtests/740627-2.svg
@@ -0,0 +1,6 @@
+<svg xmlns="http://www.w3.org/2000/svg">
+ <pattern id="test" viewBox="0 0 10 10" width="-65%">
+ <rect/>
+ </pattern>
+ <rect width="100" height="100" fill="url(#test)"/>
+</svg>
diff --git a/layout/svg/crashtests/757704-1.svg b/layout/svg/crashtests/757704-1.svg
new file mode 100644
index 0000000000..b7e610e0e1
--- /dev/null
+++ b/layout/svg/crashtests/757704-1.svg
@@ -0,0 +1,17 @@
+<?xml version="1.0"?>
+
+<svg xmlns="http://www.w3.org/2000/svg"><script>
+<![CDATA[
+
+function boom()
+{
+ var rect = document.createElementNS("http://www.w3.org/2000/svg", "rect");
+ document.documentElement.appendChild(rect);
+ document.removeChild(document.documentElement);
+ rect.getScreenCTM();
+}
+
+window.addEventListener("load", boom, false);
+
+]]>
+</script></svg>
diff --git a/layout/svg/crashtests/757718-1.svg b/layout/svg/crashtests/757718-1.svg
new file mode 100644
index 0000000000..fa948c6677
--- /dev/null
+++ b/layout/svg/crashtests/757718-1.svg
@@ -0,0 +1,18 @@
+<!DOCTYPE html>
+<html>
+<head>
+<script>
+
+function boom()
+{
+ var svgDoc = (new DOMParser).parseFromString("<svg xmlns='http://www.w3.org/2000/svg'></svg>", "image/svg+xml");
+ var svgRoot = svgDoc.documentElement;
+ var rf = svgRoot.requiredFeatures;
+ document.adoptNode(svgRoot);
+ Object.getOwnPropertyNames(rf);
+}
+
+</script>
+</head>
+<body onload="boom();"></body>
+</html>
diff --git a/layout/svg/crashtests/757751-1.svg b/layout/svg/crashtests/757751-1.svg
new file mode 100644
index 0000000000..7ab51d0d19
--- /dev/null
+++ b/layout/svg/crashtests/757751-1.svg
@@ -0,0 +1,8 @@
+<svg xmlns="http://www.w3.org/2000/svg">
+ <defs>
+ <svg id="x" viewBox=" 0 0 10 10"/>
+ </defs>
+ <script>
+ window.addEventListener("load", function() { document.getElementById("x").setAttribute("width", "2"); }, false);
+ </script>
+</svg>
diff --git a/layout/svg/crashtests/767056-1.svg b/layout/svg/crashtests/767056-1.svg
new file mode 100644
index 0000000000..b813d784b3
--- /dev/null
+++ b/layout/svg/crashtests/767056-1.svg
@@ -0,0 +1,21 @@
+<svg xmlns="http://www.w3.org/2000/svg"
+ xmlns:xlink="http://www.w3.org/1999/xlink"
+ width="100%"
+ class="reftest-wait">
+ <script>
+
+function resize() {
+ // Set the viewBox to the same width as the content area, but slightly
+ // higher. This checks that we don't enter an infinite reflow loop. See
+ // https://bugzilla.mozilla.org/show_bug.cgi?id=767056#c10
+ var viewBox = "0 0 " + window.innerWidth + " " + (window.innerHeight + 1);
+ document.documentElement.setAttribute("viewBox", viewBox);
+ document.documentElement.removeAttribute("class");
+}
+
+document.addEventListener("MozReftestInvalidate", resize, false);
+setTimeout(resize, 3000); // For non-gecko
+
+ </script>
+ <rect width="100%" height="100%"/>
+</svg>
diff --git a/layout/svg/crashtests/767535-1.xhtml b/layout/svg/crashtests/767535-1.xhtml
new file mode 100644
index 0000000000..d92bd3b1a3
--- /dev/null
+++ b/layout/svg/crashtests/767535-1.xhtml
@@ -0,0 +1,22 @@
+<html xmlns="http://www.w3.org/1999/xhtml" style="-moz-columns: 2 auto;" class="reftest-wait">
+ <head>
+ <script>
+
+function test() {
+ var r = document.documentElement;
+ document.removeChild(r);
+ document.appendChild(r);
+ document.documentElement.removeAttribute("class");
+}
+
+ </script>
+ </head>
+ <body style="filter:url(#f);" onload="setTimeout(test, 0);">
+ <div>
+ </div>
+ <svg xmlns="http://www.w3.org/2000/svg">
+ <filter id="f"/>
+ </svg>
+ </body>
+</html>
+
diff --git a/layout/svg/crashtests/768087-1.html b/layout/svg/crashtests/768087-1.html
new file mode 100644
index 0000000000..9a7899f9d1
--- /dev/null
+++ b/layout/svg/crashtests/768087-1.html
@@ -0,0 +1,4 @@
+<!DOCTYPE html>
+<html>
+<body onload="setTimeout(function() { document.body.innerHTML = '<span>x<svg viewbox=\'0 0 30 40\' ></svg></span>'; }, 0);"></body>
+</html>
diff --git a/layout/svg/crashtests/768351.svg b/layout/svg/crashtests/768351.svg
new file mode 100644
index 0000000000..50a4b9b9c4
--- /dev/null
+++ b/layout/svg/crashtests/768351.svg
@@ -0,0 +1,2 @@
+<svg xmlns="http://www.w3.org/2000/svg" style="mask: url('data:text/plain,1#f');" />
+
diff --git a/layout/svg/crashtests/778492-1.svg b/layout/svg/crashtests/778492-1.svg
new file mode 100644
index 0000000000..76deda594e
--- /dev/null
+++ b/layout/svg/crashtests/778492-1.svg
@@ -0,0 +1,4 @@
+<?xml version="1.0"?>
+<svg width="1cm" viewBox="0 0 1 1" xmlns="http://www.w3.org/2000/svg">
+<line x2="100" y2="100"/>
+</svg>
diff --git a/layout/svg/crashtests/779971-1.svg b/layout/svg/crashtests/779971-1.svg
new file mode 100644
index 0000000000..d57065a0ba
--- /dev/null
+++ b/layout/svg/crashtests/779971-1.svg
@@ -0,0 +1,14 @@
+<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" id="r" class="reftest-wait">
+<text id="t"><textPath xlink:href="#r">x</textPath>1</text>
+<script>
+
+window.addEventListener("load", function() {
+ setTimeout(function() {
+ document.getElementById("t").lastChild.data = "2";
+
+ document.documentElement.removeAttribute("class");
+ }, 200);
+}, false);
+
+</script>
+</svg>
diff --git a/layout/svg/crashtests/780764-1.svg b/layout/svg/crashtests/780764-1.svg
new file mode 100644
index 0000000000..6f4eb970bb
--- /dev/null
+++ b/layout/svg/crashtests/780764-1.svg
@@ -0,0 +1,18 @@
+<svg xmlns="http://www.w3.org/2000/svg" class="reftest-wait">
+
+<marker id="marker"><path d="M100,0 l100,100 200,200" filter="url(#filter)"/></marker>
+
+<filter id="filter"/>
+
+<path d="M100,0 l100,100 200,200" marker-mid="url(#marker)"/>
+
+<script>
+window.addEventListener("load", function() {
+ setTimeout(function() {
+ document.getElementById("filter").style.fontWeight = "bold";
+ document.documentElement.removeAttribute("class");
+ }, 200);
+}, false);
+</script>
+
+</svg>
diff --git a/layout/svg/crashtests/780963-1.html b/layout/svg/crashtests/780963-1.html
new file mode 100644
index 0000000000..8cbeb1a37f
--- /dev/null
+++ b/layout/svg/crashtests/780963-1.html
@@ -0,0 +1,27 @@
+<!DOCTYPE html>
+<html>
+ <head>
+ <script>
+
+function tweak() {
+ document.body.offsetTop;
+
+ var feImage = document.getElementsByTagName("feImage")[0];
+ feImage.setAttribute('filter', 'url(#f1)')
+ document.body.offsetTop;
+
+ var child = document.createElementNS('http://www.w3.org/2000/svg', 'g')
+ feImage.appendChild(child);
+}
+
+ </script>
+ </head>
+ <body onload="tweak()">
+ <svg xmlns="http://www.w3.org/2000/svg">
+ <filter filterUnits="userSpaceOnUse" id="f1">
+ <feImage/>
+ </filter>
+ <rect height="100" width="100"/>
+ </svg>
+ </body>
+</html>
diff --git a/layout/svg/crashtests/782141-1.svg b/layout/svg/crashtests/782141-1.svg
new file mode 100644
index 0000000000..6f0af76ff4
--- /dev/null
+++ b/layout/svg/crashtests/782141-1.svg
@@ -0,0 +1,16 @@
+<svg xmlns="http://www.w3.org/2000/svg" version="1.1"
+ onload="go()">
+ <script>
+
+function go() {
+ var f = document.getElementById('f');
+ var fm = document.getElementById('fm');
+ f.appendChild(fm.cloneNode(1));
+}
+
+ </script>
+ <filter id="f">
+ <feMorphology id="fm" radius="2147483500"/>
+ </filter>
+ <rect height="28" width="256" filter="url(#f)" />
+</svg>
diff --git a/layout/svg/crashtests/784061-1.svg b/layout/svg/crashtests/784061-1.svg
new file mode 100644
index 0000000000..6a9623154d
--- /dev/null
+++ b/layout/svg/crashtests/784061-1.svg
@@ -0,0 +1,16 @@
+<svg xmlns="http://www.w3.org/2000/svg"
+ class="reftest-wait">
+
+<defs><path id="x"/></defs>
+
+<script>
+function boom()
+{
+ document.getElementById("x").style.transform = "translate(0px)";
+ document.documentElement.removeAttribute("class");
+}
+
+window.addEventListener("load", boom, false);
+</script>
+
+</svg>
diff --git a/layout/svg/crashtests/788831-1.svg b/layout/svg/crashtests/788831-1.svg
new file mode 100644
index 0000000000..b6202a1324
--- /dev/null
+++ b/layout/svg/crashtests/788831-1.svg
@@ -0,0 +1,5 @@
+<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
+ <pattern id="pattern" width="40" height="40"><stop/></pattern>
+ <rect id="rect" width="200" height="100"/>
+ <use xlink:href="#rect" stroke="url(#pattern)" />
+</svg>
diff --git a/layout/svg/crashtests/789390-1.html b/layout/svg/crashtests/789390-1.html
new file mode 100644
index 0000000000..542037f229
--- /dev/null
+++ b/layout/svg/crashtests/789390-1.html
@@ -0,0 +1 @@
+<html style="transition: 1s;"><body onload="document.documentElement.style.stroke = '-moz-objectStroke';"></body></html>
diff --git a/layout/svg/crashtests/790072.svg b/layout/svg/crashtests/790072.svg
new file mode 100644
index 0000000000..b288251a7e
--- /dev/null
+++ b/layout/svg/crashtests/790072.svg
@@ -0,0 +1 @@
+<svg xmlns="http://www.w3.org/2000/svg"><text style="stroke: -moz-objectfill none;">abc</text></svg>
diff --git a/layout/svg/crashtests/791826-1.svg b/layout/svg/crashtests/791826-1.svg
new file mode 100644
index 0000000000..f42261a3ad
--- /dev/null
+++ b/layout/svg/crashtests/791826-1.svg
@@ -0,0 +1,14 @@
+<svg xmlns="http://www.w3.org/2000/svg" style="position: fixed;">
+<script>
+<![CDATA[
+
+function boom()
+{
+ document.documentElement.setAttribute("preserveAspectRatio", "_");
+}
+
+window.addEventListener("load", boom, false);
+
+]]>
+</script>
+</svg>
diff --git a/layout/svg/crashtests/803562-1.svg b/layout/svg/crashtests/803562-1.svg
new file mode 100644
index 0000000000..e6fc91127e
--- /dev/null
+++ b/layout/svg/crashtests/803562-1.svg
@@ -0,0 +1,18 @@
+<svg xmlns="http://www.w3.org/2000/svg">
+
+<foreignObject width="500" height="500" style="-moz-appearance: checkbox;">
+ <option xmlns="http://www.w3.org/1999/xhtml"></option>
+</foreignObject>
+
+<script>
+<![CDATA[
+
+window.addEventListener("load", function() {
+ document.getElementsByTagName("option")[0].appendChild(document.createTextNode("Option 1"));
+}, false);
+
+]]>
+</script>
+
+</svg>
+
diff --git a/layout/svg/crashtests/808318-1.svg b/layout/svg/crashtests/808318-1.svg
new file mode 100644
index 0000000000..48907225cc
--- /dev/null
+++ b/layout/svg/crashtests/808318-1.svg
@@ -0,0 +1,2 @@
+<svg xmlns="http://www.w3.org/2000/svg" style="-moz-transform-style: preserve-3d"></svg>
+
diff --git a/layout/svg/crashtests/813420-1.svg b/layout/svg/crashtests/813420-1.svg
new file mode 100644
index 0000000000..d977c0e982
--- /dev/null
+++ b/layout/svg/crashtests/813420-1.svg
@@ -0,0 +1,14 @@
+<svg xmlns="http://www.w3.org/2000/svg" class="reftest-wait">
+ <svg id="i" style="mask: url(&quot;#none&quot;);">
+ <marker id="markerEnd"/><polygon marker-end="url(#markerEnd)" points="250,150 200,150"/>
+ </svg>
+ <script>
+
+window.addEventListener("MozReftestInvalidate", function() {
+ document.getElementById("i").style.mask = "url(#none)";
+ document.documentElement.removeAttribute("class");
+}, false);
+
+ </script>
+</svg>
+
diff --git a/layout/svg/crashtests/841163-1.svg b/layout/svg/crashtests/841163-1.svg
new file mode 100644
index 0000000000..b1bb5198ab
--- /dev/null
+++ b/layout/svg/crashtests/841163-1.svg
@@ -0,0 +1,29 @@
+<svg xmlns="http://www.w3.org/2000/svg" class="reftest-wait">
+
+<filter id="f"/>
+
+<g filter="url(#f)">
+ <text>AB</text>
+</g>
+
+<script>
+
+function forceFrameConstruction()
+{
+ document.documentElement.getBoundingClientRect();
+}
+
+function boom()
+{
+ document.getElementsByTagName("text")[0].firstChild.splitText(1);
+ forceFrameConstruction();
+ document.normalize();
+ forceFrameConstruction();
+ document.documentElement.removeAttribute("class");
+}
+
+window.addEventListener("load", boom, false);
+
+</script>
+
+</svg>
diff --git a/layout/svg/crashtests/841812-1.svg b/layout/svg/crashtests/841812-1.svg
new file mode 100644
index 0000000000..e5bcaa66ee
--- /dev/null
+++ b/layout/svg/crashtests/841812-1.svg
@@ -0,0 +1,11 @@
+<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
+ <text>
+ <circle>
+ <textPath id="t" xlink:href="data:text/html,1" />
+ </circle>
+ </text>
+
+ <script>
+ window.addEventListener("load", function() { document.getElementById("t").removeAttribute('xlink:href'); }, false);
+ </script>
+</svg>
diff --git a/layout/svg/crashtests/842009-1.svg b/layout/svg/crashtests/842009-1.svg
new file mode 100644
index 0000000000..25656ba530
--- /dev/null
+++ b/layout/svg/crashtests/842009-1.svg
@@ -0,0 +1,5 @@
+<svg xmlns="http://www.w3.org/2000/svg"
+ xmlns:xlink="http://www.w3.org/1999/xlink">
+ <title id="hello">hello</title>
+ <text x="100" y="100"> <tref xlink:href="#hello"/></text>
+</svg>
diff --git a/layout/svg/crashtests/842630-1.svg b/layout/svg/crashtests/842630-1.svg
new file mode 100644
index 0000000000..8d36998be6
--- /dev/null
+++ b/layout/svg/crashtests/842630-1.svg
@@ -0,0 +1 @@
+<svg xmlns="http://www.w3.org/2000/svg"><text dy="20 20">A<tspan style="display: none;">B</tspan></text></svg>
diff --git a/layout/svg/crashtests/842909-1.svg b/layout/svg/crashtests/842909-1.svg
new file mode 100644
index 0000000000..9a1bc89eb4
--- /dev/null
+++ b/layout/svg/crashtests/842909-1.svg
@@ -0,0 +1,11 @@
+<svg xmlns="http://www.w3.org/2000/svg">
+ <defs>
+ <text id="t">X</text>
+ </defs>
+
+ <script>
+ window.addEventListener("load", function() {
+ document.getElementById("t").getSubStringLength(0, 0);
+ }, false);
+ </script>
+</svg>
diff --git a/layout/svg/crashtests/843072-1.svg b/layout/svg/crashtests/843072-1.svg
new file mode 100644
index 0000000000..590721f058
--- /dev/null
+++ b/layout/svg/crashtests/843072-1.svg
@@ -0,0 +1,11 @@
+<svg xmlns="http://www.w3.org/2000/svg">
+ <defs>
+ <text id="t"></text>
+ </defs>
+
+ <script>
+ window.addEventListener("load", function() {
+ document.getElementById("t").getExtentOfChar(0);
+ }, false);
+ </script>
+</svg>
diff --git a/layout/svg/crashtests/843917-1.svg b/layout/svg/crashtests/843917-1.svg
new file mode 100644
index 0000000000..55cf7ab186
--- /dev/null
+++ b/layout/svg/crashtests/843917-1.svg
@@ -0,0 +1,19 @@
+<svg xmlns="http://www.w3.org/2000/svg">
+
+ <filter id="f"/>
+
+ <g filter="url(#f)">
+ <text>a&#x1e82f;</text>
+ </g>
+
+ <script>
+
+ window.addEventListener("load", function() {
+ var text = document.getElementsByTagName("text")[0];
+ text.firstChild.data = "d";
+ text.appendChild(document.createElementNS("http://www.w3.org/2000/svg", "g"));
+ }, false);
+
+ </script>
+
+</svg>
diff --git a/layout/svg/crashtests/847139-1.svg b/layout/svg/crashtests/847139-1.svg
new file mode 100644
index 0000000000..81fffa4be8
--- /dev/null
+++ b/layout/svg/crashtests/847139-1.svg
@@ -0,0 +1,13 @@
+<svg xmlns="http://www.w3.org/2000/svg">
+
+<marker><text>x</text></marker>
+
+<script>
+
+window.addEventListener("load", function() {
+ document.caretPositionFromPoint(0, 0);
+}, false);
+
+</script>
+
+</svg>
diff --git a/layout/svg/crashtests/849688-1.svg b/layout/svg/crashtests/849688-1.svg
new file mode 100644
index 0000000000..142f04c933
--- /dev/null
+++ b/layout/svg/crashtests/849688-1.svg
@@ -0,0 +1,11 @@
+<svg xmlns="http://www.w3.org/2000/svg">
+
+<text></text>
+
+<script>
+window.addEventListener("load", function() {
+ document.getElementsByTagName('text')[0].getStartPositionOfChar(1);
+}, false);
+</script>
+
+</svg>
diff --git a/layout/svg/crashtests/849688-2.svg b/layout/svg/crashtests/849688-2.svg
new file mode 100644
index 0000000000..4b71b20c7c
--- /dev/null
+++ b/layout/svg/crashtests/849688-2.svg
@@ -0,0 +1,11 @@
+<svg xmlns="http://www.w3.org/2000/svg">
+
+<text>X</text>
+
+<script>
+window.addEventListener("load", function() {
+ document.getElementsByTagName('text')[0].getStartPositionOfChar(2);
+}, false);
+</script>
+
+</svg>
diff --git a/layout/svg/crashtests/860378-1.svg b/layout/svg/crashtests/860378-1.svg
new file mode 100644
index 0000000000..f4ec09bc4c
--- /dev/null
+++ b/layout/svg/crashtests/860378-1.svg
@@ -0,0 +1,24 @@
+<svg xmlns="http://www.w3.org/2000/svg">
+
+<filter id="f"/>
+<g filter="url(#f)"><text>ab</text></g>
+
+<script>
+
+function boom()
+{
+ var svgtext = document.getElementsByTagName("text")[0];
+ var text1 = svgtext.firstChild ;
+ var text2 = text1.splitText(1);
+
+ setTimeout(function() {
+ text1.data = "c";
+ svgtext.removeChild(text2);
+ }, 200);
+}
+
+window.addEventListener("load", boom, false);
+
+</script>
+
+</svg>
diff --git a/layout/svg/crashtests/868904-1.svg b/layout/svg/crashtests/868904-1.svg
new file mode 100644
index 0000000000..c8d7e9437e
--- /dev/null
+++ b/layout/svg/crashtests/868904-1.svg
@@ -0,0 +1,17 @@
+<!DOCTYPE html>
+<html>
+<head>
+<meta charset="UTF-8">
+<style>
+
+* { animation-name: a; animation-duration: 72ms }
+@keyframes a { 60% { transform: skewx(30deg); } }
+
+</style>
+</head>
+<body>
+
+<svg></svg>
+
+</body>
+</html>
diff --git a/layout/svg/crashtests/873806-1.svg b/layout/svg/crashtests/873806-1.svg
new file mode 100644
index 0000000000..e40aff201b
--- /dev/null
+++ b/layout/svg/crashtests/873806-1.svg
@@ -0,0 +1,10 @@
+<svg xmlns="http://www.w3.org/2000/svg">
+ <foreignObject requiredFeatures="fail">
+ <svg>
+ <text>a</text>
+ </svg>
+ </foreignObject>
+ <script>
+ document.querySelector("text").getBBox();
+ </script>
+</svg>
diff --git a/layout/svg/crashtests/876831-1.svg b/layout/svg/crashtests/876831-1.svg
new file mode 100644
index 0000000000..6b6c01f9e7
--- /dev/null
+++ b/layout/svg/crashtests/876831-1.svg
@@ -0,0 +1,18 @@
+<svg xmlns="http://www.w3.org/2000/svg">
+
+<script>
+
+function boom()
+{
+ var x = document.getElementById("x").firstChild;
+ x.data = x.data.slice(1);
+ document.caretPositionFromPoint(0, 0);
+}
+
+window.addEventListener("load", boom, false);
+
+</script>
+
+<text><tspan id="x">@&#x062A;</tspan></text>
+
+</svg>
diff --git a/layout/svg/crashtests/877029-1.svg b/layout/svg/crashtests/877029-1.svg
new file mode 100644
index 0000000000..1a7bad0f1b
--- /dev/null
+++ b/layout/svg/crashtests/877029-1.svg
@@ -0,0 +1,10 @@
+<!--
+ Check that we don't crash due to an nsSVGMarkerFrame having a null
+ mMarkedFrame and incorrectly calling GetCanvasTM() on the nsSVGMarkerFrame.
+ -->
+<svg xmlns="http://www.w3.org/2000/svg">
+ <marker><text>a</text></marker>
+ <script>
+ document.querySelector("text").getComputedTextLength();
+ </script>
+</svg>
diff --git a/layout/svg/crashtests/880925-1.svg b/layout/svg/crashtests/880925-1.svg
new file mode 100644
index 0000000000..77efd3c0a5
--- /dev/null
+++ b/layout/svg/crashtests/880925-1.svg
@@ -0,0 +1,26 @@
+<?xml version="1.0"?>
+
+<svg xmlns="http://www.w3.org/2000/svg">
+<script>
+<![CDATA[
+
+function boom()
+{
+ var svgText = document.createElementNS("http://www.w3.org/2000/svg", "text");
+ document.documentElement.appendChild(svgText);
+ var text1 = document.createTextNode("A");
+ svgText.appendChild(text1);
+ var text2 = document.createTextNode("");
+ svgText.appendChild(text2);
+ document.caretPositionFromPoint(0, 0);
+ setTimeout(function() {
+ text2.data = "B";
+ document.caretPositionFromPoint(0, 0);
+ }, 0);
+}
+
+window.addEventListener("load", boom, false);
+
+]]>
+</script>
+</svg>
diff --git a/layout/svg/crashtests/881031-1.svg b/layout/svg/crashtests/881031-1.svg
new file mode 100644
index 0000000000..0738e1299d
--- /dev/null
+++ b/layout/svg/crashtests/881031-1.svg
@@ -0,0 +1,15 @@
+<svg xmlns="http://www.w3.org/2000/svg">
+<script>
+
+function boom()
+{
+ var svgText = document.getElementById("t");
+ svgText.firstChild.data = "C";
+ svgText.appendChild(document.createTextNode("D"));
+ document.caretPositionFromPoint(0, 0);
+}
+window.addEventListener("load", boom, false);
+
+</script>
+<text id="t">A</text>
+</svg>
diff --git a/layout/svg/crashtests/885608-1.svg b/layout/svg/crashtests/885608-1.svg
new file mode 100644
index 0000000000..0c96777508
--- /dev/null
+++ b/layout/svg/crashtests/885608-1.svg
@@ -0,0 +1,13 @@
+<svg xmlns="http://www.w3.org/2000/svg">
+
+<mask id="m"><text id="t">z</text></mask>
+
+<rect width="600" height="400" mask="url(#m)"/>
+
+<script>
+window.addEventListener("load", function() {
+ document.getElementById("t").firstChild.data = "ab";
+}, false);
+</script>
+
+</svg>
diff --git a/layout/svg/crashtests/890782-1.svg b/layout/svg/crashtests/890782-1.svg
new file mode 100644
index 0000000000..686bc73a8f
--- /dev/null
+++ b/layout/svg/crashtests/890782-1.svg
@@ -0,0 +1,16 @@
+<svg xmlns="http://www.w3.org/2000/svg">
+
+ <foreignObject requiredFeatures="foo" id="f">
+ <svg>
+ <text id="t"/>
+ </svg>
+ </foreignObject>
+
+ <script>
+ window.addEventListener("load", function() {
+ document.documentElement.appendChild(document.getElementById("f"))
+ document.getElementById("t").getNumberOfChars();
+ }, false);
+ </script>
+
+</svg>
diff --git a/layout/svg/crashtests/890783-1.svg b/layout/svg/crashtests/890783-1.svg
new file mode 100644
index 0000000000..25f54ba2a3
--- /dev/null
+++ b/layout/svg/crashtests/890783-1.svg
@@ -0,0 +1,19 @@
+<svg xmlns="http://www.w3.org/2000/svg">
+
+ <mask id="m">
+ <text>
+ <tspan id="tspan" />
+ </text>
+ </mask>
+
+ <rect width="600" height="400" mask="url(#m)"/>
+
+ <script>
+
+ window.addEventListener("load", function() {
+ document.getElementById("tspan").style.dominantBaseline = "alphabetic";
+ }, false);
+
+ </script>
+
+</svg>
diff --git a/layout/svg/crashtests/893510-1.svg b/layout/svg/crashtests/893510-1.svg
new file mode 100644
index 0000000000..bb58be0450
--- /dev/null
+++ b/layout/svg/crashtests/893510-1.svg
@@ -0,0 +1,5 @@
+<svg xmlns="http://www.w3.org/2000/svg">
+ <g requiredExtensions="foo">
+ <text>&#x062A;z</text>
+ </g>
+</svg> \ No newline at end of file
diff --git a/layout/svg/crashtests/895311-1.svg b/layout/svg/crashtests/895311-1.svg
new file mode 100644
index 0000000000..7b0c728043
--- /dev/null
+++ b/layout/svg/crashtests/895311-1.svg
@@ -0,0 +1,17 @@
+<svg xmlns="http://www.w3.org/2000/svg">
+
+ <mask id="m">
+ <text>
+ <tspan id="ts" />
+ </text>
+ </mask>
+
+ <rect width="600" height="400" mask="url(#m)"/>
+
+ <script>
+ window.addEventListener("load", function() {
+ document.getElementById("ts").style.overflow = "hidden";
+ }, false);
+ </script>
+
+</svg>
diff --git a/layout/svg/crashtests/897342-1.svg b/layout/svg/crashtests/897342-1.svg
new file mode 100644
index 0000000000..547e919b7d
--- /dev/null
+++ b/layout/svg/crashtests/897342-1.svg
@@ -0,0 +1 @@
+<svg xmlns="http://www.w3.org/2000/svg"><text textLength="50" lengthAdjust="spacingAndGlyphs">&#x200D;</text></svg>
diff --git a/layout/svg/crashtests/898909-1.svg b/layout/svg/crashtests/898909-1.svg
new file mode 100644
index 0000000000..8a70cd7b8d
--- /dev/null
+++ b/layout/svg/crashtests/898909-1.svg
@@ -0,0 +1,11 @@
+<svg xmlns="http://www.w3.org/2000/svg" requiredFeatures="foo">
+
+ <text id="t" />
+
+ <script>
+ window.addEventListener("load", function() {
+ document.getElementById("t").getComputedTextLength();
+ }, false);
+ </script>
+
+</svg>
diff --git a/layout/svg/crashtests/898951-1.svg b/layout/svg/crashtests/898951-1.svg
new file mode 100644
index 0000000000..f42dbf69f2
--- /dev/null
+++ b/layout/svg/crashtests/898951-1.svg
@@ -0,0 +1,3 @@
+<svg xmlns="http://www.w3.org/2000/svg">
+ <text>X<tspan style="display: none;">2</tspan>&#x0301;</text>
+</svg>
diff --git a/layout/svg/crashtests/913990.html b/layout/svg/crashtests/913990.html
new file mode 100644
index 0000000000..21d8ef3cc3
--- /dev/null
+++ b/layout/svg/crashtests/913990.html
@@ -0,0 +1,5 @@
+<!DOCTYPE html>
+<html>
+<body style="filter: url('feed:javascript:5');">
+</body>
+</html>
diff --git a/layout/svg/crashtests/919371-1.xhtml b/layout/svg/crashtests/919371-1.xhtml
new file mode 100644
index 0000000000..b27ba3fa66
--- /dev/null
+++ b/layout/svg/crashtests/919371-1.xhtml
@@ -0,0 +1,5 @@
+<html xmlns="http://www.w3.org/1999/xhtml">
+ <svg xmlns="http://www.w3.org/2000/svg">
+ <marker style="position: absolute;" />
+ </svg>
+</html>
diff --git a/layout/svg/crashtests/950324-1.svg b/layout/svg/crashtests/950324-1.svg
new file mode 100644
index 0000000000..a43d84f4d8
--- /dev/null
+++ b/layout/svg/crashtests/950324-1.svg
@@ -0,0 +1,3 @@
+<svg xmlns="http://www.w3.org/2000/svg">
+ <text style="font-family: sans-serif;"> &#x0301;</text>
+</svg>
diff --git a/layout/svg/crashtests/952270-1.svg b/layout/svg/crashtests/952270-1.svg
new file mode 100644
index 0000000000..69bac47d42
--- /dev/null
+++ b/layout/svg/crashtests/952270-1.svg
@@ -0,0 +1,9 @@
+<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
+
+ <path id="path" transform="scale(2,1)" />
+
+ <text>
+ <textPath xlink:href="#path">F</textPath>
+ </text>
+
+</svg>
diff --git a/layout/svg/crashtests/963086-1.svg b/layout/svg/crashtests/963086-1.svg
new file mode 100644
index 0000000000..3805b46d75
--- /dev/null
+++ b/layout/svg/crashtests/963086-1.svg
@@ -0,0 +1,18 @@
+<svg xmlns="http://www.w3.org/2000/svg"
+ viewBox="0 0 64 64">
+ <defs>
+ <filter id="dropShadow">
+ <feGaussianBlur stdDeviation="2" />
+ <feOffset
+ result="offsetBlur"
+ dy="1073741824"/>
+ <feMerge>
+ <feMergeNode
+ in="offsetBlur" />
+ <feMergeNode
+ in="SourceGraphic" />
+ </feMerge>
+ </filter>
+ </defs>
+ <rect height="64" width="64" style="filter:url(#dropShadow)" />
+</svg>
diff --git a/layout/svg/crashtests/974746-1.svg b/layout/svg/crashtests/974746-1.svg
new file mode 100644
index 0000000000..c619c25f79
--- /dev/null
+++ b/layout/svg/crashtests/974746-1.svg
@@ -0,0 +1,9 @@
+<svg xmlns="http://www.w3.org/2000/svg">
+
+ <pattern id="patternRotated" width="1" patternTransform="rotate(45 50 50)">
+ <rect/>
+ </pattern>
+
+ <rect width="100" height="100" fill="url(#patternRotated)"/>
+
+</svg>
diff --git a/layout/svg/crashtests/975773-1.svg b/layout/svg/crashtests/975773-1.svg
new file mode 100644
index 0000000000..dd225eb2ae
--- /dev/null
+++ b/layout/svg/crashtests/975773-1.svg
@@ -0,0 +1,10 @@
+<svg xmlns="http://www.w3.org/2000/svg">
+
+ <filter id="f">
+ <feSpecularLighting style="display: none;"/>
+ <feComposite in="SourceGraphic"/>
+ </filter>
+
+ <path d="M0,0 h100 v100 h-100 z M20,20 v60 h60 v-60 z" filter="url(#f)"/>
+
+</svg>
diff --git a/layout/svg/crashtests/979407-1.svg b/layout/svg/crashtests/979407-1.svg
new file mode 100644
index 0000000000..b615f3bec2
--- /dev/null
+++ b/layout/svg/crashtests/979407-1.svg
@@ -0,0 +1,4 @@
+<svg xmlns="http://www.w3.org/2000/svg">
+ <marker id="marker" viewBox="0 0 10 10" markerHeight="-1px"/>
+ <path d="M0,0 h10" marker-start="url(#marker)"/>
+</svg>
diff --git a/layout/svg/crashtests/979407-2.svg b/layout/svg/crashtests/979407-2.svg
new file mode 100644
index 0000000000..75aee06345
--- /dev/null
+++ b/layout/svg/crashtests/979407-2.svg
@@ -0,0 +1,4 @@
+<svg xmlns="http://www.w3.org/2000/svg">
+ <marker id="marker" viewBox="0 0 10 10" markerWidth="-1px"/>
+ <path d="M0,0 h10" marker-start="url(#marker)"/>
+</svg>
diff --git a/layout/svg/crashtests/993443.svg b/layout/svg/crashtests/993443.svg
new file mode 100644
index 0000000000..30bd18543c
--- /dev/null
+++ b/layout/svg/crashtests/993443.svg
@@ -0,0 +1,4 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<svg xmlns="http://www.w3.org/2000/svg">
+<image width="10" height="10" x="17592186044416pt"/>
+</svg> \ No newline at end of file
diff --git a/layout/svg/crashtests/crashtests.list b/layout/svg/crashtests/crashtests.list
new file mode 100644
index 0000000000..adc2db66b6
--- /dev/null
+++ b/layout/svg/crashtests/crashtests.list
@@ -0,0 +1,199 @@
+load 220165-1.svg
+load 267650-1.svg
+load 294022-1.svg
+load 307314-1.svg
+load 308615-1.svg
+load 308917-1.svg
+load 310436-1.svg
+load 310638.svg
+load 313737-1.xml
+load 314244-1.xul
+load 322185-1.svg
+load 322215-1.svg
+load 323704-1.svg
+load 325427-1.svg
+load 326495-1.svg
+load 326974-1.svg
+load 327706-1.svg
+load 327709-1.svg
+load 327711-1.svg
+load 328137-1.svg
+load 329848-1.svg
+load 337408-1.xul
+load 338301-1.xhtml
+load 338312-1.xhtml
+load 340083-1.svg
+load 340945-1.svg
+load 342923-1.html
+load 343221-1.xhtml
+load 344749-1.svg
+load 344887-1.svg
+load 344892-1.svg
+load 344898-1.svg
+load 344904-1.svg
+load 345418-1.svg
+load 348982-1.xhtml
+load 354777-1.xhtml
+load 359516-1.svg
+load 361015-1.svg
+load 361587-1.svg
+load 363611-1.xhtml
+load 364688-1.svg
+load 366956-1.svg
+load 366956-2.svg
+load 367111-1.svg
+load 367368-1.xhtml
+load 369233-1.svg
+load 369438-1.svg
+load 369438-2.svg
+load 371463-1.xhtml
+load 371563-1.xhtml
+load 375775-1.svg
+load 378716.svg
+load 380691-1.svg
+load 384391-1.xhtml
+load 384499-1.svg
+load 384637-1.svg
+load 384728-1.svg
+load 385246-1.svg
+load 385246-2.svg
+load 385552-1.svg
+load 385552-2.svg
+load 385840-1.svg
+load 385852-1.svg
+load 386475-1.xhtml
+load 386566-1.svg
+load 386690-1.svg
+load 387290-1.svg
+load 402408-1.svg
+load 404677-1.xhtml
+load 409565-1.xhtml
+load 409573-1.svg
+load 420697-1.svg
+load 420697-2.svg
+load 429774-1.svg
+load 441368-1.svg
+load 453754-1.svg
+load 455314-1.xhtml
+load 458453.html
+load 459666-1.html
+load 459883.xhtml
+load 461289-1.svg
+load 464374-1.svg
+load 466585-1.svg
+load 467323-1.svg
+load 467498-1.svg
+load 470124-1.svg
+load 472782-1.svg
+load 474700-1.svg
+load 475181-1.svg
+load 475193-1.html
+load 475302-1.svg
+load 477935-1.html
+load 478128-1.svg
+load 478511-1.svg
+load 483439-1.svg
+load 492186-1.svg
+load 508247-1.svg
+load 512890-1.svg
+load 515288-1.html
+load 522394-1.svg
+load 522394-2.svg
+load 522394-3.svg
+load 566216-1.svg
+load 587336-1.html
+load 590291-1.svg
+load 601999-1.html
+load 605626-1.svg
+load 610594-1.html
+load 610954-1.html
+load 612662-1.svg
+load 612662-2.svg
+load 612736-1.svg
+load 612736-2.svg
+load 614367-1.svg
+load 620034-1.html
+load 621598-1.svg
+load 648819-1.html
+load 655025-1.svg
+load 655025-2.svg
+load 655025-3.svg
+load 657077-1.svg
+load 669025-1.svg
+load 669025-2.svg
+load 682411-1.svg
+load 692203-1.svg
+load 692203-2.svg
+load 693424-1.svg
+load 709920-1.svg
+load 709920-2.svg
+load 713413-1.svg
+load 722003-1.svg
+load 725918-1.svg
+load 732836-1.svg
+load 740627-1.svg
+load 740627-2.svg
+load 757704-1.svg
+load 757718-1.svg
+load 757751-1.svg
+load 767056-1.svg
+load 767535-1.xhtml
+load 768087-1.html
+load 768351.svg
+load 778492-1.svg
+load 779971-1.svg
+load 780764-1.svg
+load 780963-1.html
+load 782141-1.svg
+load 784061-1.svg
+load 788831-1.svg
+load 789390-1.html
+load 790072.svg
+load 791826-1.svg
+load 808318-1.svg
+load 803562-1.svg
+load 813420-1.svg
+load 841163-1.svg
+load 841812-1.svg
+load 842009-1.svg
+load 842630-1.svg
+load 842909-1.svg
+load 843072-1.svg
+load 843917-1.svg
+load 847139-1.svg
+load 849688-1.svg
+load 849688-2.svg
+load 860378-1.svg
+load 868904-1.svg
+load 873806-1.svg
+load 876831-1.svg
+load 877029-1.svg
+load 880925-1.svg
+load 881031-1.svg
+load 885608-1.svg
+load 890782-1.svg
+load 890783-1.svg
+load 893510-1.svg
+load 895311-1.svg
+load 897342-1.svg
+load 898909-1.svg
+load 898951-1.svg
+load 913990.html
+load 919371-1.xhtml
+load 950324-1.svg
+load 952270-1.svg
+load 963086-1.svg
+load 974746-1.svg
+load 975773-1.svg
+load 979407-1.svg
+load 979407-2.svg
+load 993443.svg
+load 1016145.svg
+load 1028512.svg
+load 1140080-1.svg
+load 1149542-1.svg
+load 1156581-1.svg
+load 1182496-1.html
+load 1209525-1.svg
+load 1223281-1.svg
+load extref-test-1.xhtml
diff --git a/layout/svg/crashtests/extref-test-1-resource.xhtml b/layout/svg/crashtests/extref-test-1-resource.xhtml
new file mode 100644
index 0000000000..cd47ddc2f5
--- /dev/null
+++ b/layout/svg/crashtests/extref-test-1-resource.xhtml
@@ -0,0 +1,24 @@
+<!--
+ Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/licenses/publicdomain/
+-->
+<html xmlns="http://www.w3.org/1999/xhtml"
+ xmlns:svg="http://www.w3.org/2000/svg"
+ xmlns:xlink="http://www.w3.org/1999/xlink">
+<body style="margin:0">
+ <embed type="application/x-shockwave-flash" src="data:application/x-shockwave-flash,This is a test"></embed>
+ <iframe src="date:text/plain,aaa"></iframe>
+ <div style="mask: url(#m1); width:500px; height:500px; background:lime;"></div>
+
+ <svg:svg height="0">
+ <svg:mask id="m1" maskUnits="objectBoundingBox" maskContentUnits="objectBoundingBox">
+ <svg:linearGradient id="g" gradientUnits="objectBoundingBox" x2="0" y2="1">
+ <svg:stop stop-color="white" offset="0"/>
+ <svg:stop stop-color="white" stop-opacity="0" offset="1"/>
+ </svg:linearGradient>
+ <svg:circle cx="0.25" cy="0.25" r="0.25" id="circle" fill="white"/>
+ <svg:rect x="0.5" y="0" width="0.5" height="1" fill="url(#g)"/>
+ </svg:mask>
+ </svg:svg>
+</body>
+</html>
diff --git a/layout/svg/crashtests/extref-test-1.xhtml b/layout/svg/crashtests/extref-test-1.xhtml
new file mode 100644
index 0000000000..932b679b1f
--- /dev/null
+++ b/layout/svg/crashtests/extref-test-1.xhtml
@@ -0,0 +1,11 @@
+<!--
+ Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/licenses/publicdomain/
+-->
+<html xmlns="http://www.w3.org/1999/xhtml"
+ xmlns:svg="http://www.w3.org/2000/svg"
+ xmlns:xlink="http://www.w3.org/1999/xlink">
+<body style="margin:0">
+ <div style="mask: url(extref-test-1-resource.xhtml#m1); width:500px; height:500px; background:lime;"></div>
+</body>
+</html>
diff --git a/layout/svg/moz.build b/layout/svg/moz.build
new file mode 100644
index 0000000000..b1481f4ef2
--- /dev/null
+++ b/layout/svg/moz.build
@@ -0,0 +1,76 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# vim: set filetype=python:
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+with Files('**'):
+ BUG_COMPONENT = ('Core', 'SVG')
+
+EXPORTS += [
+ 'nsFilterInstance.h',
+ 'nsSVGEffects.h',
+ 'nsSVGFilterInstance.h',
+ 'nsSVGForeignObjectFrame.h',
+ 'nsSVGIntegrationUtils.h',
+ 'nsSVGUtils.h',
+ 'SVGImageContext.h',
+]
+
+EXPORTS.mozilla += [
+ 'SVGContextPaint.h',
+]
+
+UNIFIED_SOURCES += [
+ 'nsCSSClipPathInstance.cpp',
+ 'nsCSSFilterInstance.cpp',
+ 'nsFilterInstance.cpp',
+ 'nsSVGAFrame.cpp',
+ 'nsSVGClipPathFrame.cpp',
+ 'nsSVGContainerFrame.cpp',
+ 'nsSVGEffects.cpp',
+ 'nsSVGFilterFrame.cpp',
+ 'nsSVGFilterInstance.cpp',
+ 'nsSVGForeignObjectFrame.cpp',
+ 'nsSVGGenericContainerFrame.cpp',
+ 'nsSVGGFrame.cpp',
+ 'nsSVGGradientFrame.cpp',
+ 'nsSVGImageFrame.cpp',
+ 'nsSVGInnerSVGFrame.cpp',
+ 'nsSVGIntegrationUtils.cpp',
+ 'nsSVGMarkerFrame.cpp',
+ 'nsSVGMaskFrame.cpp',
+ 'nsSVGOuterSVGFrame.cpp',
+ 'nsSVGPathGeometryFrame.cpp',
+ 'nsSVGPatternFrame.cpp',
+ 'nsSVGStopFrame.cpp',
+ 'nsSVGSwitchFrame.cpp',
+ 'nsSVGUseFrame.cpp',
+ 'nsSVGUtils.cpp',
+ 'SVGContextPaint.cpp',
+ 'SVGFEContainerFrame.cpp',
+ 'SVGFEImageFrame.cpp',
+ 'SVGFELeafFrame.cpp',
+ 'SVGFEUnstyledLeafFrame.cpp',
+ 'SVGTextFrame.cpp',
+ 'SVGViewFrame.cpp',
+]
+
+if CONFIG['CPU_ARCH'] == 'arm' and CONFIG['BUILD_ARM_NEON']:
+ SOURCES += ['nsSVGMaskFrameNEON.cpp']
+ SOURCES['nsSVGMaskFrameNEON.cpp'].flags += CONFIG['NEON_FLAGS']
+
+FINAL_LIBRARY = 'xul'
+LOCAL_INCLUDES += [
+ '../../widget',
+ '../base',
+ '../generic',
+ '../style',
+ '../xul',
+ '/dom/base',
+ '/dom/svg',
+]
+
+RESOURCE_FILES += [
+ 'svg.css',
+]
diff --git a/layout/svg/nsCSSClipPathInstance.cpp b/layout/svg/nsCSSClipPathInstance.cpp
new file mode 100644
index 0000000000..828b10eaca
--- /dev/null
+++ b/layout/svg/nsCSSClipPathInstance.cpp
@@ -0,0 +1,377 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+// Main header first:
+#include "nsCSSClipPathInstance.h"
+
+#include "gfx2DGlue.h"
+#include "gfxPlatform.h"
+#include "mozilla/gfx/2D.h"
+#include "mozilla/gfx/PathHelpers.h"
+#include "nsCSSRendering.h"
+#include "nsIFrame.h"
+#include "nsRenderingContext.h"
+#include "nsRuleNode.h"
+
+using namespace mozilla;
+using namespace mozilla::gfx;
+
+/* static*/ void
+nsCSSClipPathInstance::ApplyBasicShapeClip(gfxContext& aContext,
+ nsIFrame* aFrame)
+{
+ auto& clipPathStyle = aFrame->StyleSVGReset()->mClipPath;
+
+#ifdef DEBUG
+ StyleShapeSourceType type = clipPathStyle.GetType();
+ MOZ_ASSERT(type == StyleShapeSourceType::Shape ||
+ type == StyleShapeSourceType::Box,
+ "This function is used with basic-shape and geometry-box only.");
+#endif
+
+ nsCSSClipPathInstance instance(aFrame, clipPathStyle);
+
+ aContext.NewPath();
+ RefPtr<Path> path = instance.CreateClipPath(aContext.GetDrawTarget());
+ aContext.SetPath(path);
+ aContext.Clip();
+}
+
+/* static*/ bool
+nsCSSClipPathInstance::HitTestBasicShapeClip(nsIFrame* aFrame,
+ const gfxPoint& aPoint)
+{
+ auto& clipPathStyle = aFrame->StyleSVGReset()->mClipPath;
+ StyleShapeSourceType type = clipPathStyle.GetType();
+ MOZ_ASSERT(type != StyleShapeSourceType::None, "unexpected none value");
+ // In the future nsCSSClipPathInstance may handle <clipPath> references as
+ // well. For the time being return early.
+ if (type == StyleShapeSourceType::URL) {
+ return false;
+ }
+
+ nsCSSClipPathInstance instance(aFrame, clipPathStyle);
+
+ RefPtr<DrawTarget> drawTarget =
+ gfxPlatform::GetPlatform()->ScreenReferenceDrawTarget();
+ RefPtr<Path> path = instance.CreateClipPath(drawTarget);
+ float pixelRatio = float(nsPresContext::AppUnitsPerCSSPixel()) /
+ aFrame->PresContext()->AppUnitsPerDevPixel();
+ return path->ContainsPoint(ToPoint(aPoint) * pixelRatio, Matrix());
+}
+
+nsRect
+nsCSSClipPathInstance::ComputeSVGReferenceRect()
+{
+ MOZ_ASSERT(mTargetFrame->GetContent()->IsSVGElement());
+ nsRect r;
+
+ // For SVG elements without associated CSS layout box, the used value for
+ // content-box, padding-box, border-box and margin-box is fill-box.
+ switch (mClipPathStyle.GetReferenceBox()) {
+ case StyleClipPathGeometryBox::Stroke: {
+ // XXX Bug 1299876
+ // The size of srtoke-box is not correct if this graphic element has
+ // specific stroke-linejoin or stroke-linecap.
+ gfxRect bbox = nsSVGUtils::GetBBox(mTargetFrame,
+ nsSVGUtils::eBBoxIncludeFill | nsSVGUtils::eBBoxIncludeStroke);
+ r = nsLayoutUtils::RoundGfxRectToAppRect(bbox,
+ nsPresContext::AppUnitsPerCSSPixel());
+ break;
+ }
+ case StyleClipPathGeometryBox::View: {
+ nsIContent* content = mTargetFrame->GetContent();
+ nsSVGElement* element = static_cast<nsSVGElement*>(content);
+ SVGSVGElement* svgElement = element->GetCtx();
+ MOZ_ASSERT(svgElement);
+
+ if (svgElement && svgElement->HasViewBoxRect()) {
+ // If a ‘viewBox‘ attribute is specified for the SVG viewport creating
+ // element:
+ // 1. The reference box is positioned at the origin of the coordinate
+ // system established by the ‘viewBox‘ attribute.
+ // 2. The dimension of the reference box is set to the width and height
+ // values of the ‘viewBox‘ attribute.
+ nsSVGViewBox* viewBox = svgElement->GetViewBox();
+ const nsSVGViewBoxRect& value = viewBox->GetAnimValue();
+ r = nsRect(nsPresContext::CSSPixelsToAppUnits(value.x),
+ nsPresContext::CSSPixelsToAppUnits(value.y),
+ nsPresContext::CSSPixelsToAppUnits(value.width),
+ nsPresContext::CSSPixelsToAppUnits(value.height));
+ } else {
+ // No viewBox is specified, uses the nearest SVG viewport as reference
+ // box.
+ svgFloatSize viewportSize = svgElement->GetViewportSize();
+ r = nsRect(0, 0,
+ nsPresContext::CSSPixelsToAppUnits(viewportSize.width),
+ nsPresContext::CSSPixelsToAppUnits(viewportSize.height));
+ }
+
+ break;
+ }
+ case StyleClipPathGeometryBox::NoBox:
+ case StyleClipPathGeometryBox::Border:
+ case StyleClipPathGeometryBox::Content:
+ case StyleClipPathGeometryBox::Padding:
+ case StyleClipPathGeometryBox::Margin:
+ case StyleClipPathGeometryBox::Fill: {
+ gfxRect bbox = nsSVGUtils::GetBBox(mTargetFrame,
+ nsSVGUtils::eBBoxIncludeFill);
+ r = nsLayoutUtils::RoundGfxRectToAppRect(bbox,
+ nsPresContext::AppUnitsPerCSSPixel());
+ break;
+ }
+ default:{
+ MOZ_ASSERT_UNREACHABLE("unknown StyleClipPathGeometryBox type");
+ gfxRect bbox = nsSVGUtils::GetBBox(mTargetFrame,
+ nsSVGUtils::eBBoxIncludeFill);
+ r = nsLayoutUtils::RoundGfxRectToAppRect(bbox,
+ nsPresContext::AppUnitsPerCSSPixel());
+ break;
+ }
+ }
+
+ return r;
+}
+
+nsRect
+nsCSSClipPathInstance::ComputeHTMLReferenceRect()
+{
+ nsRect r;
+
+ // For elements with associated CSS layout box, the used value for fill-box,
+ // stroke-box and view-box is border-box.
+ switch (mClipPathStyle.GetReferenceBox()) {
+ case StyleClipPathGeometryBox::Content:
+ r = mTargetFrame->GetContentRectRelativeToSelf();
+ break;
+ case StyleClipPathGeometryBox::Padding:
+ r = mTargetFrame->GetPaddingRectRelativeToSelf();
+ break;
+ case StyleClipPathGeometryBox::Margin:
+ r = mTargetFrame->GetMarginRectRelativeToSelf();
+ break;
+ case StyleClipPathGeometryBox::NoBox:
+ case StyleClipPathGeometryBox::Border:
+ case StyleClipPathGeometryBox::Fill:
+ case StyleClipPathGeometryBox::Stroke:
+ case StyleClipPathGeometryBox::View:
+ r = mTargetFrame->GetRectRelativeToSelf();
+ break;
+ default:
+ MOZ_ASSERT_UNREACHABLE("unknown StyleClipPathGeometryBox type");
+ r = mTargetFrame->GetRectRelativeToSelf();
+ break;
+ }
+
+ return r;
+}
+
+already_AddRefed<Path>
+nsCSSClipPathInstance::CreateClipPath(DrawTarget* aDrawTarget)
+{
+ // We use ComputeSVGReferenceRect for all SVG elements, except <svg>
+ // element, which does have an associated CSS layout box. In this case we
+ // should still use ComputeHTMLReferenceRect for region computing.
+ nsRect r = mTargetFrame->IsFrameOfType(nsIFrame::eSVG) &&
+ (mTargetFrame->GetType() != nsGkAtoms::svgOuterSVGFrame)
+ ? ComputeSVGReferenceRect() : ComputeHTMLReferenceRect();
+
+ if (mClipPathStyle.GetType() != StyleShapeSourceType::Shape) {
+ // TODO Clip to border-radius/reference box if no shape
+ // was specified.
+ RefPtr<PathBuilder> builder = aDrawTarget->CreatePathBuilder();
+ return builder->Finish();
+ }
+
+ nscoord appUnitsPerDevPixel =
+ mTargetFrame->PresContext()->AppUnitsPerDevPixel();
+ r = ToAppUnits(r.ToNearestPixels(appUnitsPerDevPixel), appUnitsPerDevPixel);
+
+ StyleBasicShape* basicShape = mClipPathStyle.GetBasicShape();
+ switch (basicShape->GetShapeType()) {
+ case StyleBasicShapeType::Circle:
+ return CreateClipPathCircle(aDrawTarget, r);
+ case StyleBasicShapeType::Ellipse:
+ return CreateClipPathEllipse(aDrawTarget, r);
+ case StyleBasicShapeType::Polygon:
+ return CreateClipPathPolygon(aDrawTarget, r);
+ case StyleBasicShapeType::Inset:
+ return CreateClipPathInset(aDrawTarget, r);
+ break;
+ default:
+ MOZ_MAKE_COMPILER_ASSUME_IS_UNREACHABLE("Unexpected shape type");
+ }
+ // Return an empty Path:
+ RefPtr<PathBuilder> builder = aDrawTarget->CreatePathBuilder();
+ return builder->Finish();
+}
+
+static void
+EnumerationToLength(nscoord& aCoord, int32_t aType,
+ nscoord aCenter, nscoord aPosMin, nscoord aPosMax)
+{
+ nscoord dist1 = abs(aPosMin - aCenter);
+ nscoord dist2 = abs(aPosMax - aCenter);
+ switch (aType) {
+ case NS_RADIUS_FARTHEST_SIDE:
+ aCoord = dist1 > dist2 ? dist1 : dist2;
+ break;
+ case NS_RADIUS_CLOSEST_SIDE:
+ aCoord = dist1 > dist2 ? dist2 : dist1;
+ break;
+ default:
+ NS_NOTREACHED("unknown keyword");
+ break;
+ }
+}
+
+already_AddRefed<Path>
+nsCSSClipPathInstance::CreateClipPathCircle(DrawTarget* aDrawTarget,
+ const nsRect& aRefBox)
+{
+ StyleBasicShape* basicShape = mClipPathStyle.GetBasicShape();
+
+ RefPtr<PathBuilder> builder = aDrawTarget->CreatePathBuilder();
+
+ nsPoint topLeft, anchor;
+ nsSize size = nsSize(aRefBox.width, aRefBox.height);
+ nsImageRenderer::ComputeObjectAnchorPoint(basicShape->GetPosition(),
+ size, size,
+ &topLeft, &anchor);
+ Point center = Point(anchor.x + aRefBox.x, anchor.y + aRefBox.y);
+
+ const nsTArray<nsStyleCoord>& coords = basicShape->Coordinates();
+ MOZ_ASSERT(coords.Length() == 1, "wrong number of arguments");
+ float referenceLength = sqrt((aRefBox.width * aRefBox.width +
+ aRefBox.height * aRefBox.height) / 2.0);
+ nscoord r = 0;
+ if (coords[0].GetUnit() == eStyleUnit_Enumerated) {
+ nscoord horizontal, vertical;
+ EnumerationToLength(horizontal, coords[0].GetIntValue(),
+ center.x, aRefBox.x, aRefBox.x + aRefBox.width);
+ EnumerationToLength(vertical, coords[0].GetIntValue(),
+ center.y, aRefBox.y, aRefBox.y + aRefBox.height);
+ if (coords[0].GetIntValue() == NS_RADIUS_FARTHEST_SIDE) {
+ r = horizontal > vertical ? horizontal : vertical;
+ } else {
+ r = horizontal < vertical ? horizontal : vertical;
+ }
+ } else {
+ r = nsRuleNode::ComputeCoordPercentCalc(coords[0], referenceLength);
+ }
+
+ nscoord appUnitsPerDevPixel =
+ mTargetFrame->PresContext()->AppUnitsPerDevPixel();
+ builder->Arc(center / appUnitsPerDevPixel, r / appUnitsPerDevPixel,
+ 0, Float(2 * M_PI));
+ builder->Close();
+ return builder->Finish();
+}
+
+already_AddRefed<Path>
+nsCSSClipPathInstance::CreateClipPathEllipse(DrawTarget* aDrawTarget,
+ const nsRect& aRefBox)
+{
+ StyleBasicShape* basicShape = mClipPathStyle.GetBasicShape();
+
+ RefPtr<PathBuilder> builder = aDrawTarget->CreatePathBuilder();
+
+ nsPoint topLeft, anchor;
+ nsSize size = nsSize(aRefBox.width, aRefBox.height);
+ nsImageRenderer::ComputeObjectAnchorPoint(basicShape->GetPosition(),
+ size, size,
+ &topLeft, &anchor);
+ Point center = Point(anchor.x + aRefBox.x, anchor.y + aRefBox.y);
+
+ const nsTArray<nsStyleCoord>& coords = basicShape->Coordinates();
+ MOZ_ASSERT(coords.Length() == 2, "wrong number of arguments");
+ nscoord rx = 0, ry = 0;
+ if (coords[0].GetUnit() == eStyleUnit_Enumerated) {
+ EnumerationToLength(rx, coords[0].GetIntValue(),
+ center.x, aRefBox.x, aRefBox.x + aRefBox.width);
+ } else {
+ rx = nsRuleNode::ComputeCoordPercentCalc(coords[0], aRefBox.width);
+ }
+ if (coords[1].GetUnit() == eStyleUnit_Enumerated) {
+ EnumerationToLength(ry, coords[1].GetIntValue(),
+ center.y, aRefBox.y, aRefBox.y + aRefBox.height);
+ } else {
+ ry = nsRuleNode::ComputeCoordPercentCalc(coords[1], aRefBox.height);
+ }
+
+ nscoord appUnitsPerDevPixel =
+ mTargetFrame->PresContext()->AppUnitsPerDevPixel();
+ EllipseToBezier(builder.get(),
+ center / appUnitsPerDevPixel,
+ Size(rx, ry) / appUnitsPerDevPixel);
+ builder->Close();
+ return builder->Finish();
+}
+
+already_AddRefed<Path>
+nsCSSClipPathInstance::CreateClipPathPolygon(DrawTarget* aDrawTarget,
+ const nsRect& aRefBox)
+{
+ StyleBasicShape* basicShape = mClipPathStyle.GetBasicShape();
+ const nsTArray<nsStyleCoord>& coords = basicShape->Coordinates();
+ MOZ_ASSERT(coords.Length() % 2 == 0 &&
+ coords.Length() >= 2, "wrong number of arguments");
+
+ FillRule fillRule = basicShape->GetFillRule() == StyleFillRule::Nonzero ?
+ FillRule::FILL_WINDING : FillRule::FILL_EVEN_ODD;
+ RefPtr<PathBuilder> builder = aDrawTarget->CreatePathBuilder(fillRule);
+
+ nscoord x = nsRuleNode::ComputeCoordPercentCalc(coords[0], aRefBox.width);
+ nscoord y = nsRuleNode::ComputeCoordPercentCalc(coords[1], aRefBox.height);
+ nscoord appUnitsPerDevPixel =
+ mTargetFrame->PresContext()->AppUnitsPerDevPixel();
+ builder->MoveTo(Point(aRefBox.x + x, aRefBox.y + y) / appUnitsPerDevPixel);
+ for (size_t i = 2; i < coords.Length(); i += 2) {
+ x = nsRuleNode::ComputeCoordPercentCalc(coords[i], aRefBox.width);
+ y = nsRuleNode::ComputeCoordPercentCalc(coords[i + 1], aRefBox.height);
+ builder->LineTo(Point(aRefBox.x + x, aRefBox.y + y) / appUnitsPerDevPixel);
+ }
+ builder->Close();
+ return builder->Finish();
+}
+
+already_AddRefed<Path>
+nsCSSClipPathInstance::CreateClipPathInset(DrawTarget* aDrawTarget,
+ const nsRect& aRefBox)
+{
+ StyleBasicShape* basicShape = mClipPathStyle.GetBasicShape();
+ const nsTArray<nsStyleCoord>& coords = basicShape->Coordinates();
+ MOZ_ASSERT(coords.Length() == 4, "wrong number of arguments");
+
+ RefPtr<PathBuilder> builder = aDrawTarget->CreatePathBuilder();
+
+ nscoord appUnitsPerDevPixel =
+ mTargetFrame->PresContext()->AppUnitsPerDevPixel();
+
+ nsMargin inset(nsRuleNode::ComputeCoordPercentCalc(coords[0], aRefBox.height),
+ nsRuleNode::ComputeCoordPercentCalc(coords[1], aRefBox.width),
+ nsRuleNode::ComputeCoordPercentCalc(coords[2], aRefBox.height),
+ nsRuleNode::ComputeCoordPercentCalc(coords[3], aRefBox.width));
+
+ nsRect insetRect(aRefBox);
+ insetRect.Deflate(inset);
+ const Rect insetRectPixels = NSRectToRect(insetRect, appUnitsPerDevPixel);
+ const nsStyleCorners& radius = basicShape->GetRadius();
+
+ nscoord appUnitsRadii[8];
+
+ if (nsIFrame::ComputeBorderRadii(radius, insetRect.Size(), aRefBox.Size(),
+ Sides(), appUnitsRadii)) {
+ RectCornerRadii corners;
+ nsCSSRendering::ComputePixelRadii(appUnitsRadii,
+ appUnitsPerDevPixel, &corners);
+
+ AppendRoundedRectToPath(builder, insetRectPixels, corners, true);
+ } else {
+ AppendRectToPath(builder, insetRectPixels, true);
+ }
+ return builder->Finish();
+}
diff --git a/layout/svg/nsCSSClipPathInstance.h b/layout/svg/nsCSSClipPathInstance.h
new file mode 100644
index 0000000000..3b0724dbd0
--- /dev/null
+++ b/layout/svg/nsCSSClipPathInstance.h
@@ -0,0 +1,64 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef __NS_CSSCLIPPATHINSTANCE_H__
+#define __NS_CSSCLIPPATHINSTANCE_H__
+
+#include "nsStyleStruct.h"
+#include "nsRect.h"
+#include "mozilla/gfx/2D.h"
+
+class nsIFrame;
+class nsRenderingContext;
+
+namespace mozilla {
+
+class nsCSSClipPathInstance
+{
+ typedef mozilla::gfx::DrawTarget DrawTarget;
+ typedef mozilla::gfx::Path Path;
+
+public:
+ static void ApplyBasicShapeClip(gfxContext& aContext,
+ nsIFrame* aFrame);
+ // aPoint is in CSS pixels.
+ static bool HitTestBasicShapeClip(nsIFrame* aFrame,
+ const gfxPoint& aPoint);
+private:
+ explicit nsCSSClipPathInstance(nsIFrame* aFrame,
+ const StyleClipPath aClipPathStyle)
+ : mTargetFrame(aFrame)
+ , mClipPathStyle(aClipPathStyle)
+ {
+ }
+
+ already_AddRefed<Path> CreateClipPath(DrawTarget* aDrawTarget);
+
+ already_AddRefed<Path> CreateClipPathCircle(DrawTarget* aDrawTarget,
+ const nsRect& aRefBox);
+
+ already_AddRefed<Path> CreateClipPathEllipse(DrawTarget* aDrawTarget,
+ const nsRect& aRefBox);
+
+ already_AddRefed<Path> CreateClipPathPolygon(DrawTarget* aDrawTarget,
+ const nsRect& aRefBox);
+
+ already_AddRefed<Path> CreateClipPathInset(DrawTarget* aDrawTarget,
+ const nsRect& aRefBox);
+
+
+ nsRect ComputeHTMLReferenceRect();
+ nsRect ComputeSVGReferenceRect();
+
+ /**
+ * The frame for the element that is currently being clipped.
+ */
+ nsIFrame* mTargetFrame;
+ StyleClipPath mClipPathStyle;
+};
+
+} // namespace mozilla
+
+#endif
diff --git a/layout/svg/nsCSSFilterInstance.cpp b/layout/svg/nsCSSFilterInstance.cpp
new file mode 100644
index 0000000000..79bf83b949
--- /dev/null
+++ b/layout/svg/nsCSSFilterInstance.cpp
@@ -0,0 +1,421 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+// Main header first:
+#include "nsCSSFilterInstance.h"
+
+// Keep others in (case-insensitive) order:
+#include "gfx2DGlue.h"
+#include "gfxUtils.h"
+#include "nsIFrame.h"
+#include "nsStyleStruct.h"
+#include "nsTArray.h"
+
+using namespace mozilla;
+using namespace mozilla::gfx;
+
+static float ClampFactor(float aFactor)
+{
+ if (aFactor > 1) {
+ return 1;
+ } else if (aFactor < 0) {
+ NS_NOTREACHED("A negative value should not have been parsed.");
+ return 0;
+ }
+
+ return aFactor;
+}
+
+nsCSSFilterInstance::nsCSSFilterInstance(const nsStyleFilter& aFilter,
+ nscolor aShadowFallbackColor,
+ const nsIntRect& aTargetBoundsInFilterSpace,
+ const gfxMatrix& aFrameSpaceInCSSPxToFilterSpaceTransform)
+ : mFilter(aFilter)
+ , mShadowFallbackColor(aShadowFallbackColor)
+ , mTargetBoundsInFilterSpace(aTargetBoundsInFilterSpace)
+ , mFrameSpaceInCSSPxToFilterSpaceTransform(aFrameSpaceInCSSPxToFilterSpaceTransform)
+{
+}
+
+nsresult
+nsCSSFilterInstance::BuildPrimitives(nsTArray<FilterPrimitiveDescription>& aPrimitiveDescrs,
+ bool aInputIsTainted)
+{
+ FilterPrimitiveDescription descr;
+ nsresult result;
+
+ switch(mFilter.GetType()) {
+ case NS_STYLE_FILTER_BLUR:
+ descr = CreatePrimitiveDescription(PrimitiveType::GaussianBlur,
+ aPrimitiveDescrs,
+ aInputIsTainted);
+ result = SetAttributesForBlur(descr);
+ break;
+ case NS_STYLE_FILTER_BRIGHTNESS:
+ descr = CreatePrimitiveDescription(PrimitiveType::ComponentTransfer,
+ aPrimitiveDescrs,
+ aInputIsTainted);
+ result = SetAttributesForBrightness(descr);
+ break;
+ case NS_STYLE_FILTER_CONTRAST:
+ descr = CreatePrimitiveDescription(PrimitiveType::ComponentTransfer,
+ aPrimitiveDescrs,
+ aInputIsTainted);
+ result = SetAttributesForContrast(descr);
+ break;
+ case NS_STYLE_FILTER_DROP_SHADOW:
+ descr = CreatePrimitiveDescription(PrimitiveType::DropShadow,
+ aPrimitiveDescrs,
+ aInputIsTainted);
+ result = SetAttributesForDropShadow(descr);
+ break;
+ case NS_STYLE_FILTER_GRAYSCALE:
+ descr = CreatePrimitiveDescription(PrimitiveType::ColorMatrix,
+ aPrimitiveDescrs,
+ aInputIsTainted);
+ result = SetAttributesForGrayscale(descr);
+ break;
+ case NS_STYLE_FILTER_HUE_ROTATE:
+ descr = CreatePrimitiveDescription(PrimitiveType::ColorMatrix,
+ aPrimitiveDescrs,
+ aInputIsTainted);
+ result = SetAttributesForHueRotate(descr);
+ break;
+ case NS_STYLE_FILTER_INVERT:
+ descr = CreatePrimitiveDescription(PrimitiveType::ComponentTransfer,
+ aPrimitiveDescrs,
+ aInputIsTainted);
+ result = SetAttributesForInvert(descr);
+ break;
+ case NS_STYLE_FILTER_OPACITY:
+ descr = CreatePrimitiveDescription(PrimitiveType::ComponentTransfer,
+ aPrimitiveDescrs,
+ aInputIsTainted);
+ result = SetAttributesForOpacity(descr);
+ break;
+ case NS_STYLE_FILTER_SATURATE:
+ descr = CreatePrimitiveDescription(PrimitiveType::ColorMatrix,
+ aPrimitiveDescrs,
+ aInputIsTainted);
+ result = SetAttributesForSaturate(descr);
+ break;
+ case NS_STYLE_FILTER_SEPIA:
+ descr = CreatePrimitiveDescription(PrimitiveType::ColorMatrix,
+ aPrimitiveDescrs,
+ aInputIsTainted);
+ result = SetAttributesForSepia(descr);
+ break;
+ default:
+ NS_NOTREACHED("not a valid CSS filter type");
+ return NS_ERROR_FAILURE;
+ }
+
+ if (NS_FAILED(result)) {
+ return result;
+ }
+
+ // Compute the primitive's bounds now that we've determined its attributes.
+ // Some attributes like blur radius can influence the bounds.
+ SetBounds(descr, aPrimitiveDescrs);
+
+ // Add this primitive to the filter chain.
+ aPrimitiveDescrs.AppendElement(descr);
+ return NS_OK;
+}
+
+FilterPrimitiveDescription
+nsCSSFilterInstance::CreatePrimitiveDescription(PrimitiveType aType,
+ const nsTArray<FilterPrimitiveDescription>& aPrimitiveDescrs,
+ bool aInputIsTainted) {
+ FilterPrimitiveDescription descr(aType);
+ int32_t inputIndex = GetLastResultIndex(aPrimitiveDescrs);
+ descr.SetInputPrimitive(0, inputIndex);
+ descr.SetIsTainted(inputIndex < 0 ? aInputIsTainted : aPrimitiveDescrs[inputIndex].IsTainted());
+ descr.SetInputColorSpace(0, ColorSpace::SRGB);
+ descr.SetOutputColorSpace(ColorSpace::SRGB);
+ return descr;
+}
+
+nsresult
+nsCSSFilterInstance::SetAttributesForBlur(FilterPrimitiveDescription& aDescr)
+{
+ const nsStyleCoord& radiusInFrameSpace = mFilter.GetFilterParameter();
+ if (radiusInFrameSpace.GetUnit() != eStyleUnit_Coord) {
+ NS_NOTREACHED("unexpected unit");
+ return NS_ERROR_FAILURE;
+ }
+
+ Size radiusInFilterSpace = BlurRadiusToFilterSpace(radiusInFrameSpace.GetCoordValue());
+ aDescr.Attributes().Set(eGaussianBlurStdDeviation, radiusInFilterSpace);
+ return NS_OK;
+}
+
+nsresult
+nsCSSFilterInstance::SetAttributesForBrightness(FilterPrimitiveDescription& aDescr)
+{
+ const nsStyleCoord& styleValue = mFilter.GetFilterParameter();
+ float value = styleValue.GetFactorOrPercentValue();
+
+ // Set transfer functions for RGB.
+ AttributeMap brightnessAttrs;
+ brightnessAttrs.Set(eComponentTransferFunctionType,
+ (uint32_t)SVG_FECOMPONENTTRANSFER_TYPE_LINEAR);
+ brightnessAttrs.Set(eComponentTransferFunctionSlope, value);
+ brightnessAttrs.Set(eComponentTransferFunctionIntercept, 0.0f);
+ aDescr.Attributes().Set(eComponentTransferFunctionR, brightnessAttrs);
+ aDescr.Attributes().Set(eComponentTransferFunctionG, brightnessAttrs);
+ aDescr.Attributes().Set(eComponentTransferFunctionB, brightnessAttrs);
+
+ // Set identity transfer function for A.
+ AttributeMap identityAttrs;
+ identityAttrs.Set(eComponentTransferFunctionType,
+ (uint32_t)SVG_FECOMPONENTTRANSFER_TYPE_IDENTITY);
+ aDescr.Attributes().Set(eComponentTransferFunctionA, identityAttrs);
+
+ return NS_OK;
+}
+
+nsresult
+nsCSSFilterInstance::SetAttributesForContrast(FilterPrimitiveDescription& aDescr)
+{
+ const nsStyleCoord& styleValue = mFilter.GetFilterParameter();
+ float value = styleValue.GetFactorOrPercentValue();
+ float intercept = -(0.5 * value) + 0.5;
+
+ // Set transfer functions for RGB.
+ AttributeMap contrastAttrs;
+ contrastAttrs.Set(eComponentTransferFunctionType,
+ (uint32_t)SVG_FECOMPONENTTRANSFER_TYPE_LINEAR);
+ contrastAttrs.Set(eComponentTransferFunctionSlope, value);
+ contrastAttrs.Set(eComponentTransferFunctionIntercept, intercept);
+ aDescr.Attributes().Set(eComponentTransferFunctionR, contrastAttrs);
+ aDescr.Attributes().Set(eComponentTransferFunctionG, contrastAttrs);
+ aDescr.Attributes().Set(eComponentTransferFunctionB, contrastAttrs);
+
+ // Set identity transfer function for A.
+ AttributeMap identityAttrs;
+ identityAttrs.Set(eComponentTransferFunctionType,
+ (uint32_t)SVG_FECOMPONENTTRANSFER_TYPE_IDENTITY);
+ aDescr.Attributes().Set(eComponentTransferFunctionA, identityAttrs);
+
+ return NS_OK;
+}
+
+nsresult
+nsCSSFilterInstance::SetAttributesForDropShadow(FilterPrimitiveDescription& aDescr)
+{
+ nsCSSShadowArray* shadows = mFilter.GetDropShadow();
+ if (!shadows || shadows->Length() != 1) {
+ NS_NOTREACHED("Exactly one drop shadow should have been parsed.");
+ return NS_ERROR_FAILURE;
+ }
+
+ nsCSSShadowItem* shadow = shadows->ShadowAt(0);
+
+ // Set drop shadow blur radius.
+ Size radiusInFilterSpace = BlurRadiusToFilterSpace(shadow->mRadius);
+ aDescr.Attributes().Set(eDropShadowStdDeviation, radiusInFilterSpace);
+
+ // Set offset.
+ IntPoint offsetInFilterSpace = OffsetToFilterSpace(shadow->mXOffset, shadow->mYOffset);
+ aDescr.Attributes().Set(eDropShadowOffset, offsetInFilterSpace);
+
+ // Set color. If unspecified, use the CSS color property.
+ nscolor shadowColor = shadow->mHasColor ? shadow->mColor : mShadowFallbackColor;
+ aDescr.Attributes().Set(eDropShadowColor, ToAttributeColor(shadowColor));
+
+ return NS_OK;
+}
+
+nsresult
+nsCSSFilterInstance::SetAttributesForGrayscale(FilterPrimitiveDescription& aDescr)
+{
+ // Set color matrix type.
+ aDescr.Attributes().Set(eColorMatrixType, (uint32_t)SVG_FECOLORMATRIX_TYPE_SATURATE);
+
+ // Set color matrix values.
+ const nsStyleCoord& styleValue = mFilter.GetFilterParameter();
+ float value = 1 - ClampFactor(styleValue.GetFactorOrPercentValue());
+ aDescr.Attributes().Set(eColorMatrixValues, &value, 1);
+
+ return NS_OK;
+}
+
+nsresult
+nsCSSFilterInstance::SetAttributesForHueRotate(FilterPrimitiveDescription& aDescr)
+{
+ // Set color matrix type.
+ aDescr.Attributes().Set(eColorMatrixType, (uint32_t)SVG_FECOLORMATRIX_TYPE_HUE_ROTATE);
+
+ // Set color matrix values.
+ const nsStyleCoord& styleValue = mFilter.GetFilterParameter();
+ float value = styleValue.GetAngleValueInDegrees();
+ aDescr.Attributes().Set(eColorMatrixValues, &value, 1);
+
+ return NS_OK;
+}
+
+nsresult
+nsCSSFilterInstance::SetAttributesForInvert(FilterPrimitiveDescription& aDescr)
+{
+ const nsStyleCoord& styleValue = mFilter.GetFilterParameter();
+ float value = ClampFactor(styleValue.GetFactorOrPercentValue());
+
+ // Set transfer functions for RGB.
+ AttributeMap invertAttrs;
+ float invertTableValues[2];
+ invertTableValues[0] = value;
+ invertTableValues[1] = 1 - value;
+ invertAttrs.Set(eComponentTransferFunctionType,
+ (uint32_t)SVG_FECOMPONENTTRANSFER_TYPE_TABLE);
+ invertAttrs.Set(eComponentTransferFunctionTableValues, invertTableValues, 2);
+ aDescr.Attributes().Set(eComponentTransferFunctionR, invertAttrs);
+ aDescr.Attributes().Set(eComponentTransferFunctionG, invertAttrs);
+ aDescr.Attributes().Set(eComponentTransferFunctionB, invertAttrs);
+
+ // Set identity transfer function for A.
+ AttributeMap identityAttrs;
+ identityAttrs.Set(eComponentTransferFunctionType,
+ (uint32_t)SVG_FECOMPONENTTRANSFER_TYPE_IDENTITY);
+ aDescr.Attributes().Set(eComponentTransferFunctionA, identityAttrs);
+
+ return NS_OK;
+}
+
+nsresult
+nsCSSFilterInstance::SetAttributesForOpacity(FilterPrimitiveDescription& aDescr)
+{
+ const nsStyleCoord& styleValue = mFilter.GetFilterParameter();
+ float value = ClampFactor(styleValue.GetFactorOrPercentValue());
+
+ // Set identity transfer functions for RGB.
+ AttributeMap identityAttrs;
+ identityAttrs.Set(eComponentTransferFunctionType,
+ (uint32_t)SVG_FECOMPONENTTRANSFER_TYPE_IDENTITY);
+ aDescr.Attributes().Set(eComponentTransferFunctionR, identityAttrs);
+ aDescr.Attributes().Set(eComponentTransferFunctionG, identityAttrs);
+ aDescr.Attributes().Set(eComponentTransferFunctionB, identityAttrs);
+
+ // Set transfer function for A.
+ AttributeMap opacityAttrs;
+ float opacityTableValues[2];
+ opacityTableValues[0] = 0;
+ opacityTableValues[1] = value;
+ opacityAttrs.Set(eComponentTransferFunctionType,
+ (uint32_t)SVG_FECOMPONENTTRANSFER_TYPE_TABLE);
+ opacityAttrs.Set(eComponentTransferFunctionTableValues, opacityTableValues, 2);
+ aDescr.Attributes().Set(eComponentTransferFunctionA, opacityAttrs);
+
+ return NS_OK;
+}
+
+nsresult
+nsCSSFilterInstance::SetAttributesForSaturate(FilterPrimitiveDescription& aDescr)
+{
+ // Set color matrix type.
+ aDescr.Attributes().Set(eColorMatrixType, (uint32_t)SVG_FECOLORMATRIX_TYPE_SATURATE);
+
+ // Set color matrix values.
+ const nsStyleCoord& styleValue = mFilter.GetFilterParameter();
+ float value = styleValue.GetFactorOrPercentValue();
+ aDescr.Attributes().Set(eColorMatrixValues, &value, 1);
+
+ return NS_OK;
+}
+
+nsresult
+nsCSSFilterInstance::SetAttributesForSepia(FilterPrimitiveDescription& aDescr)
+{
+ // Set color matrix type.
+ aDescr.Attributes().Set(eColorMatrixType, (uint32_t)SVG_FECOLORMATRIX_TYPE_SEPIA);
+
+ // Set color matrix values.
+ const nsStyleCoord& styleValue = mFilter.GetFilterParameter();
+ float value = ClampFactor(styleValue.GetFactorOrPercentValue());
+ aDescr.Attributes().Set(eColorMatrixValues, &value, 1);
+
+ return NS_OK;
+}
+
+Size
+nsCSSFilterInstance::BlurRadiusToFilterSpace(nscoord aRadiusInFrameSpace)
+{
+ float radiusInFrameSpaceInCSSPx =
+ nsPresContext::AppUnitsToFloatCSSPixels(aRadiusInFrameSpace);
+
+ // Convert the radius to filter space.
+ Size radiusInFilterSpace(radiusInFrameSpaceInCSSPx,
+ radiusInFrameSpaceInCSSPx);
+ gfxSize frameSpaceInCSSPxToFilterSpaceScale =
+ mFrameSpaceInCSSPxToFilterSpaceTransform.ScaleFactors(true);
+ radiusInFilterSpace.Scale(frameSpaceInCSSPxToFilterSpaceScale.width,
+ frameSpaceInCSSPxToFilterSpaceScale.height);
+
+ // Check the radius limits.
+ if (radiusInFilterSpace.width < 0 || radiusInFilterSpace.height < 0) {
+ NS_NOTREACHED("we shouldn't have parsed a negative radius in the style");
+ return Size();
+ }
+ Float maxStdDeviation = (Float)kMaxStdDeviation;
+ radiusInFilterSpace.width = std::min(radiusInFilterSpace.width, maxStdDeviation);
+ radiusInFilterSpace.height = std::min(radiusInFilterSpace.height, maxStdDeviation);
+
+ return radiusInFilterSpace;
+}
+
+IntPoint
+nsCSSFilterInstance::OffsetToFilterSpace(nscoord aXOffsetInFrameSpace,
+ nscoord aYOffsetInFrameSpace)
+{
+ gfxPoint offsetInFilterSpace(nsPresContext::AppUnitsToFloatCSSPixels(aXOffsetInFrameSpace),
+ nsPresContext::AppUnitsToFloatCSSPixels(aYOffsetInFrameSpace));
+
+ // Convert the radius to filter space.
+ gfxSize frameSpaceInCSSPxToFilterSpaceScale =
+ mFrameSpaceInCSSPxToFilterSpaceTransform.ScaleFactors(true);
+ offsetInFilterSpace.x *= frameSpaceInCSSPxToFilterSpaceScale.width;
+ offsetInFilterSpace.y *= frameSpaceInCSSPxToFilterSpaceScale.height;
+
+ return IntPoint(int32_t(offsetInFilterSpace.x), int32_t(offsetInFilterSpace.y));
+}
+
+Color
+nsCSSFilterInstance::ToAttributeColor(nscolor aColor)
+{
+ return Color(
+ NS_GET_R(aColor) / 255.0,
+ NS_GET_G(aColor) / 255.0,
+ NS_GET_B(aColor) / 255.0,
+ NS_GET_A(aColor) / 255.0
+ );
+}
+
+int32_t
+nsCSSFilterInstance::GetLastResultIndex(const nsTArray<FilterPrimitiveDescription>& aPrimitiveDescrs)
+{
+ uint32_t numPrimitiveDescrs = aPrimitiveDescrs.Length();
+ return !numPrimitiveDescrs ?
+ FilterPrimitiveDescription::kPrimitiveIndexSourceGraphic :
+ numPrimitiveDescrs - 1;
+}
+
+void
+nsCSSFilterInstance::SetBounds(FilterPrimitiveDescription& aDescr,
+ const nsTArray<FilterPrimitiveDescription>& aPrimitiveDescrs)
+{
+ int32_t inputIndex = GetLastResultIndex(aPrimitiveDescrs);
+ nsIntRect inputBounds = (inputIndex < 0) ?
+ mTargetBoundsInFilterSpace : aPrimitiveDescrs[inputIndex].PrimitiveSubregion();
+
+ nsTArray<nsIntRegion> inputExtents;
+ inputExtents.AppendElement(inputBounds);
+
+ nsIntRegion outputExtents =
+ FilterSupport::PostFilterExtentsForPrimitive(aDescr, inputExtents);
+ IntRect outputBounds = outputExtents.GetBounds();
+
+ aDescr.SetPrimitiveSubregion(outputBounds);
+ aDescr.SetFilterSpaceBounds(outputBounds);
+}
diff --git a/layout/svg/nsCSSFilterInstance.h b/layout/svg/nsCSSFilterInstance.h
new file mode 100644
index 0000000000..63e427440c
--- /dev/null
+++ b/layout/svg/nsCSSFilterInstance.h
@@ -0,0 +1,144 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef __NS_CSSFILTERINSTANCE_H__
+#define __NS_CSSFILTERINSTANCE_H__
+
+#include "FilterSupport.h"
+#include "gfxMatrix.h"
+#include "gfxRect.h"
+#include "mozilla/gfx/Point.h"
+#include "mozilla/gfx/Types.h"
+#include "nsColor.h"
+#include "nsTArrayForwardDeclare.h"
+
+struct nsStyleFilter;
+
+/**
+ * This class helps nsFilterInstance build its filter graph. It turns a CSS
+ * filter function (e.g. blur(3px)) from the style system into a
+ * FilterPrimitiveDescription connected to the filter graph.
+ */
+class nsCSSFilterInstance
+{
+ typedef mozilla::gfx::Color Color;
+ typedef mozilla::gfx::FilterPrimitiveDescription FilterPrimitiveDescription;
+ typedef mozilla::gfx::IntPoint IntPoint;
+ typedef mozilla::gfx::PrimitiveType PrimitiveType;
+ typedef mozilla::gfx::Size Size;
+
+public:
+ /**
+ * @param aFilter The CSS filter from the style system. This class stores
+ * aFilter by reference, so callers should avoid modifying or deleting
+ * aFilter during the lifetime of nsCSSFilterInstance.
+ * @param aShadowFallbackColor The color that should be used for
+ * drop-shadow() filters that don't specify a shadow color.
+ * @param aTargetBoundsInFilterSpace The pre-filter visual overflow rect of
+ * the frame being filtered, in filter space.
+ * @param aFrameSpaceInCSSPxToFilterSpaceTransform The transformation from
+ * the filtered element's frame space in CSS pixels to filter space.
+ */
+ nsCSSFilterInstance(const nsStyleFilter& aFilter,
+ nscolor aShadowFallbackColor,
+ const nsIntRect& aTargetBoundsInFilterSpace,
+ const gfxMatrix& aFrameSpaceInCSSPxToFilterSpaceTransform);
+
+ /**
+ * Creates at least one new FilterPrimitiveDescription based on the filter
+ * from the style system. Appends the new FilterPrimitiveDescription(s) to the
+ * aPrimitiveDescrs list.
+ * aInputIsTainted describes whether the input to this filter is tainted, i.e.
+ * whether it contains security-sensitive content. This is needed to propagate
+ * taintedness to the FilterPrimitive that take tainted inputs. Something being
+ * tainted means that it contains security sensitive content.
+ * The input to this filter is the previous filter's output, i.e. the last
+ * element in aPrimitiveDescrs, or the SourceGraphic input if this is the first
+ * filter in the filter chain.
+ */
+ nsresult BuildPrimitives(nsTArray<FilterPrimitiveDescription>& aPrimitiveDescrs,
+ bool aInputIsTainted);
+
+private:
+ /**
+ * Returns a new FilterPrimitiveDescription with its basic properties set up.
+ * See the comment above BuildPrimitives for the meaning of aInputIsTainted.
+ */
+ FilterPrimitiveDescription CreatePrimitiveDescription(PrimitiveType aType,
+ const nsTArray<FilterPrimitiveDescription>& aPrimitiveDescrs,
+ bool aInputIsTainted);
+
+ /**
+ * Sets aDescr's attributes using the style info in mFilter.
+ */
+ nsresult SetAttributesForBlur(FilterPrimitiveDescription& aDescr);
+ nsresult SetAttributesForBrightness(FilterPrimitiveDescription& aDescr);
+ nsresult SetAttributesForContrast(FilterPrimitiveDescription& aDescr);
+ nsresult SetAttributesForDropShadow(FilterPrimitiveDescription& aDescr);
+ nsresult SetAttributesForGrayscale(FilterPrimitiveDescription& aDescr);
+ nsresult SetAttributesForHueRotate(FilterPrimitiveDescription& aDescr);
+ nsresult SetAttributesForInvert(FilterPrimitiveDescription& aDescr);
+ nsresult SetAttributesForOpacity(FilterPrimitiveDescription& aDescr);
+ nsresult SetAttributesForSaturate(FilterPrimitiveDescription& aDescr);
+ nsresult SetAttributesForSepia(FilterPrimitiveDescription& aDescr);
+
+ /**
+ * Returns the index of the last result in the aPrimitiveDescrs, which we'll
+ * use as the input to this CSS filter.
+ */
+ int32_t GetLastResultIndex(const nsTArray<FilterPrimitiveDescription>& aPrimitiveDescrs);
+
+ /**
+ * Sets aDescr's filter region and primitive subregion to appropriate values
+ * based on this CSS filter's input and its attributes. For example, a CSS
+ * blur filter will have bounds equal to its input bounds, inflated by the
+ * blur extents.
+ */
+ void SetBounds(FilterPrimitiveDescription& aDescr,
+ const nsTArray<FilterPrimitiveDescription>& aPrimitiveDescrs);
+
+ /**
+ * Converts an nscolor to a Color, suitable for use as a
+ * FilterPrimitiveDescription attribute.
+ */
+ Color ToAttributeColor(nscolor aColor);
+
+ /**
+ * Converts a blur radius in frame space to filter space.
+ */
+ Size BlurRadiusToFilterSpace(nscoord aRadiusInFrameSpace);
+
+ /**
+ * Converts a point defined by a pair of nscoord x, y coordinates from frame
+ * space to filter space.
+ */
+ IntPoint OffsetToFilterSpace(nscoord aXOffsetInFrameSpace,
+ nscoord aYOffsetInFrameSpace);
+
+ /**
+ * The CSS filter originally from the style system.
+ */
+ const nsStyleFilter& mFilter;
+
+ /**
+ * The color that should be used for drop-shadow() filters that don't
+ * specify a shadow color.
+ */
+ nscolor mShadowFallbackColor;
+
+ /**
+ * The pre-filter overflow rect of the frame being filtered, in filter space.
+ * Used for input bounds if this CSS filter is the first in the filter chain.
+ */
+ nsIntRect mTargetBoundsInFilterSpace;
+
+ /**
+ * The transformation from the filtered element's frame space in CSS pixels to
+ * filter space. Used to transform style values to filter space.
+ */
+ gfxMatrix mFrameSpaceInCSSPxToFilterSpaceTransform;
+};
+
+#endif
diff --git a/layout/svg/nsFilterInstance.cpp b/layout/svg/nsFilterInstance.cpp
new file mode 100644
index 0000000000..fe52b8a8f8
--- /dev/null
+++ b/layout/svg/nsFilterInstance.cpp
@@ -0,0 +1,627 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+// Main header first:
+#include "nsFilterInstance.h"
+
+// MFBT headers next:
+#include "mozilla/UniquePtr.h"
+
+// Keep others in (case-insensitive) order:
+#include "gfx2DGlue.h"
+#include "gfxContext.h"
+#include "gfxPlatform.h"
+#include "gfxUtils.h"
+#include "mozilla/gfx/Helpers.h"
+#include "mozilla/gfx/PatternHelpers.h"
+#include "nsISVGChildFrame.h"
+#include "nsCSSFilterInstance.h"
+#include "nsSVGFilterInstance.h"
+#include "nsSVGFilterPaintCallback.h"
+#include "nsSVGUtils.h"
+#include "SVGContentUtils.h"
+#include "FilterSupport.h"
+#include "gfx2DGlue.h"
+
+using namespace mozilla;
+using namespace mozilla::dom;
+using namespace mozilla::gfx;
+
+FilterDescription
+nsFilterInstance::GetFilterDescription(nsIContent* aFilteredElement,
+ const nsTArray<nsStyleFilter>& aFilterChain,
+ bool aFilterInputIsTainted,
+ const UserSpaceMetrics& aMetrics,
+ const gfxRect& aBBox,
+ nsTArray<RefPtr<SourceSurface>>& aOutAdditionalImages)
+{
+ gfxMatrix unused; // aPaintTransform arg not used since we're not painting
+ nsFilterInstance instance(nullptr, aFilteredElement, aMetrics,
+ aFilterChain, aFilterInputIsTainted, nullptr,
+ unused, nullptr, nullptr, nullptr, &aBBox);
+ if (!instance.IsInitialized()) {
+ return FilterDescription();
+ }
+ return instance.ExtractDescriptionAndAdditionalImages(aOutAdditionalImages);
+}
+
+static UniquePtr<UserSpaceMetrics>
+UserSpaceMetricsForFrame(nsIFrame* aFrame)
+{
+ if (aFrame->GetContent()->IsSVGElement()) {
+ nsSVGElement* element = static_cast<nsSVGElement*>(aFrame->GetContent());
+ return MakeUnique<SVGElementMetrics>(element);
+ }
+ return MakeUnique<NonSVGFrameUserSpaceMetrics>(aFrame);
+}
+
+nsresult
+nsFilterInstance::PaintFilteredFrame(nsIFrame *aFilteredFrame,
+ DrawTarget* aDrawTarget,
+ const gfxMatrix& aTransform,
+ nsSVGFilterPaintCallback *aPaintCallback,
+ const nsRegion *aDirtyArea)
+{
+ auto& filterChain = aFilteredFrame->StyleEffects()->mFilters;
+ UniquePtr<UserSpaceMetrics> metrics = UserSpaceMetricsForFrame(aFilteredFrame);
+ // Hardcode InputIsTainted to true because we don't want JS to be able to
+ // read the rendered contents of aFilteredFrame.
+ nsFilterInstance instance(aFilteredFrame, aFilteredFrame->GetContent(), *metrics,
+ filterChain, /* InputIsTainted */ true, aPaintCallback,
+ aTransform, aDirtyArea, nullptr, nullptr, nullptr);
+ if (!instance.IsInitialized()) {
+ return NS_OK;
+ }
+ return instance.Render(aDrawTarget);
+}
+
+nsRegion
+nsFilterInstance::GetPostFilterDirtyArea(nsIFrame *aFilteredFrame,
+ const nsRegion& aPreFilterDirtyRegion)
+{
+ if (aPreFilterDirtyRegion.IsEmpty()) {
+ return nsRegion();
+ }
+
+ gfxMatrix unused; // aPaintTransform arg not used since we're not painting
+ auto& filterChain = aFilteredFrame->StyleEffects()->mFilters;
+ UniquePtr<UserSpaceMetrics> metrics = UserSpaceMetricsForFrame(aFilteredFrame);
+ // Hardcode InputIsTainted to true because we don't want JS to be able to
+ // read the rendered contents of aFilteredFrame.
+ nsFilterInstance instance(aFilteredFrame, aFilteredFrame->GetContent(), *metrics,
+ filterChain, /* InputIsTainted */ true, nullptr, unused,
+ nullptr, &aPreFilterDirtyRegion);
+ if (!instance.IsInitialized()) {
+ return nsRegion();
+ }
+
+ // We've passed in the source's dirty area so the instance knows about it.
+ // Now we can ask the instance to compute the area of the filter output
+ // that's dirty.
+ return instance.ComputePostFilterDirtyRegion();
+}
+
+nsRegion
+nsFilterInstance::GetPreFilterNeededArea(nsIFrame *aFilteredFrame,
+ const nsRegion& aPostFilterDirtyRegion)
+{
+ gfxMatrix unused; // aPaintTransform arg not used since we're not painting
+ auto& filterChain = aFilteredFrame->StyleEffects()->mFilters;
+ UniquePtr<UserSpaceMetrics> metrics = UserSpaceMetricsForFrame(aFilteredFrame);
+ // Hardcode InputIsTainted to true because we don't want JS to be able to
+ // read the rendered contents of aFilteredFrame.
+ nsFilterInstance instance(aFilteredFrame, aFilteredFrame->GetContent(), *metrics,
+ filterChain, /* InputIsTainted */ true, nullptr, unused,
+ &aPostFilterDirtyRegion);
+ if (!instance.IsInitialized()) {
+ return nsRect();
+ }
+
+ // Now we can ask the instance to compute the area of the source
+ // that's needed.
+ return instance.ComputeSourceNeededRect();
+}
+
+nsRect
+nsFilterInstance::GetPostFilterBounds(nsIFrame *aFilteredFrame,
+ const gfxRect *aOverrideBBox,
+ const nsRect *aPreFilterBounds)
+{
+ MOZ_ASSERT(!(aFilteredFrame->GetStateBits() & NS_FRAME_SVG_LAYOUT) ||
+ !(aFilteredFrame->GetStateBits() & NS_FRAME_IS_NONDISPLAY),
+ "Non-display SVG do not maintain visual overflow rects");
+
+ nsRegion preFilterRegion;
+ nsRegion* preFilterRegionPtr = nullptr;
+ if (aPreFilterBounds) {
+ preFilterRegion = *aPreFilterBounds;
+ preFilterRegionPtr = &preFilterRegion;
+ }
+
+ gfxMatrix unused; // aPaintTransform arg not used since we're not painting
+ auto& filterChain = aFilteredFrame->StyleEffects()->mFilters;
+ UniquePtr<UserSpaceMetrics> metrics = UserSpaceMetricsForFrame(aFilteredFrame);
+ // Hardcode InputIsTainted to true because we don't want JS to be able to
+ // read the rendered contents of aFilteredFrame.
+ nsFilterInstance instance(aFilteredFrame, aFilteredFrame->GetContent(), *metrics,
+ filterChain, /* InputIsTainted */ true, nullptr, unused,
+ nullptr, preFilterRegionPtr, aPreFilterBounds,
+ aOverrideBBox);
+ if (!instance.IsInitialized()) {
+ return nsRect();
+ }
+
+ return instance.ComputePostFilterExtents();
+}
+
+nsFilterInstance::nsFilterInstance(nsIFrame *aTargetFrame,
+ nsIContent* aTargetContent,
+ const UserSpaceMetrics& aMetrics,
+ const nsTArray<nsStyleFilter>& aFilterChain,
+ bool aFilterInputIsTainted,
+ nsSVGFilterPaintCallback *aPaintCallback,
+ const gfxMatrix& aPaintTransform,
+ const nsRegion *aPostFilterDirtyRegion,
+ const nsRegion *aPreFilterDirtyRegion,
+ const nsRect *aPreFilterVisualOverflowRectOverride,
+ const gfxRect *aOverrideBBox)
+ : mTargetFrame(aTargetFrame)
+ , mTargetContent(aTargetContent)
+ , mMetrics(aMetrics)
+ , mPaintCallback(aPaintCallback)
+ , mPaintTransform(aPaintTransform)
+ , mInitialized(false)
+{
+ if (aOverrideBBox) {
+ mTargetBBox = *aOverrideBBox;
+ } else {
+ MOZ_ASSERT(mTargetFrame, "Need to supply a frame when there's no aOverrideBBox");
+ mTargetBBox = nsSVGUtils::GetBBox(mTargetFrame);
+ }
+
+ // Compute user space to filter space transforms.
+ nsresult rv = ComputeUserSpaceToFilterSpaceScale();
+ if (NS_FAILED(rv)) {
+ return;
+ }
+
+ gfxRect targetBBoxInFilterSpace = UserSpaceToFilterSpace(mTargetBBox);
+ targetBBoxInFilterSpace.RoundOut();
+ if (!gfxUtils::GfxRectToIntRect(targetBBoxInFilterSpace, &mTargetBBoxInFilterSpace)) {
+ // The target's bbox is way too big if there is float->int overflow.
+ return;
+ }
+
+ // Get various transforms:
+
+ gfxMatrix filterToUserSpace(mFilterSpaceToUserSpaceScale.width, 0.0f,
+ 0.0f, mFilterSpaceToUserSpaceScale.height,
+ 0.0f, 0.0f);
+
+ // Only used (so only set) when we paint:
+ if (mPaintCallback) {
+ mFilterSpaceToDeviceSpaceTransform = filterToUserSpace * mPaintTransform;
+ }
+
+ mFilterSpaceToFrameSpaceInCSSPxTransform =
+ filterToUserSpace * GetUserSpaceToFrameSpaceInCSSPxTransform();
+ // mFilterSpaceToFrameSpaceInCSSPxTransform is always invertible
+ mFrameSpaceInCSSPxToFilterSpaceTransform =
+ mFilterSpaceToFrameSpaceInCSSPxTransform;
+ mFrameSpaceInCSSPxToFilterSpaceTransform.Invert();
+
+ nsIntRect targetBounds;
+ if (aPreFilterVisualOverflowRectOverride) {
+ targetBounds =
+ FrameSpaceToFilterSpace(aPreFilterVisualOverflowRectOverride);
+ } else if (mTargetFrame) {
+ nsRect preFilterVOR = mTargetFrame->GetPreEffectsVisualOverflowRect();
+ targetBounds = FrameSpaceToFilterSpace(&preFilterVOR);
+ }
+ mTargetBounds.UnionRect(mTargetBBoxInFilterSpace, targetBounds);
+
+ // Build the filter graph.
+ rv = BuildPrimitives(aFilterChain, aTargetFrame, aFilterInputIsTainted);
+ if (NS_FAILED(rv)) {
+ return;
+ }
+
+ if (mPrimitiveDescriptions.IsEmpty()) {
+ // Nothing should be rendered.
+ return;
+ }
+
+ // Convert the passed in rects from frame space to filter space:
+ mPostFilterDirtyRegion = FrameSpaceToFilterSpace(aPostFilterDirtyRegion);
+ mPreFilterDirtyRegion = FrameSpaceToFilterSpace(aPreFilterDirtyRegion);
+
+ mInitialized = true;
+}
+
+nsresult
+nsFilterInstance::ComputeUserSpaceToFilterSpaceScale()
+{
+ gfxMatrix canvasTransform;
+ if (mTargetFrame) {
+ canvasTransform = nsSVGUtils::GetCanvasTM(mTargetFrame);
+ if (canvasTransform.IsSingular()) {
+ // Nothing should be rendered.
+ return NS_ERROR_FAILURE;
+ }
+ }
+
+ mUserSpaceToFilterSpaceScale = canvasTransform.ScaleFactors(true);
+ if (mUserSpaceToFilterSpaceScale.width <= 0.0f ||
+ mUserSpaceToFilterSpaceScale.height <= 0.0f) {
+ // Nothing should be rendered.
+ return NS_ERROR_FAILURE;
+ }
+
+ mFilterSpaceToUserSpaceScale = gfxSize(1.0f / mUserSpaceToFilterSpaceScale.width,
+ 1.0f / mUserSpaceToFilterSpaceScale.height);
+ return NS_OK;
+}
+
+gfxRect
+nsFilterInstance::UserSpaceToFilterSpace(const gfxRect& aUserSpaceRect) const
+{
+ gfxRect filterSpaceRect = aUserSpaceRect;
+ filterSpaceRect.Scale(mUserSpaceToFilterSpaceScale.width,
+ mUserSpaceToFilterSpaceScale.height);
+ return filterSpaceRect;
+}
+
+gfxRect
+nsFilterInstance::FilterSpaceToUserSpace(const gfxRect& aFilterSpaceRect) const
+{
+ gfxRect userSpaceRect = aFilterSpaceRect;
+ userSpaceRect.Scale(mFilterSpaceToUserSpaceScale.width,
+ mFilterSpaceToUserSpaceScale.height);
+ return userSpaceRect;
+}
+
+nsresult
+nsFilterInstance::BuildPrimitives(const nsTArray<nsStyleFilter>& aFilterChain,
+ nsIFrame* aTargetFrame,
+ bool aFilterInputIsTainted)
+{
+ NS_ASSERTION(!mPrimitiveDescriptions.Length(),
+ "expected to start building primitives from scratch");
+
+ for (uint32_t i = 0; i < aFilterChain.Length(); i++) {
+ bool inputIsTainted =
+ mPrimitiveDescriptions.IsEmpty() ? aFilterInputIsTainted :
+ mPrimitiveDescriptions.LastElement().IsTainted();
+ nsresult rv = BuildPrimitivesForFilter(aFilterChain[i], aTargetFrame, inputIsTainted);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+ }
+
+ mFilterDescription = FilterDescription(mPrimitiveDescriptions);
+
+ return NS_OK;
+}
+
+nsresult
+nsFilterInstance::BuildPrimitivesForFilter(const nsStyleFilter& aFilter,
+ nsIFrame* aTargetFrame,
+ bool aInputIsTainted)
+{
+ NS_ASSERTION(mUserSpaceToFilterSpaceScale.width > 0.0f &&
+ mFilterSpaceToUserSpaceScale.height > 0.0f,
+ "scale factors between spaces should be positive values");
+
+ if (aFilter.GetType() == NS_STYLE_FILTER_URL) {
+ // Build primitives for an SVG filter.
+ nsSVGFilterInstance svgFilterInstance(aFilter, aTargetFrame,
+ mTargetContent,
+ mMetrics, mTargetBBox,
+ mUserSpaceToFilterSpaceScale,
+ mFilterSpaceToUserSpaceScale);
+ if (!svgFilterInstance.IsInitialized()) {
+ return NS_ERROR_FAILURE;
+ }
+
+ return svgFilterInstance.BuildPrimitives(mPrimitiveDescriptions, mInputImages,
+ aInputIsTainted);
+ }
+
+ // Build primitives for a CSS filter.
+
+ // If we don't have a frame, use opaque black for shadows with unspecified
+ // shadow colors.
+ nscolor shadowFallbackColor =
+ mTargetFrame ? mTargetFrame->StyleColor()->mColor : NS_RGB(0,0,0);
+
+ nsCSSFilterInstance cssFilterInstance(aFilter, shadowFallbackColor,
+ mTargetBounds,
+ mFrameSpaceInCSSPxToFilterSpaceTransform);
+ return cssFilterInstance.BuildPrimitives(mPrimitiveDescriptions, aInputIsTainted);
+}
+
+void
+nsFilterInstance::ComputeNeededBoxes()
+{
+ if (mPrimitiveDescriptions.IsEmpty())
+ return;
+
+ nsIntRegion sourceGraphicNeededRegion;
+ nsIntRegion fillPaintNeededRegion;
+ nsIntRegion strokePaintNeededRegion;
+
+ FilterSupport::ComputeSourceNeededRegions(
+ mFilterDescription, mPostFilterDirtyRegion,
+ sourceGraphicNeededRegion, fillPaintNeededRegion, strokePaintNeededRegion);
+
+ sourceGraphicNeededRegion.And(sourceGraphicNeededRegion, mTargetBounds);
+
+ mSourceGraphic.mNeededBounds = sourceGraphicNeededRegion.GetBounds();
+ mFillPaint.mNeededBounds = fillPaintNeededRegion.GetBounds();
+ mStrokePaint.mNeededBounds = strokePaintNeededRegion.GetBounds();
+}
+
+nsresult
+nsFilterInstance::BuildSourcePaint(SourceInfo *aSource,
+ DrawTarget* aTargetDT)
+{
+ MOZ_ASSERT(mTargetFrame);
+ nsIntRect neededRect = aSource->mNeededBounds;
+
+ RefPtr<DrawTarget> offscreenDT =
+ gfxPlatform::GetPlatform()->CreateOffscreenContentDrawTarget(
+ neededRect.Size(), SurfaceFormat::B8G8R8A8);
+ if (!offscreenDT || !offscreenDT->IsValid()) {
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+
+ gfxMatrix deviceToFilterSpace = GetFilterSpaceToDeviceSpaceTransform();
+ if (!deviceToFilterSpace.Invert()) {
+ return NS_ERROR_FAILURE;
+ }
+
+ if (!mPaintTransform.IsSingular()) {
+ RefPtr<gfxContext> gfx = gfxContext::CreateOrNull(offscreenDT);
+ MOZ_ASSERT(gfx); // already checked the draw target above
+ gfx->Save();
+ gfx->Multiply(mPaintTransform *
+ deviceToFilterSpace *
+ gfxMatrix::Translation(-neededRect.TopLeft()));
+ GeneralPattern pattern;
+ if (aSource == &mFillPaint) {
+ nsSVGUtils::MakeFillPatternFor(mTargetFrame, gfx, &pattern);
+ } else if (aSource == &mStrokePaint) {
+ nsSVGUtils::MakeStrokePatternFor(mTargetFrame, gfx, &pattern);
+ }
+ if (pattern.GetPattern()) {
+ offscreenDT->FillRect(ToRect(FilterSpaceToUserSpace(ThebesRect(neededRect))),
+ pattern);
+ }
+ gfx->Restore();
+ }
+
+ aSource->mSourceSurface = offscreenDT->Snapshot();
+ aSource->mSurfaceRect = neededRect;
+
+ return NS_OK;
+}
+
+nsresult
+nsFilterInstance::BuildSourcePaints(DrawTarget* aTargetDT)
+{
+ nsresult rv = NS_OK;
+
+ if (!mFillPaint.mNeededBounds.IsEmpty()) {
+ rv = BuildSourcePaint(&mFillPaint, aTargetDT);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ if (!mStrokePaint.mNeededBounds.IsEmpty()) {
+ rv = BuildSourcePaint(&mStrokePaint, aTargetDT);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+ return rv;
+}
+
+nsresult
+nsFilterInstance::BuildSourceImage(DrawTarget* aTargetDT)
+{
+ MOZ_ASSERT(mTargetFrame);
+
+ nsIntRect neededRect = mSourceGraphic.mNeededBounds;
+ if (neededRect.IsEmpty()) {
+ return NS_OK;
+ }
+
+ RefPtr<DrawTarget> offscreenDT =
+ gfxPlatform::GetPlatform()->CreateOffscreenContentDrawTarget(
+ neededRect.Size(), SurfaceFormat::B8G8R8A8);
+ if (!offscreenDT || !offscreenDT->IsValid()) {
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+
+ gfxRect r = FilterSpaceToUserSpace(ThebesRect(neededRect));
+ r.RoundOut();
+ nsIntRect dirty;
+ if (!gfxUtils::GfxRectToIntRect(r, &dirty))
+ return NS_ERROR_FAILURE;
+
+ // SVG graphics paint to device space, so we need to set an initial device
+ // space to filter space transform on the gfxContext that SourceGraphic
+ // and SourceAlpha will paint to.
+ //
+ // (In theory it would be better to minimize error by having filtered SVG
+ // graphics temporarily paint to user space when painting the sources and
+ // only set a user space to filter space transform on the gfxContext
+ // (since that would eliminate the transform multiplications from user
+ // space to device space and back again). However, that would make the
+ // code more complex while being hard to get right without introducing
+ // subtle bugs, and in practice it probably makes no real difference.)
+ gfxMatrix deviceToFilterSpace = GetFilterSpaceToDeviceSpaceTransform();
+ if (!deviceToFilterSpace.Invert()) {
+ return NS_ERROR_FAILURE;
+ }
+ RefPtr<gfxContext> ctx = gfxContext::CreateOrNull(offscreenDT);
+ MOZ_ASSERT(ctx); // already checked the draw target above
+ ctx->SetMatrix(
+ ctx->CurrentMatrix().Translate(-neededRect.TopLeft()).
+ PreMultiply(deviceToFilterSpace));
+
+ DrawResult result =
+ mPaintCallback->Paint(*ctx, mTargetFrame, mPaintTransform, &dirty);
+
+ mSourceGraphic.mSourceSurface = offscreenDT->Snapshot();
+ mSourceGraphic.mSurfaceRect = neededRect;
+
+ return (result == DrawResult::SUCCESS) ? NS_OK : NS_ERROR_FAILURE;
+}
+
+nsresult
+nsFilterInstance::Render(DrawTarget* aDrawTarget)
+{
+ MOZ_ASSERT(mTargetFrame, "Need a frame for rendering");
+
+ nsIntRect filterRect =
+ mPostFilterDirtyRegion.GetBounds().Intersect(OutputFilterSpaceBounds());
+ gfxMatrix ctm = GetFilterSpaceToDeviceSpaceTransform();
+
+ if (filterRect.IsEmpty() || ctm.IsSingular()) {
+ return NS_OK;
+ }
+
+ AutoRestoreTransform autoRestoreTransform(aDrawTarget);
+ Matrix newTM = ToMatrix(ctm).PreTranslate(filterRect.x, filterRect.y) *
+ aDrawTarget->GetTransform();
+ aDrawTarget->SetTransform(newTM);
+
+ ComputeNeededBoxes();
+
+ nsresult rv = BuildSourceImage(aDrawTarget);
+ if (NS_FAILED(rv))
+ return rv;
+ rv = BuildSourcePaints(aDrawTarget);
+ if (NS_FAILED(rv))
+ return rv;
+
+ FilterSupport::RenderFilterDescription(
+ aDrawTarget, mFilterDescription, IntRectToRect(filterRect),
+ mSourceGraphic.mSourceSurface, mSourceGraphic.mSurfaceRect,
+ mFillPaint.mSourceSurface, mFillPaint.mSurfaceRect,
+ mStrokePaint.mSourceSurface, mStrokePaint.mSurfaceRect,
+ mInputImages, Point(0, 0));
+
+ return NS_OK;
+}
+
+nsRegion
+nsFilterInstance::ComputePostFilterDirtyRegion()
+{
+ if (mPreFilterDirtyRegion.IsEmpty()) {
+ return nsRegion();
+ }
+
+ nsIntRegion resultChangeRegion =
+ FilterSupport::ComputeResultChangeRegion(mFilterDescription,
+ mPreFilterDirtyRegion, nsIntRegion(), nsIntRegion());
+ return FilterSpaceToFrameSpace(resultChangeRegion);
+}
+
+nsRect
+nsFilterInstance::ComputePostFilterExtents()
+{
+ nsIntRegion postFilterExtents =
+ FilterSupport::ComputePostFilterExtents(mFilterDescription, mTargetBounds);
+ return FilterSpaceToFrameSpace(postFilterExtents.GetBounds());
+}
+
+nsRect
+nsFilterInstance::ComputeSourceNeededRect()
+{
+ ComputeNeededBoxes();
+ return FilterSpaceToFrameSpace(mSourceGraphic.mNeededBounds);
+}
+
+nsIntRect
+nsFilterInstance::OutputFilterSpaceBounds() const
+{
+ uint32_t numPrimitives = mPrimitiveDescriptions.Length();
+ if (numPrimitives <= 0)
+ return nsIntRect();
+
+ nsIntRect bounds =
+ mPrimitiveDescriptions[numPrimitives - 1].PrimitiveSubregion();
+ bool overflow;
+ IntSize surfaceSize =
+ nsSVGUtils::ConvertToSurfaceSize(bounds.Size(), &overflow);
+ bounds.SizeTo(surfaceSize);
+ return bounds;
+}
+
+nsIntRect
+nsFilterInstance::FrameSpaceToFilterSpace(const nsRect* aRect) const
+{
+ nsIntRect rect = OutputFilterSpaceBounds();
+ if (aRect) {
+ if (aRect->IsEmpty()) {
+ return nsIntRect();
+ }
+ gfxRect rectInCSSPx =
+ nsLayoutUtils::RectToGfxRect(*aRect, nsPresContext::AppUnitsPerCSSPixel());
+ gfxRect rectInFilterSpace =
+ mFrameSpaceInCSSPxToFilterSpaceTransform.TransformBounds(rectInCSSPx);
+ rectInFilterSpace.RoundOut();
+ nsIntRect intRect;
+ if (gfxUtils::GfxRectToIntRect(rectInFilterSpace, &intRect)) {
+ rect = intRect;
+ }
+ }
+ return rect;
+}
+
+nsRect
+nsFilterInstance::FilterSpaceToFrameSpace(const nsIntRect& aRect) const
+{
+ if (aRect.IsEmpty()) {
+ return nsRect();
+ }
+ gfxRect r(aRect.x, aRect.y, aRect.width, aRect.height);
+ r = mFilterSpaceToFrameSpaceInCSSPxTransform.TransformBounds(r);
+ // nsLayoutUtils::RoundGfxRectToAppRect rounds out.
+ return nsLayoutUtils::RoundGfxRectToAppRect(r, nsPresContext::AppUnitsPerCSSPixel());
+}
+
+nsIntRegion
+nsFilterInstance::FrameSpaceToFilterSpace(const nsRegion* aRegion) const
+{
+ if (!aRegion) {
+ return OutputFilterSpaceBounds();
+ }
+ nsIntRegion result;
+ for (auto iter = aRegion->RectIter(); !iter.Done(); iter.Next()) {
+ // FrameSpaceToFilterSpace rounds out, so this works.
+ result.Or(result, FrameSpaceToFilterSpace(&iter.Get()));
+ }
+ return result;
+}
+
+nsRegion
+nsFilterInstance::FilterSpaceToFrameSpace(const nsIntRegion& aRegion) const
+{
+ nsRegion result;
+ for (auto iter = aRegion.RectIter(); !iter.Done(); iter.Next()) {
+ // FilterSpaceToFrameSpace rounds out, so this works.
+ result.Or(result, FilterSpaceToFrameSpace(iter.Get()));
+ }
+ return result;
+}
+
+gfxMatrix
+nsFilterInstance::GetUserSpaceToFrameSpaceInCSSPxTransform() const
+{
+ if (!mTargetFrame) {
+ return gfxMatrix();
+ }
+ return gfxMatrix::Translation(-nsSVGUtils::FrameSpaceInCSSPxToUserSpaceOffset(mTargetFrame));
+}
diff --git a/layout/svg/nsFilterInstance.h b/layout/svg/nsFilterInstance.h
new file mode 100644
index 0000000000..f4158f1c76
--- /dev/null
+++ b/layout/svg/nsFilterInstance.h
@@ -0,0 +1,390 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef __NS_FILTERINSTANCE_H__
+#define __NS_FILTERINSTANCE_H__
+
+#include "gfxMatrix.h"
+#include "gfxPoint.h"
+#include "gfxRect.h"
+#include "nsCOMPtr.h"
+#include "nsHashKeys.h"
+#include "nsPoint.h"
+#include "nsRect.h"
+#include "nsSize.h"
+#include "nsSVGFilters.h"
+#include "nsSVGNumber2.h"
+#include "nsSVGNumberPair.h"
+#include "nsTArray.h"
+#include "nsIFrame.h"
+#include "mozilla/gfx/2D.h"
+
+class gfxContext;
+class nsIFrame;
+class nsSVGFilterPaintCallback;
+
+namespace mozilla {
+namespace dom {
+class UserSpaceMetrics;
+} // namespace dom
+} // namespace mozilla
+
+/**
+ * This class performs all filter processing.
+ *
+ * We build a graph of the filter image data flow, essentially
+ * converting the filter graph to SSA. This lets us easily propagate
+ * analysis data (such as bounding-boxes) over the filter primitive graph.
+ *
+ * Definition of "filter space": filter space is a coordinate system that is
+ * aligned with the user space of the filtered element, with its origin located
+ * at the top left of the filter region, and with one unit equal in size to one
+ * pixel of the offscreen surface into which the filter output would/will be
+ * painted.
+ *
+ * The definition of "filter region" can be found here:
+ * http://www.w3.org/TR/SVG11/filters.html#FilterEffectsRegion
+ */
+class nsFilterInstance
+{
+ typedef mozilla::gfx::IntRect IntRect;
+ typedef mozilla::gfx::SourceSurface SourceSurface;
+ typedef mozilla::gfx::DrawTarget DrawTarget;
+ typedef mozilla::gfx::FilterPrimitiveDescription FilterPrimitiveDescription;
+ typedef mozilla::gfx::FilterDescription FilterDescription;
+ typedef mozilla::dom::UserSpaceMetrics UserSpaceMetrics;
+
+public:
+ /**
+ * Create a FilterDescription for the supplied filter. All coordinates in
+ * the description are in filter space.
+ * @param aFilterInputIsTainted Describes whether the SourceImage / SourceAlpha
+ * input is tainted. This affects whether feDisplacementMap will respect
+ * the filter input as its map input, and it affects the IsTainted() state
+ * on the filter primitives in the FilterDescription. "Tainted" is a term
+ * from the filters spec and means security-sensitive content, i.e. pixels
+ * that JS should not be able to read in any way.
+ * @param aOutAdditionalImages Will contain additional images needed to
+ * render the filter (from feImage primitives).
+ * @return A FilterDescription describing the filter.
+ */
+ static FilterDescription GetFilterDescription(nsIContent* aFilteredElement,
+ const nsTArray<nsStyleFilter>& aFilterChain,
+ bool aFilterInputIsTainted,
+ const UserSpaceMetrics& aMetrics,
+ const gfxRect& aBBox,
+ nsTArray<RefPtr<SourceSurface>>& aOutAdditionalImages);
+
+ /**
+ * Paint the given filtered frame.
+ * @param aDirtyArea The area than needs to be painted, in aFilteredFrame's
+ * frame space (i.e. relative to its origin, the top-left corner of its
+ * border box).
+ */
+ static nsresult PaintFilteredFrame(nsIFrame *aFilteredFrame,
+ DrawTarget* aDrawTarget,
+ const gfxMatrix& aTransform,
+ nsSVGFilterPaintCallback *aPaintCallback,
+ const nsRegion* aDirtyArea);
+
+ /**
+ * Returns the post-filter area that could be dirtied when the given
+ * pre-filter area of aFilteredFrame changes.
+ * @param aPreFilterDirtyRegion The pre-filter area of aFilteredFrame that has
+ * changed, relative to aFilteredFrame, in app units.
+ */
+ static nsRegion GetPostFilterDirtyArea(nsIFrame *aFilteredFrame,
+ const nsRegion& aPreFilterDirtyRegion);
+
+ /**
+ * Returns the pre-filter area that is needed from aFilteredFrame when the
+ * given post-filter area needs to be repainted.
+ * @param aPostFilterDirtyRegion The post-filter area that is dirty, relative
+ * to aFilteredFrame, in app units.
+ */
+ static nsRegion GetPreFilterNeededArea(nsIFrame *aFilteredFrame,
+ const nsRegion& aPostFilterDirtyRegion);
+
+ /**
+ * Returns the post-filter visual overflow rect (paint bounds) of
+ * aFilteredFrame.
+ * @param aOverrideBBox A user space rect, in user units, that should be used
+ * as aFilteredFrame's bbox ('bbox' is a specific SVG term), if non-null.
+ * @param aPreFilterBounds The pre-filter visual overflow rect of
+ * aFilteredFrame, if non-null.
+ */
+ static nsRect GetPostFilterBounds(nsIFrame *aFilteredFrame,
+ const gfxRect *aOverrideBBox = nullptr,
+ const nsRect *aPreFilterBounds = nullptr);
+
+ /**
+ * @param aTargetFrame The frame of the filtered element under consideration,
+ * may be null.
+ * @param aTargetContent The filtered element itself.
+ * @param aMetrics The metrics to resolve SVG lengths against.
+ * @param aFilterChain The list of filters to apply.
+ * @param aFilterInputIsTainted Describes whether the SourceImage / SourceAlpha
+ * input is tainted. This affects whether feDisplacementMap will respect
+ * the filter input as its map input.
+ * @param aPaintCallback [optional] The callback that Render() should use to
+ * paint. Only required if you will call Render().
+ * @param aPaintTransform The transform to apply to convert to
+ * aTargetFrame's SVG user space. Only used when painting.
+ * @param aPostFilterDirtyRegion [optional] The post-filter area
+ * that has to be repainted, in app units. Only required if you will
+ * call ComputeSourceNeededRect() or Render().
+ * @param aPreFilterDirtyRegion [optional] The pre-filter area of
+ * the filtered element that changed, in app units. Only required if you
+ * will call ComputePostFilterDirtyRegion().
+ * @param aOverridePreFilterVisualOverflowRect [optional] Use a different
+ * visual overflow rect for the target element.
+ * @param aOverrideBBox [optional] Use a different SVG bbox for the target
+ * element. Must be non-null if aTargetFrame is null.
+ */
+ nsFilterInstance(nsIFrame *aTargetFrame,
+ nsIContent* aTargetContent,
+ const UserSpaceMetrics& aMetrics,
+ const nsTArray<nsStyleFilter>& aFilterChain,
+ bool aFilterInputIsTainted,
+ nsSVGFilterPaintCallback *aPaintCallback,
+ const gfxMatrix& aPaintTransform,
+ const nsRegion *aPostFilterDirtyRegion = nullptr,
+ const nsRegion *aPreFilterDirtyRegion = nullptr,
+ const nsRect *aOverridePreFilterVisualOverflowRect = nullptr,
+ const gfxRect *aOverrideBBox = nullptr);
+
+ /**
+ * Returns true if the filter instance was created successfully.
+ */
+ bool IsInitialized() const { return mInitialized; }
+
+ /**
+ * Draws the filter output into aDrawTarget. The area that
+ * needs to be painted must have been specified before calling this method
+ * by passing it as the aPostFilterDirtyRegion argument to the
+ * nsFilterInstance constructor.
+ */
+ nsresult Render(DrawTarget* aDrawTarget);
+
+ const FilterDescription& ExtractDescriptionAndAdditionalImages(nsTArray<RefPtr<SourceSurface>>& aOutAdditionalImages)
+ {
+ mInputImages.SwapElements(aOutAdditionalImages);
+ return mFilterDescription;
+ }
+
+ /**
+ * Sets the aPostFilterDirtyRegion outparam to the post-filter area in frame
+ * space that would be dirtied by mTargetFrame when a given
+ * pre-filter area of mTargetFrame is dirtied. The pre-filter area must have
+ * been specified before calling this method by passing it as the
+ * aPreFilterDirtyRegion argument to the nsFilterInstance constructor.
+ */
+ nsRegion ComputePostFilterDirtyRegion();
+
+ /**
+ * Sets the aPostFilterExtents outparam to the post-filter bounds in frame
+ * space for the whole filter output. This is not necessarily equivalent to
+ * the area that would be dirtied in the result when the entire pre-filter
+ * area is dirtied, because some filter primitives can generate output
+ * without any input.
+ */
+ nsRect ComputePostFilterExtents();
+
+ /**
+ * Sets the aDirty outparam to the pre-filter bounds in frame space of the
+ * area of mTargetFrame that is needed in order to paint the filtered output
+ * for a given post-filter dirtied area. The post-filter area must have been
+ * specified before calling this method by passing it as the aPostFilterDirtyRegion
+ * argument to the nsFilterInstance constructor.
+ */
+ nsRect ComputeSourceNeededRect();
+
+
+ /**
+ * Returns the transform from filter space to outer-<svg> device space.
+ */
+ gfxMatrix GetFilterSpaceToDeviceSpaceTransform() const {
+ return mFilterSpaceToDeviceSpaceTransform;
+ }
+
+private:
+ struct SourceInfo {
+ // Specifies which parts of the source need to be rendered.
+ // Set by ComputeNeededBoxes().
+ nsIntRect mNeededBounds;
+
+ // The surface that contains the input rendering.
+ // Set by BuildSourceImage / BuildSourcePaint.
+ RefPtr<SourceSurface> mSourceSurface;
+
+ // The position and size of mSourceSurface in filter space.
+ // Set by BuildSourceImage / BuildSourcePaint.
+ IntRect mSurfaceRect;
+ };
+
+ /**
+ * Creates a SourceSurface for either the FillPaint or StrokePaint graph
+ * nodes
+ */
+ nsresult BuildSourcePaint(SourceInfo *aPrimitive,
+ DrawTarget* aTargetDT);
+
+ /**
+ * Creates a SourceSurface for either the FillPaint and StrokePaint graph
+ * nodes, fills its contents and assigns it to mFillPaint.mSourceSurface and
+ * mStrokePaint.mSourceSurface respectively.
+ */
+ nsresult BuildSourcePaints(DrawTarget* aTargetDT);
+
+ /**
+ * Creates the SourceSurface for the SourceGraphic graph node, paints its
+ * contents, and assigns it to mSourceGraphic.mSourceSurface.
+ */
+ nsresult BuildSourceImage(DrawTarget* aTargetDT);
+
+ /**
+ * Build the list of FilterPrimitiveDescriptions that describes the filter's
+ * filter primitives and their connections. This populates
+ * mPrimitiveDescriptions and mInputImages. aFilterInputIsTainted describes
+ * whether the SourceGraphic is tainted.
+ */
+ nsresult BuildPrimitives(const nsTArray<nsStyleFilter>& aFilterChain,
+ nsIFrame* aTargetFrame,
+ bool aFilterInputIsTainted);
+
+ /**
+ * Add to the list of FilterPrimitiveDescriptions for a particular SVG
+ * reference filter or CSS filter. This populates mPrimitiveDescriptions and
+ * mInputImages. aInputIsTainted describes whether the input to aFilter is
+ * tainted.
+ */
+ nsresult BuildPrimitivesForFilter(const nsStyleFilter& aFilter,
+ nsIFrame* aTargetFrame,
+ bool aInputIsTainted);
+
+ /**
+ * Computes the filter space bounds of the areas that we actually *need* from
+ * the filter sources, based on the value of mPostFilterDirtyRegion.
+ * This sets mNeededBounds on the corresponding SourceInfo structs.
+ */
+ void ComputeNeededBoxes();
+
+ /**
+ * Returns the output bounds of the final FilterPrimitiveDescription.
+ */
+ nsIntRect OutputFilterSpaceBounds() const;
+
+ /**
+ * Compute the scale factors between user space and filter space.
+ */
+ nsresult ComputeUserSpaceToFilterSpaceScale();
+
+ /**
+ * Transform a rect between user space and filter space.
+ */
+ gfxRect UserSpaceToFilterSpace(const gfxRect& aUserSpace) const;
+ gfxRect FilterSpaceToUserSpace(const gfxRect& aFilterSpaceRect) const;
+
+ /**
+ * Converts an nsRect or an nsRegion that is relative to a filtered frame's
+ * origin (i.e. the top-left corner of its border box) into filter space,
+ * rounding out.
+ * Returns the entire filter region if aRect / aRegion is null, or if the
+ * result is too large to be stored in an nsIntRect.
+ */
+ nsIntRect FrameSpaceToFilterSpace(const nsRect* aRect) const;
+ nsIntRegion FrameSpaceToFilterSpace(const nsRegion* aRegion) const;
+
+ /**
+ * Converts an nsIntRect or an nsIntRegion from filter space into the space
+ * that is relative to a filtered frame's origin (i.e. the top-left corner
+ * of its border box) in app units, rounding out.
+ */
+ nsRect FilterSpaceToFrameSpace(const nsIntRect& aRect) const;
+ nsRegion FilterSpaceToFrameSpace(const nsIntRegion& aRegion) const;
+
+ /**
+ * Returns the transform from frame space to the coordinate space that
+ * GetCanvasTM transforms to. "Frame space" is the origin of a frame, aka the
+ * top-left corner of its border box, aka the top left corner of its mRect.
+ */
+ gfxMatrix GetUserSpaceToFrameSpaceInCSSPxTransform() const;
+
+ /**
+ * The frame for the element that is currently being filtered.
+ */
+ nsIFrame* mTargetFrame;
+
+ /**
+ * The filtered element.
+ */
+ nsIContent* mTargetContent;
+
+ /**
+ * The user space metrics of the filtered frame.
+ */
+ const UserSpaceMetrics& mMetrics;
+
+ nsSVGFilterPaintCallback* mPaintCallback;
+
+ /**
+ * The SVG bbox of the element that is being filtered, in user space.
+ */
+ gfxRect mTargetBBox;
+
+ /**
+ * The SVG bbox of the element that is being filtered, in filter space.
+ */
+ nsIntRect mTargetBBoxInFilterSpace;
+
+ /**
+ * The transform from filter space to outer-<svg> device space.
+ */
+ gfxMatrix mFilterSpaceToDeviceSpaceTransform;
+
+ /**
+ * Transform rects between filter space and frame space in CSS pixels.
+ */
+ gfxMatrix mFilterSpaceToFrameSpaceInCSSPxTransform;
+ gfxMatrix mFrameSpaceInCSSPxToFilterSpaceTransform;
+
+ /**
+ * The scale factors between user space and filter space.
+ */
+ gfxSize mUserSpaceToFilterSpaceScale;
+ gfxSize mFilterSpaceToUserSpaceScale;
+
+ /**
+ * Pre-filter paint bounds of the element that is being filtered, in filter
+ * space.
+ */
+ nsIntRect mTargetBounds;
+
+ /**
+ * The dirty area that needs to be repainted, in filter space.
+ */
+ nsIntRegion mPostFilterDirtyRegion;
+
+ /**
+ * The pre-filter area of the filtered element that changed, in filter space.
+ */
+ nsIntRegion mPreFilterDirtyRegion;
+
+ SourceInfo mSourceGraphic;
+ SourceInfo mFillPaint;
+ SourceInfo mStrokePaint;
+
+ /**
+ * The transform to the SVG user space of mTargetFrame.
+ */
+ gfxMatrix mPaintTransform;
+
+ nsTArray<RefPtr<SourceSurface>> mInputImages;
+ nsTArray<FilterPrimitiveDescription> mPrimitiveDescriptions;
+ FilterDescription mFilterDescription;
+ bool mInitialized;
+};
+
+#endif
diff --git a/layout/svg/nsISVGChildFrame.h b/layout/svg/nsISVGChildFrame.h
new file mode 100644
index 0000000000..c9fd0f12ae
--- /dev/null
+++ b/layout/svg/nsISVGChildFrame.h
@@ -0,0 +1,154 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef __NS_ISVGCHILDFRAME_H__
+#define __NS_ISVGCHILDFRAME_H__
+
+#include "gfxRect.h"
+#include "nsQueryFrame.h"
+
+class gfxContext;
+class gfxMatrix;
+class nsIFrame;
+class SVGBBox;
+
+struct nsRect;
+
+namespace mozilla {
+class SVGAnimatedLengthList;
+class SVGAnimatedNumberList;
+class SVGLengthList;
+class SVGNumberList;
+class SVGUserUnitList;
+
+namespace gfx {
+class Matrix;
+} // namespace gfx
+} // namespace mozilla
+
+/**
+ * This class is not particularly well named. It is inherited by some, but
+ * not all SVG frame classes that can be descendants of an
+ * nsSVGOuterSVGFrame in the frame tree. Note specifically that SVG container
+ * frames that do not inherit nsSVGDisplayContainerFrame do not inherit this
+ * class (so that's classes that only inherit nsSVGContainerFrame).
+ */
+class nsISVGChildFrame : public nsQueryFrame
+{
+public:
+ typedef mozilla::SVGAnimatedNumberList SVGAnimatedNumberList;
+ typedef mozilla::SVGNumberList SVGNumberList;
+ typedef mozilla::SVGAnimatedLengthList SVGAnimatedLengthList;
+ typedef mozilla::SVGLengthList SVGLengthList;
+ typedef mozilla::SVGUserUnitList SVGUserUnitList;
+ typedef mozilla::image::DrawResult DrawResult;
+
+ NS_DECL_QUERYFRAME_TARGET(nsISVGChildFrame)
+
+ /**
+ * Paint this frame.
+ *
+ * SVG is painted using a combination of display lists (trees of
+ * nsDisplayItem built by BuildDisplayList() implementations) and recursive
+ * PaintSVG calls. SVG frames with the NS_FRAME_IS_NONDISPLAY bit set are
+ * always painted using recursive PaintSVG calls since display list painting
+ * would provide no advantages (they wouldn't be retained for invalidation).
+ * Displayed SVG is normally painted via a display list tree created under
+ * nsSVGOuterSVGFrame::BuildDisplayList, unless the
+ * svg.display-lists.painting.enabled pref has been set to false by the user
+ * in which case it is done via an nsSVGOuterSVGFrame::PaintSVG() call that
+ * recurses over the entire SVG frame tree. In future we may use PaintSVG()
+ * calls on SVG container frames to avoid display list construction when it
+ * is expensive and unnecessary (see bug 934411).
+ *
+ * @param aTransform The transform that has to be multiplied onto the
+ * DrawTarget in order for drawing to be in this frame's SVG user space.
+ * Implementations of this method should avoid multiplying aTransform onto
+ * the DrawTarget when possible and instead just pass a transform down to
+ * their children. This is preferable because changing the transform is
+ * very expensive for certain DrawTarget backends so it is best to minimize
+ * the number of transform changes.
+ *
+ * @param aDirtyRect The area being redrawn, in frame offset pixel
+ * coordinates.
+ */
+ virtual DrawResult PaintSVG(gfxContext& aContext,
+ const gfxMatrix& aTransform,
+ const nsIntRect* aDirtyRect = nullptr) = 0;
+
+ /**
+ * Returns the frame that should handle pointer events at aPoint. aPoint is
+ * expected to be in the SVG user space of the frame on which this method is
+ * called. The frame returned may be the frame on which this method is
+ * called, any of its descendants or else nullptr.
+ */
+ virtual nsIFrame* GetFrameForPoint(const gfxPoint& aPoint) = 0;
+
+ // Get bounds in our nsSVGOuterSVGFrame's coordinates space (in app units)
+ virtual nsRect GetCoveredRegion()=0;
+
+ // Called on SVG child frames (except NS_FRAME_IS_NONDISPLAY frames)
+ // to update and then invalidate their cached bounds. This method is not
+ // called until after the nsSVGOuterSVGFrame has had its initial reflow
+ // (i.e. once the SVG viewport dimensions are known). It should also only
+ // be called by nsSVGOuterSVGFrame during its reflow.
+ virtual void ReflowSVG()=0;
+
+ // Flags to pass to NotifySVGChange:
+ //
+ // DO_NOT_NOTIFY_RENDERING_OBSERVERS - this should only be used when
+ // updating the descendant frames of a clipPath,
+ // mask, pattern or marker frame (or other similar
+ // NS_FRAME_IS_NONDISPLAY frame) immediately
+ // prior to painting that frame's descendants.
+ // TRANSFORM_CHANGED - the current transform matrix for this frame has changed
+ // COORD_CONTEXT_CHANGED - the dimensions of this frame's coordinate context has
+ // changed (percentage lengths must be reevaluated)
+ enum SVGChangedFlags {
+ TRANSFORM_CHANGED = 0x01,
+ COORD_CONTEXT_CHANGED = 0x02,
+ FULL_ZOOM_CHANGED = 0x04
+ };
+ /**
+ * This is called on a frame when there has been a change to one of its
+ * ancestors that might affect the frame too. SVGChangedFlags are passed
+ * to indicate what changed.
+ *
+ * Implementations do not need to invalidate, since the caller will
+ * invalidate the entire area of the ancestor that changed. However, they
+ * may need to update their bounds.
+ */
+ virtual void NotifySVGChanged(uint32_t aFlags)=0;
+
+ /**
+ * Get this frame's contribution to the rect returned by a GetBBox() call
+ * that occurred either on this element, or on one of its ancestors.
+ *
+ * SVG defines an element's bbox to be the element's fill bounds in the
+ * userspace established by that element. By allowing callers to pass in the
+ * transform from the userspace established by this element to the userspace
+ * established by an an ancestor, this method allows callers to obtain this
+ * element's fill bounds in the userspace established by that ancestor
+ * instead. In that case, since we return the bounds in a different userspace
+ * (the ancestor's), the bounds we return are not this element's bbox, but
+ * rather this element's contribution to the bbox of the ancestor.
+ *
+ * @param aToBBoxUserspace The transform from the userspace established by
+ * this element to the userspace established by the ancestor on which
+ * getBBox was called. This will be the identity matrix if we are the
+ * element on which getBBox was called.
+ *
+ * @param aFlags Flags indicating whether, stroke, for example, should be
+ * included in the bbox calculation.
+ */
+ virtual SVGBBox GetBBoxContribution(const mozilla::gfx::Matrix &aToBBoxUserspace,
+ uint32_t aFlags) = 0;
+
+ // Are we a container frame?
+ virtual bool IsDisplayContainer()=0;
+};
+
+#endif // __NS_ISVGCHILDFRAME_H__
+
diff --git a/layout/svg/nsISVGSVGFrame.h b/layout/svg/nsISVGSVGFrame.h
new file mode 100644
index 0000000000..8a30cba8bc
--- /dev/null
+++ b/layout/svg/nsISVGSVGFrame.h
@@ -0,0 +1,26 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef __NS_ISVGSVGFRAME_H__
+#define __NS_ISVGSVGFRAME_H__
+
+#include "nsQueryFrame.h"
+
+class nsISVGSVGFrame
+{
+public:
+ NS_DECL_QUERYFRAME_TARGET(nsISVGSVGFrame)
+
+ /**
+ * Called when non-attribute changes have caused the element's width/height
+ * or its for-children transform to change, and to get the element to notify
+ * its children appropriately. aFlags must be set to
+ * nsISVGChildFrame::COORD_CONTEXT_CHANGED and/or
+ * nsISVGChildFrame::TRANSFORM_CHANGED.
+ */
+ virtual void NotifyViewportOrTransformChanged(uint32_t aFlags)=0;
+};
+
+#endif // __NS_ISVGSVGFRAME_H__
diff --git a/layout/svg/nsSVGAFrame.cpp b/layout/svg/nsSVGAFrame.cpp
new file mode 100644
index 0000000000..54ec8958c6
--- /dev/null
+++ b/layout/svg/nsSVGAFrame.cpp
@@ -0,0 +1,147 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+// Keep in (case-insensitive) order:
+#include "gfxMatrix.h"
+#include "mozilla/dom/SVGAElement.h"
+#include "nsAutoPtr.h"
+#include "nsSVGContainerFrame.h"
+#include "nsSVGIntegrationUtils.h"
+#include "nsSVGUtils.h"
+#include "SVGLengthList.h"
+
+using namespace mozilla;
+
+class nsSVGAFrame : public nsSVGDisplayContainerFrame
+{
+ friend nsIFrame*
+ NS_NewSVGAFrame(nsIPresShell* aPresShell, nsStyleContext* aContext);
+protected:
+ explicit nsSVGAFrame(nsStyleContext* aContext)
+ : nsSVGDisplayContainerFrame(aContext) {}
+
+public:
+ NS_DECL_FRAMEARENA_HELPERS
+
+#ifdef DEBUG
+ virtual void Init(nsIContent* aContent,
+ nsContainerFrame* aParent,
+ nsIFrame* aPrevInFlow) override;
+#endif
+
+ // nsIFrame:
+ virtual nsresult AttributeChanged(int32_t aNameSpaceID,
+ nsIAtom* aAttribute,
+ int32_t aModType) override;
+
+ /**
+ * Get the "type" of the frame
+ *
+ * @see nsGkAtoms::svgAFrame
+ */
+ virtual nsIAtom* GetType() const override;
+
+#ifdef DEBUG_FRAME_DUMP
+ virtual nsresult GetFrameName(nsAString& aResult) const override
+ {
+ return MakeFrameName(NS_LITERAL_STRING("SVGA"), aResult);
+ }
+#endif
+ // nsISVGChildFrame interface:
+ virtual void NotifySVGChanged(uint32_t aFlags) override;
+
+ // nsSVGContainerFrame methods:
+ virtual gfxMatrix GetCanvasTM() override;
+
+private:
+ nsAutoPtr<gfxMatrix> mCanvasTM;
+};
+
+//----------------------------------------------------------------------
+// Implementation
+
+nsIFrame*
+NS_NewSVGAFrame(nsIPresShell* aPresShell, nsStyleContext* aContext)
+{
+ return new (aPresShell) nsSVGAFrame(aContext);
+}
+
+NS_IMPL_FRAMEARENA_HELPERS(nsSVGAFrame)
+
+//----------------------------------------------------------------------
+// nsIFrame methods
+#ifdef DEBUG
+void
+nsSVGAFrame::Init(nsIContent* aContent,
+ nsContainerFrame* aParent,
+ nsIFrame* aPrevInFlow)
+{
+ NS_ASSERTION(aContent->IsSVGElement(nsGkAtoms::a),
+ "Trying to construct an SVGAFrame for a "
+ "content element that doesn't support the right interfaces");
+
+ nsSVGDisplayContainerFrame::Init(aContent, aParent, aPrevInFlow);
+}
+#endif /* DEBUG */
+
+nsresult
+nsSVGAFrame::AttributeChanged(int32_t aNameSpaceID,
+ nsIAtom* aAttribute,
+ int32_t aModType)
+{
+ if (aNameSpaceID == kNameSpaceID_None &&
+ aAttribute == nsGkAtoms::transform) {
+ // We don't invalidate for transform changes (the layers code does that).
+ // Also note that SVGTransformableElement::GetAttributeChangeHint will
+ // return nsChangeHint_UpdateOverflow for "transform" attribute changes
+ // and cause DoApplyRenderingChangeToTree to make the SchedulePaint call.
+ NotifySVGChanged(TRANSFORM_CHANGED);
+ }
+
+ return NS_OK;
+}
+
+nsIAtom *
+nsSVGAFrame::GetType() const
+{
+ return nsGkAtoms::svgAFrame;
+}
+
+//----------------------------------------------------------------------
+// nsISVGChildFrame methods
+
+void
+nsSVGAFrame::NotifySVGChanged(uint32_t aFlags)
+{
+ MOZ_ASSERT(aFlags & (TRANSFORM_CHANGED | COORD_CONTEXT_CHANGED),
+ "Invalidation logic may need adjusting");
+
+ if (aFlags & TRANSFORM_CHANGED) {
+ // make sure our cached transform matrix gets (lazily) updated
+ mCanvasTM = nullptr;
+ }
+
+ nsSVGDisplayContainerFrame::NotifySVGChanged(aFlags);
+}
+
+//----------------------------------------------------------------------
+// nsSVGContainerFrame methods:
+
+gfxMatrix
+nsSVGAFrame::GetCanvasTM()
+{
+ if (!mCanvasTM) {
+ NS_ASSERTION(GetParent(), "null parent");
+
+ nsSVGContainerFrame *parent = static_cast<nsSVGContainerFrame*>(GetParent());
+ dom::SVGAElement *content = static_cast<dom::SVGAElement*>(mContent);
+
+ gfxMatrix tm = content->PrependLocalTransformsTo(parent->GetCanvasTM());
+
+ mCanvasTM = new gfxMatrix(tm);
+ }
+
+ return *mCanvasTM;
+}
diff --git a/layout/svg/nsSVGClipPathFrame.cpp b/layout/svg/nsSVGClipPathFrame.cpp
new file mode 100644
index 0000000000..a619ac9a4e
--- /dev/null
+++ b/layout/svg/nsSVGClipPathFrame.cpp
@@ -0,0 +1,511 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+// Main header first:
+#include "nsSVGClipPathFrame.h"
+
+// Keep others in (case-insensitive) order:
+#include "gfxContext.h"
+#include "mozilla/dom/SVGClipPathElement.h"
+#include "nsGkAtoms.h"
+#include "nsSVGEffects.h"
+#include "nsSVGPathGeometryElement.h"
+#include "nsSVGPathGeometryFrame.h"
+#include "nsSVGUtils.h"
+
+using namespace mozilla;
+using namespace mozilla::dom;
+using namespace mozilla::gfx;
+
+// Arbitrary number
+#define MAX_SVG_CLIP_PATH_REFERENCE_CHAIN_LENGTH int16_t(512)
+
+//----------------------------------------------------------------------
+// Implementation
+
+nsIFrame*
+NS_NewSVGClipPathFrame(nsIPresShell* aPresShell, nsStyleContext* aContext)
+{
+ return new (aPresShell) nsSVGClipPathFrame(aContext);
+}
+
+NS_IMPL_FRAMEARENA_HELPERS(nsSVGClipPathFrame)
+
+void
+nsSVGClipPathFrame::ApplyClipPath(gfxContext& aContext,
+ nsIFrame* aClippedFrame,
+ const gfxMatrix& aMatrix)
+{
+ MOZ_ASSERT(IsTrivial(), "Caller needs to use GetClipMask");
+
+ DrawTarget& aDrawTarget = *aContext.GetDrawTarget();
+
+ // No need for AutoReferenceLimiter since simple clip paths can't create
+ // a reference loop (they don't reference other clip paths).
+
+ // Restore current transform after applying clip path:
+ gfxContextMatrixAutoSaveRestore autoRestore(&aContext);
+
+ RefPtr<Path> clipPath;
+
+ nsISVGChildFrame* singleClipPathChild = nullptr;
+ IsTrivial(&singleClipPathChild);
+
+ if (singleClipPathChild) {
+ nsSVGPathGeometryFrame* pathFrame = do_QueryFrame(singleClipPathChild);
+ if (pathFrame) {
+ nsSVGPathGeometryElement* pathElement =
+ static_cast<nsSVGPathGeometryElement*>(pathFrame->GetContent());
+ gfxMatrix toChildsUserSpace = pathElement->
+ PrependLocalTransformsTo(GetClipPathTransform(aClippedFrame) * aMatrix,
+ eUserSpaceToParent);
+ gfxMatrix newMatrix =
+ aContext.CurrentMatrix().PreMultiply(toChildsUserSpace).NudgeToIntegers();
+ if (!newMatrix.IsSingular()) {
+ aContext.SetMatrix(newMatrix);
+ FillRule clipRule =
+ nsSVGUtils::ToFillRule(pathFrame->StyleSVG()->mClipRule);
+ clipPath = pathElement->GetOrBuildPath(aDrawTarget, clipRule);
+ }
+ }
+ }
+
+ if (clipPath) {
+ aContext.Clip(clipPath);
+ } else {
+ // The spec says clip away everything if we have no children or the
+ // clipping path otherwise can't be resolved:
+ aContext.Clip(Rect());
+ }
+}
+
+already_AddRefed<SourceSurface>
+nsSVGClipPathFrame::GetClipMask(gfxContext& aReferenceContext,
+ nsIFrame* aClippedFrame,
+ const gfxMatrix& aMatrix,
+ Matrix* aMaskTransform,
+ SourceSurface* aExtraMask,
+ const Matrix& aExtraMasksTransform,
+ DrawResult* aResult)
+{
+ MOZ_ASSERT(!IsTrivial(), "Caller needs to use ApplyClipPath");
+
+ if (aResult) {
+ *aResult = DrawResult::SUCCESS;
+ }
+ DrawTarget& aReferenceDT = *aReferenceContext.GetDrawTarget();
+
+ // A clipPath can reference another clipPath. We re-enter this method for
+ // each clipPath in a reference chain, so here we limit chain length:
+ static int16_t sRefChainLengthCounter = AutoReferenceLimiter::notReferencing;
+ AutoReferenceLimiter
+ refChainLengthLimiter(&sRefChainLengthCounter,
+ MAX_SVG_CLIP_PATH_REFERENCE_CHAIN_LENGTH);
+ if (!refChainLengthLimiter.Reference()) {
+ return nullptr; // Reference chain is too long!
+ }
+
+ // And to prevent reference loops we check that this clipPath only appears
+ // once in the reference chain (if any) that we're currently processing:
+ AutoReferenceLimiter refLoopDetector(&mReferencing, 1);
+ if (!refLoopDetector.Reference()) {
+ return nullptr; // Reference loop!
+ }
+
+ IntRect devSpaceClipExtents;
+ {
+ gfxContextMatrixAutoSaveRestore autoRestoreMatrix(&aReferenceContext);
+
+ aReferenceContext.SetMatrix(gfxMatrix());
+ gfxRect rect = aReferenceContext.GetClipExtents();
+ devSpaceClipExtents = RoundedOut(ToRect(rect));
+ if (devSpaceClipExtents.IsEmpty()) {
+ // We don't need to create a mask surface, all drawing is clipped anyway.
+ return nullptr;
+ }
+ }
+
+ RefPtr<DrawTarget> maskDT =
+ aReferenceDT.CreateSimilarDrawTarget(devSpaceClipExtents.Size(),
+ SurfaceFormat::A8);
+
+ gfxMatrix mat = aReferenceContext.CurrentMatrix() *
+ gfxMatrix::Translation(-devSpaceClipExtents.TopLeft());
+
+ // Paint this clipPath's contents into maskDT:
+ {
+ RefPtr<gfxContext> ctx = gfxContext::CreateOrNull(maskDT);
+ if (!ctx) {
+ gfxCriticalError() << "SVGClipPath context problem " << gfx::hexa(maskDT);
+ return nullptr;
+ }
+ ctx->SetMatrix(mat);
+
+ // We need to set mMatrixForChildren here so that under the PaintSVG calls
+ // on our children (below) our GetCanvasTM() method will return the correct
+ // transform.
+ mMatrixForChildren = GetClipPathTransform(aClippedFrame) * aMatrix;
+
+ // Check if this clipPath is itself clipped by another clipPath:
+ nsSVGClipPathFrame* clipPathThatClipsClipPath =
+ nsSVGEffects::GetEffectProperties(this).GetClipPathFrame(nullptr);
+ bool clippingOfClipPathRequiredMasking;
+ if (clipPathThatClipsClipPath) {
+ ctx->Save();
+ clippingOfClipPathRequiredMasking = !clipPathThatClipsClipPath->IsTrivial();
+ if (!clippingOfClipPathRequiredMasking) {
+ clipPathThatClipsClipPath->ApplyClipPath(*ctx, aClippedFrame, aMatrix);
+ } else {
+ Matrix maskTransform;
+ RefPtr<SourceSurface> mask =
+ clipPathThatClipsClipPath->GetClipMask(*ctx, aClippedFrame,
+ aMatrix, &maskTransform);
+ ctx->PushGroupForBlendBack(gfxContentType::ALPHA, 1.0,
+ mask, maskTransform);
+ // The corresponding PopGroupAndBlend call below will mask the
+ // blend using |mask|.
+ }
+ }
+
+ // Paint our children into the mask:
+ for (nsIFrame* kid = mFrames.FirstChild(); kid;
+ kid = kid->GetNextSibling()) {
+ nsISVGChildFrame* SVGFrame = do_QueryFrame(kid);
+ if (SVGFrame) {
+ // The CTM of each frame referencing us can be different.
+ SVGFrame->NotifySVGChanged(nsISVGChildFrame::TRANSFORM_CHANGED);
+
+ bool isOK = true;
+ // Children of this clipPath may themselves be clipped.
+ nsSVGClipPathFrame *clipPathThatClipsChild =
+ nsSVGEffects::GetEffectProperties(kid).GetClipPathFrame(&isOK);
+ if (!isOK) {
+ continue;
+ }
+
+ bool childsClipPathRequiresMasking;
+
+ if (clipPathThatClipsChild) {
+ childsClipPathRequiresMasking = !clipPathThatClipsChild->IsTrivial();
+ ctx->Save();
+ if (!childsClipPathRequiresMasking) {
+ clipPathThatClipsChild->ApplyClipPath(*ctx, aClippedFrame, aMatrix);
+ } else {
+ Matrix maskTransform;
+ RefPtr<SourceSurface> mask =
+ clipPathThatClipsChild->GetClipMask(*ctx, aClippedFrame,
+ aMatrix, &maskTransform);
+ ctx->PushGroupForBlendBack(gfxContentType::ALPHA, 1.0,
+ mask, maskTransform);
+ // The corresponding PopGroupAndBlend call below will mask the
+ // blend using |mask|.
+ }
+ }
+
+ gfxMatrix toChildsUserSpace = mMatrixForChildren;
+ nsIFrame* child = do_QueryFrame(SVGFrame);
+ nsIContent* childContent = child->GetContent();
+ if (childContent->IsSVGElement()) {
+ toChildsUserSpace =
+ static_cast<const nsSVGElement*>(childContent)->
+ PrependLocalTransformsTo(mMatrixForChildren, eUserSpaceToParent);
+ }
+
+ // Our children have NS_STATE_SVG_CLIPPATH_CHILD set on them, and
+ // nsSVGPathGeometryFrame::Render checks for that state bit and paints
+ // only the geometry (opaque black) if set.
+ DrawResult result = SVGFrame->PaintSVG(*ctx, toChildsUserSpace);
+ if (aResult) {
+ *aResult &= result;
+ }
+
+ if (clipPathThatClipsChild) {
+ if (childsClipPathRequiresMasking) {
+ ctx->PopGroupAndBlend();
+ }
+ ctx->Restore();
+ }
+ }
+ }
+
+
+ if (clipPathThatClipsClipPath) {
+ if (clippingOfClipPathRequiredMasking) {
+ ctx->PopGroupAndBlend();
+ }
+ ctx->Restore();
+ }
+ }
+
+ // Moz2D transforms in the opposite direction to Thebes
+ mat.Invert();
+
+ if (aExtraMask) {
+ // We could potentially due this more efficiently with OPERATOR_IN
+ // but that operator does not work well on CG or D2D
+ RefPtr<SourceSurface> currentMask = maskDT->Snapshot();
+ Matrix transform = maskDT->GetTransform();
+ maskDT->SetTransform(Matrix());
+ maskDT->ClearRect(Rect(0, 0,
+ devSpaceClipExtents.width,
+ devSpaceClipExtents.height));
+ maskDT->SetTransform(aExtraMasksTransform * transform);
+ // draw currentMask with the inverse of the transform that we just so that
+ // it ends up in the same spot with aExtraMask transformed by aExtraMasksTransform
+ maskDT->MaskSurface(SurfacePattern(currentMask, ExtendMode::CLAMP, aExtraMasksTransform.Inverse() * ToMatrix(mat)),
+ aExtraMask,
+ Point(0, 0));
+ }
+
+ *aMaskTransform = ToMatrix(mat);
+ return maskDT->Snapshot();
+}
+
+bool
+nsSVGClipPathFrame::PointIsInsideClipPath(nsIFrame* aClippedFrame,
+ const gfxPoint &aPoint)
+{
+ // A clipPath can reference another clipPath. We re-enter this method for
+ // each clipPath in a reference chain, so here we limit chain length:
+ static int16_t sRefChainLengthCounter = AutoReferenceLimiter::notReferencing;
+ AutoReferenceLimiter
+ refChainLengthLimiter(&sRefChainLengthCounter,
+ MAX_SVG_CLIP_PATH_REFERENCE_CHAIN_LENGTH);
+ if (!refChainLengthLimiter.Reference()) {
+ return false; // Reference chain is too long!
+ }
+
+ // And to prevent reference loops we check that this clipPath only appears
+ // once in the reference chain (if any) that we're currently processing:
+ AutoReferenceLimiter refLoopDetector(&mReferencing, 1);
+ if (!refLoopDetector.Reference()) {
+ return true; // Reference loop!
+ }
+
+ gfxMatrix matrix = GetClipPathTransform(aClippedFrame);
+ if (!matrix.Invert()) {
+ return false;
+ }
+ gfxPoint point = matrix.Transform(aPoint);
+
+ // clipPath elements can themselves be clipped by a different clip path. In
+ // that case the other clip path further clips away the element that is being
+ // clipped by the original clipPath. If this clipPath is being clipped by a
+ // different clip path we need to check if it prevents the original element
+ // from recieving events at aPoint:
+ nsSVGClipPathFrame *clipPathFrame =
+ nsSVGEffects::GetEffectProperties(this).GetClipPathFrame(nullptr);
+ if (clipPathFrame &&
+ !clipPathFrame->PointIsInsideClipPath(aClippedFrame, aPoint)) {
+ return false;
+ }
+
+ for (nsIFrame* kid = mFrames.FirstChild(); kid;
+ kid = kid->GetNextSibling()) {
+ nsISVGChildFrame* SVGFrame = do_QueryFrame(kid);
+ if (SVGFrame) {
+ gfxPoint pointForChild = point;
+ gfxMatrix m = static_cast<nsSVGElement*>(kid->GetContent())->
+ PrependLocalTransformsTo(gfxMatrix(), eUserSpaceToParent);
+ if (!m.IsIdentity()) {
+ if (!m.Invert()) {
+ return false;
+ }
+ pointForChild = m.Transform(point);
+ }
+ if (SVGFrame->GetFrameForPoint(pointForChild)) {
+ return true;
+ }
+ }
+ }
+ return false;
+}
+
+bool
+nsSVGClipPathFrame::IsTrivial(nsISVGChildFrame **aSingleChild)
+{
+ // If the clip path is clipped then it's non-trivial
+ if (nsSVGEffects::GetEffectProperties(this).GetClipPathFrame(nullptr))
+ return false;
+
+ if (aSingleChild) {
+ *aSingleChild = nullptr;
+ }
+
+ nsISVGChildFrame *foundChild = nullptr;
+
+ for (nsIFrame* kid = mFrames.FirstChild(); kid;
+ kid = kid->GetNextSibling()) {
+ nsISVGChildFrame *svgChild = do_QueryFrame(kid);
+ if (svgChild) {
+ // We consider a non-trivial clipPath to be one containing
+ // either more than one svg child and/or a svg container
+ if (foundChild || svgChild->IsDisplayContainer())
+ return false;
+
+ // or where the child is itself clipped
+ if (nsSVGEffects::GetEffectProperties(kid).GetClipPathFrame(nullptr))
+ return false;
+
+ foundChild = svgChild;
+ }
+ }
+ if (aSingleChild) {
+ *aSingleChild = foundChild;
+ }
+ return true;
+}
+
+bool
+nsSVGClipPathFrame::IsValid()
+{
+ // A clipPath can reference another clipPath. We re-enter this method for
+ // each clipPath in a reference chain, so here we limit chain length:
+ static int16_t sRefChainLengthCounter = AutoReferenceLimiter::notReferencing;
+ AutoReferenceLimiter
+ refChainLengthLimiter(&sRefChainLengthCounter,
+ MAX_SVG_CLIP_PATH_REFERENCE_CHAIN_LENGTH);
+ if (!refChainLengthLimiter.Reference()) {
+ return false; // Reference chain is too long!
+ }
+
+ // And to prevent reference loops we check that this clipPath only appears
+ // once in the reference chain (if any) that we're currently processing:
+ AutoReferenceLimiter refLoopDetector(&mReferencing, 1);
+ if (!refLoopDetector.Reference()) {
+ return false; // Reference loop!
+ }
+
+ bool isOK = true;
+ nsSVGEffects::GetEffectProperties(this).GetClipPathFrame(&isOK);
+ if (!isOK) {
+ return false;
+ }
+
+ for (nsIFrame* kid = mFrames.FirstChild(); kid;
+ kid = kid->GetNextSibling()) {
+
+ nsIAtom* kidType = kid->GetType();
+
+ if (kidType == nsGkAtoms::svgUseFrame) {
+ for (nsIFrame* grandKid : kid->PrincipalChildList()) {
+
+ nsIAtom* grandKidType = grandKid->GetType();
+
+ if (grandKidType != nsGkAtoms::svgPathGeometryFrame &&
+ grandKidType != nsGkAtoms::svgTextFrame) {
+ return false;
+ }
+ }
+ continue;
+ }
+
+ if (kidType != nsGkAtoms::svgPathGeometryFrame &&
+ kidType != nsGkAtoms::svgTextFrame) {
+ return false;
+ }
+ }
+
+ return true;
+}
+
+nsresult
+nsSVGClipPathFrame::AttributeChanged(int32_t aNameSpaceID,
+ nsIAtom* aAttribute,
+ int32_t aModType)
+{
+ if (aNameSpaceID == kNameSpaceID_None) {
+ if (aAttribute == nsGkAtoms::transform) {
+ nsSVGEffects::InvalidateDirectRenderingObservers(this);
+ nsSVGUtils::NotifyChildrenOfSVGChange(this,
+ nsISVGChildFrame::TRANSFORM_CHANGED);
+ }
+ if (aAttribute == nsGkAtoms::clipPathUnits) {
+ nsSVGEffects::InvalidateDirectRenderingObservers(this);
+ }
+ }
+
+ return nsSVGContainerFrame::AttributeChanged(aNameSpaceID,
+ aAttribute, aModType);
+}
+
+void
+nsSVGClipPathFrame::Init(nsIContent* aContent,
+ nsContainerFrame* aParent,
+ nsIFrame* aPrevInFlow)
+{
+ NS_ASSERTION(aContent->IsSVGElement(nsGkAtoms::clipPath),
+ "Content is not an SVG clipPath!");
+
+ AddStateBits(NS_STATE_SVG_CLIPPATH_CHILD);
+ nsSVGContainerFrame::Init(aContent, aParent, aPrevInFlow);
+}
+
+nsIAtom *
+nsSVGClipPathFrame::GetType() const
+{
+ return nsGkAtoms::svgClipPathFrame;
+}
+
+gfxMatrix
+nsSVGClipPathFrame::GetCanvasTM()
+{
+ return mMatrixForChildren;
+}
+
+gfxMatrix
+nsSVGClipPathFrame::GetClipPathTransform(nsIFrame* aClippedFrame)
+{
+ SVGClipPathElement *content = static_cast<SVGClipPathElement*>(mContent);
+
+ gfxMatrix tm = content->PrependLocalTransformsTo(gfxMatrix());
+
+ nsSVGEnum* clipPathUnits =
+ &content->mEnumAttributes[SVGClipPathElement::CLIPPATHUNITS];
+
+ return nsSVGUtils::AdjustMatrixForUnits(tm, clipPathUnits, aClippedFrame);
+}
+
+SVGBBox
+nsSVGClipPathFrame::GetBBoxForClipPathFrame(const SVGBBox &aBBox,
+ const gfxMatrix &aMatrix)
+{
+ nsIContent* node = GetContent()->GetFirstChild();
+ SVGBBox unionBBox, tmpBBox;
+ for (; node; node = node->GetNextSibling()) {
+ nsIFrame *frame =
+ static_cast<nsSVGElement*>(node)->GetPrimaryFrame();
+ if (frame) {
+ nsISVGChildFrame *svg = do_QueryFrame(frame);
+ if (svg) {
+ tmpBBox = svg->GetBBoxContribution(mozilla::gfx::ToMatrix(aMatrix),
+ nsSVGUtils::eBBoxIncludeFill);
+ nsSVGEffects::EffectProperties effectProperties =
+ nsSVGEffects::GetEffectProperties(frame);
+ bool isOK = true;
+ nsSVGClipPathFrame *clipPathFrame =
+ effectProperties.GetClipPathFrame(&isOK);
+ if (clipPathFrame && isOK) {
+ tmpBBox = clipPathFrame->GetBBoxForClipPathFrame(tmpBBox, aMatrix);
+ }
+ tmpBBox.Intersect(aBBox);
+ unionBBox.UnionEdges(tmpBBox);
+ }
+ }
+ }
+ nsSVGEffects::EffectProperties props =
+ nsSVGEffects::GetEffectProperties(this);
+ if (props.mClipPath) {
+ bool isOK = true;
+ nsSVGClipPathFrame *clipPathFrame = props.GetClipPathFrame(&isOK);
+ if (clipPathFrame && isOK) {
+ tmpBBox = clipPathFrame->GetBBoxForClipPathFrame(aBBox, aMatrix);
+ unionBBox.Intersect(tmpBBox);
+ } else if (!isOK) {
+ unionBBox = SVGBBox();
+ }
+ }
+ return unionBBox;
+}
diff --git a/layout/svg/nsSVGClipPathFrame.h b/layout/svg/nsSVGClipPathFrame.h
new file mode 100644
index 0000000000..42a8d16ffd
--- /dev/null
+++ b/layout/svg/nsSVGClipPathFrame.h
@@ -0,0 +1,160 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef __NS_SVGCLIPPATHFRAME_H__
+#define __NS_SVGCLIPPATHFRAME_H__
+
+#include "AutoReferenceLimiter.h"
+#include "gfxMatrix.h"
+#include "mozilla/Attributes.h"
+#include "nsSVGContainerFrame.h"
+#include "nsSVGUtils.h"
+
+class gfxContext;
+class nsISVGChildFrame;
+
+class nsSVGClipPathFrame : public nsSVGContainerFrame
+{
+ friend nsIFrame*
+ NS_NewSVGClipPathFrame(nsIPresShell* aPresShell, nsStyleContext* aContext);
+
+ typedef mozilla::gfx::Matrix Matrix;
+ typedef mozilla::gfx::SourceSurface SourceSurface;
+ typedef mozilla::image::DrawResult DrawResult;
+
+protected:
+ explicit nsSVGClipPathFrame(nsStyleContext* aContext)
+ : nsSVGContainerFrame(aContext)
+ , mReferencing(mozilla::AutoReferenceLimiter::notReferencing)
+ {
+ AddStateBits(NS_FRAME_IS_NONDISPLAY);
+ }
+
+public:
+ NS_DECL_FRAMEARENA_HELPERS
+
+ // nsIFrame methods:
+ virtual void BuildDisplayList(nsDisplayListBuilder* aBuilder,
+ const nsRect& aDirtyRect,
+ const nsDisplayListSet& aLists) override {}
+
+ // nsSVGClipPathFrame methods:
+
+ /**
+ * Applies the clipPath by pushing a clip path onto the DrawTarget.
+ *
+ * This method must only be used if IsTrivial() returns true, otherwise use
+ * GetClipMask.
+ *
+ * @param aContext The context that the clip path is to be applied to.
+ * @param aClippedFrame The/an nsIFrame of the element that references this
+ * clipPath that is currently being processed.
+ * @param aMatrix The transform from aClippedFrame's user space to aContext's
+ * current transform.
+ */
+ void ApplyClipPath(gfxContext& aContext,
+ nsIFrame* aClippedFrame,
+ const gfxMatrix &aMatrix);
+
+ /**
+ * Returns an alpha mask surface containing the clipping geometry.
+ *
+ * This method must only be used if IsTrivial() returns false, otherwise use
+ * ApplyClipPath.
+ *
+ * @param aReferenceContext Used to determine the backend for and size of the
+ * returned SourceSurface, the size being limited to the device space clip
+ * extents on the context.
+ * @param aClippedFrame The/an nsIFrame of the element that references this
+ * clipPath that is currently being processed.
+ * @param aMatrix The transform from aClippedFrame's user space to aContext's
+ * current transform.
+ * @param [out] aMaskTransform The transform to use with the returned
+ * surface.
+ * @param [in, optional] aExtraMask An extra surface that the returned
+ * surface should be masked with.
+ * @param [in, optional] aExtraMasksTransform The transform to use with
+ * aExtraMask. Should be passed when aExtraMask is passed.
+ * @param [out, optional] aResult returns the result of drawing action.
+ */
+ already_AddRefed<SourceSurface>
+ GetClipMask(gfxContext& aReferenceContext, nsIFrame* aClippedFrame,
+ const gfxMatrix& aMatrix, Matrix* aMaskTransform,
+ SourceSurface* aExtraMask = nullptr,
+ const Matrix& aExtraMasksTransform = Matrix(),
+ DrawResult* aResult = nullptr);
+
+ /**
+ * aPoint is expected to be in aClippedFrame's SVG user space.
+ */
+ bool PointIsInsideClipPath(nsIFrame* aClippedFrame, const gfxPoint &aPoint);
+
+ // Check if this clipPath is made up of more than one geometry object.
+ // If so, the clipping API in cairo isn't enough and we need to use
+ // mask based clipping.
+ bool IsTrivial(nsISVGChildFrame **aSingleChild = nullptr);
+
+ bool IsValid();
+
+ // nsIFrame interface:
+ virtual nsresult AttributeChanged(int32_t aNameSpaceID,
+ nsIAtom* aAttribute,
+ int32_t aModType) override;
+
+ virtual void Init(nsIContent* aContent,
+ nsContainerFrame* aParent,
+ nsIFrame* aPrevInFlow) override;
+
+ /**
+ * Get the "type" of the frame
+ *
+ * @see nsGkAtoms::svgClipPathFrame
+ */
+ virtual nsIAtom* GetType() const override;
+
+#ifdef DEBUG_FRAME_DUMP
+ virtual nsresult GetFrameName(nsAString& aResult) const override
+ {
+ return MakeFrameName(NS_LITERAL_STRING("SVGClipPath"), aResult);
+ }
+#endif
+
+ SVGBBox
+ GetBBoxForClipPathFrame(const SVGBBox &aBBox, const gfxMatrix &aMatrix);
+
+ /**
+ * If the clipPath element transforms its children due to
+ * clipPathUnits="objectBoundingBox" being set on it and/or due to the
+ * 'transform' attribute being set on it, this function returns the resulting
+ * transform.
+ */
+ gfxMatrix GetClipPathTransform(nsIFrame* aClippedFrame);
+
+private:
+
+ // nsSVGContainerFrame methods:
+ virtual gfxMatrix GetCanvasTM() override;
+
+ // Set, during a GetClipMask() call, to the transform that still needs to be
+ // concatenated to the transform of the DrawTarget that was passed to
+ // GetClipMask in order to establish the coordinate space that the clipPath
+ // establishes for its contents (i.e. including applying 'clipPathUnits' and
+ // any 'transform' attribute set on the clipPath) specifically for clipping
+ // the frame that was passed to GetClipMask at that moment in time. This is
+ // set so that if our GetCanvasTM method is called while GetClipMask is
+ // painting its children, the returned matrix will include the transforms
+ // that should be used when creating the mask for the frame passed to
+ // GetClipMask.
+ //
+ // Note: The removal of GetCanvasTM is nearly complete, so our GetCanvasTM
+ // may not even be called soon/any more.
+ gfxMatrix mMatrixForChildren;
+
+ // Flag used by AutoReferenceLimiter while we're processing an instance of
+ // this class to protect against (break) reference loops.
+ int16_t mReferencing;
+};
+
+#endif
diff --git a/layout/svg/nsSVGContainerFrame.cpp b/layout/svg/nsSVGContainerFrame.cpp
new file mode 100644
index 0000000000..750dcc9da0
--- /dev/null
+++ b/layout/svg/nsSVGContainerFrame.cpp
@@ -0,0 +1,445 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+// Main header first:
+#include "nsSVGContainerFrame.h"
+
+// Keep others in (case-insensitive) order:
+#include "mozilla/RestyleManagerHandle.h"
+#include "mozilla/RestyleManagerHandleInlines.h"
+#include "nsCSSFrameConstructor.h"
+#include "nsSVGEffects.h"
+#include "nsSVGElement.h"
+#include "nsSVGUtils.h"
+#include "nsSVGAnimatedTransformList.h"
+#include "SVGTextFrame.h"
+
+using namespace mozilla;
+
+NS_QUERYFRAME_HEAD(nsSVGContainerFrame)
+ NS_QUERYFRAME_ENTRY(nsSVGContainerFrame)
+NS_QUERYFRAME_TAIL_INHERITING(nsContainerFrame)
+
+NS_QUERYFRAME_HEAD(nsSVGDisplayContainerFrame)
+ NS_QUERYFRAME_ENTRY(nsSVGDisplayContainerFrame)
+ NS_QUERYFRAME_ENTRY(nsISVGChildFrame)
+NS_QUERYFRAME_TAIL_INHERITING(nsSVGContainerFrame)
+
+nsIFrame*
+NS_NewSVGContainerFrame(nsIPresShell* aPresShell,
+ nsStyleContext* aContext)
+{
+ nsIFrame *frame = new (aPresShell) nsSVGContainerFrame(aContext);
+ // If we were called directly, then the frame is for a <defs> or
+ // an unknown element type. In both cases we prevent the content
+ // from displaying directly.
+ frame->AddStateBits(NS_FRAME_IS_NONDISPLAY);
+ return frame;
+}
+
+NS_IMPL_FRAMEARENA_HELPERS(nsSVGContainerFrame)
+NS_IMPL_FRAMEARENA_HELPERS(nsSVGDisplayContainerFrame)
+
+void
+nsSVGContainerFrame::AppendFrames(ChildListID aListID,
+ nsFrameList& aFrameList)
+{
+ InsertFrames(aListID, mFrames.LastChild(), aFrameList);
+}
+
+void
+nsSVGContainerFrame::InsertFrames(ChildListID aListID,
+ nsIFrame* aPrevFrame,
+ nsFrameList& aFrameList)
+{
+ NS_ASSERTION(aListID == kPrincipalList, "unexpected child list");
+ NS_ASSERTION(!aPrevFrame || aPrevFrame->GetParent() == this,
+ "inserting after sibling frame with different parent");
+
+ mFrames.InsertFrames(this, aPrevFrame, aFrameList);
+}
+
+void
+nsSVGContainerFrame::RemoveFrame(ChildListID aListID,
+ nsIFrame* aOldFrame)
+{
+ NS_ASSERTION(aListID == kPrincipalList, "unexpected child list");
+
+ mFrames.DestroyFrame(aOldFrame);
+}
+
+bool
+nsSVGContainerFrame::ComputeCustomOverflow(nsOverflowAreas& aOverflowAreas)
+{
+ if (mState & NS_FRAME_IS_NONDISPLAY) {
+ // We don't maintain overflow rects.
+ // XXX It would have be better if the restyle request hadn't even happened.
+ return false;
+ }
+ return nsContainerFrame::ComputeCustomOverflow(aOverflowAreas);
+}
+
+/**
+ * Traverses a frame tree, marking any SVGTextFrame frames as dirty
+ * and calling InvalidateRenderingObservers() on it.
+ *
+ * The reason that this helper exists is because SVGTextFrame is special.
+ * None of the other SVG frames ever need to be reflowed when they have the
+ * NS_FRAME_IS_NONDISPLAY bit set on them because their PaintSVG methods
+ * (and those of any containers that they can validly be contained within) do
+ * not make use of mRect or overflow rects. "em" lengths, etc., are resolved
+ * as those elements are painted.
+ *
+ * SVGTextFrame is different because its anonymous block and inline frames
+ * need to be reflowed in order to get the correct metrics when things like
+ * inherited font-size of an ancestor changes, or a delayed webfont loads and
+ * applies.
+ *
+ * We assume that any change that requires the anonymous kid of an
+ * SVGTextFrame to reflow will result in an NS_FRAME_IS_DIRTY reflow. When
+ * that reflow reaches an NS_FRAME_IS_NONDISPLAY frame it would normally
+ * stop, but this helper looks for any SVGTextFrame descendants of such
+ * frames and marks them NS_FRAME_IS_DIRTY so that the next time that they are
+ * painted their anonymous kid will first get the necessary reflow.
+ */
+/* static */ void
+nsSVGContainerFrame::ReflowSVGNonDisplayText(nsIFrame* aContainer)
+{
+ NS_ASSERTION(aContainer->GetStateBits() & NS_FRAME_IS_DIRTY,
+ "expected aContainer to be NS_FRAME_IS_DIRTY");
+ NS_ASSERTION((aContainer->GetStateBits() & NS_FRAME_IS_NONDISPLAY) ||
+ !aContainer->IsFrameOfType(nsIFrame::eSVG),
+ "it is wasteful to call ReflowSVGNonDisplayText on a container "
+ "frame that is not NS_FRAME_IS_NONDISPLAY");
+ for (nsIFrame* kid : aContainer->PrincipalChildList()) {
+ nsIAtom* type = kid->GetType();
+ if (type == nsGkAtoms::svgTextFrame) {
+ static_cast<SVGTextFrame*>(kid)->ReflowSVGNonDisplayText();
+ } else {
+ if (kid->IsFrameOfType(nsIFrame::eSVG | nsIFrame::eSVGContainer) ||
+ type == nsGkAtoms::svgForeignObjectFrame ||
+ !kid->IsFrameOfType(nsIFrame::eSVG)) {
+ ReflowSVGNonDisplayText(kid);
+ }
+ }
+ }
+}
+
+void
+nsSVGDisplayContainerFrame::Init(nsIContent* aContent,
+ nsContainerFrame* aParent,
+ nsIFrame* aPrevInFlow)
+{
+ if (!(GetStateBits() & NS_STATE_IS_OUTER_SVG)) {
+ AddStateBits(aParent->GetStateBits() & NS_STATE_SVG_CLIPPATH_CHILD);
+ }
+ nsSVGContainerFrame::Init(aContent, aParent, aPrevInFlow);
+}
+
+void
+nsSVGDisplayContainerFrame::BuildDisplayList(nsDisplayListBuilder* aBuilder,
+ const nsRect& aDirtyRect,
+ const nsDisplayListSet& aLists)
+{
+ // mContent could be a XUL element so check for an SVG element before casting
+ if (mContent->IsSVGElement() &&
+ !static_cast<const nsSVGElement*>(mContent)->HasValidDimensions()) {
+ return;
+ }
+ DisplayOutline(aBuilder, aLists);
+ return BuildDisplayListForNonBlockChildren(aBuilder, aDirtyRect, aLists);
+}
+
+void
+nsSVGDisplayContainerFrame::InsertFrames(ChildListID aListID,
+ nsIFrame* aPrevFrame,
+ nsFrameList& aFrameList)
+{
+ // memorize first old frame after insertion point
+ // XXXbz once again, this would work a lot better if the nsIFrame
+ // methods returned framelist iterators....
+ nsIFrame* nextFrame = aPrevFrame ?
+ aPrevFrame->GetNextSibling() : GetChildList(aListID).FirstChild();
+ nsIFrame* firstNewFrame = aFrameList.FirstChild();
+
+ // Insert the new frames
+ nsSVGContainerFrame::InsertFrames(aListID, aPrevFrame, aFrameList);
+
+ // If we are not a non-display SVG frame and we do not have a bounds update
+ // pending, then we need to schedule one for our new children:
+ if (!(GetStateBits() &
+ (NS_FRAME_IS_DIRTY | NS_FRAME_HAS_DIRTY_CHILDREN |
+ NS_FRAME_IS_NONDISPLAY))) {
+ for (nsIFrame* kid = firstNewFrame; kid != nextFrame;
+ kid = kid->GetNextSibling()) {
+ nsISVGChildFrame* SVGFrame = do_QueryFrame(kid);
+ if (SVGFrame) {
+ MOZ_ASSERT(!(kid->GetStateBits() & NS_FRAME_IS_NONDISPLAY),
+ "Check for this explicitly in the |if|, then");
+ bool isFirstReflow = (kid->GetStateBits() & NS_FRAME_FIRST_REFLOW);
+ // Remove bits so that ScheduleBoundsUpdate will work:
+ kid->RemoveStateBits(NS_FRAME_FIRST_REFLOW | NS_FRAME_IS_DIRTY |
+ NS_FRAME_HAS_DIRTY_CHILDREN);
+ // No need to invalidate the new kid's old bounds, so we just use
+ // nsSVGUtils::ScheduleBoundsUpdate.
+ nsSVGUtils::ScheduleReflowSVG(kid);
+ if (isFirstReflow) {
+ // Add back the NS_FRAME_FIRST_REFLOW bit:
+ kid->AddStateBits(NS_FRAME_FIRST_REFLOW);
+ }
+ }
+ }
+ }
+}
+
+void
+nsSVGDisplayContainerFrame::RemoveFrame(ChildListID aListID,
+ nsIFrame* aOldFrame)
+{
+ nsSVGEffects::InvalidateRenderingObservers(aOldFrame);
+
+ // nsSVGContainerFrame::RemoveFrame doesn't call down into
+ // nsContainerFrame::RemoveFrame, so it doesn't call FrameNeedsReflow. We
+ // need to schedule a repaint and schedule an update to our overflow rects.
+ SchedulePaint();
+ PresContext()->RestyleManager()->PostRestyleEvent(
+ mContent->AsElement(), nsRestyleHint(0), nsChangeHint_UpdateOverflow);
+
+ nsSVGContainerFrame::RemoveFrame(aListID, aOldFrame);
+
+ if (!(GetStateBits() & (NS_FRAME_IS_NONDISPLAY | NS_STATE_IS_OUTER_SVG))) {
+ nsSVGUtils::NotifyAncestorsOfFilterRegionChange(this);
+ }
+}
+
+bool
+nsSVGDisplayContainerFrame::IsSVGTransformed(gfx::Matrix *aOwnTransform,
+ gfx::Matrix *aFromParentTransform) const
+{
+ bool foundTransform = false;
+
+ // Check if our parent has children-only transforms:
+ nsIFrame *parent = GetParent();
+ if (parent &&
+ parent->IsFrameOfType(nsIFrame::eSVG | nsIFrame::eSVGContainer)) {
+ foundTransform = static_cast<nsSVGContainerFrame*>(parent)->
+ HasChildrenOnlyTransform(aFromParentTransform);
+ }
+
+ // mContent could be a XUL element so check for an SVG element before casting
+ if (mContent->IsSVGElement()) {
+ nsSVGElement *content = static_cast<nsSVGElement*>(mContent);
+ nsSVGAnimatedTransformList* transformList =
+ content->GetAnimatedTransformList();
+ if ((transformList && transformList->HasTransform()) ||
+ content->GetAnimateMotionTransform()) {
+ if (aOwnTransform) {
+ *aOwnTransform = gfx::ToMatrix(
+ content->PrependLocalTransformsTo(
+ gfxMatrix(), eUserSpaceToParent));
+ }
+ foundTransform = true;
+ }
+ }
+ return foundTransform;
+}
+
+//----------------------------------------------------------------------
+// nsISVGChildFrame methods
+
+DrawResult
+nsSVGDisplayContainerFrame::PaintSVG(gfxContext& aContext,
+ const gfxMatrix& aTransform,
+ const nsIntRect *aDirtyRect)
+{
+ NS_ASSERTION(!NS_SVGDisplayListPaintingEnabled() ||
+ (mState & NS_FRAME_IS_NONDISPLAY) ||
+ PresContext()->IsGlyph(),
+ "If display lists are enabled, only painting of non-display "
+ "SVG should take this code path");
+
+ if (StyleEffects()->mOpacity == 0.0) {
+ return DrawResult::SUCCESS;
+ }
+
+ gfxMatrix matrix = aTransform;
+ if (GetContent()->IsSVGElement()) { // must check before cast
+ matrix = static_cast<const nsSVGElement*>(GetContent())->
+ PrependLocalTransformsTo(matrix, eChildToUserSpace);
+ if (matrix.IsSingular()) {
+ return DrawResult::SUCCESS;
+ }
+ }
+
+ DrawResult result = DrawResult::SUCCESS;
+ for (nsIFrame* kid = mFrames.FirstChild(); kid;
+ kid = kid->GetNextSibling()) {
+ gfxMatrix m = matrix;
+ // PaintFrameWithEffects() expects the transform that is passed to it to
+ // include the transform to the passed frame's user space, so add it:
+ const nsIContent* content = kid->GetContent();
+ if (content->IsSVGElement()) { // must check before cast
+ const nsSVGElement* element = static_cast<const nsSVGElement*>(content);
+ if (!element->HasValidDimensions()) {
+ continue; // nothing to paint for kid
+ }
+ m = element->PrependLocalTransformsTo(m, eUserSpaceToParent);
+ if (m.IsSingular()) {
+ continue;
+ }
+ }
+ result = nsSVGUtils::PaintFrameWithEffects(kid, aContext, m, aDirtyRect);
+ if (result != DrawResult::SUCCESS) {
+ return result;
+ }
+ }
+
+ return result;
+}
+
+nsIFrame*
+nsSVGDisplayContainerFrame::GetFrameForPoint(const gfxPoint& aPoint)
+{
+ NS_ASSERTION(!NS_SVGDisplayListHitTestingEnabled() ||
+ (mState & NS_FRAME_IS_NONDISPLAY),
+ "If display lists are enabled, only hit-testing of a "
+ "clipPath's contents should take this code path");
+ return nsSVGUtils::HitTestChildren(this, aPoint);
+}
+
+nsRect
+nsSVGDisplayContainerFrame::GetCoveredRegion()
+{
+ return nsSVGUtils::GetCoveredRegion(mFrames);
+}
+
+void
+nsSVGDisplayContainerFrame::ReflowSVG()
+{
+ NS_ASSERTION(nsSVGUtils::OuterSVGIsCallingReflowSVG(this),
+ "This call is probably a wasteful mistake");
+
+ MOZ_ASSERT(!(GetStateBits() & NS_FRAME_IS_NONDISPLAY),
+ "ReflowSVG mechanism not designed for this");
+
+ MOZ_ASSERT(GetType() != nsGkAtoms::svgOuterSVGFrame,
+ "Do not call on outer-<svg>");
+
+ if (!nsSVGUtils::NeedsReflowSVG(this)) {
+ return;
+ }
+
+ // If the NS_FRAME_FIRST_REFLOW bit has been removed from our parent frame,
+ // then our outer-<svg> has previously had its initial reflow. In that case
+ // we need to make sure that that bit has been removed from ourself _before_
+ // recursing over our children to ensure that they know too. Otherwise, we
+ // need to remove it _after_ recursing over our children so that they know
+ // the initial reflow is currently underway.
+
+ bool isFirstReflow = (mState & NS_FRAME_FIRST_REFLOW);
+
+ bool outerSVGHasHadFirstReflow =
+ (GetParent()->GetStateBits() & NS_FRAME_FIRST_REFLOW) == 0;
+
+ if (outerSVGHasHadFirstReflow) {
+ mState &= ~NS_FRAME_FIRST_REFLOW; // tell our children
+ }
+
+ nsOverflowAreas overflowRects;
+
+ for (nsIFrame* kid = mFrames.FirstChild(); kid;
+ kid = kid->GetNextSibling()) {
+ nsISVGChildFrame* SVGFrame = do_QueryFrame(kid);
+ if (SVGFrame) {
+ MOZ_ASSERT(!(kid->GetStateBits() & NS_FRAME_IS_NONDISPLAY),
+ "Check for this explicitly in the |if|, then");
+ kid->AddStateBits(mState & NS_FRAME_IS_DIRTY);
+ SVGFrame->ReflowSVG();
+
+ // We build up our child frame overflows here instead of using
+ // nsLayoutUtils::UnionChildOverflow since SVG frame's all use the same
+ // frame list, and we're iterating over that list now anyway.
+ ConsiderChildOverflow(overflowRects, kid);
+ } else {
+ // Inside a non-display container frame, we might have some
+ // SVGTextFrames. We need to cause those to get reflowed in
+ // case they are the target of a rendering observer.
+ NS_ASSERTION(kid->GetStateBits() & NS_FRAME_IS_NONDISPLAY,
+ "expected kid to be a NS_FRAME_IS_NONDISPLAY frame");
+ if (kid->GetStateBits() & NS_FRAME_IS_DIRTY) {
+ nsSVGContainerFrame* container = do_QueryFrame(kid);
+ if (container && container->GetContent()->IsSVGElement()) {
+ ReflowSVGNonDisplayText(container);
+ }
+ }
+ }
+ }
+
+ // <svg> can create an SVG viewport with an offset due to its
+ // x/y/width/height attributes, and <use> can introduce an offset with an
+ // empty mRect (any width/height is copied to an anonymous <svg> child).
+ // Other than that containers should not set mRect since all other offsets
+ // come from transforms, which are accounted for by nsDisplayTransform.
+ // Note that we rely on |overflow:visible| to allow display list items to be
+ // created for our children.
+ MOZ_ASSERT(mContent->IsSVGElement(nsGkAtoms::svg) ||
+ (mContent->IsSVGElement(nsGkAtoms::use) &&
+ mRect.Size() == nsSize(0,0)) ||
+ mRect.IsEqualEdges(nsRect()),
+ "Only inner-<svg>/<use> is expected to have mRect set");
+
+ if (isFirstReflow) {
+ // Make sure we have our filter property (if any) before calling
+ // FinishAndStoreOverflow (subsequent filter changes are handled off
+ // nsChangeHint_UpdateEffects):
+ nsSVGEffects::UpdateEffects(this);
+ }
+
+ FinishAndStoreOverflow(overflowRects, mRect.Size());
+
+ // Remove state bits after FinishAndStoreOverflow so that it doesn't
+ // invalidate on first reflow:
+ mState &= ~(NS_FRAME_FIRST_REFLOW | NS_FRAME_IS_DIRTY |
+ NS_FRAME_HAS_DIRTY_CHILDREN);
+}
+
+void
+nsSVGDisplayContainerFrame::NotifySVGChanged(uint32_t aFlags)
+{
+ MOZ_ASSERT(aFlags & (TRANSFORM_CHANGED | COORD_CONTEXT_CHANGED),
+ "Invalidation logic may need adjusting");
+
+ nsSVGUtils::NotifyChildrenOfSVGChange(this, aFlags);
+}
+
+SVGBBox
+nsSVGDisplayContainerFrame::GetBBoxContribution(
+ const Matrix &aToBBoxUserspace,
+ uint32_t aFlags)
+{
+ SVGBBox bboxUnion;
+
+ nsIFrame* kid = mFrames.FirstChild();
+ while (kid) {
+ nsIContent *content = kid->GetContent();
+ nsISVGChildFrame* svgKid = do_QueryFrame(kid);
+ // content could be a XUL element so check for an SVG element before casting
+ if (svgKid && (!content->IsSVGElement() ||
+ static_cast<const nsSVGElement*>(content)->HasValidDimensions())) {
+
+ gfxMatrix transform = gfx::ThebesMatrix(aToBBoxUserspace);
+ if (content->IsSVGElement()) {
+ transform = static_cast<nsSVGElement*>(content)->
+ PrependLocalTransformsTo(transform);
+ }
+ // We need to include zero width/height vertical/horizontal lines, so we have
+ // to use UnionEdges.
+ bboxUnion.UnionEdges(svgKid->GetBBoxContribution(gfx::ToMatrix(transform), aFlags));
+ }
+ kid = kid->GetNextSibling();
+ }
+
+ return bboxUnion;
+}
diff --git a/layout/svg/nsSVGContainerFrame.h b/layout/svg/nsSVGContainerFrame.h
new file mode 100644
index 0000000000..f4a01e1559
--- /dev/null
+++ b/layout/svg/nsSVGContainerFrame.h
@@ -0,0 +1,155 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef NS_SVGCONTAINERFRAME_H
+#define NS_SVGCONTAINERFRAME_H
+
+#include "mozilla/Attributes.h"
+#include "nsContainerFrame.h"
+#include "nsFrame.h"
+#include "nsIFrame.h"
+#include "nsISVGChildFrame.h"
+#include "nsQueryFrame.h"
+#include "nsRect.h"
+#include "nsSVGUtils.h"
+
+class gfxContext;
+class nsFrameList;
+class nsIContent;
+class nsIPresShell;
+class nsStyleContext;
+
+struct nsRect;
+
+/**
+ * Base class for SVG container frames. Frame sub-classes that do not
+ * display their contents directly (such as the frames for <marker> or
+ * <pattern>) just inherit this class. Frame sub-classes that do or can
+ * display their contents directly (such as the frames for inner-<svg> or
+ * <g>) inherit our nsDisplayContainerFrame sub-class.
+ *
+ * *** WARNING ***
+ *
+ * Do *not* blindly cast to SVG element types in this class's methods (see the
+ * warning comment for nsSVGDisplayContainerFrame below).
+ */
+class nsSVGContainerFrame : public nsContainerFrame
+{
+ friend nsIFrame* NS_NewSVGContainerFrame(nsIPresShell* aPresShell,
+ nsStyleContext* aContext);
+protected:
+ explicit nsSVGContainerFrame(nsStyleContext* aContext)
+ : nsContainerFrame(aContext)
+ {
+ AddStateBits(NS_FRAME_SVG_LAYOUT);
+ }
+
+public:
+ NS_DECL_QUERYFRAME_TARGET(nsSVGContainerFrame)
+ NS_DECL_QUERYFRAME
+ NS_DECL_FRAMEARENA_HELPERS
+
+ // Returns the transform to our gfxContext (to device pixels, not CSS px)
+ virtual gfxMatrix GetCanvasTM() {
+ return gfxMatrix();
+ }
+
+ /**
+ * Returns true if the frame's content has a transform that applies only to
+ * its children, and not to the frame itself. For example, an implicit
+ * transform introduced by a 'viewBox' attribute, or an explicit transform
+ * due to a root-<svg> having its currentScale/currentTransform properties
+ * set. If aTransform is non-null, then it will be set to the transform.
+ */
+ virtual bool HasChildrenOnlyTransform(Matrix *aTransform) const {
+ return false;
+ }
+
+ // nsIFrame:
+ virtual void AppendFrames(ChildListID aListID,
+ nsFrameList& aFrameList) override;
+ virtual void InsertFrames(ChildListID aListID,
+ nsIFrame* aPrevFrame,
+ nsFrameList& aFrameList) override;
+ virtual void RemoveFrame(ChildListID aListID,
+ nsIFrame* aOldFrame) override;
+
+ virtual bool IsFrameOfType(uint32_t aFlags) const override
+ {
+ return nsContainerFrame::IsFrameOfType(
+ aFlags & ~(nsIFrame::eSVG | nsIFrame::eSVGContainer));
+ }
+
+ virtual void BuildDisplayList(nsDisplayListBuilder* aBuilder,
+ const nsRect& aDirtyRect,
+ const nsDisplayListSet& aLists) override {}
+
+ virtual bool ComputeCustomOverflow(nsOverflowAreas& aOverflowAreas) override;
+
+protected:
+ /**
+ * Traverses a frame tree, marking any SVGTextFrame frames as dirty
+ * and calling InvalidateRenderingObservers() on it.
+ */
+ static void ReflowSVGNonDisplayText(nsIFrame* aContainer);
+};
+
+/**
+ * Frame class or base-class for SVG containers that can or do display their
+ * contents directly.
+ *
+ * *** WARNING ***
+ *
+ * This class's methods can *not* assume that mContent points to an instance of
+ * an SVG element class since this class is inherited by
+ * nsSVGGenericContainerFrame which is used for unrecognized elements in the
+ * SVG namespace. Do *not* blindly cast to SVG element types.
+ */
+class nsSVGDisplayContainerFrame : public nsSVGContainerFrame,
+ public nsISVGChildFrame
+{
+protected:
+ explicit nsSVGDisplayContainerFrame(nsStyleContext* aContext)
+ : nsSVGContainerFrame(aContext)
+ {
+ AddStateBits(NS_FRAME_MAY_BE_TRANSFORMED);
+ }
+
+public:
+ NS_DECL_QUERYFRAME_TARGET(nsSVGDisplayContainerFrame)
+ NS_DECL_QUERYFRAME
+ NS_DECL_FRAMEARENA_HELPERS
+
+ // nsIFrame:
+ virtual void InsertFrames(ChildListID aListID,
+ nsIFrame* aPrevFrame,
+ nsFrameList& aFrameList) override;
+ virtual void RemoveFrame(ChildListID aListID,
+ nsIFrame* aOldFrame) override;
+ virtual void Init(nsIContent* aContent,
+ nsContainerFrame* aParent,
+ nsIFrame* aPrevInFlow) override;
+
+ virtual void BuildDisplayList(nsDisplayListBuilder* aBuilder,
+ const nsRect& aDirtyRect,
+ const nsDisplayListSet& aLists) override;
+
+ virtual bool IsSVGTransformed(Matrix *aOwnTransform = nullptr,
+ Matrix *aFromParentTransform = nullptr) const override;
+
+ // nsISVGChildFrame interface:
+ virtual DrawResult PaintSVG(gfxContext& aContext,
+ const gfxMatrix& aTransform,
+ const nsIntRect *aDirtyRect = nullptr) override;
+ virtual nsIFrame* GetFrameForPoint(const gfxPoint& aPoint) override;
+ virtual nsRect GetCoveredRegion() override;
+ virtual void ReflowSVG() override;
+ virtual void NotifySVGChanged(uint32_t aFlags) override;
+ virtual SVGBBox GetBBoxContribution(const Matrix &aToBBoxUserspace,
+ uint32_t aFlags) override;
+ virtual bool IsDisplayContainer() override { return true; }
+};
+
+#endif
diff --git a/layout/svg/nsSVGEffects.cpp b/layout/svg/nsSVGEffects.cpp
new file mode 100644
index 0000000000..eac094a91f
--- /dev/null
+++ b/layout/svg/nsSVGEffects.cpp
@@ -0,0 +1,1001 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+// Main header first:
+#include "nsSVGEffects.h"
+
+// Keep others in (case-insensitive) order:
+#include "mozilla/RestyleManagerHandle.h"
+#include "mozilla/RestyleManagerHandleInlines.h"
+#include "nsCSSFrameConstructor.h"
+#include "nsISupportsImpl.h"
+#include "nsSVGClipPathFrame.h"
+#include "nsSVGPaintServerFrame.h"
+#include "nsSVGPathGeometryElement.h"
+#include "nsSVGFilterFrame.h"
+#include "nsSVGMaskFrame.h"
+#include "nsIReflowCallback.h"
+#include "nsCycleCollectionParticipant.h"
+#include "SVGUseElement.h"
+
+using namespace mozilla;
+using namespace mozilla::dom;
+
+void
+nsSVGRenderingObserver::StartListening()
+{
+ Element* target = GetTarget();
+ if (target) {
+ target->AddMutationObserver(this);
+ }
+}
+
+void
+nsSVGRenderingObserver::StopListening()
+{
+ Element* target = GetTarget();
+
+ if (target) {
+ target->RemoveMutationObserver(this);
+ if (mInObserverList) {
+ nsSVGEffects::RemoveRenderingObserver(target, this);
+ mInObserverList = false;
+ }
+ }
+ NS_ASSERTION(!mInObserverList, "still in an observer list?");
+}
+
+static nsSVGRenderingObserverList *
+GetObserverList(Element *aElement)
+{
+ return static_cast<nsSVGRenderingObserverList*>
+ (aElement->GetProperty(nsGkAtoms::renderingobserverlist));
+}
+
+Element*
+nsSVGRenderingObserver::GetReferencedElement()
+{
+ Element* target = GetTarget();
+#ifdef DEBUG
+ if (target) {
+ nsSVGRenderingObserverList *observerList = GetObserverList(target);
+ bool inObserverList = observerList && observerList->Contains(this);
+ NS_ASSERTION(inObserverList == mInObserverList, "failed to track whether we're in our referenced element's observer list!");
+ } else {
+ NS_ASSERTION(!mInObserverList, "In whose observer list are we, then?");
+ }
+#endif
+ if (target && !mInObserverList) {
+ nsSVGEffects::AddRenderingObserver(target, this);
+ mInObserverList = true;
+ }
+ return target;
+}
+
+nsIFrame*
+nsSVGRenderingObserver::GetReferencedFrame()
+{
+ Element* referencedElement = GetReferencedElement();
+ return referencedElement ? referencedElement->GetPrimaryFrame() : nullptr;
+}
+
+nsIFrame*
+nsSVGRenderingObserver::GetReferencedFrame(nsIAtom* aFrameType, bool* aOK)
+{
+ nsIFrame* frame = GetReferencedFrame();
+ if (frame) {
+ if (frame->GetType() == aFrameType)
+ return frame;
+ if (aOK) {
+ *aOK = false;
+ }
+ }
+ return nullptr;
+}
+
+void
+nsSVGRenderingObserver::InvalidateViaReferencedElement()
+{
+ mInObserverList = false;
+ DoUpdate();
+}
+
+void
+nsSVGRenderingObserver::NotifyEvictedFromRenderingObserverList()
+{
+ mInObserverList = false; // We've been removed from rendering-obs. list.
+ StopListening(); // Remove ourselves from mutation-obs. list.
+}
+
+void
+nsSVGRenderingObserver::AttributeChanged(nsIDocument* aDocument,
+ dom::Element* aElement,
+ int32_t aNameSpaceID,
+ nsIAtom* aAttribute,
+ int32_t aModType,
+ const nsAttrValue* aOldValue)
+{
+ // An attribute belonging to the element that we are observing *or one of its
+ // descendants* has changed.
+ //
+ // In the case of observing a gradient element, say, we want to know if any
+ // of its 'stop' element children change, but we don't actually want to do
+ // anything for changes to SMIL element children, for example. Maybe it's not
+ // worth having logic to optimize for that, but in most cases it could be a
+ // small check?
+ //
+ // XXXjwatt: do we really want to blindly break the link between our
+ // observers and ourselves for all attribute changes? For non-ID changes
+ // surely that is unnecessary.
+
+ DoUpdate();
+}
+
+void
+nsSVGRenderingObserver::ContentAppended(nsIDocument* aDocument,
+ nsIContent* aContainer,
+ nsIContent* aFirstNewContent,
+ int32_t /* unused */)
+{
+ DoUpdate();
+}
+
+void
+nsSVGRenderingObserver::ContentInserted(nsIDocument* aDocument,
+ nsIContent* aContainer,
+ nsIContent* aChild,
+ int32_t /* unused */)
+{
+ DoUpdate();
+}
+
+void
+nsSVGRenderingObserver::ContentRemoved(nsIDocument* aDocument,
+ nsIContent* aContainer,
+ nsIContent* aChild,
+ int32_t aIndexInContainer,
+ nsIContent* aPreviousSibling)
+{
+ DoUpdate();
+}
+
+/**
+ * Note that in the current setup there are two separate observer lists.
+ *
+ * In nsSVGIDRenderingObserver's ctor, the new object adds itself to the
+ * mutation observer list maintained by the referenced element. In this way the
+ * nsSVGIDRenderingObserver is notified if there are any attribute or content
+ * tree changes to the element or any of its *descendants*.
+ *
+ * In nsSVGIDRenderingObserver::GetReferencedElement() the
+ * nsSVGIDRenderingObserver object also adds itself to an
+ * nsSVGRenderingObserverList object belonging to the referenced
+ * element.
+ *
+ * XXX: it would be nice to have a clear and concise executive summary of the
+ * benefits/necessity of maintaining a second observer list.
+ */
+
+nsSVGIDRenderingObserver::nsSVGIDRenderingObserver(nsIURI* aURI,
+ nsIContent* aObservingContent,
+ bool aReferenceImage)
+ : mElement(this)
+{
+ // Start watching the target element
+ mElement.Reset(aObservingContent, aURI, true, aReferenceImage);
+ StartListening();
+}
+
+nsSVGIDRenderingObserver::~nsSVGIDRenderingObserver()
+{
+ StopListening();
+}
+
+void
+nsSVGIDRenderingObserver::DoUpdate()
+{
+ if (mElement.get() && mInObserverList) {
+ nsSVGEffects::RemoveRenderingObserver(mElement.get(), this);
+ mInObserverList = false;
+ }
+}
+
+void
+nsSVGFrameReferenceFromProperty::Detach()
+{
+ mFrame = nullptr;
+ mFramePresShell = nullptr;
+}
+
+nsIFrame*
+nsSVGFrameReferenceFromProperty::Get()
+{
+ if (mFramePresShell && mFramePresShell->IsDestroying()) {
+ // mFrame is no longer valid.
+ Detach();
+ }
+ return mFrame;
+}
+
+NS_IMPL_ISUPPORTS(nsSVGRenderingObserverProperty, nsIMutationObserver)
+
+void
+nsSVGRenderingObserverProperty::DoUpdate()
+{
+ nsSVGIDRenderingObserver::DoUpdate();
+
+ nsIFrame* frame = mFrameReference.Get();
+ if (frame && frame->IsFrameOfType(nsIFrame::eSVG)) {
+ // Changes should propagate out to things that might be observing
+ // the referencing frame or its ancestors.
+ nsLayoutUtils::PostRestyleEvent(
+ frame->GetContent()->AsElement(), nsRestyleHint(0),
+ nsChangeHint_InvalidateRenderingObservers);
+ }
+}
+
+NS_IMPL_CYCLE_COLLECTING_ADDREF(nsSVGFilterReference)
+NS_IMPL_CYCLE_COLLECTING_RELEASE(nsSVGFilterReference)
+
+NS_IMPL_CYCLE_COLLECTION_CLASS(nsSVGFilterReference)
+
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(nsSVGFilterReference)
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mElement)
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
+
+NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(nsSVGFilterReference)
+ tmp->StopListening();
+ NS_IMPL_CYCLE_COLLECTION_UNLINK(mElement);
+NS_IMPL_CYCLE_COLLECTION_UNLINK_END
+
+NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(nsSVGFilterReference)
+ NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsSVGIDRenderingObserver)
+ NS_INTERFACE_MAP_ENTRY(nsIMutationObserver)
+ NS_INTERFACE_MAP_ENTRY(nsISVGFilterReference)
+NS_INTERFACE_MAP_END
+
+nsSVGFilterFrame *
+nsSVGFilterReference::GetFilterFrame()
+{
+ return static_cast<nsSVGFilterFrame *>
+ (GetReferencedFrame(nsGkAtoms::svgFilterFrame, nullptr));
+}
+
+void
+nsSVGFilterReference::DoUpdate()
+{
+ nsSVGIDRenderingObserver::DoUpdate();
+
+ if (mFilterChainObserver) {
+ mFilterChainObserver->Invalidate();
+ }
+}
+
+NS_IMPL_CYCLE_COLLECTING_ADDREF(nsSVGFilterChainObserver)
+NS_IMPL_CYCLE_COLLECTING_RELEASE(nsSVGFilterChainObserver)
+
+NS_IMPL_CYCLE_COLLECTION_CLASS(nsSVGFilterChainObserver)
+
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(nsSVGFilterChainObserver)
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mReferences)
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
+
+NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(nsSVGFilterChainObserver)
+ tmp->DetachReferences();
+ NS_IMPL_CYCLE_COLLECTION_UNLINK(mReferences);
+NS_IMPL_CYCLE_COLLECTION_UNLINK_END
+
+NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(nsSVGFilterChainObserver)
+ NS_INTERFACE_MAP_ENTRY(nsISupports)
+NS_INTERFACE_MAP_END
+
+nsSVGFilterChainObserver::nsSVGFilterChainObserver(const nsTArray<nsStyleFilter>& aFilters,
+ nsIContent* aFilteredElement,
+ nsIFrame* aFilteredFrame)
+{
+ for (uint32_t i = 0; i < aFilters.Length(); i++) {
+ if (aFilters[i].GetType() != NS_STYLE_FILTER_URL)
+ continue;
+
+ // aFilteredFrame can be null if this filter belongs to a
+ // CanvasRenderingContext2D.
+ nsCOMPtr<nsIURI> filterURL = aFilteredFrame
+ ? nsSVGEffects::GetFilterURI(aFilteredFrame, i)
+ : aFilters[i].GetURL()->ResolveLocalRef(aFilteredElement);
+
+ RefPtr<nsSVGFilterReference> reference =
+ new nsSVGFilterReference(filterURL, aFilteredElement, this);
+ mReferences.AppendElement(reference);
+ }
+}
+
+nsSVGFilterChainObserver::~nsSVGFilterChainObserver()
+{
+ DetachReferences();
+}
+
+bool
+nsSVGFilterChainObserver::ReferencesValidResources()
+{
+ for (uint32_t i = 0; i < mReferences.Length(); i++) {
+ if (!mReferences[i]->ReferencesValidResource())
+ return false;
+ }
+ return true;
+}
+
+bool
+nsSVGFilterChainObserver::IsInObserverLists() const
+{
+ for (uint32_t i = 0; i < mReferences.Length(); i++) {
+ if (!mReferences[i]->IsInObserverList())
+ return false;
+ }
+ return true;
+}
+
+void
+nsSVGFilterProperty::DoUpdate()
+{
+ nsIFrame* frame = mFrameReference.Get();
+ if (!frame)
+ return;
+
+ // Repaint asynchronously in case the filter frame is being torn down
+ nsChangeHint changeHint =
+ nsChangeHint(nsChangeHint_RepaintFrame);
+
+ if (frame && frame->IsFrameOfType(nsIFrame::eSVG)) {
+ // Changes should propagate out to things that might be observing
+ // the referencing frame or its ancestors.
+ changeHint |= nsChangeHint_InvalidateRenderingObservers;
+ }
+
+ // Don't need to request UpdateOverflow if we're being reflowed.
+ if (!(frame->GetStateBits() & NS_FRAME_IN_REFLOW)) {
+ changeHint |= nsChangeHint_UpdateOverflow;
+ }
+ frame->PresContext()->RestyleManager()->PostRestyleEvent(
+ frame->GetContent()->AsElement(), nsRestyleHint(0), changeHint);
+}
+
+void
+nsSVGMarkerProperty::DoUpdate()
+{
+ nsSVGRenderingObserverProperty::DoUpdate();
+
+ nsIFrame* frame = mFrameReference.Get();
+ if (!frame)
+ return;
+
+ NS_ASSERTION(frame->IsFrameOfType(nsIFrame::eSVG), "SVG frame expected");
+
+ // Repaint asynchronously in case the marker frame is being torn down
+ nsChangeHint changeHint =
+ nsChangeHint(nsChangeHint_RepaintFrame);
+
+ // Don't need to request ReflowFrame if we're being reflowed.
+ if (!(frame->GetStateBits() & NS_FRAME_IN_REFLOW)) {
+ changeHint |= nsChangeHint_InvalidateRenderingObservers;
+ // XXXjwatt: We need to unify SVG into standard reflow so we can just use
+ // nsChangeHint_NeedReflow | nsChangeHint_NeedDirtyReflow here.
+ // XXXSDL KILL THIS!!!
+ nsSVGUtils::ScheduleReflowSVG(frame);
+ }
+ frame->PresContext()->RestyleManager()->PostRestyleEvent(
+ frame->GetContent()->AsElement(), nsRestyleHint(0), changeHint);
+}
+
+NS_IMPL_ISUPPORTS(nsSVGMaskProperty, nsISupports)
+
+nsSVGMaskProperty::nsSVGMaskProperty(nsIFrame* aFrame)
+{
+ const nsStyleSVGReset *svgReset = aFrame->StyleSVGReset();
+
+ for (uint32_t i = 0; i < svgReset->mMask.mImageCount; i++) {
+ nsCOMPtr<nsIURI> maskUri = nsSVGEffects::GetMaskURI(aFrame, i);
+ nsSVGPaintingProperty* prop = new nsSVGPaintingProperty(maskUri, aFrame,
+ false);
+ mProperties.AppendElement(prop);
+ }
+}
+
+bool
+nsSVGTextPathProperty::TargetIsValid()
+{
+ Element* target = GetTarget();
+ return target && target->IsSVGElement(nsGkAtoms::path);
+}
+
+void
+nsSVGTextPathProperty::DoUpdate()
+{
+ nsSVGRenderingObserverProperty::DoUpdate();
+
+ nsIFrame* frame = mFrameReference.Get();
+ if (!frame)
+ return;
+
+ NS_ASSERTION(frame->IsFrameOfType(nsIFrame::eSVG) || frame->IsSVGText(),
+ "SVG frame expected");
+
+ // Avoid getting into an infinite loop of reflows if the <textPath> is
+ // pointing to one of its ancestors. TargetIsValid returns true iff
+ // the target element is a <path> element, and we would not have this
+ // nsSVGTextPathProperty if this <textPath> were a descendant of the
+ // target <path>.
+ //
+ // Note that we still have to post the restyle event when we
+ // change from being valid to invalid, so that mPositions on the
+ // SVGTextFrame gets updated, skipping the <textPath>, ensuring
+ // that nothing gets painted for that element.
+ bool nowValid = TargetIsValid();
+ if (!mValid && !nowValid) {
+ // Just return if we were previously invalid, and are still invalid.
+ return;
+ }
+ mValid = nowValid;
+
+ // Repaint asynchronously in case the path frame is being torn down
+ nsChangeHint changeHint =
+ nsChangeHint(nsChangeHint_RepaintFrame | nsChangeHint_UpdateTextPath);
+ frame->PresContext()->RestyleManager()->PostRestyleEvent(
+ frame->GetContent()->AsElement(), nsRestyleHint(0), changeHint);
+}
+
+static void
+InvalidateAllContinuations(nsIFrame* aFrame)
+{
+ for (nsIFrame* f = aFrame; f;
+ f = nsLayoutUtils::GetNextContinuationOrIBSplitSibling(f)) {
+ f->InvalidateFrame();
+ }
+}
+
+void
+nsSVGPaintingProperty::DoUpdate()
+{
+ nsSVGRenderingObserverProperty::DoUpdate();
+
+ nsIFrame* frame = mFrameReference.Get();
+ if (!frame)
+ return;
+
+ if (frame->GetStateBits() & NS_FRAME_SVG_LAYOUT) {
+ nsLayoutUtils::PostRestyleEvent(
+ frame->GetContent()->AsElement(), nsRestyleHint(0),
+ nsChangeHint_InvalidateRenderingObservers);
+ frame->InvalidateFrameSubtree();
+ } else {
+ InvalidateAllContinuations(frame);
+ }
+}
+
+static nsSVGFilterProperty*
+GetOrCreateFilterProperty(nsIFrame* aFrame)
+{
+ const nsStyleEffects* effects = aFrame->StyleEffects();
+ if (!effects->HasFilters())
+ return nullptr;
+
+ FrameProperties props = aFrame->Properties();
+ nsSVGFilterProperty *prop = props.Get(nsSVGEffects::FilterProperty());
+ if (prop)
+ return prop;
+ prop = new nsSVGFilterProperty(effects->mFilters, aFrame);
+ NS_ADDREF(prop);
+ props.Set(nsSVGEffects::FilterProperty(), prop);
+ return prop;
+}
+
+static nsSVGMaskProperty*
+GetOrCreateMaskProperty(nsIFrame* aFrame)
+{
+ FrameProperties props = aFrame->Properties();
+ nsSVGMaskProperty *prop = props.Get(nsSVGEffects::MaskProperty());
+ if (prop)
+ return prop;
+
+ prop = new nsSVGMaskProperty(aFrame);
+ NS_ADDREF(prop);
+ props.Set(nsSVGEffects::MaskProperty(), prop);
+ return prop;
+}
+
+template<class T>
+static T*
+GetEffectProperty(nsIURI* aURI, nsIFrame* aFrame,
+ const mozilla::FramePropertyDescriptor<T>* aProperty)
+{
+ if (!aURI)
+ return nullptr;
+
+ FrameProperties props = aFrame->Properties();
+ T* prop = props.Get(aProperty);
+ if (prop)
+ return prop;
+ prop = new T(aURI, aFrame, false);
+ NS_ADDREF(prop);
+ props.Set(aProperty, prop);
+ return prop;
+}
+
+nsSVGMarkerProperty*
+nsSVGEffects::GetMarkerProperty(nsIURI* aURI, nsIFrame* aFrame,
+ const mozilla::FramePropertyDescriptor<nsSVGMarkerProperty>* aProperty)
+{
+ MOZ_ASSERT(aFrame->GetType() == nsGkAtoms::svgPathGeometryFrame &&
+ static_cast<nsSVGPathGeometryElement*>(aFrame->GetContent())->IsMarkable(),
+ "Bad frame");
+ return GetEffectProperty(aURI, aFrame, aProperty);
+}
+
+nsSVGTextPathProperty*
+nsSVGEffects::GetTextPathProperty(nsIURI* aURI, nsIFrame* aFrame,
+ const mozilla::FramePropertyDescriptor<nsSVGTextPathProperty>* aProperty)
+{
+ return GetEffectProperty(aURI, aFrame, aProperty);
+}
+
+nsSVGPaintingProperty*
+nsSVGEffects::GetPaintingProperty(nsIURI* aURI, nsIFrame* aFrame,
+ const mozilla::FramePropertyDescriptor<nsSVGPaintingProperty>* aProperty)
+{
+ return GetEffectProperty(aURI, aFrame, aProperty);
+}
+
+nsSVGPaintingProperty*
+nsSVGEffects::GetPaintingPropertyForURI(nsIURI* aURI, nsIFrame* aFrame,
+ URIObserverHashtablePropertyDescriptor aProperty)
+{
+ if (!aURI)
+ return nullptr;
+
+ FrameProperties props = aFrame->Properties();
+ nsSVGEffects::URIObserverHashtable *hashtable = props.Get(aProperty);
+ if (!hashtable) {
+ hashtable = new nsSVGEffects::URIObserverHashtable();
+ props.Set(aProperty, hashtable);
+ }
+ nsSVGPaintingProperty* prop =
+ static_cast<nsSVGPaintingProperty*>(hashtable->GetWeak(aURI));
+ if (!prop) {
+ bool watchImage = aProperty == nsSVGEffects::BackgroundImageProperty();
+ prop = new nsSVGPaintingProperty(aURI, aFrame, watchImage);
+ hashtable->Put(aURI, prop);
+ }
+ return prop;
+}
+
+nsSVGEffects::EffectProperties
+nsSVGEffects::GetEffectProperties(nsIFrame* aFrame)
+{
+ NS_ASSERTION(!aFrame->GetPrevContinuation(), "aFrame should be first continuation");
+
+ EffectProperties result;
+ const nsStyleSVGReset *style = aFrame->StyleSVGReset();
+
+ result.mFilter = GetOrCreateFilterProperty(aFrame);
+
+ if (style->mClipPath.GetType() == StyleShapeSourceType::URL) {
+ nsCOMPtr<nsIURI> pathURI = nsSVGEffects::GetClipPathURI(aFrame);
+ result.mClipPath =
+ GetPaintingProperty(pathURI, aFrame, ClipPathProperty());
+ } else {
+ result.mClipPath = nullptr;
+ }
+
+ MOZ_ASSERT(style->mMask.mImageCount > 0);
+ result.mMask = style->mMask.HasLayerWithImage()
+ ? GetOrCreateMaskProperty(aFrame) : nullptr;
+
+ return result;
+}
+
+nsSVGPaintServerFrame *
+nsSVGEffects::GetPaintServer(nsIFrame* aTargetFrame,
+ nsStyleSVGPaint nsStyleSVG::* aPaint,
+ PaintingPropertyDescriptor aType)
+{
+ const nsStyleSVG* svgStyle = aTargetFrame->StyleSVG();
+ if ((svgStyle->*aPaint).Type() != eStyleSVGPaintType_Server)
+ return nullptr;
+
+ // If we're looking at a frame within SVG text, then we need to look up
+ // to find the right frame to get the painting property off. We should at
+ // least look up past a text frame, and if the text frame's parent is the
+ // anonymous block frame, then we look up to its parent (the SVGTextFrame).
+ nsIFrame* frame = aTargetFrame;
+ if (frame->GetContent()->IsNodeOfType(nsINode::eTEXT)) {
+ frame = frame->GetParent();
+ nsIFrame* grandparent = frame->GetParent();
+ if (grandparent && grandparent->GetType() == nsGkAtoms::svgTextFrame) {
+ frame = grandparent;
+ }
+ }
+
+ nsCOMPtr<nsIURI> paintServerURL = nsSVGEffects::GetPaintURI(frame, aPaint);
+ nsSVGPaintingProperty *property =
+ nsSVGEffects::GetPaintingProperty(paintServerURL, frame, aType);
+ if (!property)
+ return nullptr;
+ nsIFrame *result = property->GetReferencedFrame();
+ if (!result)
+ return nullptr;
+
+ nsIAtom *type = result->GetType();
+ if (type != nsGkAtoms::svgLinearGradientFrame &&
+ type != nsGkAtoms::svgRadialGradientFrame &&
+ type != nsGkAtoms::svgPatternFrame)
+ return nullptr;
+
+ return static_cast<nsSVGPaintServerFrame*>(result);
+}
+
+nsSVGClipPathFrame *
+nsSVGEffects::EffectProperties::GetClipPathFrame(bool* aOK)
+{
+ if (!mClipPath)
+ return nullptr;
+ nsSVGClipPathFrame *frame = static_cast<nsSVGClipPathFrame *>
+ (mClipPath->GetReferencedFrame(nsGkAtoms::svgClipPathFrame, aOK));
+ if (frame && aOK && *aOK) {
+ *aOK = frame->IsValid();
+ }
+ return frame;
+}
+
+nsSVGMaskFrame *
+nsSVGEffects::EffectProperties::GetFirstMaskFrame(bool* aOK)
+{
+ if (!mMask) {
+ return nullptr;
+ }
+
+ const nsTArray<RefPtr<nsSVGPaintingProperty>>& props = mMask->GetProps();
+
+ if (props.IsEmpty()) {
+ return nullptr;
+ }
+
+ return static_cast<nsSVGMaskFrame *>
+ (props[0]->GetReferencedFrame(nsGkAtoms::svgMaskFrame, aOK));
+}
+
+nsTArray<nsSVGMaskFrame *>
+nsSVGEffects::EffectProperties::GetMaskFrames()
+{
+ nsTArray<nsSVGMaskFrame *> result;
+ if (!mMask)
+ return result;
+
+ bool ok = false;
+ const nsTArray<RefPtr<nsSVGPaintingProperty>>& props = mMask->GetProps();
+ for (size_t i = 0; i < props.Length(); i++) {
+ nsSVGMaskFrame* maskFrame =
+ static_cast<nsSVGMaskFrame *>(props[i]->GetReferencedFrame(
+ nsGkAtoms::svgMaskFrame, &ok));
+ result.AppendElement(maskFrame);
+ }
+
+ return result;
+}
+
+void
+nsSVGEffects::UpdateEffects(nsIFrame* aFrame)
+{
+ NS_ASSERTION(aFrame->GetContent()->IsElement(),
+ "aFrame's content should be an element");
+
+ FrameProperties props = aFrame->Properties();
+ props.Delete(FilterProperty());
+ props.Delete(MaskProperty());
+ props.Delete(ClipPathProperty());
+ props.Delete(MarkerBeginProperty());
+ props.Delete(MarkerMiddleProperty());
+ props.Delete(MarkerEndProperty());
+ props.Delete(FillProperty());
+ props.Delete(StrokeProperty());
+ props.Delete(BackgroundImageProperty());
+
+ // Ensure that the filter is repainted correctly
+ // We can't do that in DoUpdate as the referenced frame may not be valid
+ GetOrCreateFilterProperty(aFrame);
+
+ if (aFrame->GetType() == nsGkAtoms::svgPathGeometryFrame &&
+ static_cast<nsSVGPathGeometryElement*>(aFrame->GetContent())->IsMarkable()) {
+ // Set marker properties here to avoid reference loops
+ nsCOMPtr<nsIURI> markerURL =
+ GetMarkerURI(aFrame, &nsStyleSVG::mMarkerStart);
+ GetMarkerProperty(markerURL, aFrame, MarkerBeginProperty());
+ markerURL = GetMarkerURI(aFrame, &nsStyleSVG::mMarkerMid);
+ GetMarkerProperty(markerURL, aFrame, MarkerMiddleProperty());
+ markerURL = GetMarkerURI(aFrame, &nsStyleSVG::mMarkerEnd);
+ GetMarkerProperty(markerURL, aFrame, MarkerEndProperty());
+ }
+}
+
+nsSVGFilterProperty*
+nsSVGEffects::GetFilterProperty(nsIFrame* aFrame)
+{
+ NS_ASSERTION(!aFrame->GetPrevContinuation(), "aFrame should be first continuation");
+
+ if (!aFrame->StyleEffects()->HasFilters())
+ return nullptr;
+
+ return aFrame->Properties().Get(FilterProperty());
+}
+
+void
+nsSVGRenderingObserverList::InvalidateAll()
+{
+ if (mObservers.Count() == 0)
+ return;
+
+ AutoTArray<nsSVGRenderingObserver*,10> observers;
+
+ for (auto it = mObservers.Iter(); !it.Done(); it.Next()) {
+ observers.AppendElement(it.Get()->GetKey());
+ }
+ mObservers.Clear();
+
+ for (uint32_t i = 0; i < observers.Length(); ++i) {
+ observers[i]->InvalidateViaReferencedElement();
+ }
+}
+
+void
+nsSVGRenderingObserverList::InvalidateAllForReflow()
+{
+ if (mObservers.Count() == 0)
+ return;
+
+ AutoTArray<nsSVGRenderingObserver*,10> observers;
+
+ for (auto it = mObservers.Iter(); !it.Done(); it.Next()) {
+ nsSVGRenderingObserver* obs = it.Get()->GetKey();
+ if (obs->ObservesReflow()) {
+ observers.AppendElement(obs);
+ it.Remove();
+ }
+ }
+
+ for (uint32_t i = 0; i < observers.Length(); ++i) {
+ observers[i]->InvalidateViaReferencedElement();
+ }
+}
+
+void
+nsSVGRenderingObserverList::RemoveAll()
+{
+ AutoTArray<nsSVGRenderingObserver*,10> observers;
+
+ for (auto it = mObservers.Iter(); !it.Done(); it.Next()) {
+ observers.AppendElement(it.Get()->GetKey());
+ }
+ mObservers.Clear();
+
+ // Our list is now cleared. We need to notify the observers we've removed,
+ // so they can update their state & remove themselves as mutation-observers.
+ for (uint32_t i = 0; i < observers.Length(); ++i) {
+ observers[i]->NotifyEvictedFromRenderingObserverList();
+ }
+}
+
+void
+nsSVGEffects::AddRenderingObserver(Element* aElement,
+ nsSVGRenderingObserver* aObserver)
+{
+ nsSVGRenderingObserverList *observerList = GetObserverList(aElement);
+ if (!observerList) {
+ observerList = new nsSVGRenderingObserverList();
+ if (!observerList)
+ return;
+ aElement->SetProperty(nsGkAtoms::renderingobserverlist, observerList,
+ nsINode::DeleteProperty<nsSVGRenderingObserverList>);
+ }
+ aElement->SetHasRenderingObservers(true);
+ observerList->Add(aObserver);
+}
+
+void
+nsSVGEffects::RemoveRenderingObserver(Element* aElement,
+ nsSVGRenderingObserver* aObserver)
+{
+ nsSVGRenderingObserverList *observerList = GetObserverList(aElement);
+ if (observerList) {
+ NS_ASSERTION(observerList->Contains(aObserver),
+ "removing observer from an element we're not observing?");
+ observerList->Remove(aObserver);
+ if (observerList->IsEmpty()) {
+ aElement->SetHasRenderingObservers(false);
+ }
+ }
+}
+
+void
+nsSVGEffects::RemoveAllRenderingObservers(Element* aElement)
+{
+ nsSVGRenderingObserverList *observerList = GetObserverList(aElement);
+ if (observerList) {
+ observerList->RemoveAll();
+ aElement->SetHasRenderingObservers(false);
+ }
+}
+
+void
+nsSVGEffects::InvalidateRenderingObservers(nsIFrame* aFrame)
+{
+ NS_ASSERTION(!aFrame->GetPrevContinuation(), "aFrame must be first continuation");
+
+ nsIContent* content = aFrame->GetContent();
+ if (!content || !content->IsElement())
+ return;
+
+ // If the rendering has changed, the bounds may well have changed too:
+ aFrame->Properties().Delete(nsSVGUtils::ObjectBoundingBoxProperty());
+
+ nsSVGRenderingObserverList *observerList =
+ GetObserverList(content->AsElement());
+ if (observerList) {
+ observerList->InvalidateAll();
+ return;
+ }
+
+ // Check ancestor SVG containers. The root frame cannot be of type
+ // eSVGContainer so we don't have to check f for null here.
+ for (nsIFrame *f = aFrame->GetParent();
+ f->IsFrameOfType(nsIFrame::eSVGContainer); f = f->GetParent()) {
+ if (f->GetContent()->IsElement()) {
+ observerList = GetObserverList(f->GetContent()->AsElement());
+ if (observerList) {
+ observerList->InvalidateAll();
+ return;
+ }
+ }
+ }
+}
+
+void
+nsSVGEffects::InvalidateDirectRenderingObservers(Element* aElement, uint32_t aFlags /* = 0 */)
+{
+ nsIFrame* frame = aElement->GetPrimaryFrame();
+ if (frame) {
+ // If the rendering has changed, the bounds may well have changed too:
+ frame->Properties().Delete(nsSVGUtils::ObjectBoundingBoxProperty());
+ }
+
+ if (aElement->HasRenderingObservers()) {
+ nsSVGRenderingObserverList *observerList = GetObserverList(aElement);
+ if (observerList) {
+ if (aFlags & INVALIDATE_REFLOW) {
+ observerList->InvalidateAllForReflow();
+ } else {
+ observerList->InvalidateAll();
+ }
+ }
+ }
+}
+
+void
+nsSVGEffects::InvalidateDirectRenderingObservers(nsIFrame* aFrame, uint32_t aFlags /* = 0 */)
+{
+ nsIContent* content = aFrame->GetContent();
+ if (content && content->IsElement()) {
+ InvalidateDirectRenderingObservers(content->AsElement(), aFlags);
+ }
+}
+
+static already_AddRefed<nsIURI>
+ResolveURLUsingLocalRef(nsIFrame* aFrame, const css::URLValueData* aURL)
+{
+ MOZ_ASSERT(aFrame);
+
+ if (!aURL) {
+ return nullptr;
+ }
+
+ // Non-local-reference URL.
+ if (!aURL->IsLocalRef()) {
+ nsCOMPtr<nsIURI> result = aURL->GetURI();
+ return result.forget();
+ }
+
+ // For a local-reference URL, resolve that fragment against the current
+ // document that relative URLs are resolved against.
+ nsIContent* content = aFrame->GetContent();
+ nsCOMPtr<nsIURI> baseURI = content->OwnerDoc()->GetDocumentURI();
+
+ if (content->IsInAnonymousSubtree()) {
+ nsIContent* bindingParent = content->GetBindingParent();
+ nsCOMPtr<nsIURI> originalURI;
+
+ // content is in a shadow tree. If this URL was specified in the subtree
+ // referenced by the <use>(or -moz-binding) element, and that subtree came
+ // from a separate resource document, then we want the fragment-only URL
+ // to resolve to an element from the resource document. Otherwise, the
+ // URL was specified somewhere in the document with the <use> element, and
+ // we want the fragment-only URL to resolve to an element in that document.
+ if (bindingParent) {
+ if (content->IsAnonymousContentInSVGUseSubtree()) {
+ SVGUseElement* useElement = static_cast<SVGUseElement*>(bindingParent);
+ originalURI = useElement->GetSourceDocURI();
+ } else {
+ nsXBLBinding* binding = bindingParent->GetXBLBinding();
+ if (binding) {
+ originalURI = binding->GetSourceDocURI();
+ } else {
+ MOZ_ASSERT(content->IsInNativeAnonymousSubtree(),
+ "an non-native anonymous tree which is not from "
+ "an XBL binding?");
+ }
+ }
+
+ if (originalURI && aURL->EqualsExceptRef(originalURI)) {
+ baseURI = originalURI;
+ }
+ }
+ }
+
+ return aURL->ResolveLocalRef(baseURI);
+}
+
+already_AddRefed<nsIURI>
+nsSVGEffects::GetMarkerURI(nsIFrame* aFrame,
+ RefPtr<css::URLValue> nsStyleSVG::* aMarker)
+{
+ return ResolveURLUsingLocalRef(aFrame, aFrame->StyleSVG()->*aMarker);
+}
+
+already_AddRefed<nsIURI>
+nsSVGEffects::GetClipPathURI(nsIFrame* aFrame)
+{
+ const nsStyleSVGReset* svgResetStyle = aFrame->StyleSVGReset();
+ MOZ_ASSERT(svgResetStyle->mClipPath.GetType() == StyleShapeSourceType::URL);
+
+ css::URLValue* url = svgResetStyle->mClipPath.GetURL();
+ return ResolveURLUsingLocalRef(aFrame, url);
+}
+
+already_AddRefed<nsIURI>
+nsSVGEffects::GetFilterURI(nsIFrame* aFrame, uint32_t aIndex)
+{
+ const nsStyleEffects* effects = aFrame->StyleEffects();
+ MOZ_ASSERT(effects->mFilters.Length() > aIndex);
+ MOZ_ASSERT(effects->mFilters[aIndex].GetType() == NS_STYLE_FILTER_URL);
+
+ return ResolveURLUsingLocalRef(aFrame, effects->mFilters[aIndex].GetURL());
+}
+
+already_AddRefed<nsIURI>
+nsSVGEffects::GetFilterURI(nsIFrame* aFrame, const nsStyleFilter& aFilter)
+{
+ MOZ_ASSERT(aFrame->StyleEffects()->mFilters.Length());
+ MOZ_ASSERT(aFilter.GetType() == NS_STYLE_FILTER_URL);
+
+ return ResolveURLUsingLocalRef(aFrame, aFilter.GetURL());
+}
+
+already_AddRefed<nsIURI>
+nsSVGEffects::GetPaintURI(nsIFrame* aFrame,
+ nsStyleSVGPaint nsStyleSVG::* aPaint)
+{
+ const nsStyleSVG* svgStyle = aFrame->StyleSVG();
+ MOZ_ASSERT((svgStyle->*aPaint).Type() ==
+ nsStyleSVGPaintType::eStyleSVGPaintType_Server);
+
+ return ResolveURLUsingLocalRef(aFrame,
+ (svgStyle->*aPaint).GetPaintServer());
+}
+
+already_AddRefed<nsIURI>
+nsSVGEffects::GetMaskURI(nsIFrame* aFrame, uint32_t aIndex)
+{
+ const nsStyleSVGReset* svgReset = aFrame->StyleSVGReset();
+ MOZ_ASSERT(svgReset->mMask.mLayers.Length() > aIndex);
+
+ return ResolveURLUsingLocalRef(aFrame,
+ svgReset->mMask.mLayers[aIndex].mSourceURI);
+}
diff --git a/layout/svg/nsSVGEffects.h b/layout/svg/nsSVGEffects.h
new file mode 100644
index 0000000000..9dd92fd312
--- /dev/null
+++ b/layout/svg/nsSVGEffects.h
@@ -0,0 +1,636 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef NSSVGEFFECTS_H_
+#define NSSVGEFFECTS_H_
+
+#include "mozilla/Attributes.h"
+#include "FramePropertyTable.h"
+#include "mozilla/dom/Element.h"
+#include "nsHashKeys.h"
+#include "nsID.h"
+#include "nsIFrame.h"
+#include "nsIMutationObserver.h"
+#include "nsInterfaceHashtable.h"
+#include "nsISupportsBase.h"
+#include "nsISupportsImpl.h"
+#include "nsReferencedElement.h"
+#include "nsStubMutationObserver.h"
+#include "nsSVGUtils.h"
+#include "nsTHashtable.h"
+#include "nsURIHashKey.h"
+#include "nsCycleCollectionParticipant.h"
+
+class nsIAtom;
+class nsIPresShell;
+class nsIURI;
+class nsSVGClipPathFrame;
+class nsSVGPaintServerFrame;
+class nsSVGFilterFrame;
+class nsSVGMaskFrame;
+class nsSVGFilterChainObserver;
+
+/*
+ * This interface allows us to be notified when a piece of SVG content is
+ * re-rendered.
+ *
+ * Concrete implementations of this interface need to implement
+ * "GetTarget()" to specify the piece of SVG content that they'd like to
+ * monitor, and they need to implement "DoUpdate" to specify how we'll react
+ * when that content gets re-rendered. They also need to implement a
+ * constructor and destructor, which should call StartListening and
+ * StopListening, respectively.
+ */
+class nsSVGRenderingObserver : public nsStubMutationObserver
+{
+
+protected:
+ virtual ~nsSVGRenderingObserver()
+ {}
+
+public:
+ typedef mozilla::dom::Element Element;
+ nsSVGRenderingObserver()
+ : mInObserverList(false)
+ {}
+
+ // nsIMutationObserver
+ NS_DECL_NSIMUTATIONOBSERVER_ATTRIBUTECHANGED
+ NS_DECL_NSIMUTATIONOBSERVER_CONTENTAPPENDED
+ NS_DECL_NSIMUTATIONOBSERVER_CONTENTINSERTED
+ NS_DECL_NSIMUTATIONOBSERVER_CONTENTREMOVED
+
+ void InvalidateViaReferencedElement();
+
+ // When a nsSVGRenderingObserver list gets forcibly cleared, it uses this
+ // callback to notify every observer that's cleared from it, so they can
+ // react.
+ void NotifyEvictedFromRenderingObserverList();
+
+ bool IsInObserverList() const { return mInObserverList; }
+
+ nsIFrame* GetReferencedFrame();
+ /**
+ * @param aOK this is only for the convenience of callers. We set *aOK to false
+ * if the frame is the wrong type
+ */
+ nsIFrame* GetReferencedFrame(nsIAtom* aFrameType, bool* aOK);
+
+ Element* GetReferencedElement();
+
+ virtual bool ObservesReflow() { return true; }
+
+protected:
+ // Non-virtual protected methods
+ void StartListening();
+ void StopListening();
+
+ // Virtual protected methods
+ virtual void DoUpdate() = 0; // called when the referenced resource changes.
+
+ // This is an internally-used version of GetReferencedElement that doesn't
+ // forcibly add us as an observer. (whereas GetReferencedElement does)
+ virtual Element* GetTarget() = 0;
+
+ // Whether we're in our referenced element's observer list at this time.
+ bool mInObserverList;
+};
+
+
+/*
+ * SVG elements reference supporting resources by element ID. We need to
+ * track when those resources change and when the DOM changes in ways
+ * that affect which element is referenced by a given ID (e.g., when
+ * element IDs change). The code here is responsible for that.
+ *
+ * When a frame references a supporting resource, we create a property
+ * object derived from nsSVGIDRenderingObserver to manage the relationship. The
+ * property object is attached to the referencing frame.
+ */
+class nsSVGIDRenderingObserver : public nsSVGRenderingObserver
+{
+public:
+ typedef mozilla::dom::Element Element;
+ nsSVGIDRenderingObserver(nsIURI* aURI, nsIContent* aObservingContent,
+ bool aReferenceImage);
+ virtual ~nsSVGIDRenderingObserver();
+
+protected:
+ Element* GetTarget() override { return mElement.get(); }
+
+ // This is called when the referenced resource changes.
+ virtual void DoUpdate() override;
+
+ class SourceReference : public nsReferencedElement
+ {
+ public:
+ explicit SourceReference(nsSVGIDRenderingObserver* aContainer) : mContainer(aContainer) {}
+ protected:
+ virtual void ElementChanged(Element* aFrom, Element* aTo) override {
+ mContainer->StopListening();
+ nsReferencedElement::ElementChanged(aFrom, aTo);
+ mContainer->StartListening();
+ mContainer->DoUpdate();
+ }
+ /**
+ * Override IsPersistent because we want to keep tracking the element
+ * for the ID even when it changes.
+ */
+ virtual bool IsPersistent() override { return true; }
+ private:
+ nsSVGIDRenderingObserver* mContainer;
+ };
+
+ SourceReference mElement;
+};
+
+struct nsSVGFrameReferenceFromProperty
+{
+ explicit nsSVGFrameReferenceFromProperty(nsIFrame* aFrame)
+ : mFrame(aFrame)
+ , mFramePresShell(aFrame->PresContext()->PresShell())
+ {}
+
+ // Clear our reference to the frame.
+ void Detach();
+
+ // null if the frame has become invalid
+ nsIFrame* Get();
+
+private:
+ // The frame that this property is attached to, may be null
+ nsIFrame *mFrame;
+ // When a presshell is torn down, we don't delete the properties for
+ // each frame until after the frames are destroyed. So here we remember
+ // the presshell for the frames we care about and, before we use the frame,
+ // we test the presshell to see if it's destroying itself. If it is,
+ // then the frame pointer is not valid and we know the frame has gone away.
+ // mFramePresShell may be null, but when mFrame is non-null, mFramePresShell
+ // is guaranteed to be non-null, too.
+ nsIPresShell *mFramePresShell;
+};
+
+class nsSVGRenderingObserverProperty : public nsSVGIDRenderingObserver
+{
+public:
+ NS_DECL_ISUPPORTS
+
+ nsSVGRenderingObserverProperty(nsIURI* aURI, nsIFrame *aFrame,
+ bool aReferenceImage)
+ : nsSVGIDRenderingObserver(aURI, aFrame->GetContent(), aReferenceImage)
+ , mFrameReference(aFrame)
+ {}
+
+protected:
+ virtual ~nsSVGRenderingObserverProperty() {}
+
+ virtual void DoUpdate() override;
+
+ nsSVGFrameReferenceFromProperty mFrameReference;
+};
+
+/**
+ * In a filter chain, there can be multiple SVG reference filters.
+ * e.g. filter: url(#svg-filter-1) blur(10px) url(#svg-filter-2);
+ *
+ * This class keeps track of one SVG reference filter in a filter chain.
+ * e.g. url(#svg-filter-1)
+ *
+ * It fires invalidations when the SVG filter element's id changes or when
+ * the SVG filter element's content changes.
+ *
+ * The nsSVGFilterChainObserver class manages a list of nsSVGFilterReferences.
+ */
+class nsSVGFilterReference final : public nsSVGIDRenderingObserver
+ , public nsISVGFilterReference
+{
+public:
+ nsSVGFilterReference(nsIURI* aURI,
+ nsIContent* aObservingContent,
+ nsSVGFilterChainObserver* aFilterChainObserver)
+ : nsSVGIDRenderingObserver(aURI, aObservingContent, false)
+ , mFilterChainObserver(aFilterChainObserver)
+ {
+ }
+
+ bool ReferencesValidResource() { return GetFilterFrame(); }
+
+ void DetachFromChainObserver() { mFilterChainObserver = nullptr; }
+
+ /**
+ * @return the filter frame, or null if there is no filter frame
+ */
+ nsSVGFilterFrame *GetFilterFrame();
+
+ // nsISupports
+ NS_DECL_CYCLE_COLLECTING_ISUPPORTS
+ NS_DECL_CYCLE_COLLECTION_CLASS_AMBIGUOUS(nsSVGFilterReference, nsSVGIDRenderingObserver)
+
+ // nsISVGFilterReference
+ virtual void Invalidate() override { DoUpdate(); };
+
+protected:
+ virtual ~nsSVGFilterReference() {}
+
+ // nsSVGIDRenderingObserver
+ virtual void DoUpdate() override;
+
+private:
+ nsSVGFilterChainObserver* mFilterChainObserver;
+};
+
+/**
+ * This class manages a list of nsSVGFilterReferences, which represent SVG
+ * reference filters in a filter chain.
+ * e.g. filter: url(#svg-filter-1) blur(10px) url(#svg-filter-2);
+ *
+ * In the above example, the nsSVGFilterChainObserver will manage two
+ * nsSVGFilterReferences, one for each SVG reference filter. CSS filters like
+ * "blur(10px)" don't reference filter elements, so they don't need an
+ * nsSVGFilterReference. The style system invalidates changes to CSS filters.
+ */
+class nsSVGFilterChainObserver : public nsISupports
+{
+public:
+ nsSVGFilterChainObserver(const nsTArray<nsStyleFilter>& aFilters,
+ nsIContent* aFilteredElement,
+ nsIFrame* aFiltedFrame = nullptr);
+
+ bool ReferencesValidResources();
+ bool IsInObserverLists() const;
+ void Invalidate() { DoUpdate(); }
+
+ // nsISupports
+ NS_DECL_CYCLE_COLLECTING_ISUPPORTS
+ NS_DECL_CYCLE_COLLECTION_CLASS(nsSVGFilterChainObserver)
+
+protected:
+ virtual ~nsSVGFilterChainObserver();
+
+ virtual void DoUpdate() = 0;
+
+private:
+
+ void DetachReferences()
+ {
+ for (uint32_t i = 0; i < mReferences.Length(); i++) {
+ mReferences[i]->DetachFromChainObserver();
+ }
+ }
+
+ nsTArray<RefPtr<nsSVGFilterReference>> mReferences;
+};
+
+class nsSVGFilterProperty : public nsSVGFilterChainObserver
+{
+public:
+ nsSVGFilterProperty(const nsTArray<nsStyleFilter>& aFilters,
+ nsIFrame* aFilteredFrame)
+ : nsSVGFilterChainObserver(aFilters, aFilteredFrame->GetContent(),
+ aFilteredFrame)
+ , mFrameReference(aFilteredFrame)
+ {}
+
+ void DetachFromFrame() { mFrameReference.Detach(); }
+
+protected:
+ virtual void DoUpdate() override;
+
+ nsSVGFrameReferenceFromProperty mFrameReference;
+};
+
+class nsSVGMarkerProperty final: public nsSVGRenderingObserverProperty
+{
+public:
+ nsSVGMarkerProperty(nsIURI* aURI, nsIFrame* aFrame, bool aReferenceImage)
+ : nsSVGRenderingObserverProperty(aURI, aFrame, aReferenceImage) {}
+
+protected:
+ virtual void DoUpdate() override;
+};
+
+class nsSVGTextPathProperty final : public nsSVGRenderingObserverProperty
+{
+public:
+ nsSVGTextPathProperty(nsIURI* aURI, nsIFrame* aFrame, bool aReferenceImage)
+ : nsSVGRenderingObserverProperty(aURI, aFrame, aReferenceImage)
+ , mValid(true) {}
+
+ virtual bool ObservesReflow() override { return false; }
+
+protected:
+ virtual void DoUpdate() override;
+
+private:
+ /**
+ * Returns true if the target of the textPath is the frame of a 'path' element.
+ */
+ bool TargetIsValid();
+
+ bool mValid;
+};
+
+class nsSVGPaintingProperty final : public nsSVGRenderingObserverProperty
+{
+public:
+ nsSVGPaintingProperty(nsIURI* aURI, nsIFrame* aFrame, bool aReferenceImage)
+ : nsSVGRenderingObserverProperty(aURI, aFrame, aReferenceImage) {}
+
+protected:
+ virtual void DoUpdate() override;
+};
+
+class nsSVGMaskProperty final : public nsISupports
+{
+public:
+ explicit nsSVGMaskProperty(nsIFrame* aFrame);
+
+ // nsISupports
+ NS_DECL_ISUPPORTS
+
+ const nsTArray<RefPtr<nsSVGPaintingProperty>>& GetProps() const
+ {
+ return mProperties;
+ }
+
+private:
+ virtual ~nsSVGMaskProperty() {}
+ nsTArray<RefPtr<nsSVGPaintingProperty>> mProperties;
+};
+
+/**
+ * A manager for one-shot nsSVGRenderingObserver tracking.
+ * nsSVGRenderingObservers can be added or removed. They are not strongly
+ * referenced so an observer must be removed before it dies.
+ * When InvalidateAll is called, all outstanding references get
+ * InvalidateViaReferencedElement()
+ * called on them and the list is cleared. The intent is that
+ * the observer will force repainting of whatever part of the document
+ * is needed, and then at paint time the observer will do a clean lookup
+ * of the referenced element and [re-]add itself to the element's observer list.
+ *
+ * InvalidateAll must be called before this object is destroyed, i.e.
+ * before the referenced frame is destroyed. This should normally happen
+ * via nsSVGContainerFrame::RemoveFrame, since only frames in the frame
+ * tree should be referenced.
+ */
+class nsSVGRenderingObserverList
+{
+public:
+ nsSVGRenderingObserverList()
+ : mObservers(4)
+ {
+ MOZ_COUNT_CTOR(nsSVGRenderingObserverList);
+ }
+
+ ~nsSVGRenderingObserverList() {
+ InvalidateAll();
+ MOZ_COUNT_DTOR(nsSVGRenderingObserverList);
+ }
+
+ void Add(nsSVGRenderingObserver* aObserver)
+ { mObservers.PutEntry(aObserver); }
+ void Remove(nsSVGRenderingObserver* aObserver)
+ { mObservers.RemoveEntry(aObserver); }
+#ifdef DEBUG
+ bool Contains(nsSVGRenderingObserver* aObserver)
+ { return (mObservers.GetEntry(aObserver) != nullptr); }
+#endif
+ bool IsEmpty()
+ { return mObservers.Count() == 0; }
+
+ /**
+ * Drop all our observers, and notify them that we have changed and dropped
+ * our reference to them.
+ */
+ void InvalidateAll();
+
+ /**
+ * Drop all observers that observe reflow, and notify them that we have changed and dropped
+ * our reference to them.
+ */
+ void InvalidateAllForReflow();
+
+ /**
+ * Drop all our observers, and notify them that we have dropped our reference
+ * to them.
+ */
+ void RemoveAll();
+
+private:
+ nsTHashtable<nsPtrHashKey<nsSVGRenderingObserver> > mObservers;
+};
+
+class nsSVGEffects
+{
+public:
+ typedef mozilla::dom::Element Element;
+ typedef nsInterfaceHashtable<nsURIHashKey, nsIMutationObserver>
+ URIObserverHashtable;
+
+ using PaintingPropertyDescriptor =
+ const mozilla::FramePropertyDescriptor<nsSVGPaintingProperty>*;
+ using URIObserverHashtablePropertyDescriptor =
+ const mozilla::FramePropertyDescriptor<URIObserverHashtable>*;
+
+ static void DestroyFilterProperty(nsSVGFilterProperty* aProp)
+ {
+ // nsSVGFilterProperty is cycle-collected, so dropping the last reference
+ // doesn't necessarily destroy it. We need to tell it that the frame
+ // has now become invalid.
+ aProp->DetachFromFrame();
+
+ aProp->Release();
+ }
+
+ NS_DECLARE_FRAME_PROPERTY_WITH_DTOR(FilterProperty, nsSVGFilterProperty,
+ DestroyFilterProperty)
+ NS_DECLARE_FRAME_PROPERTY_RELEASABLE(MaskProperty, nsSVGMaskProperty)
+ NS_DECLARE_FRAME_PROPERTY_RELEASABLE(ClipPathProperty, nsSVGPaintingProperty)
+ NS_DECLARE_FRAME_PROPERTY_RELEASABLE(MarkerBeginProperty, nsSVGMarkerProperty)
+ NS_DECLARE_FRAME_PROPERTY_RELEASABLE(MarkerMiddleProperty, nsSVGMarkerProperty)
+ NS_DECLARE_FRAME_PROPERTY_RELEASABLE(MarkerEndProperty, nsSVGMarkerProperty)
+ NS_DECLARE_FRAME_PROPERTY_RELEASABLE(FillProperty, nsSVGPaintingProperty)
+ NS_DECLARE_FRAME_PROPERTY_RELEASABLE(StrokeProperty, nsSVGPaintingProperty)
+ NS_DECLARE_FRAME_PROPERTY_RELEASABLE(HrefAsTextPathProperty,
+ nsSVGTextPathProperty)
+ NS_DECLARE_FRAME_PROPERTY_RELEASABLE(HrefAsPaintingProperty,
+ nsSVGPaintingProperty)
+ NS_DECLARE_FRAME_PROPERTY_DELETABLE(BackgroundImageProperty,
+ URIObserverHashtable)
+
+ /**
+ * Get the paint server for a aTargetFrame.
+ */
+ static nsSVGPaintServerFrame *GetPaintServer(nsIFrame* aTargetFrame,
+ nsStyleSVGPaint nsStyleSVG::* aPaint,
+ PaintingPropertyDescriptor aProperty);
+
+ struct EffectProperties {
+ nsSVGFilterProperty* mFilter;
+ nsSVGMaskProperty* mMask;
+ nsSVGPaintingProperty* mClipPath;
+
+ /**
+ * @return the clip-path frame, or null if there is no clip-path frame
+ * @param aOK if a clip-path was specified and the designated element
+ * exists but is an element of the wrong type, *aOK is set to false.
+ * Otherwise *aOK is untouched.
+ */
+ nsSVGClipPathFrame *GetClipPathFrame(bool* aOK);
+ /**
+ * @return the first mask frame, or null if there is no mask frame
+ * @param aOK if a mask was specified and the designated element
+ * exists but is an element of the wrong type, *aOK is set to false.
+ * Otherwise *aOK is untouched.
+ */
+ nsSVGMaskFrame *GetFirstMaskFrame(bool* aOK = nullptr);
+
+ /**
+ * @return an array which contains all SVG mask frames.
+ */
+ nsTArray<nsSVGMaskFrame*> GetMaskFrames();
+
+ bool HasValidFilter() {
+ return mFilter && mFilter->ReferencesValidResources();
+ }
+
+ bool HasNoFilterOrHasValidFilter() {
+ return !mFilter || mFilter->ReferencesValidResources();
+ }
+ };
+
+ /**
+ * @param aFrame should be the first continuation
+ */
+ static EffectProperties GetEffectProperties(nsIFrame* aFrame);
+
+ /**
+ * Called when changes to an element (e.g. CSS property changes) cause its
+ * frame to start/stop referencing (or reference different) SVG resource
+ * elements. (_Not_ called for changes to referenced resource elements.)
+ *
+ * This function handles such changes by discarding _all_ the frame's SVG
+ * effects frame properties (causing those properties to stop watching their
+ * target element). It also synchronously (re)creates the filter and marker
+ * frame properties (XXX why not the other properties?), which makes it
+ * useful for initializing those properties during first reflow.
+ *
+ * XXX rename to something more meaningful like RefreshResourceReferences?
+ */
+ static void UpdateEffects(nsIFrame* aFrame);
+
+ /**
+ * @param aFrame should be the first continuation
+ */
+ static nsSVGFilterProperty *GetFilterProperty(nsIFrame* aFrame);
+
+ /**
+ * @param aFrame must be a first-continuation.
+ */
+ static void AddRenderingObserver(Element* aElement, nsSVGRenderingObserver *aObserver);
+ /**
+ * @param aFrame must be a first-continuation.
+ */
+ static void RemoveRenderingObserver(Element* aElement, nsSVGRenderingObserver *aObserver);
+
+ /**
+ * Removes all rendering observers from aElement.
+ */
+ static void RemoveAllRenderingObservers(Element* aElement);
+
+ /**
+ * This can be called on any frame. We invalidate the observers of aFrame's
+ * element, if any, or else walk up to the nearest observable SVG parent
+ * frame with observers and invalidate them instead.
+ *
+ * Note that this method is very different to e.g.
+ * nsNodeUtils::AttributeChanged which walks up the content node tree all the
+ * way to the root node (not stopping if it encounters a non-container SVG
+ * node) invalidating all mutation observers (not just
+ * nsSVGRenderingObservers) on all nodes along the way (not just the first
+ * node it finds with observers). In other words, by doing all the
+ * things in parentheses in the preceding sentence, this method uses
+ * knowledge about our implementation and what can be affected by SVG effects
+ * to make invalidation relatively lightweight when an SVG effect changes.
+ */
+ static void InvalidateRenderingObservers(nsIFrame* aFrame);
+
+ enum {
+ INVALIDATE_REFLOW = 1
+ };
+
+ /**
+ * This can be called on any element or frame. Only direct observers of this
+ * (frame's) element, if any, are invalidated.
+ */
+ static void InvalidateDirectRenderingObservers(Element* aElement, uint32_t aFlags = 0);
+ static void InvalidateDirectRenderingObservers(nsIFrame* aFrame, uint32_t aFlags = 0);
+
+ /**
+ * Get an nsSVGMarkerProperty for the frame, creating a fresh one if necessary
+ */
+ static nsSVGMarkerProperty *
+ GetMarkerProperty(nsIURI* aURI, nsIFrame* aFrame,
+ const mozilla::FramePropertyDescriptor<nsSVGMarkerProperty>* aProperty);
+ /**
+ * Get an nsSVGTextPathProperty for the frame, creating a fresh one if necessary
+ */
+ static nsSVGTextPathProperty *
+ GetTextPathProperty(nsIURI* aURI, nsIFrame* aFrame,
+ const mozilla::FramePropertyDescriptor<nsSVGTextPathProperty>* aProperty);
+ /**
+ * Get an nsSVGPaintingProperty for the frame, creating a fresh one if necessary
+ */
+ static nsSVGPaintingProperty*
+ GetPaintingProperty(nsIURI* aURI, nsIFrame* aFrame,
+ const mozilla::FramePropertyDescriptor<nsSVGPaintingProperty>* aProperty);
+ /**
+ * Get an nsSVGPaintingProperty for the frame for that URI, creating a fresh
+ * one if necessary
+ */
+ static nsSVGPaintingProperty*
+ GetPaintingPropertyForURI(nsIURI* aURI, nsIFrame* aFrame,
+ URIObserverHashtablePropertyDescriptor aProp);
+
+ /**
+ * A helper function to resolve marker's URL.
+ */
+ static already_AddRefed<nsIURI>
+ GetMarkerURI(nsIFrame* aFrame,
+ RefPtr<mozilla::css::URLValue> nsStyleSVG::* aMarker);
+
+ /**
+ * A helper function to resolve clip-path URL.
+ */
+ static already_AddRefed<nsIURI>
+ GetClipPathURI(nsIFrame* aFrame);
+
+ /**
+ * A helper function to resolve filter URL.
+ */
+ static already_AddRefed<nsIURI>
+ GetFilterURI(nsIFrame* aFrame, uint32_t aIndex);
+
+ /**
+ * A helper function to resolve filter URL.
+ */
+ static already_AddRefed<nsIURI>
+ GetFilterURI(nsIFrame* aFrame, const nsStyleFilter& aFilter);
+
+ /**
+ * A helper function to resolve paint-server URL.
+ */
+ static already_AddRefed<nsIURI>
+ GetPaintURI(nsIFrame* aFrame, nsStyleSVGPaint nsStyleSVG::* aPaint);
+
+ /**
+ * A helper function to resolve SVG mask URL.
+ */
+ static already_AddRefed<nsIURI>
+ GetMaskURI(nsIFrame* aFrame, uint32_t aIndex);
+};
+
+#endif /*NSSVGEFFECTS_H_*/
diff --git a/layout/svg/nsSVGFilterFrame.cpp b/layout/svg/nsSVGFilterFrame.cpp
new file mode 100644
index 0000000000..13ce16993d
--- /dev/null
+++ b/layout/svg/nsSVGFilterFrame.cpp
@@ -0,0 +1,212 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+// Main header first:
+#include "nsSVGFilterFrame.h"
+
+// Keep others in (case-insensitive) order:
+#include "gfxUtils.h"
+#include "nsGkAtoms.h"
+#include "nsSVGEffects.h"
+#include "nsSVGElement.h"
+#include "mozilla/dom/SVGFilterElement.h"
+#include "nsSVGFilterInstance.h"
+#include "nsSVGIntegrationUtils.h"
+#include "nsSVGUtils.h"
+#include "nsContentUtils.h"
+
+using namespace mozilla::dom;
+
+nsIFrame*
+NS_NewSVGFilterFrame(nsIPresShell* aPresShell, nsStyleContext* aContext)
+{
+ return new (aPresShell) nsSVGFilterFrame(aContext);
+}
+
+NS_IMPL_FRAMEARENA_HELPERS(nsSVGFilterFrame)
+
+class MOZ_RAII nsSVGFilterFrame::AutoFilterReferencer
+{
+public:
+ explicit AutoFilterReferencer(nsSVGFilterFrame *aFrame MOZ_GUARD_OBJECT_NOTIFIER_PARAM)
+ : mFrame(aFrame)
+ {
+ MOZ_GUARD_OBJECT_NOTIFIER_INIT;
+ // Reference loops should normally be detected in advance and handled, so
+ // we're not expecting to encounter them here
+ MOZ_ASSERT(!mFrame->mLoopFlag, "Undetected reference loop!");
+ mFrame->mLoopFlag = true;
+ }
+ ~AutoFilterReferencer() {
+ mFrame->mLoopFlag = false;
+ }
+private:
+ nsSVGFilterFrame *mFrame;
+ MOZ_DECL_USE_GUARD_OBJECT_NOTIFIER
+};
+
+uint16_t
+nsSVGFilterFrame::GetEnumValue(uint32_t aIndex, nsIContent *aDefault)
+{
+ nsSVGEnum& thisEnum =
+ static_cast<SVGFilterElement *>(mContent)->mEnumAttributes[aIndex];
+
+ if (thisEnum.IsExplicitlySet())
+ return thisEnum.GetAnimValue();
+
+ AutoFilterReferencer filterRef(this);
+
+ nsSVGFilterFrame *next = GetReferencedFilterIfNotInUse();
+ return next ? next->GetEnumValue(aIndex, aDefault) :
+ static_cast<SVGFilterElement *>(aDefault)->
+ mEnumAttributes[aIndex].GetAnimValue();
+}
+
+const nsSVGLength2 *
+nsSVGFilterFrame::GetLengthValue(uint32_t aIndex, nsIContent *aDefault)
+{
+ const nsSVGLength2 *thisLength =
+ &static_cast<SVGFilterElement *>(mContent)->mLengthAttributes[aIndex];
+
+ if (thisLength->IsExplicitlySet())
+ return thisLength;
+
+ AutoFilterReferencer filterRef(this);
+
+ nsSVGFilterFrame *next = GetReferencedFilterIfNotInUse();
+ return next ? next->GetLengthValue(aIndex, aDefault) :
+ &static_cast<SVGFilterElement *>(aDefault)->mLengthAttributes[aIndex];
+}
+
+const SVGFilterElement *
+nsSVGFilterFrame::GetFilterContent(nsIContent *aDefault)
+{
+ for (nsIContent* child = mContent->GetFirstChild();
+ child;
+ child = child->GetNextSibling()) {
+ RefPtr<nsSVGFE> primitive;
+ CallQueryInterface(child, (nsSVGFE**)getter_AddRefs(primitive));
+ if (primitive) {
+ return static_cast<SVGFilterElement *>(mContent);
+ }
+ }
+
+ AutoFilterReferencer filterRef(this);
+
+ nsSVGFilterFrame *next = GetReferencedFilterIfNotInUse();
+ return next ? next->GetFilterContent(aDefault) :
+ static_cast<SVGFilterElement *>(aDefault);
+}
+
+nsSVGFilterFrame *
+nsSVGFilterFrame::GetReferencedFilter()
+{
+ if (mNoHRefURI)
+ return nullptr;
+
+ nsSVGPaintingProperty *property =
+ Properties().Get(nsSVGEffects::HrefAsPaintingProperty());
+
+ if (!property) {
+ // Fetch our Filter element's href or xlink:href attribute
+ SVGFilterElement *filter = static_cast<SVGFilterElement *>(mContent);
+ nsAutoString href;
+ if (filter->mStringAttributes[SVGFilterElement::HREF].IsExplicitlySet()) {
+ filter->mStringAttributes[SVGFilterElement::HREF]
+ .GetAnimValue(href, filter);
+ } else {
+ filter->mStringAttributes[SVGFilterElement::XLINK_HREF]
+ .GetAnimValue(href, filter);
+ }
+
+ if (href.IsEmpty()) {
+ mNoHRefURI = true;
+ return nullptr; // no URL
+ }
+
+ // Convert href to an nsIURI
+ nsCOMPtr<nsIURI> targetURI;
+ nsCOMPtr<nsIURI> base = mContent->GetBaseURI();
+ nsContentUtils::NewURIWithDocumentCharset(getter_AddRefs(targetURI), href,
+ mContent->GetUncomposedDoc(), base);
+
+ property =
+ nsSVGEffects::GetPaintingProperty(targetURI, this,
+ nsSVGEffects::HrefAsPaintingProperty());
+ if (!property)
+ return nullptr;
+ }
+
+ nsIFrame *result = property->GetReferencedFrame();
+ if (!result)
+ return nullptr;
+
+ nsIAtom* frameType = result->GetType();
+ if (frameType != nsGkAtoms::svgFilterFrame)
+ return nullptr;
+
+ return static_cast<nsSVGFilterFrame*>(result);
+}
+
+nsSVGFilterFrame *
+nsSVGFilterFrame::GetReferencedFilterIfNotInUse()
+{
+ nsSVGFilterFrame *referenced = GetReferencedFilter();
+ if (!referenced)
+ return nullptr;
+
+ if (referenced->mLoopFlag) {
+ // XXXjwatt: we should really send an error to the JavaScript Console here:
+ NS_WARNING("Filter reference loop detected while inheriting attribute!");
+ return nullptr;
+ }
+
+ return referenced;
+}
+
+nsresult
+nsSVGFilterFrame::AttributeChanged(int32_t aNameSpaceID,
+ nsIAtom* aAttribute,
+ int32_t aModType)
+{
+ if (aNameSpaceID == kNameSpaceID_None &&
+ (aAttribute == nsGkAtoms::x ||
+ aAttribute == nsGkAtoms::y ||
+ aAttribute == nsGkAtoms::width ||
+ aAttribute == nsGkAtoms::height ||
+ aAttribute == nsGkAtoms::filterUnits ||
+ aAttribute == nsGkAtoms::primitiveUnits)) {
+ nsSVGEffects::InvalidateDirectRenderingObservers(this);
+ } else if ((aNameSpaceID == kNameSpaceID_XLink ||
+ aNameSpaceID == kNameSpaceID_None) &&
+ aAttribute == nsGkAtoms::href) {
+ // Blow away our reference, if any
+ Properties().Delete(nsSVGEffects::HrefAsPaintingProperty());
+ mNoHRefURI = false;
+ // And update whoever references us
+ nsSVGEffects::InvalidateDirectRenderingObservers(this);
+ }
+ return nsSVGContainerFrame::AttributeChanged(aNameSpaceID,
+ aAttribute, aModType);
+}
+
+#ifdef DEBUG
+void
+nsSVGFilterFrame::Init(nsIContent* aContent,
+ nsContainerFrame* aParent,
+ nsIFrame* aPrevInFlow)
+{
+ NS_ASSERTION(aContent->IsSVGElement(nsGkAtoms::filter),
+ "Content is not an SVG filter");
+
+ nsSVGContainerFrame::Init(aContent, aParent, aPrevInFlow);
+}
+#endif /* DEBUG */
+
+nsIAtom *
+nsSVGFilterFrame::GetType() const
+{
+ return nsGkAtoms::svgFilterFrame;
+}
diff --git a/layout/svg/nsSVGFilterFrame.h b/layout/svg/nsSVGFilterFrame.h
new file mode 100644
index 0000000000..223c787f6e
--- /dev/null
+++ b/layout/svg/nsSVGFilterFrame.h
@@ -0,0 +1,99 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef __NS_SVGFILTERFRAME_H__
+#define __NS_SVGFILTERFRAME_H__
+
+#include "mozilla/Attributes.h"
+#include "nsFrame.h"
+#include "nsQueryFrame.h"
+#include "nsSVGContainerFrame.h"
+#include "nsSVGUtils.h"
+
+class nsIAtom;
+class nsIContent;
+class nsIFrame;
+class nsIPresShell;
+class nsStyleContext;
+class nsSVGLength2;
+
+struct nsRect;
+
+namespace mozilla {
+namespace dom {
+class SVGFilterElement;
+} // namespace dom
+} // namespace mozilla
+
+class nsSVGFilterFrame : public nsSVGContainerFrame
+{
+ friend nsIFrame*
+ NS_NewSVGFilterFrame(nsIPresShell* aPresShell, nsStyleContext* aContext);
+protected:
+ explicit nsSVGFilterFrame(nsStyleContext* aContext)
+ : nsSVGContainerFrame(aContext)
+ , mLoopFlag(false)
+ , mNoHRefURI(false)
+ {
+ AddStateBits(NS_FRAME_IS_NONDISPLAY);
+ }
+
+public:
+ NS_DECL_FRAMEARENA_HELPERS
+
+ // nsIFrame methods:
+ virtual void BuildDisplayList(nsDisplayListBuilder* aBuilder,
+ const nsRect& aDirtyRect,
+ const nsDisplayListSet& aLists) override {}
+
+ virtual nsresult AttributeChanged(int32_t aNameSpaceID,
+ nsIAtom* aAttribute,
+ int32_t aModType) override;
+
+#ifdef DEBUG
+ virtual void Init(nsIContent* aContent,
+ nsContainerFrame* aParent,
+ nsIFrame* aPrevInFlow) override;
+#endif
+
+ /**
+ * Get the "type" of the frame
+ *
+ * @see nsGkAtoms::svgFilterFrame
+ */
+ virtual nsIAtom* GetType() const override;
+
+private:
+ // Parse our xlink:href and set up our nsSVGPaintingProperty if we
+ // reference another filter and we don't have a property. Return
+ // the referenced filter's frame if available, null otherwise.
+ class AutoFilterReferencer;
+ friend class nsSVGFilterInstance;
+ nsSVGFilterFrame* GetReferencedFilter();
+ nsSVGFilterFrame* GetReferencedFilterIfNotInUse();
+
+ // Accessors to lookup filter attributes
+ uint16_t GetEnumValue(uint32_t aIndex, nsIContent *aDefault);
+ uint16_t GetEnumValue(uint32_t aIndex)
+ {
+ return GetEnumValue(aIndex, mContent);
+ }
+ const nsSVGLength2 *GetLengthValue(uint32_t aIndex, nsIContent *aDefault);
+ const nsSVGLength2 *GetLengthValue(uint32_t aIndex)
+ {
+ return GetLengthValue(aIndex, mContent);
+ }
+ const mozilla::dom::SVGFilterElement *GetFilterContent(nsIContent *aDefault);
+ const mozilla::dom::SVGFilterElement *GetFilterContent()
+ {
+ return GetFilterContent(mContent);
+ }
+
+ // This flag is used to detect loops in xlink:href processing
+ bool mLoopFlag;
+ bool mNoHRefURI;
+};
+
+#endif
diff --git a/layout/svg/nsSVGFilterInstance.cpp b/layout/svg/nsSVGFilterInstance.cpp
new file mode 100644
index 0000000000..59b5a27bab
--- /dev/null
+++ b/layout/svg/nsSVGFilterInstance.cpp
@@ -0,0 +1,453 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+// Main header first:
+#include "nsSVGFilterInstance.h"
+
+// Keep others in (case-insensitive) order:
+#include "gfxPlatform.h"
+#include "gfxUtils.h"
+#include "nsISVGChildFrame.h"
+#include "mozilla/dom/HTMLCanvasElement.h"
+#include "mozilla/dom/SVGFilterElement.h"
+#include "nsReferencedElement.h"
+#include "nsSVGFilterFrame.h"
+#include "nsSVGUtils.h"
+#include "SVGContentUtils.h"
+#include "FilterSupport.h"
+#include "gfx2DGlue.h"
+
+using namespace mozilla;
+using namespace mozilla::dom;
+using namespace mozilla::gfx;
+
+nsSVGFilterInstance::nsSVGFilterInstance(const nsStyleFilter& aFilter,
+ nsIFrame* aTargetFrame,
+ nsIContent* aTargetContent,
+ const UserSpaceMetrics& aMetrics,
+ const gfxRect& aTargetBBox,
+ const gfxSize& aUserSpaceToFilterSpaceScale,
+ const gfxSize& aFilterSpaceToUserSpaceScale) :
+ mFilter(aFilter),
+ mTargetContent(aTargetContent),
+ mMetrics(aMetrics),
+ mTargetBBox(aTargetBBox),
+ mUserSpaceToFilterSpaceScale(aUserSpaceToFilterSpaceScale),
+ mFilterSpaceToUserSpaceScale(aFilterSpaceToUserSpaceScale),
+ mSourceAlphaAvailable(false),
+ mInitialized(false) {
+
+ // Get the filter frame.
+ mFilterFrame = GetFilterFrame(aTargetFrame);
+ if (!mFilterFrame) {
+ return;
+ }
+
+ // Get the filter element.
+ mFilterElement = mFilterFrame->GetFilterContent();
+ if (!mFilterElement) {
+ NS_NOTREACHED("filter frame should have a related element");
+ return;
+ }
+
+ mPrimitiveUnits =
+ mFilterFrame->GetEnumValue(SVGFilterElement::PRIMITIVEUNITS);
+
+ nsresult rv = ComputeBounds();
+ if (NS_FAILED(rv)) {
+ return;
+ }
+
+ mInitialized = true;
+}
+
+nsresult
+nsSVGFilterInstance::ComputeBounds()
+{
+ // XXX if filterUnits is set (or has defaulted) to objectBoundingBox, we
+ // should send a warning to the error console if the author has used lengths
+ // with units. This is a common mistake and can result in the filter region
+ // being *massive* below (because we ignore the units and interpret the number
+ // as a factor of the bbox width/height). We should also send a warning if the
+ // user uses a number without units (a future SVG spec should really
+ // deprecate that, since it's too confusing for a bare number to be sometimes
+ // interpreted as a fraction of the bounding box and sometimes as user-space
+ // units). So really only percentage values should be used in this case.
+
+ // Set the user space bounds (i.e. the filter region in user space).
+ nsSVGLength2 XYWH[4];
+ static_assert(sizeof(mFilterElement->mLengthAttributes) == sizeof(XYWH),
+ "XYWH size incorrect");
+ memcpy(XYWH, mFilterElement->mLengthAttributes,
+ sizeof(mFilterElement->mLengthAttributes));
+ XYWH[0] = *mFilterFrame->GetLengthValue(SVGFilterElement::ATTR_X);
+ XYWH[1] = *mFilterFrame->GetLengthValue(SVGFilterElement::ATTR_Y);
+ XYWH[2] = *mFilterFrame->GetLengthValue(SVGFilterElement::ATTR_WIDTH);
+ XYWH[3] = *mFilterFrame->GetLengthValue(SVGFilterElement::ATTR_HEIGHT);
+ uint16_t filterUnits =
+ mFilterFrame->GetEnumValue(SVGFilterElement::FILTERUNITS);
+ gfxRect userSpaceBounds = nsSVGUtils::GetRelativeRect(filterUnits,
+ XYWH, mTargetBBox, mMetrics);
+
+ // Transform the user space bounds to filter space, so we
+ // can align them with the pixel boundries of the offscreen surface.
+ // The offscreen surface has the same scale as filter space.
+ gfxRect filterSpaceBounds = UserSpaceToFilterSpace(userSpaceBounds);
+ filterSpaceBounds.RoundOut();
+ if (filterSpaceBounds.width <= 0 || filterSpaceBounds.height <= 0) {
+ // 0 disables rendering, < 0 is error. dispatch error console warning
+ // or error as appropriate.
+ return NS_ERROR_FAILURE;
+ }
+
+ // Set the filter space bounds.
+ if (!gfxUtils::GfxRectToIntRect(filterSpaceBounds, &mFilterSpaceBounds)) {
+ // The filter region is way too big if there is float -> int overflow.
+ return NS_ERROR_FAILURE;
+ }
+
+ mUserSpaceBounds = FilterSpaceToUserSpace(filterSpaceBounds);
+
+ return NS_OK;
+}
+
+nsSVGFilterFrame*
+nsSVGFilterInstance::GetFilterFrame(nsIFrame* aTargetFrame)
+{
+ if (mFilter.GetType() != NS_STYLE_FILTER_URL) {
+ // The filter is not an SVG reference filter.
+ return nullptr;
+ }
+
+ // Get the target element to use as a point of reference for looking up the
+ // filter element.
+ if (!mTargetContent) {
+ return nullptr;
+ }
+
+ // aTargetFrame can be null if this filter belongs to a
+ // CanvasRenderingContext2D.
+ nsCOMPtr<nsIURI> url = aTargetFrame
+ ? nsSVGEffects::GetFilterURI(aTargetFrame, mFilter)
+ : mFilter.GetURL()->ResolveLocalRef(mTargetContent);
+
+ if (!url) {
+ NS_NOTREACHED("an nsStyleFilter of type URL should have a non-null URL");
+ return nullptr;
+ }
+
+ // Look up the filter element by URL.
+ nsReferencedElement filterElement;
+ bool watch = false;
+ filterElement.Reset(mTargetContent, url, watch);
+ Element* element = filterElement.get();
+ if (!element) {
+ // The URL points to no element.
+ return nullptr;
+ }
+
+ // Get the frame of the filter element.
+ nsIFrame* frame = element->GetPrimaryFrame();
+ if (!frame || frame->GetType() != nsGkAtoms::svgFilterFrame) {
+ // The URL points to an element that's not an SVG filter element, or to an
+ // element that hasn't had its frame constructed yet.
+ return nullptr;
+ }
+
+ return static_cast<nsSVGFilterFrame*>(frame);
+}
+
+float
+nsSVGFilterInstance::GetPrimitiveNumber(uint8_t aCtxType, float aValue) const
+{
+ nsSVGLength2 val;
+ val.Init(aCtxType, 0xff, aValue,
+ nsIDOMSVGLength::SVG_LENGTHTYPE_NUMBER);
+
+ float value;
+ if (mPrimitiveUnits == SVG_UNIT_TYPE_OBJECTBOUNDINGBOX) {
+ value = nsSVGUtils::ObjectSpace(mTargetBBox, &val);
+ } else {
+ value = nsSVGUtils::UserSpace(mMetrics, &val);
+ }
+
+ switch (aCtxType) {
+ case SVGContentUtils::X:
+ return value * mUserSpaceToFilterSpaceScale.width;
+ case SVGContentUtils::Y:
+ return value * mUserSpaceToFilterSpaceScale.height;
+ case SVGContentUtils::XY:
+ default:
+ return value * SVGContentUtils::ComputeNormalizedHypotenuse(
+ mUserSpaceToFilterSpaceScale.width,
+ mUserSpaceToFilterSpaceScale.height);
+ }
+}
+
+Point3D
+nsSVGFilterInstance::ConvertLocation(const Point3D& aPoint) const
+{
+ nsSVGLength2 val[4];
+ val[0].Init(SVGContentUtils::X, 0xff, aPoint.x,
+ nsIDOMSVGLength::SVG_LENGTHTYPE_NUMBER);
+ val[1].Init(SVGContentUtils::Y, 0xff, aPoint.y,
+ nsIDOMSVGLength::SVG_LENGTHTYPE_NUMBER);
+ // Dummy width/height values
+ val[2].Init(SVGContentUtils::X, 0xff, 0,
+ nsIDOMSVGLength::SVG_LENGTHTYPE_NUMBER);
+ val[3].Init(SVGContentUtils::Y, 0xff, 0,
+ nsIDOMSVGLength::SVG_LENGTHTYPE_NUMBER);
+
+ gfxRect feArea = nsSVGUtils::GetRelativeRect(mPrimitiveUnits,
+ val, mTargetBBox, mMetrics);
+ gfxRect r = UserSpaceToFilterSpace(feArea);
+ return Point3D(r.x, r.y, GetPrimitiveNumber(SVGContentUtils::XY, aPoint.z));
+}
+
+gfxRect
+nsSVGFilterInstance::UserSpaceToFilterSpace(const gfxRect& aUserSpaceRect) const
+{
+ gfxRect filterSpaceRect = aUserSpaceRect;
+ filterSpaceRect.Scale(mUserSpaceToFilterSpaceScale.width,
+ mUserSpaceToFilterSpaceScale.height);
+ return filterSpaceRect;
+}
+
+gfxRect
+nsSVGFilterInstance::FilterSpaceToUserSpace(const gfxRect& aFilterSpaceRect) const
+{
+ gfxRect userSpaceRect = aFilterSpaceRect;
+ userSpaceRect.Scale(mFilterSpaceToUserSpaceScale.width,
+ mFilterSpaceToUserSpaceScale.height);
+ return userSpaceRect;
+}
+
+IntRect
+nsSVGFilterInstance::ComputeFilterPrimitiveSubregion(nsSVGFE* aFilterElement,
+ const nsTArray<FilterPrimitiveDescription>& aPrimitiveDescrs,
+ const nsTArray<int32_t>& aInputIndices)
+{
+ nsSVGFE* fE = aFilterElement;
+
+ IntRect defaultFilterSubregion(0,0,0,0);
+ if (fE->SubregionIsUnionOfRegions()) {
+ for (uint32_t i = 0; i < aInputIndices.Length(); ++i) {
+ int32_t inputIndex = aInputIndices[i];
+ bool isStandardInput = inputIndex < 0 || inputIndex == mSourceGraphicIndex;
+ IntRect inputSubregion = isStandardInput ?
+ mFilterSpaceBounds :
+ aPrimitiveDescrs[inputIndex].PrimitiveSubregion();
+
+ defaultFilterSubregion = defaultFilterSubregion.Union(inputSubregion);
+ }
+ } else {
+ defaultFilterSubregion = mFilterSpaceBounds;
+ }
+
+ gfxRect feArea = nsSVGUtils::GetRelativeRect(mPrimitiveUnits,
+ &fE->mLengthAttributes[nsSVGFE::ATTR_X], mTargetBBox, mMetrics);
+ Rect region = ToRect(UserSpaceToFilterSpace(feArea));
+
+ if (!fE->mLengthAttributes[nsSVGFE::ATTR_X].IsExplicitlySet())
+ region.x = defaultFilterSubregion.X();
+ if (!fE->mLengthAttributes[nsSVGFE::ATTR_Y].IsExplicitlySet())
+ region.y = defaultFilterSubregion.Y();
+ if (!fE->mLengthAttributes[nsSVGFE::ATTR_WIDTH].IsExplicitlySet())
+ region.width = defaultFilterSubregion.Width();
+ if (!fE->mLengthAttributes[nsSVGFE::ATTR_HEIGHT].IsExplicitlySet())
+ region.height = defaultFilterSubregion.Height();
+
+ // We currently require filter primitive subregions to be pixel-aligned.
+ // Following the spec, any pixel partially in the region is included
+ // in the region.
+ region.RoundOut();
+ return RoundedToInt(region);
+}
+
+void
+nsSVGFilterInstance::GetInputsAreTainted(const nsTArray<FilterPrimitiveDescription>& aPrimitiveDescrs,
+ const nsTArray<int32_t>& aInputIndices,
+ bool aFilterInputIsTainted,
+ nsTArray<bool>& aOutInputsAreTainted)
+{
+ for (uint32_t i = 0; i < aInputIndices.Length(); i++) {
+ int32_t inputIndex = aInputIndices[i];
+ if (inputIndex < 0) {
+ aOutInputsAreTainted.AppendElement(aFilterInputIsTainted);
+ } else {
+ aOutInputsAreTainted.AppendElement(aPrimitiveDescrs[inputIndex].IsTainted());
+ }
+ }
+}
+
+static int32_t
+GetLastResultIndex(const nsTArray<FilterPrimitiveDescription>& aPrimitiveDescrs)
+{
+ uint32_t numPrimitiveDescrs = aPrimitiveDescrs.Length();
+ return !numPrimitiveDescrs ?
+ FilterPrimitiveDescription::kPrimitiveIndexSourceGraphic :
+ numPrimitiveDescrs - 1;
+}
+
+int32_t
+nsSVGFilterInstance::GetOrCreateSourceAlphaIndex(nsTArray<FilterPrimitiveDescription>& aPrimitiveDescrs)
+{
+ // If the SourceAlpha index has already been determined or created for this
+ // SVG filter, just return it.
+ if (mSourceAlphaAvailable)
+ return mSourceAlphaIndex;
+
+ // If this is the first filter in the chain, we can just use the
+ // kPrimitiveIndexSourceAlpha keyword to refer to the SourceAlpha of the
+ // original image.
+ if (mSourceGraphicIndex < 0) {
+ mSourceAlphaIndex = FilterPrimitiveDescription::kPrimitiveIndexSourceAlpha;
+ mSourceAlphaAvailable = true;
+ return mSourceAlphaIndex;
+ }
+
+ // Otherwise, create a primitive description to turn the previous filter's
+ // output into a SourceAlpha input.
+ FilterPrimitiveDescription descr(PrimitiveType::ToAlpha);
+ descr.SetInputPrimitive(0, mSourceGraphicIndex);
+
+ const FilterPrimitiveDescription& sourcePrimitiveDescr =
+ aPrimitiveDescrs[mSourceGraphicIndex];
+ descr.SetPrimitiveSubregion(sourcePrimitiveDescr.PrimitiveSubregion());
+ descr.SetIsTainted(sourcePrimitiveDescr.IsTainted());
+
+ ColorSpace colorSpace = sourcePrimitiveDescr.OutputColorSpace();
+ descr.SetInputColorSpace(0, colorSpace);
+ descr.SetOutputColorSpace(colorSpace);
+
+ aPrimitiveDescrs.AppendElement(descr);
+ mSourceAlphaIndex = aPrimitiveDescrs.Length() - 1;
+ mSourceAlphaAvailable = true;
+ return mSourceAlphaIndex;
+}
+
+nsresult
+nsSVGFilterInstance::GetSourceIndices(nsSVGFE* aPrimitiveElement,
+ nsTArray<FilterPrimitiveDescription>& aPrimitiveDescrs,
+ const nsDataHashtable<nsStringHashKey, int32_t>& aImageTable,
+ nsTArray<int32_t>& aSourceIndices)
+{
+ AutoTArray<nsSVGStringInfo,2> sources;
+ aPrimitiveElement->GetSourceImageNames(sources);
+
+ for (uint32_t j = 0; j < sources.Length(); j++) {
+ nsAutoString str;
+ sources[j].mString->GetAnimValue(str, sources[j].mElement);
+
+ int32_t sourceIndex = 0;
+ if (str.EqualsLiteral("SourceGraphic")) {
+ sourceIndex = mSourceGraphicIndex;
+ } else if (str.EqualsLiteral("SourceAlpha")) {
+ sourceIndex = GetOrCreateSourceAlphaIndex(aPrimitiveDescrs);
+ } else if (str.EqualsLiteral("FillPaint")) {
+ sourceIndex = FilterPrimitiveDescription::kPrimitiveIndexFillPaint;
+ } else if (str.EqualsLiteral("StrokePaint")) {
+ sourceIndex = FilterPrimitiveDescription::kPrimitiveIndexStrokePaint;
+ } else if (str.EqualsLiteral("BackgroundImage") ||
+ str.EqualsLiteral("BackgroundAlpha")) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+ } else if (str.EqualsLiteral("")) {
+ sourceIndex = GetLastResultIndex(aPrimitiveDescrs);
+ } else {
+ bool inputExists = aImageTable.Get(str, &sourceIndex);
+ if (!inputExists)
+ return NS_ERROR_FAILURE;
+ }
+
+ aSourceIndices.AppendElement(sourceIndex);
+ }
+ return NS_OK;
+}
+
+nsresult
+nsSVGFilterInstance::BuildPrimitives(nsTArray<FilterPrimitiveDescription>& aPrimitiveDescrs,
+ nsTArray<RefPtr<SourceSurface>>& aInputImages,
+ bool aInputIsTainted)
+{
+ mSourceGraphicIndex = GetLastResultIndex(aPrimitiveDescrs);
+
+ // Clip previous filter's output to this filter's filter region.
+ if (mSourceGraphicIndex >= 0) {
+ FilterPrimitiveDescription& sourceDescr = aPrimitiveDescrs[mSourceGraphicIndex];
+ sourceDescr.SetPrimitiveSubregion(sourceDescr.PrimitiveSubregion().Intersect(mFilterSpaceBounds));
+ }
+
+ // Get the filter primitive elements.
+ nsTArray<RefPtr<nsSVGFE> > primitives;
+ for (nsIContent* child = mFilterElement->nsINode::GetFirstChild();
+ child;
+ child = child->GetNextSibling()) {
+ RefPtr<nsSVGFE> primitive;
+ CallQueryInterface(child, (nsSVGFE**)getter_AddRefs(primitive));
+ if (primitive) {
+ primitives.AppendElement(primitive);
+ }
+ }
+
+ // Maps source image name to source index.
+ nsDataHashtable<nsStringHashKey, int32_t> imageTable(8);
+
+ // The principal that we check principals of any loaded images against.
+ nsCOMPtr<nsIPrincipal> principal = mTargetContent->NodePrincipal();
+
+ for (uint32_t primitiveElementIndex = 0;
+ primitiveElementIndex < primitives.Length();
+ ++primitiveElementIndex) {
+ nsSVGFE* filter = primitives[primitiveElementIndex];
+
+ AutoTArray<int32_t,2> sourceIndices;
+ nsresult rv = GetSourceIndices(filter, aPrimitiveDescrs, imageTable, sourceIndices);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ IntRect primitiveSubregion =
+ ComputeFilterPrimitiveSubregion(filter, aPrimitiveDescrs, sourceIndices);
+
+ nsTArray<bool> sourcesAreTainted;
+ GetInputsAreTainted(aPrimitiveDescrs, sourceIndices, aInputIsTainted, sourcesAreTainted);
+
+ FilterPrimitiveDescription descr =
+ filter->GetPrimitiveDescription(this, primitiveSubregion, sourcesAreTainted, aInputImages);
+
+ descr.SetIsTainted(filter->OutputIsTainted(sourcesAreTainted, principal));
+ descr.SetFilterSpaceBounds(mFilterSpaceBounds);
+ descr.SetPrimitiveSubregion(primitiveSubregion.Intersect(descr.FilterSpaceBounds()));
+
+ for (uint32_t i = 0; i < sourceIndices.Length(); i++) {
+ int32_t inputIndex = sourceIndices[i];
+ descr.SetInputPrimitive(i, inputIndex);
+
+ ColorSpace inputColorSpace = inputIndex >= 0
+ ? aPrimitiveDescrs[inputIndex].OutputColorSpace()
+ : ColorSpace(ColorSpace::SRGB);
+
+ ColorSpace desiredInputColorSpace = filter->GetInputColorSpace(i, inputColorSpace);
+ descr.SetInputColorSpace(i, desiredInputColorSpace);
+ if (i == 0) {
+ // the output color space is whatever in1 is if there is an in1
+ descr.SetOutputColorSpace(desiredInputColorSpace);
+ }
+ }
+
+ if (sourceIndices.Length() == 0) {
+ descr.SetOutputColorSpace(filter->GetOutputColorSpace());
+ }
+
+ aPrimitiveDescrs.AppendElement(descr);
+ uint32_t primitiveDescrIndex = aPrimitiveDescrs.Length() - 1;
+
+ nsAutoString str;
+ filter->GetResultImageName().GetAnimValue(str, filter);
+ imageTable.Put(str, primitiveDescrIndex);
+ }
+
+ return NS_OK;
+}
diff --git a/layout/svg/nsSVGFilterInstance.h b/layout/svg/nsSVGFilterInstance.h
new file mode 100644
index 0000000000..a4001dbd4f
--- /dev/null
+++ b/layout/svg/nsSVGFilterInstance.h
@@ -0,0 +1,285 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef __NS_SVGFILTERINSTANCE_H__
+#define __NS_SVGFILTERINSTANCE_H__
+
+#include "gfxMatrix.h"
+#include "gfxRect.h"
+#include "nsSVGFilters.h"
+#include "nsSVGNumber2.h"
+#include "nsSVGNumberPair.h"
+#include "nsTArray.h"
+
+class nsSVGFilterFrame;
+struct nsStyleFilter;
+
+namespace mozilla {
+namespace dom {
+class SVGFilterElement;
+} // namespace dom
+} // namespace mozilla
+
+/**
+ * This class helps nsFilterInstance build its filter graph by processing a
+ * single SVG reference filter.
+ *
+ * In BuildPrimitives, this class iterates through the referenced <filter>
+ * element's primitive elements, creating a FilterPrimitiveDescription for
+ * each one.
+ *
+ * This class uses several different coordinate spaces, defined as follows:
+ *
+ * "user space"
+ * The filtered SVG element's user space or the filtered HTML element's
+ * CSS pixel space. The origin for an HTML element is the top left corner of
+ * its border box.
+ *
+ * "filter space"
+ * User space scaled to device pixels. Shares the same origin as user space.
+ * This space is the same across chained SVG and CSS filters. To compute the
+ * overall filter space for a chain, we first need to build each filter's
+ * FilterPrimitiveDescriptions in some common space. That space is
+ * filter space.
+ *
+ * To understand the spaces better, let's take an example filter:
+ * <filter id="f">...</filter>
+ *
+ * And apply the filter to a div element:
+ * <div style="filter: url(#f); ...">...</div>
+ *
+ * And let's say there are 2 device pixels for every 1 CSS pixel.
+ *
+ * Finally, let's define an arbitrary point in user space:
+ * "user space point" = (10, 10)
+ *
+ * The point will be inset 10 CSS pixels from both the top and left edges of the
+ * div element's border box.
+ *
+ * Now, let's transform the point from user space to filter space:
+ * "filter space point" = "user space point" * "device pixels per CSS pixel"
+ * "filter space point" = (10, 10) * 2
+ * "filter space point" = (20, 20)
+ */
+class nsSVGFilterInstance
+{
+ typedef mozilla::gfx::Point3D Point3D;
+ typedef mozilla::gfx::IntRect IntRect;
+ typedef mozilla::gfx::SourceSurface SourceSurface;
+ typedef mozilla::gfx::FilterPrimitiveDescription FilterPrimitiveDescription;
+ typedef mozilla::dom::UserSpaceMetrics UserSpaceMetrics;
+
+public:
+ /**
+ * @param aFilter The SVG filter reference from the style system. This class
+ * stores aFilter by reference, so callers should avoid modifying or
+ * deleting aFilter during the lifetime of nsSVGFilterInstance.
+ * @param aTargetContent The filtered element.
+ * @param aTargetBBox The SVG bbox to use for the target frame, computed by
+ * the caller. The caller may decide to override the actual SVG bbox.
+ */
+ nsSVGFilterInstance(const nsStyleFilter& aFilter,
+ nsIFrame* aTargetFrame,
+ nsIContent* aTargetContent,
+ const UserSpaceMetrics& aMetrics,
+ const gfxRect& aTargetBBox,
+ const gfxSize& aUserSpaceToFilterSpaceScale,
+ const gfxSize& aFilterSpaceToUserSpaceScale);
+
+ /**
+ * Returns true if the filter instance was created successfully.
+ */
+ bool IsInitialized() const { return mInitialized; }
+
+ /**
+ * Iterates through the <filter> element's primitive elements, creating a
+ * FilterPrimitiveDescription for each one. Appends the new
+ * FilterPrimitiveDescription(s) to the aPrimitiveDescrs list. Also, appends
+ * new images from feImage filter primitive elements to the aInputImages list.
+ * aInputIsTainted describes whether the input to this filter is tainted, i.e.
+ * whether it contains security-sensitive content. This is needed to propagate
+ * taintedness to the FilterPrimitive that take tainted inputs. Something being
+ * tainted means that it contains security sensitive content.
+ * The input to this filter is the previous filter's output, i.e. the last
+ * element in aPrimitiveDescrs, or the SourceGraphic input if this is the first
+ * filter in the filter chain.
+ */
+ nsresult BuildPrimitives(nsTArray<FilterPrimitiveDescription>& aPrimitiveDescrs,
+ nsTArray<RefPtr<SourceSurface>>& aInputImages,
+ bool aInputIsTainted);
+
+ /**
+ * Returns the user specified "filter region", in the filtered element's user
+ * space, after it has been adjusted out (if necessary) so that its edges
+ * coincide with pixel boundaries of the offscreen surface into which the
+ * filtered output would/will be painted.
+ */
+ gfxRect GetFilterRegion() const { return mUserSpaceBounds; }
+
+ /**
+ * Returns the size of the user specified "filter region", in filter space.
+ */
+ nsIntRect GetFilterSpaceBounds() const { return mFilterSpaceBounds; }
+
+ float GetPrimitiveNumber(uint8_t aCtxType, const nsSVGNumber2 *aNumber) const
+ {
+ return GetPrimitiveNumber(aCtxType, aNumber->GetAnimValue());
+ }
+ float GetPrimitiveNumber(uint8_t aCtxType, const nsSVGNumberPair *aNumberPair,
+ nsSVGNumberPair::PairIndex aIndex) const
+ {
+ return GetPrimitiveNumber(aCtxType, aNumberPair->GetAnimValue(aIndex));
+ }
+
+ /**
+ * Converts a userSpaceOnUse/objectBoundingBoxUnits unitless point
+ * into filter space, depending on the value of mPrimitiveUnits. (For
+ * objectBoundingBoxUnits, the bounding box offset is applied to the point.)
+ */
+ Point3D ConvertLocation(const Point3D& aPoint) const;
+
+ /**
+ * Transform a rect between user space and filter space.
+ */
+ gfxRect UserSpaceToFilterSpace(const gfxRect& aUserSpaceRect) const;
+
+private:
+ /**
+ * Finds the filter frame associated with this SVG filter.
+ */
+ nsSVGFilterFrame* GetFilterFrame(nsIFrame* aTargetFrame);
+
+ /**
+ * Computes the filter primitive subregion for the given primitive.
+ */
+ IntRect ComputeFilterPrimitiveSubregion(nsSVGFE* aFilterElement,
+ const nsTArray<FilterPrimitiveDescription>& aPrimitiveDescrs,
+ const nsTArray<int32_t>& aInputIndices);
+
+ /**
+ * Takes the input indices of a filter primitive and returns for each input
+ * whether the input's output is tainted.
+ */
+ void GetInputsAreTainted(const nsTArray<FilterPrimitiveDescription>& aPrimitiveDescrs,
+ const nsTArray<int32_t>& aInputIndices,
+ bool aFilterInputIsTainted,
+ nsTArray<bool>& aOutInputsAreTainted);
+
+ /**
+ * Scales a numeric filter primitive length in the X, Y or "XY" directions
+ * into a length in filter space (no offset is applied).
+ */
+ float GetPrimitiveNumber(uint8_t aCtxType, float aValue) const;
+
+ /**
+ * Transform a rect between user space and filter space.
+ */
+ gfxRect FilterSpaceToUserSpace(const gfxRect& aFilterSpaceRect) const;
+
+ /**
+ * Returns the transform from frame space to the coordinate space that
+ * GetCanvasTM transforms to. "Frame space" is the origin of a frame, aka the
+ * top-left corner of its border box, aka the top left corner of its mRect.
+ */
+ gfxMatrix GetUserSpaceToFrameSpaceInCSSPxTransform() const;
+
+ /**
+ * Appends a new FilterPrimitiveDescription to aPrimitiveDescrs that
+ * converts the FilterPrimitiveDescription at mSourceGraphicIndex into
+ * a SourceAlpha input for the next FilterPrimitiveDescription.
+ *
+ * The new FilterPrimitiveDescription zeros out the SourceGraphic's RGB
+ * channels and keeps the alpha channel intact.
+ */
+ int32_t GetOrCreateSourceAlphaIndex(nsTArray<FilterPrimitiveDescription>& aPrimitiveDescrs);
+
+ /**
+ * Finds the index in aPrimitiveDescrs of each input to aPrimitiveElement.
+ * For example, if aPrimitiveElement is:
+ * <feGaussianBlur in="another-primitive" .../>
+ * Then, the resulting aSourceIndices will contain the index of the
+ * FilterPrimitiveDescription representing "another-primitive".
+ */
+ nsresult GetSourceIndices(nsSVGFE* aPrimitiveElement,
+ nsTArray<FilterPrimitiveDescription>& aPrimitiveDescrs,
+ const nsDataHashtable<nsStringHashKey, int32_t>& aImageTable,
+ nsTArray<int32_t>& aSourceIndices);
+
+ /**
+ * Compute the filter region in user space, filter space, and filter
+ * space.
+ */
+ nsresult ComputeBounds();
+
+ /**
+ * The SVG reference filter originally from the style system.
+ */
+ const nsStyleFilter& mFilter;
+
+ /**
+ * The filtered element.
+ */
+ nsIContent* mTargetContent;
+
+ /**
+ * The SVG user space metrics that SVG lengths are resolved against.
+ */
+ const UserSpaceMetrics& mMetrics;
+
+ /**
+ * The filter element referenced by mTargetFrame's element.
+ */
+ const mozilla::dom::SVGFilterElement* mFilterElement;
+
+ /**
+ * The frame for the SVG filter element.
+ */
+ nsSVGFilterFrame* mFilterFrame;
+
+ /**
+ * The SVG bbox of the element that is being filtered, in user space.
+ */
+ gfxRect mTargetBBox;
+
+ /**
+ * The "filter region" in various spaces.
+ */
+ gfxRect mUserSpaceBounds;
+ nsIntRect mFilterSpaceBounds;
+
+ /**
+ * The scale factors between user space and filter space.
+ */
+ gfxSize mUserSpaceToFilterSpaceScale;
+ gfxSize mFilterSpaceToUserSpaceScale;
+
+ /**
+ * The 'primitiveUnits' attribute value (objectBoundingBox or userSpaceOnUse).
+ */
+ uint16_t mPrimitiveUnits;
+
+ /**
+ * The index of the FilterPrimitiveDescription that this SVG filter should use
+ * as its SourceGraphic, or the SourceGraphic keyword index if this is the
+ * first filter in a chain. Initialized in BuildPrimitives
+ */
+ MOZ_INIT_OUTSIDE_CTOR int32_t mSourceGraphicIndex;
+
+ /**
+ * The index of the FilterPrimitiveDescription that this SVG filter should use
+ * as its SourceAlpha, or the SourceAlpha keyword index if this is the first
+ * filter in a chain. Initialized in BuildPrimitives
+ */
+ MOZ_INIT_OUTSIDE_CTOR int32_t mSourceAlphaIndex;
+
+ /**
+ * SourceAlpha is available if GetOrCreateSourceAlphaIndex has been called.
+ */
+ int32_t mSourceAlphaAvailable;
+
+ bool mInitialized;
+};
+
+#endif
diff --git a/layout/svg/nsSVGFilterPaintCallback.h b/layout/svg/nsSVGFilterPaintCallback.h
new file mode 100644
index 0000000000..3c4bf3f707
--- /dev/null
+++ b/layout/svg/nsSVGFilterPaintCallback.h
@@ -0,0 +1,35 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef __NS_SVGFILTERPAINTCALLBACK_H__
+#define __NS_SVGFILTERPAINTCALLBACK_H__
+
+#include "nsRect.h"
+
+class nsIFrame;
+class gfxContext;
+
+class nsSVGFilterPaintCallback {
+public:
+ typedef mozilla::image::DrawResult DrawResult;
+
+ /**
+ * Paint the frame contents.
+ * SVG frames will have had matrix propagation set to false already.
+ * Non-SVG frames have to do their own thing.
+ * The caller will do a Save()/Restore() as necessary so feel free
+ * to mess with context state.
+ * The context will be configured to use the "user space" coordinate
+ * system.
+ * @param aDirtyRect the dirty rect *in user space pixels*
+ * @param aTransformRoot the outermost frame whose transform should be taken
+ * into account when painting an SVG glyph
+ */
+ virtual DrawResult Paint(gfxContext& aContext, nsIFrame *aTarget,
+ const gfxMatrix& aTransform,
+ const nsIntRect *aDirtyRect) = 0;
+};
+
+#endif
diff --git a/layout/svg/nsSVGForeignObjectFrame.cpp b/layout/svg/nsSVGForeignObjectFrame.cpp
new file mode 100644
index 0000000000..afa5912d2c
--- /dev/null
+++ b/layout/svg/nsSVGForeignObjectFrame.cpp
@@ -0,0 +1,592 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+// Main header first:
+#include "nsSVGForeignObjectFrame.h"
+
+// Keep others in (case-insensitive) order:
+#include "DrawResult.h"
+#include "gfxContext.h"
+#include "nsDisplayList.h"
+#include "nsGkAtoms.h"
+#include "nsNameSpaceManager.h"
+#include "nsLayoutUtils.h"
+#include "nsRegion.h"
+#include "nsRenderingContext.h"
+#include "nsSVGContainerFrame.h"
+#include "nsSVGEffects.h"
+#include "mozilla/dom/SVGForeignObjectElement.h"
+#include "nsSVGIntegrationUtils.h"
+#include "nsSVGOuterSVGFrame.h"
+#include "nsSVGUtils.h"
+#include "mozilla/AutoRestore.h"
+
+using namespace mozilla;
+using namespace mozilla::dom;
+using namespace mozilla::image;
+
+//----------------------------------------------------------------------
+// Implementation
+
+nsContainerFrame*
+NS_NewSVGForeignObjectFrame(nsIPresShell *aPresShell,
+ nsStyleContext *aContext)
+{
+ return new (aPresShell) nsSVGForeignObjectFrame(aContext);
+}
+
+NS_IMPL_FRAMEARENA_HELPERS(nsSVGForeignObjectFrame)
+
+nsSVGForeignObjectFrame::nsSVGForeignObjectFrame(nsStyleContext* aContext)
+ : nsContainerFrame(aContext)
+ , mInReflow(false)
+{
+ AddStateBits(NS_FRAME_REFLOW_ROOT | NS_FRAME_MAY_BE_TRANSFORMED |
+ NS_FRAME_SVG_LAYOUT);
+}
+
+//----------------------------------------------------------------------
+// nsIFrame methods
+
+NS_QUERYFRAME_HEAD(nsSVGForeignObjectFrame)
+ NS_QUERYFRAME_ENTRY(nsISVGChildFrame)
+NS_QUERYFRAME_TAIL_INHERITING(nsContainerFrame)
+
+void
+nsSVGForeignObjectFrame::Init(nsIContent* aContent,
+ nsContainerFrame* aParent,
+ nsIFrame* aPrevInFlow)
+{
+ NS_ASSERTION(aContent->IsSVGElement(nsGkAtoms::foreignObject),
+ "Content is not an SVG foreignObject!");
+
+ nsContainerFrame::Init(aContent, aParent, aPrevInFlow);
+ AddStateBits(aParent->GetStateBits() & NS_STATE_SVG_CLIPPATH_CHILD);
+ AddStateBits(NS_FRAME_FONT_INFLATION_CONTAINER |
+ NS_FRAME_FONT_INFLATION_FLOW_ROOT);
+ if (!(mState & NS_FRAME_IS_NONDISPLAY)) {
+ nsSVGUtils::GetOuterSVGFrame(this)->RegisterForeignObject(this);
+ }
+}
+
+void nsSVGForeignObjectFrame::DestroyFrom(nsIFrame* aDestructRoot)
+{
+ // Only unregister if we registered in the first place:
+ if (!(mState & NS_FRAME_IS_NONDISPLAY)) {
+ nsSVGUtils::GetOuterSVGFrame(this)->UnregisterForeignObject(this);
+ }
+ nsContainerFrame::DestroyFrom(aDestructRoot);
+}
+
+nsIAtom *
+nsSVGForeignObjectFrame::GetType() const
+{
+ return nsGkAtoms::svgForeignObjectFrame;
+}
+
+nsresult
+nsSVGForeignObjectFrame::AttributeChanged(int32_t aNameSpaceID,
+ nsIAtom *aAttribute,
+ int32_t aModType)
+{
+ if (aNameSpaceID == kNameSpaceID_None) {
+ if (aAttribute == nsGkAtoms::width ||
+ aAttribute == nsGkAtoms::height) {
+ nsLayoutUtils::PostRestyleEvent(
+ mContent->AsElement(), nsRestyleHint(0),
+ nsChangeHint_InvalidateRenderingObservers);
+ nsSVGUtils::ScheduleReflowSVG(this);
+ // XXXjwatt: why mark intrinsic widths dirty? can't we just use eResize?
+ RequestReflow(nsIPresShell::eStyleChange);
+ } else if (aAttribute == nsGkAtoms::x ||
+ aAttribute == nsGkAtoms::y) {
+ // make sure our cached transform matrix gets (lazily) updated
+ mCanvasTM = nullptr;
+ nsLayoutUtils::PostRestyleEvent(
+ mContent->AsElement(), nsRestyleHint(0),
+ nsChangeHint_InvalidateRenderingObservers);
+ nsSVGUtils::ScheduleReflowSVG(this);
+ } else if (aAttribute == nsGkAtoms::transform) {
+ // We don't invalidate for transform changes (the layers code does that).
+ // Also note that SVGTransformableElement::GetAttributeChangeHint will
+ // return nsChangeHint_UpdateOverflow for "transform" attribute changes
+ // and cause DoApplyRenderingChangeToTree to make the SchedulePaint call.
+ mCanvasTM = nullptr;
+ } else if (aAttribute == nsGkAtoms::viewBox ||
+ aAttribute == nsGkAtoms::preserveAspectRatio) {
+ nsLayoutUtils::PostRestyleEvent(
+ mContent->AsElement(), nsRestyleHint(0),
+ nsChangeHint_InvalidateRenderingObservers);
+ }
+ }
+
+ return NS_OK;
+}
+
+void
+nsSVGForeignObjectFrame::Reflow(nsPresContext* aPresContext,
+ ReflowOutput& aDesiredSize,
+ const ReflowInput& aReflowInput,
+ nsReflowStatus& aStatus)
+{
+ MOZ_ASSERT(!(GetStateBits() & NS_FRAME_IS_NONDISPLAY),
+ "Should not have been called");
+
+ // Only InvalidateAndScheduleBoundsUpdate marks us with NS_FRAME_IS_DIRTY,
+ // so if that bit is still set we still have a resize pending. If we hit
+ // this assertion, then we should get the presShell to skip reflow roots
+ // that have a dirty parent since a reflow is going to come via the
+ // reflow root's parent anyway.
+ NS_ASSERTION(!(GetStateBits() & NS_FRAME_IS_DIRTY),
+ "Reflowing while a resize is pending is wasteful");
+
+ // ReflowSVG makes sure mRect is up to date before we're called.
+
+ NS_ASSERTION(!aReflowInput.mParentReflowInput,
+ "should only get reflow from being reflow root");
+ NS_ASSERTION(aReflowInput.ComputedWidth() == GetSize().width &&
+ aReflowInput.ComputedHeight() == GetSize().height,
+ "reflow roots should be reflowed at existing size and "
+ "svg.css should ensure we have no padding/border/margin");
+
+ DoReflow();
+
+ WritingMode wm = aReflowInput.GetWritingMode();
+ LogicalSize finalSize(wm, aReflowInput.ComputedISize(),
+ aReflowInput.ComputedBSize());
+ aDesiredSize.SetSize(wm, finalSize);
+ aDesiredSize.SetOverflowAreasToDesiredBounds();
+ aStatus = NS_FRAME_COMPLETE;
+}
+
+void
+nsSVGForeignObjectFrame::BuildDisplayList(nsDisplayListBuilder* aBuilder,
+ const nsRect& aDirtyRect,
+ const nsDisplayListSet& aLists)
+{
+ if (!static_cast<const nsSVGElement*>(mContent)->HasValidDimensions()) {
+ return;
+ }
+ DisplayOutline(aBuilder, aLists);
+ BuildDisplayListForNonBlockChildren(aBuilder, aDirtyRect, aLists);
+}
+
+bool
+nsSVGForeignObjectFrame::IsSVGTransformed(Matrix *aOwnTransform,
+ Matrix *aFromParentTransform) const
+{
+ bool foundTransform = false;
+
+ // Check if our parent has children-only transforms:
+ nsIFrame *parent = GetParent();
+ if (parent &&
+ parent->IsFrameOfType(nsIFrame::eSVG | nsIFrame::eSVGContainer)) {
+ foundTransform = static_cast<nsSVGContainerFrame*>(parent)->
+ HasChildrenOnlyTransform(aFromParentTransform);
+ }
+
+ nsSVGElement *content = static_cast<nsSVGElement*>(mContent);
+ nsSVGAnimatedTransformList* transformList =
+ content->GetAnimatedTransformList();
+ if ((transformList && transformList->HasTransform()) ||
+ content->GetAnimateMotionTransform()) {
+ if (aOwnTransform) {
+ *aOwnTransform = gfx::ToMatrix(content->PrependLocalTransformsTo(
+ gfxMatrix(),
+ eUserSpaceToParent));
+ }
+ foundTransform = true;
+ }
+ return foundTransform;
+}
+
+DrawResult
+nsSVGForeignObjectFrame::PaintSVG(gfxContext& aContext,
+ const gfxMatrix& aTransform,
+ const nsIntRect* aDirtyRect)
+{
+ NS_ASSERTION(!NS_SVGDisplayListPaintingEnabled() ||
+ (mState & NS_FRAME_IS_NONDISPLAY),
+ "If display lists are enabled, only painting of non-display "
+ "SVG should take this code path");
+
+ if (IsDisabled())
+ return DrawResult::SUCCESS;
+
+ nsIFrame* kid = PrincipalChildList().FirstChild();
+ if (!kid)
+ return DrawResult::SUCCESS;
+
+ if (aTransform.IsSingular()) {
+ NS_WARNING("Can't render foreignObject element!");
+ return DrawResult::BAD_ARGS;
+ }
+
+ nsRect kidDirtyRect = kid->GetVisualOverflowRect();
+
+ /* Check if we need to draw anything. */
+ if (aDirtyRect) {
+ NS_ASSERTION(!NS_SVGDisplayListPaintingEnabled() ||
+ (mState & NS_FRAME_IS_NONDISPLAY),
+ "Display lists handle dirty rect intersection test");
+ // Transform the dirty rect into app units in our userspace.
+ gfxMatrix invmatrix = aTransform;
+ DebugOnly<bool> ok = invmatrix.Invert();
+ NS_ASSERTION(ok, "inverse of non-singular matrix should be non-singular");
+
+ gfxRect transDirtyRect = gfxRect(aDirtyRect->x, aDirtyRect->y,
+ aDirtyRect->width, aDirtyRect->height);
+ transDirtyRect = invmatrix.TransformBounds(transDirtyRect);
+
+ kidDirtyRect.IntersectRect(kidDirtyRect,
+ nsLayoutUtils::RoundGfxRectToAppRect(transDirtyRect,
+ PresContext()->AppUnitsPerCSSPixel()));
+
+ // XXX after bug 614732 is fixed, we will compare mRect with aDirtyRect,
+ // not with kidDirtyRect. I.e.
+ // int32_t appUnitsPerDevPx = PresContext()->AppUnitsPerDevPixel();
+ // mRect.ToOutsidePixels(appUnitsPerDevPx).Intersects(*aDirtyRect)
+ if (kidDirtyRect.IsEmpty())
+ return DrawResult::SUCCESS;
+ }
+
+ aContext.Save();
+
+ if (StyleDisplay()->IsScrollableOverflow()) {
+ float x, y, width, height;
+ static_cast<nsSVGElement*>(mContent)->
+ GetAnimatedLengthValues(&x, &y, &width, &height, nullptr);
+
+ gfxRect clipRect =
+ nsSVGUtils::GetClipRectForFrame(this, 0.0f, 0.0f, width, height);
+ nsSVGUtils::SetClipRect(&aContext, aTransform, clipRect);
+ }
+
+ // SVG paints in CSS px, but normally frames paint in dev pixels. Here we
+ // multiply a CSS-px-to-dev-pixel factor onto aTransform so our children
+ // paint correctly.
+ float cssPxPerDevPx = PresContext()->
+ AppUnitsToFloatCSSPixels(PresContext()->AppUnitsPerDevPixel());
+ gfxMatrix canvasTMForChildren = aTransform;
+ canvasTMForChildren.Scale(cssPxPerDevPx, cssPxPerDevPx);
+
+ aContext.Multiply(canvasTMForChildren);
+
+ using PaintFrameFlags = nsLayoutUtils::PaintFrameFlags;
+ PaintFrameFlags flags = PaintFrameFlags::PAINT_IN_TRANSFORM;
+ if (SVGAutoRenderState::IsPaintingToWindow(aContext.GetDrawTarget())) {
+ flags |= PaintFrameFlags::PAINT_TO_WINDOW;
+ }
+ nsRenderingContext rendCtx(&aContext);
+ nsresult rv = nsLayoutUtils::PaintFrame(&rendCtx, kid, nsRegion(kidDirtyRect),
+ NS_RGBA(0,0,0,0),
+ nsDisplayListBuilderMode::PAINTING,
+ flags);
+
+ aContext.Restore();
+
+ return NS_FAILED(rv) ? DrawResult::BAD_ARGS : DrawResult::SUCCESS;
+}
+
+nsIFrame*
+nsSVGForeignObjectFrame::GetFrameForPoint(const gfxPoint& aPoint)
+{
+ NS_ASSERTION(!NS_SVGDisplayListHitTestingEnabled() ||
+ (mState & NS_FRAME_IS_NONDISPLAY),
+ "If display lists are enabled, only hit-testing of a "
+ "clipPath's contents should take this code path");
+
+ if (IsDisabled() || (GetStateBits() & NS_FRAME_IS_NONDISPLAY))
+ return nullptr;
+
+ nsIFrame* kid = PrincipalChildList().FirstChild();
+ if (!kid)
+ return nullptr;
+
+ float x, y, width, height;
+ static_cast<nsSVGElement*>(mContent)->
+ GetAnimatedLengthValues(&x, &y, &width, &height, nullptr);
+
+ if (!gfxRect(x, y, width, height).Contains(aPoint) ||
+ !nsSVGUtils::HitTestClip(this, aPoint)) {
+ return nullptr;
+ }
+
+ // Convert the point to app units relative to the top-left corner of the
+ // viewport that's established by the foreignObject element:
+
+ gfxPoint pt = (aPoint + gfxPoint(x, y)) * nsPresContext::AppUnitsPerCSSPixel();
+ nsPoint point = nsPoint(NSToIntRound(pt.x), NSToIntRound(pt.y));
+
+ return nsLayoutUtils::GetFrameForPoint(kid, point);
+}
+
+nsRect
+nsSVGForeignObjectFrame::GetCoveredRegion()
+{
+ float x, y, w, h;
+ static_cast<SVGForeignObjectElement*>(mContent)->
+ GetAnimatedLengthValues(&x, &y, &w, &h, nullptr);
+ if (w < 0.0f) w = 0.0f;
+ if (h < 0.0f) h = 0.0f;
+ // GetCanvasTM includes the x,y translation
+ return nsSVGUtils::ToCanvasBounds(gfxRect(0.0, 0.0, w, h),
+ GetCanvasTM(),
+ PresContext());
+}
+
+void
+nsSVGForeignObjectFrame::ReflowSVG()
+{
+ NS_ASSERTION(nsSVGUtils::OuterSVGIsCallingReflowSVG(this),
+ "This call is probably a wasteful mistake");
+
+ MOZ_ASSERT(!(GetStateBits() & NS_FRAME_IS_NONDISPLAY),
+ "ReflowSVG mechanism not designed for this");
+
+ if (!nsSVGUtils::NeedsReflowSVG(this)) {
+ return;
+ }
+
+ // We update mRect before the DoReflow call so that DoReflow uses the
+ // correct dimensions:
+
+ float x, y, w, h;
+ static_cast<SVGForeignObjectElement*>(mContent)->
+ GetAnimatedLengthValues(&x, &y, &w, &h, nullptr);
+
+ // If mRect's width or height are negative, reflow blows up! We must clamp!
+ if (w < 0.0f) w = 0.0f;
+ if (h < 0.0f) h = 0.0f;
+
+ mRect = nsLayoutUtils::RoundGfxRectToAppRect(
+ gfxRect(x, y, w, h),
+ PresContext()->AppUnitsPerCSSPixel());
+
+ // Fully mark our kid dirty so that it gets resized if necessary
+ // (NS_FRAME_HAS_DIRTY_CHILDREN isn't enough in that case):
+ nsIFrame* kid = PrincipalChildList().FirstChild();
+ kid->AddStateBits(NS_FRAME_IS_DIRTY);
+
+ // Make sure to not allow interrupts if we're not being reflown as a root:
+ nsPresContext::InterruptPreventer noInterrupts(PresContext());
+
+ DoReflow();
+
+ if (mState & NS_FRAME_FIRST_REFLOW) {
+ // Make sure we have our filter property (if any) before calling
+ // FinishAndStoreOverflow (subsequent filter changes are handled off
+ // nsChangeHint_UpdateEffects):
+ nsSVGEffects::UpdateEffects(this);
+ }
+
+ // If we have a filter, we need to invalidate ourselves because filter
+ // output can change even if none of our descendants need repainting.
+ if (StyleEffects()->HasFilters()) {
+ InvalidateFrame();
+ }
+
+ // TODO: once we support |overflow:visible| on foreignObject, then we will
+ // need to take account of our descendants here.
+ nsRect overflow = nsRect(nsPoint(0,0), mRect.Size());
+ nsOverflowAreas overflowAreas(overflow, overflow);
+ FinishAndStoreOverflow(overflowAreas, mRect.Size());
+
+ // Now unset the various reflow bits:
+ mState &= ~(NS_FRAME_FIRST_REFLOW | NS_FRAME_IS_DIRTY |
+ NS_FRAME_HAS_DIRTY_CHILDREN);
+}
+
+void
+nsSVGForeignObjectFrame::NotifySVGChanged(uint32_t aFlags)
+{
+ MOZ_ASSERT(aFlags & (TRANSFORM_CHANGED | COORD_CONTEXT_CHANGED),
+ "Invalidation logic may need adjusting");
+
+ bool needNewBounds = false; // i.e. mRect or visual overflow rect
+ bool needReflow = false;
+ bool needNewCanvasTM = false;
+
+ if (aFlags & COORD_CONTEXT_CHANGED) {
+ SVGForeignObjectElement *fO =
+ static_cast<SVGForeignObjectElement*>(mContent);
+ // Coordinate context changes affect mCanvasTM if we have a
+ // percentage 'x' or 'y'
+ if (fO->mLengthAttributes[SVGForeignObjectElement::ATTR_X].IsPercentage() ||
+ fO->mLengthAttributes[SVGForeignObjectElement::ATTR_Y].IsPercentage()) {
+ needNewBounds = true;
+ needNewCanvasTM = true;
+ }
+ // Our coordinate context's width/height has changed. If we have a
+ // percentage width/height our dimensions will change so we must reflow.
+ if (fO->mLengthAttributes[SVGForeignObjectElement::ATTR_WIDTH].IsPercentage() ||
+ fO->mLengthAttributes[SVGForeignObjectElement::ATTR_HEIGHT].IsPercentage()) {
+ needNewBounds = true;
+ needReflow = true;
+ }
+ }
+
+ if (aFlags & TRANSFORM_CHANGED) {
+ if (mCanvasTM && mCanvasTM->IsSingular()) {
+ needNewBounds = true; // old bounds are bogus
+ }
+ needNewCanvasTM = true;
+ // In an ideal world we would reflow when our CTM changes. This is because
+ // glyph metrics do not necessarily scale uniformly with change in scale
+ // and, as a result, CTM changes may require text to break at different
+ // points. The problem would be how to keep performance acceptable when
+ // e.g. the transform of an ancestor is animated.
+ // We also seem to get some sort of infinite loop post bug 421584 if we
+ // reflow.
+ }
+
+ if (needNewBounds) {
+ // Ancestor changes can't affect how we render from the perspective of
+ // any rendering observers that we may have, so we don't need to
+ // invalidate them. We also don't need to invalidate ourself, since our
+ // changed ancestor will have invalidated its entire area, which includes
+ // our area.
+ nsSVGUtils::ScheduleReflowSVG(this);
+ }
+
+ // If we're called while the PresShell is handling reflow events then we
+ // must have been called as a result of the NotifyViewportChange() call in
+ // our nsSVGOuterSVGFrame's Reflow() method. We must not call RequestReflow
+ // at this point (i.e. during reflow) because it could confuse the
+ // PresShell and prevent it from reflowing us properly in future. Besides
+ // that, nsSVGOuterSVGFrame::DidReflow will take care of reflowing us
+ // synchronously, so there's no need.
+ if (needReflow && !PresContext()->PresShell()->IsReflowLocked()) {
+ RequestReflow(nsIPresShell::eResize);
+ }
+
+ if (needNewCanvasTM) {
+ // Do this after calling InvalidateAndScheduleBoundsUpdate in case we
+ // change the code and it needs to use it.
+ mCanvasTM = nullptr;
+ }
+}
+
+SVGBBox
+nsSVGForeignObjectFrame::GetBBoxContribution(const Matrix &aToBBoxUserspace,
+ uint32_t aFlags)
+{
+ SVGForeignObjectElement *content =
+ static_cast<SVGForeignObjectElement*>(mContent);
+
+ float x, y, w, h;
+ content->GetAnimatedLengthValues(&x, &y, &w, &h, nullptr);
+
+ if (w < 0.0f) w = 0.0f;
+ if (h < 0.0f) h = 0.0f;
+
+ if (aToBBoxUserspace.IsSingular()) {
+ // XXX ReportToConsole
+ return SVGBBox();
+ }
+ return aToBBoxUserspace.TransformBounds(gfx::Rect(0.0, 0.0, w, h));
+}
+
+//----------------------------------------------------------------------
+
+gfxMatrix
+nsSVGForeignObjectFrame::GetCanvasTM()
+{
+ if (!mCanvasTM) {
+ NS_ASSERTION(GetParent(), "null parent");
+
+ nsSVGContainerFrame *parent = static_cast<nsSVGContainerFrame*>(GetParent());
+ SVGForeignObjectElement *content =
+ static_cast<SVGForeignObjectElement*>(mContent);
+
+ gfxMatrix tm = content->PrependLocalTransformsTo(parent->GetCanvasTM());
+
+ mCanvasTM = new gfxMatrix(tm);
+ }
+ return *mCanvasTM;
+}
+
+//----------------------------------------------------------------------
+// Implementation helpers
+
+void nsSVGForeignObjectFrame::RequestReflow(nsIPresShell::IntrinsicDirty aType)
+{
+ if (GetStateBits() & NS_FRAME_FIRST_REFLOW)
+ // If we haven't had a ReflowSVG() yet, nothing to do.
+ return;
+
+ nsIFrame* kid = PrincipalChildList().FirstChild();
+ if (!kid)
+ return;
+
+ PresContext()->PresShell()->FrameNeedsReflow(kid, aType, NS_FRAME_IS_DIRTY);
+}
+
+void
+nsSVGForeignObjectFrame::DoReflow()
+{
+ MarkInReflow();
+ // Skip reflow if we're zero-sized, unless this is our first reflow.
+ if (IsDisabled() &&
+ !(GetStateBits() & NS_FRAME_FIRST_REFLOW))
+ return;
+
+ nsPresContext *presContext = PresContext();
+ nsIFrame* kid = PrincipalChildList().FirstChild();
+ if (!kid)
+ return;
+
+ // initiate a synchronous reflow here and now:
+ nsRenderingContext renderingContext(
+ presContext->PresShell()->CreateReferenceRenderingContext());
+
+ mInReflow = true;
+
+ WritingMode wm = kid->GetWritingMode();
+ ReflowInput reflowInput(presContext, kid,
+ &renderingContext,
+ LogicalSize(wm, ISize(wm),
+ NS_UNCONSTRAINEDSIZE));
+ ReflowOutput desiredSize(reflowInput);
+ nsReflowStatus status;
+
+ // We don't use mRect.height above because that tells the child to do
+ // page/column breaking at that height.
+ NS_ASSERTION(reflowInput.ComputedPhysicalBorderPadding() == nsMargin(0, 0, 0, 0) &&
+ reflowInput.ComputedPhysicalMargin() == nsMargin(0, 0, 0, 0),
+ "style system should ensure that :-moz-svg-foreign-content "
+ "does not get styled");
+ NS_ASSERTION(reflowInput.ComputedISize() == ISize(wm),
+ "reflow state made child wrong size");
+ reflowInput.SetComputedBSize(BSize(wm));
+
+ ReflowChild(kid, presContext, desiredSize, reflowInput, 0, 0,
+ NS_FRAME_NO_MOVE_FRAME, status);
+ NS_ASSERTION(mRect.width == desiredSize.Width() &&
+ mRect.height == desiredSize.Height(), "unexpected size");
+ FinishReflowChild(kid, presContext, desiredSize, &reflowInput, 0, 0,
+ NS_FRAME_NO_MOVE_FRAME);
+
+ mInReflow = false;
+}
+
+nsRect
+nsSVGForeignObjectFrame::GetInvalidRegion()
+{
+ MOZ_ASSERT(!NS_SVGDisplayListPaintingEnabled(),
+ "Only called by nsDisplayOuterSVG code");
+
+ nsIFrame* kid = PrincipalChildList().FirstChild();
+ if (kid->HasInvalidFrameInSubtree()) {
+ gfxRect r(mRect.x, mRect.y, mRect.width, mRect.height);
+ r.Scale(1.0 / nsPresContext::AppUnitsPerCSSPixel());
+ nsRect rect = nsSVGUtils::ToCanvasBounds(r, GetCanvasTM(), PresContext());
+ rect = nsSVGUtils::GetPostFilterVisualOverflowRect(this, rect);
+ return rect;
+ }
+ return nsRect();
+}
+
+
diff --git a/layout/svg/nsSVGForeignObjectFrame.h b/layout/svg/nsSVGForeignObjectFrame.h
new file mode 100644
index 0000000000..57df6d5b5b
--- /dev/null
+++ b/layout/svg/nsSVGForeignObjectFrame.h
@@ -0,0 +1,105 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef NSSVGFOREIGNOBJECTFRAME_H__
+#define NSSVGFOREIGNOBJECTFRAME_H__
+
+#include "mozilla/Attributes.h"
+#include "nsAutoPtr.h"
+#include "nsContainerFrame.h"
+#include "nsIPresShell.h"
+#include "nsISVGChildFrame.h"
+#include "nsRegion.h"
+#include "nsSVGUtils.h"
+
+class gfxContext;
+
+class nsSVGForeignObjectFrame : public nsContainerFrame
+ , public nsISVGChildFrame
+{
+ friend nsContainerFrame*
+ NS_NewSVGForeignObjectFrame(nsIPresShell* aPresShell, nsStyleContext* aContext);
+protected:
+ explicit nsSVGForeignObjectFrame(nsStyleContext* aContext);
+
+public:
+ NS_DECL_QUERYFRAME
+ NS_DECL_FRAMEARENA_HELPERS
+
+ // nsIFrame:
+ virtual void Init(nsIContent* aContent,
+ nsContainerFrame* aParent,
+ nsIFrame* aPrevInFlow) override;
+ virtual void DestroyFrom(nsIFrame* aDestructRoot) override;
+ virtual nsresult AttributeChanged(int32_t aNameSpaceID,
+ nsIAtom* aAttribute,
+ int32_t aModType) override;
+
+ virtual nsContainerFrame* GetContentInsertionFrame() override {
+ return PrincipalChildList().FirstChild()->GetContentInsertionFrame();
+ }
+
+ virtual void Reflow(nsPresContext* aPresContext,
+ ReflowOutput& aDesiredSize,
+ const ReflowInput& aReflowInput,
+ nsReflowStatus& aStatus) override;
+
+ virtual void BuildDisplayList(nsDisplayListBuilder* aBuilder,
+ const nsRect& aDirtyRect,
+ const nsDisplayListSet& aLists) override;
+
+ /**
+ * Get the "type" of the frame
+ *
+ * @see nsGkAtoms::svgForeignObjectFrame
+ */
+ virtual nsIAtom* GetType() const override;
+
+ virtual bool IsFrameOfType(uint32_t aFlags) const override
+ {
+ return nsContainerFrame::IsFrameOfType(aFlags &
+ ~(nsIFrame::eSVG | nsIFrame::eSVGForeignObject));
+ }
+
+ virtual bool IsSVGTransformed(Matrix *aOwnTransform,
+ Matrix *aFromParentTransform) const override;
+
+#ifdef DEBUG_FRAME_DUMP
+ virtual nsresult GetFrameName(nsAString& aResult) const override
+ {
+ return MakeFrameName(NS_LITERAL_STRING("SVGForeignObject"), aResult);
+ }
+#endif
+
+ // nsISVGChildFrame interface:
+ virtual DrawResult PaintSVG(gfxContext& aContext,
+ const gfxMatrix& aTransform,
+ const nsIntRect* aDirtyRect = nullptr) override;
+ virtual nsIFrame* GetFrameForPoint(const gfxPoint& aPoint) override;
+ virtual nsRect GetCoveredRegion() override;
+ virtual void ReflowSVG() override;
+ virtual void NotifySVGChanged(uint32_t aFlags) override;
+ virtual SVGBBox GetBBoxContribution(const Matrix &aToBBoxUserspace,
+ uint32_t aFlags) override;
+ virtual bool IsDisplayContainer() override { return true; }
+
+ gfxMatrix GetCanvasTM();
+
+ nsRect GetInvalidRegion();
+
+protected:
+ // implementation helpers:
+ void DoReflow();
+ void RequestReflow(nsIPresShell::IntrinsicDirty aType);
+
+ // If width or height is less than or equal to zero we must disable rendering
+ bool IsDisabled() const { return mRect.width <= 0 || mRect.height <= 0; }
+
+ nsAutoPtr<gfxMatrix> mCanvasTM;
+
+ bool mInReflow;
+};
+
+#endif
diff --git a/layout/svg/nsSVGGFrame.cpp b/layout/svg/nsSVGGFrame.cpp
new file mode 100644
index 0000000000..1fc1249564
--- /dev/null
+++ b/layout/svg/nsSVGGFrame.cpp
@@ -0,0 +1,98 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+// Main header first:
+#include "nsSVGGFrame.h"
+
+// Keep others in (case-insensitive) order:
+#include "nsGkAtoms.h"
+#include "SVGTransformableElement.h"
+#include "nsIFrame.h"
+#include "SVGGraphicsElement.h"
+#include "nsSVGIntegrationUtils.h"
+#include "nsSVGUtils.h"
+
+using namespace mozilla::dom;
+
+//----------------------------------------------------------------------
+// Implementation
+
+nsIFrame*
+NS_NewSVGGFrame(nsIPresShell* aPresShell, nsStyleContext* aContext)
+{
+ return new (aPresShell) nsSVGGFrame(aContext);
+}
+
+NS_IMPL_FRAMEARENA_HELPERS(nsSVGGFrame)
+
+#ifdef DEBUG
+void
+nsSVGGFrame::Init(nsIContent* aContent,
+ nsContainerFrame* aParent,
+ nsIFrame* aPrevInFlow)
+{
+ NS_ASSERTION(aContent->IsSVGElement() &&
+ static_cast<nsSVGElement*>(aContent)->IsTransformable(),
+ "The element doesn't support nsIDOMSVGTransformable");
+
+ nsSVGDisplayContainerFrame::Init(aContent, aParent, aPrevInFlow);
+}
+#endif /* DEBUG */
+
+nsIAtom *
+nsSVGGFrame::GetType() const
+{
+ return nsGkAtoms::svgGFrame;
+}
+
+//----------------------------------------------------------------------
+// nsISVGChildFrame methods
+
+void
+nsSVGGFrame::NotifySVGChanged(uint32_t aFlags)
+{
+ MOZ_ASSERT(aFlags & (TRANSFORM_CHANGED | COORD_CONTEXT_CHANGED),
+ "Invalidation logic may need adjusting");
+
+ if (aFlags & TRANSFORM_CHANGED) {
+ // make sure our cached transform matrix gets (lazily) updated
+ mCanvasTM = nullptr;
+ }
+
+ nsSVGDisplayContainerFrame::NotifySVGChanged(aFlags);
+}
+
+gfxMatrix
+nsSVGGFrame::GetCanvasTM()
+{
+ if (!mCanvasTM) {
+ NS_ASSERTION(GetParent(), "null parent");
+
+ nsSVGContainerFrame *parent = static_cast<nsSVGContainerFrame*>(GetParent());
+ SVGGraphicsElement *content = static_cast<SVGGraphicsElement*>(mContent);
+
+ gfxMatrix tm = content->PrependLocalTransformsTo(parent->GetCanvasTM());
+
+ mCanvasTM = new gfxMatrix(tm);
+ }
+ return *mCanvasTM;
+}
+
+nsresult
+nsSVGGFrame::AttributeChanged(int32_t aNameSpaceID,
+ nsIAtom* aAttribute,
+ int32_t aModType)
+{
+ if (aNameSpaceID == kNameSpaceID_None &&
+ aAttribute == nsGkAtoms::transform) {
+ // We don't invalidate for transform changes (the layers code does that).
+ // Also note that SVGTransformableElement::GetAttributeChangeHint will
+ // return nsChangeHint_UpdateOverflow for "transform" attribute changes
+ // and cause DoApplyRenderingChangeToTree to make the SchedulePaint call.
+ NotifySVGChanged(TRANSFORM_CHANGED);
+ }
+
+ return NS_OK;
+}
diff --git a/layout/svg/nsSVGGFrame.h b/layout/svg/nsSVGGFrame.h
new file mode 100644
index 0000000000..2f10451d52
--- /dev/null
+++ b/layout/svg/nsSVGGFrame.h
@@ -0,0 +1,59 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef NSSVGGFRAME_H
+#define NSSVGGFRAME_H
+
+#include "mozilla/Attributes.h"
+#include "gfxMatrix.h"
+#include "nsAutoPtr.h"
+#include "nsSVGContainerFrame.h"
+
+class nsSVGGFrame : public nsSVGDisplayContainerFrame
+{
+ friend nsIFrame*
+ NS_NewSVGGFrame(nsIPresShell* aPresShell, nsStyleContext* aContext);
+protected:
+ explicit nsSVGGFrame(nsStyleContext* aContext)
+ : nsSVGDisplayContainerFrame(aContext) {}
+
+public:
+ NS_DECL_FRAMEARENA_HELPERS
+
+#ifdef DEBUG
+ virtual void Init(nsIContent* aContent,
+ nsContainerFrame* aParent,
+ nsIFrame* aPrevInFlow) override;
+#endif
+
+ /**
+ * Get the "type" of the frame
+ *
+ * @see nsGkAtoms::svgGFrame
+ */
+ virtual nsIAtom* GetType() const override;
+
+#ifdef DEBUG_FRAME_DUMP
+ virtual nsresult GetFrameName(nsAString& aResult) const override
+ {
+ return MakeFrameName(NS_LITERAL_STRING("SVGG"), aResult);
+ }
+#endif
+
+ // nsIFrame interface:
+ virtual nsresult AttributeChanged(int32_t aNameSpaceID,
+ nsIAtom* aAttribute,
+ int32_t aModType) override;
+
+ // nsISVGChildFrame interface:
+ virtual void NotifySVGChanged(uint32_t aFlags) override;
+
+ // nsSVGContainerFrame methods:
+ virtual gfxMatrix GetCanvasTM() override;
+
+ nsAutoPtr<gfxMatrix> mCanvasTM;
+};
+
+#endif
diff --git a/layout/svg/nsSVGGenericContainerFrame.cpp b/layout/svg/nsSVGGenericContainerFrame.cpp
new file mode 100644
index 0000000000..1bff8bad92
--- /dev/null
+++ b/layout/svg/nsSVGGenericContainerFrame.cpp
@@ -0,0 +1,54 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+// Main header first:
+#include "nsSVGGenericContainerFrame.h"
+#include "nsSVGIntegrationUtils.h"
+
+//----------------------------------------------------------------------
+// nsSVGGenericContainerFrame Implementation
+
+nsIFrame*
+NS_NewSVGGenericContainerFrame(nsIPresShell* aPresShell, nsStyleContext* aContext)
+{
+ return new (aPresShell) nsSVGGenericContainerFrame(aContext);
+}
+
+NS_IMPL_FRAMEARENA_HELPERS(nsSVGGenericContainerFrame)
+
+//----------------------------------------------------------------------
+// nsIFrame methods
+
+nsresult
+nsSVGGenericContainerFrame::AttributeChanged(int32_t aNameSpaceID,
+ nsIAtom* aAttribute,
+ int32_t aModType)
+{
+#ifdef DEBUG
+ nsAutoString str;
+ aAttribute->ToString(str);
+ printf("** nsSVGGenericContainerFrame::AttributeChanged(%s)\n",
+ NS_LossyConvertUTF16toASCII(str).get());
+#endif
+
+ return NS_OK;
+}
+
+nsIAtom *
+nsSVGGenericContainerFrame::GetType() const
+{
+ return nsGkAtoms::svgGenericContainerFrame;
+}
+
+//----------------------------------------------------------------------
+// nsSVGContainerFrame methods:
+
+gfxMatrix
+nsSVGGenericContainerFrame::GetCanvasTM()
+{
+ NS_ASSERTION(GetParent(), "null parent");
+
+ return static_cast<nsSVGContainerFrame*>(GetParent())->GetCanvasTM();
+}
diff --git a/layout/svg/nsSVGGenericContainerFrame.h b/layout/svg/nsSVGGenericContainerFrame.h
new file mode 100644
index 0000000000..eff7375baf
--- /dev/null
+++ b/layout/svg/nsSVGGenericContainerFrame.h
@@ -0,0 +1,55 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef __NS_SVGGENERICCONTAINERFRAME_H__
+#define __NS_SVGGENERICCONTAINERFRAME_H__
+
+#include "mozilla/Attributes.h"
+#include "gfxMatrix.h"
+#include "nsFrame.h"
+#include "nsLiteralString.h"
+#include "nsQueryFrame.h"
+#include "nsSVGContainerFrame.h"
+
+class nsIAtom;
+class nsIFrame;
+class nsIPresShell;
+class nsStyleContext;
+
+class nsSVGGenericContainerFrame : public nsSVGDisplayContainerFrame
+{
+ friend nsIFrame*
+ NS_NewSVGGenericContainerFrame(nsIPresShell* aPresShell, nsStyleContext* aContext);
+
+protected:
+ explicit nsSVGGenericContainerFrame(nsStyleContext* aContext)
+ : nsSVGDisplayContainerFrame(aContext) {}
+
+public:
+ NS_DECL_FRAMEARENA_HELPERS
+
+ // nsIFrame:
+ virtual nsresult AttributeChanged(int32_t aNameSpaceID,
+ nsIAtom* aAttribute,
+ int32_t aModType) override;
+ /**
+ * Get the "type" of the frame
+ *
+ * @see nsGkAtoms::svgGenericContainerFrame
+ */
+ virtual nsIAtom* GetType() const override;
+
+#ifdef DEBUG_FRAME_DUMP
+ virtual nsresult GetFrameName(nsAString& aResult) const override
+ {
+ return MakeFrameName(NS_LITERAL_STRING("SVGGenericContainer"), aResult);
+ }
+#endif
+
+ // nsSVGContainerFrame methods:
+ virtual gfxMatrix GetCanvasTM() override;
+};
+
+#endif // __NS_SVGGENERICCONTAINERFRAME_H__
diff --git a/layout/svg/nsSVGGradientFrame.cpp b/layout/svg/nsSVGGradientFrame.cpp
new file mode 100644
index 0000000000..217ab8c4a4
--- /dev/null
+++ b/layout/svg/nsSVGGradientFrame.cpp
@@ -0,0 +1,678 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+// Main header first:
+#include "nsSVGGradientFrame.h"
+#include <algorithm>
+
+// Keep others in (case-insensitive) order:
+#include "gfxPattern.h"
+#include "mozilla/dom/SVGGradientElement.h"
+#include "mozilla/dom/SVGStopElement.h"
+#include "nsContentUtils.h"
+#include "nsSVGEffects.h"
+#include "nsSVGAnimatedTransformList.h"
+
+// XXX Tight coupling with content classes ahead!
+
+using namespace mozilla;
+using namespace mozilla::dom;
+using namespace mozilla::gfx;
+
+//----------------------------------------------------------------------
+// Helper classes
+
+class MOZ_RAII nsSVGGradientFrame::AutoGradientReferencer
+{
+public:
+ explicit AutoGradientReferencer(nsSVGGradientFrame *aFrame
+ MOZ_GUARD_OBJECT_NOTIFIER_PARAM)
+ : mFrame(aFrame)
+ {
+ MOZ_GUARD_OBJECT_NOTIFIER_INIT;
+ // Reference loops should normally be detected in advance and handled, so
+ // we're not expecting to encounter them here
+ MOZ_ASSERT(!mFrame->mLoopFlag, "Undetected reference loop!");
+ mFrame->mLoopFlag = true;
+ }
+ ~AutoGradientReferencer() {
+ mFrame->mLoopFlag = false;
+ }
+private:
+ nsSVGGradientFrame *mFrame;
+ MOZ_DECL_USE_GUARD_OBJECT_NOTIFIER
+};
+
+//----------------------------------------------------------------------
+// Implementation
+
+nsSVGGradientFrame::nsSVGGradientFrame(nsStyleContext* aContext)
+ : nsSVGPaintServerFrame(aContext)
+ , mLoopFlag(false)
+ , mNoHRefURI(false)
+{
+}
+
+//----------------------------------------------------------------------
+// nsIFrame methods:
+
+nsresult
+nsSVGGradientFrame::AttributeChanged(int32_t aNameSpaceID,
+ nsIAtom* aAttribute,
+ int32_t aModType)
+{
+ if (aNameSpaceID == kNameSpaceID_None &&
+ (aAttribute == nsGkAtoms::gradientUnits ||
+ aAttribute == nsGkAtoms::gradientTransform ||
+ aAttribute == nsGkAtoms::spreadMethod)) {
+ nsSVGEffects::InvalidateDirectRenderingObservers(this);
+ } else if ((aNameSpaceID == kNameSpaceID_XLink ||
+ aNameSpaceID == kNameSpaceID_None) &&
+ aAttribute == nsGkAtoms::href) {
+ // Blow away our reference, if any
+ Properties().Delete(nsSVGEffects::HrefAsPaintingProperty());
+ mNoHRefURI = false;
+ // And update whoever references us
+ nsSVGEffects::InvalidateDirectRenderingObservers(this);
+ }
+
+ return nsSVGPaintServerFrame::AttributeChanged(aNameSpaceID,
+ aAttribute, aModType);
+}
+
+//----------------------------------------------------------------------
+
+uint16_t
+nsSVGGradientFrame::GetEnumValue(uint32_t aIndex, nsIContent *aDefault)
+{
+ const nsSVGEnum& thisEnum =
+ static_cast<dom::SVGGradientElement*>(mContent)->mEnumAttributes[aIndex];
+
+ if (thisEnum.IsExplicitlySet())
+ return thisEnum.GetAnimValue();
+
+ AutoGradientReferencer gradientRef(this);
+
+ nsSVGGradientFrame *next = GetReferencedGradientIfNotInUse();
+ return next ? next->GetEnumValue(aIndex, aDefault) :
+ static_cast<dom::SVGGradientElement*>(aDefault)->
+ mEnumAttributes[aIndex].GetAnimValue();
+}
+
+uint16_t
+nsSVGGradientFrame::GetGradientUnits()
+{
+ // This getter is called every time the others are called - maybe cache it?
+ return GetEnumValue(dom::SVGGradientElement::GRADIENTUNITS);
+}
+
+uint16_t
+nsSVGGradientFrame::GetSpreadMethod()
+{
+ return GetEnumValue(dom::SVGGradientElement::SPREADMETHOD);
+}
+
+const nsSVGAnimatedTransformList*
+nsSVGGradientFrame::GetGradientTransformList(nsIContent* aDefault)
+{
+ nsSVGAnimatedTransformList *thisTransformList =
+ static_cast<dom::SVGGradientElement*>(mContent)->GetAnimatedTransformList();
+
+ if (thisTransformList && thisTransformList->IsExplicitlySet())
+ return thisTransformList;
+
+ AutoGradientReferencer gradientRef(this);
+
+ nsSVGGradientFrame *next = GetReferencedGradientIfNotInUse();
+ return next ? next->GetGradientTransformList(aDefault) :
+ static_cast<const dom::SVGGradientElement*>(aDefault)
+ ->mGradientTransform.get();
+}
+
+gfxMatrix
+nsSVGGradientFrame::GetGradientTransform(nsIFrame *aSource,
+ const gfxRect *aOverrideBounds)
+{
+ gfxMatrix bboxMatrix;
+
+ uint16_t gradientUnits = GetGradientUnits();
+ if (gradientUnits != SVG_UNIT_TYPE_USERSPACEONUSE) {
+ NS_ASSERTION(gradientUnits == SVG_UNIT_TYPE_OBJECTBOUNDINGBOX,
+ "Unknown gradientUnits type");
+ // objectBoundingBox is the default anyway
+
+ gfxRect bbox =
+ aOverrideBounds ? *aOverrideBounds : nsSVGUtils::GetBBox(aSource);
+ bboxMatrix =
+ gfxMatrix(bbox.Width(), 0, 0, bbox.Height(), bbox.X(), bbox.Y());
+ }
+
+ const nsSVGAnimatedTransformList* animTransformList =
+ GetGradientTransformList(mContent);
+ if (!animTransformList)
+ return bboxMatrix;
+
+ gfxMatrix gradientTransform =
+ animTransformList->GetAnimValue().GetConsolidationMatrix();
+ return bboxMatrix.PreMultiply(gradientTransform);
+}
+
+dom::SVGLinearGradientElement*
+nsSVGGradientFrame::GetLinearGradientWithLength(uint32_t aIndex,
+ dom::SVGLinearGradientElement* aDefault)
+{
+ // If this was a linear gradient with the required length, we would have
+ // already found it in nsSVGLinearGradientFrame::GetLinearGradientWithLength.
+ // Since we didn't find the length, continue looking down the chain.
+
+ AutoGradientReferencer gradientRef(this);
+
+ nsSVGGradientFrame *next = GetReferencedGradientIfNotInUse();
+ return next ? next->GetLinearGradientWithLength(aIndex, aDefault) : aDefault;
+}
+
+dom::SVGRadialGradientElement*
+nsSVGGradientFrame::GetRadialGradientWithLength(uint32_t aIndex,
+ dom::SVGRadialGradientElement* aDefault)
+{
+ // If this was a radial gradient with the required length, we would have
+ // already found it in nsSVGRadialGradientFrame::GetRadialGradientWithLength.
+ // Since we didn't find the length, continue looking down the chain.
+
+ AutoGradientReferencer gradientRef(this);
+
+ nsSVGGradientFrame *next = GetReferencedGradientIfNotInUse();
+ return next ? next->GetRadialGradientWithLength(aIndex, aDefault) : aDefault;
+}
+
+//----------------------------------------------------------------------
+// nsSVGPaintServerFrame methods:
+
+//helper
+static void GetStopInformation(nsIFrame* aStopFrame,
+ float *aOffset,
+ nscolor *aStopColor,
+ float *aStopOpacity)
+{
+ nsIContent* stopContent = aStopFrame->GetContent();
+ MOZ_ASSERT(stopContent && stopContent->IsSVGElement(nsGkAtoms::stop));
+
+ static_cast<SVGStopElement*>(stopContent)->
+ GetAnimatedNumberValues(aOffset, nullptr);
+
+ *aOffset = mozilla::clamped(*aOffset, 0.0f, 1.0f);
+ *aStopColor = aStopFrame->StyleSVGReset()->mStopColor;
+ *aStopOpacity = aStopFrame->StyleSVGReset()->mStopOpacity;
+}
+
+already_AddRefed<gfxPattern>
+nsSVGGradientFrame::GetPaintServerPattern(nsIFrame* aSource,
+ const DrawTarget* aDrawTarget,
+ const gfxMatrix& aContextMatrix,
+ nsStyleSVGPaint nsStyleSVG::*aFillOrStroke,
+ float aGraphicOpacity,
+ const gfxRect* aOverrideBounds)
+{
+ uint16_t gradientUnits = GetGradientUnits();
+ MOZ_ASSERT(gradientUnits == SVG_UNIT_TYPE_OBJECTBOUNDINGBOX ||
+ gradientUnits == SVG_UNIT_TYPE_USERSPACEONUSE);
+ if (gradientUnits == SVG_UNIT_TYPE_USERSPACEONUSE) {
+ // Set mSource for this consumer.
+ // If this gradient is applied to text, our caller will be the glyph, which
+ // is not an element, so we need to get the parent
+ mSource = aSource->GetContent()->IsNodeOfType(nsINode::eTEXT) ?
+ aSource->GetParent() : aSource;
+ }
+
+ AutoTArray<nsIFrame*,8> stopFrames;
+ GetStopFrames(&stopFrames);
+
+ uint32_t nStops = stopFrames.Length();
+
+ // SVG specification says that no stops should be treated like
+ // the corresponding fill or stroke had "none" specified.
+ if (nStops == 0) {
+ RefPtr<gfxPattern> pattern = new gfxPattern(Color());
+ return pattern.forget();
+ }
+
+ if (nStops == 1 || GradientVectorLengthIsZero()) {
+ // The gradient paints a single colour, using the stop-color of the last
+ // gradient step if there are more than one.
+ float stopOpacity = stopFrames[nStops-1]->StyleSVGReset()->mStopOpacity;
+ nscolor stopColor = stopFrames[nStops-1]->StyleSVGReset()->mStopColor;
+
+ Color stopColor2 = Color::FromABGR(stopColor);
+ stopColor2.a *= stopOpacity * aGraphicOpacity;
+ RefPtr<gfxPattern> pattern = new gfxPattern(stopColor2);
+ return pattern.forget();
+ }
+
+ // Get the transform list (if there is one). We do this after the returns
+ // above since this call can be expensive when "gradientUnits" is set to
+ // "objectBoundingBox" (since that requiring a GetBBox() call).
+ gfxMatrix patternMatrix = GetGradientTransform(aSource, aOverrideBounds);
+
+ if (patternMatrix.IsSingular()) {
+ return nullptr;
+ }
+
+ // revert any vector effect transform so that the gradient appears unchanged
+ if (aFillOrStroke == &nsStyleSVG::mStroke) {
+ gfxMatrix userToOuterSVG;
+ if (nsSVGUtils::GetNonScalingStrokeTransform(aSource, &userToOuterSVG)) {
+ patternMatrix *= userToOuterSVG;
+ }
+ }
+
+ if (!patternMatrix.Invert()) {
+ return nullptr;
+ }
+
+ RefPtr<gfxPattern> gradient = CreateGradient();
+ if (!gradient || gradient->CairoStatus())
+ return nullptr;
+
+ uint16_t aSpread = GetSpreadMethod();
+ if (aSpread == SVG_SPREADMETHOD_PAD)
+ gradient->SetExtend(ExtendMode::CLAMP);
+ else if (aSpread == SVG_SPREADMETHOD_REFLECT)
+ gradient->SetExtend(ExtendMode::REFLECT);
+ else if (aSpread == SVG_SPREADMETHOD_REPEAT)
+ gradient->SetExtend(ExtendMode::REPEAT);
+
+ gradient->SetMatrix(patternMatrix);
+
+ // setup stops
+ float lastOffset = 0.0f;
+
+ for (uint32_t i = 0; i < nStops; i++) {
+ float offset, stopOpacity;
+ nscolor stopColor;
+
+ GetStopInformation(stopFrames[i], &offset, &stopColor, &stopOpacity);
+
+ if (offset < lastOffset)
+ offset = lastOffset;
+ else
+ lastOffset = offset;
+
+ Color stopColor2 = Color::FromABGR(stopColor);
+ stopColor2.a *= stopOpacity * aGraphicOpacity;
+ gradient->AddColorStop(offset, stopColor2);
+ }
+
+ return gradient.forget();
+}
+
+// Private (helper) methods
+
+nsSVGGradientFrame *
+nsSVGGradientFrame::GetReferencedGradient()
+{
+ if (mNoHRefURI)
+ return nullptr;
+
+ nsSVGPaintingProperty *property =
+ Properties().Get(nsSVGEffects::HrefAsPaintingProperty());
+
+ if (!property) {
+ // Fetch our gradient element's href or xlink:href attribute
+ dom::SVGGradientElement* grad =
+ static_cast<dom::SVGGradientElement*>(mContent);
+ nsAutoString href;
+ if (grad->mStringAttributes[dom::SVGGradientElement::HREF]
+ .IsExplicitlySet()) {
+ grad->mStringAttributes[dom::SVGGradientElement::HREF]
+ .GetAnimValue(href, grad);
+ } else {
+ grad->mStringAttributes[dom::SVGGradientElement::XLINK_HREF]
+ .GetAnimValue(href, grad);
+ }
+
+ if (href.IsEmpty()) {
+ mNoHRefURI = true;
+ return nullptr; // no URL
+ }
+
+ // Convert href to an nsIURI
+ nsCOMPtr<nsIURI> targetURI;
+ nsCOMPtr<nsIURI> base = mContent->GetBaseURI();
+ nsContentUtils::NewURIWithDocumentCharset(getter_AddRefs(targetURI), href,
+ mContent->GetUncomposedDoc(), base);
+
+ property =
+ nsSVGEffects::GetPaintingProperty(targetURI, this,
+ nsSVGEffects::HrefAsPaintingProperty());
+ if (!property)
+ return nullptr;
+ }
+
+ nsIFrame *result = property->GetReferencedFrame();
+ if (!result)
+ return nullptr;
+
+ nsIAtom* frameType = result->GetType();
+ if (frameType != nsGkAtoms::svgLinearGradientFrame &&
+ frameType != nsGkAtoms::svgRadialGradientFrame)
+ return nullptr;
+
+ return static_cast<nsSVGGradientFrame*>(result);
+}
+
+nsSVGGradientFrame *
+nsSVGGradientFrame::GetReferencedGradientIfNotInUse()
+{
+ nsSVGGradientFrame *referenced = GetReferencedGradient();
+ if (!referenced)
+ return nullptr;
+
+ if (referenced->mLoopFlag) {
+ // XXXjwatt: we should really send an error to the JavaScript Console here:
+ NS_WARNING("gradient reference loop detected while inheriting attribute!");
+ return nullptr;
+ }
+
+ return referenced;
+}
+
+void
+nsSVGGradientFrame::GetStopFrames(nsTArray<nsIFrame*>* aStopFrames)
+{
+ nsIFrame *stopFrame = nullptr;
+ for (stopFrame = mFrames.FirstChild(); stopFrame;
+ stopFrame = stopFrame->GetNextSibling()) {
+ if (stopFrame->GetType() == nsGkAtoms::svgStopFrame) {
+ aStopFrames->AppendElement(stopFrame);
+ }
+ }
+ if (aStopFrames->Length() > 0) {
+ return;
+ }
+
+ // Our gradient element doesn't have stops - try to "inherit" them
+
+ AutoGradientReferencer gradientRef(this);
+ nsSVGGradientFrame* next = GetReferencedGradientIfNotInUse();
+ if (!next) {
+ return;
+ }
+
+ return next->GetStopFrames(aStopFrames);
+}
+
+// -------------------------------------------------------------------------
+// Linear Gradients
+// -------------------------------------------------------------------------
+
+#ifdef DEBUG
+void
+nsSVGLinearGradientFrame::Init(nsIContent* aContent,
+ nsContainerFrame* aParent,
+ nsIFrame* aPrevInFlow)
+{
+ NS_ASSERTION(aContent->IsSVGElement(nsGkAtoms::linearGradient),
+ "Content is not an SVG linearGradient");
+
+ nsSVGGradientFrame::Init(aContent, aParent, aPrevInFlow);
+}
+#endif /* DEBUG */
+
+nsIAtom*
+nsSVGLinearGradientFrame::GetType() const
+{
+ return nsGkAtoms::svgLinearGradientFrame;
+}
+
+nsresult
+nsSVGLinearGradientFrame::AttributeChanged(int32_t aNameSpaceID,
+ nsIAtom* aAttribute,
+ int32_t aModType)
+{
+ if (aNameSpaceID == kNameSpaceID_None &&
+ (aAttribute == nsGkAtoms::x1 ||
+ aAttribute == nsGkAtoms::y1 ||
+ aAttribute == nsGkAtoms::x2 ||
+ aAttribute == nsGkAtoms::y2)) {
+ nsSVGEffects::InvalidateDirectRenderingObservers(this);
+ }
+
+ return nsSVGGradientFrame::AttributeChanged(aNameSpaceID,
+ aAttribute, aModType);
+}
+
+//----------------------------------------------------------------------
+
+float
+nsSVGLinearGradientFrame::GetLengthValue(uint32_t aIndex)
+{
+ dom::SVGLinearGradientElement* lengthElement =
+ GetLinearGradientWithLength(aIndex,
+ static_cast<dom::SVGLinearGradientElement*>(mContent));
+ // We passed in mContent as a fallback, so, assuming mContent is non-null, the
+ // return value should also be non-null.
+ MOZ_ASSERT(lengthElement,
+ "Got unexpected null element from GetLinearGradientWithLength");
+ const nsSVGLength2 &length = lengthElement->mLengthAttributes[aIndex];
+
+ // Object bounding box units are handled by setting the appropriate
+ // transform in GetGradientTransform, but we need to handle user
+ // space units as part of the individual Get* routines. Fixes 323669.
+
+ uint16_t gradientUnits = GetGradientUnits();
+ if (gradientUnits == SVG_UNIT_TYPE_USERSPACEONUSE) {
+ return nsSVGUtils::UserSpace(mSource, &length);
+ }
+
+ NS_ASSERTION(
+ gradientUnits == SVG_UNIT_TYPE_OBJECTBOUNDINGBOX,
+ "Unknown gradientUnits type");
+
+ return length.GetAnimValue(static_cast<SVGSVGElement*>(nullptr));
+}
+
+dom::SVGLinearGradientElement*
+nsSVGLinearGradientFrame::GetLinearGradientWithLength(uint32_t aIndex,
+ dom::SVGLinearGradientElement* aDefault)
+{
+ dom::SVGLinearGradientElement* thisElement =
+ static_cast<dom::SVGLinearGradientElement*>(mContent);
+ const nsSVGLength2 &length = thisElement->mLengthAttributes[aIndex];
+
+ if (length.IsExplicitlySet()) {
+ return thisElement;
+ }
+
+ return nsSVGGradientFrame::GetLinearGradientWithLength(aIndex, aDefault);
+}
+
+bool
+nsSVGLinearGradientFrame::GradientVectorLengthIsZero()
+{
+ return GetLengthValue(dom::SVGLinearGradientElement::ATTR_X1) ==
+ GetLengthValue(dom::SVGLinearGradientElement::ATTR_X2) &&
+ GetLengthValue(dom::SVGLinearGradientElement::ATTR_Y1) ==
+ GetLengthValue(dom::SVGLinearGradientElement::ATTR_Y2);
+}
+
+already_AddRefed<gfxPattern>
+nsSVGLinearGradientFrame::CreateGradient()
+{
+ float x1, y1, x2, y2;
+
+ x1 = GetLengthValue(dom::SVGLinearGradientElement::ATTR_X1);
+ y1 = GetLengthValue(dom::SVGLinearGradientElement::ATTR_Y1);
+ x2 = GetLengthValue(dom::SVGLinearGradientElement::ATTR_X2);
+ y2 = GetLengthValue(dom::SVGLinearGradientElement::ATTR_Y2);
+
+ RefPtr<gfxPattern> pattern = new gfxPattern(x1, y1, x2, y2);
+ return pattern.forget();
+}
+
+// -------------------------------------------------------------------------
+// Radial Gradients
+// -------------------------------------------------------------------------
+
+#ifdef DEBUG
+void
+nsSVGRadialGradientFrame::Init(nsIContent* aContent,
+ nsContainerFrame* aParent,
+ nsIFrame* aPrevInFlow)
+{
+ NS_ASSERTION(aContent->IsSVGElement(nsGkAtoms::radialGradient),
+ "Content is not an SVG radialGradient");
+
+ nsSVGGradientFrame::Init(aContent, aParent, aPrevInFlow);
+}
+#endif /* DEBUG */
+
+nsIAtom*
+nsSVGRadialGradientFrame::GetType() const
+{
+ return nsGkAtoms::svgRadialGradientFrame;
+}
+
+nsresult
+nsSVGRadialGradientFrame::AttributeChanged(int32_t aNameSpaceID,
+ nsIAtom* aAttribute,
+ int32_t aModType)
+{
+ if (aNameSpaceID == kNameSpaceID_None &&
+ (aAttribute == nsGkAtoms::r ||
+ aAttribute == nsGkAtoms::cx ||
+ aAttribute == nsGkAtoms::cy ||
+ aAttribute == nsGkAtoms::fx ||
+ aAttribute == nsGkAtoms::fy)) {
+ nsSVGEffects::InvalidateDirectRenderingObservers(this);
+ }
+
+ return nsSVGGradientFrame::AttributeChanged(aNameSpaceID,
+ aAttribute, aModType);
+}
+
+//----------------------------------------------------------------------
+
+float
+nsSVGRadialGradientFrame::GetLengthValue(uint32_t aIndex)
+{
+ dom::SVGRadialGradientElement* lengthElement =
+ GetRadialGradientWithLength(aIndex,
+ static_cast<dom::SVGRadialGradientElement*>(mContent));
+ // We passed in mContent as a fallback, so, assuming mContent is non-null,
+ // the return value should also be non-null.
+ MOZ_ASSERT(lengthElement,
+ "Got unexpected null element from GetRadialGradientWithLength");
+ return GetLengthValueFromElement(aIndex, *lengthElement);
+}
+
+float
+nsSVGRadialGradientFrame::GetLengthValue(uint32_t aIndex, float aDefaultValue)
+{
+ dom::SVGRadialGradientElement* lengthElement =
+ GetRadialGradientWithLength(aIndex, nullptr);
+
+ return lengthElement ? GetLengthValueFromElement(aIndex, *lengthElement)
+ : aDefaultValue;
+}
+
+float
+nsSVGRadialGradientFrame::GetLengthValueFromElement(uint32_t aIndex,
+ dom::SVGRadialGradientElement& aElement)
+{
+ const nsSVGLength2 &length = aElement.mLengthAttributes[aIndex];
+
+ // Object bounding box units are handled by setting the appropriate
+ // transform in GetGradientTransform, but we need to handle user
+ // space units as part of the individual Get* routines. Fixes 323669.
+
+ uint16_t gradientUnits = GetGradientUnits();
+ if (gradientUnits == SVG_UNIT_TYPE_USERSPACEONUSE) {
+ return nsSVGUtils::UserSpace(mSource, &length);
+ }
+
+ NS_ASSERTION(
+ gradientUnits == SVG_UNIT_TYPE_OBJECTBOUNDINGBOX,
+ "Unknown gradientUnits type");
+
+ return length.GetAnimValue(static_cast<SVGSVGElement*>(nullptr));
+}
+
+dom::SVGRadialGradientElement*
+nsSVGRadialGradientFrame::GetRadialGradientWithLength(uint32_t aIndex,
+ dom::SVGRadialGradientElement* aDefault)
+{
+ dom::SVGRadialGradientElement* thisElement =
+ static_cast<dom::SVGRadialGradientElement*>(mContent);
+ const nsSVGLength2 &length = thisElement->mLengthAttributes[aIndex];
+
+ if (length.IsExplicitlySet()) {
+ return thisElement;
+ }
+
+ return nsSVGGradientFrame::GetRadialGradientWithLength(aIndex, aDefault);
+}
+
+bool
+nsSVGRadialGradientFrame::GradientVectorLengthIsZero()
+{
+ return GetLengthValue(dom::SVGRadialGradientElement::ATTR_R) == 0;
+}
+
+already_AddRefed<gfxPattern>
+nsSVGRadialGradientFrame::CreateGradient()
+{
+ float cx, cy, r, fx, fy;
+
+ cx = GetLengthValue(dom::SVGRadialGradientElement::ATTR_CX);
+ cy = GetLengthValue(dom::SVGRadialGradientElement::ATTR_CY);
+ r = GetLengthValue(dom::SVGRadialGradientElement::ATTR_R);
+ // If fx or fy are not set, use cx/cy instead
+ fx = GetLengthValue(dom::SVGRadialGradientElement::ATTR_FX, cx);
+ fy = GetLengthValue(dom::SVGRadialGradientElement::ATTR_FY, cy);
+
+ if (fx != cx || fy != cy) {
+ // The focal point (fFx and fFy) must be clamped to be *inside* - not on -
+ // the circumference of the gradient or we'll get rendering anomalies. We
+ // calculate the distance from the focal point to the gradient center and
+ // make sure it is *less* than the gradient radius.
+ // 1/128 is the limit of the fractional part of cairo's 24.8 fixed point
+ // representation divided by 2 to ensure that we get different cairo
+ // fractions
+ double dMax = std::max(0.0, r - 1.0/128);
+ float dx = fx - cx;
+ float dy = fy - cy;
+ double d = sqrt((dx * dx) + (dy * dy));
+ if (d > dMax) {
+ double angle = atan2(dy, dx);
+ fx = (float)(dMax * cos(angle)) + cx;
+ fy = (float)(dMax * sin(angle)) + cy;
+ }
+ }
+
+ RefPtr<gfxPattern> pattern = new gfxPattern(fx, fy, 0, cx, cy, r);
+ return pattern.forget();
+}
+
+// -------------------------------------------------------------------------
+// Public functions
+// -------------------------------------------------------------------------
+
+nsIFrame*
+NS_NewSVGLinearGradientFrame(nsIPresShell* aPresShell,
+ nsStyleContext* aContext)
+{
+ return new (aPresShell) nsSVGLinearGradientFrame(aContext);
+}
+
+NS_IMPL_FRAMEARENA_HELPERS(nsSVGLinearGradientFrame)
+
+nsIFrame*
+NS_NewSVGRadialGradientFrame(nsIPresShell* aPresShell,
+ nsStyleContext* aContext)
+{
+ return new (aPresShell) nsSVGRadialGradientFrame(aContext);
+}
+
+NS_IMPL_FRAMEARENA_HELPERS(nsSVGRadialGradientFrame)
diff --git a/layout/svg/nsSVGGradientFrame.h b/layout/svg/nsSVGGradientFrame.h
new file mode 100644
index 0000000000..f12b132533
--- /dev/null
+++ b/layout/svg/nsSVGGradientFrame.h
@@ -0,0 +1,213 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef __NS_SVGGRADIENTFRAME_H__
+#define __NS_SVGGRADIENTFRAME_H__
+
+#include "mozilla/Attributes.h"
+#include "gfxMatrix.h"
+#include "nsCOMPtr.h"
+#include "nsFrame.h"
+#include "nsLiteralString.h"
+#include "nsSVGPaintServerFrame.h"
+
+class gfxPattern;
+class nsIAtom;
+class nsIContent;
+class nsIFrame;
+class nsIPresShell;
+class nsStyleContext;
+
+struct gfxRect;
+
+namespace mozilla {
+class nsSVGAnimatedTransformList;
+
+namespace dom {
+class SVGLinearGradientElement;
+class SVGRadialGradientElement;
+} // namespace dom
+} // namespace mozilla
+
+/**
+ * Gradients can refer to other gradients. We create an nsSVGPaintingProperty
+ * with property type nsGkAtoms::href to track the referenced gradient.
+ */
+class nsSVGGradientFrame : public nsSVGPaintServerFrame
+{
+ typedef mozilla::gfx::ExtendMode ExtendMode;
+
+protected:
+ explicit nsSVGGradientFrame(nsStyleContext* aContext);
+
+public:
+ NS_DECL_ABSTRACT_FRAME(nsSVGGradientFrame)
+
+ // nsSVGPaintServerFrame methods:
+ virtual already_AddRefed<gfxPattern>
+ GetPaintServerPattern(nsIFrame* aSource,
+ const DrawTarget* aDrawTarget,
+ const gfxMatrix& aContextMatrix,
+ nsStyleSVGPaint nsStyleSVG::*aFillOrStroke,
+ float aGraphicOpacity,
+ const gfxRect* aOverrideBounds) override;
+
+ // nsIFrame interface:
+ virtual nsresult AttributeChanged(int32_t aNameSpaceID,
+ nsIAtom* aAttribute,
+ int32_t aModType) override;
+
+#ifdef DEBUG_FRAME_DUMP
+ virtual nsresult GetFrameName(nsAString& aResult) const override
+ {
+ return MakeFrameName(NS_LITERAL_STRING("SVGGradient"), aResult);
+ }
+#endif // DEBUG
+
+private:
+
+ // Parse our xlink:href and set up our nsSVGPaintingProperty if we
+ // reference another gradient and we don't have a property. Return
+ // the referenced gradient's frame if available, null otherwise.
+ nsSVGGradientFrame* GetReferencedGradient();
+
+ // Optionally get a stop frame (returns stop index/count)
+ void GetStopFrames(nsTArray<nsIFrame*>* aStopFrames);
+
+ const mozilla::nsSVGAnimatedTransformList* GetGradientTransformList(
+ nsIContent* aDefault);
+ // Will be singular for gradientUnits="objectBoundingBox" with an empty bbox.
+ gfxMatrix GetGradientTransform(nsIFrame *aSource,
+ const gfxRect *aOverrideBounds);
+
+protected:
+ virtual bool GradientVectorLengthIsZero() = 0;
+ virtual already_AddRefed<gfxPattern> CreateGradient() = 0;
+
+ // Internal methods for handling referenced gradients
+ class AutoGradientReferencer;
+ nsSVGGradientFrame* GetReferencedGradientIfNotInUse();
+
+ // Accessors to lookup gradient attributes
+ uint16_t GetEnumValue(uint32_t aIndex, nsIContent *aDefault);
+ uint16_t GetEnumValue(uint32_t aIndex)
+ {
+ return GetEnumValue(aIndex, mContent);
+ }
+ uint16_t GetGradientUnits();
+ uint16_t GetSpreadMethod();
+
+ // Gradient-type-specific lookups since the length values differ between
+ // linear and radial gradients
+ virtual mozilla::dom::SVGLinearGradientElement * GetLinearGradientWithLength(
+ uint32_t aIndex, mozilla::dom::SVGLinearGradientElement* aDefault);
+ virtual mozilla::dom::SVGRadialGradientElement * GetRadialGradientWithLength(
+ uint32_t aIndex, mozilla::dom::SVGRadialGradientElement* aDefault);
+
+ // The frame our gradient is (currently) being applied to
+ nsIFrame* mSource;
+
+private:
+ // Flag to mark this frame as "in use" during recursive calls along our
+ // gradient's reference chain so we can detect reference loops. See:
+ // http://www.w3.org/TR/SVG11/pservers.html#LinearGradientElementHrefAttribute
+ bool mLoopFlag;
+ // Gradients often don't reference other gradients, so here we cache
+ // the fact that that isn't happening.
+ bool mNoHRefURI;
+};
+
+
+// -------------------------------------------------------------------------
+// Linear Gradients
+// -------------------------------------------------------------------------
+
+class nsSVGLinearGradientFrame : public nsSVGGradientFrame
+{
+ friend nsIFrame* NS_NewSVGLinearGradientFrame(nsIPresShell* aPresShell,
+ nsStyleContext* aContext);
+protected:
+ explicit nsSVGLinearGradientFrame(nsStyleContext* aContext)
+ : nsSVGGradientFrame(aContext) {}
+
+public:
+ NS_DECL_FRAMEARENA_HELPERS
+
+ // nsIFrame interface:
+#ifdef DEBUG
+ virtual void Init(nsIContent* aContent,
+ nsContainerFrame* aParent,
+ nsIFrame* aPrevInFlow) override;
+#endif
+
+ virtual nsIAtom* GetType() const override; // frame type: nsGkAtoms::svgLinearGradientFrame
+
+ virtual nsresult AttributeChanged(int32_t aNameSpaceID,
+ nsIAtom* aAttribute,
+ int32_t aModType) override;
+
+#ifdef DEBUG_FRAME_DUMP
+ virtual nsresult GetFrameName(nsAString& aResult) const override
+ {
+ return MakeFrameName(NS_LITERAL_STRING("SVGLinearGradient"), aResult);
+ }
+#endif // DEBUG
+
+protected:
+ float GetLengthValue(uint32_t aIndex);
+ virtual mozilla::dom::SVGLinearGradientElement* GetLinearGradientWithLength(
+ uint32_t aIndex, mozilla::dom::SVGLinearGradientElement* aDefault) override;
+ virtual bool GradientVectorLengthIsZero() override;
+ virtual already_AddRefed<gfxPattern> CreateGradient() override;
+};
+
+// -------------------------------------------------------------------------
+// Radial Gradients
+// -------------------------------------------------------------------------
+
+class nsSVGRadialGradientFrame : public nsSVGGradientFrame
+{
+ friend nsIFrame* NS_NewSVGRadialGradientFrame(nsIPresShell* aPresShell,
+ nsStyleContext* aContext);
+protected:
+ explicit nsSVGRadialGradientFrame(nsStyleContext* aContext)
+ : nsSVGGradientFrame(aContext) {}
+
+public:
+ NS_DECL_FRAMEARENA_HELPERS
+
+ // nsIFrame interface:
+#ifdef DEBUG
+ virtual void Init(nsIContent* aContent,
+ nsContainerFrame* aParent,
+ nsIFrame* aPrevInFlow) override;
+#endif
+
+ virtual nsIAtom* GetType() const override; // frame type: nsGkAtoms::svgRadialGradientFrame
+
+ virtual nsresult AttributeChanged(int32_t aNameSpaceID,
+ nsIAtom* aAttribute,
+ int32_t aModType) override;
+
+#ifdef DEBUG_FRAME_DUMP
+ virtual nsresult GetFrameName(nsAString& aResult) const override
+ {
+ return MakeFrameName(NS_LITERAL_STRING("SVGRadialGradient"), aResult);
+ }
+#endif // DEBUG
+
+protected:
+ float GetLengthValue(uint32_t aIndex);
+ float GetLengthValue(uint32_t aIndex, float aDefaultValue);
+ float GetLengthValueFromElement(uint32_t aIndex,
+ mozilla::dom::SVGRadialGradientElement& aElement);
+ virtual mozilla::dom::SVGRadialGradientElement* GetRadialGradientWithLength(
+ uint32_t aIndex, mozilla::dom::SVGRadialGradientElement* aDefault) override;
+ virtual bool GradientVectorLengthIsZero() override;
+ virtual already_AddRefed<gfxPattern> CreateGradient() override;
+};
+
+#endif // __NS_SVGGRADIENTFRAME_H__
+
diff --git a/layout/svg/nsSVGImageFrame.cpp b/layout/svg/nsSVGImageFrame.cpp
new file mode 100644
index 0000000000..c0a7f9419b
--- /dev/null
+++ b/layout/svg/nsSVGImageFrame.cpp
@@ -0,0 +1,667 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+// Keep in (case-insensitive) order:
+#include "gfxContext.h"
+#include "gfxPlatform.h"
+#include "mozilla/gfx/2D.h"
+#include "imgIContainer.h"
+#include "nsContainerFrame.h"
+#include "nsIImageLoadingContent.h"
+#include "nsLayoutUtils.h"
+#include "imgINotificationObserver.h"
+#include "nsSVGEffects.h"
+#include "nsSVGPathGeometryFrame.h"
+#include "mozilla/dom/SVGSVGElement.h"
+#include "nsSVGUtils.h"
+#include "SVGContentUtils.h"
+#include "SVGImageContext.h"
+#include "mozilla/dom/SVGImageElement.h"
+#include "nsContentUtils.h"
+#include "nsIReflowCallback.h"
+#include "mozilla/Unused.h"
+
+using namespace mozilla;
+using namespace mozilla::dom;
+using namespace mozilla::gfx;
+using namespace mozilla::image;
+
+class nsSVGImageFrame;
+
+class nsSVGImageListener final : public imgINotificationObserver
+{
+public:
+ explicit nsSVGImageListener(nsSVGImageFrame *aFrame);
+
+ NS_DECL_ISUPPORTS
+ NS_DECL_IMGINOTIFICATIONOBSERVER
+
+ void SetFrame(nsSVGImageFrame *frame) { mFrame = frame; }
+
+private:
+ ~nsSVGImageListener() {}
+
+ nsSVGImageFrame *mFrame;
+};
+
+class nsSVGImageFrame : public nsSVGPathGeometryFrame
+ , public nsIReflowCallback
+{
+ friend nsIFrame*
+ NS_NewSVGImageFrame(nsIPresShell* aPresShell, nsStyleContext* aContext);
+
+protected:
+ explicit nsSVGImageFrame(nsStyleContext* aContext)
+ : nsSVGPathGeometryFrame(aContext)
+ , mReflowCallbackPosted(false)
+ {
+ EnableVisibilityTracking();
+ }
+
+ virtual ~nsSVGImageFrame();
+
+public:
+ NS_DECL_FRAMEARENA_HELPERS
+
+ // nsISVGChildFrame interface:
+ virtual DrawResult PaintSVG(gfxContext& aContext,
+ const gfxMatrix& aTransform,
+ const nsIntRect* aDirtyRect = nullptr) override;
+ virtual nsIFrame* GetFrameForPoint(const gfxPoint& aPoint) override;
+ virtual void ReflowSVG() override;
+
+ // nsSVGPathGeometryFrame methods:
+ virtual uint16_t GetHitTestFlags() override;
+
+ // nsIFrame interface:
+ virtual nsresult AttributeChanged(int32_t aNameSpaceID,
+ nsIAtom* aAttribute,
+ int32_t aModType) override;
+
+ void OnVisibilityChange(Visibility aNewVisibility,
+ Maybe<OnNonvisible> aNonvisibleAction = Nothing()) override;
+
+ virtual void Init(nsIContent* aContent,
+ nsContainerFrame* aParent,
+ nsIFrame* aPrevInFlow) override;
+ virtual void DestroyFrom(nsIFrame* aDestructRoot) override;
+
+ /**
+ * Get the "type" of the frame
+ *
+ * @see nsGkAtoms::svgImageFrame
+ */
+ virtual nsIAtom* GetType() const override;
+
+#ifdef DEBUG_FRAME_DUMP
+ virtual nsresult GetFrameName(nsAString& aResult) const override
+ {
+ return MakeFrameName(NS_LITERAL_STRING("SVGImage"), aResult);
+ }
+#endif
+
+ // nsIReflowCallback
+ virtual bool ReflowFinished() override;
+ virtual void ReflowCallbackCanceled() override;
+
+private:
+ gfx::Matrix GetRasterImageTransform(int32_t aNativeWidth,
+ int32_t aNativeHeight);
+ gfx::Matrix GetVectorImageTransform();
+ bool TransformContextForPainting(gfxContext* aGfxContext,
+ const gfxMatrix& aTransform);
+
+ nsCOMPtr<imgINotificationObserver> mListener;
+
+ nsCOMPtr<imgIContainer> mImageContainer;
+
+ bool mReflowCallbackPosted;
+
+ friend class nsSVGImageListener;
+};
+
+//----------------------------------------------------------------------
+// Implementation
+
+nsIFrame*
+NS_NewSVGImageFrame(nsIPresShell* aPresShell, nsStyleContext* aContext)
+{
+ return new (aPresShell) nsSVGImageFrame(aContext);
+}
+
+NS_IMPL_FRAMEARENA_HELPERS(nsSVGImageFrame)
+
+nsSVGImageFrame::~nsSVGImageFrame()
+{
+ // set the frame to null so we don't send messages to a dead object.
+ if (mListener) {
+ nsCOMPtr<nsIImageLoadingContent> imageLoader = do_QueryInterface(mContent);
+ if (imageLoader) {
+ imageLoader->RemoveObserver(mListener);
+ }
+ reinterpret_cast<nsSVGImageListener*>(mListener.get())->SetFrame(nullptr);
+ }
+ mListener = nullptr;
+}
+
+void
+nsSVGImageFrame::Init(nsIContent* aContent,
+ nsContainerFrame* aParent,
+ nsIFrame* aPrevInFlow)
+{
+ NS_ASSERTION(aContent->IsSVGElement(nsGkAtoms::image),
+ "Content is not an SVG image!");
+
+ nsSVGPathGeometryFrame::Init(aContent, aParent, aPrevInFlow);
+
+ if (GetStateBits() & NS_FRAME_IS_NONDISPLAY) {
+ // Non-display frames are likely to be patterns, masks or the like.
+ // Treat them as always visible.
+ IncApproximateVisibleCount();
+ }
+
+ mListener = new nsSVGImageListener(this);
+ nsCOMPtr<nsIImageLoadingContent> imageLoader = do_QueryInterface(mContent);
+ if (!imageLoader) {
+ NS_RUNTIMEABORT("Why is this not an image loading content?");
+ }
+
+ // We should have a PresContext now, so let's notify our image loader that
+ // we need to register any image animations with the refresh driver.
+ imageLoader->FrameCreated(this);
+
+ imageLoader->AddObserver(mListener);
+}
+
+/* virtual */ void
+nsSVGImageFrame::DestroyFrom(nsIFrame* aDestructRoot)
+{
+ if (GetStateBits() & NS_FRAME_IS_NONDISPLAY) {
+ DecApproximateVisibleCount();
+ }
+
+ if (mReflowCallbackPosted) {
+ PresContext()->PresShell()->CancelReflowCallback(this);
+ mReflowCallbackPosted = false;
+ }
+
+ nsCOMPtr<nsIImageLoadingContent> imageLoader =
+ do_QueryInterface(nsFrame::mContent);
+
+ if (imageLoader) {
+ imageLoader->FrameDestroyed(this);
+ }
+
+ nsFrame::DestroyFrom(aDestructRoot);
+}
+
+//----------------------------------------------------------------------
+// nsIFrame methods:
+
+nsresult
+nsSVGImageFrame::AttributeChanged(int32_t aNameSpaceID,
+ nsIAtom* aAttribute,
+ int32_t aModType)
+{
+ if (aNameSpaceID == kNameSpaceID_None) {
+ if (aAttribute == nsGkAtoms::x ||
+ aAttribute == nsGkAtoms::y ||
+ aAttribute == nsGkAtoms::width ||
+ aAttribute == nsGkAtoms::height) {
+ nsLayoutUtils::PostRestyleEvent(
+ mContent->AsElement(), nsRestyleHint(0),
+ nsChangeHint_InvalidateRenderingObservers);
+ nsSVGUtils::ScheduleReflowSVG(this);
+ return NS_OK;
+ }
+ else if (aAttribute == nsGkAtoms::preserveAspectRatio) {
+ // We don't paint the content of the image using display lists, therefore
+ // we have to invalidate for this children-only transform changes since
+ // there is no layer tree to notice that the transform changed and
+ // recomposite.
+ InvalidateFrame();
+ return NS_OK;
+ }
+ }
+ if ((aNameSpaceID == kNameSpaceID_XLink ||
+ aNameSpaceID == kNameSpaceID_None) &&
+ aAttribute == nsGkAtoms::href) {
+ SVGImageElement *element = static_cast<SVGImageElement*>(mContent);
+
+ bool hrefIsSet =
+ element->mStringAttributes[SVGImageElement::HREF].IsExplicitlySet() ||
+ element->mStringAttributes[SVGImageElement::XLINK_HREF].IsExplicitlySet();
+ if (hrefIsSet) {
+ element->LoadSVGImage(true, true);
+ } else {
+ element->CancelImageRequests(true);
+ }
+ }
+
+ return nsSVGPathGeometryFrame::AttributeChanged(aNameSpaceID,
+ aAttribute, aModType);
+}
+
+void
+nsSVGImageFrame::OnVisibilityChange(Visibility aNewVisibility,
+ Maybe<OnNonvisible> aNonvisibleAction)
+{
+ nsCOMPtr<nsIImageLoadingContent> imageLoader = do_QueryInterface(mContent);
+ if (!imageLoader) {
+ nsSVGPathGeometryFrame::OnVisibilityChange(aNewVisibility, aNonvisibleAction);
+ return;
+ }
+
+ imageLoader->OnVisibilityChange(aNewVisibility, aNonvisibleAction);
+
+ nsSVGPathGeometryFrame::OnVisibilityChange(aNewVisibility, aNonvisibleAction);
+}
+
+gfx::Matrix
+nsSVGImageFrame::GetRasterImageTransform(int32_t aNativeWidth,
+ int32_t aNativeHeight)
+{
+ float x, y, width, height;
+ SVGImageElement *element = static_cast<SVGImageElement*>(mContent);
+ element->GetAnimatedLengthValues(&x, &y, &width, &height, nullptr);
+
+ Matrix viewBoxTM =
+ SVGContentUtils::GetViewBoxTransform(width, height,
+ 0, 0, aNativeWidth, aNativeHeight,
+ element->mPreserveAspectRatio);
+
+ return viewBoxTM * gfx::Matrix::Translation(x, y);
+}
+
+gfx::Matrix
+nsSVGImageFrame::GetVectorImageTransform()
+{
+ float x, y, width, height;
+ SVGImageElement *element = static_cast<SVGImageElement*>(mContent);
+ element->GetAnimatedLengthValues(&x, &y, &width, &height, nullptr);
+
+ // No viewBoxTM needed here -- our height/width overrides any concept of
+ // "native size" that the SVG image has, and it will handle viewBox and
+ // preserveAspectRatio on its own once we give it a region to draw into.
+
+ return gfx::Matrix::Translation(x, y);
+}
+
+bool
+nsSVGImageFrame::TransformContextForPainting(gfxContext* aGfxContext,
+ const gfxMatrix& aTransform)
+{
+ gfx::Matrix imageTransform;
+ if (mImageContainer->GetType() == imgIContainer::TYPE_VECTOR) {
+ imageTransform = GetVectorImageTransform() * ToMatrix(aTransform);
+ } else {
+ int32_t nativeWidth, nativeHeight;
+ if (NS_FAILED(mImageContainer->GetWidth(&nativeWidth)) ||
+ NS_FAILED(mImageContainer->GetHeight(&nativeHeight)) ||
+ nativeWidth == 0 || nativeHeight == 0) {
+ return false;
+ }
+ imageTransform =
+ GetRasterImageTransform(nativeWidth, nativeHeight) * ToMatrix(aTransform);
+
+ // NOTE: We need to cancel out the effects of Full-Page-Zoom, or else
+ // it'll get applied an extra time by DrawSingleUnscaledImage.
+ nscoord appUnitsPerDevPx = PresContext()->AppUnitsPerDevPixel();
+ gfxFloat pageZoomFactor =
+ nsPresContext::AppUnitsToFloatCSSPixels(appUnitsPerDevPx);
+ imageTransform.PreScale(pageZoomFactor, pageZoomFactor);
+ }
+
+ if (imageTransform.IsSingular()) {
+ return false;
+ }
+
+ aGfxContext->Multiply(ThebesMatrix(imageTransform));
+ return true;
+}
+
+//----------------------------------------------------------------------
+// nsISVGChildFrame methods:
+DrawResult
+nsSVGImageFrame::PaintSVG(gfxContext& aContext,
+ const gfxMatrix& aTransform,
+ const nsIntRect *aDirtyRect)
+{
+ if (!StyleVisibility()->IsVisible())
+ return DrawResult::SUCCESS;
+
+ float x, y, width, height;
+ SVGImageElement *imgElem = static_cast<SVGImageElement*>(mContent);
+ imgElem->GetAnimatedLengthValues(&x, &y, &width, &height, nullptr);
+ NS_ASSERTION(width > 0 && height > 0,
+ "Should only be painting things with valid width/height");
+
+ if (!mImageContainer) {
+ nsCOMPtr<imgIRequest> currentRequest;
+ nsCOMPtr<nsIImageLoadingContent> imageLoader = do_QueryInterface(mContent);
+ if (imageLoader)
+ imageLoader->GetRequest(nsIImageLoadingContent::CURRENT_REQUEST,
+ getter_AddRefs(currentRequest));
+
+ if (currentRequest)
+ currentRequest->GetImage(getter_AddRefs(mImageContainer));
+ }
+
+ DrawResult result = DrawResult::SUCCESS;
+ if (mImageContainer) {
+ gfxContextAutoSaveRestore autoRestorer(&aContext);
+
+ if (StyleDisplay()->IsScrollableOverflow()) {
+ gfxRect clipRect = nsSVGUtils::GetClipRectForFrame(this, x, y,
+ width, height);
+ nsSVGUtils::SetClipRect(&aContext, aTransform, clipRect);
+ }
+
+ if (!TransformContextForPainting(&aContext, aTransform)) {
+ return DrawResult::SUCCESS;
+ }
+
+ // fill-opacity doesn't affect <image>, so if we're allowed to
+ // optimize group opacity, the opacity used for compositing the
+ // image into the current canvas is just the group opacity.
+ float opacity = 1.0f;
+ if (nsSVGUtils::CanOptimizeOpacity(this)) {
+ opacity = StyleEffects()->mOpacity;
+ }
+
+ if (opacity != 1.0f || StyleEffects()->mMixBlendMode != NS_STYLE_BLEND_NORMAL) {
+ aContext.PushGroupForBlendBack(gfxContentType::COLOR_ALPHA, opacity);
+ }
+
+ nscoord appUnitsPerDevPx = PresContext()->AppUnitsPerDevPixel();
+ nsRect dirtyRect; // only used if aDirtyRect is non-null
+ if (aDirtyRect) {
+ NS_ASSERTION(!NS_SVGDisplayListPaintingEnabled() ||
+ (mState & NS_FRAME_IS_NONDISPLAY),
+ "Display lists handle dirty rect intersection test");
+ dirtyRect = ToAppUnits(*aDirtyRect, appUnitsPerDevPx);
+ // Adjust dirtyRect to match our local coordinate system.
+ nsRect rootRect =
+ nsSVGUtils::TransformFrameRectToOuterSVG(mRect, aTransform,
+ PresContext());
+ dirtyRect.MoveBy(-rootRect.TopLeft());
+ }
+
+ // XXXbholley - I don't think huge images in SVGs are common enough to
+ // warrant worrying about the responsiveness impact of doing synchronous
+ // decodes. The extra code complexity of determinining when we want to
+ // force sync probably just isn't worth it, so always pass FLAG_SYNC_DECODE
+ uint32_t drawFlags = imgIContainer::FLAG_SYNC_DECODE;
+
+ if (mImageContainer->GetType() == imgIContainer::TYPE_VECTOR) {
+ // Package up the attributes of this image element which can override the
+ // attributes of mImageContainer's internal SVG document. The 'width' &
+ // 'height' values we're passing in here are in CSS units (though they
+ // come from width/height *attributes* in SVG). They influence the region
+ // of the SVG image's internal document that is visible, in combination
+ // with preserveAspectRatio and viewBox.
+ SVGImageContext context(CSSIntSize::Truncate(width, height),
+ Some(imgElem->mPreserveAspectRatio.GetAnimValue()),
+ 1.0, true);
+
+ // For the actual draw operation to draw crisply (and at the right size),
+ // our destination rect needs to be |width|x|height|, *in dev pixels*.
+ LayoutDeviceSize devPxSize(width, height);
+ nsRect destRect(nsPoint(),
+ LayoutDevicePixel::ToAppUnits(devPxSize,
+ appUnitsPerDevPx));
+
+ // Note: Can't use DrawSingleUnscaledImage for the TYPE_VECTOR case.
+ // That method needs our image to have a fixed native width & height,
+ // and that's not always true for TYPE_VECTOR images.
+ result = nsLayoutUtils::DrawSingleImage(
+ aContext,
+ PresContext(),
+ mImageContainer,
+ nsLayoutUtils::GetSamplingFilterForFrame(this),
+ destRect,
+ aDirtyRect ? dirtyRect : destRect,
+ &context,
+ drawFlags);
+ } else { // mImageContainer->GetType() == TYPE_RASTER
+ result = nsLayoutUtils::DrawSingleUnscaledImage(
+ aContext,
+ PresContext(),
+ mImageContainer,
+ nsLayoutUtils::GetSamplingFilterForFrame(this),
+ nsPoint(0, 0),
+ aDirtyRect ? &dirtyRect : nullptr,
+ drawFlags);
+ }
+
+ if (opacity != 1.0f || StyleEffects()->mMixBlendMode != NS_STYLE_BLEND_NORMAL) {
+ aContext.PopGroupAndBlend();
+ }
+ // gfxContextAutoSaveRestore goes out of scope & cleans up our gfxContext
+ }
+
+ return result;
+}
+
+nsIFrame*
+nsSVGImageFrame::GetFrameForPoint(const gfxPoint& aPoint)
+{
+ if (!(GetStateBits() & NS_STATE_SVG_CLIPPATH_CHILD) && !GetHitTestFlags()) {
+ return nullptr;
+ }
+
+ Rect rect;
+ SVGImageElement *element = static_cast<SVGImageElement*>(mContent);
+ element->GetAnimatedLengthValues(&rect.x, &rect.y,
+ &rect.width, &rect.height, nullptr);
+
+ if (!rect.Contains(ToPoint(aPoint))) {
+ return nullptr;
+ }
+
+ // Special case for raster images -- we only want to accept points that fall
+ // in the underlying image's (scaled to fit) native bounds. That region
+ // doesn't necessarily map to our <image> element's [x,y,width,height] if the
+ // raster image's aspect ratio is being preserved. We have to look up the
+ // native image size & our viewBox transform in order to filter out points
+ // that fall outside that area. (This special case doesn't apply to vector
+ // images because they don't limit their drawing to explicit "native
+ // bounds" -- they have an infinite canvas on which to place content.)
+ if (StyleDisplay()->IsScrollableOverflow() && mImageContainer) {
+ if (mImageContainer->GetType() == imgIContainer::TYPE_RASTER) {
+ int32_t nativeWidth, nativeHeight;
+ if (NS_FAILED(mImageContainer->GetWidth(&nativeWidth)) ||
+ NS_FAILED(mImageContainer->GetHeight(&nativeHeight)) ||
+ nativeWidth == 0 || nativeHeight == 0) {
+ return nullptr;
+ }
+ Matrix viewBoxTM =
+ SVGContentUtils::GetViewBoxTransform(rect.width, rect.height,
+ 0, 0, nativeWidth, nativeHeight,
+ element->mPreserveAspectRatio);
+ if (!nsSVGUtils::HitTestRect(viewBoxTM,
+ 0, 0, nativeWidth, nativeHeight,
+ aPoint.x - rect.x, aPoint.y - rect.y)) {
+ return nullptr;
+ }
+ }
+ }
+
+ return this;
+}
+
+nsIAtom *
+nsSVGImageFrame::GetType() const
+{
+ return nsGkAtoms::svgImageFrame;
+}
+
+//----------------------------------------------------------------------
+// nsSVGPathGeometryFrame methods:
+
+// Lie about our fill/stroke so that covered region and hit detection work properly
+
+void
+nsSVGImageFrame::ReflowSVG()
+{
+ NS_ASSERTION(nsSVGUtils::OuterSVGIsCallingReflowSVG(this),
+ "This call is probably a wasteful mistake");
+
+ MOZ_ASSERT(!(GetStateBits() & NS_FRAME_IS_NONDISPLAY),
+ "ReflowSVG mechanism not designed for this");
+
+ if (!nsSVGUtils::NeedsReflowSVG(this)) {
+ return;
+ }
+
+ float x, y, width, height;
+ SVGImageElement *element = static_cast<SVGImageElement*>(mContent);
+ element->GetAnimatedLengthValues(&x, &y, &width, &height, nullptr);
+
+ Rect extent(x, y, width, height);
+
+ if (!extent.IsEmpty()) {
+ mRect = nsLayoutUtils::RoundGfxRectToAppRect(extent,
+ PresContext()->AppUnitsPerCSSPixel());
+ } else {
+ mRect.SetEmpty();
+ }
+
+ if (mState & NS_FRAME_FIRST_REFLOW) {
+ // Make sure we have our filter property (if any) before calling
+ // FinishAndStoreOverflow (subsequent filter changes are handled off
+ // nsChangeHint_UpdateEffects):
+ nsSVGEffects::UpdateEffects(this);
+
+ if (!mReflowCallbackPosted) {
+ nsIPresShell* shell = PresContext()->PresShell();
+ mReflowCallbackPosted = true;
+ shell->PostReflowCallback(this);
+ }
+ }
+
+ nsRect overflow = nsRect(nsPoint(0,0), mRect.Size());
+ nsOverflowAreas overflowAreas(overflow, overflow);
+ FinishAndStoreOverflow(overflowAreas, mRect.Size());
+
+ mState &= ~(NS_FRAME_FIRST_REFLOW | NS_FRAME_IS_DIRTY |
+ NS_FRAME_HAS_DIRTY_CHILDREN);
+
+ // Invalidate, but only if this is not our first reflow (since if it is our
+ // first reflow then we haven't had our first paint yet).
+ if (!(GetParent()->GetStateBits() & NS_FRAME_FIRST_REFLOW)) {
+ InvalidateFrame();
+ }
+}
+
+bool
+nsSVGImageFrame::ReflowFinished()
+{
+ mReflowCallbackPosted = false;
+
+ // XXX(seth): We don't need this. The purpose of updating visibility
+ // synchronously is to ensure that animated images start animating
+ // immediately. In the short term, however,
+ // nsImageLoadingContent::OnUnlockedDraw() is enough to ensure that
+ // animations start as soon as the image is painted for the first time, and in
+ // the long term we want to update visibility information from the display
+ // list whenever we paint, so we don't actually need to do this. However, to
+ // avoid behavior changes during the transition from the old image visibility
+ // code, we'll leave it in for now.
+ UpdateVisibilitySynchronously();
+
+ return false;
+}
+
+void
+nsSVGImageFrame::ReflowCallbackCanceled()
+{
+ mReflowCallbackPosted = false;
+}
+
+uint16_t
+nsSVGImageFrame::GetHitTestFlags()
+{
+ uint16_t flags = 0;
+
+ switch (StyleUserInterface()->mPointerEvents) {
+ case NS_STYLE_POINTER_EVENTS_NONE:
+ break;
+ case NS_STYLE_POINTER_EVENTS_VISIBLEPAINTED:
+ case NS_STYLE_POINTER_EVENTS_AUTO:
+ if (StyleVisibility()->IsVisible()) {
+ /* XXX: should check pixel transparency */
+ flags |= SVG_HIT_TEST_FILL;
+ }
+ break;
+ case NS_STYLE_POINTER_EVENTS_VISIBLEFILL:
+ case NS_STYLE_POINTER_EVENTS_VISIBLESTROKE:
+ case NS_STYLE_POINTER_EVENTS_VISIBLE:
+ if (StyleVisibility()->IsVisible()) {
+ flags |= SVG_HIT_TEST_FILL;
+ }
+ break;
+ case NS_STYLE_POINTER_EVENTS_PAINTED:
+ /* XXX: should check pixel transparency */
+ flags |= SVG_HIT_TEST_FILL;
+ break;
+ case NS_STYLE_POINTER_EVENTS_FILL:
+ case NS_STYLE_POINTER_EVENTS_STROKE:
+ case NS_STYLE_POINTER_EVENTS_ALL:
+ flags |= SVG_HIT_TEST_FILL;
+ break;
+ default:
+ NS_ERROR("not reached");
+ break;
+ }
+
+ return flags;
+}
+
+//----------------------------------------------------------------------
+// nsSVGImageListener implementation
+
+NS_IMPL_ISUPPORTS(nsSVGImageListener, imgINotificationObserver)
+
+nsSVGImageListener::nsSVGImageListener(nsSVGImageFrame *aFrame) : mFrame(aFrame)
+{
+}
+
+NS_IMETHODIMP
+nsSVGImageListener::Notify(imgIRequest *aRequest, int32_t aType, const nsIntRect* aData)
+{
+ if (!mFrame)
+ return NS_ERROR_FAILURE;
+
+ if (aType == imgINotificationObserver::LOAD_COMPLETE) {
+ mFrame->InvalidateFrame();
+ nsLayoutUtils::PostRestyleEvent(
+ mFrame->GetContent()->AsElement(), nsRestyleHint(0),
+ nsChangeHint_InvalidateRenderingObservers);
+ nsSVGUtils::ScheduleReflowSVG(mFrame);
+ }
+
+ if (aType == imgINotificationObserver::FRAME_UPDATE) {
+ // No new dimensions, so we don't need to call
+ // nsSVGUtils::InvalidateAndScheduleBoundsUpdate.
+ nsLayoutUtils::PostRestyleEvent(
+ mFrame->GetContent()->AsElement(), nsRestyleHint(0),
+ nsChangeHint_InvalidateRenderingObservers);
+ mFrame->InvalidateFrame();
+ }
+
+ if (aType == imgINotificationObserver::SIZE_AVAILABLE) {
+ // Called once the resource's dimensions have been obtained.
+ aRequest->GetImage(getter_AddRefs(mFrame->mImageContainer));
+ mFrame->InvalidateFrame();
+ nsLayoutUtils::PostRestyleEvent(
+ mFrame->GetContent()->AsElement(), nsRestyleHint(0),
+ nsChangeHint_InvalidateRenderingObservers);
+ nsSVGUtils::ScheduleReflowSVG(mFrame);
+ }
+
+ return NS_OK;
+}
+
diff --git a/layout/svg/nsSVGInnerSVGFrame.cpp b/layout/svg/nsSVGInnerSVGFrame.cpp
new file mode 100644
index 0000000000..8b5750d167
--- /dev/null
+++ b/layout/svg/nsSVGInnerSVGFrame.cpp
@@ -0,0 +1,322 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+// Main header first:
+#include "nsSVGInnerSVGFrame.h"
+
+// Keep others in (case-insensitive) order:
+#include "gfx2DGlue.h"
+#include "gfxContext.h"
+#include "nsIFrame.h"
+#include "nsISVGChildFrame.h"
+#include "nsSVGContainerFrame.h"
+#include "nsSVGIntegrationUtils.h"
+#include "mozilla/dom/SVGSVGElement.h"
+
+using namespace mozilla;
+using namespace mozilla::dom;
+using namespace mozilla::gfx;
+using namespace mozilla::image;
+
+nsIFrame*
+NS_NewSVGInnerSVGFrame(nsIPresShell* aPresShell, nsStyleContext* aContext)
+{
+ return new (aPresShell) nsSVGInnerSVGFrame(aContext);
+}
+
+NS_IMPL_FRAMEARENA_HELPERS(nsSVGInnerSVGFrame)
+
+//----------------------------------------------------------------------
+// nsIFrame methods
+
+NS_QUERYFRAME_HEAD(nsSVGInnerSVGFrame)
+ NS_QUERYFRAME_ENTRY(nsSVGInnerSVGFrame)
+ NS_QUERYFRAME_ENTRY(nsISVGSVGFrame)
+NS_QUERYFRAME_TAIL_INHERITING(nsSVGDisplayContainerFrame)
+
+#ifdef DEBUG
+void
+nsSVGInnerSVGFrame::Init(nsIContent* aContent,
+ nsContainerFrame* aParent,
+ nsIFrame* aPrevInFlow)
+{
+ NS_ASSERTION(aContent->IsSVGElement(nsGkAtoms::svg),
+ "Content is not an SVG 'svg' element!");
+
+ nsSVGDisplayContainerFrame::Init(aContent, aParent, aPrevInFlow);
+}
+#endif /* DEBUG */
+
+nsIAtom *
+nsSVGInnerSVGFrame::GetType() const
+{
+ return nsGkAtoms::svgInnerSVGFrame;
+}
+
+//----------------------------------------------------------------------
+// nsISVGChildFrame methods
+
+DrawResult
+nsSVGInnerSVGFrame::PaintSVG(gfxContext& aContext,
+ const gfxMatrix& aTransform,
+ const nsIntRect *aDirtyRect)
+{
+ NS_ASSERTION(!NS_SVGDisplayListPaintingEnabled() ||
+ (mState & NS_FRAME_IS_NONDISPLAY),
+ "If display lists are enabled, only painting of non-display "
+ "SVG should take this code path");
+
+ gfxContextAutoSaveRestore autoSR;
+
+ if (StyleDisplay()->IsScrollableOverflow()) {
+ float x, y, width, height;
+ static_cast<SVGSVGElement*>(mContent)->
+ GetAnimatedLengthValues(&x, &y, &width, &height, nullptr);
+
+ if (width <= 0 || height <= 0) {
+ return DrawResult::SUCCESS;
+ }
+
+ autoSR.SetContext(&aContext);
+ gfxRect clipRect =
+ nsSVGUtils::GetClipRectForFrame(this, x, y, width, height);
+ nsSVGUtils::SetClipRect(&aContext, aTransform, clipRect);
+ }
+
+ return nsSVGDisplayContainerFrame::PaintSVG(aContext, aTransform, aDirtyRect);
+}
+
+nsRect
+nsSVGInnerSVGFrame::GetCoveredRegion()
+{
+ float x, y, w, h;
+ static_cast<SVGSVGElement*>(mContent)->
+ GetAnimatedLengthValues(&x, &y, &w, &h, nullptr);
+ if (w < 0.0f) w = 0.0f;
+ if (h < 0.0f) h = 0.0f;
+ // GetCanvasTM includes the x,y translation
+ nsRect bounds = nsSVGUtils::ToCanvasBounds(gfxRect(0.0, 0.0, w, h),
+ GetCanvasTM(),
+ PresContext());
+
+ if (!StyleDisplay()->IsScrollableOverflow()) {
+ bounds.UnionRect(bounds, nsSVGUtils::GetCoveredRegion(mFrames));
+ }
+ return bounds;
+}
+
+void
+nsSVGInnerSVGFrame::ReflowSVG()
+{
+ // mRect must be set before FinishAndStoreOverflow is called in order
+ // for our overflow areas to be clipped correctly.
+ float x, y, width, height;
+ static_cast<SVGSVGElement*>(mContent)->
+ GetAnimatedLengthValues(&x, &y, &width, &height, nullptr);
+ mRect = nsLayoutUtils::RoundGfxRectToAppRect(
+ gfxRect(x, y, width, height),
+ PresContext()->AppUnitsPerCSSPixel());
+
+ // If we have a filter, we need to invalidate ourselves because filter
+ // output can change even if none of our descendants need repainting.
+ if (StyleEffects()->HasFilters()) {
+ InvalidateFrame();
+ }
+
+ nsSVGDisplayContainerFrame::ReflowSVG();
+}
+
+void
+nsSVGInnerSVGFrame::NotifySVGChanged(uint32_t aFlags)
+{
+ MOZ_ASSERT(aFlags & (TRANSFORM_CHANGED | COORD_CONTEXT_CHANGED),
+ "Invalidation logic may need adjusting");
+
+ if (aFlags & COORD_CONTEXT_CHANGED) {
+
+ SVGSVGElement *svg = static_cast<SVGSVGElement*>(mContent);
+
+ bool xOrYIsPercentage =
+ svg->mLengthAttributes[SVGSVGElement::ATTR_X].IsPercentage() ||
+ svg->mLengthAttributes[SVGSVGElement::ATTR_Y].IsPercentage();
+ bool widthOrHeightIsPercentage =
+ svg->mLengthAttributes[SVGSVGElement::ATTR_WIDTH].IsPercentage() ||
+ svg->mLengthAttributes[SVGSVGElement::ATTR_HEIGHT].IsPercentage();
+
+ if (xOrYIsPercentage || widthOrHeightIsPercentage) {
+ // Ancestor changes can't affect how we render from the perspective of
+ // any rendering observers that we may have, so we don't need to
+ // invalidate them. We also don't need to invalidate ourself, since our
+ // changed ancestor will have invalidated its entire area, which includes
+ // our area.
+ // For perf reasons we call this before calling NotifySVGChanged() below.
+ nsSVGUtils::ScheduleReflowSVG(this);
+ }
+
+ // Coordinate context changes affect mCanvasTM if we have a
+ // percentage 'x' or 'y', or if we have a percentage 'width' or 'height' AND
+ // a 'viewBox'.
+
+ if (!(aFlags & TRANSFORM_CHANGED) &&
+ (xOrYIsPercentage ||
+ (widthOrHeightIsPercentage && svg->HasViewBoxRect()))) {
+ aFlags |= TRANSFORM_CHANGED;
+ }
+
+ if (svg->HasViewBoxRect() || !widthOrHeightIsPercentage) {
+ // Remove COORD_CONTEXT_CHANGED, since we establish the coordinate
+ // context for our descendants and this notification won't change its
+ // dimensions:
+ aFlags &= ~COORD_CONTEXT_CHANGED;
+
+ if (!aFlags) {
+ return; // No notification flags left
+ }
+ }
+ }
+
+ if (aFlags & TRANSFORM_CHANGED) {
+ // make sure our cached transform matrix gets (lazily) updated
+ mCanvasTM = nullptr;
+ }
+
+ nsSVGDisplayContainerFrame::NotifySVGChanged(aFlags);
+}
+
+nsresult
+nsSVGInnerSVGFrame::AttributeChanged(int32_t aNameSpaceID,
+ nsIAtom* aAttribute,
+ int32_t aModType)
+{
+ if (aNameSpaceID == kNameSpaceID_None &&
+ !(GetStateBits() & NS_FRAME_IS_NONDISPLAY)) {
+
+ SVGSVGElement* content = static_cast<SVGSVGElement*>(mContent);
+
+ if (aAttribute == nsGkAtoms::width ||
+ aAttribute == nsGkAtoms::height) {
+ nsLayoutUtils::PostRestyleEvent(
+ mContent->AsElement(), nsRestyleHint(0),
+ nsChangeHint_InvalidateRenderingObservers);
+ nsSVGUtils::ScheduleReflowSVG(this);
+
+ if (content->HasViewBoxOrSyntheticViewBox()) {
+ // make sure our cached transform matrix gets (lazily) updated
+ mCanvasTM = nullptr;
+ content->ChildrenOnlyTransformChanged();
+ nsSVGUtils::NotifyChildrenOfSVGChange(this, TRANSFORM_CHANGED);
+ } else {
+ uint32_t flags = COORD_CONTEXT_CHANGED;
+ if (mCanvasTM && mCanvasTM->IsSingular()) {
+ mCanvasTM = nullptr;
+ flags |= TRANSFORM_CHANGED;
+ }
+ nsSVGUtils::NotifyChildrenOfSVGChange(this, flags);
+ }
+
+ } else if (aAttribute == nsGkAtoms::transform ||
+ aAttribute == nsGkAtoms::preserveAspectRatio ||
+ aAttribute == nsGkAtoms::viewBox ||
+ aAttribute == nsGkAtoms::x ||
+ aAttribute == nsGkAtoms::y) {
+ // make sure our cached transform matrix gets (lazily) updated
+ mCanvasTM = nullptr;
+
+ nsSVGUtils::NotifyChildrenOfSVGChange(
+ this, aAttribute == nsGkAtoms::viewBox ?
+ TRANSFORM_CHANGED | COORD_CONTEXT_CHANGED : TRANSFORM_CHANGED);
+
+ // We don't invalidate for transform changes (the layers code does that).
+ // Also note that SVGTransformableElement::GetAttributeChangeHint will
+ // return nsChangeHint_UpdateOverflow for "transform" attribute changes
+ // and cause DoApplyRenderingChangeToTree to make the SchedulePaint call.
+
+ if (aAttribute == nsGkAtoms::x || aAttribute == nsGkAtoms::y) {
+ nsLayoutUtils::PostRestyleEvent(
+ mContent->AsElement(), nsRestyleHint(0),
+ nsChangeHint_InvalidateRenderingObservers);
+ nsSVGUtils::ScheduleReflowSVG(this);
+ } else if (aAttribute == nsGkAtoms::viewBox ||
+ (aAttribute == nsGkAtoms::preserveAspectRatio &&
+ content->HasViewBoxOrSyntheticViewBox())) {
+ content->ChildrenOnlyTransformChanged();
+ // SchedulePaint sets a global state flag so we only need to call it once
+ // (on ourself is fine), not once on each child (despite bug 828240).
+ SchedulePaint();
+ }
+ }
+ }
+
+ return NS_OK;
+}
+
+nsIFrame*
+nsSVGInnerSVGFrame::GetFrameForPoint(const gfxPoint& aPoint)
+{
+ NS_ASSERTION(!NS_SVGDisplayListHitTestingEnabled() ||
+ (mState & NS_FRAME_IS_NONDISPLAY),
+ "If display lists are enabled, only hit-testing of non-display "
+ "SVG should take this code path");
+
+ if (StyleDisplay()->IsScrollableOverflow()) {
+ Rect clip;
+ static_cast<nsSVGElement*>(mContent)->
+ GetAnimatedLengthValues(&clip.x, &clip.y,
+ &clip.width, &clip.height, nullptr);
+ if (!clip.Contains(ToPoint(aPoint))) {
+ return nullptr;
+ }
+ }
+
+ return nsSVGDisplayContainerFrame::GetFrameForPoint(aPoint);
+}
+
+//----------------------------------------------------------------------
+// nsISVGSVGFrame methods:
+
+void
+nsSVGInnerSVGFrame::NotifyViewportOrTransformChanged(uint32_t aFlags)
+{
+ // The dimensions of inner-<svg> frames are purely defined by their "width"
+ // and "height" attributes, and transform changes can only occur as a result
+ // of changes to their "width", "height", "viewBox" or "preserveAspectRatio"
+ // attributes. Changes to all of these attributes are handled in
+ // AttributeChanged(), so we should never be called.
+ NS_ERROR("Not called for nsSVGInnerSVGFrame");
+}
+
+//----------------------------------------------------------------------
+// nsSVGContainerFrame methods:
+
+gfxMatrix
+nsSVGInnerSVGFrame::GetCanvasTM()
+{
+ if (!mCanvasTM) {
+ NS_ASSERTION(GetParent(), "null parent");
+
+ nsSVGContainerFrame *parent = static_cast<nsSVGContainerFrame*>(GetParent());
+ SVGSVGElement *content = static_cast<SVGSVGElement*>(mContent);
+
+ gfxMatrix tm = content->PrependLocalTransformsTo(parent->GetCanvasTM());
+
+ mCanvasTM = new gfxMatrix(tm);
+ }
+ return *mCanvasTM;
+}
+
+bool
+nsSVGInnerSVGFrame::HasChildrenOnlyTransform(gfx::Matrix *aTransform) const
+{
+ SVGSVGElement *content = static_cast<SVGSVGElement*>(mContent);
+
+ if (content->HasViewBoxOrSyntheticViewBox()) {
+ // XXX Maybe return false if the transform is the identity transform?
+ if (aTransform) {
+ *aTransform = content->GetViewBoxTransform();
+ }
+ return true;
+ }
+ return false;
+}
diff --git a/layout/svg/nsSVGInnerSVGFrame.h b/layout/svg/nsSVGInnerSVGFrame.h
new file mode 100644
index 0000000000..5675509c19
--- /dev/null
+++ b/layout/svg/nsSVGInnerSVGFrame.h
@@ -0,0 +1,77 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef __NS_SVGINNERSVGFRAME_H__
+#define __NS_SVGINNERSVGFRAME_H__
+
+#include "mozilla/Attributes.h"
+#include "nsAutoPtr.h"
+#include "nsSVGContainerFrame.h"
+#include "nsISVGSVGFrame.h"
+
+class gfxContext;
+
+class nsSVGInnerSVGFrame : public nsSVGDisplayContainerFrame
+ , public nsISVGSVGFrame
+{
+ friend nsIFrame*
+ NS_NewSVGInnerSVGFrame(nsIPresShell* aPresShell, nsStyleContext* aContext);
+protected:
+ explicit nsSVGInnerSVGFrame(nsStyleContext* aContext)
+ : nsSVGDisplayContainerFrame(aContext) {}
+
+public:
+ NS_DECL_QUERYFRAME_TARGET(nsSVGInnerSVGFrame)
+ NS_DECL_QUERYFRAME
+ NS_DECL_FRAMEARENA_HELPERS
+
+#ifdef DEBUG
+ virtual void Init(nsIContent* aContent,
+ nsContainerFrame* aParent,
+ nsIFrame* aPrevInFlow) override;
+#endif
+
+ /**
+ * Get the "type" of the frame
+ *
+ * @see nsGkAtoms::svgInnerSVGFrame
+ */
+ virtual nsIAtom* GetType() const override;
+
+#ifdef DEBUG_FRAME_DUMP
+ virtual nsresult GetFrameName(nsAString& aResult) const override
+ {
+ return MakeFrameName(NS_LITERAL_STRING("SVGInnerSVG"), aResult);
+ }
+#endif
+
+ virtual nsresult AttributeChanged(int32_t aNameSpaceID,
+ nsIAtom* aAttribute,
+ int32_t aModType) override;
+
+ // nsISVGChildFrame interface:
+ virtual DrawResult PaintSVG(gfxContext& aContext,
+ const gfxMatrix& aTransform,
+ const nsIntRect *aDirtyRect = nullptr) override;
+ virtual nsRect GetCoveredRegion() override;
+ virtual void ReflowSVG() override;
+ virtual void NotifySVGChanged(uint32_t aFlags) override;
+ virtual nsIFrame* GetFrameForPoint(const gfxPoint& aPoint) override;
+
+ // nsSVGContainerFrame methods:
+ virtual gfxMatrix GetCanvasTM() override;
+
+ virtual bool HasChildrenOnlyTransform(Matrix *aTransform) const override;
+
+ // nsISVGSVGFrame interface:
+ virtual void NotifyViewportOrTransformChanged(uint32_t aFlags) override;
+
+protected:
+
+ nsAutoPtr<gfxMatrix> mCanvasTM;
+};
+
+#endif
+
diff --git a/layout/svg/nsSVGIntegrationUtils.cpp b/layout/svg/nsSVGIntegrationUtils.cpp
new file mode 100644
index 0000000000..498f693937
--- /dev/null
+++ b/layout/svg/nsSVGIntegrationUtils.cpp
@@ -0,0 +1,1142 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+// Main header first:
+#include "nsSVGIntegrationUtils.h"
+
+// Keep others in (case-insensitive) order:
+#include "gfxDrawable.h"
+#include "nsCSSAnonBoxes.h"
+#include "nsCSSClipPathInstance.h"
+#include "nsDisplayList.h"
+#include "nsFilterInstance.h"
+#include "nsLayoutUtils.h"
+#include "nsRenderingContext.h"
+#include "nsSVGClipPathFrame.h"
+#include "nsSVGEffects.h"
+#include "nsSVGElement.h"
+#include "nsSVGFilterPaintCallback.h"
+#include "nsSVGMaskFrame.h"
+#include "nsSVGPaintServerFrame.h"
+#include "nsSVGUtils.h"
+#include "FrameLayerBuilder.h"
+#include "BasicLayers.h"
+#include "mozilla/gfx/Point.h"
+#include "nsCSSRendering.h"
+#include "mozilla/Unused.h"
+
+using namespace mozilla;
+using namespace mozilla::layers;
+using namespace mozilla::gfx;
+using namespace mozilla::image;
+
+// ----------------------------------------------------------------------
+
+/**
+ * This class is used to get the pre-effects visual overflow rect of a frame,
+ * or, in the case of a frame with continuations, to collect the union of the
+ * pre-effects visual overflow rects of all the continuations. The result is
+ * relative to the origin (top left corner of the border box) of the frame, or,
+ * if the frame has continuations, the origin of the _first_ continuation.
+ */
+class PreEffectsVisualOverflowCollector : public nsLayoutUtils::BoxCallback
+{
+public:
+ /**
+ * If the pre-effects visual overflow rect of the frame being examined
+ * happens to be known, it can be passed in as aCurrentFrame and its
+ * pre-effects visual overflow rect can be passed in as
+ * aCurrentFrameOverflowArea. This is just an optimization to save a
+ * frame property lookup - these arguments are optional.
+ */
+ PreEffectsVisualOverflowCollector(nsIFrame* aFirstContinuation,
+ nsIFrame* aCurrentFrame,
+ const nsRect& aCurrentFrameOverflowArea)
+ : mFirstContinuation(aFirstContinuation)
+ , mCurrentFrame(aCurrentFrame)
+ , mCurrentFrameOverflowArea(aCurrentFrameOverflowArea)
+ {
+ NS_ASSERTION(!mFirstContinuation->GetPrevContinuation(),
+ "We want the first continuation here");
+ }
+
+ virtual void AddBox(nsIFrame* aFrame) override {
+ nsRect overflow = (aFrame == mCurrentFrame) ?
+ mCurrentFrameOverflowArea : GetPreEffectsVisualOverflowRect(aFrame);
+ mResult.UnionRect(mResult, overflow + aFrame->GetOffsetTo(mFirstContinuation));
+ }
+
+ nsRect GetResult() const {
+ return mResult;
+ }
+
+private:
+
+ static nsRect GetPreEffectsVisualOverflowRect(nsIFrame* aFrame) {
+ nsRect* r = aFrame->Properties().Get(nsIFrame::PreEffectsBBoxProperty());
+ if (r) {
+ return *r;
+ }
+ // Despite the fact that we're invoked for frames with SVG effects applied,
+ // we can actually get here. All continuations and IB split siblings of a
+ // frame with SVG effects applied will have the PreEffectsBBoxProperty
+ // property set on them. Therefore, the frames that are passed to us will
+ // always have that property set...well, with one exception. If the frames
+ // for an element with SVG effects applied have been subject to an "IB
+ // split", then the block frame(s) that caused the split will have been
+ // wrapped in anonymous, inline-block, nsBlockFrames of pseudo-type
+ // nsCSSAnonBoxes::mozAnonymousBlock. These "IB split sibling" anonymous
+ // blocks will have the PreEffectsBBoxProperty property set on them, but
+ // they will never be passed to us. Instead, we'll be passed the block
+ // children that they wrap, which don't have the PreEffectsBBoxProperty
+ // property set on them. This is actually okay. What we care about is
+ // collecting the _pre_ effects visual overflow rects of the frames to
+ // which the SVG effects have been applied. Since the IB split results in
+ // any overflow rect adjustments for transforms, effects, etc. taking
+ // place on the anonymous block wrappers, the wrapped children are left
+ // with their overflow rects unaffected. In other words, calling
+ // GetVisualOverflowRect() on the children will return their pre-effects
+ // visual overflow rects, just as we need.
+ //
+ // A couple of tests that demonstrate the IB split and cause us to get here
+ // are:
+ //
+ // * reftests/svg/svg-integration/clipPath-html-06.xhtml
+ // * reftests/svg/svg-integration/clipPath-html-06-extref.xhtml
+ //
+ // If we ever got passed a frame with the PreTransformOverflowAreasProperty
+ // property set, that would be bad, since then our GetVisualOverflowRect()
+ // call would give us the post-effects, and post-transform, overflow rect.
+ //
+ NS_ASSERTION(aFrame->GetParent()->StyleContext()->GetPseudo() ==
+ nsCSSAnonBoxes::mozAnonymousBlock,
+ "How did we getting here, then?");
+ NS_ASSERTION(!aFrame->Properties().Get(
+ aFrame->PreTransformOverflowAreasProperty()),
+ "GetVisualOverflowRect() won't return the pre-effects rect!");
+ return aFrame->GetVisualOverflowRect();
+ }
+
+ nsIFrame* mFirstContinuation;
+ nsIFrame* mCurrentFrame;
+ const nsRect& mCurrentFrameOverflowArea;
+ nsRect mResult;
+};
+
+/**
+ * Gets the union of the pre-effects visual overflow rects of all of a frame's
+ * continuations, in "user space".
+ */
+static nsRect
+GetPreEffectsVisualOverflowUnion(nsIFrame* aFirstContinuation,
+ nsIFrame* aCurrentFrame,
+ const nsRect& aCurrentFramePreEffectsOverflow,
+ const nsPoint& aFirstContinuationToUserSpace)
+{
+ NS_ASSERTION(!aFirstContinuation->GetPrevContinuation(),
+ "Need first continuation here");
+ PreEffectsVisualOverflowCollector collector(aFirstContinuation,
+ aCurrentFrame,
+ aCurrentFramePreEffectsOverflow);
+ // Compute union of all overflow areas relative to aFirstContinuation:
+ nsLayoutUtils::GetAllInFlowBoxes(aFirstContinuation, &collector);
+ // Return the result in user space:
+ return collector.GetResult() + aFirstContinuationToUserSpace;
+}
+
+
+bool
+nsSVGIntegrationUtils::UsingEffectsForFrame(const nsIFrame* aFrame)
+{
+ // Even when SVG display lists are disabled, returning true for SVG frames
+ // does not adversely affect any of our callers. Therefore we don't bother
+ // checking the SDL prefs here, since we don't know if we're being called for
+ // painting or hit-testing anyway.
+ const nsStyleSVGReset *style = aFrame->StyleSVGReset();
+ return aFrame->StyleEffects()->HasFilters() ||
+ style->HasClipPath() ||
+ style->mMask.HasLayerWithImage();
+}
+
+bool
+nsSVGIntegrationUtils::UsingMaskOrClipPathForFrame(const nsIFrame* aFrame)
+{
+ const nsStyleSVGReset *style = aFrame->StyleSVGReset();
+ return style->HasClipPath() ||
+ style->mMask.HasLayerWithImage();
+}
+
+nsPoint
+nsSVGIntegrationUtils::GetOffsetToBoundingBox(nsIFrame* aFrame)
+{
+ if ((aFrame->GetStateBits() & NS_FRAME_SVG_LAYOUT)) {
+ // Do NOT call GetAllInFlowRectsUnion for SVG - it will get the
+ // covered region relative to the nsSVGOuterSVGFrame, which is absolutely
+ // not what we want. SVG frames are always in user space, so they have
+ // no offset adjustment to make.
+ return nsPoint();
+ }
+
+ // The GetAllInFlowRectsUnion() call gets the union of the frame border-box
+ // rects over all continuations, relative to the origin (top-left of the
+ // border box) of its second argument (here, aFrame, the first continuation).
+ return -nsLayoutUtils::GetAllInFlowRectsUnion(aFrame, aFrame).TopLeft();
+}
+
+/* static */ nsSize
+nsSVGIntegrationUtils::GetContinuationUnionSize(nsIFrame* aNonSVGFrame)
+{
+ NS_ASSERTION(!aNonSVGFrame->IsFrameOfType(nsIFrame::eSVG),
+ "SVG frames should not get here");
+ nsIFrame* firstFrame =
+ nsLayoutUtils::FirstContinuationOrIBSplitSibling(aNonSVGFrame);
+ return nsLayoutUtils::GetAllInFlowRectsUnion(firstFrame, firstFrame).Size();
+}
+
+/* static */ gfx::Size
+nsSVGIntegrationUtils::GetSVGCoordContextForNonSVGFrame(nsIFrame* aNonSVGFrame)
+{
+ NS_ASSERTION(!aNonSVGFrame->IsFrameOfType(nsIFrame::eSVG),
+ "SVG frames should not get here");
+ nsIFrame* firstFrame =
+ nsLayoutUtils::FirstContinuationOrIBSplitSibling(aNonSVGFrame);
+ nsRect r = nsLayoutUtils::GetAllInFlowRectsUnion(firstFrame, firstFrame);
+ nsPresContext* presContext = firstFrame->PresContext();
+ return gfx::Size(presContext->AppUnitsToFloatCSSPixels(r.width),
+ presContext->AppUnitsToFloatCSSPixels(r.height));
+}
+
+gfxRect
+nsSVGIntegrationUtils::GetSVGBBoxForNonSVGFrame(nsIFrame* aNonSVGFrame)
+{
+ NS_ASSERTION(!aNonSVGFrame->IsFrameOfType(nsIFrame::eSVG),
+ "SVG frames should not get here");
+ nsIFrame* firstFrame =
+ nsLayoutUtils::FirstContinuationOrIBSplitSibling(aNonSVGFrame);
+ // 'r' is in "user space":
+ nsRect r = GetPreEffectsVisualOverflowUnion(firstFrame, nullptr, nsRect(),
+ GetOffsetToBoundingBox(firstFrame));
+ return nsLayoutUtils::RectToGfxRect(r,
+ aNonSVGFrame->PresContext()->AppUnitsPerCSSPixel());
+}
+
+// XXX Since we're called during reflow, this method is broken for frames with
+// continuations. When we're called for a frame with continuations, we're
+// called for each continuation in turn as it's reflowed. However, it isn't
+// until the last continuation is reflowed that this method's
+// GetOffsetToBoundingBox() and GetPreEffectsVisualOverflowUnion() calls will
+// obtain valid border boxes for all the continuations. As a result, we'll
+// end up returning bogus post-filter visual overflow rects for all the prior
+// continuations. Unfortunately, by the time the last continuation is
+// reflowed, it's too late to go back and set and propagate the overflow
+// rects on the previous continuations.
+//
+// The reason that we need to pass an override bbox to
+// GetPreEffectsVisualOverflowUnion rather than just letting it call into our
+// GetSVGBBoxForNonSVGFrame method is because we get called by
+// ComputeEffectsRect when it has been called with
+// aStoreRectProperties set to false. In this case the pre-effects visual
+// overflow rect that it has been passed may be different to that stored on
+// aFrame, resulting in a different bbox.
+//
+// XXXjwatt The pre-effects visual overflow rect passed to
+// ComputeEffectsRect won't include continuation overflows, so
+// for frames with continuation the following filter analysis will likely end
+// up being carried out with a bbox created as if the frame didn't have
+// continuations.
+//
+// XXXjwatt Using aPreEffectsOverflowRect to create the bbox isn't really right
+// for SVG frames, since for SVG frames the SVG spec defines the bbox to be
+// something quite different to the pre-effects visual overflow rect. However,
+// we're essentially calculating an invalidation area here, and using the
+// pre-effects overflow rect will actually overestimate that area which, while
+// being a bit wasteful, isn't otherwise a problem.
+//
+nsRect
+ nsSVGIntegrationUtils::
+ ComputePostEffectsVisualOverflowRect(nsIFrame* aFrame,
+ const nsRect& aPreEffectsOverflowRect)
+{
+ NS_ASSERTION(!(aFrame->GetStateBits() & NS_FRAME_SVG_LAYOUT),
+ "Don't call this on SVG child frames");
+
+ nsIFrame* firstFrame =
+ nsLayoutUtils::FirstContinuationOrIBSplitSibling(aFrame);
+ nsSVGEffects::EffectProperties effectProperties =
+ nsSVGEffects::GetEffectProperties(firstFrame);
+ if (!effectProperties.HasValidFilter()) {
+ return aPreEffectsOverflowRect;
+ }
+
+ // Create an override bbox - see comment above:
+ nsPoint firstFrameToBoundingBox = GetOffsetToBoundingBox(firstFrame);
+ // overrideBBox is in "user space", in _CSS_ pixels:
+ // XXX Why are we rounding out to pixel boundaries? We don't do that in
+ // GetSVGBBoxForNonSVGFrame, and it doesn't appear to be necessary.
+ gfxRect overrideBBox =
+ nsLayoutUtils::RectToGfxRect(
+ GetPreEffectsVisualOverflowUnion(firstFrame, aFrame,
+ aPreEffectsOverflowRect,
+ firstFrameToBoundingBox),
+ aFrame->PresContext()->AppUnitsPerCSSPixel());
+ overrideBBox.RoundOut();
+
+ nsRect overflowRect =
+ nsFilterInstance::GetPostFilterBounds(firstFrame, &overrideBBox);
+
+ // Return overflowRect relative to aFrame, rather than "user space":
+ return overflowRect - (aFrame->GetOffsetTo(firstFrame) + firstFrameToBoundingBox);
+}
+
+nsIntRegion
+nsSVGIntegrationUtils::AdjustInvalidAreaForSVGEffects(nsIFrame* aFrame,
+ const nsPoint& aToReferenceFrame,
+ const nsIntRegion& aInvalidRegion)
+{
+ if (aInvalidRegion.IsEmpty()) {
+ return nsIntRect();
+ }
+
+ // Don't bother calling GetEffectProperties; the filter property should
+ // already have been set up during reflow/ComputeFrameEffectsRect
+ nsIFrame* firstFrame =
+ nsLayoutUtils::FirstContinuationOrIBSplitSibling(aFrame);
+ nsSVGFilterProperty *prop = nsSVGEffects::GetFilterProperty(firstFrame);
+ if (!prop || !prop->IsInObserverLists()) {
+ return aInvalidRegion;
+ }
+
+ int32_t appUnitsPerDevPixel = aFrame->PresContext()->AppUnitsPerDevPixel();
+
+ if (!prop || !prop->ReferencesValidResources()) {
+ // The frame is either not there or not currently available,
+ // perhaps because we're in the middle of tearing stuff down.
+ // Be conservative, return our visual overflow rect relative
+ // to the reference frame.
+ nsRect overflow = aFrame->GetVisualOverflowRect() + aToReferenceFrame;
+ return overflow.ToOutsidePixels(appUnitsPerDevPixel);
+ }
+
+ // Convert aInvalidRegion into bounding box frame space in app units:
+ nsPoint toBoundingBox =
+ aFrame->GetOffsetTo(firstFrame) + GetOffsetToBoundingBox(firstFrame);
+ // The initial rect was relative to the reference frame, so we need to
+ // remove that offset to get a rect relative to the current frame.
+ toBoundingBox -= aToReferenceFrame;
+ nsRegion preEffectsRegion = aInvalidRegion.ToAppUnits(appUnitsPerDevPixel).MovedBy(toBoundingBox);
+
+ // Adjust the dirty area for effects, and shift it back to being relative to
+ // the reference frame.
+ nsRegion result = nsFilterInstance::GetPostFilterDirtyArea(firstFrame,
+ preEffectsRegion).MovedBy(-toBoundingBox);
+ // Return the result, in pixels relative to the reference frame.
+ return result.ToOutsidePixels(appUnitsPerDevPixel);
+}
+
+nsRect
+nsSVGIntegrationUtils::GetRequiredSourceForInvalidArea(nsIFrame* aFrame,
+ const nsRect& aDirtyRect)
+{
+ // Don't bother calling GetEffectProperties; the filter property should
+ // already have been set up during reflow/ComputeFrameEffectsRect
+ nsIFrame* firstFrame =
+ nsLayoutUtils::FirstContinuationOrIBSplitSibling(aFrame);
+ nsSVGFilterProperty *prop = nsSVGEffects::GetFilterProperty(firstFrame);
+ if (!prop || !prop->ReferencesValidResources()) {
+ return aDirtyRect;
+ }
+
+ // Convert aDirtyRect into "user space" in app units:
+ nsPoint toUserSpace =
+ aFrame->GetOffsetTo(firstFrame) + GetOffsetToBoundingBox(firstFrame);
+ nsRect postEffectsRect = aDirtyRect + toUserSpace;
+
+ // Return ther result, relative to aFrame, not in user space:
+ return nsFilterInstance::GetPreFilterNeededArea(firstFrame, postEffectsRect).GetBounds()
+ - toUserSpace;
+}
+
+bool
+nsSVGIntegrationUtils::HitTestFrameForEffects(nsIFrame* aFrame, const nsPoint& aPt)
+{
+ nsIFrame* firstFrame =
+ nsLayoutUtils::FirstContinuationOrIBSplitSibling(aFrame);
+ // Convert aPt to user space:
+ nsPoint toUserSpace;
+ if (aFrame->GetStateBits() & NS_FRAME_SVG_LAYOUT) {
+ // XXXmstange Isn't this wrong for svg:use and innerSVG frames?
+ toUserSpace = aFrame->GetPosition();
+ } else {
+ toUserSpace =
+ aFrame->GetOffsetTo(firstFrame) + GetOffsetToBoundingBox(firstFrame);
+ }
+ nsPoint pt = aPt + toUserSpace;
+ gfxPoint userSpacePt =
+ gfxPoint(pt.x, pt.y) / aFrame->PresContext()->AppUnitsPerCSSPixel();
+ return nsSVGUtils::HitTestClip(firstFrame, userSpacePt);
+}
+
+class RegularFramePaintCallback : public nsSVGFilterPaintCallback
+{
+public:
+ RegularFramePaintCallback(nsDisplayListBuilder* aBuilder,
+ LayerManager* aManager,
+ const nsPoint& aOffset)
+ : mBuilder(aBuilder), mLayerManager(aManager),
+ mOffset(aOffset) {}
+
+ virtual DrawResult Paint(gfxContext& aContext, nsIFrame *aTarget,
+ const gfxMatrix& aTransform,
+ const nsIntRect* aDirtyRect) override
+ {
+ BasicLayerManager* basic = mLayerManager->AsBasicLayerManager();
+ basic->SetTarget(&aContext);
+
+ gfxPoint devPixelOffset =
+ nsLayoutUtils::PointToGfxPoint(-mOffset,
+ aTarget->PresContext()->AppUnitsPerDevPixel());
+
+ gfxContextMatrixAutoSaveRestore autoSR(&aContext);
+ aContext.SetMatrix(aContext.CurrentMatrix().Translate(devPixelOffset));
+
+ mLayerManager->EndTransaction(FrameLayerBuilder::DrawPaintedLayer, mBuilder);
+ return DrawResult::SUCCESS;
+ }
+
+private:
+ nsDisplayListBuilder* mBuilder;
+ LayerManager* mLayerManager;
+ nsPoint mOffset;
+};
+
+/**
+ * Returns true if any of the masks is an image mask (and not an SVG mask).
+ */
+static bool
+HasNonSVGMask(const nsTArray<nsSVGMaskFrame*>& aMaskFrames)
+{
+ for (size_t i = 0; i < aMaskFrames.Length() ; i++) {
+ nsSVGMaskFrame *maskFrame = aMaskFrames[i];
+ if (!maskFrame) {
+ return true;
+ }
+ }
+
+ return false;
+}
+
+typedef nsSVGIntegrationUtils::PaintFramesParams PaintFramesParams;
+
+/**
+ * Paint css-positioned-mask onto a given target(aMaskDT).
+ */
+static DrawResult
+PaintMaskSurface(const PaintFramesParams& aParams,
+ DrawTarget* aMaskDT, float aOpacity, nsStyleContext* aSC,
+ const nsTArray<nsSVGMaskFrame*>& aMaskFrames,
+ const gfxMatrix& aMaskSurfaceMatrix,
+ const nsPoint& aOffsetToUserSpace)
+{
+ MOZ_ASSERT(aMaskFrames.Length() > 0);
+ MOZ_ASSERT(aMaskDT->GetFormat() == SurfaceFormat::A8);
+
+ const nsStyleSVGReset *svgReset = aSC->StyleSVGReset();
+ gfxMatrix cssPxToDevPxMatrix =
+ nsSVGIntegrationUtils::GetCSSPxToDevPxMatrix(aParams.frame);
+
+ nsPresContext* presContext = aParams.frame->PresContext();
+ gfxPoint devPixelOffsetToUserSpace =
+ nsLayoutUtils::PointToGfxPoint(aOffsetToUserSpace,
+ presContext->AppUnitsPerDevPixel());
+
+ RefPtr<gfxContext> maskContext = gfxContext::CreateOrNull(aMaskDT);
+ MOZ_ASSERT(maskContext);
+ maskContext->SetMatrix(aMaskSurfaceMatrix);
+
+ // Multiple SVG masks interleave with image mask. Paint each layer onto
+ // aMaskDT one at a time.
+ for (int i = aMaskFrames.Length() - 1; i >= 0 ; i--) {
+ nsSVGMaskFrame *maskFrame = aMaskFrames[i];
+
+ CompositionOp compositionOp = (i == int(aMaskFrames.Length() - 1))
+ ? CompositionOp::OP_OVER
+ : nsCSSRendering::GetGFXCompositeMode(svgReset->mMask.mLayers[i].mComposite);
+
+ // maskFrame != nullptr means we get a SVG mask.
+ // maskFrame == nullptr means we get an image mask.
+ if (maskFrame) {
+ Matrix svgMaskMatrix;
+ RefPtr<SourceSurface> svgMask =
+ maskFrame->GetMaskForMaskedFrame(maskContext, aParams.frame,
+ cssPxToDevPxMatrix,
+ aOpacity,
+ &svgMaskMatrix,
+ svgReset->mMask.mLayers[i].mMaskMode);
+ if (svgMask) {
+ gfxContextMatrixAutoSaveRestore matRestore(maskContext);
+
+ maskContext->Multiply(ThebesMatrix(svgMaskMatrix));
+ Rect drawRect = IntRectToRect(IntRect(IntPoint(0, 0), svgMask->GetSize()));
+ aMaskDT->MaskSurface(ColorPattern(Color(0.0, 0.0, 0.0, 1.0)), svgMask,
+ drawRect.TopLeft(),
+ DrawOptions(1.0, compositionOp));
+ }
+ } else {
+ gfxContextMatrixAutoSaveRestore matRestore(maskContext);
+
+ maskContext->Multiply(gfxMatrix::Translation(-devPixelOffsetToUserSpace));
+ nsRenderingContext rc(maskContext);
+ nsCSSRendering::PaintBGParams params =
+ nsCSSRendering::PaintBGParams::ForSingleLayer(*presContext,
+ rc, aParams.dirtyRect,
+ aParams.borderArea,
+ aParams.frame,
+ aParams.builder->GetBackgroundPaintFlags() |
+ nsCSSRendering::PAINTBG_MASK_IMAGE,
+ i, compositionOp);
+
+ DrawResult result =
+ nsCSSRendering::PaintBackgroundWithSC(params, aSC,
+ *aParams.frame->StyleBorder());
+ if (result != DrawResult::SUCCESS) {
+ return result;
+ }
+ }
+ }
+
+ return DrawResult::SUCCESS;
+}
+
+static DrawResult
+CreateAndPaintMaskSurface(const PaintFramesParams& aParams,
+ float aOpacity, nsStyleContext* aSC,
+ const nsTArray<nsSVGMaskFrame*>& aMaskFrames,
+ const nsPoint& aOffsetToUserSpace,
+ Matrix& aOutMaskTransform,
+ RefPtr<SourceSurface>& aOutMaskSurface,
+ bool& aOpacityApplied)
+{
+ const nsStyleSVGReset *svgReset = aSC->StyleSVGReset();
+ MOZ_ASSERT(aMaskFrames.Length() > 0);
+
+ gfxContext& ctx = aParams.ctx;
+
+ // There is only one SVG mask.
+ if (((aMaskFrames.Length() == 1) && aMaskFrames[0])) {
+ gfxMatrix cssPxToDevPxMatrix =
+ nsSVGIntegrationUtils::GetCSSPxToDevPxMatrix(aParams.frame);
+
+ aOpacityApplied = true;
+ aOutMaskSurface =
+ aMaskFrames[0]->GetMaskForMaskedFrame(&ctx, aParams.frame,
+ cssPxToDevPxMatrix, aOpacity,
+ &aOutMaskTransform,
+ svgReset->mMask.mLayers[0].mMaskMode);
+ return DrawResult::SUCCESS;
+ }
+
+ const IntRect& maskSurfaceRect = aParams.maskRect;
+ if (maskSurfaceRect.IsEmpty()) {
+ return DrawResult::SUCCESS;
+ }
+
+ RefPtr<DrawTarget> maskDT =
+ ctx.GetDrawTarget()->CreateSimilarDrawTarget(maskSurfaceRect.Size(),
+ SurfaceFormat::A8);
+ if (!maskDT || !maskDT->IsValid()) {
+ return DrawResult::TEMPORARY_ERROR;
+ }
+
+ // Set aAppliedOpacity as true only if all mask layers are svg mask.
+ // In this case, we will apply opacity into the final mask surface, so the
+ // caller does not need to apply it again.
+ aOpacityApplied = !HasNonSVGMask(aMaskFrames);
+
+ // Set context's matrix on maskContext, offset by the maskSurfaceRect's
+ // position. This makes sure that we combine the masks in device space.
+ gfxMatrix maskSurfaceMatrix =
+ ctx.CurrentMatrix() * gfxMatrix::Translation(-aParams.maskRect.TopLeft());
+
+ DrawResult result = PaintMaskSurface(aParams, maskDT,
+ aOpacityApplied ? aOpacity : 1.0,
+ aSC, aMaskFrames, maskSurfaceMatrix,
+ aOffsetToUserSpace);
+ if (result != DrawResult::SUCCESS) {
+ return result;
+ }
+
+ aOutMaskTransform = ToMatrix(maskSurfaceMatrix);
+ if (!aOutMaskTransform.Invert()) {
+ return DrawResult::SUCCESS;
+ }
+
+ aOutMaskSurface = maskDT->Snapshot();
+ return DrawResult::SUCCESS;
+}
+
+static bool
+ValidateSVGFrame(nsIFrame* aFrame)
+{
+#ifdef DEBUG
+ NS_ASSERTION(!(aFrame->GetStateBits() & NS_FRAME_SVG_LAYOUT) ||
+ (NS_SVGDisplayListPaintingEnabled() &&
+ !(aFrame->GetStateBits() & NS_FRAME_IS_NONDISPLAY)),
+ "Should not use nsSVGIntegrationUtils on this SVG frame");
+#endif
+
+ bool hasSVGLayout = (aFrame->GetStateBits() & NS_FRAME_SVG_LAYOUT);
+ if (hasSVGLayout) {
+#ifdef DEBUG
+ nsISVGChildFrame *svgChildFrame = do_QueryFrame(aFrame);
+ MOZ_ASSERT(svgChildFrame && aFrame->GetContent()->IsSVGElement(),
+ "A non-SVG frame carries NS_FRAME_SVG_LAYOUT flag?");
+#endif
+
+ const nsIContent* content = aFrame->GetContent();
+ if (!static_cast<const nsSVGElement*>(content)->HasValidDimensions()) {
+ // The SVG spec says not to draw _anything_
+ return false;
+ }
+ }
+
+ return true;
+}
+
+/**
+ * Setup transform matrix of a gfx context by a specific frame. Depend on
+ * aClipCtx, this function may clip that context by the visual overflow area
+ * of aFrame.
+ *
+ * @param aFrame is the target frame.
+ * @param aOffsetToBoundingBox returns the offset between the reference frame
+ * and the bounding box of aFrame.
+ * @oaram aOffsetToUserSpace returns the offset between the reference frame and
+ * the user space coordinate of aFrame.
+ */
+static void
+SetupContextMatrix(nsIFrame* aFrame, const PaintFramesParams& aParams,
+ nsPoint& aOffsetToBoundingBox, nsPoint& aOffsetToUserSpace)
+{
+ aOffsetToBoundingBox = aParams.builder->ToReferenceFrame(aFrame) -
+ nsSVGIntegrationUtils::GetOffsetToBoundingBox(aFrame);
+ if (!aFrame->IsFrameOfType(nsIFrame::eSVG)) {
+ /* Snap the offset if the reference frame is not a SVG frame,
+ * since other frames will be snapped to pixel when rendering. */
+ aOffsetToBoundingBox = nsPoint(
+ aFrame->PresContext()->RoundAppUnitsToNearestDevPixels(aOffsetToBoundingBox.x),
+ aFrame->PresContext()->RoundAppUnitsToNearestDevPixels(aOffsetToBoundingBox.y));
+ }
+
+ // After applying only "aOffsetToBoundingBox", aParams.ctx would have its
+ // origin at the top left corner of frame's bounding box (over all
+ // continuations).
+ // However, SVG painting needs the origin to be located at the origin of the
+ // SVG frame's "user space", i.e. the space in which, for example, the
+ // frame's BBox lives.
+ // SVG geometry frames and foreignObject frames apply their own offsets, so
+ // their position is relative to their user space. So for these frame types,
+ // if we want aCtx to be in user space, we first need to subtract the
+ // frame's position so that SVG painting can later add it again and the
+ // frame is painted in the right place.
+
+ gfxPoint toUserSpaceGfx = nsSVGUtils::FrameSpaceInCSSPxToUserSpaceOffset(aFrame);
+ nsPoint toUserSpace =
+ nsPoint(nsPresContext::CSSPixelsToAppUnits(float(toUserSpaceGfx.x)),
+ nsPresContext::CSSPixelsToAppUnits(float(toUserSpaceGfx.y)));
+
+ aOffsetToUserSpace = aOffsetToBoundingBox - toUserSpace;
+
+#ifdef DEBUG
+ bool hasSVGLayout = (aFrame->GetStateBits() & NS_FRAME_SVG_LAYOUT);
+ NS_ASSERTION(hasSVGLayout || aOffsetToBoundingBox == aOffsetToUserSpace,
+ "For non-SVG frames there shouldn't be any additional offset");
+#endif
+
+ gfxPoint devPixelOffsetToUserSpace =
+ nsLayoutUtils::PointToGfxPoint(aOffsetToUserSpace,
+ aFrame->PresContext()->AppUnitsPerDevPixel());
+ gfxContext& context = aParams.ctx;
+ context.SetMatrix(context.CurrentMatrix().Translate(devPixelOffsetToUserSpace));
+}
+
+bool
+nsSVGIntegrationUtils::IsMaskResourceReady(nsIFrame* aFrame)
+{
+ nsIFrame* firstFrame =
+ nsLayoutUtils::FirstContinuationOrIBSplitSibling(aFrame);
+ nsSVGEffects::EffectProperties effectProperties =
+ nsSVGEffects::GetEffectProperties(firstFrame);
+ nsTArray<nsSVGMaskFrame*> maskFrames = effectProperties.GetMaskFrames();
+ const nsStyleSVGReset* svgReset = firstFrame->StyleSVGReset();
+
+ for (uint32_t i = 0; i < maskFrames.Length(); i++) {
+ // Refers to a valid SVG mask.
+ if (maskFrames[i]) {
+ continue;
+ }
+
+ // Refers to an external resource, which is not ready yet.
+ if (!svgReset->mMask.mLayers[i].mImage.IsComplete()) {
+ return false;
+ }
+ }
+
+ // Either all mask resources are ready, or no mask resource is needed.
+ return true;
+}
+
+DrawResult
+nsSVGIntegrationUtils::PaintMask(const PaintFramesParams& aParams)
+{
+ nsSVGUtils::MaskUsage maskUsage;
+ nsSVGUtils::DetermineMaskUsage(aParams.frame, aParams.handleOpacity,
+ maskUsage);
+ MOZ_ASSERT(maskUsage.shouldGenerateMaskLayer);
+
+ nsIFrame* frame = aParams.frame;
+ if (!ValidateSVGFrame(frame)) {
+ return DrawResult::SUCCESS;
+ }
+
+ if (maskUsage.opacity == 0.0f) {
+ return DrawResult::SUCCESS;
+ }
+
+ gfxContext& ctx = aParams.ctx;
+
+ gfxContextMatrixAutoSaveRestore matSR(&ctx);
+
+ nsIFrame* firstFrame =
+ nsLayoutUtils::FirstContinuationOrIBSplitSibling(frame);
+ nsSVGEffects::EffectProperties effectProperties =
+ nsSVGEffects::GetEffectProperties(firstFrame);
+ nsTArray<nsSVGMaskFrame *> maskFrames = effectProperties.GetMaskFrames();
+ bool opacityApplied = !HasNonSVGMask(maskFrames);
+
+ nsPoint offsetToBoundingBox;
+ nsPoint offsetToUserSpace;
+ SetupContextMatrix(frame, aParams, offsetToBoundingBox,
+ offsetToUserSpace);
+
+ return PaintMaskSurface(aParams, ctx.GetDrawTarget(),
+ opacityApplied ? maskUsage.opacity : 1.0,
+ firstFrame->StyleContext(), maskFrames,
+ ctx.CurrentMatrix(), offsetToUserSpace);
+}
+
+DrawResult
+nsSVGIntegrationUtils::PaintMaskAndClipPath(const PaintFramesParams& aParams)
+{
+ MOZ_ASSERT(UsingMaskOrClipPathForFrame(aParams.frame),
+ "Should not use this method when no mask or clipPath effect"
+ "on this frame");
+
+ /* SVG defines the following rendering model:
+ *
+ * 1. Render geometry
+ * 2. Apply filter
+ * 3. Apply clipping, masking, group opacity
+ *
+ * We handle #3 here and perform a couple of optimizations:
+ *
+ * + Use cairo's clipPath when representable natively (single object
+ * clip region).
+ *
+ * + Merge opacity and masking if both used together.
+ */
+ nsIFrame* frame = aParams.frame;
+ DrawResult result = DrawResult::SUCCESS;
+ if (!ValidateSVGFrame(frame)) {
+ return result;
+ }
+
+ nsSVGUtils::MaskUsage maskUsage;
+ nsSVGUtils::DetermineMaskUsage(aParams.frame, aParams.handleOpacity,
+ maskUsage);
+
+ if (maskUsage.opacity == 0.0f) {
+ return DrawResult::SUCCESS;
+ }
+
+ gfxContext& context = aParams.ctx;
+ gfxContextMatrixAutoSaveRestore matrixAutoSaveRestore(&context);
+
+ /* Properties are added lazily and may have been removed by a restyle,
+ so make sure all applicable ones are set again. */
+ nsIFrame* firstFrame =
+ nsLayoutUtils::FirstContinuationOrIBSplitSibling(frame);
+ nsSVGEffects::EffectProperties effectProperties =
+ nsSVGEffects::GetEffectProperties(firstFrame);
+
+ bool isOK = effectProperties.HasNoFilterOrHasValidFilter();
+ nsSVGClipPathFrame *clipPathFrame = effectProperties.GetClipPathFrame(&isOK);
+
+ gfxMatrix cssPxToDevPxMatrix = GetCSSPxToDevPxMatrix(frame);
+ nsTArray<nsSVGMaskFrame*> maskFrames = effectProperties.GetMaskFrames();
+
+ nsPoint offsetToBoundingBox;
+ nsPoint offsetToUserSpace;
+
+ bool shouldGenerateMask = (maskUsage.opacity != 1.0f ||
+ maskUsage.shouldGenerateClipMaskLayer ||
+ maskUsage.shouldGenerateMaskLayer);
+
+ /* Check if we need to do additional operations on this child's
+ * rendering, which necessitates rendering into another surface. */
+ if (shouldGenerateMask) {
+ gfxContextMatrixAutoSaveRestore matSR;
+
+ Matrix maskTransform;
+ RefPtr<SourceSurface> maskSurface;
+ bool opacityApplied = false;
+
+ if (maskUsage.shouldGenerateMaskLayer) {
+ matSR.SetContext(&context);
+
+ // For css-mask, we want to generate a mask for each continuation frame,
+ // so we setup context matrix by the position of the current frame,
+ // instead of the first continuation frame.
+ SetupContextMatrix(frame, aParams, offsetToBoundingBox,
+ offsetToUserSpace);
+ result = CreateAndPaintMaskSurface(aParams, maskUsage.opacity,
+ firstFrame->StyleContext(),
+ maskFrames, offsetToUserSpace,
+ maskTransform, maskSurface,
+ opacityApplied);
+ if (!maskSurface) {
+ // Entire surface is clipped out.
+ return result;
+ }
+ }
+
+ if (maskUsage.shouldGenerateClipMaskLayer) {
+ matSR.Restore();
+ matSR.SetContext(&context);
+
+ SetupContextMatrix(firstFrame, aParams, offsetToBoundingBox,
+ offsetToUserSpace);
+ Matrix clippedMaskTransform;
+ RefPtr<SourceSurface> clipMaskSurface =
+ clipPathFrame->GetClipMask(context, frame, cssPxToDevPxMatrix,
+ &clippedMaskTransform, maskSurface,
+ maskTransform, &result);
+
+ if (clipMaskSurface) {
+ maskSurface = clipMaskSurface;
+ maskTransform = clippedMaskTransform;
+ } else {
+ // Either entire surface is clipped out, or gfx buffer allocation
+ // failure in nsSVGClipPathFrame::GetClipMask.
+ return result;
+ }
+ }
+
+ // opacity != 1.0f.
+ if (!maskUsage.shouldGenerateClipMaskLayer &&
+ !maskUsage.shouldGenerateMaskLayer) {
+ MOZ_ASSERT(maskUsage.opacity != 1.0f);
+
+ matSR.SetContext(&context);
+ SetupContextMatrix(firstFrame, aParams, offsetToBoundingBox,
+ offsetToUserSpace);
+ }
+
+ if (aParams.layerManager->GetRoot()->GetContentFlags() & Layer::CONTENT_COMPONENT_ALPHA) {
+ context.PushGroupAndCopyBackground(gfxContentType::COLOR_ALPHA,
+ opacityApplied
+ ? 1.0
+ : maskUsage.opacity,
+ maskSurface, maskTransform);
+ } else {
+ context.PushGroupForBlendBack(gfxContentType::COLOR_ALPHA,
+ opacityApplied ? 1.0 : maskUsage.opacity,
+ maskSurface, maskTransform);
+ }
+ }
+
+ /* If this frame has only a trivial clipPath, set up cairo's clipping now so
+ * we can just do normal painting and get it clipped appropriately.
+ */
+ if (maskUsage.shouldApplyClipPath || maskUsage.shouldApplyBasicShape) {
+ gfxContextMatrixAutoSaveRestore matSR(&context);
+
+ SetupContextMatrix(firstFrame, aParams, offsetToBoundingBox,
+ offsetToUserSpace);
+
+ MOZ_ASSERT(!maskUsage.shouldApplyClipPath ||
+ !maskUsage.shouldApplyBasicShape);
+ if (maskUsage.shouldApplyClipPath) {
+ clipPathFrame->ApplyClipPath(context, frame, cssPxToDevPxMatrix);
+ } else {
+ nsCSSClipPathInstance::ApplyBasicShapeClip(context, frame);
+ }
+ }
+
+ /* Paint the child */
+ context.SetMatrix(matrixAutoSaveRestore.Matrix());
+ BasicLayerManager* basic = aParams.layerManager->AsBasicLayerManager();
+ RefPtr<gfxContext> oldCtx = basic->GetTarget();
+ basic->SetTarget(&context);
+ aParams.layerManager->EndTransaction(FrameLayerBuilder::DrawPaintedLayer,
+ aParams.builder);
+ basic->SetTarget(oldCtx);
+
+ if (gfxPrefs::DrawMaskLayer()) {
+ gfxContextAutoSaveRestore saver(&context);
+
+ context.NewPath();
+ gfxRect drawingRect =
+ nsLayoutUtils::RectToGfxRect(aParams.borderArea,
+ frame->PresContext()->AppUnitsPerDevPixel());
+ context.Rectangle(drawingRect, true);
+ context.SetColor(Color(0.0, 1.0, 0.0, 1.0));
+ context.Fill();
+ }
+
+ if (maskUsage.shouldApplyClipPath || maskUsage.shouldApplyBasicShape) {
+ context.PopClip();
+ }
+
+ if (shouldGenerateMask) {
+ context.PopGroupAndBlend();
+ }
+
+ return result;
+}
+
+DrawResult
+nsSVGIntegrationUtils::PaintFilter(const PaintFramesParams& aParams)
+{
+ MOZ_ASSERT(!aParams.builder->IsForGenerateGlyphMask(),
+ "Filter effect is discarded while generating glyph mask.");
+ MOZ_ASSERT(aParams.frame->StyleEffects()->HasFilters(),
+ "Should not use this method when no filter effect on this frame");
+
+ nsIFrame* frame = aParams.frame;
+ if (!ValidateSVGFrame(frame)) {
+ return DrawResult::SUCCESS;
+ }
+
+ float opacity = nsSVGUtils::ComputeOpacity(frame, aParams.handleOpacity);
+ if (opacity == 0.0f) {
+ return DrawResult::SUCCESS;
+ }
+
+ /* Properties are added lazily and may have been removed by a restyle,
+ so make sure all applicable ones are set again. */
+ nsIFrame* firstFrame =
+ nsLayoutUtils::FirstContinuationOrIBSplitSibling(frame);
+ nsSVGEffects::EffectProperties effectProperties =
+ nsSVGEffects::GetEffectProperties(firstFrame);
+
+ if (!effectProperties.HasValidFilter()) {
+ return DrawResult::NOT_READY;
+ }
+
+ gfxContext& context = aParams.ctx;
+ nsPoint offsetToBoundingBox;
+ nsPoint offsetToUserSpace;
+
+ gfxContextAutoSaveRestore autoSR(&context);
+ SetupContextMatrix(firstFrame, aParams, offsetToBoundingBox,
+ offsetToUserSpace);
+
+ if (opacity != 1.0f) {
+ context.PushGroupForBlendBack(gfxContentType::COLOR_ALPHA, opacity,
+ nullptr, Matrix());
+ }
+
+ /* Paint the child and apply filters */
+ RegularFramePaintCallback callback(aParams.builder, aParams.layerManager,
+ offsetToUserSpace);
+ nsRegion dirtyRegion = aParams.dirtyRect - offsetToBoundingBox;
+ gfxMatrix tm = nsSVGIntegrationUtils::GetCSSPxToDevPxMatrix(frame);
+ nsFilterInstance::PaintFilteredFrame(frame, context.GetDrawTarget(),
+ tm, &callback, &dirtyRegion);
+
+ if (opacity != 1.0f) {
+ context.PopGroupAndBlend();
+ }
+
+ return DrawResult::SUCCESS;
+}
+
+gfxMatrix
+nsSVGIntegrationUtils::GetCSSPxToDevPxMatrix(nsIFrame* aNonSVGFrame)
+{
+ int32_t appUnitsPerDevPixel = aNonSVGFrame->PresContext()->AppUnitsPerDevPixel();
+ float devPxPerCSSPx =
+ 1 / nsPresContext::AppUnitsToFloatCSSPixels(appUnitsPerDevPixel);
+
+ return gfxMatrix(devPxPerCSSPx, 0.0,
+ 0.0, devPxPerCSSPx,
+ 0.0, 0.0);
+}
+
+class PaintFrameCallback : public gfxDrawingCallback {
+public:
+ PaintFrameCallback(nsIFrame* aFrame,
+ const nsSize aPaintServerSize,
+ const IntSize aRenderSize,
+ uint32_t aFlags)
+ : mFrame(aFrame)
+ , mPaintServerSize(aPaintServerSize)
+ , mRenderSize(aRenderSize)
+ , mFlags (aFlags)
+ {}
+ virtual bool operator()(gfxContext* aContext,
+ const gfxRect& aFillRect,
+ const SamplingFilter aSamplingFilter,
+ const gfxMatrix& aTransform) override;
+private:
+ nsIFrame* mFrame;
+ nsSize mPaintServerSize;
+ IntSize mRenderSize;
+ uint32_t mFlags;
+};
+
+bool
+PaintFrameCallback::operator()(gfxContext* aContext,
+ const gfxRect& aFillRect,
+ const SamplingFilter aSamplingFilter,
+ const gfxMatrix& aTransform)
+{
+ if (mFrame->GetStateBits() & NS_FRAME_DRAWING_AS_PAINTSERVER)
+ return false;
+
+ mFrame->AddStateBits(NS_FRAME_DRAWING_AS_PAINTSERVER);
+
+ aContext->Save();
+
+ // Clip to aFillRect so that we don't paint outside.
+ aContext->NewPath();
+ aContext->Rectangle(aFillRect);
+ aContext->Clip();
+
+ gfxMatrix invmatrix = aTransform;
+ if (!invmatrix.Invert()) {
+ return false;
+ }
+ aContext->Multiply(invmatrix);
+
+ // nsLayoutUtils::PaintFrame will anchor its painting at mFrame. But we want
+ // to have it anchored at the top left corner of the bounding box of all of
+ // mFrame's continuations. So we add a translation transform.
+ int32_t appUnitsPerDevPixel = mFrame->PresContext()->AppUnitsPerDevPixel();
+ nsPoint offset = nsSVGIntegrationUtils::GetOffsetToBoundingBox(mFrame);
+ gfxPoint devPxOffset = gfxPoint(offset.x, offset.y) / appUnitsPerDevPixel;
+ aContext->Multiply(gfxMatrix::Translation(devPxOffset));
+
+ gfxSize paintServerSize =
+ gfxSize(mPaintServerSize.width, mPaintServerSize.height) /
+ mFrame->PresContext()->AppUnitsPerDevPixel();
+
+ // nsLayoutUtils::PaintFrame wants to render with paintServerSize, but we
+ // want it to render with mRenderSize, so we need to set up a scale transform.
+ gfxFloat scaleX = mRenderSize.width / paintServerSize.width;
+ gfxFloat scaleY = mRenderSize.height / paintServerSize.height;
+ aContext->Multiply(gfxMatrix::Scaling(scaleX, scaleY));
+
+ // Draw.
+ nsRect dirty(-offset.x, -offset.y,
+ mPaintServerSize.width, mPaintServerSize.height);
+
+ using PaintFrameFlags = nsLayoutUtils::PaintFrameFlags;
+ PaintFrameFlags flags = PaintFrameFlags::PAINT_IN_TRANSFORM;
+ if (mFlags & nsSVGIntegrationUtils::FLAG_SYNC_DECODE_IMAGES) {
+ flags |= PaintFrameFlags::PAINT_SYNC_DECODE_IMAGES;
+ }
+ nsRenderingContext context(aContext);
+ nsLayoutUtils::PaintFrame(&context, mFrame,
+ dirty, NS_RGBA(0, 0, 0, 0),
+ nsDisplayListBuilderMode::PAINTING,
+ flags);
+
+ nsIFrame* currentFrame = mFrame;
+ while ((currentFrame = currentFrame->GetNextContinuation()) != nullptr) {
+ offset = currentFrame->GetOffsetToCrossDoc(mFrame);
+ devPxOffset = gfxPoint(offset.x, offset.y) / appUnitsPerDevPixel;
+
+ aContext->Save();
+ aContext->Multiply(gfxMatrix::Scaling(1/scaleX, 1/scaleY));
+ aContext->Multiply(gfxMatrix::Translation(devPxOffset));
+ aContext->Multiply(gfxMatrix::Scaling(scaleX, scaleY));
+
+ nsLayoutUtils::PaintFrame(&context, currentFrame,
+ dirty - offset, NS_RGBA(0, 0, 0, 0),
+ nsDisplayListBuilderMode::PAINTING,
+ flags);
+
+ aContext->Restore();
+ }
+
+ aContext->Restore();
+
+ mFrame->RemoveStateBits(NS_FRAME_DRAWING_AS_PAINTSERVER);
+
+ return true;
+}
+
+/* static */ already_AddRefed<gfxDrawable>
+nsSVGIntegrationUtils::DrawableFromPaintServer(nsIFrame* aFrame,
+ nsIFrame* aTarget,
+ const nsSize& aPaintServerSize,
+ const IntSize& aRenderSize,
+ const DrawTarget* aDrawTarget,
+ const gfxMatrix& aContextMatrix,
+ uint32_t aFlags)
+{
+ // aPaintServerSize is the size that would be filled when using
+ // background-repeat:no-repeat and background-size:auto. For normal background
+ // images, this would be the intrinsic size of the image; for gradients and
+ // patterns this would be the whole target frame fill area.
+ // aRenderSize is what we will be actually filling after accounting for
+ // background-size.
+ if (aFrame->IsFrameOfType(nsIFrame::eSVGPaintServer)) {
+ // aFrame is either a pattern or a gradient. These fill the whole target
+ // frame by default, so aPaintServerSize is the whole target background fill
+ // area.
+ nsSVGPaintServerFrame* server =
+ static_cast<nsSVGPaintServerFrame*>(aFrame);
+
+ gfxRect overrideBounds(0, 0,
+ aPaintServerSize.width, aPaintServerSize.height);
+ overrideBounds.ScaleInverse(aFrame->PresContext()->AppUnitsPerDevPixel());
+ RefPtr<gfxPattern> pattern =
+ server->GetPaintServerPattern(aTarget, aDrawTarget,
+ aContextMatrix, &nsStyleSVG::mFill, 1.0,
+ &overrideBounds);
+
+ if (!pattern)
+ return nullptr;
+
+ // pattern is now set up to fill aPaintServerSize. But we want it to
+ // fill aRenderSize, so we need to add a scaling transform.
+ // We couldn't just have set overrideBounds to aRenderSize - it would have
+ // worked for gradients, but for patterns it would result in a different
+ // pattern size.
+ gfxFloat scaleX = overrideBounds.Width() / aRenderSize.width;
+ gfxFloat scaleY = overrideBounds.Height() / aRenderSize.height;
+ gfxMatrix scaleMatrix = gfxMatrix::Scaling(scaleX, scaleY);
+ pattern->SetMatrix(scaleMatrix * pattern->GetMatrix());
+ RefPtr<gfxDrawable> drawable =
+ new gfxPatternDrawable(pattern, aRenderSize);
+ return drawable.forget();
+ }
+
+ if (aFrame->IsFrameOfType(nsIFrame::eSVG) &&
+ !static_cast<nsISVGChildFrame*>(do_QueryFrame(aFrame))) {
+ MOZ_ASSERT_UNREACHABLE("We should prevent painting of unpaintable SVG "
+ "before we get here");
+ return nullptr;
+ }
+
+ // We don't want to paint into a surface as long as we don't need to, so we
+ // set up a drawing callback.
+ RefPtr<gfxDrawingCallback> cb =
+ new PaintFrameCallback(aFrame, aPaintServerSize, aRenderSize, aFlags);
+ RefPtr<gfxDrawable> drawable = new gfxCallbackDrawable(cb, aRenderSize);
+ return drawable.forget();
+}
diff --git a/layout/svg/nsSVGIntegrationUtils.h b/layout/svg/nsSVGIntegrationUtils.h
new file mode 100644
index 0000000000..e3eb3c5f22
--- /dev/null
+++ b/layout/svg/nsSVGIntegrationUtils.h
@@ -0,0 +1,231 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef NSSVGINTEGRATIONUTILS_H_
+#define NSSVGINTEGRATIONUTILS_H_
+
+#include "gfxMatrix.h"
+#include "gfxRect.h"
+#include "nsRegionFwd.h"
+#include "mozilla/gfx/Rect.h"
+
+class gfxContext;
+class gfxDrawable;
+class nsDisplayList;
+class nsDisplayListBuilder;
+class nsIFrame;
+
+struct nsRect;
+
+namespace mozilla {
+namespace gfx {
+class DrawTarget;
+} // namespace gfx
+namespace layers {
+class LayerManager;
+} // namespace layers
+} // namespace mozilla
+
+struct nsPoint;
+struct nsSize;
+
+/**
+ * Integration of SVG effects (clipPath clipping, masking and filters) into
+ * regular display list based painting and hit-testing.
+ */
+class nsSVGIntegrationUtils final
+{
+ typedef mozilla::gfx::DrawTarget DrawTarget;
+ typedef mozilla::gfx::IntRect IntRect;
+ typedef mozilla::image::DrawResult DrawResult;
+
+public:
+ /**
+ * Returns true if SVG effects are currently applied to this frame.
+ */
+ static bool
+ UsingEffectsForFrame(const nsIFrame* aFrame);
+
+ /**
+ * Returns true if mask or clippath are currently applied to this frame.
+ */
+ static bool
+ UsingMaskOrClipPathForFrame(const nsIFrame* aFrame);
+
+ /**
+ * Returns the size of the union of the border-box rects of all of
+ * aNonSVGFrame's continuations.
+ */
+ static nsSize
+ GetContinuationUnionSize(nsIFrame* aNonSVGFrame);
+
+ /**
+ * When SVG effects need to resolve percentage, userSpaceOnUse lengths, they
+ * need a coordinate context to resolve them against. This method provides
+ * that coordinate context for non-SVG frames with SVG effects applied to
+ * them. The gfxSize returned is the size of the union of all of the given
+ * frame's continuations' border boxes, converted to SVG user units (equal to
+ * CSS px units), as required by the SVG code.
+ */
+ static mozilla::gfx::Size
+ GetSVGCoordContextForNonSVGFrame(nsIFrame* aNonSVGFrame);
+
+ /**
+ * SVG effects such as SVG filters, masking and clipPath may require an SVG
+ * "bbox" for the element they're being applied to in order to make decisions
+ * about positioning, and to resolve various lengths against. This method
+ * provides the "bbox" for non-SVG frames. The bbox returned is in CSS px
+ * units, and is the union of all aNonSVGFrame's continuations' overflow
+ * areas, relative to the top-left of the union of all aNonSVGFrame's
+ * continuations' border box rects.
+ */
+ static gfxRect
+ GetSVGBBoxForNonSVGFrame(nsIFrame* aNonSVGFrame);
+
+ /**
+ * Used to adjust a frame's pre-effects visual overflow rect to take account
+ * of SVG effects.
+ *
+ * XXX This method will not do the right thing for frames with continuations.
+ * It really needs all the continuations to have been reflowed before being
+ * called, but we currently call it on each continuation as its overflow
+ * rects are set during the reflow of each particular continuation. Gecko's
+ * current reflow architecture does not allow us to set the overflow rects
+ * for a whole chain of continuations for a given element at the point when
+ * the last continuation is reflowed. See:
+ * http://groups.google.com/group/mozilla.dev.tech.layout/msg/6b179066f3051f65
+ */
+ static nsRect
+ ComputePostEffectsVisualOverflowRect(nsIFrame* aFrame,
+ const nsRect& aPreEffectsOverflowRect);
+
+ /**
+ * Used to adjust the area of a frame that needs to be invalidated to take
+ * account of SVG effects.
+ *
+ * @param aFrame The effects frame.
+ * @param aToReferenceFrame The offset (in app units) from aFrame to its
+ * reference display item.
+ * @param aInvalidRegion The pre-effects invalid region in pixels relative to
+ * the reference display item.
+ * @return The post-effects invalid rect in pixels relative to the reference
+ * display item.
+ */
+ static nsIntRegion
+ AdjustInvalidAreaForSVGEffects(nsIFrame* aFrame, const nsPoint& aToReferenceFrame,
+ const nsIntRegion& aInvalidRegion);
+
+ /**
+ * Figure out which area of the source is needed given an area to
+ * repaint
+ */
+ static nsRect
+ GetRequiredSourceForInvalidArea(nsIFrame* aFrame, const nsRect& aDamageRect);
+
+ /**
+ * Returns true if the given point is not clipped out by effects.
+ * @param aPt in appunits relative to aFrame
+ */
+ static bool
+ HitTestFrameForEffects(nsIFrame* aFrame, const nsPoint& aPt);
+
+ struct PaintFramesParams {
+ gfxContext& ctx;
+ nsIFrame* frame;
+ const nsRect& dirtyRect;
+ const nsRect& borderArea;
+ nsDisplayListBuilder* builder;
+ mozilla::layers::LayerManager* layerManager;
+ bool handleOpacity; // If true, PaintMaskAndClipPath/ PaintFilter should
+ // apply css opacity.
+ IntRect maskRect;
+
+ explicit PaintFramesParams(gfxContext& aCtx, nsIFrame* aFrame,
+ const nsRect& aDirtyRect,
+ const nsRect& aBorderArea,
+ nsDisplayListBuilder* aBuilder,
+ mozilla::layers::LayerManager* aLayerManager,
+ bool aHandleOpacity)
+ : ctx(aCtx), frame(aFrame), dirtyRect(aDirtyRect),
+ borderArea(aBorderArea), builder(aBuilder),
+ layerManager(aLayerManager), handleOpacity(aHandleOpacity)
+ { }
+ };
+
+ /**
+ * Paint non-SVG frame with mask, clipPath and opacity effect.
+ */
+ static DrawResult
+ PaintMaskAndClipPath(const PaintFramesParams& aParams);
+
+ /**
+ * Paint mask of non-SVG frame onto a given context, aParams.ctx.
+ * aParams.ctx must contain an A8 surface.
+ */
+ static DrawResult
+ PaintMask(const PaintFramesParams& aParams);
+
+ /**
+ * Return true if all the mask resource of aFrame are ready.
+ */
+ static bool
+ IsMaskResourceReady(nsIFrame* aFrame);
+
+ /**
+ * Paint non-SVG frame with filter and opacity effect.
+ */
+ static DrawResult
+ PaintFilter(const PaintFramesParams& aParams);
+
+ /**
+ * SVG frames expect to paint in SVG user units, which are equal to CSS px
+ * units. This method provides a transform matrix to multiply onto a
+ * gfxContext's current transform to convert the context's current units from
+ * its usual dev pixels to SVG user units/CSS px to keep the SVG code happy.
+ */
+ static gfxMatrix
+ GetCSSPxToDevPxMatrix(nsIFrame* aNonSVGFrame);
+
+ /**
+ * @param aRenderingContext the target rendering context in which the paint
+ * server will be rendered
+ * @param aTarget the target frame onto which the paint server will be
+ * rendered
+ * @param aPaintServer a first-continuation frame to use as the source
+ * @param aFilter a filter to be applied when scaling
+ * @param aDest the area the paint server image should be mapped to
+ * @param aFill the area to be filled with copies of the paint server image
+ * @param aAnchor a point in aFill which we will ensure is pixel-aligned in
+ * the output
+ * @param aDirty pixels outside this area may be skipped
+ * @param aPaintServerSize the size that would be filled when using
+ * background-repeat:no-repeat and background-size:auto. For normal background
+ * images, this would be the intrinsic size of the image; for gradients and
+ * patterns this would be the whole target frame fill area.
+ * @param aFlags pass FLAG_SYNC_DECODE_IMAGES and any images in the paint
+ * server will be decoding synchronously if they are not decoded already.
+ */
+ enum {
+ FLAG_SYNC_DECODE_IMAGES = 0x01,
+ };
+
+ static already_AddRefed<gfxDrawable>
+ DrawableFromPaintServer(nsIFrame* aFrame,
+ nsIFrame* aTarget,
+ const nsSize& aPaintServerSize,
+ const mozilla::gfx::IntSize& aRenderSize,
+ const DrawTarget* aDrawTarget,
+ const gfxMatrix& aContextMatrix,
+ uint32_t aFlags);
+
+ /**
+ * For non-SVG frames, this gives the offset to the frame's "user space".
+ * For SVG frames, this returns a zero offset.
+ */
+ static nsPoint
+ GetOffsetToBoundingBox(nsIFrame* aFrame);
+};
+
+#endif /*NSSVGINTEGRATIONUTILS_H_*/
diff --git a/layout/svg/nsSVGMarkerFrame.cpp b/layout/svg/nsSVGMarkerFrame.cpp
new file mode 100644
index 0000000000..93b76b17fd
--- /dev/null
+++ b/layout/svg/nsSVGMarkerFrame.cpp
@@ -0,0 +1,278 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+// Main header first:
+#include "nsSVGMarkerFrame.h"
+
+// Keep others in (case-insensitive) order:
+#include "gfxContext.h"
+#include "nsSVGEffects.h"
+#include "mozilla/dom/SVGMarkerElement.h"
+#include "nsSVGPathGeometryElement.h"
+#include "nsSVGPathGeometryFrame.h"
+
+using namespace mozilla::dom;
+using namespace mozilla::gfx;
+
+nsContainerFrame*
+NS_NewSVGMarkerFrame(nsIPresShell* aPresShell, nsStyleContext* aContext)
+{
+ return new (aPresShell) nsSVGMarkerFrame(aContext);
+}
+
+NS_IMPL_FRAMEARENA_HELPERS(nsSVGMarkerFrame)
+
+//----------------------------------------------------------------------
+// nsIFrame methods:
+
+nsresult
+nsSVGMarkerFrame::AttributeChanged(int32_t aNameSpaceID,
+ nsIAtom* aAttribute,
+ int32_t aModType)
+{
+ if (aNameSpaceID == kNameSpaceID_None &&
+ (aAttribute == nsGkAtoms::markerUnits ||
+ aAttribute == nsGkAtoms::refX ||
+ aAttribute == nsGkAtoms::refY ||
+ aAttribute == nsGkAtoms::markerWidth ||
+ aAttribute == nsGkAtoms::markerHeight ||
+ aAttribute == nsGkAtoms::orient ||
+ aAttribute == nsGkAtoms::preserveAspectRatio ||
+ aAttribute == nsGkAtoms::viewBox)) {
+ nsSVGEffects::InvalidateDirectRenderingObservers(this);
+ }
+
+ return nsSVGContainerFrame::AttributeChanged(aNameSpaceID,
+ aAttribute, aModType);
+}
+
+#ifdef DEBUG
+void
+nsSVGMarkerFrame::Init(nsIContent* aContent,
+ nsContainerFrame* aParent,
+ nsIFrame* aPrevInFlow)
+{
+ NS_ASSERTION(aContent->IsSVGElement(nsGkAtoms::marker), "Content is not an SVG marker");
+
+ nsSVGContainerFrame::Init(aContent, aParent, aPrevInFlow);
+}
+#endif /* DEBUG */
+
+nsIAtom *
+nsSVGMarkerFrame::GetType() const
+{
+ return nsGkAtoms::svgMarkerFrame;
+}
+
+//----------------------------------------------------------------------
+// nsSVGContainerFrame methods:
+
+gfxMatrix
+nsSVGMarkerFrame::GetCanvasTM()
+{
+ NS_ASSERTION(mMarkedFrame, "null nsSVGPathGeometry frame");
+
+ if (mInUse2) {
+ // We're going to be bailing drawing the marker, so return an identity.
+ return gfxMatrix();
+ }
+
+ SVGMarkerElement *content = static_cast<SVGMarkerElement*>(mContent);
+
+ mInUse2 = true;
+ gfxMatrix markedTM = mMarkedFrame->GetCanvasTM();
+ mInUse2 = false;
+
+ Matrix markerTM = content->GetMarkerTransform(mStrokeWidth, mX, mY,
+ mAutoAngle, mIsStart);
+ Matrix viewBoxTM = content->GetViewBoxTransform();
+
+ return ThebesMatrix(viewBoxTM * markerTM) * markedTM;
+}
+
+static nsIFrame*
+GetAnonymousChildFrame(nsIFrame* aFrame)
+{
+ nsIFrame* kid = aFrame->PrincipalChildList().FirstChild();
+ MOZ_ASSERT(kid && kid->GetType() == nsGkAtoms::svgMarkerAnonChildFrame,
+ "expected to find anonymous child of marker frame");
+ return kid;
+}
+
+nsresult
+nsSVGMarkerFrame::PaintMark(gfxContext& aContext,
+ const gfxMatrix& aToMarkedFrameUserSpace,
+ nsSVGPathGeometryFrame *aMarkedFrame,
+ nsSVGMark *aMark, float aStrokeWidth)
+{
+ // If the flag is set when we get here, it means this marker frame
+ // has already been used painting the current mark, and the document
+ // has a marker reference loop.
+ if (mInUse)
+ return NS_OK;
+
+ AutoMarkerReferencer markerRef(this, aMarkedFrame);
+
+ SVGMarkerElement *marker = static_cast<SVGMarkerElement*>(mContent);
+ if (!marker->HasValidDimensions()) {
+ return NS_OK;
+ }
+
+ const nsSVGViewBoxRect viewBox = marker->GetViewBoxRect();
+
+ if (viewBox.width <= 0.0f || viewBox.height <= 0.0f) {
+ // We must disable rendering if the viewBox width or height are zero.
+ return NS_OK;
+ }
+
+ mStrokeWidth = aStrokeWidth;
+ mX = aMark->x;
+ mY = aMark->y;
+ mAutoAngle = aMark->angle;
+ mIsStart = aMark->type == nsSVGMark::eStart;
+
+ Matrix viewBoxTM = marker->GetViewBoxTransform();
+
+ Matrix markerTM = marker->GetMarkerTransform(mStrokeWidth, mX, mY,
+ mAutoAngle, mIsStart);
+
+ gfxMatrix markTM = ThebesMatrix(viewBoxTM) * ThebesMatrix(markerTM) *
+ aToMarkedFrameUserSpace;
+
+ if (StyleDisplay()->IsScrollableOverflow()) {
+ aContext.Save();
+ gfxRect clipRect =
+ nsSVGUtils::GetClipRectForFrame(this, viewBox.x, viewBox.y,
+ viewBox.width, viewBox.height);
+ nsSVGUtils::SetClipRect(&aContext, markTM, clipRect);
+ }
+
+
+ nsIFrame* kid = GetAnonymousChildFrame(this);
+ nsISVGChildFrame* SVGFrame = do_QueryFrame(kid);
+ // The CTM of each frame referencing us may be different.
+ SVGFrame->NotifySVGChanged(nsISVGChildFrame::TRANSFORM_CHANGED);
+ DrawResult result = nsSVGUtils::PaintFrameWithEffects(kid, aContext, markTM);
+
+ if (StyleDisplay()->IsScrollableOverflow())
+ aContext.Restore();
+
+ return (result == DrawResult::SUCCESS) ? NS_OK : NS_ERROR_FAILURE;
+}
+
+SVGBBox
+nsSVGMarkerFrame::GetMarkBBoxContribution(const Matrix &aToBBoxUserspace,
+ uint32_t aFlags,
+ nsSVGPathGeometryFrame *aMarkedFrame,
+ const nsSVGMark *aMark,
+ float aStrokeWidth)
+{
+ SVGBBox bbox;
+
+ // If the flag is set when we get here, it means this marker frame
+ // has already been used in calculating the current mark bbox, and
+ // the document has a marker reference loop.
+ if (mInUse)
+ return bbox;
+
+ AutoMarkerReferencer markerRef(this, aMarkedFrame);
+
+ SVGMarkerElement *content = static_cast<SVGMarkerElement*>(mContent);
+ if (!content->HasValidDimensions()) {
+ return bbox;
+ }
+
+ const nsSVGViewBoxRect viewBox = content->GetViewBoxRect();
+
+ if (viewBox.width <= 0.0f || viewBox.height <= 0.0f) {
+ return bbox;
+ }
+
+ mStrokeWidth = aStrokeWidth;
+ mX = aMark->x;
+ mY = aMark->y;
+ mAutoAngle = aMark->angle;
+ mIsStart = aMark->type == nsSVGMark::eStart;
+
+ Matrix markerTM =
+ content->GetMarkerTransform(mStrokeWidth, mX, mY, mAutoAngle, mIsStart);
+ Matrix viewBoxTM = content->GetViewBoxTransform();
+
+ Matrix tm = viewBoxTM * markerTM * aToBBoxUserspace;
+
+ nsISVGChildFrame* child = do_QueryFrame(GetAnonymousChildFrame(this));
+ // When we're being called to obtain the invalidation area, we need to
+ // pass down all the flags so that stroke is included. However, once DOM
+ // getBBox() accepts flags, maybe we should strip some of those here?
+
+ // We need to include zero width/height vertical/horizontal lines, so we have
+ // to use UnionEdges.
+ bbox.UnionEdges(child->GetBBoxContribution(tm, aFlags));
+
+ return bbox;
+}
+
+void
+nsSVGMarkerFrame::SetParentCoordCtxProvider(SVGSVGElement *aContext)
+{
+ SVGMarkerElement *marker = static_cast<SVGMarkerElement*>(mContent);
+ marker->SetParentCoordCtxProvider(aContext);
+}
+
+//----------------------------------------------------------------------
+// helper class
+
+nsSVGMarkerFrame::AutoMarkerReferencer::AutoMarkerReferencer(
+ nsSVGMarkerFrame *aFrame,
+ nsSVGPathGeometryFrame *aMarkedFrame
+ MOZ_GUARD_OBJECT_NOTIFIER_PARAM_IN_IMPL)
+ : mFrame(aFrame)
+{
+ MOZ_GUARD_OBJECT_NOTIFIER_INIT;
+ mFrame->mInUse = true;
+ mFrame->mMarkedFrame = aMarkedFrame;
+
+ SVGSVGElement *ctx =
+ static_cast<nsSVGElement*>(aMarkedFrame->GetContent())->GetCtx();
+ mFrame->SetParentCoordCtxProvider(ctx);
+}
+
+nsSVGMarkerFrame::AutoMarkerReferencer::~AutoMarkerReferencer()
+{
+ mFrame->SetParentCoordCtxProvider(nullptr);
+
+ mFrame->mMarkedFrame = nullptr;
+ mFrame->mInUse = false;
+}
+
+//----------------------------------------------------------------------
+// Implementation of nsSVGMarkerAnonChildFrame
+
+nsContainerFrame*
+NS_NewSVGMarkerAnonChildFrame(nsIPresShell* aPresShell,
+ nsStyleContext* aContext)
+{
+ return new (aPresShell) nsSVGMarkerAnonChildFrame(aContext);
+}
+
+NS_IMPL_FRAMEARENA_HELPERS(nsSVGMarkerAnonChildFrame)
+
+#ifdef DEBUG
+void
+nsSVGMarkerAnonChildFrame::Init(nsIContent* aContent,
+ nsContainerFrame* aParent,
+ nsIFrame* aPrevInFlow)
+{
+ MOZ_ASSERT(aParent->GetType() == nsGkAtoms::svgMarkerFrame,
+ "Unexpected parent");
+ nsSVGDisplayContainerFrame::Init(aContent, aParent, aPrevInFlow);
+}
+#endif
+
+nsIAtom *
+nsSVGMarkerAnonChildFrame::GetType() const
+{
+ return nsGkAtoms::svgMarkerAnonChildFrame;
+}
diff --git a/layout/svg/nsSVGMarkerFrame.h b/layout/svg/nsSVGMarkerFrame.h
new file mode 100644
index 0000000000..22ac017090
--- /dev/null
+++ b/layout/svg/nsSVGMarkerFrame.h
@@ -0,0 +1,174 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef __NS_SVGMARKERFRAME_H__
+#define __NS_SVGMARKERFRAME_H__
+
+#include "mozilla/Attributes.h"
+#include "gfxMatrix.h"
+#include "gfxRect.h"
+#include "nsFrame.h"
+#include "nsLiteralString.h"
+#include "nsQueryFrame.h"
+#include "nsSVGContainerFrame.h"
+#include "nsSVGUtils.h"
+
+class gfxContext;
+class nsSVGPathGeometryFrame;
+
+namespace mozilla {
+namespace dom {
+class SVGSVGElement;
+} // namespace dom
+} // namespace mozilla
+
+struct nsSVGMark;
+
+class nsSVGMarkerFrame : public nsSVGContainerFrame
+{
+ friend class nsSVGMarkerAnonChildFrame;
+ friend nsContainerFrame*
+ NS_NewSVGMarkerFrame(nsIPresShell* aPresShell, nsStyleContext* aContext);
+protected:
+ explicit nsSVGMarkerFrame(nsStyleContext* aContext)
+ : nsSVGContainerFrame(aContext)
+ , mMarkedFrame(nullptr)
+ , mInUse(false)
+ , mInUse2(false)
+ {
+ AddStateBits(NS_FRAME_IS_NONDISPLAY);
+ }
+
+public:
+ NS_DECL_FRAMEARENA_HELPERS
+
+ // nsIFrame interface:
+#ifdef DEBUG
+ virtual void Init(nsIContent* aContent,
+ nsContainerFrame* aParent,
+ nsIFrame* aPrevInFlow) override;
+#endif
+
+ virtual void BuildDisplayList(nsDisplayListBuilder* aBuilder,
+ const nsRect& aDirtyRect,
+ const nsDisplayListSet& aLists) override {}
+
+ virtual nsresult AttributeChanged(int32_t aNameSpaceID,
+ nsIAtom* aAttribute,
+ int32_t aModType) override;
+ /**
+ * Get the "type" of the frame
+ *
+ * @see nsGkAtoms::svgMarkerFrame
+ */
+ virtual nsIAtom* GetType() const override;
+
+#ifdef DEBUG_FRAME_DUMP
+ virtual nsresult GetFrameName(nsAString& aResult) const override
+ {
+ return MakeFrameName(NS_LITERAL_STRING("SVGMarker"), aResult);
+ }
+#endif
+
+ virtual nsContainerFrame* GetContentInsertionFrame() override {
+ // Any children must be added to our single anonymous inner frame kid.
+ MOZ_ASSERT(PrincipalChildList().FirstChild() &&
+ PrincipalChildList().FirstChild()->GetType() ==
+ nsGkAtoms::svgMarkerAnonChildFrame,
+ "Where is our anonymous child?");
+ return PrincipalChildList().FirstChild()->GetContentInsertionFrame();
+ }
+
+ // nsSVGMarkerFrame methods:
+ nsresult PaintMark(gfxContext& aContext,
+ const gfxMatrix& aToMarkedFrameUserSpace,
+ nsSVGPathGeometryFrame *aMarkedFrame,
+ nsSVGMark *aMark,
+ float aStrokeWidth);
+
+ SVGBBox GetMarkBBoxContribution(const Matrix &aToBBoxUserspace,
+ uint32_t aFlags,
+ nsSVGPathGeometryFrame *aMarkedFrame,
+ const nsSVGMark *aMark,
+ float aStrokeWidth);
+
+private:
+ // stuff needed for callback
+ nsSVGPathGeometryFrame *mMarkedFrame;
+ float mStrokeWidth, mX, mY, mAutoAngle;
+ bool mIsStart; // whether the callback is for a marker-start marker
+
+ // nsSVGContainerFrame methods:
+ virtual gfxMatrix GetCanvasTM() override;
+
+ // A helper class to allow us to paint markers safely. The helper
+ // automatically sets and clears the mInUse flag on the marker frame (to
+ // prevent nasty reference loops) as well as the reference to the marked
+ // frame and its coordinate context. It's easy to mess this up
+ // and break things, so this helper makes the code far more robust.
+ class MOZ_RAII AutoMarkerReferencer
+ {
+ public:
+ AutoMarkerReferencer(nsSVGMarkerFrame *aFrame,
+ nsSVGPathGeometryFrame *aMarkedFrame
+ MOZ_GUARD_OBJECT_NOTIFIER_PARAM);
+ ~AutoMarkerReferencer();
+ private:
+ nsSVGMarkerFrame *mFrame;
+ MOZ_DECL_USE_GUARD_OBJECT_NOTIFIER
+ };
+
+ // nsSVGMarkerFrame methods:
+ void SetParentCoordCtxProvider(mozilla::dom::SVGSVGElement *aContext);
+
+ // recursion prevention flag
+ bool mInUse;
+
+ // second recursion prevention flag, for GetCanvasTM()
+ bool mInUse2;
+};
+
+////////////////////////////////////////////////////////////////////////
+// nsMarkerAnonChildFrame class
+
+class nsSVGMarkerAnonChildFrame : public nsSVGDisplayContainerFrame
+{
+ friend nsContainerFrame*
+ NS_NewSVGMarkerAnonChildFrame(nsIPresShell* aPresShell,
+ nsStyleContext* aContext);
+
+ explicit nsSVGMarkerAnonChildFrame(nsStyleContext* aContext)
+ : nsSVGDisplayContainerFrame(aContext)
+ {}
+
+public:
+ NS_DECL_FRAMEARENA_HELPERS
+
+#ifdef DEBUG
+ virtual void Init(nsIContent* aContent,
+ nsContainerFrame* aParent,
+ nsIFrame* aPrevInFlow) override;
+#endif
+
+#ifdef DEBUG_FRAME_DUMP
+ virtual nsresult GetFrameName(nsAString& aResult) const override {
+ return MakeFrameName(NS_LITERAL_STRING("SVGMarkerAnonChild"), aResult);
+ }
+#endif
+
+ /**
+ * Get the "type" of the frame
+ *
+ * @see nsGkAtoms::svgMarkerAnonChildFrame
+ */
+ virtual nsIAtom* GetType() const override;
+
+ // nsSVGContainerFrame methods:
+ virtual gfxMatrix GetCanvasTM() override
+ {
+ return static_cast<nsSVGMarkerFrame*>(GetParent())->GetCanvasTM();
+ }
+};
+#endif
diff --git a/layout/svg/nsSVGMaskFrame.cpp b/layout/svg/nsSVGMaskFrame.cpp
new file mode 100644
index 0000000000..b8e4b32ae9
--- /dev/null
+++ b/layout/svg/nsSVGMaskFrame.cpp
@@ -0,0 +1,403 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+// Main header first:
+#include "nsSVGMaskFrame.h"
+
+// Keep others in (case-insensitive) order:
+#include "gfx2DGlue.h"
+#include "gfxContext.h"
+#include "mozilla/gfx/2D.h"
+#include "mozilla/RefPtr.h"
+#include "nsSVGEffects.h"
+#include "mozilla/dom/SVGMaskElement.h"
+#ifdef BUILD_ARM_NEON
+#include "mozilla/arm.h"
+#include "nsSVGMaskFrameNEON.h"
+#endif
+
+using namespace mozilla;
+using namespace mozilla::dom;
+using namespace mozilla::gfx;
+
+// c = n / 255
+// c <= 0.04045 ? c / 12.92 : pow((c + 0.055) / 1.055, 2.4)) * 255 + 0.5
+static const uint8_t gsRGBToLinearRGBMap[256] = {
+ 0, 0, 0, 0, 0, 0, 0, 1,
+ 1, 1, 1, 1, 1, 1, 1, 1,
+ 1, 1, 2, 2, 2, 2, 2, 2,
+ 2, 2, 3, 3, 3, 3, 3, 3,
+ 4, 4, 4, 4, 4, 5, 5, 5,
+ 5, 6, 6, 6, 6, 7, 7, 7,
+ 8, 8, 8, 8, 9, 9, 9, 10,
+ 10, 10, 11, 11, 12, 12, 12, 13,
+ 13, 13, 14, 14, 15, 15, 16, 16,
+ 17, 17, 17, 18, 18, 19, 19, 20,
+ 20, 21, 22, 22, 23, 23, 24, 24,
+ 25, 25, 26, 27, 27, 28, 29, 29,
+ 30, 30, 31, 32, 32, 33, 34, 35,
+ 35, 36, 37, 37, 38, 39, 40, 41,
+ 41, 42, 43, 44, 45, 45, 46, 47,
+ 48, 49, 50, 51, 51, 52, 53, 54,
+ 55, 56, 57, 58, 59, 60, 61, 62,
+ 63, 64, 65, 66, 67, 68, 69, 70,
+ 71, 72, 73, 74, 76, 77, 78, 79,
+ 80, 81, 82, 84, 85, 86, 87, 88,
+ 90, 91, 92, 93, 95, 96, 97, 99,
+100, 101, 103, 104, 105, 107, 108, 109,
+111, 112, 114, 115, 116, 118, 119, 121,
+122, 124, 125, 127, 128, 130, 131, 133,
+134, 136, 138, 139, 141, 142, 144, 146,
+147, 149, 151, 152, 154, 156, 157, 159,
+161, 163, 164, 166, 168, 170, 171, 173,
+175, 177, 179, 181, 183, 184, 186, 188,
+190, 192, 194, 196, 198, 200, 202, 204,
+206, 208, 210, 212, 214, 216, 218, 220,
+222, 224, 226, 229, 231, 233, 235, 237,
+239, 242, 244, 246, 248, 250, 253, 255
+};
+
+static void
+ComputesRGBLuminanceMask(const uint8_t *aSourceData,
+ int32_t aSourceStride,
+ uint8_t *aDestData,
+ int32_t aDestStride,
+ const IntSize &aSize,
+ float aOpacity)
+{
+#ifdef BUILD_ARM_NEON
+ if (mozilla::supports_neon()) {
+ ComputesRGBLuminanceMask_NEON(aSourceData, aSourceStride,
+ aDestData, aDestStride,
+ aSize, aOpacity);
+ return;
+ }
+#endif
+
+ int32_t redFactor = 55 * aOpacity; // 255 * 0.2125 * opacity
+ int32_t greenFactor = 183 * aOpacity; // 255 * 0.7154 * opacity
+ int32_t blueFactor = 18 * aOpacity; // 255 * 0.0721
+ int32_t sourceOffset = aSourceStride - 4 * aSize.width;
+ const uint8_t *sourcePixel = aSourceData;
+ int32_t destOffset = aDestStride - aSize.width;
+ uint8_t *destPixel = aDestData;
+
+ for (int32_t y = 0; y < aSize.height; y++) {
+ for (int32_t x = 0; x < aSize.width; x++) {
+ uint8_t a = sourcePixel[GFX_ARGB32_OFFSET_A];
+
+ if (a) {
+ *destPixel = (redFactor * sourcePixel[GFX_ARGB32_OFFSET_R] +
+ greenFactor * sourcePixel[GFX_ARGB32_OFFSET_G] +
+ blueFactor * sourcePixel[GFX_ARGB32_OFFSET_B]) >> 8;
+ } else {
+ *destPixel = 0;
+ }
+ sourcePixel += 4;
+ destPixel++;
+ }
+ sourcePixel += sourceOffset;
+ destPixel += destOffset;
+ }
+}
+
+static void
+ComputeLinearRGBLuminanceMask(const uint8_t *aSourceData,
+ int32_t aSourceStride,
+ uint8_t *aDestData,
+ int32_t aDestStride,
+ const IntSize &aSize,
+ float aOpacity)
+{
+ int32_t redFactor = 55 * aOpacity; // 255 * 0.2125 * opacity
+ int32_t greenFactor = 183 * aOpacity; // 255 * 0.7154 * opacity
+ int32_t blueFactor = 18 * aOpacity; // 255 * 0.0721
+ int32_t sourceOffset = aSourceStride - 4 * aSize.width;
+ const uint8_t *sourcePixel = aSourceData;
+ int32_t destOffset = aDestStride - aSize.width;
+ uint8_t *destPixel = aDestData;
+
+ for (int32_t y = 0; y < aSize.height; y++) {
+ for (int32_t x = 0; x < aSize.width; x++) {
+ uint8_t a = sourcePixel[GFX_ARGB32_OFFSET_A];
+
+ // unpremultiply
+ if (a) {
+ if (a == 255) {
+ /* sRGB -> linearRGB -> intensity */
+ *destPixel =
+ static_cast<uint8_t>
+ ((gsRGBToLinearRGBMap[sourcePixel[GFX_ARGB32_OFFSET_R]] *
+ redFactor +
+ gsRGBToLinearRGBMap[sourcePixel[GFX_ARGB32_OFFSET_G]] *
+ greenFactor +
+ gsRGBToLinearRGBMap[sourcePixel[GFX_ARGB32_OFFSET_B]] *
+ blueFactor) >> 8);
+ } else {
+ uint8_t tempPixel[4];
+ tempPixel[GFX_ARGB32_OFFSET_B] =
+ (255 * sourcePixel[GFX_ARGB32_OFFSET_B]) / a;
+ tempPixel[GFX_ARGB32_OFFSET_G] =
+ (255 * sourcePixel[GFX_ARGB32_OFFSET_G]) / a;
+ tempPixel[GFX_ARGB32_OFFSET_R] =
+ (255 * sourcePixel[GFX_ARGB32_OFFSET_R]) / a;
+
+ /* sRGB -> linearRGB -> intensity */
+ *destPixel =
+ static_cast<uint8_t>
+ (((gsRGBToLinearRGBMap[tempPixel[GFX_ARGB32_OFFSET_R]] *
+ redFactor +
+ gsRGBToLinearRGBMap[tempPixel[GFX_ARGB32_OFFSET_G]] *
+ greenFactor +
+ gsRGBToLinearRGBMap[tempPixel[GFX_ARGB32_OFFSET_B]] *
+ blueFactor) >> 8) * (a / 255.0f));
+ }
+ } else {
+ *destPixel = 0;
+ }
+ sourcePixel += 4;
+ destPixel++;
+ }
+ sourcePixel += sourceOffset;
+ destPixel += destOffset;
+ }
+}
+
+static void
+ComputeAlphaMask(const uint8_t *aSourceData,
+ int32_t aSourceStride,
+ uint8_t *aDestData,
+ int32_t aDestStride,
+ const IntSize &aSize,
+ float aOpacity)
+{
+ int32_t sourceOffset = aSourceStride - 4 * aSize.width;
+ const uint8_t *sourcePixel = aSourceData;
+ int32_t destOffset = aDestStride - aSize.width;
+ uint8_t *destPixel = aDestData;
+
+ for (int32_t y = 0; y < aSize.height; y++) {
+ for (int32_t x = 0; x < aSize.width; x++) {
+ *destPixel = sourcePixel[GFX_ARGB32_OFFSET_A] * aOpacity;
+ sourcePixel += 4;
+ destPixel++;
+ }
+ sourcePixel += sourceOffset;
+ destPixel += destOffset;
+ }
+}
+
+//----------------------------------------------------------------------
+// Implementation
+
+nsIFrame*
+NS_NewSVGMaskFrame(nsIPresShell* aPresShell, nsStyleContext* aContext)
+{
+ return new (aPresShell) nsSVGMaskFrame(aContext);
+}
+
+NS_IMPL_FRAMEARENA_HELPERS(nsSVGMaskFrame)
+
+already_AddRefed<SourceSurface>
+nsSVGMaskFrame::GetMaskForMaskedFrame(gfxContext* aContext,
+ nsIFrame* aMaskedFrame,
+ const gfxMatrix &aMatrix,
+ float aOpacity,
+ Matrix* aMaskTransform,
+ uint8_t aMaskOp)
+{
+ // If the flag is set when we get here, it means this mask frame
+ // has already been used painting the current mask, and the document
+ // has a mask reference loop.
+ if (mInUse) {
+ NS_WARNING("Mask loop detected!");
+ return nullptr;
+ }
+ AutoMaskReferencer maskRef(this);
+
+ gfxRect maskArea = GetMaskArea(aMaskedFrame);
+
+ // Get the clip extents in device space:
+ // Minimizing the mask surface extents (using both the current clip extents
+ // and maskArea) is important for performance.
+ aContext->Save();
+ nsSVGUtils::SetClipRect(aContext, aMatrix, maskArea);
+ aContext->SetMatrix(gfxMatrix());
+ gfxRect maskSurfaceRect = aContext->GetClipExtents();
+ maskSurfaceRect.RoundOut();
+ aContext->Restore();
+
+ bool resultOverflows;
+ IntSize maskSurfaceSize =
+ nsSVGUtils::ConvertToSurfaceSize(maskSurfaceRect.Size(), &resultOverflows);
+
+ if (resultOverflows || maskSurfaceSize.IsEmpty()) {
+ // XXXjwatt we should return an empty surface so we don't paint aMaskedFrame!
+ return nullptr;
+ }
+
+ RefPtr<DrawTarget> maskDT =
+ Factory::CreateDrawTarget(BackendType::CAIRO, maskSurfaceSize,
+ SurfaceFormat::B8G8R8A8);
+ if (!maskDT || !maskDT->IsValid()) {
+ return nullptr;
+ }
+
+ gfxMatrix maskSurfaceMatrix =
+ aContext->CurrentMatrix() * gfxMatrix::Translation(-maskSurfaceRect.TopLeft());
+
+ RefPtr<gfxContext> tmpCtx = gfxContext::CreateOrNull(maskDT);
+ MOZ_ASSERT(tmpCtx); // already checked the draw target above
+ tmpCtx->SetMatrix(maskSurfaceMatrix);
+
+ mMatrixForChildren = GetMaskTransform(aMaskedFrame) * aMatrix;
+ for (nsIFrame* kid = mFrames.FirstChild(); kid;
+ kid = kid->GetNextSibling()) {
+ // The CTM of each frame referencing us can be different
+ nsISVGChildFrame* SVGFrame = do_QueryFrame(kid);
+ if (SVGFrame) {
+ SVGFrame->NotifySVGChanged(nsISVGChildFrame::TRANSFORM_CHANGED);
+ }
+ gfxMatrix m = mMatrixForChildren;
+ if (kid->GetContent()->IsSVGElement()) {
+ m = static_cast<nsSVGElement*>(kid->GetContent())->
+ PrependLocalTransformsTo(m, eUserSpaceToParent);
+ }
+ Unused << nsSVGUtils::PaintFrameWithEffects(kid, *tmpCtx, m);
+ }
+
+ RefPtr<SourceSurface> maskSnapshot = maskDT->Snapshot();
+ if (!maskSnapshot) {
+ return nullptr;
+ }
+ RefPtr<DataSourceSurface> maskSurface = maskSnapshot->GetDataSurface();
+ DataSourceSurface::MappedSurface map;
+ if (!maskSurface->Map(DataSourceSurface::MapType::READ, &map)) {
+ return nullptr;
+ }
+
+ // Create alpha channel mask for output
+ RefPtr<DataSourceSurface> destMaskSurface =
+ Factory::CreateDataSourceSurface(maskSurfaceSize, SurfaceFormat::A8);
+ if (!destMaskSurface) {
+ return nullptr;
+ }
+ DataSourceSurface::MappedSurface destMap;
+ if (!destMaskSurface->Map(DataSourceSurface::MapType::WRITE, &destMap)) {
+ return nullptr;
+ }
+
+ uint8_t maskType;
+ if (aMaskOp == NS_STYLE_MASK_MODE_MATCH_SOURCE) {
+ maskType = StyleSVGReset()->mMaskType;
+ } else {
+ maskType = aMaskOp == NS_STYLE_MASK_MODE_LUMINANCE ?
+ NS_STYLE_MASK_TYPE_LUMINANCE : NS_STYLE_MASK_TYPE_ALPHA;
+ }
+
+ if (maskType == NS_STYLE_MASK_TYPE_LUMINANCE) {
+ if (StyleSVG()->mColorInterpolation ==
+ NS_STYLE_COLOR_INTERPOLATION_LINEARRGB) {
+ ComputeLinearRGBLuminanceMask(map.mData, map.mStride,
+ destMap.mData, destMap.mStride,
+ maskSurfaceSize, aOpacity);
+ } else {
+ ComputesRGBLuminanceMask(map.mData, map.mStride,
+ destMap.mData, destMap.mStride,
+ maskSurfaceSize, aOpacity);
+ }
+ } else {
+ ComputeAlphaMask(map.mData, map.mStride,
+ destMap.mData, destMap.mStride,
+ maskSurfaceSize, aOpacity);
+ }
+
+ maskSurface->Unmap();
+ destMaskSurface->Unmap();
+
+ // Moz2D transforms in the opposite direction to Thebes
+ if (!maskSurfaceMatrix.Invert()) {
+ return nullptr;
+ }
+
+ *aMaskTransform = ToMatrix(maskSurfaceMatrix);
+ return destMaskSurface.forget();
+}
+
+gfxRect
+nsSVGMaskFrame::GetMaskArea(nsIFrame* aMaskedFrame)
+{
+ SVGMaskElement *maskElem = static_cast<SVGMaskElement*>(mContent);
+
+ uint16_t units =
+ maskElem->mEnumAttributes[SVGMaskElement::MASKUNITS].GetAnimValue();
+ gfxRect bbox;
+ if (units == SVG_UNIT_TYPE_OBJECTBOUNDINGBOX) {
+ bbox = nsSVGUtils::GetBBox(aMaskedFrame);
+ }
+
+ // Bounds in the user space of aMaskedFrame
+ gfxRect maskArea = nsSVGUtils::GetRelativeRect(units,
+ &maskElem->mLengthAttributes[SVGMaskElement::ATTR_X],
+ bbox, aMaskedFrame);
+
+ return maskArea;
+}
+
+nsresult
+nsSVGMaskFrame::AttributeChanged(int32_t aNameSpaceID,
+ nsIAtom* aAttribute,
+ int32_t aModType)
+{
+ if (aNameSpaceID == kNameSpaceID_None &&
+ (aAttribute == nsGkAtoms::x ||
+ aAttribute == nsGkAtoms::y ||
+ aAttribute == nsGkAtoms::width ||
+ aAttribute == nsGkAtoms::height||
+ aAttribute == nsGkAtoms::maskUnits ||
+ aAttribute == nsGkAtoms::maskContentUnits)) {
+ nsSVGEffects::InvalidateDirectRenderingObservers(this);
+ }
+
+ return nsSVGContainerFrame::AttributeChanged(aNameSpaceID,
+ aAttribute, aModType);
+}
+
+#ifdef DEBUG
+void
+nsSVGMaskFrame::Init(nsIContent* aContent,
+ nsContainerFrame* aParent,
+ nsIFrame* aPrevInFlow)
+{
+ NS_ASSERTION(aContent->IsSVGElement(nsGkAtoms::mask),
+ "Content is not an SVG mask");
+
+ nsSVGContainerFrame::Init(aContent, aParent, aPrevInFlow);
+}
+#endif /* DEBUG */
+
+nsIAtom *
+nsSVGMaskFrame::GetType() const
+{
+ return nsGkAtoms::svgMaskFrame;
+}
+
+gfxMatrix
+nsSVGMaskFrame::GetCanvasTM()
+{
+ return mMatrixForChildren;
+}
+
+gfxMatrix
+nsSVGMaskFrame::GetMaskTransform(nsIFrame* aMaskedFrame)
+{
+ SVGMaskElement *content = static_cast<SVGMaskElement*>(mContent);
+
+ nsSVGEnum* maskContentUnits =
+ &content->mEnumAttributes[SVGMaskElement::MASKCONTENTUNITS];
+
+ return nsSVGUtils::AdjustMatrixForUnits(gfxMatrix(), maskContentUnits,
+ aMaskedFrame);
+}
diff --git a/layout/svg/nsSVGMaskFrame.h b/layout/svg/nsSVGMaskFrame.h
new file mode 100644
index 0000000000..380306a481
--- /dev/null
+++ b/layout/svg/nsSVGMaskFrame.h
@@ -0,0 +1,131 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef __NS_SVGMASKFRAME_H__
+#define __NS_SVGMASKFRAME_H__
+
+#include "mozilla/Attributes.h"
+#include "mozilla/gfx/2D.h"
+#include "mozilla/RefPtr.h"
+#include "gfxPattern.h"
+#include "gfxMatrix.h"
+#include "nsSVGContainerFrame.h"
+#include "nsSVGUtils.h"
+
+class gfxContext;
+
+/**
+ * Byte offsets of channels in a native packed gfxColor or cairo image surface.
+ */
+#ifdef IS_BIG_ENDIAN
+#define GFX_ARGB32_OFFSET_A 0
+#define GFX_ARGB32_OFFSET_R 1
+#define GFX_ARGB32_OFFSET_G 2
+#define GFX_ARGB32_OFFSET_B 3
+#else
+#define GFX_ARGB32_OFFSET_A 3
+#define GFX_ARGB32_OFFSET_R 2
+#define GFX_ARGB32_OFFSET_G 1
+#define GFX_ARGB32_OFFSET_B 0
+#endif
+
+class nsSVGMaskFrame final : public nsSVGContainerFrame
+{
+ friend nsIFrame*
+ NS_NewSVGMaskFrame(nsIPresShell* aPresShell, nsStyleContext* aContext);
+
+ typedef mozilla::gfx::Matrix Matrix;
+ typedef mozilla::gfx::SourceSurface SourceSurface;
+
+protected:
+ explicit nsSVGMaskFrame(nsStyleContext* aContext)
+ : nsSVGContainerFrame(aContext)
+ , mInUse(false)
+ {
+ AddStateBits(NS_FRAME_IS_NONDISPLAY);
+ }
+
+public:
+ NS_DECL_FRAMEARENA_HELPERS
+
+ // nsSVGMaskFrame method:
+ already_AddRefed<SourceSurface>
+ GetMaskForMaskedFrame(gfxContext* aContext,
+ nsIFrame* aMaskedFrame,
+ const gfxMatrix &aMatrix,
+ float aOpacity,
+ Matrix* aMaskTransform,
+ uint8_t aMaskOp = NS_STYLE_MASK_MODE_MATCH_SOURCE);
+
+ gfxRect
+ GetMaskArea(nsIFrame* aMaskedFrame);
+
+ virtual nsresult AttributeChanged(int32_t aNameSpaceID,
+ nsIAtom* aAttribute,
+ int32_t aModType) override;
+
+#ifdef DEBUG
+ virtual void Init(nsIContent* aContent,
+ nsContainerFrame* aParent,
+ nsIFrame* aPrevInFlow) override;
+#endif
+
+ virtual void BuildDisplayList(nsDisplayListBuilder* aBuilder,
+ const nsRect& aDirtyRect,
+ const nsDisplayListSet& aLists) override {}
+
+ /**
+ * Get the "type" of the frame
+ *
+ * @see nsGkAtoms::svgMaskFrame
+ */
+ virtual nsIAtom* GetType() const override;
+
+#ifdef DEBUG_FRAME_DUMP
+ virtual nsresult GetFrameName(nsAString& aResult) const override
+ {
+ return MakeFrameName(NS_LITERAL_STRING("SVGMask"), aResult);
+ }
+#endif
+
+private:
+ /**
+ * If the mask element transforms its children due to
+ * maskContentUnits="objectBoundingBox" being set on it, this function
+ * returns the resulting transform.
+ */
+ gfxMatrix GetMaskTransform(nsIFrame* aMaskedFrame);
+
+ // A helper class to allow us to paint masks safely. The helper
+ // automatically sets and clears the mInUse flag on the mask frame
+ // (to prevent nasty reference loops). It's easy to mess this up
+ // and break things, so this helper makes the code far more robust.
+ class MOZ_RAII AutoMaskReferencer
+ {
+ public:
+ explicit AutoMaskReferencer(nsSVGMaskFrame *aFrame
+ MOZ_GUARD_OBJECT_NOTIFIER_PARAM)
+ : mFrame(aFrame) {
+ MOZ_GUARD_OBJECT_NOTIFIER_INIT;
+ NS_ASSERTION(!mFrame->mInUse, "reference loop!");
+ mFrame->mInUse = true;
+ }
+ ~AutoMaskReferencer() {
+ mFrame->mInUse = false;
+ }
+ private:
+ nsSVGMaskFrame *mFrame;
+ MOZ_DECL_USE_GUARD_OBJECT_NOTIFIER
+ };
+
+ gfxMatrix mMatrixForChildren;
+ // recursion prevention flag
+ bool mInUse;
+
+ // nsSVGContainerFrame methods:
+ virtual gfxMatrix GetCanvasTM() override;
+};
+
+#endif
diff --git a/layout/svg/nsSVGMaskFrameNEON.cpp b/layout/svg/nsSVGMaskFrameNEON.cpp
new file mode 100644
index 0000000000..f690b8164d
--- /dev/null
+++ b/layout/svg/nsSVGMaskFrameNEON.cpp
@@ -0,0 +1,73 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsSVGMaskFrameNEON.h"
+#include "nsSVGMaskFrame.h"
+#include <arm_neon.h>
+
+using namespace mozilla::gfx;
+
+void
+ComputesRGBLuminanceMask_NEON(const uint8_t *aSourceData,
+ int32_t aSourceStride,
+ uint8_t *aDestData,
+ int32_t aDestStride,
+ const IntSize &aSize,
+ float aOpacity)
+{
+ int32_t redFactor = 55 * aOpacity; // 255 * 0.2125 * opacity
+ int32_t greenFactor = 183 * aOpacity; // 255 * 0.7154 * opacity
+ int32_t blueFactor = 18 * aOpacity; // 255 * 0.0721
+ const uint8_t *sourcePixel = aSourceData;
+ int32_t sourceOffset = aSourceStride - 4 * aSize.width;
+ uint8_t *destPixel = aDestData;
+ int32_t destOffset = aDestStride - aSize.width;
+
+ sourcePixel = aSourceData;
+ int32_t remainderWidth = aSize.width % 8;
+ int32_t roundedWidth = aSize.width - remainderWidth;
+ uint16x8_t temp;
+ uint8x8_t gray;
+ uint8x8_t redVector = vdup_n_u8(redFactor);
+ uint8x8_t greenVector = vdup_n_u8(greenFactor);
+ uint8x8_t blueVector = vdup_n_u8(blueFactor);
+ uint8x8_t fullBitVector = vdup_n_u8(255);
+ uint8x8_t oneVector = vdup_n_u8(1);
+ for (int32_t y = 0; y < aSize.height; y++) {
+ // Calculate luminance by neon with 8 pixels per loop
+ for (int32_t x = 0; x < roundedWidth; x += 8) {
+ uint8x8x4_t argb = vld4_u8(sourcePixel);
+ temp = vmull_u8(argb.val[GFX_ARGB32_OFFSET_R], redVector); // temp = red * redFactor
+ temp = vmlal_u8(temp, argb.val[GFX_ARGB32_OFFSET_G], greenVector); // temp += green * greenFactor
+ temp = vmlal_u8(temp, argb.val[GFX_ARGB32_OFFSET_B], blueVector); // temp += blue * blueFactor
+ gray = vshrn_n_u16(temp, 8); // gray = temp >> 8
+
+ // Check alpha value
+ uint8x8_t alphaVector = vtst_u8(argb.val[GFX_ARGB32_OFFSET_A], fullBitVector);
+ gray = vmul_u8(gray, vand_u8(alphaVector, oneVector));
+
+ // Put the result to the 8 pixels
+ vst1_u8(destPixel, gray);
+ sourcePixel += 8 * 4;
+ destPixel += 8;
+ }
+
+ // Calculate the rest pixels of the line by cpu
+ for (int32_t x = 0; x < remainderWidth; x++) {
+ if (sourcePixel[GFX_ARGB32_OFFSET_A] > 0) {
+ *destPixel = (redFactor * sourcePixel[GFX_ARGB32_OFFSET_R]+
+ greenFactor * sourcePixel[GFX_ARGB32_OFFSET_G] +
+ blueFactor * sourcePixel[GFX_ARGB32_OFFSET_B]) >> 8;
+ } else {
+ *destPixel = 0;
+ }
+ sourcePixel += 4;
+ destPixel++;
+ }
+ sourcePixel += sourceOffset;
+ destPixel += destOffset;
+ }
+}
+
diff --git a/layout/svg/nsSVGMaskFrameNEON.h b/layout/svg/nsSVGMaskFrameNEON.h
new file mode 100644
index 0000000000..1da901ba7f
--- /dev/null
+++ b/layout/svg/nsSVGMaskFrameNEON.h
@@ -0,0 +1,19 @@
+/* -*- mode: c++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* this source code form is subject to the terms of the mozilla public
+ * license, v. 2.0. if a copy of the mpl was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef __NS_SVGMASKFRAMENEON_H__
+#define __NS_SVGMASKFRAMENEON_H__
+
+#include "mozilla/gfx/Point.h"
+
+void
+ComputesRGBLuminanceMask_NEON(const uint8_t *aSourceData,
+ int32_t aSourceStride,
+ uint8_t *aDestData,
+ int32_t aDestStride,
+ const mozilla::gfx::IntSize &aSize,
+ float aOpacity);
+
+#endif /* __NS_SVGMASKFRAMENEON_H__ */
diff --git a/layout/svg/nsSVGOuterSVGFrame.cpp b/layout/svg/nsSVGOuterSVGFrame.cpp
new file mode 100644
index 0000000000..683f10bc7b
--- /dev/null
+++ b/layout/svg/nsSVGOuterSVGFrame.cpp
@@ -0,0 +1,999 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+// Main header first:
+#include "nsSVGOuterSVGFrame.h"
+
+// Keep others in (case-insensitive) order:
+#include "nsDisplayList.h"
+#include "nsIDocument.h"
+#include "nsIDOMHTMLIFrameElement.h"
+#include "nsIInterfaceRequestorUtils.h"
+#include "nsIObjectLoadingContent.h"
+#include "nsRenderingContext.h"
+#include "nsSVGIntegrationUtils.h"
+#include "nsSVGForeignObjectFrame.h"
+#include "mozilla/dom/SVGSVGElement.h"
+#include "mozilla/dom/SVGViewElement.h"
+#include "nsSubDocumentFrame.h"
+
+using namespace mozilla;
+using namespace mozilla::dom;
+
+//----------------------------------------------------------------------
+// Implementation helpers
+
+void
+nsSVGOuterSVGFrame::RegisterForeignObject(nsSVGForeignObjectFrame* aFrame)
+{
+ NS_ASSERTION(aFrame, "Who on earth is calling us?!");
+
+ if (!mForeignObjectHash) {
+ mForeignObjectHash = new nsTHashtable<nsPtrHashKey<nsSVGForeignObjectFrame> >();
+ }
+
+ NS_ASSERTION(!mForeignObjectHash->GetEntry(aFrame),
+ "nsSVGForeignObjectFrame already registered!");
+
+ mForeignObjectHash->PutEntry(aFrame);
+
+ NS_ASSERTION(mForeignObjectHash->GetEntry(aFrame),
+ "Failed to register nsSVGForeignObjectFrame!");
+}
+
+void
+nsSVGOuterSVGFrame::UnregisterForeignObject(nsSVGForeignObjectFrame* aFrame)
+{
+ NS_ASSERTION(aFrame, "Who on earth is calling us?!");
+ NS_ASSERTION(mForeignObjectHash && mForeignObjectHash->GetEntry(aFrame),
+ "nsSVGForeignObjectFrame not in registry!");
+ return mForeignObjectHash->RemoveEntry(aFrame);
+}
+
+//----------------------------------------------------------------------
+// Implementation
+
+nsContainerFrame*
+NS_NewSVGOuterSVGFrame(nsIPresShell* aPresShell, nsStyleContext* aContext)
+{
+ return new (aPresShell) nsSVGOuterSVGFrame(aContext);
+}
+
+NS_IMPL_FRAMEARENA_HELPERS(nsSVGOuterSVGFrame)
+
+nsSVGOuterSVGFrame::nsSVGOuterSVGFrame(nsStyleContext* aContext)
+ : nsSVGDisplayContainerFrame(aContext)
+ , mFullZoom(aContext->PresContext()->GetFullZoom())
+ , mViewportInitialized(false)
+ , mIsRootContent(false)
+{
+ // Outer-<svg> has CSS layout, so remove this bit:
+ RemoveStateBits(NS_FRAME_SVG_LAYOUT);
+}
+
+// helper
+static inline bool
+DependsOnIntrinsicSize(const nsIFrame* aEmbeddingFrame)
+{
+ const nsStylePosition *pos = aEmbeddingFrame->StylePosition();
+ const nsStyleCoord &width = pos->mWidth;
+ const nsStyleCoord &height = pos->mHeight;
+
+ // XXX it would be nice to know if the size of aEmbeddingFrame's containing
+ // block depends on aEmbeddingFrame, then we'd know if we can return false
+ // for eStyleUnit_Percent too.
+ return !width.ConvertsToLength() ||
+ !height.ConvertsToLength();
+}
+
+void
+nsSVGOuterSVGFrame::Init(nsIContent* aContent,
+ nsContainerFrame* aParent,
+ nsIFrame* aPrevInFlow)
+{
+ NS_ASSERTION(aContent->IsSVGElement(nsGkAtoms::svg),
+ "Content is not an SVG 'svg' element!");
+
+ AddStateBits(NS_STATE_IS_OUTER_SVG |
+ NS_FRAME_FONT_INFLATION_CONTAINER |
+ NS_FRAME_FONT_INFLATION_FLOW_ROOT);
+
+ // Check for conditional processing attributes here rather than in
+ // nsCSSFrameConstructor::FindSVGData because we want to avoid
+ // simply giving failing outer <svg> elements an nsSVGContainerFrame.
+ // We don't create other SVG frames if PassesConditionalProcessingTests
+ // returns false, but since we do create nsSVGOuterSVGFrame frames we
+ // prevent them from painting by [ab]use NS_FRAME_IS_NONDISPLAY. The
+ // frame will be recreated via an nsChangeHint_ReconstructFrame restyle if
+ // the value returned by PassesConditionalProcessingTests changes.
+ SVGSVGElement *svg = static_cast<SVGSVGElement*>(aContent);
+ if (!svg->PassesConditionalProcessingTests()) {
+ AddStateBits(NS_FRAME_IS_NONDISPLAY);
+ }
+
+ nsSVGDisplayContainerFrame::Init(aContent, aParent, aPrevInFlow);
+
+ nsIDocument* doc = mContent->GetUncomposedDoc();
+ if (doc) {
+ // we only care about our content's zoom and pan values if it's the root element
+ if (doc->GetRootElement() == mContent) {
+ mIsRootContent = true;
+
+ nsIFrame* embeddingFrame;
+ if (IsRootOfReplacedElementSubDoc(&embeddingFrame) && embeddingFrame) {
+ if (MOZ_UNLIKELY(!embeddingFrame->HasAllStateBits(NS_FRAME_IS_DIRTY)) &&
+ DependsOnIntrinsicSize(embeddingFrame)) {
+ // Looks like this document is loading after the embedding element
+ // has had its first reflow, and that its size depends on our
+ // intrinsic size. We need it to resize itself to use our (now
+ // available) intrinsic size:
+ embeddingFrame->PresContext()->PresShell()->
+ FrameNeedsReflow(embeddingFrame, nsIPresShell::eStyleChange, NS_FRAME_IS_DIRTY);
+ }
+ }
+ }
+ }
+}
+
+//----------------------------------------------------------------------
+// nsQueryFrame methods
+
+NS_QUERYFRAME_HEAD(nsSVGOuterSVGFrame)
+ NS_QUERYFRAME_ENTRY(nsISVGSVGFrame)
+NS_QUERYFRAME_TAIL_INHERITING(nsSVGDisplayContainerFrame)
+
+//----------------------------------------------------------------------
+// nsIFrame methods
+//----------------------------------------------------------------------
+// reflowing
+
+/* virtual */ nscoord
+nsSVGOuterSVGFrame::GetMinISize(nsRenderingContext *aRenderingContext)
+{
+ nscoord result;
+ DISPLAY_MIN_WIDTH(this, result);
+
+ result = nscoord(0);
+
+ return result;
+}
+
+/* virtual */ nscoord
+nsSVGOuterSVGFrame::GetPrefISize(nsRenderingContext *aRenderingContext)
+{
+ nscoord result;
+ DISPLAY_PREF_WIDTH(this, result);
+
+ SVGSVGElement *svg = static_cast<SVGSVGElement*>(mContent);
+ WritingMode wm = GetWritingMode();
+ const nsSVGLength2& isize = wm.IsVertical()
+ ? svg->mLengthAttributes[SVGSVGElement::ATTR_HEIGHT]
+ : svg->mLengthAttributes[SVGSVGElement::ATTR_WIDTH];
+
+ if (isize.IsPercentage()) {
+ // It looks like our containing block's isize may depend on our isize. In
+ // that case our behavior is undefined according to CSS 2.1 section 10.3.2.
+ // As a last resort, we'll fall back to returning zero.
+ result = nscoord(0);
+
+ // Returning zero may be unhelpful, however, as it leads to unexpected
+ // disappearance of %-sized SVGs in orthogonal contexts, where our
+ // containing block wants to shrink-wrap. So let's look for an ancestor
+ // with non-zero size in this dimension, and use that as a (somewhat
+ // arbitrary) result instead.
+ nsIFrame *parent = GetParent();
+ while (parent) {
+ nscoord parentISize = parent->GetLogicalSize(wm).ISize(wm);
+ if (parentISize > 0 && parentISize != NS_UNCONSTRAINEDSIZE) {
+ result = parentISize;
+ break;
+ }
+ parent = parent->GetParent();
+ }
+ } else {
+ result = nsPresContext::CSSPixelsToAppUnits(isize.GetAnimValue(svg));
+ if (result < 0) {
+ result = nscoord(0);
+ }
+ }
+
+ return result;
+}
+
+/* virtual */ IntrinsicSize
+nsSVGOuterSVGFrame::GetIntrinsicSize()
+{
+ // XXXjwatt Note that here we want to return the CSS width/height if they're
+ // specified and we're embedded inside an nsIObjectLoadingContent.
+
+ IntrinsicSize intrinsicSize;
+
+ SVGSVGElement *content = static_cast<SVGSVGElement*>(mContent);
+ nsSVGLength2 &width = content->mLengthAttributes[SVGSVGElement::ATTR_WIDTH];
+ nsSVGLength2 &height = content->mLengthAttributes[SVGSVGElement::ATTR_HEIGHT];
+
+ if (!width.IsPercentage()) {
+ nscoord val = nsPresContext::CSSPixelsToAppUnits(width.GetAnimValue(content));
+ if (val < 0) val = 0;
+ intrinsicSize.width.SetCoordValue(val);
+ }
+
+ if (!height.IsPercentage()) {
+ nscoord val = nsPresContext::CSSPixelsToAppUnits(height.GetAnimValue(content));
+ if (val < 0) val = 0;
+ intrinsicSize.height.SetCoordValue(val);
+ }
+
+ return intrinsicSize;
+}
+
+/* virtual */ nsSize
+nsSVGOuterSVGFrame::GetIntrinsicRatio()
+{
+ // We only have an intrinsic size/ratio if our width and height attributes
+ // are both specified and set to non-percentage values, or we have a viewBox
+ // rect: http://www.w3.org/TR/SVGMobile12/coords.html#IntrinsicSizing
+
+ SVGSVGElement *content = static_cast<SVGSVGElement*>(mContent);
+ nsSVGLength2 &width = content->mLengthAttributes[SVGSVGElement::ATTR_WIDTH];
+ nsSVGLength2 &height = content->mLengthAttributes[SVGSVGElement::ATTR_HEIGHT];
+
+ if (!width.IsPercentage() && !height.IsPercentage()) {
+ nsSize ratio(NSToCoordRoundWithClamp(width.GetAnimValue(content)),
+ NSToCoordRoundWithClamp(height.GetAnimValue(content)));
+ if (ratio.width < 0) {
+ ratio.width = 0;
+ }
+ if (ratio.height < 0) {
+ ratio.height = 0;
+ }
+ return ratio;
+ }
+
+ SVGViewElement* viewElement = content->GetCurrentViewElement();
+ const nsSVGViewBoxRect* viewbox = nullptr;
+
+ // The logic here should match HasViewBox().
+ if (viewElement && viewElement->mViewBox.HasRect()) {
+ viewbox = &viewElement->mViewBox.GetAnimValue();
+ } else if (content->mViewBox.HasRect()) {
+ viewbox = &content->mViewBox.GetAnimValue();
+ }
+
+ if (viewbox) {
+ float viewBoxWidth = viewbox->width;
+ float viewBoxHeight = viewbox->height;
+
+ if (viewBoxWidth < 0.0f) {
+ viewBoxWidth = 0.0f;
+ }
+ if (viewBoxHeight < 0.0f) {
+ viewBoxHeight = 0.0f;
+ }
+ return nsSize(NSToCoordRoundWithClamp(viewBoxWidth),
+ NSToCoordRoundWithClamp(viewBoxHeight));
+ }
+
+ return nsSVGDisplayContainerFrame::GetIntrinsicRatio();
+}
+
+/* virtual */
+LogicalSize
+nsSVGOuterSVGFrame::ComputeSize(nsRenderingContext *aRenderingContext,
+ WritingMode aWM,
+ const LogicalSize& aCBSize,
+ nscoord aAvailableISize,
+ const LogicalSize& aMargin,
+ const LogicalSize& aBorder,
+ const LogicalSize& aPadding,
+ ComputeSizeFlags aFlags)
+{
+ if (IsRootOfImage() || IsRootOfReplacedElementSubDoc()) {
+ // The embedding element has sized itself using the CSS replaced element
+ // sizing rules, using our intrinsic dimensions as necessary. The SVG spec
+ // says that the width and height of embedded SVG is overridden by the
+ // width and height of the embedding element, so we just need to size to
+ // the viewport that the embedding element has established for us.
+ return aCBSize;
+ }
+
+ LogicalSize cbSize = aCBSize;
+ IntrinsicSize intrinsicSize = GetIntrinsicSize();
+
+ if (!mContent->GetParent()) {
+ // We're the root of the outermost browsing context, so we need to scale
+ // cbSize by the full-zoom so that SVGs with percentage width/height zoom:
+
+ NS_ASSERTION(aCBSize.ISize(aWM) != NS_AUTOHEIGHT &&
+ aCBSize.BSize(aWM) != NS_AUTOHEIGHT,
+ "root should not have auto-width/height containing block");
+ cbSize.ISize(aWM) *= PresContext()->GetFullZoom();
+ cbSize.BSize(aWM) *= PresContext()->GetFullZoom();
+
+ // We also need to honour the width and height attributes' default values
+ // of 100% when we're the root of a browsing context. (GetIntrinsicSize()
+ // doesn't report these since there's no such thing as a percentage
+ // intrinsic size. Also note that explicit percentage values are mapped
+ // into style, so the following isn't for them.)
+
+ SVGSVGElement* content = static_cast<SVGSVGElement*>(mContent);
+
+ nsSVGLength2 &width =
+ content->mLengthAttributes[SVGSVGElement::ATTR_WIDTH];
+ if (width.IsPercentage()) {
+ MOZ_ASSERT(intrinsicSize.width.GetUnit() == eStyleUnit_None,
+ "GetIntrinsicSize should have reported no intrinsic width");
+ float val = width.GetAnimValInSpecifiedUnits() / 100.0f;
+ if (val < 0.0f) val = 0.0f;
+ intrinsicSize.width.SetCoordValue(val * cbSize.Width(aWM));
+ }
+
+ nsSVGLength2 &height =
+ content->mLengthAttributes[SVGSVGElement::ATTR_HEIGHT];
+ NS_ASSERTION(aCBSize.BSize(aWM) != NS_AUTOHEIGHT,
+ "root should not have auto-height containing block");
+ if (height.IsPercentage()) {
+ MOZ_ASSERT(intrinsicSize.height.GetUnit() == eStyleUnit_None,
+ "GetIntrinsicSize should have reported no intrinsic height");
+ float val = height.GetAnimValInSpecifiedUnits() / 100.0f;
+ if (val < 0.0f) val = 0.0f;
+ intrinsicSize.height.SetCoordValue(val * cbSize.Height(aWM));
+ }
+ MOZ_ASSERT(intrinsicSize.height.GetUnit() == eStyleUnit_Coord &&
+ intrinsicSize.width.GetUnit() == eStyleUnit_Coord,
+ "We should have just handled the only situation where"
+ "we lack an intrinsic height or width.");
+ }
+
+ return ComputeSizeWithIntrinsicDimensions(aRenderingContext, aWM,
+ intrinsicSize, GetIntrinsicRatio(),
+ cbSize, aMargin, aBorder, aPadding,
+ aFlags);
+}
+
+void
+nsSVGOuterSVGFrame::Reflow(nsPresContext* aPresContext,
+ ReflowOutput& aDesiredSize,
+ const ReflowInput& aReflowInput,
+ nsReflowStatus& aStatus)
+{
+ MarkInReflow();
+ DO_GLOBAL_REFLOW_COUNT("nsSVGOuterSVGFrame");
+ DISPLAY_REFLOW(aPresContext, this, aReflowInput, aDesiredSize, aStatus);
+ NS_FRAME_TRACE(NS_FRAME_TRACE_CALLS,
+ ("enter nsSVGOuterSVGFrame::Reflow: availSize=%d,%d",
+ aReflowInput.AvailableWidth(), aReflowInput.AvailableHeight()));
+
+ NS_PRECONDITION(mState & NS_FRAME_IN_REFLOW, "frame is not in reflow");
+
+ aStatus = NS_FRAME_COMPLETE;
+
+ aDesiredSize.Width() = aReflowInput.ComputedWidth() +
+ aReflowInput.ComputedPhysicalBorderPadding().LeftRight();
+ aDesiredSize.Height() = aReflowInput.ComputedHeight() +
+ aReflowInput.ComputedPhysicalBorderPadding().TopBottom();
+
+ NS_ASSERTION(!GetPrevInFlow(), "SVG can't currently be broken across pages.");
+
+ SVGSVGElement *svgElem = static_cast<SVGSVGElement*>(mContent);
+
+ nsSVGOuterSVGAnonChildFrame *anonKid =
+ static_cast<nsSVGOuterSVGAnonChildFrame*>(PrincipalChildList().FirstChild());
+
+ if (mState & NS_FRAME_FIRST_REFLOW) {
+ // Initialize
+ svgElem->UpdateHasChildrenOnlyTransform();
+ }
+
+ // If our SVG viewport has changed, update our content and notify.
+ // http://www.w3.org/TR/SVG11/coords.html#ViewportSpace
+
+ svgFloatSize newViewportSize(
+ nsPresContext::AppUnitsToFloatCSSPixels(aReflowInput.ComputedWidth()),
+ nsPresContext::AppUnitsToFloatCSSPixels(aReflowInput.ComputedHeight()));
+
+ svgFloatSize oldViewportSize = svgElem->GetViewportSize();
+
+ uint32_t changeBits = 0;
+ if (newViewportSize != oldViewportSize) {
+ // When our viewport size changes, we may need to update the overflow rects
+ // of our child frames. This is the case if:
+ //
+ // * We have a real/synthetic viewBox (a children-only transform), since
+ // the viewBox transform will change as the viewport dimensions change.
+ //
+ // * We do not have a real/synthetic viewBox, but the last time we
+ // reflowed (or the last time UpdateOverflow() was called) we did.
+ //
+ // We only handle the former case here, in which case we mark all our child
+ // frames as dirty so that we reflow them below and update their overflow
+ // rects.
+ //
+ // In the latter case, updating of overflow rects is handled for removal of
+ // real viewBox (the viewBox attribute) in AttributeChanged. Synthetic
+ // viewBox "removal" (e.g. a document references the same SVG via both an
+ // <svg:image> and then as a CSS background image (a synthetic viewBox is
+ // used when painting the former, but not when painting the latter)) is
+ // handled in SVGSVGElement::FlushImageTransformInvalidation.
+ //
+ if (svgElem->HasViewBoxOrSyntheticViewBox()) {
+ nsIFrame* anonChild = PrincipalChildList().FirstChild();
+ anonChild->AddStateBits(NS_FRAME_IS_DIRTY);
+ for (nsIFrame* child : anonChild->PrincipalChildList()) {
+ child->AddStateBits(NS_FRAME_IS_DIRTY);
+ }
+ }
+ changeBits |= COORD_CONTEXT_CHANGED;
+ svgElem->SetViewportSize(newViewportSize);
+ }
+ if (mFullZoom != PresContext()->GetFullZoom()) {
+ changeBits |= FULL_ZOOM_CHANGED;
+ mFullZoom = PresContext()->GetFullZoom();
+ }
+ if (changeBits) {
+ NotifyViewportOrTransformChanged(changeBits);
+ }
+ mViewportInitialized = true;
+
+ // Now that we've marked the necessary children as dirty, call
+ // ReflowSVG() or ReflowSVGNonDisplayText() on them, depending
+ // on whether we are non-display.
+ mCallingReflowSVG = true;
+ if (GetStateBits() & NS_FRAME_IS_NONDISPLAY) {
+ ReflowSVGNonDisplayText(this);
+ } else {
+ // Update the mRects and visual overflow rects of all our descendants,
+ // including our anonymous wrapper kid:
+ anonKid->AddStateBits(mState & NS_FRAME_IS_DIRTY);
+ anonKid->ReflowSVG();
+ MOZ_ASSERT(!anonKid->GetNextSibling(),
+ "We should have one anonymous child frame wrapping our real "
+ "children");
+ }
+ mCallingReflowSVG = false;
+
+ // Set our anonymous kid's offset from our border box:
+ anonKid->SetPosition(GetContentRectRelativeToSelf().TopLeft());
+
+ // Including our size in our overflow rects regardless of the value of
+ // 'background', 'border', etc. makes sure that we usually (when we clip to
+ // our content area) don't have to keep changing our overflow rects as our
+ // descendants move about (see perf comment below). Including our size in our
+ // scrollable overflow rect also makes sure that we scroll if we're too big
+ // for our viewport.
+ //
+ // <svg> never allows scrolling to anything outside its mRect (only panning),
+ // so we must always keep our scrollable overflow set to our size.
+ //
+ // With regards to visual overflow, we always clip root-<svg> (see our
+ // BuildDisplayList method) regardless of the value of the 'overflow'
+ // property since that is per-spec, even for the initial 'visible' value. For
+ // that reason there's no point in adding descendant visual overflow to our
+ // own when this frame is for a root-<svg>. That said, there's also a very
+ // good performance reason for us wanting to avoid doing so. If we did, then
+ // the frame's overflow would often change as descendants that are partially
+ // or fully outside its rect moved (think animation on/off screen), and that
+ // would cause us to do a full NS_FRAME_IS_DIRTY reflow and repaint of the
+ // entire document tree each such move (see bug 875175).
+ //
+ // So it's only non-root outer-<svg> that has the visual overflow of its
+ // descendants added to its own. (Note that the default user-agent style
+ // sheet makes 'hidden' the default value for :not(root(svg)), so usually
+ // FinishAndStoreOverflow will still clip this back to the frame's rect.)
+ //
+ // WARNING!! Keep UpdateBounds below in sync with whatever we do for our
+ // overflow rects here! (Again, see bug 875175.)
+ //
+ aDesiredSize.SetOverflowAreasToDesiredBounds();
+ if (!mIsRootContent) {
+ aDesiredSize.mOverflowAreas.VisualOverflow().UnionRect(
+ aDesiredSize.mOverflowAreas.VisualOverflow(),
+ anonKid->GetVisualOverflowRect() + anonKid->GetPosition());
+ }
+ FinishAndStoreOverflow(&aDesiredSize);
+
+ NS_FRAME_TRACE(NS_FRAME_TRACE_CALLS,
+ ("exit nsSVGOuterSVGFrame::Reflow: size=%d,%d",
+ aDesiredSize.Width(), aDesiredSize.Height()));
+ NS_FRAME_SET_TRUNCATION(aStatus, aReflowInput, aDesiredSize);
+}
+
+void
+nsSVGOuterSVGFrame::DidReflow(nsPresContext* aPresContext,
+ const ReflowInput* aReflowInput,
+ nsDidReflowStatus aStatus)
+{
+ nsSVGDisplayContainerFrame::DidReflow(aPresContext,aReflowInput,aStatus);
+
+ // Make sure elements styled by :hover get updated if script/animation moves
+ // them under or out from under the pointer:
+ PresContext()->PresShell()->SynthesizeMouseMove(false);
+}
+
+/* virtual */ void
+nsSVGOuterSVGFrame::UnionChildOverflow(nsOverflowAreas& aOverflowAreas)
+{
+ // See the comments in Reflow above.
+
+ // WARNING!! Keep this in sync with Reflow above!
+
+ if (!mIsRootContent) {
+ nsIFrame *anonKid = PrincipalChildList().FirstChild();
+ aOverflowAreas.VisualOverflow().UnionRect(
+ aOverflowAreas.VisualOverflow(),
+ anonKid->GetVisualOverflowRect() + anonKid->GetPosition());
+ }
+}
+
+
+//----------------------------------------------------------------------
+// container methods
+
+/**
+ * Used to paint/hit-test SVG when SVG display lists are disabled.
+ */
+class nsDisplayOuterSVG : public nsDisplayItem {
+public:
+ nsDisplayOuterSVG(nsDisplayListBuilder* aBuilder,
+ nsSVGOuterSVGFrame* aFrame) :
+ nsDisplayItem(aBuilder, aFrame) {
+ MOZ_COUNT_CTOR(nsDisplayOuterSVG);
+ }
+#ifdef NS_BUILD_REFCNT_LOGGING
+ virtual ~nsDisplayOuterSVG() {
+ MOZ_COUNT_DTOR(nsDisplayOuterSVG);
+ }
+#endif
+
+ virtual void HitTest(nsDisplayListBuilder* aBuilder, const nsRect& aRect,
+ HitTestState* aState,
+ nsTArray<nsIFrame*> *aOutFrames) override;
+ virtual void Paint(nsDisplayListBuilder* aBuilder,
+ nsRenderingContext* aCtx) override;
+
+ virtual void ComputeInvalidationRegion(nsDisplayListBuilder* aBuilder,
+ const nsDisplayItemGeometry* aGeometry,
+ nsRegion* aInvalidRegion) override;
+
+ nsDisplayItemGeometry* AllocateGeometry(nsDisplayListBuilder* aBuilder) override
+ {
+ return new nsDisplayItemGenericImageGeometry(this, aBuilder);
+ }
+
+ NS_DISPLAY_DECL_NAME("SVGOuterSVG", TYPE_SVG_OUTER_SVG)
+};
+
+void
+nsDisplayOuterSVG::HitTest(nsDisplayListBuilder* aBuilder, const nsRect& aRect,
+ HitTestState* aState, nsTArray<nsIFrame*> *aOutFrames)
+{
+ nsSVGOuterSVGFrame *outerSVGFrame = static_cast<nsSVGOuterSVGFrame*>(mFrame);
+
+ nsPoint refFrameToContentBox =
+ ToReferenceFrame() + outerSVGFrame->GetContentRectRelativeToSelf().TopLeft();
+
+ nsPoint pointRelativeToContentBox =
+ nsPoint(aRect.x + aRect.width / 2, aRect.y + aRect.height / 2) -
+ refFrameToContentBox;
+
+ gfxPoint svgViewportRelativePoint =
+ gfxPoint(pointRelativeToContentBox.x, pointRelativeToContentBox.y) /
+ outerSVGFrame->PresContext()->AppUnitsPerCSSPixel();
+
+ nsSVGOuterSVGAnonChildFrame *anonKid =
+ static_cast<nsSVGOuterSVGAnonChildFrame*>(
+ outerSVGFrame->PrincipalChildList().FirstChild());
+
+ nsIFrame* frame =
+ nsSVGUtils::HitTestChildren(anonKid, svgViewportRelativePoint);
+ if (frame) {
+ aOutFrames->AppendElement(frame);
+ }
+}
+
+void
+nsDisplayOuterSVG::Paint(nsDisplayListBuilder* aBuilder,
+ nsRenderingContext* aContext)
+{
+#if defined(DEBUG) && defined(SVG_DEBUG_PAINT_TIMING)
+ PRTime start = PR_Now();
+#endif
+
+ // Create an SVGAutoRenderState so we can call SetPaintingToWindow on it.
+ SVGAutoRenderState state(aContext->GetDrawTarget());
+
+ if (aBuilder->IsPaintingToWindow()) {
+ state.SetPaintingToWindow(true);
+ }
+
+ nsRect viewportRect =
+ mFrame->GetContentRectRelativeToSelf() + ToReferenceFrame();
+
+ nsRect clipRect = mVisibleRect.Intersect(viewportRect);
+
+ uint32_t appUnitsPerDevPixel = mFrame->PresContext()->AppUnitsPerDevPixel();
+
+ nsIntRect contentAreaDirtyRect =
+ (clipRect - viewportRect.TopLeft()).
+ ToOutsidePixels(appUnitsPerDevPixel);
+
+ gfxPoint devPixelOffset =
+ nsLayoutUtils::PointToGfxPoint(viewportRect.TopLeft(), appUnitsPerDevPixel);
+
+ aContext->ThebesContext()->Save();
+ // We include the offset of our frame and a scale from device pixels to user
+ // units (i.e. CSS px) in the matrix that we pass to our children):
+ gfxMatrix tm = nsSVGIntegrationUtils::GetCSSPxToDevPxMatrix(mFrame) *
+ gfxMatrix::Translation(devPixelOffset);
+ DrawResult result =
+ nsSVGUtils::PaintFrameWithEffects(mFrame, *aContext->ThebesContext(), tm,
+ &contentAreaDirtyRect);
+ nsDisplayItemGenericImageGeometry::UpdateDrawResult(this, result);
+ aContext->ThebesContext()->Restore();
+
+#if defined(DEBUG) && defined(SVG_DEBUG_PAINT_TIMING)
+ PRTime end = PR_Now();
+ printf("SVG Paint Timing: %f ms\n", (end-start)/1000.0);
+#endif
+}
+
+nsRegion
+nsSVGOuterSVGFrame::FindInvalidatedForeignObjectFrameChildren(nsIFrame* aFrame)
+{
+ nsRegion result;
+ if (mForeignObjectHash && mForeignObjectHash->Count()) {
+ for (auto it = mForeignObjectHash->Iter(); !it.Done(); it.Next()) {
+ result.Or(result, it.Get()->GetKey()->GetInvalidRegion());
+ }
+ }
+ return result;
+}
+
+void
+nsDisplayOuterSVG::ComputeInvalidationRegion(nsDisplayListBuilder* aBuilder,
+ const nsDisplayItemGeometry* aGeometry,
+ nsRegion* aInvalidRegion)
+{
+ nsSVGOuterSVGFrame *frame = static_cast<nsSVGOuterSVGFrame*>(mFrame);
+ frame->InvalidateSVG(frame->FindInvalidatedForeignObjectFrameChildren(frame));
+
+ nsRegion result = frame->GetInvalidRegion();
+ result.MoveBy(ToReferenceFrame());
+ frame->ClearInvalidRegion();
+
+ nsDisplayItem::ComputeInvalidationRegion(aBuilder, aGeometry, aInvalidRegion);
+ aInvalidRegion->Or(*aInvalidRegion, result);
+
+ auto geometry =
+ static_cast<const nsDisplayItemGenericImageGeometry*>(aGeometry);
+
+ if (aBuilder->ShouldSyncDecodeImages() &&
+ geometry->ShouldInvalidateToSyncDecodeImages()) {
+ bool snap;
+ aInvalidRegion->Or(*aInvalidRegion, GetBounds(aBuilder, &snap));
+ }
+}
+
+nsresult
+nsSVGOuterSVGFrame::AttributeChanged(int32_t aNameSpaceID,
+ nsIAtom* aAttribute,
+ int32_t aModType)
+{
+ if (aNameSpaceID == kNameSpaceID_None &&
+ !(GetStateBits() & (NS_FRAME_FIRST_REFLOW | NS_FRAME_IS_NONDISPLAY))) {
+ if (aAttribute == nsGkAtoms::viewBox ||
+ aAttribute == nsGkAtoms::preserveAspectRatio ||
+ aAttribute == nsGkAtoms::transform) {
+
+ // make sure our cached transform matrix gets (lazily) updated
+ mCanvasTM = nullptr;
+
+ nsSVGUtils::NotifyChildrenOfSVGChange(PrincipalChildList().FirstChild(),
+ aAttribute == nsGkAtoms::viewBox ?
+ TRANSFORM_CHANGED | COORD_CONTEXT_CHANGED : TRANSFORM_CHANGED);
+
+ if (aAttribute != nsGkAtoms::transform) {
+ static_cast<SVGSVGElement*>(mContent)->ChildrenOnlyTransformChanged();
+ }
+
+ } else if (aAttribute == nsGkAtoms::width ||
+ aAttribute == nsGkAtoms::height) {
+
+ // Don't call ChildrenOnlyTransformChanged() here, since we call it
+ // under Reflow if the width/height actually changed.
+
+ nsIFrame* embeddingFrame;
+ if (IsRootOfReplacedElementSubDoc(&embeddingFrame) && embeddingFrame) {
+ if (DependsOnIntrinsicSize(embeddingFrame)) {
+ // Tell embeddingFrame's presShell it needs to be reflowed (which takes
+ // care of reflowing us too).
+ embeddingFrame->PresContext()->PresShell()->
+ FrameNeedsReflow(embeddingFrame, nsIPresShell::eStyleChange, NS_FRAME_IS_DIRTY);
+ }
+ // else our width and height is overridden - don't reflow anything
+ } else {
+ // We are not embedded by reference, so our 'width' and 'height'
+ // attributes are not overridden - we need to reflow.
+ PresContext()->PresShell()->
+ FrameNeedsReflow(this, nsIPresShell::eStyleChange, NS_FRAME_IS_DIRTY);
+ }
+ }
+ }
+
+ return NS_OK;
+}
+
+//----------------------------------------------------------------------
+// painting
+
+void
+nsSVGOuterSVGFrame::BuildDisplayList(nsDisplayListBuilder* aBuilder,
+ const nsRect& aDirtyRect,
+ const nsDisplayListSet& aLists)
+{
+ if (GetStateBits() & NS_FRAME_IS_NONDISPLAY) {
+ return;
+ }
+
+ DisplayBorderBackgroundOutline(aBuilder, aLists);
+
+ // Per-spec, we always clip root-<svg> even when 'overflow' has its initial
+ // value of 'visible'. See also the "visual overflow" comments in Reflow.
+ DisplayListClipState::AutoSaveRestore autoSR(aBuilder);
+ if (mIsRootContent ||
+ StyleDisplay()->IsScrollableOverflow()) {
+ autoSR.ClipContainingBlockDescendantsToContentBox(aBuilder, this);
+ }
+
+ if ((aBuilder->IsForEventDelivery() &&
+ NS_SVGDisplayListHitTestingEnabled()) ||
+ (!aBuilder->IsForEventDelivery() &&
+ NS_SVGDisplayListPaintingEnabled())) {
+ nsDisplayList *contentList = aLists.Content();
+ nsDisplayListSet set(contentList, contentList, contentList,
+ contentList, contentList, contentList);
+ BuildDisplayListForNonBlockChildren(aBuilder, aDirtyRect, set);
+ } else if (IsVisibleForPainting(aBuilder) || !aBuilder->IsForPainting()) {
+ aLists.Content()->AppendNewToTop(
+ new (aBuilder) nsDisplayOuterSVG(aBuilder, this));
+ }
+}
+
+nsSplittableType
+nsSVGOuterSVGFrame::GetSplittableType() const
+{
+ return NS_FRAME_NOT_SPLITTABLE;
+}
+
+nsIAtom *
+nsSVGOuterSVGFrame::GetType() const
+{
+ return nsGkAtoms::svgOuterSVGFrame;
+}
+
+//----------------------------------------------------------------------
+// nsISVGSVGFrame methods:
+
+void
+nsSVGOuterSVGFrame::NotifyViewportOrTransformChanged(uint32_t aFlags)
+{
+ MOZ_ASSERT(aFlags &&
+ !(aFlags & ~(COORD_CONTEXT_CHANGED | TRANSFORM_CHANGED |
+ FULL_ZOOM_CHANGED)),
+ "Unexpected aFlags value");
+
+ // No point in doing anything when were not init'ed yet:
+ if (!mViewportInitialized) {
+ return;
+ }
+
+ SVGSVGElement *content = static_cast<SVGSVGElement*>(mContent);
+
+ if (aFlags & COORD_CONTEXT_CHANGED) {
+ if (content->HasViewBoxRect()) {
+ // Percentage lengths on children resolve against the viewBox rect so we
+ // don't need to notify them of the viewport change, but the viewBox
+ // transform will have changed, so we need to notify them of that instead.
+ aFlags = TRANSFORM_CHANGED;
+ }
+ else if (content->ShouldSynthesizeViewBox()) {
+ // In the case of a synthesized viewBox, the synthetic viewBox's rect
+ // changes as the viewport changes. As a result we need to maintain the
+ // COORD_CONTEXT_CHANGED flag.
+ aFlags |= TRANSFORM_CHANGED;
+ }
+ else if (mCanvasTM && mCanvasTM->IsSingular()) {
+ // A width/height of zero will result in us having a singular mCanvasTM
+ // even when we don't have a viewBox. So we also want to recompute our
+ // mCanvasTM for this width/height change even though we don't have a
+ // viewBox.
+ aFlags |= TRANSFORM_CHANGED;
+ }
+ }
+
+ bool haveNonFulLZoomTransformChange = (aFlags & TRANSFORM_CHANGED);
+
+ if (aFlags & FULL_ZOOM_CHANGED) {
+ // Convert FULL_ZOOM_CHANGED to TRANSFORM_CHANGED:
+ aFlags = (aFlags & ~FULL_ZOOM_CHANGED) | TRANSFORM_CHANGED;
+ }
+
+ if (aFlags & TRANSFORM_CHANGED) {
+ // Make sure our canvas transform matrix gets (lazily) recalculated:
+ mCanvasTM = nullptr;
+
+ if (haveNonFulLZoomTransformChange &&
+ !(mState & NS_FRAME_IS_NONDISPLAY)) {
+ uint32_t flags = (mState & NS_FRAME_IN_REFLOW) ?
+ SVGSVGElement::eDuringReflow : 0;
+ content->ChildrenOnlyTransformChanged(flags);
+ }
+ }
+
+ nsSVGUtils::NotifyChildrenOfSVGChange(PrincipalChildList().FirstChild(), aFlags);
+}
+
+//----------------------------------------------------------------------
+// nsISVGChildFrame methods:
+
+DrawResult
+nsSVGOuterSVGFrame::PaintSVG(gfxContext& aContext,
+ const gfxMatrix& aTransform,
+ const nsIntRect* aDirtyRect)
+{
+ NS_ASSERTION(PrincipalChildList().FirstChild()->GetType() ==
+ nsGkAtoms::svgOuterSVGAnonChildFrame &&
+ !PrincipalChildList().FirstChild()->GetNextSibling(),
+ "We should have a single, anonymous, child");
+ nsSVGOuterSVGAnonChildFrame *anonKid =
+ static_cast<nsSVGOuterSVGAnonChildFrame*>(PrincipalChildList().FirstChild());
+ return anonKid->PaintSVG(aContext, aTransform, aDirtyRect);
+}
+
+SVGBBox
+nsSVGOuterSVGFrame::GetBBoxContribution(const gfx::Matrix &aToBBoxUserspace,
+ uint32_t aFlags)
+{
+ NS_ASSERTION(PrincipalChildList().FirstChild()->GetType() ==
+ nsGkAtoms::svgOuterSVGAnonChildFrame &&
+ !PrincipalChildList().FirstChild()->GetNextSibling(),
+ "We should have a single, anonymous, child");
+ // We must defer to our child so that we don't include our
+ // content->PrependLocalTransformsTo() transforms.
+ nsSVGOuterSVGAnonChildFrame *anonKid =
+ static_cast<nsSVGOuterSVGAnonChildFrame*>(PrincipalChildList().FirstChild());
+ return anonKid->GetBBoxContribution(aToBBoxUserspace, aFlags);
+}
+
+//----------------------------------------------------------------------
+// nsSVGContainerFrame methods:
+
+gfxMatrix
+nsSVGOuterSVGFrame::GetCanvasTM()
+{
+ if (!mCanvasTM) {
+ SVGSVGElement *content = static_cast<SVGSVGElement*>(mContent);
+
+ float devPxPerCSSPx =
+ 1.0f / PresContext()->AppUnitsToFloatCSSPixels(
+ PresContext()->AppUnitsPerDevPixel());
+
+ gfxMatrix tm = content->PrependLocalTransformsTo(
+ gfxMatrix::Scaling(devPxPerCSSPx, devPxPerCSSPx));
+ mCanvasTM = new gfxMatrix(tm);
+ }
+ return *mCanvasTM;
+}
+
+//----------------------------------------------------------------------
+// Implementation helpers
+
+bool
+nsSVGOuterSVGFrame::IsRootOfReplacedElementSubDoc(nsIFrame **aEmbeddingFrame)
+{
+ if (!mContent->GetParent()) {
+ // Our content is the document element
+ nsCOMPtr<nsIDocShell> docShell = PresContext()->GetDocShell();
+ nsCOMPtr<nsPIDOMWindowOuter> window;
+ if (docShell) {
+ window = docShell->GetWindow();
+ }
+
+ if (window) {
+ nsCOMPtr<nsIDOMElement> frameElement = window->GetFrameElement();
+ nsCOMPtr<nsIObjectLoadingContent> olc = do_QueryInterface(frameElement);
+ nsCOMPtr<nsIDOMHTMLIFrameElement> iframeElement =
+ do_QueryInterface(frameElement);
+ if (olc || iframeElement) {
+ // Our document is inside an HTML 'object', 'embed', 'applet'
+ // or 'iframe' element
+ if (aEmbeddingFrame) {
+ nsCOMPtr<nsIContent> element = do_QueryInterface(frameElement);
+ *aEmbeddingFrame = element->GetPrimaryFrame();
+ NS_ASSERTION(*aEmbeddingFrame, "Yikes, no embedding frame!");
+ }
+ return true;
+ }
+ }
+ }
+ if (aEmbeddingFrame) {
+ *aEmbeddingFrame = nullptr;
+ }
+ return false;
+}
+
+bool
+nsSVGOuterSVGFrame::IsRootOfImage()
+{
+ if (!mContent->GetParent()) {
+ // Our content is the document element
+ nsIDocument* doc = mContent->GetUncomposedDoc();
+ if (doc && doc->IsBeingUsedAsImage()) {
+ // Our document is being used as an image
+ return true;
+ }
+ }
+
+ return false;
+}
+
+bool
+nsSVGOuterSVGFrame::VerticalScrollbarNotNeeded() const
+{
+ nsSVGLength2 &height = static_cast<SVGSVGElement*>(mContent)->
+ mLengthAttributes[SVGSVGElement::ATTR_HEIGHT];
+ return height.IsPercentage() && height.GetBaseValInSpecifiedUnits() <= 100;
+}
+
+
+//----------------------------------------------------------------------
+// Implementation of nsSVGOuterSVGAnonChildFrame
+
+nsContainerFrame*
+NS_NewSVGOuterSVGAnonChildFrame(nsIPresShell* aPresShell,
+ nsStyleContext* aContext)
+{
+ return new (aPresShell) nsSVGOuterSVGAnonChildFrame(aContext);
+}
+
+NS_IMPL_FRAMEARENA_HELPERS(nsSVGOuterSVGAnonChildFrame)
+
+#ifdef DEBUG
+void
+nsSVGOuterSVGAnonChildFrame::Init(nsIContent* aContent,
+ nsContainerFrame* aParent,
+ nsIFrame* aPrevInFlow)
+{
+ MOZ_ASSERT(aParent->GetType() == nsGkAtoms::svgOuterSVGFrame,
+ "Unexpected parent");
+ nsSVGDisplayContainerFrame::Init(aContent, aParent, aPrevInFlow);
+}
+#endif
+
+nsIAtom *
+nsSVGOuterSVGAnonChildFrame::GetType() const
+{
+ return nsGkAtoms::svgOuterSVGAnonChildFrame;
+}
+
+bool
+nsSVGOuterSVGAnonChildFrame::HasChildrenOnlyTransform(gfx::Matrix *aTransform) const
+{
+ // We must claim our nsSVGOuterSVGFrame's children-only transforms as our own
+ // so that the children we are used to wrap are transformed properly.
+
+ SVGSVGElement *content = static_cast<SVGSVGElement*>(mContent);
+
+ bool hasTransform = content->HasChildrenOnlyTransform();
+
+ if (hasTransform && aTransform) {
+ // Outer-<svg> doesn't use x/y, so we can pass eChildToUserSpace here.
+ gfxMatrix identity;
+ *aTransform = gfx::ToMatrix(
+ content->PrependLocalTransformsTo(identity, eChildToUserSpace));
+ }
+
+ return hasTransform;
+}
diff --git a/layout/svg/nsSVGOuterSVGFrame.h b/layout/svg/nsSVGOuterSVGFrame.h
new file mode 100644
index 0000000000..a085936789
--- /dev/null
+++ b/layout/svg/nsSVGOuterSVGFrame.h
@@ -0,0 +1,277 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef __NS_SVGOUTERSVGFRAME_H__
+#define __NS_SVGOUTERSVGFRAME_H__
+
+#include "mozilla/Attributes.h"
+#include "nsAutoPtr.h"
+#include "nsISVGSVGFrame.h"
+#include "nsSVGContainerFrame.h"
+#include "nsRegion.h"
+
+class gfxContext;
+class nsSVGForeignObjectFrame;
+
+////////////////////////////////////////////////////////////////////////
+// nsSVGOuterSVGFrame class
+
+class nsSVGOuterSVGFrame final : public nsSVGDisplayContainerFrame
+ , public nsISVGSVGFrame
+{
+ friend nsContainerFrame*
+ NS_NewSVGOuterSVGFrame(nsIPresShell* aPresShell, nsStyleContext* aContext);
+protected:
+ explicit nsSVGOuterSVGFrame(nsStyleContext* aContext);
+
+public:
+ NS_DECL_QUERYFRAME
+ NS_DECL_FRAMEARENA_HELPERS
+
+#ifdef DEBUG
+ ~nsSVGOuterSVGFrame() {
+ NS_ASSERTION(!mForeignObjectHash || mForeignObjectHash->Count() == 0,
+ "foreignObject(s) still registered!");
+ }
+#endif
+
+ // nsIFrame:
+ virtual nscoord GetMinISize(nsRenderingContext *aRenderingContext) override;
+ virtual nscoord GetPrefISize(nsRenderingContext *aRenderingContext) override;
+
+ virtual mozilla::IntrinsicSize GetIntrinsicSize() override;
+ virtual nsSize GetIntrinsicRatio() override;
+
+ virtual mozilla::LogicalSize
+ ComputeSize(nsRenderingContext *aRenderingContext,
+ mozilla::WritingMode aWritingMode,
+ const mozilla::LogicalSize& aCBSize,
+ nscoord aAvailableISize,
+ const mozilla::LogicalSize& aMargin,
+ const mozilla::LogicalSize& aBorder,
+ const mozilla::LogicalSize& aPadding,
+ ComputeSizeFlags aFlags) override;
+
+ virtual void Reflow(nsPresContext* aPresContext,
+ ReflowOutput& aDesiredSize,
+ const ReflowInput& aReflowInput,
+ nsReflowStatus& aStatus) override;
+
+ virtual void DidReflow(nsPresContext* aPresContext,
+ const ReflowInput* aReflowInput,
+ nsDidReflowStatus aStatus) override;
+
+ virtual void UnionChildOverflow(nsOverflowAreas& aOverflowAreas) override;
+
+ virtual void BuildDisplayList(nsDisplayListBuilder* aBuilder,
+ const nsRect& aDirtyRect,
+ const nsDisplayListSet& aLists) override;
+
+ virtual void Init(nsIContent* aContent,
+ nsContainerFrame* aParent,
+ nsIFrame* aPrevInFlow) override;
+
+ virtual nsSplittableType GetSplittableType() const override;
+
+ /**
+ * Get the "type" of the frame
+ *
+ * @see nsGkAtoms::svgOuterSVGFrame
+ */
+ virtual nsIAtom* GetType() const override;
+
+#ifdef DEBUG_FRAME_DUMP
+ virtual nsresult GetFrameName(nsAString& aResult) const override
+ {
+ return MakeFrameName(NS_LITERAL_STRING("SVGOuterSVG"), aResult);
+ }
+#endif
+
+ virtual nsresult AttributeChanged(int32_t aNameSpaceID,
+ nsIAtom* aAttribute,
+ int32_t aModType) override;
+
+ virtual nsContainerFrame* GetContentInsertionFrame() override {
+ // Any children must be added to our single anonymous inner frame kid.
+ MOZ_ASSERT(PrincipalChildList().FirstChild() &&
+ PrincipalChildList().FirstChild()->GetType() ==
+ nsGkAtoms::svgOuterSVGAnonChildFrame,
+ "Where is our anonymous child?");
+ return PrincipalChildList().FirstChild()->GetContentInsertionFrame();
+ }
+
+ virtual bool IsSVGTransformed(Matrix *aOwnTransform,
+ Matrix *aFromParentTransform) const override {
+ // Our anonymous wrapper performs the transforms. We simply
+ // return whether we are transformed here but don't apply the transforms
+ // themselves.
+ return PrincipalChildList().FirstChild()->IsSVGTransformed();
+ }
+
+ // nsISVGSVGFrame interface:
+ virtual void NotifyViewportOrTransformChanged(uint32_t aFlags) override;
+
+ // nsISVGChildFrame methods:
+ virtual DrawResult PaintSVG(gfxContext& aContext,
+ const gfxMatrix& aTransform,
+ const nsIntRect* aDirtyRect = nullptr) override;
+ virtual SVGBBox GetBBoxContribution(const Matrix &aToBBoxUserspace,
+ uint32_t aFlags) override;
+
+ // nsSVGContainerFrame methods:
+ virtual gfxMatrix GetCanvasTM() override;
+
+ /* Methods to allow descendant nsSVGForeignObjectFrame frames to register and
+ * unregister themselves with their nearest nsSVGOuterSVGFrame ancestor. This
+ * is temporary until display list based invalidation is impleented for SVG.
+ * Maintaining a list of our foreignObject descendants allows us to search
+ * them for areas that need to be invalidated, without having to also search
+ * the SVG frame tree for foreignObjects. This is important so that bug 539356
+ * does not slow down SVG in general (only foreignObjects, until bug 614732 is
+ * fixed).
+ */
+ void RegisterForeignObject(nsSVGForeignObjectFrame* aFrame);
+ void UnregisterForeignObject(nsSVGForeignObjectFrame* aFrame);
+
+ virtual bool HasChildrenOnlyTransform(Matrix *aTransform) const override {
+ // Our anonymous wrapper child must claim our children-only transforms as
+ // its own so that our real children (the frames it wraps) are transformed
+ // by them, and we must pretend we don't have any children-only transforms
+ // so that our anonymous child is _not_ transformed by them.
+ return false;
+ }
+
+ /**
+ * Return true only if the height is unspecified (defaulting to 100%) or else
+ * the height is explicitly set to a percentage value no greater than 100%.
+ */
+ bool VerticalScrollbarNotNeeded() const;
+
+ bool IsCallingReflowSVG() const {
+ return mCallingReflowSVG;
+ }
+
+ void InvalidateSVG(const nsRegion& aRegion)
+ {
+ if (!aRegion.IsEmpty()) {
+ mInvalidRegion.Or(mInvalidRegion, aRegion);
+ InvalidateFrame();
+ }
+ }
+
+ void ClearInvalidRegion() { mInvalidRegion.SetEmpty(); }
+
+ const nsRegion& GetInvalidRegion() {
+ nsRect rect;
+ if (!IsInvalid(rect)) {
+ mInvalidRegion.SetEmpty();
+ }
+ return mInvalidRegion;
+ }
+
+ nsRegion FindInvalidatedForeignObjectFrameChildren(nsIFrame* aFrame);
+
+protected:
+
+ bool mCallingReflowSVG;
+
+ /* Returns true if our content is the document element and our document is
+ * embedded in an HTML 'object', 'embed' or 'applet' element. Set
+ * aEmbeddingFrame to obtain the nsIFrame for the embedding HTML element.
+ */
+ bool IsRootOfReplacedElementSubDoc(nsIFrame **aEmbeddingFrame = nullptr);
+
+ /* Returns true if our content is the document element and our document is
+ * being used as an image.
+ */
+ bool IsRootOfImage();
+
+ // This is temporary until display list based invalidation is implemented for
+ // SVG.
+ // A hash-set containing our nsSVGForeignObjectFrame descendants. Note we use
+ // a hash-set to avoid the O(N^2) behavior we'd get tearing down an SVG frame
+ // subtree if we were to use a list (see bug 381285 comment 20).
+ nsAutoPtr<nsTHashtable<nsPtrHashKey<nsSVGForeignObjectFrame> > > mForeignObjectHash;
+
+ nsAutoPtr<gfxMatrix> mCanvasTM;
+
+ nsRegion mInvalidRegion;
+
+ float mFullZoom;
+
+ bool mViewportInitialized;
+ bool mIsRootContent;
+};
+
+////////////////////////////////////////////////////////////////////////
+// nsSVGOuterSVGAnonChildFrame class
+
+/**
+ * nsSVGOuterSVGFrames have a single direct child that is an instance of this
+ * class, and which is used to wrap their real child frames. Such anonymous
+ * wrapper frames created from this class exist because SVG frames need their
+ * GetPosition() offset to be their offset relative to "user space" (in app
+ * units) so that they can play nicely with nsDisplayTransform. This is fine
+ * for all SVG frames except for direct children of an nsSVGOuterSVGFrame,
+ * since an nsSVGOuterSVGFrame can have CSS border and padding (unlike other
+ * SVG frames). The direct children can't include the offsets due to any such
+ * border/padding in their mRects since that would break nsDisplayTransform,
+ * but not including these offsets would break other parts of the Mozilla code
+ * that assume a frame's mRect contains its border-box-to-parent-border-box
+ * offset, in particular nsIFrame::GetOffsetTo and the functions that depend on
+ * it. Wrapping an nsSVGOuterSVGFrame's children in an instance of this class
+ * with its GetPosition() set to its nsSVGOuterSVGFrame's border/padding offset
+ * keeps both nsDisplayTransform and nsIFrame::GetOffsetTo happy.
+ *
+ * The reason that this class inherit from nsSVGDisplayContainerFrame rather
+ * than simply from nsContainerFrame is so that we can avoid having special
+ * handling for these inner wrappers in multiple parts of the SVG code. For
+ * example, the implementations of IsSVGTransformed and GetCanvasTM assume
+ * nsSVGContainerFrame instances all the way up to the nsSVGOuterSVGFrame.
+ */
+class nsSVGOuterSVGAnonChildFrame : public nsSVGDisplayContainerFrame
+{
+ friend nsContainerFrame*
+ NS_NewSVGOuterSVGAnonChildFrame(nsIPresShell* aPresShell,
+ nsStyleContext* aContext);
+
+ explicit nsSVGOuterSVGAnonChildFrame(nsStyleContext* aContext)
+ : nsSVGDisplayContainerFrame(aContext)
+ {}
+
+public:
+ NS_DECL_FRAMEARENA_HELPERS
+
+#ifdef DEBUG
+ virtual void Init(nsIContent* aContent,
+ nsContainerFrame* aParent,
+ nsIFrame* aPrevInFlow) override;
+#endif
+
+#ifdef DEBUG_FRAME_DUMP
+ virtual nsresult GetFrameName(nsAString& aResult) const override {
+ return MakeFrameName(NS_LITERAL_STRING("SVGOuterSVGAnonChild"), aResult);
+ }
+#endif
+
+ /**
+ * Get the "type" of the frame
+ *
+ * @see nsGkAtoms::svgOuterSVGAnonChildFrame
+ */
+ virtual nsIAtom* GetType() const override;
+
+ // nsSVGContainerFrame methods:
+ virtual gfxMatrix GetCanvasTM() override {
+ // GetCanvasTM returns the transform from an SVG frame to the frame's
+ // nsSVGOuterSVGFrame's content box, so we do not include any x/y offset
+ // set on us for any CSS border or padding on our nsSVGOuterSVGFrame.
+ return static_cast<nsSVGOuterSVGFrame*>(GetParent())->GetCanvasTM();
+ }
+
+ virtual bool HasChildrenOnlyTransform(Matrix *aTransform) const override;
+};
+
+#endif
diff --git a/layout/svg/nsSVGPaintServerFrame.h b/layout/svg/nsSVGPaintServerFrame.h
new file mode 100644
index 0000000000..6b568f8727
--- /dev/null
+++ b/layout/svg/nsSVGPaintServerFrame.h
@@ -0,0 +1,70 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef __NS_SVGPAINTSERVERFRAME_H__
+#define __NS_SVGPAINTSERVERFRAME_H__
+
+#include "mozilla/Attributes.h"
+#include "nsCOMPtr.h"
+#include "nsFrame.h"
+#include "nsIFrame.h"
+#include "nsQueryFrame.h"
+#include "nsSVGContainerFrame.h"
+#include "nsSVGUtils.h"
+
+namespace mozilla {
+namespace gfx {
+class DrawTarget;
+} // namespace gfx
+} // namespace mozilla
+
+class gfxContext;
+class gfxPattern;
+class nsStyleContext;
+
+struct gfxRect;
+
+class nsSVGPaintServerFrame : public nsSVGContainerFrame
+{
+protected:
+ typedef mozilla::gfx::DrawTarget DrawTarget;
+
+ explicit nsSVGPaintServerFrame(nsStyleContext* aContext)
+ : nsSVGContainerFrame(aContext)
+ {
+ AddStateBits(NS_FRAME_IS_NONDISPLAY);
+ }
+
+public:
+ NS_DECL_ABSTRACT_FRAME(nsSVGPaintServerFrame)
+
+ /**
+ * Constructs a gfxPattern of the paint server rendering.
+ *
+ * @param aContextMatrix The transform matrix that is currently applied to
+ * the gfxContext that is being drawn to. This is needed by SVG patterns so
+ * that surfaces of the correct size can be created. (SVG gradients are
+ * vector based, so it's not used there.)
+ */
+ virtual already_AddRefed<gfxPattern>
+ GetPaintServerPattern(nsIFrame *aSource,
+ const DrawTarget* aDrawTarget,
+ const gfxMatrix& aContextMatrix,
+ nsStyleSVGPaint nsStyleSVG::*aFillOrStroke,
+ float aOpacity,
+ const gfxRect *aOverrideBounds = nullptr) = 0;
+
+ // nsIFrame methods:
+ virtual void BuildDisplayList(nsDisplayListBuilder* aBuilder,
+ const nsRect& aDirtyRect,
+ const nsDisplayListSet& aLists) override {}
+
+ virtual bool IsFrameOfType(uint32_t aFlags) const override
+ {
+ return nsSVGContainerFrame::IsFrameOfType(aFlags & ~nsIFrame::eSVGPaintServer);
+ }
+};
+
+#endif // __NS_SVGPAINTSERVERFRAME_H__
diff --git a/layout/svg/nsSVGPathGeometryFrame.cpp b/layout/svg/nsSVGPathGeometryFrame.cpp
new file mode 100644
index 0000000000..32cd0e4965
--- /dev/null
+++ b/layout/svg/nsSVGPathGeometryFrame.cpp
@@ -0,0 +1,928 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+// Main header first:
+#include "nsSVGPathGeometryFrame.h"
+
+// Keep others in (case-insensitive) order:
+#include "gfx2DGlue.h"
+#include "gfxContext.h"
+#include "gfxPlatform.h"
+#include "gfxUtils.h"
+#include "mozilla/gfx/2D.h"
+#include "mozilla/gfx/Helpers.h"
+#include "mozilla/RefPtr.h"
+#include "mozilla/SVGContextPaint.h"
+#include "nsDisplayList.h"
+#include "nsGkAtoms.h"
+#include "nsLayoutUtils.h"
+#include "nsRenderingContext.h"
+#include "nsSVGEffects.h"
+#include "nsSVGIntegrationUtils.h"
+#include "nsSVGMarkerFrame.h"
+#include "nsSVGPathGeometryElement.h"
+#include "nsSVGUtils.h"
+#include "mozilla/ArrayUtils.h"
+#include "SVGAnimatedTransformList.h"
+#include "SVGContentUtils.h"
+#include "SVGGraphicsElement.h"
+
+using namespace mozilla;
+using namespace mozilla::gfx;
+using namespace mozilla::image;
+
+//----------------------------------------------------------------------
+// Implementation
+
+nsIFrame*
+NS_NewSVGPathGeometryFrame(nsIPresShell* aPresShell,
+ nsStyleContext* aContext)
+{
+ return new (aPresShell) nsSVGPathGeometryFrame(aContext);
+}
+
+NS_IMPL_FRAMEARENA_HELPERS(nsSVGPathGeometryFrame)
+
+//----------------------------------------------------------------------
+// nsQueryFrame methods
+
+NS_QUERYFRAME_HEAD(nsSVGPathGeometryFrame)
+ NS_QUERYFRAME_ENTRY(nsISVGChildFrame)
+ NS_QUERYFRAME_ENTRY(nsSVGPathGeometryFrame)
+NS_QUERYFRAME_TAIL_INHERITING(nsFrame)
+
+//----------------------------------------------------------------------
+// Display list item:
+
+class nsDisplaySVGPathGeometry : public nsDisplayItem {
+public:
+ nsDisplaySVGPathGeometry(nsDisplayListBuilder* aBuilder,
+ nsSVGPathGeometryFrame* aFrame)
+ : nsDisplayItem(aBuilder, aFrame)
+ {
+ MOZ_COUNT_CTOR(nsDisplaySVGPathGeometry);
+ MOZ_ASSERT(aFrame, "Must have a frame!");
+ }
+#ifdef NS_BUILD_REFCNT_LOGGING
+ virtual ~nsDisplaySVGPathGeometry() {
+ MOZ_COUNT_DTOR(nsDisplaySVGPathGeometry);
+ }
+#endif
+
+ NS_DISPLAY_DECL_NAME("nsDisplaySVGPathGeometry", TYPE_SVG_PATH_GEOMETRY)
+
+ virtual void HitTest(nsDisplayListBuilder* aBuilder, const nsRect& aRect,
+ HitTestState* aState, nsTArray<nsIFrame*> *aOutFrames) override;
+ virtual void Paint(nsDisplayListBuilder* aBuilder,
+ nsRenderingContext* aCtx) override;
+
+ nsDisplayItemGeometry* AllocateGeometry(nsDisplayListBuilder* aBuilder) override
+ {
+ return new nsDisplayItemGenericImageGeometry(this, aBuilder);
+ }
+
+ void ComputeInvalidationRegion(nsDisplayListBuilder* aBuilder,
+ const nsDisplayItemGeometry* aGeometry,
+ nsRegion *aInvalidRegion) override;
+};
+
+void
+nsDisplaySVGPathGeometry::HitTest(nsDisplayListBuilder* aBuilder, const nsRect& aRect,
+ HitTestState* aState, nsTArray<nsIFrame*> *aOutFrames)
+{
+ nsSVGPathGeometryFrame *frame = static_cast<nsSVGPathGeometryFrame*>(mFrame);
+ nsPoint pointRelativeToReferenceFrame = aRect.Center();
+ // ToReferenceFrame() includes frame->GetPosition(), our user space position.
+ nsPoint userSpacePtInAppUnits = pointRelativeToReferenceFrame -
+ (ToReferenceFrame() - frame->GetPosition());
+ gfxPoint userSpacePt =
+ gfxPoint(userSpacePtInAppUnits.x, userSpacePtInAppUnits.y) /
+ frame->PresContext()->AppUnitsPerCSSPixel();
+ if (frame->GetFrameForPoint(userSpacePt)) {
+ aOutFrames->AppendElement(frame);
+ }
+}
+
+void
+nsDisplaySVGPathGeometry::Paint(nsDisplayListBuilder* aBuilder,
+ nsRenderingContext* aCtx)
+{
+ uint32_t appUnitsPerDevPixel = mFrame->PresContext()->AppUnitsPerDevPixel();
+
+ // ToReferenceFrame includes our mRect offset, but painting takes
+ // account of that too. To avoid double counting, we subtract that
+ // here.
+ nsPoint offset = ToReferenceFrame() - mFrame->GetPosition();
+
+ gfxPoint devPixelOffset =
+ nsLayoutUtils::PointToGfxPoint(offset, appUnitsPerDevPixel);
+
+ gfxMatrix tm = nsSVGIntegrationUtils::GetCSSPxToDevPxMatrix(mFrame) *
+ gfxMatrix::Translation(devPixelOffset);
+ DrawResult result =
+ static_cast<nsSVGPathGeometryFrame*>(mFrame)->PaintSVG(*aCtx->ThebesContext(), tm);
+
+ nsDisplayItemGenericImageGeometry::UpdateDrawResult(this, result);
+}
+
+void
+nsDisplaySVGPathGeometry::ComputeInvalidationRegion(
+ nsDisplayListBuilder* aBuilder,
+ const nsDisplayItemGeometry* aGeometry,
+ nsRegion* aInvalidRegion)
+{
+ auto geometry =
+ static_cast<const nsDisplayItemGenericImageGeometry*>(aGeometry);
+
+ if (aBuilder->ShouldSyncDecodeImages() &&
+ geometry->ShouldInvalidateToSyncDecodeImages()) {
+ bool snap;
+ aInvalidRegion->Or(*aInvalidRegion, GetBounds(aBuilder, &snap));
+ }
+
+ nsDisplayItem::ComputeInvalidationRegion(aBuilder, aGeometry, aInvalidRegion);
+}
+
+//----------------------------------------------------------------------
+// nsIFrame methods
+
+void
+nsSVGPathGeometryFrame::Init(nsIContent* aContent,
+ nsContainerFrame* aParent,
+ nsIFrame* aPrevInFlow)
+{
+ AddStateBits(aParent->GetStateBits() & NS_STATE_SVG_CLIPPATH_CHILD);
+ nsFrame::Init(aContent, aParent, aPrevInFlow);
+}
+
+nsresult
+nsSVGPathGeometryFrame::AttributeChanged(int32_t aNameSpaceID,
+ nsIAtom* aAttribute,
+ int32_t aModType)
+{
+ // We don't invalidate for transform changes (the layers code does that).
+ // Also note that SVGTransformableElement::GetAttributeChangeHint will
+ // return nsChangeHint_UpdateOverflow for "transform" attribute changes
+ // and cause DoApplyRenderingChangeToTree to make the SchedulePaint call.
+
+ if (aNameSpaceID == kNameSpaceID_None &&
+ (static_cast<nsSVGPathGeometryElement*>
+ (mContent)->AttributeDefinesGeometry(aAttribute))) {
+ nsLayoutUtils::PostRestyleEvent(
+ mContent->AsElement(), nsRestyleHint(0),
+ nsChangeHint_InvalidateRenderingObservers);
+ nsSVGUtils::ScheduleReflowSVG(this);
+ }
+ return NS_OK;
+}
+
+/* virtual */ void
+nsSVGPathGeometryFrame::DidSetStyleContext(nsStyleContext* aOldStyleContext)
+{
+ nsFrame::DidSetStyleContext(aOldStyleContext);
+
+ if (aOldStyleContext) {
+ auto oldStyleEffects = aOldStyleContext->PeekStyleEffects();
+ if (oldStyleEffects &&
+ StyleEffects()->mOpacity != oldStyleEffects->mOpacity &&
+ nsSVGUtils::CanOptimizeOpacity(this)) {
+ // nsIFrame::BuildDisplayListForStackingContext() is not going to create an
+ // nsDisplayOpacity display list item, so DLBI won't invalidate for us.
+ InvalidateFrame();
+ }
+
+ nsSVGPathGeometryElement* element =
+ static_cast<nsSVGPathGeometryElement*>(mContent);
+
+ auto oldStyleSVG = aOldStyleContext->PeekStyleSVG();
+ if (oldStyleSVG && !SVGContentUtils::ShapeTypeHasNoCorners(mContent)) {
+ if (StyleSVG()->mStrokeLinecap != oldStyleSVG->mStrokeLinecap &&
+ element->IsSVGElement(nsGkAtoms::path)) {
+ // If the stroke-linecap changes to or from "butt" then our element
+ // needs to update its cached Moz2D Path, since SVGPathData::BuildPath
+ // decides whether or not to insert little lines into the path for zero
+ // length subpaths base on that property.
+ element->ClearAnyCachedPath();
+ } else if (GetStateBits() & NS_STATE_SVG_CLIPPATH_CHILD) {
+ if (StyleSVG()->mClipRule != oldStyleSVG->mClipRule) {
+ // Moz2D Path objects are fill-rule specific.
+ // For clipPath we use clip-rule as the path's fill-rule.
+ element->ClearAnyCachedPath();
+ }
+ } else {
+ if (StyleSVG()->mFillRule != oldStyleSVG->mFillRule) {
+ // Moz2D Path objects are fill-rule specific.
+ element->ClearAnyCachedPath();
+ }
+ }
+ }
+ }
+}
+
+nsIAtom *
+nsSVGPathGeometryFrame::GetType() const
+{
+ return nsGkAtoms::svgPathGeometryFrame;
+}
+
+bool
+nsSVGPathGeometryFrame::IsSVGTransformed(gfx::Matrix *aOwnTransform,
+ gfx::Matrix *aFromParentTransform) const
+{
+ bool foundTransform = false;
+
+ // Check if our parent has children-only transforms:
+ nsIFrame *parent = GetParent();
+ if (parent &&
+ parent->IsFrameOfType(nsIFrame::eSVG | nsIFrame::eSVGContainer)) {
+ foundTransform = static_cast<nsSVGContainerFrame*>(parent)->
+ HasChildrenOnlyTransform(aFromParentTransform);
+ }
+
+ nsSVGElement *content = static_cast<nsSVGElement*>(mContent);
+ nsSVGAnimatedTransformList* transformList =
+ content->GetAnimatedTransformList();
+ if ((transformList && transformList->HasTransform()) ||
+ content->GetAnimateMotionTransform()) {
+ if (aOwnTransform) {
+ *aOwnTransform = gfx::ToMatrix(
+ content->PrependLocalTransformsTo(
+ gfxMatrix(),
+ eUserSpaceToParent));
+ }
+ foundTransform = true;
+ }
+ return foundTransform;
+}
+
+void
+nsSVGPathGeometryFrame::BuildDisplayList(nsDisplayListBuilder* aBuilder,
+ const nsRect& aDirtyRect,
+ const nsDisplayListSet& aLists)
+{
+ if (!static_cast<const nsSVGElement*>(mContent)->HasValidDimensions() ||
+ (!IsVisibleForPainting(aBuilder) && aBuilder->IsForPainting())) {
+ return;
+ }
+ DisplayOutline(aBuilder, aLists);
+ aLists.Content()->AppendNewToTop(
+ new (aBuilder) nsDisplaySVGPathGeometry(aBuilder, this));
+}
+
+//----------------------------------------------------------------------
+// nsISVGChildFrame methods
+
+DrawResult
+nsSVGPathGeometryFrame::PaintSVG(gfxContext& aContext,
+ const gfxMatrix& aTransform,
+ const nsIntRect* aDirtyRect)
+{
+ if (!StyleVisibility()->IsVisible())
+ return DrawResult::SUCCESS;
+
+ gfxMatrix childToUserSpace = aTransform;
+ if (GetContent()->IsSVGElement()) {
+ childToUserSpace = static_cast<const nsSVGElement*>(GetContent())->
+ PrependLocalTransformsTo(childToUserSpace,
+ eChildToUserSpace);
+ }
+
+ // Matrix to the geometry's user space:
+ gfxMatrix newMatrix =
+ aContext.CurrentMatrix().PreMultiply(childToUserSpace).NudgeToIntegers();
+ if (newMatrix.IsSingular()) {
+ return DrawResult::BAD_ARGS;
+ }
+
+ uint32_t paintOrder = StyleSVG()->mPaintOrder;
+ if (paintOrder == NS_STYLE_PAINT_ORDER_NORMAL) {
+ Render(&aContext, eRenderFill | eRenderStroke, newMatrix);
+ PaintMarkers(aContext, aTransform);
+ } else {
+ while (paintOrder) {
+ uint32_t component =
+ paintOrder & ((1 << NS_STYLE_PAINT_ORDER_BITWIDTH) - 1);
+ switch (component) {
+ case NS_STYLE_PAINT_ORDER_FILL:
+ Render(&aContext, eRenderFill, newMatrix);
+ break;
+ case NS_STYLE_PAINT_ORDER_STROKE:
+ Render(&aContext, eRenderStroke, newMatrix);
+ break;
+ case NS_STYLE_PAINT_ORDER_MARKERS:
+ PaintMarkers(aContext, aTransform);
+ break;
+ }
+ paintOrder >>= NS_STYLE_PAINT_ORDER_BITWIDTH;
+ }
+ }
+
+ return DrawResult::SUCCESS;
+}
+
+nsIFrame*
+nsSVGPathGeometryFrame::GetFrameForPoint(const gfxPoint& aPoint)
+{
+ FillRule fillRule;
+ uint16_t hitTestFlags;
+ if (GetStateBits() & NS_STATE_SVG_CLIPPATH_CHILD) {
+ hitTestFlags = SVG_HIT_TEST_FILL;
+ fillRule = nsSVGUtils::ToFillRule(StyleSVG()->mClipRule);
+ } else {
+ hitTestFlags = GetHitTestFlags();
+ if (!hitTestFlags) {
+ return nullptr;
+ }
+ if (hitTestFlags & SVG_HIT_TEST_CHECK_MRECT) {
+ gfxRect rect =
+ nsLayoutUtils::RectToGfxRect(mRect, PresContext()->AppUnitsPerCSSPixel());
+ if (!rect.Contains(aPoint)) {
+ return nullptr;
+ }
+ }
+ fillRule = nsSVGUtils::ToFillRule(StyleSVG()->mFillRule);
+ }
+
+ bool isHit = false;
+
+ nsSVGPathGeometryElement* content =
+ static_cast<nsSVGPathGeometryElement*>(mContent);
+
+ // Using ScreenReferenceDrawTarget() opens us to Moz2D backend specific hit-
+ // testing bugs. Maybe we should use a BackendType::CAIRO DT for hit-testing
+ // so that we get more consistent/backwards compatible results?
+ RefPtr<DrawTarget> drawTarget =
+ gfxPlatform::GetPlatform()->ScreenReferenceDrawTarget();
+ RefPtr<Path> path = content->GetOrBuildPath(*drawTarget, fillRule);
+ if (!path) {
+ return nullptr; // no path, so we don't paint anything that can be hit
+ }
+
+ if (hitTestFlags & SVG_HIT_TEST_FILL) {
+ isHit = path->ContainsPoint(ToPoint(aPoint), Matrix());
+ }
+ if (!isHit && (hitTestFlags & SVG_HIT_TEST_STROKE)) {
+ Point point = ToPoint(aPoint);
+ SVGContentUtils::AutoStrokeOptions stroke;
+ SVGContentUtils::GetStrokeOptions(&stroke, content, StyleContext(), nullptr);
+ gfxMatrix userToOuterSVG;
+ if (nsSVGUtils::GetNonScalingStrokeTransform(this, &userToOuterSVG)) {
+ // We need to transform the path back into the appropriate ancestor
+ // coordinate system in order for non-scaled stroke to be correct.
+ // Naturally we also need to transform the point into the same
+ // coordinate system in order to hit-test against the path.
+ point = ToMatrix(userToOuterSVG).TransformPoint(point);
+ RefPtr<PathBuilder> builder =
+ path->TransformedCopyToBuilder(ToMatrix(userToOuterSVG), fillRule);
+ path = builder->Finish();
+ }
+ isHit = path->StrokeContainsPoint(stroke, point, Matrix());
+ }
+
+ if (isHit && nsSVGUtils::HitTestClip(this, aPoint))
+ return this;
+
+ return nullptr;
+}
+
+nsRect
+nsSVGPathGeometryFrame::GetCoveredRegion()
+{
+ gfxMatrix canvasTM = GetCanvasTM();
+ if (canvasTM.PreservesAxisAlignedRectangles()) {
+ return nsSVGUtils::TransformFrameRectToOuterSVG(
+ mRect, canvasTM, PresContext());
+ }
+
+ // To get tight bounds we need to compute directly in outer SVG coordinates
+ uint32_t flags = nsSVGUtils::eBBoxIncludeFill |
+ nsSVGUtils::eBBoxIncludeStroke |
+ nsSVGUtils::eBBoxIncludeMarkers;
+ gfxRect extent =
+ GetBBoxContribution(ToMatrix(canvasTM), flags).ToThebesRect();
+ nsRect region = nsLayoutUtils::RoundGfxRectToAppRect(
+ extent, PresContext()->AppUnitsPerCSSPixel());
+
+ return nsSVGUtils::TransformFrameRectToOuterSVG(
+ region, gfxMatrix(), PresContext());
+}
+
+void
+nsSVGPathGeometryFrame::ReflowSVG()
+{
+ NS_ASSERTION(nsSVGUtils::OuterSVGIsCallingReflowSVG(this),
+ "This call is probably a wasteful mistake");
+
+ MOZ_ASSERT(!(GetStateBits() & NS_FRAME_IS_NONDISPLAY),
+ "ReflowSVG mechanism not designed for this");
+
+ if (!nsSVGUtils::NeedsReflowSVG(this)) {
+ return;
+ }
+
+ uint32_t flags = nsSVGUtils::eBBoxIncludeFill |
+ nsSVGUtils::eBBoxIncludeStroke |
+ nsSVGUtils::eBBoxIncludeMarkers;
+ // Our "visual" overflow rect needs to be valid for building display lists
+ // for hit testing, which means that for certain values of 'pointer-events'
+ // it needs to include the geometry of the fill or stroke even when the fill/
+ // stroke don't actually render (e.g. when stroke="none" or
+ // stroke-opacity="0"). GetHitTestFlags() accounts for 'pointer-events'.
+ uint16_t hitTestFlags = GetHitTestFlags();
+ if ((hitTestFlags & SVG_HIT_TEST_FILL)) {
+ flags |= nsSVGUtils::eBBoxIncludeFillGeometry;
+ }
+ if ((hitTestFlags & SVG_HIT_TEST_STROKE)) {
+ flags |= nsSVGUtils::eBBoxIncludeStrokeGeometry;
+ }
+
+ gfxRect extent = GetBBoxContribution(Matrix(), flags).ToThebesRect();
+ mRect = nsLayoutUtils::RoundGfxRectToAppRect(extent,
+ PresContext()->AppUnitsPerCSSPixel());
+
+ if (mState & NS_FRAME_FIRST_REFLOW) {
+ // Make sure we have our filter property (if any) before calling
+ // FinishAndStoreOverflow (subsequent filter changes are handled off
+ // nsChangeHint_UpdateEffects):
+ nsSVGEffects::UpdateEffects(this);
+ }
+
+ nsRect overflow = nsRect(nsPoint(0,0), mRect.Size());
+ nsOverflowAreas overflowAreas(overflow, overflow);
+ FinishAndStoreOverflow(overflowAreas, mRect.Size());
+
+ mState &= ~(NS_FRAME_FIRST_REFLOW | NS_FRAME_IS_DIRTY |
+ NS_FRAME_HAS_DIRTY_CHILDREN);
+
+ // Invalidate, but only if this is not our first reflow (since if it is our
+ // first reflow then we haven't had our first paint yet).
+ if (!(GetParent()->GetStateBits() & NS_FRAME_FIRST_REFLOW)) {
+ InvalidateFrame();
+ }
+}
+
+void
+nsSVGPathGeometryFrame::NotifySVGChanged(uint32_t aFlags)
+{
+ MOZ_ASSERT(aFlags & (TRANSFORM_CHANGED | COORD_CONTEXT_CHANGED),
+ "Invalidation logic may need adjusting");
+
+ // Changes to our ancestors may affect how we render when we are rendered as
+ // part of our ancestor (specifically, if our coordinate context changes size
+ // and we have percentage lengths defining our geometry, then we need to be
+ // reflowed). However, ancestor changes cannot affect how we render when we
+ // are rendered as part of any rendering observers that we may have.
+ // Therefore no need to notify rendering observers here.
+
+ // Don't try to be too smart trying to avoid the ScheduleReflowSVG calls
+ // for the stroke properties examined below. Checking HasStroke() is not
+ // enough, since what we care about is whether we include the stroke in our
+ // overflow rects or not, and we sometimes deliberately include stroke
+ // when it's not visible. See the complexities of GetBBoxContribution.
+
+ if (aFlags & COORD_CONTEXT_CHANGED) {
+ // Stroke currently contributes to our mRect, which is why we have to take
+ // account of stroke-width here. Note that we do not need to take account
+ // of stroke-dashoffset since, although that can have a percentage value
+ // that is resolved against our coordinate context, it does not affect our
+ // mRect.
+ if (static_cast<nsSVGPathGeometryElement*>(mContent)->GeometryDependsOnCoordCtx() ||
+ StyleSVG()->mStrokeWidth.HasPercent()) {
+ static_cast<nsSVGPathGeometryElement*>(mContent)->ClearAnyCachedPath();
+ nsSVGUtils::ScheduleReflowSVG(this);
+ }
+ }
+
+ if ((aFlags & TRANSFORM_CHANGED) && StyleSVGReset()->HasNonScalingStroke()) {
+ // Stroke currently contributes to our mRect, and our stroke depends on
+ // the transform to our outer-<svg> if |vector-effect:non-scaling-stroke|.
+ nsSVGUtils::ScheduleReflowSVG(this);
+ }
+}
+
+SVGBBox
+nsSVGPathGeometryFrame::GetBBoxContribution(const Matrix &aToBBoxUserspace,
+ uint32_t aFlags)
+{
+ SVGBBox bbox;
+
+ if (aToBBoxUserspace.IsSingular()) {
+ // XXX ReportToConsole
+ return bbox;
+ }
+
+ nsSVGPathGeometryElement* element =
+ static_cast<nsSVGPathGeometryElement*>(mContent);
+
+ bool getFill = (aFlags & nsSVGUtils::eBBoxIncludeFillGeometry) ||
+ ((aFlags & nsSVGUtils::eBBoxIncludeFill) &&
+ StyleSVG()->mFill.Type() != eStyleSVGPaintType_None);
+
+ bool getStroke = (aFlags & nsSVGUtils::eBBoxIncludeStrokeGeometry) ||
+ ((aFlags & nsSVGUtils::eBBoxIncludeStroke) &&
+ nsSVGUtils::HasStroke(this));
+
+ SVGContentUtils::AutoStrokeOptions strokeOptions;
+ if (getStroke) {
+ SVGContentUtils::GetStrokeOptions(&strokeOptions, element,
+ StyleContext(), nullptr,
+ SVGContentUtils::eIgnoreStrokeDashing);
+ } else {
+ // Override the default line width of 1.f so that when we call
+ // GetGeometryBounds below the result doesn't include stroke bounds.
+ strokeOptions.mLineWidth = 0.f;
+ }
+
+ Rect simpleBounds;
+ bool gotSimpleBounds = false;
+ gfxMatrix userToOuterSVG;
+ if (getStroke &&
+ nsSVGUtils::GetNonScalingStrokeTransform(this, &userToOuterSVG)) {
+ Matrix moz2dUserToOuterSVG = ToMatrix(userToOuterSVG);
+ if (moz2dUserToOuterSVG.IsSingular()) {
+ return bbox;
+ }
+ gotSimpleBounds = element->GetGeometryBounds(&simpleBounds,
+ strokeOptions,
+ aToBBoxUserspace,
+ &moz2dUserToOuterSVG);
+ } else {
+ gotSimpleBounds = element->GetGeometryBounds(&simpleBounds,
+ strokeOptions,
+ aToBBoxUserspace);
+ }
+
+ if (gotSimpleBounds) {
+ bbox = simpleBounds;
+ } else {
+ // Get the bounds using a Moz2D Path object (more expensive):
+ RefPtr<DrawTarget> tmpDT;
+#ifdef XP_WIN
+ // Unfortunately D2D backed DrawTarget produces bounds with rounding errors
+ // when whole number results are expected, even in the case of trivial
+ // calculations. To avoid that and meet the expectations of web content we
+ // have to use a CAIRO DrawTarget. The most efficient way to do that is to
+ // wrap the cached cairo_surface_t from ScreenReferenceSurface():
+ RefPtr<gfxASurface> refSurf =
+ gfxPlatform::GetPlatform()->ScreenReferenceSurface();
+ tmpDT = gfxPlatform::GetPlatform()->
+ CreateDrawTargetForSurface(refSurf, IntSize(1, 1));
+#else
+ tmpDT = gfxPlatform::GetPlatform()->ScreenReferenceDrawTarget();
+#endif
+
+ FillRule fillRule = nsSVGUtils::ToFillRule(StyleSVG()->mFillRule);
+ RefPtr<Path> pathInUserSpace = element->GetOrBuildPath(*tmpDT, fillRule);
+ if (!pathInUserSpace) {
+ return bbox;
+ }
+ RefPtr<Path> pathInBBoxSpace;
+ if (aToBBoxUserspace.IsIdentity()) {
+ pathInBBoxSpace = pathInUserSpace;
+ } else {
+ RefPtr<PathBuilder> builder =
+ pathInUserSpace->TransformedCopyToBuilder(aToBBoxUserspace, fillRule);
+ pathInBBoxSpace = builder->Finish();
+ if (!pathInBBoxSpace) {
+ return bbox;
+ }
+ }
+
+ // Be careful when replacing the following logic to get the fill and stroke
+ // extents independently (instead of computing the stroke extents from the
+ // path extents). You may think that you can just use the stroke extents if
+ // there is both a fill and a stroke. In reality it's necessary to
+ // calculate both the fill and stroke extents, and take the union of the
+ // two. There are two reasons for this:
+ //
+ // # Due to stroke dashing, in certain cases the fill extents could
+ // actually extend outside the stroke extents.
+ // # If the stroke is very thin, cairo won't paint any stroke, and so the
+ // stroke bounds that it will return will be empty.
+
+ Rect pathBBoxExtents = pathInBBoxSpace->GetBounds();
+ if (!pathBBoxExtents.IsFinite()) {
+ // This can happen in the case that we only have a move-to command in the
+ // path commands, in which case we know nothing gets rendered.
+ return bbox;
+ }
+
+ // Account for fill:
+ if (getFill) {
+ bbox = pathBBoxExtents;
+ }
+
+ // Account for stroke:
+ if (getStroke) {
+#if 0
+ // This disabled code is how we would calculate the stroke bounds using
+ // Moz2D Path::GetStrokedBounds(). Unfortunately at the time of writing
+ // it there are two problems that prevent us from using it.
+ //
+ // First, it seems that some of the Moz2D backends are really dumb. Not
+ // only do some GetStrokeOptions() implementations sometimes
+ // significantly overestimate the stroke bounds, but if an argument is
+ // passed for the aTransform parameter then they just return bounds-of-
+ // transformed-bounds. These two things combined can lead the bounds to
+ // be unacceptably oversized, leading to massive over-invalidation.
+ //
+ // Second, the way we account for non-scaling-stroke by transforming the
+ // path using the transform to the outer-<svg> element is not compatible
+ // with the way that nsSVGPathGeometryFrame::Reflow() inserts a scale
+ // into aToBBoxUserspace and then scales the bounds that we return.
+ SVGContentUtils::AutoStrokeOptions strokeOptions;
+ SVGContentUtils::GetStrokeOptions(&strokeOptions, element,
+ StyleContext(), nullptr,
+ SVGContentUtils::eIgnoreStrokeDashing);
+ Rect strokeBBoxExtents;
+ gfxMatrix userToOuterSVG;
+ if (nsSVGUtils::GetNonScalingStrokeTransform(this, &userToOuterSVG)) {
+ Matrix outerSVGToUser = ToMatrix(userToOuterSVG);
+ outerSVGToUser.Invert();
+ Matrix outerSVGToBBox = aToBBoxUserspace * outerSVGToUser;
+ RefPtr<PathBuilder> builder =
+ pathInUserSpace->TransformedCopyToBuilder(ToMatrix(userToOuterSVG));
+ RefPtr<Path> pathInOuterSVGSpace = builder->Finish();
+ strokeBBoxExtents =
+ pathInOuterSVGSpace->GetStrokedBounds(strokeOptions, outerSVGToBBox);
+ } else {
+ strokeBBoxExtents =
+ pathInUserSpace->GetStrokedBounds(strokeOptions, aToBBoxUserspace);
+ }
+ MOZ_ASSERT(strokeBBoxExtents.IsFinite(), "bbox is about to go bad");
+ bbox.UnionEdges(strokeBBoxExtents);
+#else
+ // For now we just use nsSVGUtils::PathExtentsToMaxStrokeExtents:
+ gfxRect strokeBBoxExtents =
+ nsSVGUtils::PathExtentsToMaxStrokeExtents(ThebesRect(pathBBoxExtents),
+ this,
+ ThebesMatrix(aToBBoxUserspace));
+ MOZ_ASSERT(ToRect(strokeBBoxExtents).IsFinite(), "bbox is about to go bad");
+ bbox.UnionEdges(strokeBBoxExtents);
+#endif
+ }
+ }
+
+ // Account for markers:
+ if ((aFlags & nsSVGUtils::eBBoxIncludeMarkers) != 0 &&
+ static_cast<nsSVGPathGeometryElement*>(mContent)->IsMarkable()) {
+
+ float strokeWidth = nsSVGUtils::GetStrokeWidth(this);
+ MarkerProperties properties = GetMarkerProperties(this);
+
+ if (properties.MarkersExist()) {
+ nsTArray<nsSVGMark> marks;
+ static_cast<nsSVGPathGeometryElement*>(mContent)->GetMarkPoints(&marks);
+ uint32_t num = marks.Length();
+
+ // These are in the same order as the nsSVGMark::Type constants.
+ nsSVGMarkerFrame* markerFrames[] = {
+ properties.GetMarkerStartFrame(),
+ properties.GetMarkerMidFrame(),
+ properties.GetMarkerEndFrame(),
+ };
+ static_assert(MOZ_ARRAY_LENGTH(markerFrames) == nsSVGMark::eTypeCount,
+ "Number of Marker frames should be equal to eTypeCount");
+
+ for (uint32_t i = 0; i < num; i++) {
+ nsSVGMark& mark = marks[i];
+ nsSVGMarkerFrame* frame = markerFrames[mark.type];
+ if (frame) {
+ SVGBBox mbbox =
+ frame->GetMarkBBoxContribution(aToBBoxUserspace, aFlags, this,
+ &marks[i], strokeWidth);
+ MOZ_ASSERT(mbbox.IsFinite(), "bbox is about to go bad");
+ bbox.UnionEdges(mbbox);
+ }
+ }
+ }
+ }
+
+ return bbox;
+}
+
+//----------------------------------------------------------------------
+// nsSVGPathGeometryFrame methods:
+
+gfxMatrix
+nsSVGPathGeometryFrame::GetCanvasTM()
+{
+ NS_ASSERTION(GetParent(), "null parent");
+
+ nsSVGContainerFrame *parent = static_cast<nsSVGContainerFrame*>(GetParent());
+ dom::SVGGraphicsElement *content = static_cast<dom::SVGGraphicsElement*>(mContent);
+
+ return content->PrependLocalTransformsTo(parent->GetCanvasTM());
+}
+
+nsSVGPathGeometryFrame::MarkerProperties
+nsSVGPathGeometryFrame::GetMarkerProperties(nsSVGPathGeometryFrame *aFrame)
+{
+ NS_ASSERTION(!aFrame->GetPrevContinuation(), "aFrame should be first continuation");
+
+ MarkerProperties result;
+ nsCOMPtr<nsIURI> markerURL =
+ nsSVGEffects::GetMarkerURI(aFrame, &nsStyleSVG::mMarkerStart);
+ result.mMarkerStart =
+ nsSVGEffects::GetMarkerProperty(markerURL, aFrame,
+ nsSVGEffects::MarkerBeginProperty());
+
+ markerURL = nsSVGEffects::GetMarkerURI(aFrame, &nsStyleSVG::mMarkerMid);
+ result.mMarkerMid =
+ nsSVGEffects::GetMarkerProperty(markerURL, aFrame,
+ nsSVGEffects::MarkerMiddleProperty());
+
+ markerURL = nsSVGEffects::GetMarkerURI(aFrame, &nsStyleSVG::mMarkerEnd);
+ result.mMarkerEnd =
+ nsSVGEffects::GetMarkerProperty(markerURL, aFrame,
+ nsSVGEffects::MarkerEndProperty());
+ return result;
+}
+
+nsSVGMarkerFrame *
+nsSVGPathGeometryFrame::MarkerProperties::GetMarkerStartFrame()
+{
+ if (!mMarkerStart)
+ return nullptr;
+ return static_cast<nsSVGMarkerFrame *>
+ (mMarkerStart->GetReferencedFrame(nsGkAtoms::svgMarkerFrame, nullptr));
+}
+
+nsSVGMarkerFrame *
+nsSVGPathGeometryFrame::MarkerProperties::GetMarkerMidFrame()
+{
+ if (!mMarkerMid)
+ return nullptr;
+ return static_cast<nsSVGMarkerFrame *>
+ (mMarkerMid->GetReferencedFrame(nsGkAtoms::svgMarkerFrame, nullptr));
+}
+
+nsSVGMarkerFrame *
+nsSVGPathGeometryFrame::MarkerProperties::GetMarkerEndFrame()
+{
+ if (!mMarkerEnd)
+ return nullptr;
+ return static_cast<nsSVGMarkerFrame *>
+ (mMarkerEnd->GetReferencedFrame(nsGkAtoms::svgMarkerFrame, nullptr));
+}
+
+void
+nsSVGPathGeometryFrame::Render(gfxContext* aContext,
+ uint32_t aRenderComponents,
+ const gfxMatrix& aNewTransform)
+{
+ MOZ_ASSERT(!aNewTransform.IsSingular());
+
+ DrawTarget* drawTarget = aContext->GetDrawTarget();
+
+ FillRule fillRule =
+ nsSVGUtils::ToFillRule((GetStateBits() & NS_STATE_SVG_CLIPPATH_CHILD) ?
+ StyleSVG()->mClipRule : StyleSVG()->mFillRule);
+
+ nsSVGPathGeometryElement* element =
+ static_cast<nsSVGPathGeometryElement*>(mContent);
+
+ AntialiasMode aaMode =
+ (StyleSVG()->mShapeRendering == NS_STYLE_SHAPE_RENDERING_OPTIMIZESPEED ||
+ StyleSVG()->mShapeRendering == NS_STYLE_SHAPE_RENDERING_CRISPEDGES) ?
+ AntialiasMode::NONE : AntialiasMode::SUBPIXEL;
+
+ // We wait as late as possible before setting the transform so that we don't
+ // set it unnecessarily if we return early (it's an expensive operation for
+ // some backends).
+ gfxContextMatrixAutoSaveRestore autoRestoreTransform(aContext);
+ aContext->SetMatrix(aNewTransform);
+
+ if (GetStateBits() & NS_STATE_SVG_CLIPPATH_CHILD) {
+ // We don't complicate this code with GetAsSimplePath since the cost of
+ // masking will dwarf Path creation overhead anyway.
+ RefPtr<Path> path = element->GetOrBuildPath(*drawTarget, fillRule);
+ if (path) {
+ ColorPattern white(ToDeviceColor(Color(1.0f, 1.0f, 1.0f, 1.0f)));
+ drawTarget->Fill(path, white,
+ DrawOptions(1.0f, CompositionOp::OP_OVER, aaMode));
+ }
+ return;
+ }
+
+ nsSVGPathGeometryElement::SimplePath simplePath;
+ RefPtr<Path> path;
+
+ element->GetAsSimplePath(&simplePath);
+ if (!simplePath.IsPath()) {
+ path = element->GetOrBuildPath(*drawTarget, fillRule);
+ if (!path) {
+ return;
+ }
+ }
+
+ SVGContextPaint* contextPaint = SVGContextPaint::GetContextPaint(mContent);
+
+ if (aRenderComponents & eRenderFill) {
+ GeneralPattern fillPattern;
+ nsSVGUtils::MakeFillPatternFor(this, aContext, &fillPattern, contextPaint);
+ if (fillPattern.GetPattern()) {
+ DrawOptions drawOptions(1.0f, CompositionOp::OP_OVER, aaMode);
+ if (simplePath.IsRect()) {
+ drawTarget->FillRect(simplePath.AsRect(), fillPattern, drawOptions);
+ } else if (path) {
+ drawTarget->Fill(path, fillPattern, drawOptions);
+ }
+ }
+ }
+
+ if ((aRenderComponents & eRenderStroke) &&
+ nsSVGUtils::HasStroke(this, contextPaint)) {
+ // Account for vector-effect:non-scaling-stroke:
+ gfxMatrix userToOuterSVG;
+ if (nsSVGUtils::GetNonScalingStrokeTransform(this, &userToOuterSVG)) {
+ // A simple Rect can't be transformed with rotate/skew, so let's switch
+ // to using a real path:
+ if (!path) {
+ path = element->GetOrBuildPath(*drawTarget, fillRule);
+ if (!path) {
+ return;
+ }
+ simplePath.Reset();
+ }
+ // We need to transform the path back into the appropriate ancestor
+ // coordinate system, and paint it it that coordinate system, in order
+ // for non-scaled stroke to paint correctly.
+ gfxMatrix outerSVGToUser = userToOuterSVG;
+ outerSVGToUser.Invert();
+ aContext->Multiply(outerSVGToUser);
+ RefPtr<PathBuilder> builder =
+ path->TransformedCopyToBuilder(ToMatrix(userToOuterSVG), fillRule);
+ path = builder->Finish();
+ }
+ GeneralPattern strokePattern;
+ nsSVGUtils::MakeStrokePatternFor(this, aContext, &strokePattern, contextPaint);
+ if (strokePattern.GetPattern()) {
+ SVGContentUtils::AutoStrokeOptions strokeOptions;
+ SVGContentUtils::GetStrokeOptions(&strokeOptions,
+ static_cast<nsSVGElement*>(mContent),
+ StyleContext(), contextPaint);
+ // GetStrokeOptions may set the line width to zero as an optimization
+ if (strokeOptions.mLineWidth <= 0) {
+ return;
+ }
+ DrawOptions drawOptions(1.0f, CompositionOp::OP_OVER, aaMode);
+ if (simplePath.IsRect()) {
+ drawTarget->StrokeRect(simplePath.AsRect(), strokePattern,
+ strokeOptions, drawOptions);
+ } else if (simplePath.IsLine()) {
+ drawTarget->StrokeLine(simplePath.Point1(), simplePath.Point2(),
+ strokePattern, strokeOptions, drawOptions);
+ } else {
+ drawTarget->Stroke(path, strokePattern, strokeOptions, drawOptions);
+ }
+ }
+ }
+}
+
+void
+nsSVGPathGeometryFrame::PaintMarkers(gfxContext& aContext,
+ const gfxMatrix& aTransform)
+{
+ SVGContextPaint* contextPaint = SVGContextPaint::GetContextPaint(mContent);
+
+ if (static_cast<nsSVGPathGeometryElement*>(mContent)->IsMarkable()) {
+ MarkerProperties properties = GetMarkerProperties(this);
+
+ if (properties.MarkersExist()) {
+ float strokeWidth = nsSVGUtils::GetStrokeWidth(this, contextPaint);
+
+ nsTArray<nsSVGMark> marks;
+ static_cast<nsSVGPathGeometryElement*>
+ (mContent)->GetMarkPoints(&marks);
+
+ uint32_t num = marks.Length();
+ if (num) {
+ // These are in the same order as the nsSVGMark::Type constants.
+ nsSVGMarkerFrame* markerFrames[] = {
+ properties.GetMarkerStartFrame(),
+ properties.GetMarkerMidFrame(),
+ properties.GetMarkerEndFrame(),
+ };
+ static_assert(MOZ_ARRAY_LENGTH(markerFrames) == nsSVGMark::eTypeCount,
+ "Number of Marker frames should be equal to eTypeCount");
+
+ for (uint32_t i = 0; i < num; i++) {
+ nsSVGMark& mark = marks[i];
+ nsSVGMarkerFrame* frame = markerFrames[mark.type];
+ if (frame) {
+ frame->PaintMark(aContext, aTransform, this, &mark, strokeWidth);
+ }
+ }
+ }
+ }
+ }
+}
+
+uint16_t
+nsSVGPathGeometryFrame::GetHitTestFlags()
+{
+ return nsSVGUtils::GetGeometryHitTestFlags(this);
+}
diff --git a/layout/svg/nsSVGPathGeometryFrame.h b/layout/svg/nsSVGPathGeometryFrame.h
new file mode 100644
index 0000000000..6b7c75d97c
--- /dev/null
+++ b/layout/svg/nsSVGPathGeometryFrame.h
@@ -0,0 +1,147 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef __NS_SVGPATHGEOMETRYFRAME_H__
+#define __NS_SVGPATHGEOMETRYFRAME_H__
+
+#include "mozilla/Attributes.h"
+#include "gfxMatrix.h"
+#include "gfxRect.h"
+#include "nsFrame.h"
+#include "nsISVGChildFrame.h"
+#include "nsLiteralString.h"
+#include "nsQueryFrame.h"
+#include "nsSVGUtils.h"
+
+namespace mozilla {
+namespace gfx {
+class DrawTarget;
+} // namespace gfx
+} // namespace mozilla
+
+class gfxContext;
+class nsDisplaySVGPathGeometry;
+class nsIAtom;
+class nsIFrame;
+class nsIPresShell;
+class nsStyleContext;
+class nsSVGMarkerFrame;
+class nsSVGMarkerProperty;
+
+struct nsRect;
+
+class nsSVGPathGeometryFrame : public nsFrame
+ , public nsISVGChildFrame
+{
+ typedef mozilla::gfx::DrawTarget DrawTarget;
+
+ friend nsIFrame*
+ NS_NewSVGPathGeometryFrame(nsIPresShell* aPresShell, nsStyleContext* aContext);
+
+ friend class nsDisplaySVGPathGeometry;
+
+protected:
+ explicit nsSVGPathGeometryFrame(nsStyleContext* aContext)
+ : nsFrame(aContext)
+ {
+ AddStateBits(NS_FRAME_SVG_LAYOUT | NS_FRAME_MAY_BE_TRANSFORMED);
+ }
+
+public:
+ NS_DECL_QUERYFRAME_TARGET(nsSVGPathGeometryFrame)
+ NS_DECL_QUERYFRAME
+ NS_DECL_FRAMEARENA_HELPERS
+
+ // nsIFrame interface:
+ virtual void Init(nsIContent* aContent,
+ nsContainerFrame* aParent,
+ nsIFrame* aPrevInFlow) override;
+
+ virtual bool IsFrameOfType(uint32_t aFlags) const override
+ {
+ return nsFrame::IsFrameOfType(aFlags & ~(nsIFrame::eSVG | nsIFrame::eSVGGeometry));
+ }
+
+ virtual nsresult AttributeChanged(int32_t aNameSpaceID,
+ nsIAtom* aAttribute,
+ int32_t aModType) override;
+
+ virtual void DidSetStyleContext(nsStyleContext* aOldStyleContext) override;
+
+ /**
+ * Get the "type" of the frame
+ *
+ * @see nsGkAtoms::svgPathGeometryFrame
+ */
+ virtual nsIAtom* GetType() const override;
+
+ virtual bool IsSVGTransformed(Matrix *aOwnTransforms = nullptr,
+ Matrix *aFromParentTransforms = nullptr) const override;
+
+#ifdef DEBUG_FRAME_DUMP
+ virtual nsresult GetFrameName(nsAString& aResult) const override
+ {
+ return MakeFrameName(NS_LITERAL_STRING("SVGPathGeometry"), aResult);
+ }
+#endif
+
+ virtual void BuildDisplayList(nsDisplayListBuilder* aBuilder,
+ const nsRect& aDirtyRect,
+ const nsDisplayListSet& aLists) override;
+
+ // nsSVGPathGeometryFrame methods
+ gfxMatrix GetCanvasTM();
+protected:
+ // nsISVGChildFrame interface:
+ virtual DrawResult PaintSVG(gfxContext& aContext,
+ const gfxMatrix& aTransform,
+ const nsIntRect* aDirtyRect = nullptr) override;
+ virtual nsIFrame* GetFrameForPoint(const gfxPoint& aPoint) override;
+ virtual nsRect GetCoveredRegion() override;
+ virtual void ReflowSVG() override;
+ virtual void NotifySVGChanged(uint32_t aFlags) override;
+ virtual SVGBBox GetBBoxContribution(const Matrix &aToBBoxUserspace,
+ uint32_t aFlags) override;
+ virtual bool IsDisplayContainer() override { return false; }
+
+ /**
+ * This function returns a set of bit flags indicating which parts of the
+ * element (fill, stroke, bounds) should intercept pointer events. It takes
+ * into account the type of element and the value of the 'pointer-events'
+ * property on the element.
+ */
+ virtual uint16_t GetHitTestFlags();
+private:
+ enum { eRenderFill = 1, eRenderStroke = 2 };
+ void Render(gfxContext* aContext, uint32_t aRenderComponents,
+ const gfxMatrix& aTransform);
+
+ /**
+ * @param aMatrix The transform that must be multiplied onto aContext to
+ * establish this frame's SVG user space.
+ */
+ void PaintMarkers(gfxContext& aContext, const gfxMatrix& aMatrix);
+
+ struct MarkerProperties {
+ nsSVGMarkerProperty* mMarkerStart;
+ nsSVGMarkerProperty* mMarkerMid;
+ nsSVGMarkerProperty* mMarkerEnd;
+
+ bool MarkersExist() const {
+ return mMarkerStart || mMarkerMid || mMarkerEnd;
+ }
+
+ nsSVGMarkerFrame *GetMarkerStartFrame();
+ nsSVGMarkerFrame *GetMarkerMidFrame();
+ nsSVGMarkerFrame *GetMarkerEndFrame();
+ };
+
+ /**
+ * @param aFrame should be the first continuation
+ */
+ static MarkerProperties GetMarkerProperties(nsSVGPathGeometryFrame *aFrame);
+};
+
+#endif // __NS_SVGPATHGEOMETRYFRAME_H__
diff --git a/layout/svg/nsSVGPatternFrame.cpp b/layout/svg/nsSVGPatternFrame.cpp
new file mode 100644
index 0000000000..198163d7f5
--- /dev/null
+++ b/layout/svg/nsSVGPatternFrame.cpp
@@ -0,0 +1,748 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+// Main header first:
+#include "nsSVGPatternFrame.h"
+
+// Keep others in (case-insensitive) order:
+#include "gfx2DGlue.h"
+#include "gfxContext.h"
+#include "gfxMatrix.h"
+#include "gfxPattern.h"
+#include "gfxPlatform.h"
+#include "mozilla/gfx/2D.h"
+#include "nsContentUtils.h"
+#include "nsGkAtoms.h"
+#include "nsISVGChildFrame.h"
+#include "nsStyleContext.h"
+#include "nsSVGEffects.h"
+#include "nsSVGPathGeometryFrame.h"
+#include "mozilla/dom/SVGPatternElement.h"
+#include "nsSVGUtils.h"
+#include "nsSVGAnimatedTransformList.h"
+#include "SVGContentUtils.h"
+
+using namespace mozilla;
+using namespace mozilla::dom;
+using namespace mozilla::gfx;
+
+//----------------------------------------------------------------------
+// Helper classes
+
+class MOZ_RAII nsSVGPatternFrame::AutoPatternReferencer
+{
+public:
+ explicit AutoPatternReferencer(nsSVGPatternFrame *aFrame
+ MOZ_GUARD_OBJECT_NOTIFIER_PARAM)
+ : mFrame(aFrame)
+ {
+ MOZ_GUARD_OBJECT_NOTIFIER_INIT;
+ // Reference loops should normally be detected in advance and handled, so
+ // we're not expecting to encounter them here
+ MOZ_ASSERT(!mFrame->mLoopFlag, "Undetected reference loop!");
+ mFrame->mLoopFlag = true;
+ }
+ ~AutoPatternReferencer() {
+ mFrame->mLoopFlag = false;
+ }
+private:
+ nsSVGPatternFrame *mFrame;
+ MOZ_DECL_USE_GUARD_OBJECT_NOTIFIER
+};
+
+//----------------------------------------------------------------------
+// Implementation
+
+nsSVGPatternFrame::nsSVGPatternFrame(nsStyleContext* aContext)
+ : nsSVGPaintServerFrame(aContext)
+ , mLoopFlag(false)
+ , mNoHRefURI(false)
+{
+}
+
+NS_IMPL_FRAMEARENA_HELPERS(nsSVGPatternFrame)
+
+//----------------------------------------------------------------------
+// nsIFrame methods:
+
+nsresult
+nsSVGPatternFrame::AttributeChanged(int32_t aNameSpaceID,
+ nsIAtom* aAttribute,
+ int32_t aModType)
+{
+ if (aNameSpaceID == kNameSpaceID_None &&
+ (aAttribute == nsGkAtoms::patternUnits ||
+ aAttribute == nsGkAtoms::patternContentUnits ||
+ aAttribute == nsGkAtoms::patternTransform ||
+ aAttribute == nsGkAtoms::x ||
+ aAttribute == nsGkAtoms::y ||
+ aAttribute == nsGkAtoms::width ||
+ aAttribute == nsGkAtoms::height ||
+ aAttribute == nsGkAtoms::preserveAspectRatio ||
+ aAttribute == nsGkAtoms::viewBox)) {
+ nsSVGEffects::InvalidateDirectRenderingObservers(this);
+ }
+
+ if ((aNameSpaceID == kNameSpaceID_XLink ||
+ aNameSpaceID == kNameSpaceID_None) &&
+ aAttribute == nsGkAtoms::href) {
+ // Blow away our reference, if any
+ Properties().Delete(nsSVGEffects::HrefAsPaintingProperty());
+ mNoHRefURI = false;
+ // And update whoever references us
+ nsSVGEffects::InvalidateDirectRenderingObservers(this);
+ }
+
+ return nsSVGPaintServerFrame::AttributeChanged(aNameSpaceID,
+ aAttribute, aModType);
+}
+
+#ifdef DEBUG
+void
+nsSVGPatternFrame::Init(nsIContent* aContent,
+ nsContainerFrame* aParent,
+ nsIFrame* aPrevInFlow)
+{
+ NS_ASSERTION(aContent->IsSVGElement(nsGkAtoms::pattern), "Content is not an SVG pattern");
+
+ nsSVGPaintServerFrame::Init(aContent, aParent, aPrevInFlow);
+}
+#endif /* DEBUG */
+
+nsIAtom*
+nsSVGPatternFrame::GetType() const
+{
+ return nsGkAtoms::svgPatternFrame;
+}
+
+//----------------------------------------------------------------------
+// nsSVGContainerFrame methods:
+
+// If our GetCanvasTM is getting called, we
+// need to return *our current* transformation
+// matrix, which depends on our units parameters
+// and X, Y, Width, and Height
+gfxMatrix
+nsSVGPatternFrame::GetCanvasTM()
+{
+ if (mCTM) {
+ return *mCTM;
+ }
+
+ // Do we know our rendering parent?
+ if (mSource) {
+ // Yes, use it!
+ return mSource->GetCanvasTM();
+ }
+
+ // We get here when geometry in the <pattern> container is updated
+ return gfxMatrix();
+}
+
+// -------------------------------------------------------------------------
+// Helper functions
+// -------------------------------------------------------------------------
+
+/** Calculate the maximum expansion of a matrix */
+static float
+MaxExpansion(const Matrix &aMatrix)
+{
+ // maximum expansion derivation from
+ // http://lists.cairographics.org/archives/cairo/2004-October/001980.html
+ // and also implemented in cairo_matrix_transformed_circle_major_axis
+ double a = aMatrix._11;
+ double b = aMatrix._12;
+ double c = aMatrix._21;
+ double d = aMatrix._22;
+ double f = (a * a + b * b + c * c + d * d) / 2;
+ double g = (a * a + b * b - c * c - d * d) / 2;
+ double h = a * c + b * d;
+ return sqrt(f + sqrt(g * g + h * h));
+}
+
+// The SVG specification says that the 'patternContentUnits' attribute "has no effect if
+// attribute ‘viewBox’ is specified". We still need to include a bbox scale
+// if the viewBox is specified and _patternUnits_ is set to or defaults to
+// objectBoundingBox though, since in that case the viewBox is relative to the bbox
+static bool
+IncludeBBoxScale(const nsSVGViewBox& aViewBox,
+ uint32_t aPatternContentUnits, uint32_t aPatternUnits)
+{
+ return (!aViewBox.IsExplicitlySet() &&
+ aPatternContentUnits == SVG_UNIT_TYPE_OBJECTBOUNDINGBOX) ||
+ (aViewBox.IsExplicitlySet() &&
+ aPatternUnits == SVG_UNIT_TYPE_OBJECTBOUNDINGBOX);
+}
+
+// Given the matrix for the pattern element's own transform, this returns a
+// combined matrix including the transforms applicable to its target.
+static Matrix
+GetPatternMatrix(uint16_t aPatternUnits,
+ const Matrix &patternTransform,
+ const gfxRect &bbox,
+ const gfxRect &callerBBox,
+ const Matrix &callerCTM)
+{
+ // We really want the pattern matrix to handle translations
+ gfxFloat minx = bbox.X();
+ gfxFloat miny = bbox.Y();
+
+ if (aPatternUnits == SVG_UNIT_TYPE_OBJECTBOUNDINGBOX) {
+ minx += callerBBox.X();
+ miny += callerBBox.Y();
+ }
+
+ float scale = 1.0f / MaxExpansion(callerCTM);
+ Matrix patternMatrix = patternTransform;
+ patternMatrix.PreScale(scale, scale);
+ patternMatrix.PreTranslate(minx, miny);
+
+ return patternMatrix;
+}
+
+static nsresult
+GetTargetGeometry(gfxRect *aBBox,
+ const nsSVGViewBox &aViewBox,
+ uint16_t aPatternContentUnits,
+ uint16_t aPatternUnits,
+ nsIFrame *aTarget,
+ const Matrix &aContextMatrix,
+ const gfxRect *aOverrideBounds)
+{
+ *aBBox = aOverrideBounds ? *aOverrideBounds : nsSVGUtils::GetBBox(aTarget);
+
+ // Sanity check
+ if (IncludeBBoxScale(aViewBox, aPatternContentUnits, aPatternUnits) &&
+ (aBBox->Width() <= 0 || aBBox->Height() <= 0)) {
+ return NS_ERROR_FAILURE;
+ }
+
+ // OK, now fix up the bounding box to reflect user coordinates
+ // We handle device unit scaling in pattern matrix
+ float scale = MaxExpansion(aContextMatrix);
+ if (scale <= 0) {
+ return NS_ERROR_FAILURE;
+ }
+ aBBox->Scale(scale);
+ return NS_OK;
+}
+
+already_AddRefed<SourceSurface>
+nsSVGPatternFrame::PaintPattern(const DrawTarget* aDrawTarget,
+ Matrix* patternMatrix,
+ const Matrix &aContextMatrix,
+ nsIFrame *aSource,
+ nsStyleSVGPaint nsStyleSVG::*aFillOrStroke,
+ float aGraphicOpacity,
+ const gfxRect *aOverrideBounds)
+{
+ /*
+ * General approach:
+ * Set the content geometry stuff
+ * Calculate our bbox (using x,y,width,height & patternUnits &
+ * patternTransform)
+ * Create the surface
+ * Calculate the content transformation matrix
+ * Get our children (we may need to get them from another Pattern)
+ * Call SVGPaint on all of our children
+ * Return
+ */
+
+ nsSVGPatternFrame* patternWithChildren = GetPatternWithChildren();
+ if (!patternWithChildren) {
+ return nullptr; // Either no kids or a bad reference
+ }
+ nsIFrame* firstKid = patternWithChildren->mFrames.FirstChild();
+
+ const nsSVGViewBox& viewBox = GetViewBox();
+
+ uint16_t patternContentUnits =
+ GetEnumValue(SVGPatternElement::PATTERNCONTENTUNITS);
+ uint16_t patternUnits =
+ GetEnumValue(SVGPatternElement::PATTERNUNITS);
+
+ /*
+ * Get the content geometry information. This is a little tricky --
+ * our parent is probably a <defs>, but we are rendering in the context
+ * of some geometry source. Our content geometry information needs to
+ * come from our rendering parent as opposed to our content parent. We
+ * get that information from aSource, which is passed to us from the
+ * backend renderer.
+ *
+ * There are three "geometries" that we need:
+ * 1) The bounding box for the pattern. We use this to get the
+ * width and height for the surface, and as the return to
+ * GetBBox.
+ * 2) The transformation matrix for the pattern. This is not *quite*
+ * the same as the canvas transformation matrix that we will
+ * provide to our rendering children since we "fudge" it a little
+ * to get the renderer to handle the translations correctly for us.
+ * 3) The CTM that we return to our children who make up the pattern.
+ */
+
+ // Get all of the information we need from our "caller" -- i.e.
+ // the geometry that is being rendered with a pattern
+ gfxRect callerBBox;
+ if (NS_FAILED(GetTargetGeometry(&callerBBox,
+ viewBox,
+ patternContentUnits, patternUnits,
+ aSource,
+ aContextMatrix,
+ aOverrideBounds))) {
+ return nullptr;
+ }
+
+ // Construct the CTM that we will provide to our children when we
+ // render them into the tile.
+ gfxMatrix ctm = ConstructCTM(viewBox, patternContentUnits, patternUnits,
+ callerBBox, aContextMatrix, aSource);
+ if (ctm.IsSingular()) {
+ return nullptr;
+ }
+
+ if (patternWithChildren->mCTM) {
+ *patternWithChildren->mCTM = ctm;
+ } else {
+ patternWithChildren->mCTM = new gfxMatrix(ctm);
+ }
+
+ // Get the bounding box of the pattern. This will be used to determine
+ // the size of the surface, and will also be used to define the bounding
+ // box for the pattern tile.
+ gfxRect bbox = GetPatternRect(patternUnits, callerBBox, aContextMatrix, aSource);
+ if (bbox.Width() <= 0.0 || bbox.Height() <= 0.0) {
+ return nullptr;
+ }
+
+ // Get the pattern transform
+ Matrix patternTransform = ToMatrix(GetPatternTransform());
+
+ // revert the vector effect transform so that the pattern appears unchanged
+ if (aFillOrStroke == &nsStyleSVG::mStroke) {
+ gfxMatrix userToOuterSVG;
+ if (nsSVGUtils::GetNonScalingStrokeTransform(aSource, &userToOuterSVG)) {
+ patternTransform *= ToMatrix(userToOuterSVG);
+ if (patternTransform.IsSingular()) {
+ NS_WARNING("Singular matrix painting non-scaling-stroke");
+ return nullptr;
+ }
+ }
+ }
+
+ // Get the transformation matrix that we will hand to the renderer's pattern
+ // routine.
+ *patternMatrix = GetPatternMatrix(patternUnits, patternTransform,
+ bbox, callerBBox, aContextMatrix);
+ if (patternMatrix->IsSingular()) {
+ return nullptr;
+ }
+
+ // Now that we have all of the necessary geometries, we can
+ // create our surface.
+ gfxRect transformedBBox = ThebesRect(patternTransform.TransformBounds(ToRect(bbox)));
+
+ bool resultOverflows;
+ IntSize surfaceSize =
+ nsSVGUtils::ConvertToSurfaceSize(
+ transformedBBox.Size(), &resultOverflows);
+
+ // 0 disables rendering, < 0 is an error
+ if (surfaceSize.width <= 0 || surfaceSize.height <= 0) {
+ return nullptr;
+ }
+
+ gfxFloat patternWidth = bbox.Width();
+ gfxFloat patternHeight = bbox.Height();
+
+ if (resultOverflows ||
+ patternWidth != surfaceSize.width ||
+ patternHeight != surfaceSize.height) {
+ // scale drawing to pattern surface size
+ gfxMatrix tempTM =
+ gfxMatrix(surfaceSize.width / patternWidth, 0.0,
+ 0.0, surfaceSize.height / patternHeight,
+ 0.0, 0.0);
+ patternWithChildren->mCTM->PreMultiply(tempTM);
+
+ // and rescale pattern to compensate
+ patternMatrix->PreScale(patternWidth / surfaceSize.width,
+ patternHeight / surfaceSize.height);
+ }
+
+ RefPtr<DrawTarget> dt =
+ aDrawTarget->CreateSimilarDrawTarget(surfaceSize, SurfaceFormat::B8G8R8A8);
+ if (!dt || !dt->IsValid()) {
+ return nullptr;
+ }
+ dt->ClearRect(Rect(0, 0, surfaceSize.width, surfaceSize.height));
+
+ RefPtr<gfxContext> gfx = gfxContext::CreateOrNull(dt);
+ MOZ_ASSERT(gfx); // already checked the draw target above
+
+ if (aGraphicOpacity != 1.0f) {
+ gfx->Save();
+ gfx->PushGroupForBlendBack(gfxContentType::COLOR_ALPHA, aGraphicOpacity);
+ }
+
+ // OK, now render -- note that we use "firstKid", which
+ // we got at the beginning because it takes care of the
+ // referenced pattern situation for us
+
+ if (aSource->IsFrameOfType(nsIFrame::eSVGGeometry)) {
+ // Set the geometrical parent of the pattern we are rendering
+ patternWithChildren->mSource = static_cast<nsSVGPathGeometryFrame*>(aSource);
+ }
+
+ // Delay checking NS_FRAME_DRAWING_AS_PAINTSERVER bit until here so we can
+ // give back a clear surface if there's a loop
+ if (!(patternWithChildren->GetStateBits() & NS_FRAME_DRAWING_AS_PAINTSERVER)) {
+ patternWithChildren->AddStateBits(NS_FRAME_DRAWING_AS_PAINTSERVER);
+ for (nsIFrame* kid = firstKid; kid;
+ kid = kid->GetNextSibling()) {
+ // The CTM of each frame referencing us can be different
+ nsISVGChildFrame* SVGFrame = do_QueryFrame(kid);
+ if (SVGFrame) {
+ SVGFrame->NotifySVGChanged(nsISVGChildFrame::TRANSFORM_CHANGED);
+ }
+ gfxMatrix tm = *(patternWithChildren->mCTM);
+ if (kid->GetContent()->IsSVGElement()) {
+ tm = static_cast<nsSVGElement*>(kid->GetContent())->
+ PrependLocalTransformsTo(tm, eUserSpaceToParent);
+ }
+ Unused << nsSVGUtils::PaintFrameWithEffects(kid, *gfx, tm);
+ }
+ patternWithChildren->RemoveStateBits(NS_FRAME_DRAWING_AS_PAINTSERVER);
+ }
+
+ patternWithChildren->mSource = nullptr;
+
+ if (aGraphicOpacity != 1.0f) {
+ gfx->PopGroupAndBlend();
+ gfx->Restore();
+ }
+
+ // caller now owns the surface
+ return dt->Snapshot();
+}
+
+/* Will probably need something like this... */
+// How do we handle the insertion of a new frame?
+// We really don't want to rerender this every time,
+// do we?
+nsSVGPatternFrame*
+nsSVGPatternFrame::GetPatternWithChildren()
+{
+ // Do we have any children ourselves?
+ if (!mFrames.IsEmpty())
+ return this;
+
+ // No, see if we chain to someone who does
+ AutoPatternReferencer patternRef(this);
+
+ nsSVGPatternFrame* next = GetReferencedPatternIfNotInUse();
+ if (!next)
+ return nullptr;
+
+ return next->GetPatternWithChildren();
+}
+
+uint16_t
+nsSVGPatternFrame::GetEnumValue(uint32_t aIndex, nsIContent *aDefault)
+{
+ nsSVGEnum& thisEnum =
+ static_cast<SVGPatternElement *>(mContent)->mEnumAttributes[aIndex];
+
+ if (thisEnum.IsExplicitlySet())
+ return thisEnum.GetAnimValue();
+
+ AutoPatternReferencer patternRef(this);
+
+ nsSVGPatternFrame *next = GetReferencedPatternIfNotInUse();
+ return next ? next->GetEnumValue(aIndex, aDefault) :
+ static_cast<SVGPatternElement *>(aDefault)->
+ mEnumAttributes[aIndex].GetAnimValue();
+}
+
+nsSVGAnimatedTransformList*
+nsSVGPatternFrame::GetPatternTransformList(nsIContent* aDefault)
+{
+ nsSVGAnimatedTransformList *thisTransformList =
+ static_cast<SVGPatternElement *>(mContent)->GetAnimatedTransformList();
+
+ if (thisTransformList && thisTransformList->IsExplicitlySet())
+ return thisTransformList;
+
+ AutoPatternReferencer patternRef(this);
+
+ nsSVGPatternFrame *next = GetReferencedPatternIfNotInUse();
+ return next ? next->GetPatternTransformList(aDefault) :
+ static_cast<SVGPatternElement *>(aDefault)->mPatternTransform.get();
+}
+
+gfxMatrix
+nsSVGPatternFrame::GetPatternTransform()
+{
+ nsSVGAnimatedTransformList* animTransformList =
+ GetPatternTransformList(mContent);
+ if (!animTransformList)
+ return gfxMatrix();
+
+ return animTransformList->GetAnimValue().GetConsolidationMatrix();
+}
+
+const nsSVGViewBox &
+nsSVGPatternFrame::GetViewBox(nsIContent* aDefault)
+{
+ const nsSVGViewBox &thisViewBox =
+ static_cast<SVGPatternElement *>(mContent)->mViewBox;
+
+ if (thisViewBox.IsExplicitlySet())
+ return thisViewBox;
+
+ AutoPatternReferencer patternRef(this);
+
+ nsSVGPatternFrame *next = GetReferencedPatternIfNotInUse();
+ return next ? next->GetViewBox(aDefault) :
+ static_cast<SVGPatternElement *>(aDefault)->mViewBox;
+}
+
+const SVGAnimatedPreserveAspectRatio &
+nsSVGPatternFrame::GetPreserveAspectRatio(nsIContent *aDefault)
+{
+ const SVGAnimatedPreserveAspectRatio &thisPar =
+ static_cast<SVGPatternElement *>(mContent)->mPreserveAspectRatio;
+
+ if (thisPar.IsExplicitlySet())
+ return thisPar;
+
+ AutoPatternReferencer patternRef(this);
+
+ nsSVGPatternFrame *next = GetReferencedPatternIfNotInUse();
+ return next ? next->GetPreserveAspectRatio(aDefault) :
+ static_cast<SVGPatternElement *>(aDefault)->mPreserveAspectRatio;
+}
+
+const nsSVGLength2 *
+nsSVGPatternFrame::GetLengthValue(uint32_t aIndex, nsIContent *aDefault)
+{
+ const nsSVGLength2 *thisLength =
+ &static_cast<SVGPatternElement *>(mContent)->mLengthAttributes[aIndex];
+
+ if (thisLength->IsExplicitlySet())
+ return thisLength;
+
+ AutoPatternReferencer patternRef(this);
+
+ nsSVGPatternFrame *next = GetReferencedPatternIfNotInUse();
+ return next ? next->GetLengthValue(aIndex, aDefault) :
+ &static_cast<SVGPatternElement *>(aDefault)->mLengthAttributes[aIndex];
+}
+
+// Private (helper) methods
+nsSVGPatternFrame *
+nsSVGPatternFrame::GetReferencedPattern()
+{
+ if (mNoHRefURI)
+ return nullptr;
+
+ nsSVGPaintingProperty *property =
+ Properties().Get(nsSVGEffects::HrefAsPaintingProperty());
+
+ if (!property) {
+ // Fetch our pattern element's href or xlink:href attribute
+ SVGPatternElement *pattern = static_cast<SVGPatternElement *>(mContent);
+ nsAutoString href;
+ if (pattern->mStringAttributes[SVGPatternElement::HREF].IsExplicitlySet()) {
+ pattern->mStringAttributes[SVGPatternElement::HREF]
+ .GetAnimValue(href, pattern);
+ } else {
+ pattern->mStringAttributes[SVGPatternElement::XLINK_HREF]
+ .GetAnimValue(href, pattern);
+ }
+
+ if (href.IsEmpty()) {
+ mNoHRefURI = true;
+ return nullptr; // no URL
+ }
+
+ // Convert href to an nsIURI
+ nsCOMPtr<nsIURI> targetURI;
+ nsCOMPtr<nsIURI> base = mContent->GetBaseURI();
+ nsContentUtils::NewURIWithDocumentCharset(getter_AddRefs(targetURI), href,
+ mContent->GetUncomposedDoc(), base);
+
+ property =
+ nsSVGEffects::GetPaintingProperty(targetURI, this,
+ nsSVGEffects::HrefAsPaintingProperty());
+ if (!property)
+ return nullptr;
+ }
+
+ nsIFrame *result = property->GetReferencedFrame();
+ if (!result)
+ return nullptr;
+
+ nsIAtom* frameType = result->GetType();
+ if (frameType != nsGkAtoms::svgPatternFrame)
+ return nullptr;
+
+ return static_cast<nsSVGPatternFrame*>(result);
+}
+
+nsSVGPatternFrame *
+nsSVGPatternFrame::GetReferencedPatternIfNotInUse()
+{
+ nsSVGPatternFrame *referenced = GetReferencedPattern();
+ if (!referenced)
+ return nullptr;
+
+ if (referenced->mLoopFlag) {
+ // XXXjwatt: we should really send an error to the JavaScript Console here:
+ NS_WARNING("pattern reference loop detected while inheriting attribute!");
+ return nullptr;
+ }
+
+ return referenced;
+}
+
+gfxRect
+nsSVGPatternFrame::GetPatternRect(uint16_t aPatternUnits,
+ const gfxRect &aTargetBBox,
+ const Matrix &aTargetCTM,
+ nsIFrame *aTarget)
+{
+ // We need to initialize our box
+ float x,y,width,height;
+
+ // Get the pattern x,y,width, and height
+ const nsSVGLength2 *tmpX, *tmpY, *tmpHeight, *tmpWidth;
+ tmpX = GetLengthValue(SVGPatternElement::ATTR_X);
+ tmpY = GetLengthValue(SVGPatternElement::ATTR_Y);
+ tmpHeight = GetLengthValue(SVGPatternElement::ATTR_HEIGHT);
+ tmpWidth = GetLengthValue(SVGPatternElement::ATTR_WIDTH);
+
+ if (aPatternUnits == SVG_UNIT_TYPE_OBJECTBOUNDINGBOX) {
+ x = nsSVGUtils::ObjectSpace(aTargetBBox, tmpX);
+ y = nsSVGUtils::ObjectSpace(aTargetBBox, tmpY);
+ width = nsSVGUtils::ObjectSpace(aTargetBBox, tmpWidth);
+ height = nsSVGUtils::ObjectSpace(aTargetBBox, tmpHeight);
+ } else {
+ float scale = MaxExpansion(aTargetCTM);
+ x = nsSVGUtils::UserSpace(aTarget, tmpX) * scale;
+ y = nsSVGUtils::UserSpace(aTarget, tmpY) * scale;
+ width = nsSVGUtils::UserSpace(aTarget, tmpWidth) * scale;
+ height = nsSVGUtils::UserSpace(aTarget, tmpHeight) * scale;
+ }
+
+ return gfxRect(x, y, width, height);
+}
+
+gfxMatrix
+nsSVGPatternFrame::ConstructCTM(const nsSVGViewBox& aViewBox,
+ uint16_t aPatternContentUnits,
+ uint16_t aPatternUnits,
+ const gfxRect &callerBBox,
+ const Matrix &callerCTM,
+ nsIFrame *aTarget)
+{
+ SVGSVGElement *ctx = nullptr;
+ nsIContent* targetContent = aTarget->GetContent();
+ gfxFloat scaleX, scaleY;
+
+ // The objectBoundingBox conversion must be handled in the CTM:
+ if (IncludeBBoxScale(aViewBox, aPatternContentUnits, aPatternUnits)) {
+ scaleX = callerBBox.Width();
+ scaleY = callerBBox.Height();
+ } else {
+ if (targetContent->IsSVGElement()) {
+ ctx = static_cast<nsSVGElement*>(targetContent)->GetCtx();
+ }
+ scaleX = scaleY = MaxExpansion(callerCTM);
+ }
+
+ if (!aViewBox.IsExplicitlySet()) {
+ return gfxMatrix(scaleX, 0.0, 0.0, scaleY, 0.0, 0.0);
+ }
+ const nsSVGViewBoxRect viewBoxRect = aViewBox.GetAnimValue();
+
+ if (viewBoxRect.height <= 0.0f || viewBoxRect.width <= 0.0f) {
+ return gfxMatrix(0.0, 0.0, 0.0, 0.0, 0.0, 0.0); // singular
+ }
+
+ float viewportWidth, viewportHeight;
+ if (targetContent->IsSVGElement()) {
+ // If we're dealing with an SVG target only retrieve the context once.
+ // Calling the nsIFrame* variant of GetAnimValue would look it up on
+ // every call.
+ viewportWidth =
+ GetLengthValue(SVGPatternElement::ATTR_WIDTH)->GetAnimValue(ctx);
+ viewportHeight =
+ GetLengthValue(SVGPatternElement::ATTR_HEIGHT)->GetAnimValue(ctx);
+ } else {
+ // No SVG target, call the nsIFrame* variant of GetAnimValue.
+ viewportWidth =
+ GetLengthValue(SVGPatternElement::ATTR_WIDTH)->GetAnimValue(aTarget);
+ viewportHeight =
+ GetLengthValue(SVGPatternElement::ATTR_HEIGHT)->GetAnimValue(aTarget);
+ }
+
+ if (viewportWidth <= 0.0f || viewportHeight <= 0.0f) {
+ return gfxMatrix(0.0, 0.0, 0.0, 0.0, 0.0, 0.0); // singular
+ }
+
+ Matrix tm = SVGContentUtils::GetViewBoxTransform(
+ viewportWidth * scaleX, viewportHeight * scaleY,
+ viewBoxRect.x, viewBoxRect.y,
+ viewBoxRect.width, viewBoxRect.height,
+ GetPreserveAspectRatio());
+
+ return ThebesMatrix(tm);
+}
+
+//----------------------------------------------------------------------
+// nsSVGPaintServerFrame methods:
+
+already_AddRefed<gfxPattern>
+nsSVGPatternFrame::GetPaintServerPattern(nsIFrame *aSource,
+ const DrawTarget* aDrawTarget,
+ const gfxMatrix& aContextMatrix,
+ nsStyleSVGPaint nsStyleSVG::*aFillOrStroke,
+ float aGraphicOpacity,
+ const gfxRect *aOverrideBounds)
+{
+ if (aGraphicOpacity == 0.0f) {
+ RefPtr<gfxPattern> pattern = new gfxPattern(Color());
+ return pattern.forget();
+ }
+
+ // Paint it!
+ Matrix pMatrix;
+ RefPtr<SourceSurface> surface =
+ PaintPattern(aDrawTarget, &pMatrix, ToMatrix(aContextMatrix), aSource,
+ aFillOrStroke, aGraphicOpacity, aOverrideBounds);
+
+ if (!surface) {
+ return nullptr;
+ }
+
+ RefPtr<gfxPattern> pattern = new gfxPattern(surface, pMatrix);
+
+ if (!pattern || pattern->CairoStatus())
+ return nullptr;
+
+ pattern->SetExtend(ExtendMode::REPEAT);
+ return pattern.forget();
+}
+
+// -------------------------------------------------------------------------
+// Public functions
+// -------------------------------------------------------------------------
+
+nsIFrame* NS_NewSVGPatternFrame(nsIPresShell* aPresShell,
+ nsStyleContext* aContext)
+{
+ return new (aPresShell) nsSVGPatternFrame(aContext);
+}
+
diff --git a/layout/svg/nsSVGPatternFrame.h b/layout/svg/nsSVGPatternFrame.h
new file mode 100644
index 0000000000..7c3cd1ad18
--- /dev/null
+++ b/layout/svg/nsSVGPatternFrame.h
@@ -0,0 +1,154 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef __NS_SVGPATTERNFRAME_H__
+#define __NS_SVGPATTERNFRAME_H__
+
+#include "mozilla/Attributes.h"
+#include "gfxMatrix.h"
+#include "mozilla/gfx/2D.h"
+#include "mozilla/RefPtr.h"
+#include "nsAutoPtr.h"
+#include "nsSVGPaintServerFrame.h"
+
+class nsIFrame;
+class nsSVGLength2;
+class nsSVGPathGeometryFrame;
+class nsSVGViewBox;
+
+namespace mozilla {
+class SVGAnimatedPreserveAspectRatio;
+class nsSVGAnimatedTransformList;
+} // namespace mozilla
+
+/**
+ * Patterns can refer to other patterns. We create an nsSVGPaintingProperty
+ * with property type nsGkAtoms::href to track the referenced pattern.
+ */
+class nsSVGPatternFrame : public nsSVGPaintServerFrame
+{
+ typedef mozilla::gfx::SourceSurface SourceSurface;
+
+public:
+ NS_DECL_FRAMEARENA_HELPERS
+
+ friend nsIFrame* NS_NewSVGPatternFrame(nsIPresShell* aPresShell,
+ nsStyleContext* aContext);
+
+ explicit nsSVGPatternFrame(nsStyleContext* aContext);
+
+ // nsSVGPaintServerFrame methods:
+ virtual already_AddRefed<gfxPattern>
+ GetPaintServerPattern(nsIFrame *aSource,
+ const DrawTarget* aDrawTarget,
+ const gfxMatrix& aContextMatrix,
+ nsStyleSVGPaint nsStyleSVG::*aFillOrStroke,
+ float aOpacity,
+ const gfxRect *aOverrideBounds) override;
+
+public:
+ typedef mozilla::SVGAnimatedPreserveAspectRatio SVGAnimatedPreserveAspectRatio;
+
+ // nsSVGContainerFrame methods:
+ virtual gfxMatrix GetCanvasTM() override;
+
+ // nsIFrame interface:
+ virtual nsresult AttributeChanged(int32_t aNameSpaceID,
+ nsIAtom* aAttribute,
+ int32_t aModType) override;
+
+#ifdef DEBUG
+ virtual void Init(nsIContent* aContent,
+ nsContainerFrame* aParent,
+ nsIFrame* aPrevInFlow) override;
+#endif
+
+ /**
+ * Get the "type" of the frame
+ *
+ * @see nsGkAtoms::svgPatternFrame
+ */
+ virtual nsIAtom* GetType() const override;
+
+#ifdef DEBUG_FRAME_DUMP
+ virtual nsresult GetFrameName(nsAString& aResult) const override
+ {
+ return MakeFrameName(NS_LITERAL_STRING("SVGPattern"), aResult);
+ }
+#endif // DEBUG
+
+protected:
+ // Internal methods for handling referenced patterns
+ class AutoPatternReferencer;
+ nsSVGPatternFrame* GetReferencedPattern();
+ nsSVGPatternFrame* GetReferencedPatternIfNotInUse();
+
+ // Accessors to lookup pattern attributes
+ uint16_t GetEnumValue(uint32_t aIndex, nsIContent *aDefault);
+ uint16_t GetEnumValue(uint32_t aIndex)
+ {
+ return GetEnumValue(aIndex, mContent);
+ }
+ mozilla::nsSVGAnimatedTransformList* GetPatternTransformList(
+ nsIContent* aDefault);
+ gfxMatrix GetPatternTransform();
+ const nsSVGViewBox &GetViewBox(nsIContent *aDefault);
+ const nsSVGViewBox &GetViewBox() { return GetViewBox(mContent); }
+ const SVGAnimatedPreserveAspectRatio &GetPreserveAspectRatio(
+ nsIContent *aDefault);
+ const SVGAnimatedPreserveAspectRatio &GetPreserveAspectRatio()
+ {
+ return GetPreserveAspectRatio(mContent);
+ }
+ const nsSVGLength2 *GetLengthValue(uint32_t aIndex, nsIContent *aDefault);
+ const nsSVGLength2 *GetLengthValue(uint32_t aIndex)
+ {
+ return GetLengthValue(aIndex, mContent);
+ }
+
+ already_AddRefed<SourceSurface>
+ PaintPattern(const DrawTarget* aDrawTarget,
+ Matrix *patternMatrix,
+ const Matrix &aContextMatrix,
+ nsIFrame *aSource,
+ nsStyleSVGPaint nsStyleSVG::*aFillOrStroke,
+ float aGraphicOpacity,
+ const gfxRect *aOverrideBounds);
+
+ /**
+ * A <pattern> element may reference another <pattern> element using
+ * xlink:href and, if it doesn't have any child content of its own, then it
+ * will "inherit" the children of the referenced pattern (which may itself be
+ * inheriting its children if it references another <pattern>). This
+ * function returns this nsSVGPatternFrame or the first pattern along the
+ * reference chain (if there is one) to have children.
+ */
+ nsSVGPatternFrame* GetPatternWithChildren();
+
+ gfxRect GetPatternRect(uint16_t aPatternUnits,
+ const gfxRect &bbox,
+ const Matrix &callerCTM,
+ nsIFrame *aTarget);
+ gfxMatrix ConstructCTM(const nsSVGViewBox& aViewBox,
+ uint16_t aPatternContentUnits,
+ uint16_t aPatternUnits,
+ const gfxRect &callerBBox,
+ const Matrix &callerCTM,
+ nsIFrame *aTarget);
+
+private:
+ // this is a *temporary* reference to the frame of the element currently
+ // referencing our pattern. This must be temporary because different
+ // referencing frames will all reference this one frame
+ nsSVGPathGeometryFrame *mSource;
+ nsAutoPtr<gfxMatrix> mCTM;
+
+protected:
+ // This flag is used to detect loops in xlink:href processing
+ bool mLoopFlag;
+ bool mNoHRefURI;
+};
+
+#endif
diff --git a/layout/svg/nsSVGStopFrame.cpp b/layout/svg/nsSVGStopFrame.cpp
new file mode 100644
index 0000000000..1b75fa1028
--- /dev/null
+++ b/layout/svg/nsSVGStopFrame.cpp
@@ -0,0 +1,116 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+// Keep in (case-insensitive) order:
+#include "nsContainerFrame.h"
+#include "nsFrame.h"
+#include "nsGkAtoms.h"
+#include "nsStyleContext.h"
+#include "nsSVGEffects.h"
+
+// This is a very simple frame whose only purpose is to capture style change
+// events and propagate them to the parent. Most of the heavy lifting is done
+// within the nsSVGGradientFrame, which is the parent for this frame
+
+class nsSVGStopFrame : public nsFrame
+{
+ friend nsIFrame*
+ NS_NewSVGStopFrame(nsIPresShell* aPresShell, nsStyleContext* aContext);
+protected:
+ explicit nsSVGStopFrame(nsStyleContext* aContext)
+ : nsFrame(aContext)
+ {
+ AddStateBits(NS_FRAME_IS_NONDISPLAY);
+ }
+
+public:
+ NS_DECL_FRAMEARENA_HELPERS
+
+ // nsIFrame interface:
+#ifdef DEBUG
+ virtual void Init(nsIContent* aContent,
+ nsContainerFrame* aParent,
+ nsIFrame* aPrevInFlow) override;
+#endif
+
+ void BuildDisplayList(nsDisplayListBuilder* aBuilder,
+ const nsRect& aDirtyRect,
+ const nsDisplayListSet& aLists) override {}
+
+ virtual nsresult AttributeChanged(int32_t aNameSpaceID,
+ nsIAtom* aAttribute,
+ int32_t aModType) override;
+
+ /**
+ * Get the "type" of the frame
+ *
+ * @see nsGkAtoms::svgStopFrame
+ */
+ virtual nsIAtom* GetType() const override;
+
+ virtual bool IsFrameOfType(uint32_t aFlags) const override
+ {
+ return nsFrame::IsFrameOfType(aFlags & ~(nsIFrame::eSVG));
+ }
+
+#ifdef DEBUG_FRAME_DUMP
+ virtual nsresult GetFrameName(nsAString& aResult) const override
+ {
+ return MakeFrameName(NS_LITERAL_STRING("SVGStop"), aResult);
+ }
+#endif
+};
+
+//----------------------------------------------------------------------
+// Implementation
+
+NS_IMPL_FRAMEARENA_HELPERS(nsSVGStopFrame)
+
+//----------------------------------------------------------------------
+// nsIFrame methods:
+
+#ifdef DEBUG
+void
+nsSVGStopFrame::Init(nsIContent* aContent,
+ nsContainerFrame* aParent,
+ nsIFrame* aPrevInFlow)
+{
+ NS_ASSERTION(aContent->IsSVGElement(nsGkAtoms::stop), "Content is not a stop element");
+
+ nsFrame::Init(aContent, aParent, aPrevInFlow);
+}
+#endif /* DEBUG */
+
+nsIAtom *
+nsSVGStopFrame::GetType() const
+{
+ return nsGkAtoms::svgStopFrame;
+}
+
+nsresult
+nsSVGStopFrame::AttributeChanged(int32_t aNameSpaceID,
+ nsIAtom* aAttribute,
+ int32_t aModType)
+{
+ if (aNameSpaceID == kNameSpaceID_None &&
+ aAttribute == nsGkAtoms::offset) {
+ MOZ_ASSERT(GetParent()->GetType() == nsGkAtoms::svgLinearGradientFrame ||
+ GetParent()->GetType() == nsGkAtoms::svgRadialGradientFrame,
+ "Observers observe the gradient, so that's what we must invalidate");
+ nsSVGEffects::InvalidateDirectRenderingObservers(GetParent());
+ }
+
+ return nsFrame::AttributeChanged(aNameSpaceID, aAttribute, aModType);
+}
+
+// -------------------------------------------------------------------------
+// Public functions
+// -------------------------------------------------------------------------
+
+nsIFrame* NS_NewSVGStopFrame(nsIPresShell* aPresShell,
+ nsStyleContext* aContext)
+{
+ return new (aPresShell) nsSVGStopFrame(aContext);
+}
diff --git a/layout/svg/nsSVGSwitchFrame.cpp b/layout/svg/nsSVGSwitchFrame.cpp
new file mode 100644
index 0000000000..26e77071b9
--- /dev/null
+++ b/layout/svg/nsSVGSwitchFrame.cpp
@@ -0,0 +1,268 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+// Keep in (case-insensitive) order:
+#include "gfxRect.h"
+#include "nsSVGEffects.h"
+#include "nsSVGGFrame.h"
+#include "mozilla/dom/SVGSwitchElement.h"
+#include "nsSVGUtils.h"
+
+using namespace mozilla::gfx;
+
+class nsSVGSwitchFrame : public nsSVGGFrame
+{
+ friend nsIFrame*
+ NS_NewSVGSwitchFrame(nsIPresShell* aPresShell, nsStyleContext* aContext);
+protected:
+ explicit nsSVGSwitchFrame(nsStyleContext* aContext)
+ : nsSVGGFrame(aContext) {}
+
+public:
+ NS_DECL_FRAMEARENA_HELPERS
+
+#ifdef DEBUG
+ virtual void Init(nsIContent* aContent,
+ nsContainerFrame* aParent,
+ nsIFrame* aPrevInFlow) override;
+#endif
+
+ /**
+ * Get the "type" of the frame
+ *
+ * @see nsGkAtoms::svgSwitchFrame
+ */
+ virtual nsIAtom* GetType() const override;
+
+#ifdef DEBUG_FRAME_DUMP
+ virtual nsresult GetFrameName(nsAString& aResult) const override
+ {
+ return MakeFrameName(NS_LITERAL_STRING("SVGSwitch"), aResult);
+ }
+#endif
+
+ virtual void BuildDisplayList(nsDisplayListBuilder* aBuilder,
+ const nsRect& aDirtyRect,
+ const nsDisplayListSet& aLists) override;
+
+ // nsISVGChildFrame interface:
+ virtual DrawResult PaintSVG(gfxContext& aContext,
+ const gfxMatrix& aTransform,
+ const nsIntRect* aDirtyRect = nullptr) override;
+ nsIFrame* GetFrameForPoint(const gfxPoint& aPoint) override;
+ nsRect GetCoveredRegion() override;
+ virtual void ReflowSVG() override;
+ virtual SVGBBox GetBBoxContribution(const Matrix &aToBBoxUserspace,
+ uint32_t aFlags) override;
+
+private:
+ nsIFrame *GetActiveChildFrame();
+};
+
+//----------------------------------------------------------------------
+// Implementation
+
+nsIFrame*
+NS_NewSVGSwitchFrame(nsIPresShell* aPresShell, nsStyleContext* aContext)
+{
+ return new (aPresShell) nsSVGSwitchFrame(aContext);
+}
+
+NS_IMPL_FRAMEARENA_HELPERS(nsSVGSwitchFrame)
+
+#ifdef DEBUG
+void
+nsSVGSwitchFrame::Init(nsIContent* aContent,
+ nsContainerFrame* aParent,
+ nsIFrame* aPrevInFlow)
+{
+ NS_ASSERTION(aContent->IsSVGElement(nsGkAtoms::svgSwitch),
+ "Content is not an SVG switch");
+
+ nsSVGGFrame::Init(aContent, aParent, aPrevInFlow);
+}
+#endif /* DEBUG */
+
+nsIAtom *
+nsSVGSwitchFrame::GetType() const
+{
+ return nsGkAtoms::svgSwitchFrame;
+}
+
+void
+nsSVGSwitchFrame::BuildDisplayList(nsDisplayListBuilder* aBuilder,
+ const nsRect& aDirtyRect,
+ const nsDisplayListSet& aLists)
+{
+ nsIFrame* kid = GetActiveChildFrame();
+ if (kid) {
+ BuildDisplayListForChild(aBuilder, kid, aDirtyRect, aLists);
+ }
+}
+
+DrawResult
+nsSVGSwitchFrame::PaintSVG(gfxContext& aContext,
+ const gfxMatrix& aTransform,
+ const nsIntRect* aDirtyRect)
+{
+ NS_ASSERTION(!NS_SVGDisplayListPaintingEnabled() ||
+ (mState & NS_FRAME_IS_NONDISPLAY),
+ "If display lists are enabled, only painting of non-display "
+ "SVG should take this code path");
+
+ if (StyleEffects()->mOpacity == 0.0)
+ return DrawResult::SUCCESS;
+
+ DrawResult result = DrawResult::SUCCESS;
+ nsIFrame *kid = GetActiveChildFrame();
+ if (kid) {
+ gfxMatrix tm = aTransform;
+ if (kid->GetContent()->IsSVGElement()) {
+ tm = static_cast<nsSVGElement*>(kid->GetContent())->
+ PrependLocalTransformsTo(tm, eUserSpaceToParent);
+ }
+ result = nsSVGUtils::PaintFrameWithEffects(kid, aContext, tm, aDirtyRect);
+ }
+ return result;
+}
+
+
+nsIFrame*
+nsSVGSwitchFrame::GetFrameForPoint(const gfxPoint& aPoint)
+{
+ NS_ASSERTION(!NS_SVGDisplayListHitTestingEnabled() ||
+ (mState & NS_FRAME_IS_NONDISPLAY),
+ "If display lists are enabled, only hit-testing of non-display "
+ "SVG should take this code path");
+
+ nsIFrame *kid = GetActiveChildFrame();
+ nsISVGChildFrame* svgFrame = do_QueryFrame(kid);
+ if (svgFrame) {
+ // Transform the point from our SVG user space to our child's.
+ gfxPoint point = aPoint;
+ gfxMatrix m =
+ static_cast<const nsSVGElement*>(mContent)->
+ PrependLocalTransformsTo(gfxMatrix(), eChildToUserSpace);
+ m = static_cast<const nsSVGElement*>(kid->GetContent())->
+ PrependLocalTransformsTo(m, eUserSpaceToParent);
+ if (!m.IsIdentity()) {
+ if (!m.Invert()) {
+ return nullptr;
+ }
+ point = m.Transform(point);
+ }
+ return svgFrame->GetFrameForPoint(point);
+ }
+
+ return nullptr;
+}
+
+nsRect
+nsSVGSwitchFrame::GetCoveredRegion()
+{
+ nsRect rect;
+
+ nsIFrame *kid = GetActiveChildFrame();
+ nsISVGChildFrame* child = do_QueryFrame(kid);
+ if (child) {
+ rect = child->GetCoveredRegion();
+ }
+ return rect;
+}
+
+void
+nsSVGSwitchFrame::ReflowSVG()
+{
+ NS_ASSERTION(nsSVGUtils::OuterSVGIsCallingReflowSVG(this),
+ "This call is probably a wasteful mistake");
+
+ MOZ_ASSERT(!(GetStateBits() & NS_FRAME_IS_NONDISPLAY),
+ "ReflowSVG mechanism not designed for this");
+
+ if (!nsSVGUtils::NeedsReflowSVG(this)) {
+ return;
+ }
+
+ // If the NS_FRAME_FIRST_REFLOW bit has been removed from our parent frame,
+ // then our outer-<svg> has previously had its initial reflow. In that case
+ // we need to make sure that that bit has been removed from ourself _before_
+ // recursing over our children to ensure that they know too. Otherwise, we
+ // need to remove it _after_ recursing over our children so that they know
+ // the initial reflow is currently underway.
+
+ bool isFirstReflow = (mState & NS_FRAME_FIRST_REFLOW);
+
+ bool outerSVGHasHadFirstReflow =
+ (GetParent()->GetStateBits() & NS_FRAME_FIRST_REFLOW) == 0;
+
+ if (outerSVGHasHadFirstReflow) {
+ mState &= ~NS_FRAME_FIRST_REFLOW; // tell our children
+ }
+
+ nsOverflowAreas overflowRects;
+
+ nsIFrame *child = GetActiveChildFrame();
+ nsISVGChildFrame* svgChild = do_QueryFrame(child);
+ if (svgChild) {
+ MOZ_ASSERT(!(child->GetStateBits() & NS_FRAME_IS_NONDISPLAY),
+ "Check for this explicitly in the |if|, then");
+ svgChild->ReflowSVG();
+
+ // We build up our child frame overflows here instead of using
+ // nsLayoutUtils::UnionChildOverflow since SVG frame's all use the same
+ // frame list, and we're iterating over that list now anyway.
+ ConsiderChildOverflow(overflowRects, child);
+ }
+
+ if (isFirstReflow) {
+ // Make sure we have our filter property (if any) before calling
+ // FinishAndStoreOverflow (subsequent filter changes are handled off
+ // nsChangeHint_UpdateEffects):
+ nsSVGEffects::UpdateEffects(this);
+ }
+
+ FinishAndStoreOverflow(overflowRects, mRect.Size());
+
+ // Remove state bits after FinishAndStoreOverflow so that it doesn't
+ // invalidate on first reflow:
+ mState &= ~(NS_FRAME_FIRST_REFLOW | NS_FRAME_IS_DIRTY |
+ NS_FRAME_HAS_DIRTY_CHILDREN);
+}
+
+SVGBBox
+nsSVGSwitchFrame::GetBBoxContribution(const Matrix &aToBBoxUserspace,
+ uint32_t aFlags)
+{
+ nsIFrame* kid = GetActiveChildFrame();
+ nsISVGChildFrame* svgKid = do_QueryFrame(kid);
+ if (svgKid) {
+ nsIContent *content = kid->GetContent();
+ gfxMatrix transform = ThebesMatrix(aToBBoxUserspace);
+ if (content->IsSVGElement()) {
+ transform = static_cast<nsSVGElement*>(content)->
+ PrependLocalTransformsTo(transform);
+ }
+ return svgKid->GetBBoxContribution(ToMatrix(transform), aFlags);
+ }
+ return SVGBBox();
+}
+
+nsIFrame *
+nsSVGSwitchFrame::GetActiveChildFrame()
+{
+ nsIContent *activeChild =
+ static_cast<mozilla::dom::SVGSwitchElement*>(mContent)->GetActiveChild();
+
+ if (activeChild) {
+ for (nsIFrame* kid = mFrames.FirstChild(); kid;
+ kid = kid->GetNextSibling()) {
+
+ if (activeChild == kid->GetContent()) {
+ return kid;
+ }
+ }
+ }
+ return nullptr;
+}
diff --git a/layout/svg/nsSVGUseFrame.cpp b/layout/svg/nsSVGUseFrame.cpp
new file mode 100644
index 0000000000..118d24f691
--- /dev/null
+++ b/layout/svg/nsSVGUseFrame.cpp
@@ -0,0 +1,259 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+// Keep in (case-insensitive) order:
+#include "nsIAnonymousContentCreator.h"
+#include "nsSVGEffects.h"
+#include "nsSVGGFrame.h"
+#include "mozilla/dom/SVGUseElement.h"
+#include "nsContentList.h"
+
+using namespace mozilla::dom;
+
+class nsSVGUseFrame : public nsSVGGFrame
+ , public nsIAnonymousContentCreator
+{
+ friend nsIFrame*
+ NS_NewSVGUseFrame(nsIPresShell* aPresShell, nsStyleContext* aContext);
+
+protected:
+ explicit nsSVGUseFrame(nsStyleContext* aContext)
+ : nsSVGGFrame(aContext)
+ , mHasValidDimensions(true)
+ {}
+
+public:
+ NS_DECL_QUERYFRAME
+ NS_DECL_FRAMEARENA_HELPERS
+
+ // nsIFrame interface:
+ virtual void Init(nsIContent* aContent,
+ nsContainerFrame* aParent,
+ nsIFrame* aPrevInFlow) override;
+
+ virtual nsresult AttributeChanged(int32_t aNameSpaceID,
+ nsIAtom* aAttribute,
+ int32_t aModType) override;
+
+ virtual void DestroyFrom(nsIFrame* aDestructRoot) override;
+
+ /**
+ * Get the "type" of the frame
+ *
+ * @see nsGkAtoms::svgUseFrame
+ */
+ virtual nsIAtom* GetType() const override;
+
+ virtual bool IsLeaf() const override;
+
+#ifdef DEBUG_FRAME_DUMP
+ virtual nsresult GetFrameName(nsAString& aResult) const override
+ {
+ return MakeFrameName(NS_LITERAL_STRING("SVGUse"), aResult);
+ }
+#endif
+
+ // nsISVGChildFrame interface:
+ virtual void ReflowSVG() override;
+ virtual void NotifySVGChanged(uint32_t aFlags) override;
+
+ // nsIAnonymousContentCreator
+ virtual nsresult CreateAnonymousContent(nsTArray<ContentInfo>& aElements) override;
+ virtual void AppendAnonymousContentTo(nsTArray<nsIContent*>& aElements,
+ uint32_t aFilter) override;
+
+private:
+ bool mHasValidDimensions;
+};
+
+//----------------------------------------------------------------------
+// Implementation
+
+nsIFrame*
+NS_NewSVGUseFrame(nsIPresShell* aPresShell, nsStyleContext* aContext)
+{
+ return new (aPresShell) nsSVGUseFrame(aContext);
+}
+
+NS_IMPL_FRAMEARENA_HELPERS(nsSVGUseFrame)
+
+nsIAtom *
+nsSVGUseFrame::GetType() const
+{
+ return nsGkAtoms::svgUseFrame;
+}
+
+//----------------------------------------------------------------------
+// nsQueryFrame methods
+
+NS_QUERYFRAME_HEAD(nsSVGUseFrame)
+ NS_QUERYFRAME_ENTRY(nsIAnonymousContentCreator)
+NS_QUERYFRAME_TAIL_INHERITING(nsSVGGFrame)
+
+//----------------------------------------------------------------------
+// nsIFrame methods:
+
+void
+nsSVGUseFrame::Init(nsIContent* aContent,
+ nsContainerFrame* aParent,
+ nsIFrame* aPrevInFlow)
+{
+ NS_ASSERTION(aContent->IsSVGElement(nsGkAtoms::use),
+ "Content is not an SVG use!");
+
+ mHasValidDimensions =
+ static_cast<SVGUseElement*>(aContent)->HasValidDimensions();
+
+ nsSVGGFrame::Init(aContent, aParent, aPrevInFlow);
+}
+
+nsresult
+nsSVGUseFrame::AttributeChanged(int32_t aNameSpaceID,
+ nsIAtom* aAttribute,
+ int32_t aModType)
+{
+ SVGUseElement *useElement = static_cast<SVGUseElement*>(mContent);
+
+ if (aNameSpaceID == kNameSpaceID_None) {
+ if (aAttribute == nsGkAtoms::x || aAttribute == nsGkAtoms::y) {
+ // make sure our cached transform matrix gets (lazily) updated
+ mCanvasTM = nullptr;
+ nsLayoutUtils::PostRestyleEvent(
+ useElement, nsRestyleHint(0),
+ nsChangeHint_InvalidateRenderingObservers);
+ nsSVGUtils::ScheduleReflowSVG(this);
+ nsSVGUtils::NotifyChildrenOfSVGChange(this, TRANSFORM_CHANGED);
+ } else if (aAttribute == nsGkAtoms::width ||
+ aAttribute == nsGkAtoms::height) {
+ bool invalidate = false;
+ if (mHasValidDimensions != useElement->HasValidDimensions()) {
+ mHasValidDimensions = !mHasValidDimensions;
+ invalidate = true;
+ }
+ if (useElement->OurWidthAndHeightAreUsed()) {
+ invalidate = true;
+ useElement->SyncWidthOrHeight(aAttribute);
+ }
+ if (invalidate) {
+ nsLayoutUtils::PostRestyleEvent(
+ useElement, nsRestyleHint(0),
+ nsChangeHint_InvalidateRenderingObservers);
+ nsSVGUtils::ScheduleReflowSVG(this);
+ }
+ }
+ }
+
+ if ((aNameSpaceID == kNameSpaceID_XLink ||
+ aNameSpaceID == kNameSpaceID_None) &&
+ aAttribute == nsGkAtoms::href) {
+ // we're changing our nature, clear out the clone information
+ nsLayoutUtils::PostRestyleEvent(
+ useElement, nsRestyleHint(0),
+ nsChangeHint_InvalidateRenderingObservers);
+ nsSVGUtils::ScheduleReflowSVG(this);
+ useElement->mOriginal = nullptr;
+ useElement->UnlinkSource();
+ useElement->TriggerReclone();
+ }
+
+ return nsSVGGFrame::AttributeChanged(aNameSpaceID, aAttribute, aModType);
+}
+
+void
+nsSVGUseFrame::DestroyFrom(nsIFrame* aDestructRoot)
+{
+ RefPtr<SVGUseElement> use = static_cast<SVGUseElement*>(mContent);
+ nsSVGGFrame::DestroyFrom(aDestructRoot);
+ use->DestroyAnonymousContent();
+}
+
+bool
+nsSVGUseFrame::IsLeaf() const
+{
+ return true;
+}
+
+
+//----------------------------------------------------------------------
+// nsISVGChildFrame methods
+
+void
+nsSVGUseFrame::ReflowSVG()
+{
+ // We only handle x/y offset here, since any width/height that is in force is
+ // handled by the nsSVGOuterSVGFrame for the anonymous <svg> that will be
+ // created for that purpose.
+ float x, y;
+ static_cast<SVGUseElement*>(mContent)->
+ GetAnimatedLengthValues(&x, &y, nullptr);
+ mRect.MoveTo(nsLayoutUtils::RoundGfxRectToAppRect(
+ gfxRect(x, y, 0.0, 0.0),
+ PresContext()->AppUnitsPerCSSPixel()).TopLeft());
+
+ // If we have a filter, we need to invalidate ourselves because filter
+ // output can change even if none of our descendants need repainting.
+ if (StyleEffects()->HasFilters()) {
+ InvalidateFrame();
+ }
+
+ nsSVGGFrame::ReflowSVG();
+}
+
+void
+nsSVGUseFrame::NotifySVGChanged(uint32_t aFlags)
+{
+ if (aFlags & COORD_CONTEXT_CHANGED &&
+ !(aFlags & TRANSFORM_CHANGED)) {
+ // Coordinate context changes affect mCanvasTM if we have a
+ // percentage 'x' or 'y'
+ SVGUseElement *use = static_cast<SVGUseElement*>(mContent);
+ if (use->mLengthAttributes[SVGUseElement::ATTR_X].IsPercentage() ||
+ use->mLengthAttributes[SVGUseElement::ATTR_Y].IsPercentage()) {
+ aFlags |= TRANSFORM_CHANGED;
+ // Ancestor changes can't affect how we render from the perspective of
+ // any rendering observers that we may have, so we don't need to
+ // invalidate them. We also don't need to invalidate ourself, since our
+ // changed ancestor will have invalidated its entire area, which includes
+ // our area.
+ // For perf reasons we call this before calling NotifySVGChanged() below.
+ nsSVGUtils::ScheduleReflowSVG(this);
+ }
+ }
+
+ // We don't remove the TRANSFORM_CHANGED flag here if we have a viewBox or
+ // non-percentage width/height, since if they're set then they are cloned to
+ // an anonymous child <svg>, and its nsSVGInnerSVGFrame will do that.
+
+ nsSVGGFrame::NotifySVGChanged(aFlags);
+}
+
+//----------------------------------------------------------------------
+// nsIAnonymousContentCreator methods:
+
+nsresult
+nsSVGUseFrame::CreateAnonymousContent(nsTArray<ContentInfo>& aElements)
+{
+ SVGUseElement *use = static_cast<SVGUseElement*>(mContent);
+
+ nsIContent* clone = use->CreateAnonymousContent();
+ nsLayoutUtils::PostRestyleEvent(
+ use, nsRestyleHint(0), nsChangeHint_InvalidateRenderingObservers);
+ if (!clone)
+ return NS_ERROR_FAILURE;
+ if (!aElements.AppendElement(clone))
+ return NS_ERROR_OUT_OF_MEMORY;
+ return NS_OK;
+}
+
+void
+nsSVGUseFrame::AppendAnonymousContentTo(nsTArray<nsIContent*>& aElements,
+ uint32_t aFilter)
+{
+ SVGUseElement *use = static_cast<SVGUseElement*>(mContent);
+ nsIContent* clone = use->GetAnonymousContent();
+ if (clone) {
+ aElements.AppendElement(clone);
+ }
+}
diff --git a/layout/svg/nsSVGUtils.cpp b/layout/svg/nsSVGUtils.cpp
new file mode 100644
index 0000000000..ff74d5bafc
--- /dev/null
+++ b/layout/svg/nsSVGUtils.cpp
@@ -0,0 +1,1829 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+// Main header first:
+// This is also necessary to ensure our definition of M_SQRT1_2 is picked up
+#include "nsSVGUtils.h"
+#include <algorithm>
+
+// Keep others in (case-insensitive) order:
+#include "gfx2DGlue.h"
+#include "gfxContext.h"
+#include "gfxMatrix.h"
+#include "gfxPlatform.h"
+#include "gfxRect.h"
+#include "gfxUtils.h"
+#include "mozilla/gfx/2D.h"
+#include "mozilla/gfx/PatternHelpers.h"
+#include "mozilla/Preferences.h"
+#include "mozilla/SVGContextPaint.h"
+#include "nsCSSClipPathInstance.h"
+#include "nsCSSFrameConstructor.h"
+#include "nsDisplayList.h"
+#include "nsFilterInstance.h"
+#include "nsFrameList.h"
+#include "nsGkAtoms.h"
+#include "nsIContent.h"
+#include "nsIDocument.h"
+#include "nsIFrame.h"
+#include "nsIPresShell.h"
+#include "nsISVGChildFrame.h"
+#include "nsLayoutUtils.h"
+#include "nsPresContext.h"
+#include "nsStyleCoord.h"
+#include "nsStyleStruct.h"
+#include "nsSVGClipPathFrame.h"
+#include "nsSVGContainerFrame.h"
+#include "nsSVGEffects.h"
+#include "nsSVGFilterPaintCallback.h"
+#include "nsSVGForeignObjectFrame.h"
+#include "nsSVGInnerSVGFrame.h"
+#include "nsSVGIntegrationUtils.h"
+#include "nsSVGLength2.h"
+#include "nsSVGMaskFrame.h"
+#include "nsSVGOuterSVGFrame.h"
+#include "mozilla/dom/SVGClipPathElement.h"
+#include "mozilla/dom/SVGPathElement.h"
+#include "nsSVGPathGeometryElement.h"
+#include "nsSVGPathGeometryFrame.h"
+#include "nsSVGPaintServerFrame.h"
+#include "mozilla/dom/SVGSVGElement.h"
+#include "nsTextFrame.h"
+#include "SVGContentUtils.h"
+#include "SVGTextFrame.h"
+#include "mozilla/Unused.h"
+
+using namespace mozilla;
+using namespace mozilla::dom;
+using namespace mozilla::gfx;
+
+static bool sSVGPathCachingEnabled;
+static bool sSVGDisplayListHitTestingEnabled;
+static bool sSVGDisplayListPaintingEnabled;
+static bool sSVGNewGetBBoxEnabled;
+
+bool
+NS_SVGPathCachingEnabled()
+{
+ return sSVGPathCachingEnabled;
+}
+
+bool
+NS_SVGDisplayListHitTestingEnabled()
+{
+ return sSVGDisplayListHitTestingEnabled;
+}
+
+bool
+NS_SVGDisplayListPaintingEnabled()
+{
+ return sSVGDisplayListPaintingEnabled;
+}
+
+bool
+NS_SVGNewGetBBoxEnabled()
+{
+ return sSVGNewGetBBoxEnabled;
+}
+
+
+// we only take the address of this:
+static mozilla::gfx::UserDataKey sSVGAutoRenderStateKey;
+
+SVGAutoRenderState::SVGAutoRenderState(DrawTarget* aDrawTarget
+ MOZ_GUARD_OBJECT_NOTIFIER_PARAM_IN_IMPL)
+ : mDrawTarget(aDrawTarget)
+ , mOriginalRenderState(nullptr)
+ , mPaintingToWindow(false)
+{
+ MOZ_GUARD_OBJECT_NOTIFIER_INIT;
+ mOriginalRenderState =
+ aDrawTarget->RemoveUserData(&sSVGAutoRenderStateKey);
+ // We always remove ourselves from aContext before it dies, so
+ // passing nullptr as the destroy function is okay.
+ aDrawTarget->AddUserData(&sSVGAutoRenderStateKey, this, nullptr);
+}
+
+SVGAutoRenderState::~SVGAutoRenderState()
+{
+ mDrawTarget->RemoveUserData(&sSVGAutoRenderStateKey);
+ if (mOriginalRenderState) {
+ mDrawTarget->AddUserData(&sSVGAutoRenderStateKey,
+ mOriginalRenderState, nullptr);
+ }
+}
+
+void
+SVGAutoRenderState::SetPaintingToWindow(bool aPaintingToWindow)
+{
+ mPaintingToWindow = aPaintingToWindow;
+}
+
+/* static */ bool
+SVGAutoRenderState::IsPaintingToWindow(DrawTarget* aDrawTarget)
+{
+ void *state = aDrawTarget->GetUserData(&sSVGAutoRenderStateKey);
+ if (state) {
+ return static_cast<SVGAutoRenderState*>(state)->mPaintingToWindow;
+ }
+ return false;
+}
+
+void
+nsSVGUtils::Init()
+{
+ Preferences::AddBoolVarCache(&sSVGPathCachingEnabled,
+ "svg.path-caching.enabled");
+
+ Preferences::AddBoolVarCache(&sSVGDisplayListHitTestingEnabled,
+ "svg.display-lists.hit-testing.enabled");
+
+ Preferences::AddBoolVarCache(&sSVGDisplayListPaintingEnabled,
+ "svg.display-lists.painting.enabled");
+
+ Preferences::AddBoolVarCache(&sSVGNewGetBBoxEnabled,
+ "svg.new-getBBox.enabled");
+}
+
+nsSVGDisplayContainerFrame*
+nsSVGUtils::GetNearestSVGViewport(nsIFrame *aFrame)
+{
+ NS_ASSERTION(aFrame->IsFrameOfType(nsIFrame::eSVG), "SVG frame expected");
+ if (aFrame->GetType() == nsGkAtoms::svgOuterSVGFrame) {
+ return nullptr;
+ }
+ while ((aFrame = aFrame->GetParent())) {
+ NS_ASSERTION(aFrame->IsFrameOfType(nsIFrame::eSVG), "SVG frame expected");
+ if (aFrame->GetType() == nsGkAtoms::svgInnerSVGFrame ||
+ aFrame->GetType() == nsGkAtoms::svgOuterSVGFrame) {
+ return do_QueryFrame(aFrame);
+ }
+ }
+ NS_NOTREACHED("This is not reached. It's only needed to compile.");
+ return nullptr;
+}
+
+nsRect
+nsSVGUtils::GetPostFilterVisualOverflowRect(nsIFrame *aFrame,
+ const nsRect &aPreFilterRect)
+{
+ MOZ_ASSERT(aFrame->GetStateBits() & NS_FRAME_SVG_LAYOUT,
+ "Called on invalid frame type");
+
+ nsSVGFilterProperty *property = nsSVGEffects::GetFilterProperty(aFrame);
+ if (!property || !property->ReferencesValidResources()) {
+ return aPreFilterRect;
+ }
+
+ return nsFilterInstance::GetPostFilterBounds(aFrame, nullptr, &aPreFilterRect);
+}
+
+bool
+nsSVGUtils::OuterSVGIsCallingReflowSVG(nsIFrame *aFrame)
+{
+ return GetOuterSVGFrame(aFrame)->IsCallingReflowSVG();
+}
+
+bool
+nsSVGUtils::AnyOuterSVGIsCallingReflowSVG(nsIFrame* aFrame)
+{
+ nsSVGOuterSVGFrame* outer = GetOuterSVGFrame(aFrame);
+ do {
+ if (outer->IsCallingReflowSVG()) {
+ return true;
+ }
+ outer = GetOuterSVGFrame(outer->GetParent());
+ } while (outer);
+ return false;
+}
+
+void
+nsSVGUtils::ScheduleReflowSVG(nsIFrame *aFrame)
+{
+ MOZ_ASSERT(aFrame->IsFrameOfType(nsIFrame::eSVG),
+ "Passed bad frame!");
+
+ // If this is triggered, the callers should be fixed to call us before
+ // ReflowSVG is called. If we try to mark dirty bits on frames while we're
+ // in the process of removing them, things will get messed up.
+ NS_ASSERTION(!OuterSVGIsCallingReflowSVG(aFrame),
+ "Do not call under nsISVGChildFrame::ReflowSVG!");
+
+ // We don't call nsSVGEffects::InvalidateRenderingObservers here because
+ // we should only be called under InvalidateAndScheduleReflowSVG (which
+ // calls InvalidateBounds) or nsSVGDisplayContainerFrame::InsertFrames
+ // (at which point the frame has no observers).
+
+ if (aFrame->GetStateBits() & NS_FRAME_IS_NONDISPLAY) {
+ return;
+ }
+
+ if (aFrame->GetStateBits() &
+ (NS_FRAME_IS_DIRTY | NS_FRAME_FIRST_REFLOW)) {
+ // Nothing to do if we're already dirty, or if the outer-<svg>
+ // hasn't yet had its initial reflow.
+ return;
+ }
+
+ nsSVGOuterSVGFrame *outerSVGFrame = nullptr;
+
+ // We must not add dirty bits to the nsSVGOuterSVGFrame or else
+ // PresShell::FrameNeedsReflow won't work when we pass it in below.
+ if (aFrame->GetStateBits() & NS_STATE_IS_OUTER_SVG) {
+ outerSVGFrame = static_cast<nsSVGOuterSVGFrame*>(aFrame);
+ } else {
+ aFrame->AddStateBits(NS_FRAME_IS_DIRTY);
+
+ nsIFrame *f = aFrame->GetParent();
+ while (f && !(f->GetStateBits() & NS_STATE_IS_OUTER_SVG)) {
+ if (f->GetStateBits() &
+ (NS_FRAME_IS_DIRTY | NS_FRAME_HAS_DIRTY_CHILDREN)) {
+ return;
+ }
+ f->AddStateBits(NS_FRAME_HAS_DIRTY_CHILDREN);
+ f = f->GetParent();
+ MOZ_ASSERT(f->IsFrameOfType(nsIFrame::eSVG),
+ "NS_STATE_IS_OUTER_SVG check above not valid!");
+ }
+
+ outerSVGFrame = static_cast<nsSVGOuterSVGFrame*>(f);
+
+ MOZ_ASSERT(outerSVGFrame &&
+ outerSVGFrame->GetType() == nsGkAtoms::svgOuterSVGFrame,
+ "Did not find nsSVGOuterSVGFrame!");
+ }
+
+ if (outerSVGFrame->GetStateBits() & NS_FRAME_IN_REFLOW) {
+ // We're currently under an nsSVGOuterSVGFrame::Reflow call so there is no
+ // need to call PresShell::FrameNeedsReflow, since we have an
+ // nsSVGOuterSVGFrame::DidReflow call pending.
+ return;
+ }
+
+ nsFrameState dirtyBit =
+ (outerSVGFrame == aFrame ? NS_FRAME_IS_DIRTY : NS_FRAME_HAS_DIRTY_CHILDREN);
+
+ aFrame->PresContext()->PresShell()->FrameNeedsReflow(
+ outerSVGFrame, nsIPresShell::eResize, dirtyBit);
+}
+
+bool
+nsSVGUtils::NeedsReflowSVG(nsIFrame *aFrame)
+{
+ MOZ_ASSERT(aFrame->IsFrameOfType(nsIFrame::eSVG),
+ "SVG uses bits differently!");
+
+ // The flags we test here may change, hence why we have this separate
+ // function.
+ return NS_SUBTREE_DIRTY(aFrame);
+}
+
+void
+nsSVGUtils::NotifyAncestorsOfFilterRegionChange(nsIFrame *aFrame)
+{
+ MOZ_ASSERT(!(aFrame->GetStateBits() & NS_STATE_IS_OUTER_SVG),
+ "Not expecting to be called on the outer SVG Frame");
+
+ aFrame = aFrame->GetParent();
+
+ while (aFrame) {
+ if (aFrame->GetStateBits() & NS_STATE_IS_OUTER_SVG)
+ return;
+
+ nsSVGFilterProperty *property = nsSVGEffects::GetFilterProperty(aFrame);
+ if (property) {
+ property->Invalidate();
+ }
+ aFrame = aFrame->GetParent();
+ }
+}
+
+Size
+nsSVGUtils::GetContextSize(const nsIFrame* aFrame)
+{
+ Size size;
+
+ MOZ_ASSERT(aFrame->GetContent()->IsSVGElement(), "bad cast");
+ const nsSVGElement* element = static_cast<nsSVGElement*>(aFrame->GetContent());
+
+ SVGSVGElement* ctx = element->GetCtx();
+ if (ctx) {
+ size.width = ctx->GetLength(SVGContentUtils::X);
+ size.height = ctx->GetLength(SVGContentUtils::Y);
+ }
+ return size;
+}
+
+float
+nsSVGUtils::ObjectSpace(const gfxRect &aRect, const nsSVGLength2 *aLength)
+{
+ float axis;
+
+ switch (aLength->GetCtxType()) {
+ case SVGContentUtils::X:
+ axis = aRect.Width();
+ break;
+ case SVGContentUtils::Y:
+ axis = aRect.Height();
+ break;
+ case SVGContentUtils::XY:
+ axis = float(SVGContentUtils::ComputeNormalizedHypotenuse(
+ aRect.Width(), aRect.Height()));
+ break;
+ default:
+ NS_NOTREACHED("unexpected ctx type");
+ axis = 0.0f;
+ break;
+ }
+ if (aLength->IsPercentage()) {
+ // Multiply first to avoid precision errors:
+ return axis * aLength->GetAnimValInSpecifiedUnits() / 100;
+ }
+ return aLength->GetAnimValue(static_cast<SVGSVGElement*>(nullptr)) * axis;
+}
+
+float
+nsSVGUtils::UserSpace(nsSVGElement *aSVGElement, const nsSVGLength2 *aLength)
+{
+ return aLength->GetAnimValue(aSVGElement);
+}
+
+float
+nsSVGUtils::UserSpace(nsIFrame *aNonSVGContext, const nsSVGLength2 *aLength)
+{
+ return aLength->GetAnimValue(aNonSVGContext);
+}
+
+float
+nsSVGUtils::UserSpace(const UserSpaceMetrics& aMetrics, const nsSVGLength2 *aLength)
+{
+ return aLength->GetAnimValue(aMetrics);
+}
+
+nsSVGOuterSVGFrame *
+nsSVGUtils::GetOuterSVGFrame(nsIFrame *aFrame)
+{
+ while (aFrame) {
+ if (aFrame->GetStateBits() & NS_STATE_IS_OUTER_SVG) {
+ return static_cast<nsSVGOuterSVGFrame*>(aFrame);
+ }
+ aFrame = aFrame->GetParent();
+ }
+
+ return nullptr;
+}
+
+nsIFrame*
+nsSVGUtils::GetOuterSVGFrameAndCoveredRegion(nsIFrame* aFrame, nsRect* aRect)
+{
+ nsISVGChildFrame* svg = do_QueryFrame(aFrame);
+ if (!svg)
+ return nullptr;
+ nsSVGOuterSVGFrame* outer = GetOuterSVGFrame(aFrame);
+ if (outer == svg) {
+ return nullptr;
+ }
+ nsMargin bp = outer->GetUsedBorderAndPadding();
+ *aRect = ((aFrame->GetStateBits() & NS_FRAME_IS_NONDISPLAY) ?
+ nsRect(0, 0, 0, 0) : svg->GetCoveredRegion()) +
+ nsPoint(bp.left, bp.top);
+ return outer;
+}
+
+gfxMatrix
+nsSVGUtils::GetCanvasTM(nsIFrame *aFrame)
+{
+ // XXX yuck, we really need a common interface for GetCanvasTM
+
+ if (!aFrame->IsFrameOfType(nsIFrame::eSVG)) {
+ return nsSVGIntegrationUtils::GetCSSPxToDevPxMatrix(aFrame);
+ }
+
+ nsIAtom* type = aFrame->GetType();
+ if (type == nsGkAtoms::svgForeignObjectFrame) {
+ return static_cast<nsSVGForeignObjectFrame*>(aFrame)->GetCanvasTM();
+ }
+ if (type == nsGkAtoms::svgOuterSVGFrame) {
+ return nsSVGIntegrationUtils::GetCSSPxToDevPxMatrix(aFrame);
+ }
+
+ nsSVGContainerFrame *containerFrame = do_QueryFrame(aFrame);
+ if (containerFrame) {
+ return containerFrame->GetCanvasTM();
+ }
+
+ return static_cast<nsSVGPathGeometryFrame*>(aFrame)->GetCanvasTM();
+}
+
+gfxMatrix
+nsSVGUtils::GetUserToCanvasTM(nsIFrame *aFrame)
+{
+ nsISVGChildFrame* svgFrame = do_QueryFrame(aFrame);
+ NS_ASSERTION(svgFrame, "bad frame");
+
+ gfxMatrix tm;
+ if (svgFrame) {
+ nsSVGElement *content = static_cast<nsSVGElement*>(aFrame->GetContent());
+ tm = content->PrependLocalTransformsTo(
+ GetCanvasTM(aFrame->GetParent()),
+ eUserSpaceToParent);
+ }
+ return tm;
+}
+
+void
+nsSVGUtils::NotifyChildrenOfSVGChange(nsIFrame *aFrame, uint32_t aFlags)
+{
+ for (nsIFrame* kid : aFrame->PrincipalChildList()) {
+ nsISVGChildFrame* SVGFrame = do_QueryFrame(kid);
+ if (SVGFrame) {
+ SVGFrame->NotifySVGChanged(aFlags);
+ } else {
+ NS_ASSERTION(kid->IsFrameOfType(nsIFrame::eSVG) || kid->IsSVGText(),
+ "SVG frame expected");
+ // recurse into the children of container frames e.g. <clipPath>, <mask>
+ // in case they have child frames with transformation matrices
+ if (kid->IsFrameOfType(nsIFrame::eSVG)) {
+ NotifyChildrenOfSVGChange(kid, aFlags);
+ }
+ }
+ }
+}
+
+// ************************************************************
+
+class SVGPaintCallback : public nsSVGFilterPaintCallback
+{
+public:
+ virtual DrawResult Paint(gfxContext& aContext, nsIFrame *aTarget,
+ const gfxMatrix& aTransform,
+ const nsIntRect* aDirtyRect) override
+ {
+ nsISVGChildFrame *svgChildFrame = do_QueryFrame(aTarget);
+ NS_ASSERTION(svgChildFrame, "Expected SVG frame here");
+
+ nsIntRect* dirtyRect = nullptr;
+ nsIntRect tmpDirtyRect;
+
+ // aDirtyRect is in user-space pixels, we need to convert to
+ // outer-SVG-frame-relative device pixels.
+ if (aDirtyRect) {
+ gfxMatrix userToDeviceSpace = aTransform;
+ if (userToDeviceSpace.IsSingular()) {
+ return DrawResult::SUCCESS;
+ }
+ gfxRect dirtyBounds = userToDeviceSpace.TransformBounds(
+ gfxRect(aDirtyRect->x, aDirtyRect->y, aDirtyRect->width, aDirtyRect->height));
+ dirtyBounds.RoundOut();
+ if (gfxUtils::GfxRectToIntRect(dirtyBounds, &tmpDirtyRect)) {
+ dirtyRect = &tmpDirtyRect;
+ }
+ }
+
+ return svgChildFrame->PaintSVG(aContext, aTransform, dirtyRect);
+ }
+};
+
+float
+nsSVGUtils::ComputeOpacity(nsIFrame* aFrame, bool aHandleOpacity)
+{
+ float opacity = aFrame->StyleEffects()->mOpacity;
+
+ if (opacity != 1.0f &&
+ (nsSVGUtils::CanOptimizeOpacity(aFrame) || !aHandleOpacity)) {
+ return 1.0f;
+ }
+
+ return opacity;
+}
+
+void
+nsSVGUtils::DetermineMaskUsage(nsIFrame* aFrame, bool aHandleOpacity,
+ MaskUsage& aUsage)
+{
+ aUsage.opacity = ComputeOpacity(aFrame, aHandleOpacity);
+
+ nsIFrame* firstFrame =
+ nsLayoutUtils::FirstContinuationOrIBSplitSibling(aFrame);
+
+ nsSVGEffects::EffectProperties effectProperties =
+ nsSVGEffects::GetEffectProperties(firstFrame);
+ const nsStyleSVGReset *svgReset = firstFrame->StyleSVGReset();
+
+ nsTArray<nsSVGMaskFrame*> maskFrames = effectProperties.GetMaskFrames();
+
+#ifdef MOZ_ENABLE_MASK_AS_SHORTHAND
+ // For a HTML doc:
+ // According to css-masking spec, always create a mask surface when we
+ // have any item in maskFrame even if all of those items are
+ // non-resolvable <mask-sources> or <images>, we still need to create a
+ // transparent black mask layer under this condition.
+ // For a SVG doc:
+ // SVG 1.1 say that if we fail to resolve a mask, we should draw the
+ // object unmasked.
+ aUsage.shouldGenerateMaskLayer =
+ (aFrame->GetStateBits() & NS_FRAME_SVG_LAYOUT)
+ ? maskFrames.Length() == 1 && maskFrames[0]
+ : maskFrames.Length() > 0;
+#else
+ // Since we do not support image mask so far, we should treat any
+ // unresolvable mask as no mask. Otherwise, any object with a valid image
+ // mask, e.g. url("xxx.png"), will become invisible just because we can not
+ // handle image mask correctly. (See bug 1294171)
+ aUsage.shouldGenerateMaskLayer = maskFrames.Length() == 1 && maskFrames[0];
+#endif
+
+ bool isOK = effectProperties.HasNoFilterOrHasValidFilter();
+ nsSVGClipPathFrame *clipPathFrame = effectProperties.GetClipPathFrame(&isOK);
+ MOZ_ASSERT_IF(clipPathFrame,
+ svgReset->mClipPath.GetType() == StyleShapeSourceType::URL);
+
+ switch (svgReset->mClipPath.GetType()) {
+ case StyleShapeSourceType::URL:
+ if (clipPathFrame) {
+ if (clipPathFrame->IsTrivial()) {
+ aUsage.shouldApplyClipPath = true;
+ } else {
+ aUsage.shouldGenerateClipMaskLayer = true;
+ }
+ }
+ break;
+ case StyleShapeSourceType::Shape:
+ case StyleShapeSourceType::Box:
+ aUsage.shouldApplyBasicShape = true;
+ break;
+ case StyleShapeSourceType::None:
+ MOZ_ASSERT(!aUsage.shouldGenerateClipMaskLayer &&
+ !aUsage.shouldApplyClipPath && !aUsage.shouldApplyBasicShape);
+ break;
+ default:
+ MOZ_ASSERT_UNREACHABLE("Unsupported clip-path type.");
+ break;
+ }
+}
+
+static IntRect
+ComputeClipExtsInDeviceSpace(gfxContext& aCtx)
+{
+ gfxContextMatrixAutoSaveRestore matRestore(&aCtx);
+
+ // Get the clip extents in device space.
+ aCtx.SetMatrix(gfxMatrix());
+ gfxRect clippedFrameSurfaceRect = aCtx.GetClipExtents();
+ clippedFrameSurfaceRect.RoundOut();
+
+ IntRect result;
+ ToRect(clippedFrameSurfaceRect).ToIntRect(&result);
+ return mozilla::gfx::Factory::CheckSurfaceSize(result.Size()) ? result
+ : IntRect();
+}
+
+static already_AddRefed<gfxContext>
+CreateBlendTarget(gfxContext* aContext, IntPoint& aTargetOffset)
+{
+ // Create a temporary context to draw to so we can blend it back with
+ // another operator.
+ IntRect drawRect = ComputeClipExtsInDeviceSpace(*aContext);
+
+ RefPtr<DrawTarget> targetDT =
+ aContext->GetDrawTarget()->CreateSimilarDrawTarget(drawRect.Size(),
+ SurfaceFormat::B8G8R8A8);
+ if (!targetDT || !targetDT->IsValid()) {
+ return nullptr;
+ }
+
+ RefPtr<gfxContext> target = gfxContext::CreateOrNull(targetDT);
+ MOZ_ASSERT(target); // already checked the draw target above
+ target->SetMatrix(aContext->CurrentMatrix() *
+ gfxMatrix::Translation(-drawRect.TopLeft()));
+ aTargetOffset = drawRect.TopLeft();
+
+ return target.forget();
+}
+
+static void
+BlendToTarget(nsIFrame* aFrame, gfxContext* aSource, gfxContext* aTarget,
+ const IntPoint& aTargetOffset)
+{
+ MOZ_ASSERT(aFrame->StyleEffects()->mMixBlendMode != NS_STYLE_BLEND_NORMAL);
+
+ RefPtr<DrawTarget> targetDT = aTarget->GetDrawTarget();
+ RefPtr<SourceSurface> targetSurf = targetDT->Snapshot();
+
+ gfxContextAutoSaveRestore save(aSource);
+ aSource->SetMatrix(gfxMatrix()); // This will be restored right after.
+ RefPtr<gfxPattern> pattern = new gfxPattern(targetSurf, Matrix::Translation(aTargetOffset.x, aTargetOffset.y));
+ aSource->SetPattern(pattern);
+ aSource->Paint();
+}
+
+DrawResult
+nsSVGUtils::PaintFrameWithEffects(nsIFrame *aFrame,
+ gfxContext& aContext,
+ const gfxMatrix& aTransform,
+ const nsIntRect *aDirtyRect)
+{
+ NS_ASSERTION(!NS_SVGDisplayListPaintingEnabled() ||
+ (aFrame->GetStateBits() & NS_FRAME_IS_NONDISPLAY) ||
+ aFrame->PresContext()->IsGlyph(),
+ "If display lists are enabled, only painting of non-display "
+ "SVG should take this code path");
+
+ nsISVGChildFrame *svgChildFrame = do_QueryFrame(aFrame);
+ if (!svgChildFrame)
+ return DrawResult::SUCCESS;
+
+ MaskUsage maskUsage;
+ DetermineMaskUsage(aFrame, true, maskUsage);
+ if (maskUsage.opacity == 0.0f) {
+ return DrawResult::SUCCESS;
+ }
+
+ const nsIContent* content = aFrame->GetContent();
+ if (content->IsSVGElement() &&
+ !static_cast<const nsSVGElement*>(content)->HasValidDimensions()) {
+ return DrawResult::SUCCESS;
+ }
+
+ if (aDirtyRect &&
+ !(aFrame->GetStateBits() & NS_FRAME_IS_NONDISPLAY)) {
+ // Here we convert aFrame's paint bounds to outer-<svg> device space,
+ // compare it to aDirtyRect, and return early if they don't intersect.
+ // We don't do this optimization for nondisplay SVG since nondisplay
+ // SVG doesn't maintain bounds/overflow rects.
+ nsRect overflowRect = aFrame->GetVisualOverflowRectRelativeToSelf();
+ if (aFrame->IsFrameOfType(nsIFrame::eSVGGeometry) ||
+ aFrame->IsSVGText()) {
+ // Unlike containers, leaf frames do not include GetPosition() in
+ // GetCanvasTM().
+ overflowRect = overflowRect + aFrame->GetPosition();
+ }
+ int32_t appUnitsPerDevPx = aFrame->PresContext()->AppUnitsPerDevPixel();
+ gfxMatrix tm = aTransform;
+ if (aFrame->IsFrameOfType(nsIFrame::eSVG | nsIFrame::eSVGContainer)) {
+ gfx::Matrix childrenOnlyTM;
+ if (static_cast<nsSVGContainerFrame*>(aFrame)->
+ HasChildrenOnlyTransform(&childrenOnlyTM)) {
+ // Undo the children-only transform:
+ if (!childrenOnlyTM.Invert()) {
+ return DrawResult::SUCCESS;
+ }
+ tm = ThebesMatrix(childrenOnlyTM) * tm;
+ }
+ }
+ nsIntRect bounds = TransformFrameRectToOuterSVG(overflowRect,
+ tm, aFrame->PresContext()).
+ ToOutsidePixels(appUnitsPerDevPx);
+ if (!aDirtyRect->Intersects(bounds)) {
+ return DrawResult::SUCCESS;
+ }
+ }
+
+ /* SVG defines the following rendering model:
+ *
+ * 1. Render fill
+ * 2. Render stroke
+ * 3. Render markers
+ * 4. Apply filter
+ * 5. Apply clipping, masking, group opacity
+ *
+ * We follow this, but perform a couple of optimizations:
+ *
+ * + Use cairo's clipPath when representable natively (single object
+ * clip region).
+ *f
+ * + Merge opacity and masking if both used together.
+ */
+
+ /* Properties are added lazily and may have been removed by a restyle,
+ so make sure all applicable ones are set again. */
+ nsSVGEffects::EffectProperties effectProperties =
+ nsSVGEffects::GetEffectProperties(aFrame);
+ bool isOK = effectProperties.HasNoFilterOrHasValidFilter();
+ nsSVGClipPathFrame *clipPathFrame = effectProperties.GetClipPathFrame(&isOK);
+ nsSVGMaskFrame *maskFrame = effectProperties.GetFirstMaskFrame(&isOK);
+ if (!isOK) {
+ // Some resource is invalid. We shouldn't paint anything.
+ return DrawResult::SUCCESS;
+ }
+
+ // These are used if we require a temporary surface for a custom blend mode.
+ // Clip the source context first, so that we can generate a smaller temporary
+ // surface. (Since we will clip this context in SetupContextMatrix, a pair
+ // of save/restore is needed.)
+ aContext.Save();
+ if (!(aFrame->GetStateBits() & NS_FRAME_IS_NONDISPLAY)) {
+ // aFrame has a valid visual overflow rect, so clip to it before calling
+ // PushGroup() to minimize the size of the surfaces we'll composite:
+ gfxContextMatrixAutoSaveRestore matrixAutoSaveRestore(&aContext);
+ aContext.Multiply(aTransform);
+ nsRect overflowRect = aFrame->GetVisualOverflowRectRelativeToSelf();
+ if (aFrame->IsFrameOfType(nsIFrame::eSVGGeometry) ||
+ aFrame->IsSVGText()) {
+ // Unlike containers, leaf frames do not include GetPosition() in
+ // GetCanvasTM().
+ overflowRect = overflowRect + aFrame->GetPosition();
+ }
+ aContext.Clip(NSRectToSnappedRect(overflowRect,
+ aFrame->PresContext()->AppUnitsPerDevPixel(),
+ *aContext.GetDrawTarget()));
+ }
+ IntPoint targetOffset;
+ RefPtr<gfxContext> target =
+ (aFrame->StyleEffects()->mMixBlendMode == NS_STYLE_BLEND_NORMAL)
+ ? RefPtr<gfxContext>(&aContext).forget()
+ : CreateBlendTarget(&aContext, targetOffset);
+ aContext.Restore();
+
+ if (!target) {
+ return DrawResult::TEMPORARY_ERROR;
+ }
+
+ /* Check if we need to do additional operations on this child's
+ * rendering, which necessitates rendering into another surface. */
+ bool shouldGenerateMask = (maskUsage.opacity != 1.0f ||
+ maskUsage.shouldGenerateClipMaskLayer ||
+ maskUsage.shouldGenerateMaskLayer ||
+ aFrame->StyleEffects()->mMixBlendMode != NS_STYLE_BLEND_NORMAL);
+
+ if (shouldGenerateMask) {
+ Matrix maskTransform;
+ RefPtr<SourceSurface> maskSurface;
+
+ if (maskUsage.shouldGenerateMaskLayer) {
+ maskSurface =
+ maskFrame->GetMaskForMaskedFrame(&aContext, aFrame, aTransform,
+ maskUsage.opacity, &maskTransform);
+
+ if (!maskSurface) {
+ // Entire surface is clipped out.
+ return DrawResult::SUCCESS;
+ }
+ }
+
+ if (maskUsage.shouldGenerateClipMaskLayer) {
+ Matrix clippedMaskTransform;
+ RefPtr<SourceSurface> clipMaskSurface =
+ clipPathFrame->GetClipMask(aContext, aFrame, aTransform,
+ &clippedMaskTransform, maskSurface,
+ maskTransform);
+
+ if (clipMaskSurface) {
+ maskSurface = clipMaskSurface;
+ maskTransform = clippedMaskTransform;
+ }
+ }
+
+ // SVG mask multiply opacity into maskSurface already, so we do not bother
+ // to apply opacity again.
+ float opacity = maskFrame ? 1.0 : maskUsage.opacity;
+ target->PushGroupForBlendBack(gfxContentType::COLOR_ALPHA, opacity,
+ maskSurface, maskTransform);
+ }
+
+ /* If this frame has only a trivial clipPath, set up cairo's clipping now so
+ * we can just do normal painting and get it clipped appropriately.
+ */
+ if (maskUsage.shouldApplyClipPath || maskUsage.shouldApplyBasicShape) {
+ if (maskUsage.shouldApplyClipPath) {
+ clipPathFrame->ApplyClipPath(aContext, aFrame, aTransform);
+ } else {
+ nsCSSClipPathInstance::ApplyBasicShapeClip(aContext, aFrame);
+ }
+ }
+
+ DrawResult result = DrawResult::SUCCESS;
+
+ /* Paint the child */
+ if (effectProperties.HasValidFilter()) {
+ nsRegion* dirtyRegion = nullptr;
+ nsRegion tmpDirtyRegion;
+ if (aDirtyRect) {
+ // aDirtyRect is in outer-<svg> device pixels, but the filter code needs
+ // it in frame space.
+ gfxMatrix userToDeviceSpace = GetUserToCanvasTM(aFrame);
+ if (userToDeviceSpace.IsSingular()) {
+ return DrawResult::SUCCESS;
+ }
+ gfxMatrix deviceToUserSpace = userToDeviceSpace;
+ deviceToUserSpace.Invert();
+ gfxRect dirtyBounds = deviceToUserSpace.TransformBounds(
+ gfxRect(aDirtyRect->x, aDirtyRect->y,
+ aDirtyRect->width, aDirtyRect->height));
+ tmpDirtyRegion =
+ nsLayoutUtils::RoundGfxRectToAppRect(
+ dirtyBounds, aFrame->PresContext()->AppUnitsPerCSSPixel()) -
+ aFrame->GetPosition();
+ dirtyRegion = &tmpDirtyRegion;
+ }
+ SVGPaintCallback paintCallback;
+ nsFilterInstance::PaintFilteredFrame(aFrame, target->GetDrawTarget(),
+ aTransform, &paintCallback,
+ dirtyRegion);
+ } else {
+ result = svgChildFrame->PaintSVG(*target, aTransform, aDirtyRect);
+ }
+
+ if (maskUsage.shouldApplyClipPath || maskUsage.shouldApplyBasicShape) {
+ aContext.PopClip();
+ }
+
+ if (shouldGenerateMask) {
+ target->PopGroupAndBlend();
+ }
+
+ if (aFrame->StyleEffects()->mMixBlendMode != NS_STYLE_BLEND_NORMAL) {
+ MOZ_ASSERT(target != &aContext);
+ BlendToTarget(aFrame, &aContext, target, targetOffset);
+ }
+
+ return result;
+}
+
+bool
+nsSVGUtils::HitTestClip(nsIFrame *aFrame, const gfxPoint &aPoint)
+{
+ nsSVGEffects::EffectProperties props =
+ nsSVGEffects::GetEffectProperties(aFrame);
+ if (!props.mClipPath) {
+ const nsStyleSVGReset *style = aFrame->StyleSVGReset();
+ if (style->HasClipPath()) {
+ return nsCSSClipPathInstance::HitTestBasicShapeClip(aFrame, aPoint);
+ }
+ return true;
+ }
+
+ bool isOK = true;
+ nsSVGClipPathFrame *clipPathFrame = props.GetClipPathFrame(&isOK);
+ if (!isOK) {
+ // clipPath is not a valid resource, so nothing gets painted, so
+ // hit-testing must fail.
+ return false;
+ }
+ if (!clipPathFrame) {
+ // clipPath doesn't exist, ignore it.
+ return true;
+ }
+
+ return clipPathFrame->PointIsInsideClipPath(aFrame, aPoint);
+}
+
+nsIFrame *
+nsSVGUtils::HitTestChildren(nsSVGDisplayContainerFrame* aFrame,
+ const gfxPoint& aPoint)
+{
+ // First we transform aPoint into the coordinate space established by aFrame
+ // for its children (e.g. take account of any 'viewBox' attribute):
+ gfxPoint point = aPoint;
+ if (aFrame->GetContent()->IsSVGElement()) { // must check before cast
+ gfxMatrix m = static_cast<const nsSVGElement*>(aFrame->GetContent())->
+ PrependLocalTransformsTo(gfxMatrix(),
+ eChildToUserSpace);
+ if (!m.IsIdentity()) {
+ if (!m.Invert()) {
+ return nullptr;
+ }
+ point = m.Transform(point);
+ }
+ }
+
+ // Traverse the list in reverse order, so that if we get a hit we know that's
+ // the topmost frame that intersects the point; then we can just return it.
+ nsIFrame* result = nullptr;
+ for (nsIFrame* current = aFrame->PrincipalChildList().LastChild();
+ current;
+ current = current->GetPrevSibling()) {
+ nsISVGChildFrame* SVGFrame = do_QueryFrame(current);
+ if (SVGFrame) {
+ const nsIContent* content = current->GetContent();
+ if (content->IsSVGElement() &&
+ !static_cast<const nsSVGElement*>(content)->HasValidDimensions()) {
+ continue;
+ }
+ // GetFrameForPoint() expects a point in its frame's SVG user space, so
+ // we need to convert to that space:
+ gfxPoint p = point;
+ if (content->IsSVGElement()) { // must check before cast
+ gfxMatrix m = static_cast<const nsSVGElement*>(content)->
+ PrependLocalTransformsTo(gfxMatrix(),
+ eUserSpaceToParent);
+ if (!m.IsIdentity()) {
+ if (!m.Invert()) {
+ continue;
+ }
+ p = m.Transform(p);
+ }
+ }
+ result = SVGFrame->GetFrameForPoint(p);
+ if (result)
+ break;
+ }
+ }
+
+ if (result && !HitTestClip(aFrame, aPoint))
+ result = nullptr;
+
+ return result;
+}
+
+nsRect
+nsSVGUtils::GetCoveredRegion(const nsFrameList &aFrames)
+{
+ nsRect rect;
+
+ for (nsIFrame* kid = aFrames.FirstChild();
+ kid;
+ kid = kid->GetNextSibling()) {
+ nsISVGChildFrame* child = do_QueryFrame(kid);
+ if (child) {
+ nsRect childRect = child->GetCoveredRegion();
+ rect.UnionRect(rect, childRect);
+ }
+ }
+
+ return rect;
+}
+
+nsRect
+nsSVGUtils::TransformFrameRectToOuterSVG(const nsRect& aRect,
+ const gfxMatrix& aMatrix,
+ nsPresContext* aPresContext)
+{
+ gfxRect r(aRect.x, aRect.y, aRect.width, aRect.height);
+ r.Scale(1.0 / nsPresContext::AppUnitsPerCSSPixel());
+ return nsLayoutUtils::RoundGfxRectToAppRect(
+ aMatrix.TransformBounds(r), aPresContext->AppUnitsPerDevPixel());
+}
+
+IntSize
+nsSVGUtils::ConvertToSurfaceSize(const gfxSize& aSize,
+ bool *aResultOverflows)
+{
+ IntSize surfaceSize(ClampToInt(ceil(aSize.width)), ClampToInt(ceil(aSize.height)));
+
+ *aResultOverflows = surfaceSize.width != ceil(aSize.width) ||
+ surfaceSize.height != ceil(aSize.height);
+
+ if (!Factory::CheckSurfaceSize(surfaceSize)) {
+ surfaceSize.width = std::min(NS_SVG_OFFSCREEN_MAX_DIMENSION,
+ surfaceSize.width);
+ surfaceSize.height = std::min(NS_SVG_OFFSCREEN_MAX_DIMENSION,
+ surfaceSize.height);
+ *aResultOverflows = true;
+ }
+
+ return surfaceSize;
+}
+
+bool
+nsSVGUtils::HitTestRect(const gfx::Matrix &aMatrix,
+ float aRX, float aRY, float aRWidth, float aRHeight,
+ float aX, float aY)
+{
+ gfx::Rect rect(aRX, aRY, aRWidth, aRHeight);
+ if (rect.IsEmpty() || aMatrix.IsSingular()) {
+ return false;
+ }
+ gfx::Matrix toRectSpace = aMatrix;
+ toRectSpace.Invert();
+ gfx::Point p = toRectSpace.TransformPoint(gfx::Point(aX, aY));
+ return rect.x <= p.x && p.x <= rect.XMost() &&
+ rect.y <= p.y && p.y <= rect.YMost();
+}
+
+gfxRect
+nsSVGUtils::GetClipRectForFrame(nsIFrame *aFrame,
+ float aX, float aY, float aWidth, float aHeight)
+{
+ const nsStyleDisplay* disp = aFrame->StyleDisplay();
+ const nsStyleEffects* effects = aFrame->StyleEffects();
+
+ if (!(effects->mClipFlags & NS_STYLE_CLIP_RECT)) {
+ NS_ASSERTION(effects->mClipFlags == NS_STYLE_CLIP_AUTO,
+ "We don't know about this type of clip.");
+ return gfxRect(aX, aY, aWidth, aHeight);
+ }
+
+ if (disp->mOverflowX == NS_STYLE_OVERFLOW_HIDDEN ||
+ disp->mOverflowY == NS_STYLE_OVERFLOW_HIDDEN) {
+
+ nsIntRect clipPxRect =
+ effects->mClip.ToOutsidePixels(aFrame->PresContext()->AppUnitsPerDevPixel());
+ gfxRect clipRect =
+ gfxRect(clipPxRect.x, clipPxRect.y, clipPxRect.width, clipPxRect.height);
+
+ if (NS_STYLE_CLIP_RIGHT_AUTO & effects->mClipFlags) {
+ clipRect.width = aWidth - clipRect.X();
+ }
+ if (NS_STYLE_CLIP_BOTTOM_AUTO & effects->mClipFlags) {
+ clipRect.height = aHeight - clipRect.Y();
+ }
+
+ if (disp->mOverflowX != NS_STYLE_OVERFLOW_HIDDEN) {
+ clipRect.x = aX;
+ clipRect.width = aWidth;
+ }
+ if (disp->mOverflowY != NS_STYLE_OVERFLOW_HIDDEN) {
+ clipRect.y = aY;
+ clipRect.height = aHeight;
+ }
+
+ return clipRect;
+ }
+ return gfxRect(aX, aY, aWidth, aHeight);
+}
+
+void
+nsSVGUtils::SetClipRect(gfxContext *aContext,
+ const gfxMatrix &aCTM,
+ const gfxRect &aRect)
+{
+ if (aCTM.IsSingular())
+ return;
+
+ gfxContextMatrixAutoSaveRestore matrixAutoSaveRestore(aContext);
+ aContext->Multiply(aCTM);
+ aContext->Clip(aRect);
+}
+
+gfxRect
+nsSVGUtils::GetBBox(nsIFrame *aFrame, uint32_t aFlags)
+{
+ if (aFrame->GetContent()->IsNodeOfType(nsINode::eTEXT)) {
+ aFrame = aFrame->GetParent();
+ }
+ gfxRect bbox;
+ nsISVGChildFrame *svg = do_QueryFrame(aFrame);
+ if (svg || aFrame->IsSVGText()) {
+ // It is possible to apply a gradient, pattern, clipping path, mask or
+ // filter to text. When one of these facilities is applied to text
+ // the bounding box is the entire text element in all
+ // cases.
+ if (aFrame->IsSVGText()) {
+ nsIFrame* ancestor = GetFirstNonAAncestorFrame(aFrame);
+ if (ancestor && ancestor->IsSVGText()) {
+ while (ancestor->GetType() != nsGkAtoms::svgTextFrame) {
+ ancestor = ancestor->GetParent();
+ }
+ }
+ svg = do_QueryFrame(ancestor);
+ }
+ nsIContent* content = aFrame->GetContent();
+ if (content->IsSVGElement() &&
+ !static_cast<const nsSVGElement*>(content)->HasValidDimensions()) {
+ return bbox;
+ }
+
+ FrameProperties props = aFrame->Properties();
+
+ if (aFlags == eBBoxIncludeFillGeometry) {
+ gfxRect* prop = props.Get(ObjectBoundingBoxProperty());
+ if (prop) {
+ return *prop;
+ }
+ }
+
+ gfxMatrix matrix;
+ if (aFrame->GetType() == nsGkAtoms::svgForeignObjectFrame ||
+ aFrame->GetType() == nsGkAtoms::svgUseFrame) {
+ // The spec says getBBox "Returns the tight bounding box in *current user
+ // space*". So we should really be doing this for all elements, but that
+ // needs investigation to check that we won't break too much content.
+ // NOTE: When changing this to apply to other frame types, make sure to
+ // also update nsSVGUtils::FrameSpaceInCSSPxToUserSpaceOffset.
+ MOZ_ASSERT(content->IsSVGElement(), "bad cast");
+ nsSVGElement *element = static_cast<nsSVGElement*>(content);
+ matrix = element->PrependLocalTransformsTo(matrix, eChildToUserSpace);
+ }
+ bbox = svg->GetBBoxContribution(ToMatrix(matrix), aFlags).ToThebesRect();
+ // Account for 'clipped'.
+ if (aFlags & nsSVGUtils::eBBoxIncludeClipped) {
+ gfxRect clipRect(0, 0, 0, 0);
+ float x, y, width, height;
+ gfxMatrix tm;
+ gfxRect fillBBox =
+ svg->GetBBoxContribution(ToMatrix(tm),
+ nsSVGUtils::eBBoxIncludeFill).ToThebesRect();
+ x = fillBBox.x;
+ y = fillBBox.y;
+ width = fillBBox.width;
+ height = fillBBox.height;
+ bool hasClip = aFrame->StyleDisplay()->IsScrollableOverflow();
+ if (hasClip) {
+ clipRect =
+ nsSVGUtils::GetClipRectForFrame(aFrame, x, y, width, height);
+ if (aFrame->GetType() == nsGkAtoms::svgForeignObjectFrame ||
+ aFrame->GetType() == nsGkAtoms::svgUseFrame) {
+ clipRect = matrix.TransformBounds(clipRect);
+ }
+ }
+ nsSVGEffects::EffectProperties effectProperties =
+ nsSVGEffects::GetEffectProperties(aFrame);
+ bool isOK = true;
+ nsSVGClipPathFrame *clipPathFrame =
+ effectProperties.GetClipPathFrame(&isOK);
+ if (clipPathFrame && isOK) {
+ SVGClipPathElement *clipContent =
+ static_cast<SVGClipPathElement*>(clipPathFrame->GetContent());
+ RefPtr<SVGAnimatedEnumeration> units = clipContent->ClipPathUnits();
+ if (units->AnimVal() == SVG_UNIT_TYPE_OBJECTBOUNDINGBOX) {
+ matrix.Translate(gfxPoint(x, y));
+ matrix.Scale(width, height);
+ } else if (aFrame->GetType() == nsGkAtoms::svgForeignObjectFrame) {
+ matrix.Reset();
+ }
+ bbox =
+ clipPathFrame->GetBBoxForClipPathFrame(bbox, matrix).ToThebesRect();
+ if (hasClip) {
+ bbox = bbox.Intersect(clipRect);
+ }
+ } else {
+ if (!isOK) {
+ bbox = gfxRect(0, 0, 0, 0);
+ } else {
+ if (hasClip) {
+ bbox = bbox.Intersect(clipRect);
+ }
+ }
+ }
+ if (bbox.IsEmpty()) {
+ bbox = gfxRect(0, 0, 0, 0);
+ }
+ }
+
+ if (aFlags == eBBoxIncludeFillGeometry) {
+ // Obtaining the bbox for objectBoundingBox calculations is common so we
+ // cache the result for future calls, since calculation can be expensive:
+ props.Set(ObjectBoundingBoxProperty(), new gfxRect(bbox));
+ }
+
+ return bbox;
+ }
+ return nsSVGIntegrationUtils::GetSVGBBoxForNonSVGFrame(aFrame);
+}
+
+gfxPoint
+nsSVGUtils::FrameSpaceInCSSPxToUserSpaceOffset(nsIFrame *aFrame)
+{
+ if (!(aFrame->GetStateBits() & NS_FRAME_SVG_LAYOUT)) {
+ // The user space for non-SVG frames is defined as the bounding box of the
+ // frame's border-box rects over all continuations.
+ return gfxPoint();
+ }
+
+ // Leaf frames apply their own offset inside their user space.
+ if (aFrame->IsFrameOfType(nsIFrame::eSVGGeometry) ||
+ aFrame->IsSVGText()) {
+ return nsLayoutUtils::RectToGfxRect(aFrame->GetRect(),
+ nsPresContext::AppUnitsPerCSSPixel()).TopLeft();
+ }
+
+ // For foreignObject frames, nsSVGUtils::GetBBox applies their local
+ // transform, so we need to do the same here.
+ if (aFrame->GetType() == nsGkAtoms::svgForeignObjectFrame ||
+ aFrame->GetType() == nsGkAtoms::svgUseFrame) {
+ gfxMatrix transform = static_cast<nsSVGElement*>(aFrame->GetContent())->
+ PrependLocalTransformsTo(gfxMatrix(), eChildToUserSpace);
+ NS_ASSERTION(!transform.HasNonTranslation(), "we're relying on this being an offset-only transform");
+ return transform.GetTranslation();
+ }
+
+ return gfxPoint();
+}
+
+static gfxRect
+GetBoundingBoxRelativeRect(const nsSVGLength2 *aXYWH,
+ const gfxRect& aBBox)
+{
+ return gfxRect(aBBox.x + nsSVGUtils::ObjectSpace(aBBox, &aXYWH[0]),
+ aBBox.y + nsSVGUtils::ObjectSpace(aBBox, &aXYWH[1]),
+ nsSVGUtils::ObjectSpace(aBBox, &aXYWH[2]),
+ nsSVGUtils::ObjectSpace(aBBox, &aXYWH[3]));
+}
+
+gfxRect
+nsSVGUtils::GetRelativeRect(uint16_t aUnits, const nsSVGLength2 *aXYWH,
+ const gfxRect& aBBox,
+ const UserSpaceMetrics& aMetrics)
+{
+ if (aUnits == SVG_UNIT_TYPE_OBJECTBOUNDINGBOX) {
+ return GetBoundingBoxRelativeRect(aXYWH, aBBox);
+ }
+ return gfxRect(UserSpace(aMetrics, &aXYWH[0]),
+ UserSpace(aMetrics, &aXYWH[1]),
+ UserSpace(aMetrics, &aXYWH[2]),
+ UserSpace(aMetrics, &aXYWH[3]));
+}
+
+gfxRect
+nsSVGUtils::GetRelativeRect(uint16_t aUnits, const nsSVGLength2 *aXYWH,
+ const gfxRect& aBBox, nsIFrame *aFrame)
+{
+ if (aUnits == SVG_UNIT_TYPE_OBJECTBOUNDINGBOX) {
+ return GetBoundingBoxRelativeRect(aXYWH, aBBox);
+ }
+ nsIContent* content = aFrame->GetContent();
+ if (content->IsSVGElement()) {
+ nsSVGElement* svgElement = static_cast<nsSVGElement*>(content);
+ return GetRelativeRect(aUnits, aXYWH, aBBox, SVGElementMetrics(svgElement));
+ }
+ return GetRelativeRect(aUnits, aXYWH, aBBox, NonSVGFrameUserSpaceMetrics(aFrame));
+}
+
+bool
+nsSVGUtils::CanOptimizeOpacity(nsIFrame *aFrame)
+{
+ if (!(aFrame->GetStateBits() & NS_FRAME_SVG_LAYOUT)) {
+ return false;
+ }
+ nsIAtom *type = aFrame->GetType();
+ if (type != nsGkAtoms::svgImageFrame &&
+ type != nsGkAtoms::svgPathGeometryFrame) {
+ return false;
+ }
+ if (aFrame->StyleEffects()->HasFilters()) {
+ return false;
+ }
+ // XXX The SVG WG is intending to allow fill, stroke and markers on <image>
+ if (type == nsGkAtoms::svgImageFrame) {
+ return true;
+ }
+ const nsStyleSVG *style = aFrame->StyleSVG();
+ if (style->HasMarker()) {
+ return false;
+ }
+ if (!style->HasFill() || !HasStroke(aFrame)) {
+ return true;
+ }
+ return false;
+}
+
+gfxMatrix
+nsSVGUtils::AdjustMatrixForUnits(const gfxMatrix &aMatrix,
+ nsSVGEnum *aUnits,
+ nsIFrame *aFrame)
+{
+ if (aFrame &&
+ aUnits->GetAnimValue() == SVG_UNIT_TYPE_OBJECTBOUNDINGBOX) {
+ gfxRect bbox = GetBBox(aFrame);
+ gfxMatrix tm = aMatrix;
+ tm.Translate(gfxPoint(bbox.X(), bbox.Y()));
+ tm.Scale(bbox.Width(), bbox.Height());
+ return tm;
+ }
+ return aMatrix;
+}
+
+nsIFrame*
+nsSVGUtils::GetFirstNonAAncestorFrame(nsIFrame* aStartFrame)
+{
+ for (nsIFrame *ancestorFrame = aStartFrame; ancestorFrame;
+ ancestorFrame = ancestorFrame->GetParent()) {
+ if (ancestorFrame->GetType() != nsGkAtoms::svgAFrame) {
+ return ancestorFrame;
+ }
+ }
+ return nullptr;
+}
+
+bool
+nsSVGUtils::GetNonScalingStrokeTransform(nsIFrame *aFrame,
+ gfxMatrix* aUserToOuterSVG)
+{
+ if (aFrame->GetContent()->IsNodeOfType(nsINode::eTEXT)) {
+ aFrame = aFrame->GetParent();
+ }
+
+ if (!aFrame->StyleSVGReset()->HasNonScalingStroke()) {
+ return false;
+ }
+
+ nsIContent *content = aFrame->GetContent();
+ MOZ_ASSERT(content->IsSVGElement(), "bad cast");
+
+ *aUserToOuterSVG = ThebesMatrix(SVGContentUtils::GetCTM(
+ static_cast<nsSVGElement*>(content), true));
+
+ return !aUserToOuterSVG->IsIdentity();
+}
+
+// The logic here comes from _cairo_stroke_style_max_distance_from_path
+static gfxRect
+PathExtentsToMaxStrokeExtents(const gfxRect& aPathExtents,
+ nsIFrame* aFrame,
+ double aStyleExpansionFactor,
+ const gfxMatrix& aMatrix)
+{
+ double style_expansion =
+ aStyleExpansionFactor * nsSVGUtils::GetStrokeWidth(aFrame);
+
+ gfxMatrix matrix = aMatrix;
+
+ gfxMatrix outerSVGToUser;
+ if (nsSVGUtils::GetNonScalingStrokeTransform(aFrame, &outerSVGToUser)) {
+ outerSVGToUser.Invert();
+ matrix.PreMultiply(outerSVGToUser);
+ }
+
+ double dx = style_expansion * (fabs(matrix._11) + fabs(matrix._21));
+ double dy = style_expansion * (fabs(matrix._22) + fabs(matrix._12));
+
+ gfxRect strokeExtents = aPathExtents;
+ strokeExtents.Inflate(dx, dy);
+ return strokeExtents;
+}
+
+/*static*/ gfxRect
+nsSVGUtils::PathExtentsToMaxStrokeExtents(const gfxRect& aPathExtents,
+ nsTextFrame* aFrame,
+ const gfxMatrix& aMatrix)
+{
+ NS_ASSERTION(aFrame->IsSVGText(), "expected an nsTextFrame for SVG text");
+ return ::PathExtentsToMaxStrokeExtents(aPathExtents, aFrame, 0.5, aMatrix);
+}
+
+/*static*/ gfxRect
+nsSVGUtils::PathExtentsToMaxStrokeExtents(const gfxRect& aPathExtents,
+ nsSVGPathGeometryFrame* aFrame,
+ const gfxMatrix& aMatrix)
+{
+ bool strokeMayHaveCorners =
+ !SVGContentUtils::ShapeTypeHasNoCorners(aFrame->GetContent());
+
+ // For a shape without corners the stroke can only extend half the stroke
+ // width from the path in the x/y-axis directions. For shapes with corners
+ // the stroke can extend by sqrt(1/2) (think 45 degree rotated rect, or line
+ // with stroke-linecaps="square").
+ double styleExpansionFactor = strokeMayHaveCorners ? M_SQRT1_2 : 0.5;
+
+ // The stroke can extend even further for paths that can be affected by
+ // stroke-miterlimit.
+ bool affectedByMiterlimit =
+ aFrame->GetContent()->IsAnyOfSVGElements(nsGkAtoms::path,
+ nsGkAtoms::polyline,
+ nsGkAtoms::polygon);
+
+ if (affectedByMiterlimit) {
+ const nsStyleSVG* style = aFrame->StyleSVG();
+ if (style->mStrokeLinejoin == NS_STYLE_STROKE_LINEJOIN_MITER &&
+ styleExpansionFactor < style->mStrokeMiterlimit / 2.0) {
+ styleExpansionFactor = style->mStrokeMiterlimit / 2.0;
+ }
+ }
+
+ return ::PathExtentsToMaxStrokeExtents(aPathExtents,
+ aFrame,
+ styleExpansionFactor,
+ aMatrix);
+}
+
+// ----------------------------------------------------------------------
+
+/* static */ nscolor
+nsSVGUtils::GetFallbackOrPaintColor(nsStyleContext *aStyleContext,
+ nsStyleSVGPaint nsStyleSVG::*aFillOrStroke)
+{
+ const nsStyleSVGPaint &paint = aStyleContext->StyleSVG()->*aFillOrStroke;
+ nsStyleContext *styleIfVisited = aStyleContext->GetStyleIfVisited();
+ bool isServer = paint.Type() == eStyleSVGPaintType_Server ||
+ paint.Type() == eStyleSVGPaintType_ContextFill ||
+ paint.Type() == eStyleSVGPaintType_ContextStroke;
+ nscolor color = isServer ? paint.GetFallbackColor() : paint.GetColor();
+ if (styleIfVisited) {
+ const nsStyleSVGPaint &paintIfVisited =
+ styleIfVisited->StyleSVG()->*aFillOrStroke;
+ // To prevent Web content from detecting if a user has visited a URL
+ // (via URL loading triggered by paint servers or performance
+ // differences between paint servers or between a paint server and a
+ // color), we do not allow whether links are visited to change which
+ // paint server is used or switch between paint servers and simple
+ // colors. A :visited style may only override a simple color with
+ // another simple color.
+ if (paintIfVisited.Type() == eStyleSVGPaintType_Color &&
+ paint.Type() == eStyleSVGPaintType_Color) {
+ nscolor colors[2] = { color, paintIfVisited.GetColor() };
+ return nsStyleContext::CombineVisitedColors(
+ colors, aStyleContext->RelevantLinkVisited());
+ }
+ }
+ return color;
+}
+
+/* static */ void
+nsSVGUtils::MakeFillPatternFor(nsIFrame* aFrame,
+ gfxContext* aContext,
+ GeneralPattern* aOutPattern,
+ SVGContextPaint* aContextPaint)
+{
+ const nsStyleSVG* style = aFrame->StyleSVG();
+ if (style->mFill.Type() == eStyleSVGPaintType_None) {
+ return;
+ }
+
+ const float opacity = aFrame->StyleEffects()->mOpacity;
+
+ float fillOpacity = GetOpacity(style->FillOpacitySource(),
+ style->mFillOpacity,
+ aContextPaint);
+ if (opacity < 1.0f &&
+ nsSVGUtils::CanOptimizeOpacity(aFrame)) {
+ // Combine the group opacity into the fill opacity (we will have skipped
+ // creating an offscreen surface to apply the group opacity).
+ fillOpacity *= opacity;
+ }
+
+ const DrawTarget* dt = aContext->GetDrawTarget();
+
+ nsSVGPaintServerFrame *ps =
+ nsSVGEffects::GetPaintServer(aFrame, &nsStyleSVG::mFill,
+ nsSVGEffects::FillProperty());
+ if (ps) {
+ RefPtr<gfxPattern> pattern =
+ ps->GetPaintServerPattern(aFrame, dt, aContext->CurrentMatrix(),
+ &nsStyleSVG::mFill, fillOpacity);
+ if (pattern) {
+ pattern->CacheColorStops(dt);
+ aOutPattern->Init(*pattern->GetPattern(dt));
+ return;
+ }
+ }
+
+ if (aContextPaint) {
+ RefPtr<gfxPattern> pattern;
+ switch (style->mFill.Type()) {
+ case eStyleSVGPaintType_ContextFill:
+ pattern = aContextPaint->GetFillPattern(dt, fillOpacity,
+ aContext->CurrentMatrix());
+ break;
+ case eStyleSVGPaintType_ContextStroke:
+ pattern = aContextPaint->GetStrokePattern(dt, fillOpacity,
+ aContext->CurrentMatrix());
+ break;
+ default:
+ ;
+ }
+ if (pattern) {
+ aOutPattern->Init(*pattern->GetPattern(dt));
+ return;
+ }
+ }
+
+ // On failure, use the fallback colour in case we have an
+ // objectBoundingBox where the width or height of the object is zero.
+ // See http://www.w3.org/TR/SVG11/coords.html#ObjectBoundingBox
+ Color color(Color::FromABGR(GetFallbackOrPaintColor(aFrame->StyleContext(),
+ &nsStyleSVG::mFill)));
+ color.a *= fillOpacity;
+ aOutPattern->InitColorPattern(ToDeviceColor(color));
+}
+
+/* static */ void
+nsSVGUtils::MakeStrokePatternFor(nsIFrame* aFrame,
+ gfxContext* aContext,
+ GeneralPattern* aOutPattern,
+ SVGContextPaint* aContextPaint)
+{
+ const nsStyleSVG* style = aFrame->StyleSVG();
+ if (style->mStroke.Type() == eStyleSVGPaintType_None) {
+ return;
+ }
+
+ const float opacity = aFrame->StyleEffects()->mOpacity;
+
+ float strokeOpacity = GetOpacity(style->StrokeOpacitySource(),
+ style->mStrokeOpacity,
+ aContextPaint);
+ if (opacity < 1.0f &&
+ nsSVGUtils::CanOptimizeOpacity(aFrame)) {
+ // Combine the group opacity into the stroke opacity (we will have skipped
+ // creating an offscreen surface to apply the group opacity).
+ strokeOpacity *= opacity;
+ }
+
+ const DrawTarget* dt = aContext->GetDrawTarget();
+
+ nsSVGPaintServerFrame *ps =
+ nsSVGEffects::GetPaintServer(aFrame, &nsStyleSVG::mStroke,
+ nsSVGEffects::StrokeProperty());
+ if (ps) {
+ RefPtr<gfxPattern> pattern =
+ ps->GetPaintServerPattern(aFrame, dt, aContext->CurrentMatrix(),
+ &nsStyleSVG::mStroke, strokeOpacity);
+ if (pattern) {
+ pattern->CacheColorStops(dt);
+ aOutPattern->Init(*pattern->GetPattern(dt));
+ return;
+ }
+ }
+
+ if (aContextPaint) {
+ RefPtr<gfxPattern> pattern;
+ switch (style->mStroke.Type()) {
+ case eStyleSVGPaintType_ContextFill:
+ pattern = aContextPaint->GetFillPattern(dt, strokeOpacity,
+ aContext->CurrentMatrix());
+ break;
+ case eStyleSVGPaintType_ContextStroke:
+ pattern = aContextPaint->GetStrokePattern(dt, strokeOpacity,
+ aContext->CurrentMatrix());
+ break;
+ default:
+ ;
+ }
+ if (pattern) {
+ aOutPattern->Init(*pattern->GetPattern(dt));
+ return;
+ }
+ }
+
+ // On failure, use the fallback colour in case we have an
+ // objectBoundingBox where the width or height of the object is zero.
+ // See http://www.w3.org/TR/SVG11/coords.html#ObjectBoundingBox
+ Color color(Color::FromABGR(GetFallbackOrPaintColor(aFrame->StyleContext(),
+ &nsStyleSVG::mStroke)));
+ color.a *= strokeOpacity;
+ aOutPattern->InitColorPattern(ToDeviceColor(color));
+}
+
+/* static */ float
+nsSVGUtils::GetOpacity(nsStyleSVGOpacitySource aOpacityType,
+ const float& aOpacity,
+ SVGContextPaint *aContextPaint)
+{
+ float opacity = 1.0f;
+ switch (aOpacityType) {
+ case eStyleSVGOpacitySource_Normal:
+ opacity = aOpacity;
+ break;
+ case eStyleSVGOpacitySource_ContextFillOpacity:
+ if (aContextPaint) {
+ opacity = aContextPaint->GetFillOpacity();
+ } else {
+ NS_WARNING("Content used context-fill-opacity when not in a context element");
+ }
+ break;
+ case eStyleSVGOpacitySource_ContextStrokeOpacity:
+ if (aContextPaint) {
+ opacity = aContextPaint->GetStrokeOpacity();
+ } else {
+ NS_WARNING("Content used context-stroke-opacity when not in a context element");
+ }
+ break;
+ default:
+ NS_NOTREACHED("Unknown object opacity inheritance type for SVG glyph");
+ }
+ return opacity;
+}
+
+bool
+nsSVGUtils::HasStroke(nsIFrame* aFrame, SVGContextPaint* aContextPaint)
+{
+ const nsStyleSVG *style = aFrame->StyleSVG();
+ return style->HasStroke() && GetStrokeWidth(aFrame, aContextPaint) > 0;
+}
+
+float
+nsSVGUtils::GetStrokeWidth(nsIFrame* aFrame, SVGContextPaint* aContextPaint)
+{
+ const nsStyleSVG *style = aFrame->StyleSVG();
+ if (aContextPaint && style->StrokeWidthFromObject()) {
+ return aContextPaint->GetStrokeWidth();
+ }
+
+ nsIContent* content = aFrame->GetContent();
+ if (content->IsNodeOfType(nsINode::eTEXT)) {
+ content = content->GetParent();
+ }
+
+ nsSVGElement *ctx = static_cast<nsSVGElement*>(content);
+
+ return SVGContentUtils::CoordToFloat(ctx, style->mStrokeWidth);
+}
+
+static bool
+GetStrokeDashData(nsIFrame* aFrame,
+ nsTArray<gfxFloat>& aDashes,
+ gfxFloat* aDashOffset,
+ SVGContextPaint* aContextPaint)
+{
+ const nsStyleSVG* style = aFrame->StyleSVG();
+ nsIContent *content = aFrame->GetContent();
+ nsSVGElement *ctx = static_cast<nsSVGElement*>
+ (content->IsNodeOfType(nsINode::eTEXT) ?
+ content->GetParent() : content);
+
+ gfxFloat totalLength = 0.0;
+ if (aContextPaint && style->StrokeDasharrayFromObject()) {
+ aDashes = aContextPaint->GetStrokeDashArray();
+
+ for (uint32_t i = 0; i < aDashes.Length(); i++) {
+ if (aDashes[i] < 0.0) {
+ return false;
+ }
+ totalLength += aDashes[i];
+ }
+
+ } else {
+ uint32_t count = style->mStrokeDasharray.Length();
+ if (!count || !aDashes.SetLength(count, fallible)) {
+ return false;
+ }
+
+ gfxFloat pathScale = 1.0;
+
+ if (content->IsSVGElement(nsGkAtoms::path)) {
+ pathScale = static_cast<SVGPathElement*>(content)->
+ GetPathLengthScale(SVGPathElement::eForStroking);
+ if (pathScale <= 0) {
+ return false;
+ }
+ }
+
+ const nsTArray<nsStyleCoord>& dasharray = style->mStrokeDasharray;
+
+ for (uint32_t i = 0; i < count; i++) {
+ aDashes[i] = SVGContentUtils::CoordToFloat(ctx,
+ dasharray[i]) * pathScale;
+ if (aDashes[i] < 0.0) {
+ return false;
+ }
+ totalLength += aDashes[i];
+ }
+ }
+
+ if (aContextPaint && style->StrokeDashoffsetFromObject()) {
+ *aDashOffset = aContextPaint->GetStrokeDashOffset();
+ } else {
+ *aDashOffset = SVGContentUtils::CoordToFloat(ctx,
+ style->mStrokeDashoffset);
+ }
+
+ return (totalLength > 0.0);
+}
+
+void
+nsSVGUtils::SetupCairoStrokeGeometry(nsIFrame* aFrame,
+ gfxContext *aContext,
+ SVGContextPaint* aContextPaint)
+{
+ float width = GetStrokeWidth(aFrame, aContextPaint);
+ if (width <= 0)
+ return;
+ aContext->SetLineWidth(width);
+
+ // Apply any stroke-specific transform
+ gfxMatrix outerSVGToUser;
+ if (GetNonScalingStrokeTransform(aFrame, &outerSVGToUser) &&
+ outerSVGToUser.Invert()) {
+ aContext->Multiply(outerSVGToUser);
+ }
+
+ const nsStyleSVG* style = aFrame->StyleSVG();
+
+ switch (style->mStrokeLinecap) {
+ case NS_STYLE_STROKE_LINECAP_BUTT:
+ aContext->SetLineCap(CapStyle::BUTT);
+ break;
+ case NS_STYLE_STROKE_LINECAP_ROUND:
+ aContext->SetLineCap(CapStyle::ROUND);
+ break;
+ case NS_STYLE_STROKE_LINECAP_SQUARE:
+ aContext->SetLineCap(CapStyle::SQUARE);
+ break;
+ }
+
+ aContext->SetMiterLimit(style->mStrokeMiterlimit);
+
+ switch (style->mStrokeLinejoin) {
+ case NS_STYLE_STROKE_LINEJOIN_MITER:
+ aContext->SetLineJoin(JoinStyle::MITER_OR_BEVEL);
+ break;
+ case NS_STYLE_STROKE_LINEJOIN_ROUND:
+ aContext->SetLineJoin(JoinStyle::ROUND);
+ break;
+ case NS_STYLE_STROKE_LINEJOIN_BEVEL:
+ aContext->SetLineJoin(JoinStyle::BEVEL);
+ break;
+ }
+
+ AutoTArray<gfxFloat, 10> dashes;
+ gfxFloat dashOffset;
+ if (GetStrokeDashData(aFrame, dashes, &dashOffset, aContextPaint)) {
+ aContext->SetDash(dashes.Elements(), dashes.Length(), dashOffset);
+ }
+}
+
+uint16_t
+nsSVGUtils::GetGeometryHitTestFlags(nsIFrame* aFrame)
+{
+ uint16_t flags = 0;
+
+ switch (aFrame->StyleUserInterface()->mPointerEvents) {
+ case NS_STYLE_POINTER_EVENTS_NONE:
+ break;
+ case NS_STYLE_POINTER_EVENTS_AUTO:
+ case NS_STYLE_POINTER_EVENTS_VISIBLEPAINTED:
+ if (aFrame->StyleVisibility()->IsVisible()) {
+ if (aFrame->StyleSVG()->mFill.Type() != eStyleSVGPaintType_None)
+ flags |= SVG_HIT_TEST_FILL;
+ if (aFrame->StyleSVG()->mStroke.Type() != eStyleSVGPaintType_None)
+ flags |= SVG_HIT_TEST_STROKE;
+ if (aFrame->StyleSVG()->mStrokeOpacity > 0)
+ flags |= SVG_HIT_TEST_CHECK_MRECT;
+ }
+ break;
+ case NS_STYLE_POINTER_EVENTS_VISIBLEFILL:
+ if (aFrame->StyleVisibility()->IsVisible()) {
+ flags |= SVG_HIT_TEST_FILL;
+ }
+ break;
+ case NS_STYLE_POINTER_EVENTS_VISIBLESTROKE:
+ if (aFrame->StyleVisibility()->IsVisible()) {
+ flags |= SVG_HIT_TEST_STROKE;
+ }
+ break;
+ case NS_STYLE_POINTER_EVENTS_VISIBLE:
+ if (aFrame->StyleVisibility()->IsVisible()) {
+ flags |= SVG_HIT_TEST_FILL | SVG_HIT_TEST_STROKE;
+ }
+ break;
+ case NS_STYLE_POINTER_EVENTS_PAINTED:
+ if (aFrame->StyleSVG()->mFill.Type() != eStyleSVGPaintType_None)
+ flags |= SVG_HIT_TEST_FILL;
+ if (aFrame->StyleSVG()->mStroke.Type() != eStyleSVGPaintType_None)
+ flags |= SVG_HIT_TEST_STROKE;
+ if (aFrame->StyleSVG()->mStrokeOpacity)
+ flags |= SVG_HIT_TEST_CHECK_MRECT;
+ break;
+ case NS_STYLE_POINTER_EVENTS_FILL:
+ flags |= SVG_HIT_TEST_FILL;
+ break;
+ case NS_STYLE_POINTER_EVENTS_STROKE:
+ flags |= SVG_HIT_TEST_STROKE;
+ break;
+ case NS_STYLE_POINTER_EVENTS_ALL:
+ flags |= SVG_HIT_TEST_FILL | SVG_HIT_TEST_STROKE;
+ break;
+ default:
+ NS_ERROR("not reached");
+ break;
+ }
+
+ return flags;
+}
+
+bool
+nsSVGUtils::PaintSVGGlyph(Element* aElement, gfxContext* aContext)
+{
+ nsIFrame* frame = aElement->GetPrimaryFrame();
+ nsISVGChildFrame* svgFrame = do_QueryFrame(frame);
+ if (!svgFrame) {
+ return false;
+ }
+ gfxMatrix m;
+ if (frame->GetContent()->IsSVGElement()) {
+ // PaintSVG() expects the passed transform to be the transform to its own
+ // SVG user space, so we need to account for any 'transform' attribute:
+ m = static_cast<nsSVGElement*>(frame->GetContent())->
+ PrependLocalTransformsTo(gfxMatrix(), eUserSpaceToParent);
+ }
+ DrawResult result = svgFrame->PaintSVG(*aContext, m);
+ return (result == DrawResult::SUCCESS);
+}
+
+bool
+nsSVGUtils::GetSVGGlyphExtents(Element* aElement,
+ const gfxMatrix& aSVGToAppSpace,
+ gfxRect* aResult)
+{
+ nsIFrame* frame = aElement->GetPrimaryFrame();
+ nsISVGChildFrame* svgFrame = do_QueryFrame(frame);
+ if (!svgFrame) {
+ return false;
+ }
+
+ gfxMatrix transform(aSVGToAppSpace);
+ nsIContent* content = frame->GetContent();
+ if (content->IsSVGElement()) {
+ transform = static_cast<nsSVGElement*>(content)->
+ PrependLocalTransformsTo(aSVGToAppSpace);
+ }
+
+ *aResult = svgFrame->GetBBoxContribution(gfx::ToMatrix(transform),
+ nsSVGUtils::eBBoxIncludeFill | nsSVGUtils::eBBoxIncludeFillGeometry |
+ nsSVGUtils::eBBoxIncludeStroke | nsSVGUtils::eBBoxIncludeStrokeGeometry |
+ nsSVGUtils::eBBoxIncludeMarkers).ToThebesRect();
+ return true;
+}
+
+nsRect
+nsSVGUtils::ToCanvasBounds(const gfxRect &aUserspaceRect,
+ const gfxMatrix &aToCanvas,
+ const nsPresContext *presContext)
+{
+ return nsLayoutUtils::RoundGfxRectToAppRect(
+ aToCanvas.TransformBounds(aUserspaceRect),
+ presContext->AppUnitsPerDevPixel());
+}
diff --git a/layout/svg/nsSVGUtils.h b/layout/svg/nsSVGUtils.h
new file mode 100644
index 0000000000..edfc43bee7
--- /dev/null
+++ b/layout/svg/nsSVGUtils.h
@@ -0,0 +1,600 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef NS_SVGUTILS_H
+#define NS_SVGUTILS_H
+
+// include math.h to pick up definition of M_ maths defines e.g. M_PI
+#include <math.h>
+
+#include "DrawMode.h"
+#include "gfx2DGlue.h"
+#include "gfxMatrix.h"
+#include "gfxPoint.h"
+#include "gfxRect.h"
+#include "mozilla/gfx/Rect.h"
+#include "nsAlgorithm.h"
+#include "nsChangeHint.h"
+#include "nsColor.h"
+#include "nsCOMPtr.h"
+#include "nsID.h"
+#include "nsIFrame.h"
+#include "nsISupportsBase.h"
+#include "nsMathUtils.h"
+#include "nsStyleStruct.h"
+#include <algorithm>
+
+class gfxContext;
+class nsFrameList;
+class nsIContent;
+class nsIDocument;
+class nsIFrame;
+class nsPresContext;
+class nsStyleContext;
+class nsStyleSVGPaint;
+class nsSVGDisplayContainerFrame;
+class nsSVGElement;
+class nsSVGEnum;
+class nsSVGLength2;
+class nsSVGOuterSVGFrame;
+class nsSVGPathGeometryFrame;
+class nsTextFrame;
+
+struct nsStyleSVG;
+struct nsRect;
+
+namespace mozilla {
+class SVGContextPaint;
+struct SVGContextPaintImpl;
+namespace dom {
+class Element;
+class UserSpaceMetrics;
+} // namespace dom
+namespace gfx {
+class DrawTarget;
+class GeneralPattern;
+} // namespace gfx
+} // namespace mozilla
+
+// maximum dimension of an offscreen surface - choose so that
+// the surface size doesn't overflow a 32-bit signed int using
+// 4 bytes per pixel; in line with Factory::CheckSurfaceSize
+// In fact Macs can't even manage that
+#define NS_SVG_OFFSCREEN_MAX_DIMENSION 4096
+
+#define SVG_HIT_TEST_FILL 0x01
+#define SVG_HIT_TEST_STROKE 0x02
+#define SVG_HIT_TEST_CHECK_MRECT 0x04
+
+
+bool NS_SVGPathCachingEnabled();
+bool NS_SVGDisplayListHitTestingEnabled();
+bool NS_SVGDisplayListPaintingEnabled();
+bool NS_SVGNewGetBBoxEnabled();
+
+/**
+ * Sometimes we need to distinguish between an empty box and a box
+ * that contains an element that has no size e.g. a point at the origin.
+ */
+class SVGBBox {
+ typedef mozilla::gfx::Rect Rect;
+
+public:
+ SVGBBox()
+ : mIsEmpty(true) {}
+
+ MOZ_IMPLICIT SVGBBox(const Rect& aRect)
+ : mBBox(aRect), mIsEmpty(false) {}
+
+ MOZ_IMPLICIT SVGBBox(const gfxRect& aRect)
+ : mBBox(ToRect(aRect)), mIsEmpty(false) {}
+
+ gfxRect ToThebesRect() const {
+ return ThebesRect(mBBox);
+ }
+
+ bool IsEmpty() const {
+ return mIsEmpty;
+ }
+
+ bool IsFinite() const {
+ return mBBox.IsFinite();
+ }
+
+ void Scale(float aScale) {
+ mBBox.Scale(aScale);
+ }
+
+ void UnionEdges(const SVGBBox& aSVGBBox) {
+ if (aSVGBBox.mIsEmpty) {
+ return;
+ }
+ mBBox = mIsEmpty ? aSVGBBox.mBBox : mBBox.UnionEdges(aSVGBBox.mBBox);
+ mIsEmpty = false;
+ }
+
+ void Intersect(const SVGBBox& aSVGBBox) {
+ if (!mIsEmpty && !aSVGBBox.mIsEmpty) {
+ mBBox = mBBox.Intersect(aSVGBBox.mBBox);
+ if (mBBox.IsEmpty()) {
+ mIsEmpty = true;
+ mBBox = Rect(0, 0, 0, 0);
+ }
+ } else {
+ mIsEmpty = true;
+ mBBox = Rect(0, 0, 0, 0);
+ }
+ }
+
+private:
+ Rect mBBox;
+ bool mIsEmpty;
+};
+
+// GRRR WINDOWS HATE HATE HATE
+#undef CLIP_MASK
+
+class MOZ_RAII SVGAutoRenderState
+{
+ typedef mozilla::gfx::DrawTarget DrawTarget;
+
+public:
+ explicit SVGAutoRenderState(DrawTarget* aDrawTarget
+ MOZ_GUARD_OBJECT_NOTIFIER_PARAM);
+ ~SVGAutoRenderState();
+
+ void SetPaintingToWindow(bool aPaintingToWindow);
+
+ static bool IsPaintingToWindow(DrawTarget* aDrawTarget);
+
+private:
+ DrawTarget* mDrawTarget;
+ void* mOriginalRenderState;
+ bool mPaintingToWindow;
+ MOZ_DECL_USE_GUARD_OBJECT_NOTIFIER
+};
+
+
+#define NS_ISVGFILTERREFERENCE_IID \
+{ 0x9744ee20, 0x1bcf, 0x4c62, \
+ { 0x86, 0x7d, 0xd3, 0x7a, 0x91, 0x60, 0x3e, 0xef } }
+
+class nsISVGFilterReference : public nsISupports
+{
+public:
+ NS_DECLARE_STATIC_IID_ACCESSOR(NS_ISVGFILTERREFERENCE_IID)
+ virtual void Invalidate() = 0;
+};
+
+NS_DEFINE_STATIC_IID_ACCESSOR(nsISVGFilterReference, NS_ISVGFILTERREFERENCE_IID)
+
+/**
+ * General functions used by all of SVG layout and possibly content code.
+ * If a method is used by content and depends only on other content methods
+ * it should go in SVGContentUtils instead.
+ */
+class nsSVGUtils
+{
+public:
+ typedef mozilla::dom::Element Element;
+ typedef mozilla::gfx::AntialiasMode AntialiasMode;
+ typedef mozilla::gfx::DrawTarget DrawTarget;
+ typedef mozilla::gfx::FillRule FillRule;
+ typedef mozilla::gfx::GeneralPattern GeneralPattern;
+ typedef mozilla::gfx::Size Size;
+ typedef mozilla::SVGContextPaint SVGContextPaint;
+ typedef mozilla::SVGContextPaintImpl SVGContextPaintImpl;
+ typedef mozilla::image::DrawResult DrawResult;
+
+ static void Init();
+
+ NS_DECLARE_FRAME_PROPERTY_DELETABLE(ObjectBoundingBoxProperty, gfxRect)
+
+ /**
+ * Gets the nearest nsSVGInnerSVGFrame or nsSVGOuterSVGFrame frame. aFrame
+ * must be an SVG frame. If aFrame is of type nsGkAtoms::svgOuterSVGFrame,
+ * returns nullptr.
+ */
+ static nsSVGDisplayContainerFrame* GetNearestSVGViewport(nsIFrame *aFrame);
+
+ /**
+ * Returns the frame's post-filter visual overflow rect when passed the
+ * frame's pre-filter visual overflow rect. If the frame is not currently
+ * being filtered, this function simply returns aUnfilteredRect.
+ */
+ static nsRect GetPostFilterVisualOverflowRect(nsIFrame *aFrame,
+ const nsRect &aUnfilteredRect);
+
+ /**
+ * Schedules an update of the frame's bounds (which will in turn invalidate
+ * the new area that the frame should paint to).
+ *
+ * This does nothing when passed an NS_FRAME_IS_NONDISPLAY frame.
+ * In future we may want to allow ReflowSVG to be called on such frames,
+ * but that would be better implemented as a ForceReflowSVG function to
+ * be called synchronously while painting them without marking or paying
+ * attention to dirty bits like this function.
+ *
+ * This is very similar to PresShell::FrameNeedsReflow. The main reason that
+ * we have this function instead of using FrameNeedsReflow is because we need
+ * to be able to call it under nsSVGOuterSVGFrame::NotifyViewportChange when
+ * that function is called by nsSVGOuterSVGFrame::Reflow. FrameNeedsReflow
+ * is not suitable for calling during reflow though, and it asserts as much.
+ * The reason that we want to be callable under NotifyViewportChange is
+ * because we want to synchronously notify and dirty the nsSVGOuterSVGFrame's
+ * children so that when nsSVGOuterSVGFrame::DidReflow is called its children
+ * will be updated for the new size as appropriate. Otherwise we'd have to
+ * post an event to the event loop to mark dirty flags and request an update.
+ *
+ * Another reason that we don't currently want to call
+ * PresShell::FrameNeedsReflow is because passing eRestyle to it to get it to
+ * mark descendants dirty would cause it to descend through
+ * nsSVGForeignObjectFrame frames to mark their children dirty, but we want to
+ * handle nsSVGForeignObjectFrame specially. It would also do unnecessary work
+ * descending into NS_FRAME_IS_NONDISPLAY frames.
+ */
+ static void ScheduleReflowSVG(nsIFrame *aFrame);
+
+ /**
+ * Returns true if the frame or any of its children need ReflowSVG
+ * to be called on them.
+ */
+ static bool NeedsReflowSVG(nsIFrame *aFrame);
+
+ /*
+ * Update the filter invalidation region for ancestor frames, if relevant.
+ */
+ static void NotifyAncestorsOfFilterRegionChange(nsIFrame *aFrame);
+
+ /**
+ * Percentage lengths in SVG are resolved against the width/height of the
+ * nearest viewport (or its viewBox, if set). This helper returns the size
+ * of this "context" for the given frame so that percentage values can be
+ * resolved.
+ */
+ static Size GetContextSize(const nsIFrame* aFrame);
+
+ /* Computes the input length in terms of object space coordinates.
+ Input: rect - bounding box
+ length - length to be converted
+ */
+ static float ObjectSpace(const gfxRect &aRect, const nsSVGLength2 *aLength);
+
+ /* Computes the input length in terms of user space coordinates.
+ Input: content - object to be used for determining user space
+ Input: length - length to be converted
+ */
+ static float UserSpace(nsSVGElement *aSVGElement, const nsSVGLength2 *aLength);
+ static float UserSpace(nsIFrame *aFrame, const nsSVGLength2 *aLength);
+ static float UserSpace(const mozilla::dom::UserSpaceMetrics& aMetrics, const nsSVGLength2 *aLength);
+
+ /* Find the outermost SVG frame of the passed frame */
+ static nsSVGOuterSVGFrame *
+ GetOuterSVGFrame(nsIFrame *aFrame);
+
+ /**
+ * Get the covered region for a frame. Return null if it's not an SVG frame.
+ * @param aRect gets a rectangle in app units
+ * @return the outer SVG frame which aRect is relative to
+ */
+ static nsIFrame*
+ GetOuterSVGFrameAndCoveredRegion(nsIFrame* aFrame, nsRect* aRect);
+
+ /* Paint SVG frame with SVG effects - aDirtyRect is the area being
+ * redrawn, in device pixel coordinates relative to the outer svg */
+ static DrawResult
+ PaintFrameWithEffects(nsIFrame *aFrame,
+ gfxContext& aContext,
+ const gfxMatrix& aTransform,
+ const nsIntRect *aDirtyRect = nullptr);
+
+ /* Hit testing - check if point hits the clipPath of indicated
+ * frame. Returns true if no clipPath set. */
+ static bool
+ HitTestClip(nsIFrame *aFrame, const gfxPoint &aPoint);
+
+ /**
+ * Hit testing - check if point hits any children of aFrame. aPoint is
+ * expected to be in the coordinate space established by aFrame for its
+ * children (e.g. the space established by the 'viewBox' attribute on <svg>).
+ */
+ static nsIFrame *
+ HitTestChildren(nsSVGDisplayContainerFrame *aFrame, const gfxPoint &aPoint);
+
+ /*
+ * Returns the CanvasTM of the indicated frame, whether it's a
+ * child SVG frame, container SVG frame, or a regular frame.
+ * For regular frames, we just return an identity matrix.
+ */
+ static gfxMatrix GetCanvasTM(nsIFrame* aFrame);
+
+ /**
+ * Returns the transform from aFrame's user space to canvas space. Only call
+ * with SVG frames. This is like GetCanvasTM, except that it only includes
+ * the transforms from aFrame's user space (i.e. the coordinate context
+ * established by its 'transform' attribute, or else the coordinate context
+ * that its _parent_ establishes for its children) to outer-<svg> device
+ * space. Specifically, it does not include any other transforms introduced
+ * by the frame such as x/y offsets and viewBox attributes.
+ */
+ static gfxMatrix GetUserToCanvasTM(nsIFrame* aFrame);
+
+ /**
+ * Notify the descendants of aFrame of a change to one of their ancestors
+ * that might affect them.
+ */
+ static void
+ NotifyChildrenOfSVGChange(nsIFrame *aFrame, uint32_t aFlags);
+
+ /*
+ * Get frame's covered region by walking the children and doing union.
+ */
+ static nsRect
+ GetCoveredRegion(const nsFrameList &aFrames);
+
+ static nsRect
+ TransformFrameRectToOuterSVG(const nsRect& aRect,
+ const gfxMatrix& aMatrix,
+ nsPresContext* aPresContext);
+
+ /*
+ * Convert a surface size to an integer for use by thebes
+ * possibly making it smaller in the process so the surface does not
+ * use excessive memory.
+ *
+ * @param aSize the desired surface size
+ * @param aResultOverflows true if the desired surface size is too big
+ * @return the surface size to use
+ */
+ static mozilla::gfx::IntSize ConvertToSurfaceSize(const gfxSize& aSize,
+ bool *aResultOverflows);
+
+ /*
+ * Hit test a given rectangle/matrix.
+ */
+ static bool
+ HitTestRect(const mozilla::gfx::Matrix &aMatrix,
+ float aRX, float aRY, float aRWidth, float aRHeight,
+ float aX, float aY);
+
+
+ /**
+ * Get the clip rect for the given frame, taking into account the CSS 'clip'
+ * property. See:
+ * http://www.w3.org/TR/SVG11/masking.html#OverflowAndClipProperties
+ * The arguments for aX, aY, aWidth and aHeight should be the dimensions of
+ * the viewport established by aFrame.
+ */
+ static gfxRect
+ GetClipRectForFrame(nsIFrame *aFrame,
+ float aX, float aY, float aWidth, float aHeight);
+
+ static void SetClipRect(gfxContext *aContext,
+ const gfxMatrix &aCTM,
+ const gfxRect &aRect);
+
+ /* Using group opacity instead of fill or stroke opacity on a
+ * geometry object seems to be a common authoring mistake. If we're
+ * not applying filters and not both stroking and filling, we can
+ * generate the same result without going through the overhead of a
+ * push/pop group. */
+ static bool
+ CanOptimizeOpacity(nsIFrame *aFrame);
+
+ /**
+ * Take the CTM to userspace for an element, and adjust it to a CTM to its
+ * object bounding box space if aUnits is SVG_UNIT_TYPE_OBJECTBOUNDINGBOX.
+ * (I.e. so that [0,0] is at the top left of its bbox, and [1,1] is at the
+ * bottom right of its bbox).
+ *
+ * If the bbox is empty, this will return a singular matrix.
+ */
+ static gfxMatrix
+ AdjustMatrixForUnits(const gfxMatrix &aMatrix,
+ nsSVGEnum *aUnits,
+ nsIFrame *aFrame);
+
+ enum BBoxFlags {
+ eBBoxIncludeFill = 1 << 0,
+ eBBoxIncludeFillGeometry = 1 << 1,
+ eBBoxIncludeStroke = 1 << 2,
+ eBBoxIncludeStrokeGeometry = 1 << 3,
+ eBBoxIncludeMarkers = 1 << 4,
+ eBBoxIncludeClipped = 1 << 5
+ };
+ /**
+ * Get the SVG bbox (the SVG spec's simplified idea of bounds) of aFrame in
+ * aFrame's userspace.
+ */
+ static gfxRect GetBBox(nsIFrame *aFrame,
+ // If the default arg changes, update the handling for
+ // ObjectBoundingBoxProperty() in the implementation.
+ uint32_t aFlags = eBBoxIncludeFillGeometry);
+
+ /*
+ * "User space" is the space that the frame's BBox (as calculated by
+ * nsSVGUtils::GetBBox) is in. "Frame space" is the space that has its origin
+ * at the top left of the union of the frame's border-box rects over all
+ * continuations.
+ * This function returns the offset one needs to add to something in frame
+ * space in order to get its coordinates in user space.
+ */
+ static gfxPoint FrameSpaceInCSSPxToUserSpaceOffset(nsIFrame *aFrame);
+
+ /**
+ * Convert a userSpaceOnUse/objectBoundingBoxUnits rectangle that's specified
+ * using four nsSVGLength2 values into a user unit rectangle in user space.
+ *
+ * @param aXYWH pointer to 4 consecutive nsSVGLength2 objects containing
+ * the x, y, width and height values in that order
+ * @param aBBox the bounding box of the object the rect is relative to;
+ * may be null if aUnits is not SVG_UNIT_TYPE_OBJECTBOUNDINGBOX
+ * @param aFrame the object in which to interpret user-space units;
+ * may be null if aUnits is SVG_UNIT_TYPE_OBJECTBOUNDINGBOX
+ */
+ static gfxRect
+ GetRelativeRect(uint16_t aUnits, const nsSVGLength2 *aXYWH,
+ const gfxRect& aBBox, nsIFrame *aFrame);
+
+ static gfxRect
+ GetRelativeRect(uint16_t aUnits, const nsSVGLength2 *aXYWH,
+ const gfxRect& aBBox,
+ const mozilla::dom::UserSpaceMetrics& aMetrics);
+
+ /**
+ * Find the first frame, starting with aStartFrame and going up its
+ * parent chain, that is not an svgAFrame.
+ */
+ static nsIFrame* GetFirstNonAAncestorFrame(nsIFrame* aStartFrame);
+
+ static bool OuterSVGIsCallingReflowSVG(nsIFrame *aFrame);
+ static bool AnyOuterSVGIsCallingReflowSVG(nsIFrame *aFrame);
+
+ /**
+ * See https://svgwg.org/svg2-draft/painting.html#NonScalingStroke
+ *
+ * If the computed value of the 'vector-effect' property on aFrame is
+ * 'non-scaling-stroke', then this function will set aUserToOuterSVG to the
+ * transform from aFrame's SVG user space to the initial coordinate system
+ * established by the viewport of aFrame's outer-<svg>'s (the coordinate
+ * system in which the stroke is fixed). If aUserToOuterSVG is set to a
+ * non-identity matrix this function returns true, else it returns false.
+ */
+ static bool GetNonScalingStrokeTransform(nsIFrame *aFrame,
+ gfxMatrix* aUserToOuterSVG);
+
+ /**
+ * Compute the maximum possible device space stroke extents of a path given
+ * the path's device space path extents, its stroke style and its ctm.
+ *
+ * This is a workaround for the lack of suitable cairo API for getting the
+ * tight device space stroke extents of a path. This basically gives us the
+ * tightest extents that we can guarantee fully enclose the inked stroke
+ * without doing the calculations for the actual tight extents. We exploit
+ * the fact that cairo does have an API for getting the tight device space
+ * fill/path extents.
+ *
+ * This should die once bug 478152 is fixed.
+ */
+ static gfxRect PathExtentsToMaxStrokeExtents(const gfxRect& aPathExtents,
+ nsTextFrame* aFrame,
+ const gfxMatrix& aMatrix);
+ static gfxRect PathExtentsToMaxStrokeExtents(const gfxRect& aPathExtents,
+ nsSVGPathGeometryFrame* aFrame,
+ const gfxMatrix& aMatrix);
+
+ /**
+ * Convert a floating-point value to a 32-bit integer value, clamping to
+ * the range of valid integers.
+ */
+ static int32_t ClampToInt(double aVal)
+ {
+ return NS_lround(std::max(double(INT32_MIN),
+ std::min(double(INT32_MAX), aVal)));
+ }
+
+ static nscolor GetFallbackOrPaintColor(nsStyleContext *aStyleContext,
+ nsStyleSVGPaint nsStyleSVG::*aFillOrStroke);
+
+ static void
+ MakeFillPatternFor(nsIFrame *aFrame,
+ gfxContext* aContext,
+ GeneralPattern* aOutPattern,
+ SVGContextPaint* aContextPaint = nullptr);
+
+ static void
+ MakeStrokePatternFor(nsIFrame* aFrame,
+ gfxContext* aContext,
+ GeneralPattern* aOutPattern,
+ SVGContextPaint* aContextPaint = nullptr);
+
+ static float GetOpacity(nsStyleSVGOpacitySource aOpacityType,
+ const float& aOpacity,
+ SVGContextPaint* aContextPaint);
+
+ /*
+ * @return false if there is no stroke
+ */
+ static bool HasStroke(nsIFrame* aFrame,
+ SVGContextPaint* aContextPaint = nullptr);
+
+ static float GetStrokeWidth(nsIFrame* aFrame,
+ SVGContextPaint* aContextPaint = nullptr);
+
+ /*
+ * Set up a cairo context for a stroked path (including any dashing that
+ * applies).
+ */
+ static void SetupCairoStrokeGeometry(nsIFrame* aFrame, gfxContext *aContext,
+ SVGContextPaint* aContextPaint = nullptr);
+
+ /**
+ * This function returns a set of bit flags indicating which parts of the
+ * element (fill, stroke, bounds) should intercept pointer events. It takes
+ * into account the type of element and the value of the 'pointer-events'
+ * property on the element.
+ */
+ static uint16_t GetGeometryHitTestFlags(nsIFrame* aFrame);
+
+ static FillRule ToFillRule(mozilla::StyleFillRule aFillRule) {
+ return aFillRule == mozilla::StyleFillRule::Evenodd ?
+ FillRule::FILL_EVEN_ODD : FillRule::FILL_WINDING;
+ }
+
+ static AntialiasMode ToAntialiasMode(uint8_t aTextRendering) {
+ return aTextRendering == NS_STYLE_TEXT_RENDERING_OPTIMIZESPEED ?
+ AntialiasMode::NONE : AntialiasMode::SUBPIXEL;
+ }
+
+ /**
+ * Render a SVG glyph.
+ * @param aElement the SVG glyph element to render
+ * @param aContext the thebes aContext to draw to
+ * @return true if rendering succeeded
+ */
+ static bool PaintSVGGlyph(Element* aElement, gfxContext* aContext);
+ /**
+ * Get the extents of a SVG glyph.
+ * @param aElement the SVG glyph element
+ * @param aSVGToAppSpace the matrix mapping the SVG glyph space to the
+ * target context space
+ * @param aResult the result (valid when true is returned)
+ * @return true if calculating the extents succeeded
+ */
+ static bool GetSVGGlyphExtents(Element* aElement,
+ const gfxMatrix& aSVGToAppSpace,
+ gfxRect* aResult);
+
+ /**
+ * Returns the app unit canvas bounds of a userspace rect.
+ *
+ * @param aToCanvas Transform from userspace to canvas device space.
+ */
+ static nsRect
+ ToCanvasBounds(const gfxRect &aUserspaceRect,
+ const gfxMatrix &aToCanvas,
+ const nsPresContext *presContext);
+
+ struct MaskUsage {
+ bool shouldGenerateMaskLayer;
+ bool shouldGenerateClipMaskLayer;
+ bool shouldApplyClipPath;
+ bool shouldApplyBasicShape;
+ float opacity;
+
+ MaskUsage()
+ : shouldGenerateMaskLayer(false), shouldGenerateClipMaskLayer(false),
+ shouldApplyClipPath(false), shouldApplyBasicShape(false), opacity(0.0)
+ { }
+ };
+
+ static void
+ DetermineMaskUsage(nsIFrame* aFrame, bool aHandleOpacity, MaskUsage& aUsage);
+
+ static float
+ ComputeOpacity(nsIFrame* aFrame, bool aHandleOpacity);
+};
+
+#endif
diff --git a/layout/svg/resources/content/svgBindings.xml b/layout/svg/resources/content/svgBindings.xml
new file mode 100644
index 0000000000..15abee34d2
--- /dev/null
+++ b/layout/svg/resources/content/svgBindings.xml
@@ -0,0 +1,15 @@
+<?xml version="1.0"?>
+
+<!-- This Source Code Form is subject to the terms of the Mozilla Public
+ - License, v. 2.0. If a copy of the MPL was not distributed with this
+ - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
+
+<!-- NOTE: This file is currently unused, but it's still in the tree because
+ bug 163068 will likely involve adding content to it. -->
+<bindings id="svgBindings"
+ xmlns="http://www.mozilla.org/xbl"
+ xmlns:xbl="http://www.mozilla.org/xbl"
+ xmlns:xul="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ xmlns:svg="http://www.w3.org/2000/svg"
+ xmlns:xlink="http://www.w3.org/1999/xlink" >
+</bindings>
diff --git a/layout/svg/svg.css b/layout/svg/svg.css
new file mode 100644
index 0000000000..79aed0482c
--- /dev/null
+++ b/layout/svg/svg.css
@@ -0,0 +1,106 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+@namespace url(http://www.w3.org/2000/svg);
+@namespace xml url(http://www.w3.org/XML/1998/namespace);
+
+style, script, symbol {
+ display: none;
+}
+
+switch {
+ -moz-binding: none !important;
+}
+
+svg:not(:root), symbol, image, marker, pattern, foreignObject {
+ overflow: hidden;
+}
+
+@media all and (-moz-is-glyph) {
+ :root {
+ fill: context-fill;
+ fill-opacity: context-fill-opacity;
+ stroke: context-stroke;
+ stroke-opacity: context-stroke-opacity;
+ stroke-width: context-value;
+ stroke-dasharray: context-value;
+ stroke-dashoffset: context-value;
+ }
+}
+
+foreignObject {
+ -moz-appearance: none ! important;
+ margin: 0 ! important;
+ padding: 0 ! important;
+ border-width: 0 ! important;
+ white-space: normal;
+}
+
+@media all and (-moz-is-resource-document) {
+ foreignObject *|* {
+ -moz-appearance: none !important;
+ }
+}
+
+*|*::-moz-svg-foreign-content {
+ display: block !important;
+ /* We need to be an absolute and fixed container */
+ transform: translate(0) !important;
+ text-indent: 0;
+}
+
+/* Set |transform-origin:0 0;| for all SVG elements except outer-<svg>,
+ noting that 'svg' as a child of 'foreignObject' counts as outer-<svg>.
+*/
+*:not(svg),
+*:not(foreignObject) > svg {
+ transform-origin:0 0;
+}
+
+*|*::-moz-svg-text {
+ unicode-bidi: inherit;
+ vector-effect: inherit;
+}
+
+*[xml|space=preserve] {
+ white-space: -moz-pre-space;
+}
+
+*|*::-moz-svg-marker-anon-child {
+ clip-path: inherit;
+ filter: inherit;
+ mask: inherit;
+ opacity: inherit;
+}
+
+*:-moz-focusring {
+ /* Don't specify the outline-color, we should always use initial value. */
+ outline: 1px dotted;
+}
+
+/* nsDocumentViewer::CreateStyleSet doesn't load ua.css.
+ * A few styles are common to html and SVG though
+ * so we copy the rules below from that file.
+ */
+
+/* Links */
+
+*|*:any-link {
+ cursor: pointer;
+}
+
+*|*:any-link:-moz-focusring {
+ /* Don't specify the outline-color, we should always use initial value. */
+ outline: 1px dotted;
+}
+
+/*
+ * SVG-as-an-image needs this rule
+ */
+*|*::-moz-viewport, *|*::-moz-viewport-scroll, *|*::-moz-canvas, *|*::-moz-scrolled-canvas {
+ display: block !important;
+ background-color: inherit;
+}
diff --git a/layout/svg/tests/example.xml b/layout/svg/tests/example.xml
new file mode 100644
index 0000000000..fb82a7becb
--- /dev/null
+++ b/layout/svg/tests/example.xml
@@ -0,0 +1,227 @@
+<?xml version="1.0"?>
+<?xml-stylesheet href="svg.css" type="text/css"?>
+<!DOCTYPE svg SYSTEM "SVG-20000202.dtd" >
+<!--<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 10 January 2000//EN" -->
+<!-- "http://www.w3.org/Graphics/SVG/SVG-19991203.dtd"> -->
+<svg xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.svg"
+ xmlns:html="http://www.w3.org/1999/xhtml">
+
+ <html:script>
+<![CDATA[
+
+var gIsInit = false;
+var barXPnts = new Array();
+var barYPnts = new Array();
+
+var nodes = new Array();
+var gBarMax = 200;
+var gHeight = 80;
+var gBarCount = 10;
+var gBarDir = 1;
+var gGo = true;
+
+function init()
+{
+ dump("----------------\n");
+ nodes[0] = findNode(document.documentElement, "bar21");
+ nodes[1] = findNode(document.documentElement, "bar22");
+ nodes[2] = findNode(document.documentElement, "bar23");
+ nodes[3] = findNode(document.documentElement, "bargrid21");
+ nodes[4] = findNode(document.documentElement, "bargrid22");
+ nodes[5] = findNode(document.documentElement, "bargrid23");
+ dump("----------------\n");
+ gGo = true;
+ setTimeout("moveit()", 100);
+}
+function stop()
+{
+ gGo = false;
+}
+function ChangeBar(name, height)
+{
+ today = new Date();
+ stime = today.getMilliseconds();
+ dump("----------------\n");
+ str = name+"1";
+ node = findNode(document.documentElement, str);
+ //node = document.getElementById(str);
+ today = new Date();
+ etime = today.getMilliseconds();
+ dump("1----------------"+(etime-stime)+"\n");
+ attr = document.createAttribute("points");
+ attr.value = "30 " + height + " 30 210 50 210 50 " + height;
+ node.attributes.setNamedItem(attr);
+ today = new Date();
+ stime = today.getMilliseconds();
+ dump("2----------------"+(stime-etime)+"\n");
+
+ str = name+"2";
+ node = findNode(document.documentElement, str);
+ dump("3----------------\n");
+ attr.value = "30 " + height + " 50 " + height + " 60 " + (height-10) + " 40 " + (height-10);
+ node.attributes.setNamedItem(attr);
+ dump("4----------------\n");
+
+ str = name+"3";
+ node = findNode(document.documentElement, str);
+ dump("5----------------\n");
+ attr.value = "50 " + height + " 60 " + (height-10) + " 60 200 50 210";
+ node.attributes.setNamedItem(attr);
+ dump("=================\n");
+}
+
+function ChangeBarWithNodes(node1, node2, node3, height)
+{
+ attr = document.createAttribute("points");
+ attr.value = "30 " + height + " 30 210 50 210 50 " + height;
+ node1.attributes.setNamedItem(attr);
+
+ attr.value = "30 " + height + " 50 " + height + " 60 " + (height-10) + " 40 " + (height-10);
+ node2.attributes.setNamedItem(attr);
+
+ attr.value = "50 " + height + " 60 " + (height-10) + " 60 200 50 210";
+ node3.attributes.setNamedItem(attr);
+}
+
+function moveit()
+{
+ //ChangeBar("bar2", 150);
+ //ChangeBar("bargrid2", 150);
+
+ ChangeBarWithNodes(nodes[0], nodes[1], nodes[2], gHeight);
+ ChangeBarWithNodes(nodes[3], nodes[4], nodes[5], gHeight);
+
+ gHeight += gBarDir;
+ gBarCount--;
+ //dump("gHeight: "+gHeight+" gBarCount: "+gBarCount+" gBarDir: "+gBarDir+"\n");
+ if (gHeight > gBarMax || gHeight < 1) {
+ gBarDir *= -1;
+ gBarCount = (Math.random() * 15)+3;
+ //dump("Changining directions: "+gBarDir+"\n");
+ if (gHeight > gBarMax) {
+ gHeight = gBarMax;
+ } else {
+ gHeight = 1;
+ }
+ } else {
+ if (gBarCount < 1) {
+ gBarDir *= -1;
+ gBarCount = (Math.random() * 15)+3;
+ //dump("----> "+gBarCount+"\n");
+ }
+ }
+ if (gGo) {
+ setTimeout("moveit()", 100);
+ }
+
+}
+
+function findNode(node, nodename)
+{
+ var type = node.nodeType;
+ if (type == Node.ELEMENT_NODE) {
+
+ // open tag
+ //dump("\<" + node.tagName);
+
+ // dump the attributes if any
+ attributes = node.attributes;
+ if (null != attributes) {
+ var countAttrs = attributes.length;
+ var index = 0;
+ while(index < countAttrs) {
+ att = attributes[index];
+ if (null != att) {
+ //dump(" " + att.name + "=" + att.value+" ["+nodename+"]\n");
+ if (att.name == "id" && att.value == nodename) {
+ //dump("Found it!\n");
+ return node;
+ }
+ }
+ index++;
+ }
+ }
+
+ // recursively dump the children
+ if (node.hasChildNodes()) {
+ // close tag
+ //dump(">");
+
+ // get the children
+ var children = node.childNodes;
+ var length = children.length;
+ var count = 0;
+ while(count < length) {
+ child = children[count];
+ fndNode = findNode(child, nodename);
+ if (fndNode != null) {
+ return fndNode;
+ }
+ count++;
+ }
+ //dump("</" + node.tagName + ">");
+ } else {
+ // close tag
+ //dump("/>");
+ }
+
+
+ }
+ // if it's a piece of text just dump the text
+ else if (type == Node.TEXT_NODE) {
+ //dump(node.data);
+ }
+ return null;
+}
+
+]]>
+ </html:script>
+
+ <g>
+ <polyline x="55" y="10" id="bg" points="20 0 220 0 220 200 20 200"/>
+ <polyline x="55" y="10" id="bg" points="20 0 20 200 0 220 0 20 0 20 20 0"/>
+ <polyline x="55" y="10" id="bg" points="20 200 220 200 200 220 0 220"/>
+
+ <polyline x="55" y="10" id="grid" points="20 0 220 0 220 200 20 200"/>
+ <polyline x="55" y="10" id="grid" points="20 0 20 200 0 220 0 20 0 20"/>
+ <polyline x="55" y="10" id="grid" points="20 200 220 200 200 220 0 220"/>
+
+ <polyline x="55" y="10" id="grid" points="20 220 40 200 40 0"/>
+ <polyline x="55" y="10" id="grid" points="40 220 60 200 60 0"/>
+ <polyline x="55" y="10" id="grid" points="60 220 80 200 80 0"/>
+ <polyline x="55" y="10" id="grid" points="80 220 100 200 100 0"/>
+ <polyline x="55" y="10" id="grid" points="100 220 120 200 120 0"/>
+ <polyline x="55" y="10" id="grid" points="120 220 140 200 140 0"/>
+ <polyline x="55" y="10" id="grid" points="140 220 160 200 160 0"/>
+ <polyline x="55" y="10" id="grid" points="160 220 180 200 180 0"/>
+ <polyline x="55" y="10" id="grid" points="180 220 200 200 200 0"/>
+
+ <polygon x="55" y="10" id="bar1" points="30 60 30 210 50 210 50 60"/>
+ <polygon x="55" y="10" id="bar1" points="30 60 50 60 60 50 40 50"/>
+ <polygon x="55" y="10" id="bar1" points="50 60 60 50 60 200 50 210"/>
+
+ <polyline x="55" y="10" id="grid" points="30 60 30 210 50 210 50 60"/>
+ <polyline x="55" y="10" id="grid" points="30 60 50 60 60 50 40 50"/>
+ <polyline x="55" y="10" id="grid" points="50 60 60 50 60 200 50 210"/>
+
+
+ <polygon x="95" y="10" id="bar21" points="30 80 30 210 50 210 50 80"/>
+ <polygon x="95" y="10" id="bar22" points="30 80 50 80 60 70 40 70"/>
+ <polygon x="95" y="10" id="bar23" points="50 80 60 70 60 200 50 210"/>
+
+ <polyline x="95" y="10" id="bargrid21" points="30 80 30 210 50 210 50 80"/>
+ <polyline x="95" y="10" id="bargrid22" points="30 80 50 80 60 70 40 70"/>
+ <polyline x="95" y="10" id="bargrid23" points="50 80 60 70 60 200 50 210"/>
+
+ <polygon x="400" y="20" id="rect" points="10 10 50 10 50 50"/>
+ <polygon x="400" y="75" id="poly" points="10 10 50 10 50 50 45 70 32 32 80 20"/>
+ <polyline x="400" y="150" id="poly" points="10 10 50 10 50 50 45 70 32 32 80 20"/>
+
+ </g>
+ <foreignobject>
+ <html:div style="position:absolute;top:5px;left:385px;">Simple Polygons</html:div>
+ <html:div style="position:absolute;top:240px;left:115px;">A Simple Graph</html:div>
+ <html:input type="button" style="position:absolute;top:260px;left:350px;" onclick="init();" value="Start"/>
+ <html:input type="button" style="position:absolute;top:260px;left:390px;" onclick="stop();" value="Stop"/>
+ </foreignobject>
+</svg>
diff --git a/layout/svg/tests/svg.css b/layout/svg/tests/svg.css
new file mode 100644
index 0000000000..358811af7e
--- /dev/null
+++ b/layout/svg/tests/svg.css
@@ -0,0 +1,33 @@
+
+polygon {
+ color: black;
+}
+
+polygon[id="grid"] {
+ color: black;
+}
+
+polygon[id="bar1"] {
+ color: green;
+}
+
+polygon[id="rect"] {
+ color: blue;
+}
+
+polygon[id="bg"] {
+ color: #DDDDDD;
+}
+
+polygon[id="bar21"], polygon[id="bar22"], polygon[id="bar23"] {
+ color: #6F00DD;
+}
+
+polygon[id="bargrid21"], polygon[id="bargrid22"], polygon[id="bargrid23"] {
+ color: black;
+}
+
+polygon[id="poly"] {
+ color: #c0d0f0;
+}
+