Bug 828805 - Implement SVG paint-order property. r=bz,roc
authorCameron McCormack <cam@mcc.id.au>
Sun, 13 Jan 2013 10:27:53 +1100
changeset 118713 bfef9b308f923eba2585be8a827a945b91f90672
parent 118712 ee10b02f78df7bb47db09c4c6051595f4610dafd
child 118714 76199236555348fd585b05bb53d568353b114bc5
push id24173
push userryanvm@gmail.com
push dateSun, 13 Jan 2013 15:19:08 +0000
treeherdermozilla-central@43d65f5d22b2 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersbz, roc
bugs828805
milestone21.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 828805 - Implement SVG paint-order property. r=bz,roc
content/base/src/nsGkAtomList.h
content/svg/content/src/nsSVGElement.cpp
gfx/thebes/gfxFont.cpp
gfx/thebes/gfxFont.h
layout/base/nsStyleConsts.h
layout/reftests/svg/paint-order-01-ref.svg
layout/reftests/svg/paint-order-01.svg
layout/reftests/svg/paint-order-02-ref.svg
layout/reftests/svg/paint-order-02.svg
layout/reftests/svg/paint-order-03-ref.svg
layout/reftests/svg/paint-order-03.svg
layout/reftests/svg/reftest.list
layout/style/nsCSSKeywordList.h
layout/style/nsCSSParser.cpp
layout/style/nsCSSPropList.h
layout/style/nsCSSValue.cpp
layout/style/nsComputedDOMStyle.cpp
layout/style/nsComputedDOMStyle.h
layout/style/nsRuleNode.cpp
layout/style/nsStyleStruct.cpp
layout/style/nsStyleStruct.h
layout/style/nsStyleUtil.cpp
layout/style/nsStyleUtil.h
layout/style/test/property_database.js
layout/svg/nsSVGGlyphFrame.cpp
layout/svg/nsSVGPathGeometryFrame.cpp
layout/svg/nsSVGPathGeometryFrame.h
modules/libpref/src/init/all.js
--- a/content/base/src/nsGkAtomList.h
+++ b/content/base/src/nsGkAtomList.h
@@ -812,16 +812,17 @@ GK_ATOM(overflowchanged, "overflowchange
 GK_ATOM(overlay, "overlay")
 GK_ATOM(overlap, "overlap")
 GK_ATOM(p, "p")
 GK_ATOM(pack, "pack")
 GK_ATOM(page, "page")
 GK_ATOM(pageincrement, "pageincrement")
 GK_ATOM(pagex, "pagex")
 GK_ATOM(pagey, "pagey")
+GK_ATOM(paint_order, "paint-order")
 GK_ATOM(palettename, "palettename")
 GK_ATOM(panel, "panel")
 GK_ATOM(param, "param")
 GK_ATOM(parameter, "parameter")
 GK_ATOM(parent, "parent")
 GK_ATOM(parentfocused, "parentfocused")
 GK_ATOM(parsetype, "parsetype")
 GK_ATOM(pattern, "pattern")
--- a/content/svg/content/src/nsSVGElement.cpp
+++ b/content/svg/content/src/nsSVGElement.cpp
@@ -953,16 +953,17 @@ nsSVGElement::IsAttributeMapped(const ns
 }
 
 // PresentationAttributes-FillStroke
 /* static */ const Element::MappedAttributeEntry
 nsSVGElement::sFillStrokeMap[] = {
   { &nsGkAtoms::fill },
   { &nsGkAtoms::fill_opacity },
   { &nsGkAtoms::fill_rule },
+  { &nsGkAtoms::paint_order },
   { &nsGkAtoms::stroke },
   { &nsGkAtoms::stroke_dasharray },
   { &nsGkAtoms::stroke_dashoffset },
   { &nsGkAtoms::stroke_linecap },
   { &nsGkAtoms::stroke_linejoin },
   { &nsGkAtoms::stroke_miterlimit },
   { &nsGkAtoms::stroke_opacity },
   { &nsGkAtoms::stroke_width },
--- a/gfx/thebes/gfxFont.cpp
+++ b/gfx/thebes/gfxFont.cpp
@@ -1555,52 +1555,63 @@ struct GlyphBuffer {
                 mGlyphBuffer[i] = mGlyphBuffer[mNumGlyphs - 1 - i];
                 mGlyphBuffer[mNumGlyphs - 1 - i] = tmp;
             }
         }
 
         if (aDrawMode == gfxFont::GLYPH_PATH) {
             cairo_glyph_path(aCR, mGlyphBuffer, mNumGlyphs);
         } else {
+            if ((aDrawMode & (gfxFont::GLYPH_STROKE | gfxFont::GLYPH_STROKE_UNDERNEATH)) ==
+                             (gfxFont::GLYPH_STROKE | gfxFont::GLYPH_STROKE_UNDERNEATH)) {
+                FlushStroke(aCR, aObjectPaint, aGlobalMatrix);
+            }
             if (aDrawMode & gfxFont::GLYPH_FILL) {
                 SAMPLE_LABEL("GlyphBuffer", "cairo_show_glyphs");
                 nsRefPtr<gfxPattern> pattern;
                 if (aObjectPaint &&
                     !!(pattern = aObjectPaint->GetFillPattern(aGlobalMatrix))) {
                     cairo_save(aCR);
                     cairo_set_source(aCR, pattern->CairoPattern());
                 }
 
                 cairo_show_glyphs(aCR, mGlyphBuffer, mNumGlyphs);
 
                 if (pattern) {
                     cairo_restore(aCR);
                 }
             }
-
-            if (aDrawMode & gfxFont::GLYPH_STROKE) {
-                nsRefPtr<gfxPattern> pattern;
-                if (aObjectPaint &&
-                    !!(pattern = aObjectPaint->GetStrokePattern(aGlobalMatrix))) {
-                    cairo_save(aCR);
-                    cairo_set_source(aCR, pattern->CairoPattern());
-                }
-
-                cairo_new_path(aCR);
-                cairo_glyph_path(aCR, mGlyphBuffer, mNumGlyphs);
-                cairo_stroke(aCR);
-
-                if (pattern) {
-                    cairo_restore(aCR);
-                }
+            if ((aDrawMode & (gfxFont::GLYPH_STROKE | gfxFont::GLYPH_STROKE_UNDERNEATH)) ==
+                              gfxFont::GLYPH_STROKE) {
+                FlushStroke(aCR, aObjectPaint, aGlobalMatrix);
             }
         }
 
         mNumGlyphs = 0;
     }
+
+private:
+    void FlushStroke(cairo_t *aCR, gfxTextObjectPaint *aObjectPaint,
+                     const gfxMatrix& aGlobalMatrix) {
+        nsRefPtr<gfxPattern> pattern;
+        if (aObjectPaint &&
+            !!(pattern = aObjectPaint->GetStrokePattern(aGlobalMatrix))) {
+            cairo_save(aCR);
+            cairo_set_source(aCR, pattern->CairoPattern());
+        }
+
+        cairo_new_path(aCR);
+        cairo_glyph_path(aCR, mGlyphBuffer, mNumGlyphs);
+        cairo_stroke(aCR);
+
+        if (pattern) {
+            cairo_restore(aCR);
+        }
+    }
+
 #undef GLYPH_BUFFER_SIZE
 };
 
 static AntialiasMode Get2DAAMode(gfxFont::AntialiasOption aAAOption) {
   switch (aAAOption) {
   case gfxFont::kAntialiasSubpixel:
     return AA_SUBPIXEL;
   case gfxFont::kAntialiasGrayscale:
@@ -1640,16 +1651,20 @@ struct GlyphBufferAzure {
             std::reverse(begin, end);
         }
         
         gfx::GlyphBuffer buf;
         buf.mGlyphs = mGlyphBuffer;
         buf.mNumGlyphs = mNumGlyphs;
 
         gfxContext::AzureState state = aThebesContext->CurrentState();
+        if ((aDrawMode & (gfxFont::GLYPH_STROKE | gfxFont::GLYPH_STROKE_UNDERNEATH)) ==
+                         (gfxFont::GLYPH_STROKE | gfxFont::GLYPH_STROKE_UNDERNEATH)) {
+            FlushStroke(aDT, aObjectPaint, aFont, aThebesContext, buf, state);
+        }
         if (aDrawMode & gfxFont::GLYPH_FILL) {
             if (state.pattern || aObjectPaint) {
                 Pattern *pat;
 
                 nsRefPtr<gfxPattern> fillPattern;
                 if (!aObjectPaint ||
                     !(fillPattern = aObjectPaint->GetFillPattern(aThebesContext->CurrentMatrix()))) {
                     pat = state.pattern->GetPattern(aDT, state.patternTransformChanged ? &state.patternTransform : nullptr);
@@ -1695,29 +1710,39 @@ struct GlyphBufferAzure {
                 aDT->FillGlyphs(aFont, buf, ColorPattern(state.color),
                                 aDrawOptions, aOptions);
             }
         }
         if (aDrawMode & gfxFont::GLYPH_PATH) {
             aThebesContext->EnsurePathBuilder();
             aFont->CopyGlyphsToBuilder(buf, aThebesContext->mPathBuilder);
         }
-        if (aDrawMode & gfxFont::GLYPH_STROKE) {
-            RefPtr<Path> path = aFont->GetPathForGlyphs(buf, aDT);
-            if (aObjectPaint) {
-                nsRefPtr<gfxPattern> strokePattern =
-                  aObjectPaint->GetStrokePattern(aThebesContext->CurrentMatrix());
-                if (strokePattern) {
-                    aDT->Stroke(path, *strokePattern->GetPattern(aDT), state.strokeOptions);
-                }
-            }
+        if ((aDrawMode & (gfxFont::GLYPH_STROKE | gfxFont::GLYPH_STROKE_UNDERNEATH)) ==
+                          gfxFont::GLYPH_STROKE) {
+            FlushStroke(aDT, aObjectPaint, aFont, aThebesContext, buf, state);
         }
 
         mNumGlyphs = 0;
     }
+
+private:
+    void FlushStroke(DrawTarget *aDT, gfxTextObjectPaint *aObjectPaint,
+                     ScaledFont *aFont, gfxContext *aThebesContext,
+                     gfx::GlyphBuffer& aBuf, gfxContext::AzureState& aState)
+    {
+        RefPtr<Path> path = aFont->GetPathForGlyphs(aBuf, aDT);
+        if (aObjectPaint) {
+            nsRefPtr<gfxPattern> strokePattern =
+              aObjectPaint->GetStrokePattern(aThebesContext->CurrentMatrix());
+            if (strokePattern) {
+                aDT->Stroke(path, *strokePattern->GetPattern(aDT), aState.strokeOptions);
+            }
+        }
+    }
+
 #undef GLYPH_BUFFER_SIZE
 };
 
 // Bug 674909. When synthetic bolding text by drawing twice, need to
 // render using a pixel offset in device pixels, otherwise text
 // doesn't appear bolded, it appears as if a bad text shadow exists
 // when a non-identity transform exists.  Use an offset factor so that
 // the second draw occurs at a constant offset in device pixels.
@@ -1743,17 +1768,18 @@ gfxFont::CalcXScale(gfxContext *aContext
     return 1.0 / m;
 }
 
 void
 gfxFont::Draw(gfxTextRun *aTextRun, uint32_t aStart, uint32_t aEnd,
               gfxContext *aContext, DrawMode aDrawMode, gfxPoint *aPt,
               Spacing *aSpacing, gfxTextObjectPaint *aObjectPaint)
 {
-    NS_ASSERTION(aDrawMode <= gfxFont::GLYPH_PATH, "GLYPH_PATH cannot be used with GLYPH_FILL or GLYPH_STROKE");
+    NS_ASSERTION(aDrawMode == gfxFont::GLYPH_PATH || !(aDrawMode & gfxFont::GLYPH_PATH),
+                 "GLYPH_PATH cannot be used with GLYPH_FILL, GLYPH_STROKE or GLYPH_STROKE_UNDERNEATH");
 
     if (aStart >= aEnd)
         return;
 
     const gfxTextRun::CompressedGlyph *charGlyphs = aTextRun->GetCharacterGlyphs();
     const uint32_t appUnitsPerDevUnit = aTextRun->GetAppUnitsPerDevUnit();
     const double devUnitsPerAppUnit = 1.0/double(appUnitsPerDevUnit);
     bool isRTL = aTextRun->IsRightToLeft();
@@ -5063,17 +5089,18 @@ struct BufferAlphaColor {
 void
 gfxTextRun::Draw(gfxContext *aContext, gfxPoint aPt, gfxFont::DrawMode aDrawMode,
                  uint32_t aStart, uint32_t aLength,
                  PropertyProvider *aProvider, gfxFloat *aAdvanceWidth,
                  gfxTextObjectPaint *aObjectPaint,
                  gfxTextRun::DrawCallbacks *aCallbacks)
 {
     NS_ASSERTION(aStart + aLength <= GetLength(), "Substring out of range");
-    NS_ASSERTION(aDrawMode <= gfxFont::GLYPH_PATH, "GLYPH_PATH cannot be used with GLYPH_FILL or GLYPH_STROKE");
+    NS_ASSERTION(aDrawMode == gfxFont::GLYPH_PATH || !(aDrawMode & gfxFont::GLYPH_PATH),
+                 "GLYPH_PATH cannot be used with GLYPH_FILL, GLYPH_STROKE or GLYPH_STROKE_UNDERNEATH");
     NS_ASSERTION(aDrawMode == gfxFont::GLYPH_PATH || !aCallbacks, "callback must not be specified unless using GLYPH_PATH");
 
     gfxFloat direction = GetDirection();
 
     if (mSkipDrawing) {
         // We're waiting for a user font to finish downloading;
         // but if the caller wants advance width, we need to compute it here
         if (aAdvanceWidth) {
--- a/gfx/thebes/gfxFont.h
+++ b/gfx/thebes/gfxFont.h
@@ -1189,17 +1189,20 @@ public:
     typedef enum {
         // GLYPH_FILL and GLYPH_STROKE draw into the current context
         //  and may be used together with bitwise OR.
         GLYPH_FILL = 1,
         // Note: using GLYPH_STROKE will destroy the current path.
         GLYPH_STROKE = 2,
         // Appends glyphs to the current path. Can NOT be used with
         //  GLYPH_FILL or GLYPH_STROKE.
-        GLYPH_PATH = 4
+        GLYPH_PATH = 4,
+        // When GLYPH_FILL and GLYPH_STROKE are both set, draws the
+        //  stroke underneath the fill.
+        GLYPH_STROKE_UNDERNEATH = 8
     } DrawMode;
 
 protected:
     nsAutoRefCnt mRefCnt;
     cairo_scaled_font_t *mScaledFont;
 
     void NotifyReleased() {
         gfxFontCache *cache = gfxFontCache::GetCache();
--- a/layout/base/nsStyleConsts.h
+++ b/layout/base/nsStyleConsts.h
@@ -858,16 +858,26 @@ static inline mozilla::css::Side operato
 #define NS_STYLE_IMAGE_RENDERING_OPTIMIZESPEED    1
 #define NS_STYLE_IMAGE_RENDERING_OPTIMIZEQUALITY  2
 #define NS_STYLE_IMAGE_RENDERING_CRISPEDGES       3
 
 // mask-type
 #define NS_STYLE_MASK_TYPE_LUMINANCE            0
 #define NS_STYLE_MASK_TYPE_ALPHA                1
 
+// paint-order
+#define NS_STYLE_PAINT_ORDER_NORMAL             0
+#define NS_STYLE_PAINT_ORDER_FILL               1
+#define NS_STYLE_PAINT_ORDER_STROKE             2
+#define NS_STYLE_PAINT_ORDER_MARKERS            3
+#define NS_STYLE_PAINT_ORDER_LAST_VALUE NS_STYLE_PAINT_ORDER_MARKERS
+// NS_STYLE_PAINT_ORDER_BITWIDTH is the number of bits required to store
+// a single paint-order component value.
+#define NS_STYLE_PAINT_ORDER_BITWIDTH           2
+
 // shape-rendering
 #define NS_STYLE_SHAPE_RENDERING_AUTO               0
 #define NS_STYLE_SHAPE_RENDERING_OPTIMIZESPEED      1
 #define NS_STYLE_SHAPE_RENDERING_CRISPEDGES         2
 #define NS_STYLE_SHAPE_RENDERING_GEOMETRICPRECISION 3
 
 // stroke-linecap
 #define NS_STYLE_STROKE_LINECAP_BUTT            0
new file mode 100644
--- /dev/null
+++ b/layout/reftests/svg/paint-order-01-ref.svg
@@ -0,0 +1,32 @@
+<!--
+     Any copyright is dedicated to the Public Domain.
+     http://creativecommons.org/publicdomain/zero/1.0/
+-->
+<svg xmlns="http://www.w3.org/2000/svg">
+  <title>Reference for paint-order-01.svg</title>
+  <!-- https://bugzilla.mozilla.org/show_bug.cgi?id=XXXXXX -->
+
+  <g fill="gold" stroke="black" stroke-width="8">
+
+    <circle cx="50" cy="50" r="20" fill-opacity="1" stroke-opacity="0"/>
+    <circle cx="50" cy="50" r="20" fill-opacity="0" stroke-opacity="1"/>
+
+    <circle cx="50" cy="100" r="20" fill-opacity="0" stroke-opacity="1"/>
+    <circle cx="50" cy="100" r="20" fill-opacity="1" stroke-opacity="0"/>
+
+    <circle cx="50" cy="150" r="20" fill-opacity="0" stroke-opacity="1"/>
+    <circle cx="50" cy="150" r="20" fill-opacity="1" stroke-opacity="0"/>
+
+    <circle cx="50" cy="200" r="20" fill-opacity="0" stroke-opacity="1"/>
+    <circle cx="50" cy="200" r="20" fill-opacity="1" stroke-opacity="0"/>
+
+    <circle cx="50" cy="250" r="20" fill-opacity="0" stroke-opacity="1"/>
+    <circle cx="50" cy="250" r="20" fill-opacity="1" stroke-opacity="0"/>
+
+    <circle cx="50" cy="300" r="20" fill-opacity="0" stroke-opacity="1"/>
+    <circle cx="50" cy="300" r="20" fill-opacity="1" stroke-opacity="0"/>
+
+    <circle cx="50" cy="350" r="20" fill-opacity="1" stroke-opacity="0"/>
+    <circle cx="50" cy="350" r="20" fill-opacity="0" stroke-opacity="1"/>
+  </g>
+</svg>
new file mode 100644
--- /dev/null
+++ b/layout/reftests/svg/paint-order-01.svg
@@ -0,0 +1,18 @@
+<!--
+     Any copyright is dedicated to the Public Domain.
+     http://creativecommons.org/publicdomain/zero/1.0/
+-->
+<svg xmlns="http://www.w3.org/2000/svg">
+  <title>Test that the paint-order property works</title>
+  <!-- https://bugzilla.mozilla.org/show_bug.cgi?id=XXXXXX -->
+
+  <g fill="gold" stroke="black" stroke-width="8">
+    <circle cx="50" cy="50" r="20"/>
+    <circle cx="50" cy="100" r="20" style="paint-order: stroke fill markers"/>
+    <circle cx="50" cy="150" r="20" paint-order="stroke fill markers"/>
+    <circle cx="50" cy="200" r="20" paint-order="stroke markers fill"/>
+    <circle cx="50" cy="250" r="20" paint-order="stroke fill"/>
+    <circle cx="50" cy="300" r="20" paint-order="stroke"/>
+    <circle cx="50" cy="350" r="20" paint-order="normal"/>
+  </g>
+</svg>
new file mode 100644
--- /dev/null
+++ b/layout/reftests/svg/paint-order-02-ref.svg
@@ -0,0 +1,28 @@
+<!--
+     Any copyright is dedicated to the Public Domain.
+     http://creativecommons.org/publicdomain/zero/1.0/
+-->
+<svg xmlns="http://www.w3.org/2000/svg">
+  <title>Reference for paint-order-02.svg</title>
+  <!-- https://bugzilla.mozilla.org/show_bug.cgi?id=XXXXXX -->
+
+  <g fill="gold" stroke="black" stroke-width="8" paint-order="stroke">
+    <ellipse cx="50" cy="50" rx="40" ry="20" fill-opacity="0" stroke-opacity="1"/>
+    <ellipse cx="50" cy="50" rx="40" ry="20" fill-opacity="1" stroke-opacity="0"/>
+
+    <path d="M 50,100 h 40 v 30 z" fill-opacity="0" stroke-opacity="1"/>
+    <path d="M 50,100 h 40 v 30 z" fill-opacity="1" stroke-opacity="0"/>
+
+    <polygon points="50,150 90,150 90,180" fill-opacity="0" stroke-opacity="1"/>
+    <polygon points="50,150 90,150 90,180" fill-opacity="1" stroke-opacity="0"/>
+
+    <polyline points="50,200 90,200 90,230" fill-opacity="0" stroke-opacity="1"/>
+    <polyline points="50,200 90,200 90,230" fill-opacity="1" stroke-opacity="0"/>
+
+    <rect x="50" y="250" width="80" height="30" r="16" fill-opacity="0" stroke-opacity="1"/>
+    <rect x="50" y="250" width="80" height="30" r="16" fill-opacity="1" stroke-opacity="0"/>
+
+    <text x="50" y="350" font-size="80" stroke-width="4" fill-opacity="0" stroke-opacity="1">hello <tspan fill-opacity="1" stroke-opacity="0">there</tspan></text>
+    <text x="50" y="350" font-size="80" stroke-width="4" fill-opacity="1" stroke-opacity="0">hello <tspan fill-opacity="0" stroke-opacity="1">there</tspan></text>
+  </g>
+</svg>
new file mode 100644
--- /dev/null
+++ b/layout/reftests/svg/paint-order-02.svg
@@ -0,0 +1,17 @@
+<!--
+     Any copyright is dedicated to the Public Domain.
+     http://creativecommons.org/publicdomain/zero/1.0/
+-->
+<svg xmlns="http://www.w3.org/2000/svg">
+  <title>Test that the paint-order property works</title>
+  <!-- https://bugzilla.mozilla.org/show_bug.cgi?id=XXXXXX -->
+
+  <g fill="gold" stroke="black" stroke-width="8" paint-order="stroke">
+    <ellipse cx="50" cy="50" rx="40" ry="20"/>
+    <path d="M 50,100 h 40 v 30 z"/>
+    <polygon points="50,150 90,150 90,180"/>
+    <polyline points="50,200 90,200 90,230"/>
+    <rect x="50" y="250" width="80" height="30" r="16"/>
+    <text x="50" y="350" font-size="80" stroke-width="4">hello <tspan paint-order="normal">there</tspan></text>
+  </g>
+</svg>
new file mode 100644
--- /dev/null
+++ b/layout/reftests/svg/paint-order-03-ref.svg
@@ -0,0 +1,32 @@
+<!--
+     Any copyright is dedicated to the Public Domain.
+     http://creativecommons.org/publicdomain/zero/1.0/
+-->
+<svg xmlns="http://www.w3.org/2000/svg">
+  <title>Reference for paint-order-03.svg</title>
+  <!-- https://bugzilla.mozilla.org/show_bug.cgi?id=XXXXXX -->
+
+  <defs>
+    <marker id="m" markerWidth="40" markerHeight="40" refX="20" refY="20" markerUnits="userSpaceOnUse">
+      <circle cx="20" cy="20" r="16" fill="fuchsia"/>
+    </marker>
+  </defs>
+
+  <g stroke-width="8">
+    <g fill="none" stroke="none" style="marker: url(#m)">
+      <path d="M 50,50 h 100 v 100 h -50 z"/>
+      <line x1="200" y1="50" x2="300" y2="150"/>
+      <polygon points="50,200 50,300 200,200 200,300"/>
+    </g>
+    <g fill="none" stroke="black">
+      <path d="M 50,50 h 100 v 100 h -50 z"/>
+      <line x1="200" y1="50" x2="300" y2="150"/>
+      <polygon points="50,200 50,300 200,200 200,300"/>
+    </g>
+    <g fill="gold" stroke="none">
+      <path d="M 50,50 h 100 v 100 h -50 z"/>
+      <line x1="200" y1="50" x2="300" y2="150"/>
+      <polygon points="50,200 50,300 200,200 200,300"/>
+    </g>
+  </g>
+</svg>
new file mode 100644
--- /dev/null
+++ b/layout/reftests/svg/paint-order-03.svg
@@ -0,0 +1,20 @@
+<!--
+     Any copyright is dedicated to the Public Domain.
+     http://creativecommons.org/publicdomain/zero/1.0/
+-->
+<svg xmlns="http://www.w3.org/2000/svg">
+  <title>Test that the paint-order property works</title>
+  <!-- https://bugzilla.mozilla.org/show_bug.cgi?id=XXXXXX -->
+
+  <defs>
+    <marker id="m" markerWidth="40" markerHeight="40" refX="20" refY="20" markerUnits="userSpaceOnUse">
+      <circle cx="20" cy="20" r="16" fill="fuchsia"/>
+    </marker>
+  </defs>
+
+  <g fill="gold" stroke="black" stroke-width="8" style="marker: url(#m)" paint-order="markers stroke fill">
+    <path d="M 50,50 h 100 v 100 h -50 z"/>
+    <line x1="200" y1="50" x2="300" y2="150"/>
+    <polygon points="50,200 50,300 200,200 200,300"/>
+  </g>
+</svg>
--- a/layout/reftests/svg/reftest.list
+++ b/layout/reftests/svg/reftest.list
@@ -200,16 +200,19 @@ random-if(gtk2Widget) == objectBoundingB
 == objectBoundingBox-and-pattern-01c.svg objectBoundingBox-and-pattern-01-ref.svg
 == objectBoundingBox-and-pattern-02.svg pass.svg
 == objectBoundingBox-and-pattern-03.svg objectBoundingBox-and-pattern-03-ref.svg
 == opacity-and-gradient-01.svg pass.svg
 == opacity-and-gradient-02.svg opacity-and-gradient-02-ref.svg
 == opacity-and-pattern-01.svg pass.svg
 == opacity-and-transform-01.svg opacity-and-transform-01-ref.svg
 == outer-svg-border-and-padding-01.svg outer-svg-border-and-padding-01-ref.svg 
+pref(svg.paint-order.enabled,true) == paint-order-01.svg paint-order-01-ref.svg
+pref(svg.paint-order.enabled,true) == paint-order-02.svg paint-order-02-ref.svg
+pref(svg.paint-order.enabled,true) == paint-order-03.svg paint-order-03-ref.svg
 == path-01.svg path-01-ref.svg
 == path-02.svg pass.svg
 == path-03.svg pass.svg
 == path-04.svg pass.svg
 == path-05.svg pass.svg
 == path-06.svg path-06-ref.svg
 == path-07.svg path-07-ref.svg
 == pathLength-01.svg pass.svg
--- a/layout/style/nsCSSKeywordList.h
+++ b/layout/style/nsCSSKeywordList.h
@@ -316,16 +316,17 @@ CSS_KEY(lower-alpha, lower_alpha)
 CSS_KEY(lower-greek, lower_greek)
 CSS_KEY(lower-latin, lower_latin)
 CSS_KEY(lower-roman, lower_roman)
 CSS_KEY(lowercase, lowercase)
 CSS_KEY(ltr, ltr)
 CSS_KEY(luminance, luminance)
 CSS_KEY(manual, manual)
 CSS_KEY(margin-box, margin_box)
+CSS_KEY(markers, markers)
 CSS_KEY(matrix, matrix)
 CSS_KEY(matrix3d, matrix3d)
 CSS_KEY(medium, medium)
 CSS_KEY(menu, menu)
 CSS_KEY(menutext, menutext)
 CSS_KEY(message-box, message_box)
 CSS_KEY(middle, middle)
 CSS_KEY(mix, mix)
--- a/layout/style/nsCSSParser.cpp
+++ b/layout/style/nsCSSParser.cpp
@@ -617,16 +617,17 @@ protected:
                                         nsCSSValue* aValues,
                                         size_t aNumProperties);
   bool ParseTransition();
   bool ParseAnimation();
 
   bool ParsePaint(nsCSSProperty aPropID);
   bool ParseDasharray();
   bool ParseMarker();
+  bool ParsePaintOrder();
 
   // Reused utility parsing routines
   void AppendValue(nsCSSProperty aPropID, const nsCSSValue& aValue);
   bool ParseBoxProperties(const nsCSSProperty aPropIDs[]);
   bool ParseGroupedBoxProperty(int32_t aVariantMask,
                                nsCSSValue& aValue);
   bool ParseDirectionalBoxProperty(nsCSSProperty aProperty,
                                      int32_t aSourceType);
@@ -6313,16 +6314,18 @@ CSSParserImpl::ParsePropertyByFunction(n
     return ParseTransitionProperty();
   case eCSSProperty_fill:
   case eCSSProperty_stroke:
     return ParsePaint(aPropID);
   case eCSSProperty_stroke_dasharray:
     return ParseDasharray();
   case eCSSProperty_marker:
     return ParseMarker();
+  case eCSSProperty_paint_order:
+    return ParsePaintOrder();
   default:
     NS_ABORT_IF_FALSE(false, "should not be called");
     return false;
   }
 }
 
 // Bits used in determining which background position info we have
 #define BG_CENTER  NS_STYLE_BG_POSITION_CENTER
@@ -10131,16 +10134,103 @@ CSSParserImpl::ParseMarker()
       AppendValue(eCSSProperty_marker_mid, marker);
       AppendValue(eCSSProperty_marker_start, marker);
       return true;
     }
   }
   return false;
 }
 
+bool
+CSSParserImpl::ParsePaintOrder()
+{
+  MOZ_STATIC_ASSERT
+    ((1 << NS_STYLE_PAINT_ORDER_BITWIDTH) > NS_STYLE_PAINT_ORDER_LAST_VALUE,
+     "bitfield width insufficient for paint-order constants");
+
+  static const int32_t kPaintOrderKTable[] = {
+    eCSSKeyword_normal,  NS_STYLE_PAINT_ORDER_NORMAL,
+    eCSSKeyword_fill,    NS_STYLE_PAINT_ORDER_FILL,
+    eCSSKeyword_stroke,  NS_STYLE_PAINT_ORDER_STROKE,
+    eCSSKeyword_markers, NS_STYLE_PAINT_ORDER_MARKERS,
+    eCSSKeyword_UNKNOWN,-1
+  };
+
+  MOZ_STATIC_ASSERT(NS_ARRAY_LENGTH(kPaintOrderKTable) ==
+                      2 * (NS_STYLE_PAINT_ORDER_LAST_VALUE + 2),
+                    "missing paint-order values in kPaintOrderKTable");
+
+  nsCSSValue value;
+  if (!ParseVariant(value, VARIANT_HK, kPaintOrderKTable)) {
+    return false;
+  }
+
+  uint32_t seen = 0;
+  uint32_t order = 0;
+  uint32_t position = 0;
+
+  // Ensure that even cast to a signed int32_t when stored in CSSValue,
+  // we have enough space for the entire paint-order value.
+  MOZ_STATIC_ASSERT
+    (NS_STYLE_PAINT_ORDER_BITWIDTH * NS_STYLE_PAINT_ORDER_LAST_VALUE < 32,
+     "seen and order not big enough");
+
+  if (value.GetUnit() == eCSSUnit_Enumerated) {
+    uint32_t component = static_cast<uint32_t>(value.GetIntValue());
+    if (component != NS_STYLE_PAINT_ORDER_NORMAL) {
+      bool parsedOK = true;
+      for (;;) {
+        if (seen & (1 << component)) {
+          // Already seen this component.
+          UngetToken();
+          parsedOK = false;
+          break;
+        }
+        seen |= (1 << component);
+        order |= (component << position);
+        position += NS_STYLE_PAINT_ORDER_BITWIDTH;
+        if (!ParseEnum(value, kPaintOrderKTable)) {
+          break;
+        }
+        component = value.GetIntValue();
+        if (component == NS_STYLE_PAINT_ORDER_NORMAL) {
+          // Can't have "normal" in the middle of the list of paint components.
+          UngetToken();
+          parsedOK = false;
+          break;
+        }
+      }
+
+      // Fill in the remaining paint-order components in the order of their
+      // constant values.
+      if (parsedOK) {
+        for (component = 1;
+             component <= NS_STYLE_PAINT_ORDER_LAST_VALUE;
+             component++) {
+          if (!(seen & (1 << component))) {
+            order |= (component << position);
+            position += NS_STYLE_PAINT_ORDER_BITWIDTH;
+          }
+        }
+      }
+    }
+
+    MOZ_STATIC_ASSERT(NS_STYLE_PAINT_ORDER_NORMAL == 0,
+                      "unexpected value for NS_STYLE_PAINT_ORDER_NORMAL");
+    value.SetIntValue(static_cast<int32_t>(order), eCSSUnit_Enumerated);
+  }
+
+  if (!ExpectEndProperty()) {
+    return false;
+  }
+
+  AppendValue(eCSSProperty_paint_order, value);
+  return true;
+}
+
 } // anonymous namespace
 
 // Recycling of parser implementation objects
 
 static CSSParserImpl* gFreeList = nullptr;
 
 nsCSSParser::nsCSSParser(mozilla::css::Loader* aLoader,
                          nsCSSStyleSheet* aSheet)
--- a/layout/style/nsCSSPropList.h
+++ b/layout/style/nsCSSPropList.h
@@ -2491,16 +2491,26 @@ CSS_PROP_DISPLAY(
     page_break_inside,
     PageBreakInside,
     CSS_PROPERTY_PARSE_VALUE,
     "",
     VARIANT_HK,
     kPageBreakInsideKTable,
     CSS_PROP_NO_OFFSET,
     eStyleAnimType_None)
+CSS_PROP_SVG(
+    paint-order,
+    paint_order,
+    PaintOrder,
+    CSS_PROPERTY_PARSE_FUNCTION,
+    "svg.paint-order.enabled",
+    0,
+    nullptr,
+    CSS_PROP_NO_OFFSET,
+    eStyleAnimType_None)
 CSS_PROP_VISIBILITY(
     pointer-events,
     pointer_events,
     PointerEvents,
     CSS_PROPERTY_PARSE_VALUE |
         CSS_PROPERTY_APPLIES_TO_PLACEHOLDER,
     "",
     VARIANT_HK,
--- a/layout/style/nsCSSValue.cpp
+++ b/layout/style/nsCSSValue.cpp
@@ -829,16 +829,23 @@ nsCSSValue::AppendToString(nsCSSProperty
                            aResult);
       } else {
         nsStyleUtil::AppendBitmaskCSSValue(aProperty, intValue,
                                            NS_STYLE_PAGE_MARKS_CROP,
                                            NS_STYLE_PAGE_MARKS_REGISTER,
                                            aResult);
       }
     }
+    else if (eCSSProperty_paint_order == aProperty) {
+      MOZ_STATIC_ASSERT
+        (NS_STYLE_PAINT_ORDER_BITWIDTH * NS_STYLE_PAINT_ORDER_LAST_VALUE <= 8,
+         "SVGStyleStruct::mPaintOrder and the following cast not big enough");
+      nsStyleUtil::AppendPaintOrderValue(static_cast<uint8_t>(GetIntValue()),
+                                         aResult);
+    }
     else {
       const nsAFlatCString& name = nsCSSProps::LookupPropertyValue(aProperty, GetIntValue());
       AppendASCIItoUTF16(name, aResult);
     }
   }
   else if (eCSSUnit_EnumColor == unit) {
     // we can lookup the property in the ColorTable and then
     // get a string mapping the name
--- a/layout/style/nsComputedDOMStyle.cpp
+++ b/layout/style/nsComputedDOMStyle.cpp
@@ -4368,16 +4368,27 @@ nsComputedDOMStyle::DoGetMaskType()
   nsROCSSPrimitiveValue *val = GetROCSSPrimitiveValue();
   val->SetIdent(
     nsCSSProps::ValueToKeywordEnum(GetStyleSVGReset()->mMaskType,
                                    nsCSSProps::kMaskTypeKTable));
   return val;
 }
 
 CSSValue*
+nsComputedDOMStyle::DoGetPaintOrder()
+{
+  nsROCSSPrimitiveValue *val = GetROCSSPrimitiveValue();
+  nsAutoString string;
+  uint8_t paintOrder = GetStyleSVG()->mPaintOrder;
+  nsStyleUtil::AppendPaintOrderValue(paintOrder, string);
+  val->SetString(string);
+  return val;
+}
+
+CSSValue*
 nsComputedDOMStyle::DoGetTransitionDelay()
 {
   const nsStyleDisplay* display = GetStyleDisplay();
 
   nsDOMCSSValueList *valueList = GetROCSSValueList(true);
 
   NS_ABORT_IF_FALSE(display->mTransitionDelayCount > 0,
                     "first item must be explicit");
@@ -4935,16 +4946,17 @@ nsComputedDOMStyle::GetQueryableProperty
     COMPUTED_STYLE_MAP_ENTRY(flood_opacity,                 FloodOpacity),
     COMPUTED_STYLE_MAP_ENTRY(image_rendering,               ImageRendering),
     COMPUTED_STYLE_MAP_ENTRY(lighting_color,                LightingColor),
     COMPUTED_STYLE_MAP_ENTRY(marker_end,                    MarkerEnd),
     COMPUTED_STYLE_MAP_ENTRY(marker_mid,                    MarkerMid),
     COMPUTED_STYLE_MAP_ENTRY(marker_start,                  MarkerStart),
     COMPUTED_STYLE_MAP_ENTRY(mask,                          Mask),
     COMPUTED_STYLE_MAP_ENTRY(mask_type,                     MaskType),
+    COMPUTED_STYLE_MAP_ENTRY(paint_order,                   PaintOrder),
     COMPUTED_STYLE_MAP_ENTRY(shape_rendering,               ShapeRendering),
     COMPUTED_STYLE_MAP_ENTRY(stop_color,                    StopColor),
     COMPUTED_STYLE_MAP_ENTRY(stop_opacity,                  StopOpacity),
     COMPUTED_STYLE_MAP_ENTRY(stroke,                        Stroke),
     COMPUTED_STYLE_MAP_ENTRY(stroke_dasharray,              StrokeDasharray),
     COMPUTED_STYLE_MAP_ENTRY(stroke_dashoffset,             StrokeDashoffset),
     COMPUTED_STYLE_MAP_ENTRY(stroke_linecap,                StrokeLinecap),
     COMPUTED_STYLE_MAP_ENTRY(stroke_linejoin,               StrokeLinejoin),
--- a/layout/style/nsComputedDOMStyle.h
+++ b/layout/style/nsComputedDOMStyle.h
@@ -417,16 +417,17 @@ private:
   mozilla::dom::CSSValue* DoGetFloodColor();
   mozilla::dom::CSSValue* DoGetLightingColor();
   mozilla::dom::CSSValue* DoGetStopColor();
 
   mozilla::dom::CSSValue* DoGetClipPath();
   mozilla::dom::CSSValue* DoGetFilter();
   mozilla::dom::CSSValue* DoGetMask();
   mozilla::dom::CSSValue* DoGetMaskType();
+  mozilla::dom::CSSValue* DoGetPaintOrder();
 
   nsROCSSPrimitiveValue* GetROCSSPrimitiveValue();
   nsDOMCSSValueList* GetROCSSValueList(bool aCommaDelimited);
   void SetToRGBAColor(nsROCSSPrimitiveValue* aValue, nscolor aColor);
   void SetValueToStyleImage(const nsStyleImage& aStyleImage,
                             nsROCSSPrimitiveValue* aValue);
 
   /**
--- a/layout/style/nsRuleNode.cpp
+++ b/layout/style/nsRuleNode.cpp
@@ -25,16 +25,17 @@
 #include "gfxFont.h"
 #include "nsStyleUtil.h"
 #include "nsCSSPseudoElements.h"
 #include "nsThemeConstants.h"
 #include "nsITheme.h"
 #include "pldhash.h"
 #include "nsStyleContext.h"
 #include "nsStyleSet.h"
+#include "nsStyleStruct.h"
 #include "nsSize.h"
 #include "imgIRequest.h"
 #include "nsRuleData.h"
 #include "nsIStyleRule.h"
 #include "nsBidiUtils.h"
 #include "nsUnicharUtils.h"
 #include "nsStyleStructInlines.h"
 #include "nsStyleTransformMatrix.h"
@@ -7343,16 +7344,36 @@ nsRuleNode::ComputeSVGData(void* aStartS
   } else if (eCSSUnit_None == markerStartValue->GetUnit() ||
              eCSSUnit_Initial == markerStartValue->GetUnit()) {
     svg->mMarkerStart = nullptr;
   } else if (eCSSUnit_Inherit == markerStartValue->GetUnit()) {
     canStoreInRuleTree = false;
     svg->mMarkerStart = parentSVG->mMarkerStart;
   }
 
+  // paint-order: enum (bit field), inherit, initial
+  const nsCSSValue* paintOrderValue = aRuleData->ValueForPaintOrder();
+  switch (paintOrderValue->GetUnit()) {
+    case eCSSUnit_Enumerated:
+      MOZ_STATIC_ASSERT
+        (NS_STYLE_PAINT_ORDER_BITWIDTH * NS_STYLE_PAINT_ORDER_LAST_VALUE <= 8,
+         "SVGStyleStruct::mPaintOrder not big enough");
+      svg->mPaintOrder = static_cast<uint8_t>(paintOrderValue->GetIntValue());
+      break;
+
+    case eCSSUnit_Inherit:
+      canStoreInRuleTree = false;
+      svg->mPaintOrder = parentSVG->mPaintOrder;
+      break;
+
+    case eCSSUnit_Initial:
+      svg->mPaintOrder = NS_STYLE_PAINT_ORDER_NORMAL;
+      break;
+  }
+
   // shape-rendering: enum, inherit
   SetDiscrete(*aRuleData->ValueForShapeRendering(),
               svg->mShapeRendering, canStoreInRuleTree,
               SETDSC_ENUMERATED, parentSVG->mShapeRendering,
               NS_STYLE_SHAPE_RENDERING_AUTO, 0, 0, 0, 0);
 
   // stroke:
   SetSVGPaint(*aRuleData->ValueForStroke(),
--- a/layout/style/nsStyleStruct.cpp
+++ b/layout/style/nsStyleStruct.cpp
@@ -808,16 +808,17 @@ nsStyleSVG::nsStyleSVG()
     mStrokeOpacity           = 1.0f;
 
     mStrokeDasharrayLength   = 0;
     mClipRule                = NS_STYLE_FILL_RULE_NONZERO;
     mColorInterpolation      = NS_STYLE_COLOR_INTERPOLATION_SRGB;
     mColorInterpolationFilters = NS_STYLE_COLOR_INTERPOLATION_LINEARRGB;
     mFillRule                = NS_STYLE_FILL_RULE_NONZERO;
     mImageRendering          = NS_STYLE_IMAGE_RENDERING_AUTO;
+    mPaintOrder              = NS_STYLE_PAINT_ORDER_NORMAL;
     mShapeRendering          = NS_STYLE_SHAPE_RENDERING_AUTO;
     mStrokeLinecap           = NS_STYLE_STROKE_LINECAP_BUTT;
     mStrokeLinejoin          = NS_STYLE_STROKE_LINEJOIN_MITER;
     mTextAnchor              = NS_STYLE_TEXT_ANCHOR_START;
     mTextRendering           = NS_STYLE_TEXT_RENDERING_AUTO;
     mFillOpacitySource       = eStyleSVGOpacitySource_Normal;
     mStrokeOpacitySource     = eStyleSVGOpacitySource_Normal;
     mStrokeDasharrayFromObject = false;
@@ -861,16 +862,17 @@ nsStyleSVG::nsStyleSVG(const nsStyleSVG&
   mStrokeMiterlimit = aSource.mStrokeMiterlimit;
   mStrokeOpacity = aSource.mStrokeOpacity;
 
   mClipRule = aSource.mClipRule;
   mColorInterpolation = aSource.mColorInterpolation;
   mColorInterpolationFilters = aSource.mColorInterpolationFilters;
   mFillRule = aSource.mFillRule;
   mImageRendering = aSource.mImageRendering;
+  mPaintOrder = aSource.mPaintOrder;
   mShapeRendering = aSource.mShapeRendering;
   mStrokeLinecap = aSource.mStrokeLinecap;
   mStrokeLinejoin = aSource.mStrokeLinejoin;
   mTextAnchor = aSource.mTextAnchor;
   mTextRendering = aSource.mTextRendering;
   mFillOpacitySource = aSource.mFillOpacitySource;
   mStrokeOpacitySource = aSource.mStrokeOpacitySource;
   mStrokeDasharrayFromObject = aSource.mStrokeDasharrayFromObject;
@@ -925,16 +927,17 @@ nsChangeHint nsStyleSVG::CalcDifference(
        mStrokeMiterlimit      != aOther.mStrokeMiterlimit      ||
        mStrokeOpacity         != aOther.mStrokeOpacity         ||
 
        mClipRule              != aOther.mClipRule              ||
        mColorInterpolation    != aOther.mColorInterpolation    ||
        mColorInterpolationFilters != aOther.mColorInterpolationFilters ||
        mFillRule              != aOther.mFillRule              ||
        mImageRendering        != aOther.mImageRendering        ||
+       mPaintOrder            != aOther.mPaintOrder            ||
        mShapeRendering        != aOther.mShapeRendering        ||
        mStrokeDasharrayLength != aOther.mStrokeDasharrayLength ||
        mStrokeLinecap         != aOther.mStrokeLinecap         ||
        mStrokeLinejoin        != aOther.mStrokeLinejoin        ||
        mTextAnchor            != aOther.mTextAnchor            ||
        mFillOpacitySource     != aOther.mFillOpacitySource     ||
        mStrokeOpacitySource   != aOther.mStrokeOpacitySource   ||
        mStrokeDasharrayFromObject != aOther.mStrokeDasharrayFromObject ||
--- a/layout/style/nsStyleStruct.h
+++ b/layout/style/nsStyleStruct.h
@@ -2227,16 +2227,17 @@ struct nsStyleSVG {
   float            mStrokeOpacity;    // [inherited]
 
   uint32_t         mStrokeDasharrayLength;
   uint8_t          mClipRule;         // [inherited]
   uint8_t          mColorInterpolation; // [inherited] see nsStyleConsts.h
   uint8_t          mColorInterpolationFilters; // [inherited] see nsStyleConsts.h
   uint8_t          mFillRule;         // [inherited] see nsStyleConsts.h
   uint8_t          mImageRendering;   // [inherited] see nsStyleConsts.h
+  uint8_t          mPaintOrder;       // [inherited] see nsStyleConsts.h
   uint8_t          mShapeRendering;   // [inherited] see nsStyleConsts.h
   uint8_t          mStrokeLinecap;    // [inherited] see nsStyleConsts.h
   uint8_t          mStrokeLinejoin;   // [inherited] see nsStyleConsts.h
   uint8_t          mTextAnchor;       // [inherited] see nsStyleConsts.h
   uint8_t          mTextRendering;    // [inherited] see nsStyleConsts.h
 
   // In SVG glyphs, whether we inherit fill or stroke opacity from the outer
   // text object.
--- a/layout/style/nsStyleUtil.cpp
+++ b/layout/style/nsStyleUtil.cpp
@@ -161,16 +161,77 @@ nsStyleUtil::AppendBitmaskCSSValue(nsCSS
         aResult.Append(PRUnichar(' '));
       }
     }
   }
   NS_ABORT_IF_FALSE(aMaskedValue == 0, "unexpected bit remaining in bitfield");
 }
 
 /* static */ void
+nsStyleUtil::AppendPaintOrderValue(uint8_t aValue,
+                                   nsAString& aResult)
+{
+  MOZ_STATIC_ASSERT
+    (NS_STYLE_PAINT_ORDER_BITWIDTH * NS_STYLE_PAINT_ORDER_LAST_VALUE <= 8,
+     "SVGStyleStruct::mPaintOrder and local variables not big enough");
+
+  if (aValue == NS_STYLE_PAINT_ORDER_NORMAL) {
+    aResult.AppendLiteral("normal");
+    return;
+  }
+
+  // Append the minimal value necessary for the given paint order.
+  MOZ_STATIC_ASSERT(NS_STYLE_PAINT_ORDER_LAST_VALUE == 3,
+                    "paint-order values added; check serialization");
+
+  // The following relies on the default order being the order of the
+  // constant values.
+
+  const uint8_t MASK = (1 << NS_STYLE_PAINT_ORDER_BITWIDTH) - 1;
+
+  uint32_t lastPositionToSerialize = 0;
+  for (uint32_t position = NS_STYLE_PAINT_ORDER_LAST_VALUE - 1;
+       position > 0;
+       position--) {
+    uint8_t component =
+      (aValue >> (position * NS_STYLE_PAINT_ORDER_BITWIDTH)) & MASK;
+    uint8_t earlierComponent =
+      (aValue >> ((position - 1) * NS_STYLE_PAINT_ORDER_BITWIDTH)) & MASK;
+    if (component < earlierComponent) {
+      lastPositionToSerialize = position - 1;
+      break;
+    }
+  }
+
+  for (uint32_t position = 0; position <= lastPositionToSerialize; position++) {
+    if (position > 0) {
+      aResult.AppendLiteral(" ");
+    }
+    uint8_t component = aValue & MASK;
+    switch (component) {
+      case NS_STYLE_PAINT_ORDER_FILL:
+        aResult.AppendLiteral("fill");
+        break;
+
+      case NS_STYLE_PAINT_ORDER_STROKE:
+        aResult.AppendLiteral("stroke");
+        break;
+
+      case NS_STYLE_PAINT_ORDER_MARKERS:
+        aResult.AppendLiteral("markers");
+        break;
+
+      default:
+        NS_NOTREACHED("unexpected paint-order component value");
+    }
+    aValue >>= NS_STYLE_PAINT_ORDER_BITWIDTH;
+  }
+}
+
+/* static */ void
 nsStyleUtil::AppendFontFeatureSettings(const nsTArray<gfxFontFeature>& aFeatures,
                                        nsAString& aResult)
 {
   for (uint32_t i = 0, numFeat = aFeatures.Length(); i < numFeat; i++) {
     const gfxFontFeature& feat = aFeatures[i];
 
     if (i != 0) {
         aResult.AppendLiteral(", ");
--- a/layout/style/nsStyleUtil.h
+++ b/layout/style/nsStyleUtil.h
@@ -40,16 +40,18 @@ public:
 
   // Append a bitmask-valued property's value(s) (space-separated) to aResult.
   static void AppendBitmaskCSSValue(nsCSSProperty aProperty,
                                     int32_t aMaskedValue,
                                     int32_t aFirstMask,
                                     int32_t aLastMask,
                                     nsAString& aResult);
 
+  static void AppendPaintOrderValue(uint8_t aValue, nsAString& aResult);
+
   static void AppendFontFeatureSettings(const nsTArray<gfxFontFeature>& aFeatures,
                                         nsAString& aResult);
 
   static void AppendFontFeatureSettings(const nsCSSValue& src,
                                         nsAString& aResult);
 
   /*
    * Convert an author-provided floating point number to an integer (0
--- a/layout/style/test/property_database.js
+++ b/layout/style/test/property_database.js
@@ -4246,8 +4246,19 @@ if (SpecialPowers.getBoolPref("layout.cs
 	}
 	};
 	for (var prop in flexProperties) {
 		gCSSProperties[prop] = flexProperties[prop];
 	}
 	gCSSProperties["display"].other_values.push("flex");
 	gCSSProperties["display"].other_values.push("inline-flex");
 }
+
+if (SpecialPowers.getBoolPref("svg.paint-order.enabled")) {
+  gCSSProperties["paint-order"] = {
+    domProp: "paintOrder",
+    inherited: true,
+    type: CSS_TYPE_LONGHAND,
+    initial_values: [ "normal" ],
+    other_values: [ "fill", "fill stroke", "fill stroke markers", "stroke markers fill" ],
+    invalid_values: [ "fill stroke markers fill", "fill normal" ]
+  };
+}
--- a/layout/svg/nsSVGGlyphFrame.cpp
+++ b/layout/svg/nsSVGGlyphFrame.cpp
@@ -951,16 +951,30 @@ nsSVGGlyphFrame::SetupCairoState(gfxCont
   if (SetupCairoStroke(aContext, aOuterObjectPaint, thisObjectPaint)) {
     toDraw = DrawMode(toDraw | gfxFont::GLYPH_STROKE);
   }
 
   if (SetupCairoFill(aContext, aOuterObjectPaint, thisObjectPaint)) {
     toDraw = DrawMode(toDraw | gfxFont::GLYPH_FILL);
   }
 
+  uint32_t paintOrder = GetStyleSVG()->mPaintOrder;
+  while (paintOrder) {
+    uint32_t component =
+      paintOrder & ((1 << NS_STYLE_PAINT_ORDER_BITWIDTH) - 1);
+    if (component == NS_STYLE_PAINT_ORDER_FILL) {
+      break;
+    }
+    if (component == NS_STYLE_PAINT_ORDER_STROKE) {
+      toDraw = DrawMode(toDraw | gfxFont::GLYPH_STROKE_UNDERNEATH);
+      break;
+    }
+    paintOrder >>= NS_STYLE_PAINT_ORDER_BITWIDTH;
+  }
+
   *aThisObjectPaint = thisObjectPaint;
 
   return toDraw;
 }
 
 bool
 nsSVGGlyphFrame::SetupCairoStroke(gfxContext *aContext,
                                   gfxTextObjectPaint *aOuterObjectPaint,
--- a/layout/svg/nsSVGPathGeometryFrame.cpp
+++ b/layout/svg/nsSVGPathGeometryFrame.cpp
@@ -180,49 +180,36 @@ nsSVGPathGeometryFrame::BuildDisplayList
 
 NS_IMETHODIMP
 nsSVGPathGeometryFrame::PaintSVG(nsRenderingContext *aContext,
                                  const nsIntRect *aDirtyRect)
 {
   if (!GetStyleVisibility()->IsVisible())
     return NS_OK;
 
-  /* render */
-  Render(aContext);
-
-  gfxTextObjectPaint *objectPaint =
-    (gfxTextObjectPaint*)aContext->GetUserData(&gfxTextObjectPaint::sUserDataKey);
-
-  if (static_cast<nsSVGPathGeometryElement*>(mContent)->IsMarkable()) {
-    MarkerProperties properties = GetMarkerProperties(this);
-      
-    if (properties.MarkersExist()) {
-      float strokeWidth = nsSVGUtils::GetStrokeWidth(this, objectPaint);
-        
-      nsTArray<nsSVGMark> marks;
-      static_cast<nsSVGPathGeometryElement*>
-                 (mContent)->GetMarkPoints(&marks);
-        
-      uint32_t num = marks.Length();
-
-      if (num) {
-        nsSVGMarkerFrame *frame = properties.GetMarkerStartFrame();
-        if (frame)
-          frame->PaintMark(aContext, this, &marks[0], strokeWidth);
-
-        frame = properties.GetMarkerMidFrame();
-        if (frame) {
-          for (uint32_t i = 1; i < num - 1; i++)
-            frame->PaintMark(aContext, this, &marks[i], strokeWidth);
-        }
-
-        frame = properties.GetMarkerEndFrame();
-        if (frame)
-          frame->PaintMark(aContext, this, &marks[num-1], strokeWidth);
+  uint32_t paintOrder = GetStyleSVG()->mPaintOrder;
+  if (paintOrder == NS_STYLE_PAINT_ORDER_NORMAL) {
+    Render(aContext, eRenderFill | eRenderStroke);
+    PaintMarkers(aContext);
+  } 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);
+          break;
+        case NS_STYLE_PAINT_ORDER_STROKE:
+          Render(aContext, eRenderStroke);
+          break;
+        case NS_STYLE_PAINT_ORDER_MARKERS:
+          PaintMarkers(aContext);
+          break;
       }
+      paintOrder >>= NS_STYLE_PAINT_ORDER_BITWIDTH;
     }
   }
 
   return NS_OK;
 }
 
 NS_IMETHODIMP_(nsIFrame*)
 nsSVGPathGeometryFrame::GetFrameForPoint(const nsPoint &aPoint)
@@ -542,17 +529,18 @@ nsSVGPathGeometryFrame::MarkerProperties
 {
   if (!mMarkerEnd)
     return nullptr;
   return static_cast<nsSVGMarkerFrame *>
     (mMarkerEnd->GetReferencedFrame(nsGkAtoms::svgMarkerFrame, nullptr));
 }
 
 void
-nsSVGPathGeometryFrame::Render(nsRenderingContext *aContext)
+nsSVGPathGeometryFrame::Render(nsRenderingContext *aContext,
+                               uint32_t aRenderComponents)
 {
   gfxContext *gfx = aContext->ThebesContext();
 
   uint16_t renderMode = SVGAutoRenderState::GetRenderMode(aContext);
 
   switch (GetStyleSVG()->mShapeRendering) {
   case NS_STYLE_SHAPE_RENDERING_OPTIMIZESPEED:
   case NS_STYLE_SHAPE_RENDERING_CRISPEDGES:
@@ -586,21 +574,23 @@ nsSVGPathGeometryFrame::Render(nsRenderi
     }
 
     return;
   }
 
   gfxTextObjectPaint *objectPaint =
     (gfxTextObjectPaint*)aContext->GetUserData(&gfxTextObjectPaint::sUserDataKey);
 
-  if (nsSVGUtils::SetupCairoFillPaint(this, gfx, objectPaint)) {
+  if ((aRenderComponents & eRenderFill) &&
+      nsSVGUtils::SetupCairoFillPaint(this, gfx, objectPaint)) {
     gfx->Fill();
   }
 
-  if (nsSVGUtils::SetupCairoStroke(this, gfx, objectPaint)) {
+  if ((aRenderComponents & eRenderStroke) &&
+       nsSVGUtils::SetupCairoStroke(this, gfx, objectPaint)) {
     gfx->Stroke();
   }
 
   gfx->NewPath();
 
   gfx->Restore();
 }
 
@@ -620,8 +610,45 @@ nsSVGPathGeometryFrame::GeneratePath(gfx
   const nsStyleSVG* style = GetStyleSVG();
   if (style->mStrokeLinecap == NS_STYLE_STROKE_LINECAP_SQUARE) {
     aContext->SetLineCap(gfxContext::LINE_CAP_SQUARE);
   }
 
   aContext->NewPath();
   static_cast<nsSVGPathGeometryElement*>(mContent)->ConstructPath(aContext);
 }
+
+void
+nsSVGPathGeometryFrame::PaintMarkers(nsRenderingContext* aContext)
+{
+  gfxTextObjectPaint *objectPaint =
+    (gfxTextObjectPaint*)aContext->GetUserData(&gfxTextObjectPaint::sUserDataKey);
+
+  if (static_cast<nsSVGPathGeometryElement*>(mContent)->IsMarkable()) {
+    MarkerProperties properties = GetMarkerProperties(this);
+
+    if (properties.MarkersExist()) {
+      float strokeWidth = nsSVGUtils::GetStrokeWidth(this, objectPaint);
+
+      nsTArray<nsSVGMark> marks;
+      static_cast<nsSVGPathGeometryElement*>
+                 (mContent)->GetMarkPoints(&marks);
+
+      uint32_t num = marks.Length();
+
+      if (num) {
+        nsSVGMarkerFrame *frame = properties.GetMarkerStartFrame();
+        if (frame)
+          frame->PaintMark(aContext, this, &marks[0], strokeWidth);
+
+        frame = properties.GetMarkerMidFrame();
+        if (frame) {
+          for (uint32_t i = 1; i < num - 1; i++)
+            frame->PaintMark(aContext, this, &marks[i], strokeWidth);
+        }
+
+        frame = properties.GetMarkerEndFrame();
+        if (frame)
+          frame->PaintMark(aContext, this, &marks[num-1], strokeWidth);
+      }
+    }
+  }
+}
--- a/layout/svg/nsSVGPathGeometryFrame.h
+++ b/layout/svg/nsSVGPathGeometryFrame.h
@@ -93,17 +93,19 @@ protected:
   virtual SVGBBox GetBBoxContribution(const gfxMatrix &aToBBoxUserspace,
                                       uint32_t aFlags) MOZ_OVERRIDE;
   NS_IMETHOD_(bool) IsDisplayContainer() MOZ_OVERRIDE { return false; }
 
 protected:
   void GeneratePath(gfxContext *aContext, const gfxMatrix &aTransform);
 
 private:
-  void Render(nsRenderingContext *aContext);
+  enum { eRenderFill = 1, eRenderStroke = 2 };
+  void Render(nsRenderingContext *aContext, uint32_t aRenderComponents);
+  void PaintMarkers(nsRenderingContext *aContext);
 
   struct MarkerProperties {
     nsSVGMarkerProperty* mMarkerStart;
     nsSVGMarkerProperty* mMarkerMid;
     nsSVGMarkerProperty* mMarkerEnd;
 
     bool MarkersExist() const {
       return mMarkerStart || mMarkerMid || mMarkerEnd;
--- a/modules/libpref/src/init/all.js
+++ b/modules/libpref/src/init/all.js
@@ -1795,16 +1795,23 @@ pref("dom.ipc.plugins.flash.subprocess.c
 pref("dom.ipc.processCount", 1);
 
 pref("svg.smil.enabled", true);
 
 // Enable the use of display-lists for SVG hit-testing and painting.
 pref("svg.display-lists.hit-testing.enabled", true);
 pref("svg.display-lists.painting.enabled", true);
 
+// Is support for the SVG 2 paint-order property enabled?
+#ifdef RELEASE_BUILD
+pref("svg.paint-order.enabled", false);
+#else
+pref("svg.paint-order.enabled", true);
+#endif
+
 pref("font.minimum-size.ar", 0);
 pref("font.minimum-size.x-armn", 0);
 pref("font.minimum-size.x-beng", 0);
 pref("font.minimum-size.x-baltic", 0);
 pref("font.minimum-size.x-central-euro", 0);
 pref("font.minimum-size.zh-CN", 0);
 pref("font.minimum-size.zh-HK", 0);
 pref("font.minimum-size.zh-TW", 0);