Bug 550426 - Add support for {background,mask}-position-{x,y}, most of the style system changes. r=dbaron
☠☠ backed out by 2e2c516bd64f ☠ ☠
authorMarkus Stange <mstange@themasta.com>
Fri, 29 Apr 2016 14:12:10 -0400
changeset 295463 1a030aba3acca5c4580ec1e68f347e0e144cc012
parent 295462 3655883e7faf15a858aca4bc4cfed506761c26ee
child 295464 2162ecc18859471f8071fd3b499280154783731f
push id75939
push usermstange@themasta.com
push dateFri, 29 Apr 2016 20:32:28 +0000
treeherdermozilla-inbound@6ff2e12738ca [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersdbaron
bugs550426
milestone49.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 550426 - Add support for {background,mask}-position-{x,y}, most of the style system changes. r=dbaron MozReview-Commit-ID: JgZIoj43aZv
layout/style/Declaration.cpp
layout/style/Declaration.h
layout/style/nsCSSParser.cpp
layout/style/nsCSSPropAliasList.h
layout/style/nsCSSPropList.h
layout/style/nsCSSProps.cpp
layout/style/nsCSSValue.cpp
layout/style/nsComputedDOMStyle.cpp
layout/style/nsRuleNode.cpp
layout/style/nsStyleStruct.cpp
layout/style/nsStyleStruct.h
layout/style/test/test_shorthand_property_getters.html
--- a/layout/style/Declaration.cpp
+++ b/layout/style/Declaration.cpp
@@ -196,16 +196,51 @@ Declaration::GetValue(nsCSSProperty aPro
 }
 
 void
 Declaration::GetAuthoredValue(nsCSSProperty aProperty, nsAString& aValue) const
 {
   GetValue(aProperty, aValue, nsCSSValue::eAuthorSpecified);
 }
 
+static void
+AppendSingleImageLayerPositionValue(const nsCSSValue& aPositionX,
+                                    const nsCSSValue& aPositionY,
+                                    const nsCSSProperty aTable[],
+                                    nsAString& aValue,
+                                    nsCSSValue::Serialization aSerialization)
+{
+  // We need to make sure that we don't serialize to an invalid 3-value form.
+  // The 3-value form is only valid if both edges are present.
+  const nsCSSValue &xEdge = aPositionX.GetArrayValue()->Item(0);
+  const nsCSSValue &xOffset = aPositionX.GetArrayValue()->Item(1);
+  const nsCSSValue &yEdge = aPositionY.GetArrayValue()->Item(0);
+  const nsCSSValue &yOffset = aPositionY.GetArrayValue()->Item(1);
+  bool xHasEdge = (eCSSUnit_Enumerated == xEdge.GetUnit());
+  bool xHasBoth = xHasEdge && (eCSSUnit_Null != xOffset.GetUnit());
+  bool yHasEdge = (eCSSUnit_Enumerated == yEdge.GetUnit());
+  bool yHasBoth = yHasEdge && (eCSSUnit_Null != yOffset.GetUnit());
+
+  if (yHasBoth && !xHasEdge) {
+    // Output 4-value form by adding the x edge.
+    aValue.AppendLiteral("left ");
+  }
+  aPositionX.AppendToString(aTable[nsStyleImageLayers::positionX],
+                            aValue, aSerialization);
+
+  aValue.Append(char16_t(' '));
+
+  if (xHasBoth && !yHasEdge) {
+    // Output 4-value form by adding the y edge.
+    aValue.AppendLiteral("top ");
+  }
+  aPositionY.AppendToString(aTable[nsStyleImageLayers::positionY],
+                            aValue, aSerialization);
+}
+
 void
 Declaration::GetImageLayerValue(
                    nsCSSCompressedDataBlock *data,
                    nsAString& aValue,
                    nsCSSValue::Serialization aSerialization,
                    const nsCSSProperty aTable[]) const
 {
   // We know from our caller that all subproperties were specified.
@@ -216,18 +251,20 @@ Declaration::GetImageLayerValue(
   // background-origin that are different and not the default
   // values.  (We omit them if they're both default.)
 
   // Common CSS properties for both background & mask layer.
   const nsCSSValueList *image =
     data->ValueFor(aTable[nsStyleImageLayers::image])->GetListValue();
   const nsCSSValuePairList *repeat =
     data->ValueFor(aTable[nsStyleImageLayers::repeat])->GetPairListValue();
-  const nsCSSValueList *position =
-    data->ValueFor(aTable[nsStyleImageLayers::position])->GetListValue();
+  const nsCSSValueList *positionX =
+    data->ValueFor(aTable[nsStyleImageLayers::positionX])->GetListValue();
+  const nsCSSValueList *positionY =
+    data->ValueFor(aTable[nsStyleImageLayers::positionY])->GetListValue();
   const nsCSSValueList *clip =
     data->ValueFor(aTable[nsStyleImageLayers::clip])->GetListValue();
   const nsCSSValueList *origin =
     data->ValueFor(aTable[nsStyleImageLayers::origin])->GetListValue();
   const nsCSSValuePairList *size =
     data->ValueFor(aTable[nsStyleImageLayers::size])->GetPairListValue();
 
   // Background layer property.
@@ -269,18 +306,18 @@ Declaration::GetImageLayerValue(
 
     if (attachment) {
       aValue.Append(char16_t(' '));
           attachment->mValue.AppendToString(aTable[nsStyleImageLayers::attachment],
                                             aValue, aSerialization);
     }
 
     aValue.Append(char16_t(' '));
-    position->mValue.AppendToString(aTable[nsStyleImageLayers::position],
-                                    aValue, aSerialization);
+    AppendSingleImageLayerPositionValue(positionX->mValue, positionY->mValue,
+                                        aTable, aValue, aSerialization);
 
     if (size->mXValue.GetUnit() != eCSSUnit_Auto ||
         size->mYValue.GetUnit() != eCSSUnit_Auto) {
       aValue.Append(char16_t(' '));
       aValue.Append(char16_t('/'));
       aValue.Append(char16_t(' '));
       size->mXValue.AppendToString(aTable[nsStyleImageLayers::size], aValue,
                                    aSerialization);
@@ -332,75 +369,112 @@ Declaration::GetImageLayerValue(
     if (mode) {
       aValue.Append(char16_t(' '));
       mode->mValue.AppendToString(aTable[nsStyleImageLayers::maskMode],
                                   aValue, aSerialization);
     }
 
     image = image->mNext;
     repeat = repeat->mNext;
-    position = position->mNext;
+    positionX = positionX->mNext;
+    positionY = positionY->mNext;
     clip = clip->mNext;
     origin = origin->mNext;
     size = size->mNext;
     attachment = attachment ? attachment->mNext : nullptr;
     composite = composite ? composite->mNext : nullptr;
     mode = mode ? mode->mNext : nullptr;
 
     if (!image) {
       // This layer is an background layer
       if (aTable == nsStyleImageLayers::kBackgroundLayerTable) {
-        if (repeat || position || clip || origin || size || attachment) {
+        if (repeat || positionX || positionY || clip || origin || size ||
+            attachment) {
           // Uneven length lists, so can't be serialized as shorthand.
           aValue.Truncate();
           return;
         }
       // This layer is an mask layer
       } else {
 #ifdef MOZ_ENABLE_MASK_AS_SHORTHAND
         MOZ_ASSERT(aTable == nsStyleImageLayers::kMaskLayerTable);
 #else
         MOZ_ASSERT_UNREACHABLE("Should never get here when mask-as-shorthand is disable");
 #endif
-        if (repeat || position || clip || origin || size || composite || mode) {
+        if (repeat || positionX || positionY || clip || origin || size ||
+            composite || mode) {
           // Uneven length lists, so can't be serialized as shorthand.
           aValue.Truncate();
           return;
         }
       }
       break;
     }
 
     // This layer is an background layer
     if (aTable == nsStyleImageLayers::kBackgroundLayerTable) {
-      if (!repeat || !position || !clip || !origin || !size || !attachment) {
+      if (!repeat || !positionX || !positionY || !clip || !origin || !size ||
+          !attachment) {
         // Uneven length lists, so can't be serialized as shorthand.
         aValue.Truncate();
         return;
       }
     // This layer is an mask layer
     } else {
 #ifdef MOZ_ENABLE_MASK_AS_SHORTHAND
       MOZ_ASSERT(aTable == nsStyleImageLayers::kMaskLayerTable);
 #else
       MOZ_ASSERT_UNREACHABLE("Should never get here when mask-as-shorthand is disable");
 #endif
-      if (!repeat || !position || !clip || !origin || !size ||
+      if (!repeat || !positionX || !positionY || !clip || !origin || !size ||
           !composite || !mode) {
         // Uneven length lists, so can't be serialized as shorthand.
         aValue.Truncate();
         return;
       }
     }
     aValue.Append(char16_t(','));
     aValue.Append(char16_t(' '));
   }
 }
 
 void
+Declaration::GetImageLayerPositionValue(
+                   nsCSSCompressedDataBlock *data,
+                   nsAString& aValue,
+                   nsCSSValue::Serialization aSerialization,
+                   const nsCSSProperty aTable[]) const
+{
+  // We know from above that all subproperties were specified.
+  // However, we still can't represent that in the shorthand unless
+  // they're all lists of the same length.  So if they're different
+  // lengths, we need to bail out.
+  const nsCSSValueList *positionX =
+    data->ValueFor(aTable[nsStyleImageLayers::positionX])->GetListValue();
+  const nsCSSValueList *positionY =
+    data->ValueFor(aTable[nsStyleImageLayers::positionY])->GetListValue();
+  for (;;) {
+    AppendSingleImageLayerPositionValue(positionX->mValue, positionY->mValue,
+                                        aTable, aValue, aSerialization);
+    positionX = positionX->mNext;
+    positionY = positionY->mNext;
+
+    if (!positionX || !positionY) {
+      if (positionX || positionY) {
+        // Uneven length lists, so can't be serialized as shorthand.
+        aValue.Truncate();
+      }
+      return;
+    }
+    aValue.Append(char16_t(','));
+    aValue.Append(char16_t(' '));
+  }
+}
+
+void
 Declaration::GetValue(nsCSSProperty aProperty, nsAString& aValue,
                       nsCSSValue::Serialization aSerialization) const
 {
   aValue.Truncate(0);
 
   // simple properties are easy.
   if (!nsCSSProps::IsShorthand(aProperty)) {
     AppendValueToString(aProperty, aValue, aSerialization);
@@ -664,22 +738,32 @@ Declaration::GetValue(nsCSSProperty aPro
       }
       break;
     }
     case eCSSProperty_background: {
       GetImageLayerValue(data, aValue, aSerialization,
                          nsStyleImageLayers::kBackgroundLayerTable);
       break;
     }
+    case eCSSProperty_background_position: {
+      GetImageLayerPositionValue(data, aValue, aSerialization,
+                                 nsStyleImageLayers::kBackgroundLayerTable);
+      break;
+    }
 #ifdef MOZ_ENABLE_MASK_AS_SHORTHAND
     case eCSSProperty_mask: {
       GetImageLayerValue(data, aValue, aSerialization,
                          nsStyleImageLayers::kMaskLayerTable);
       break;
     }
+    case eCSSProperty_mask_position: {
+      GetImageLayerPositionValue(data, aValue, aSerialization,
+                                 nsStyleImageLayers::kMaskLayerTable);
+      break;
+    }
 #endif
     case eCSSProperty_font: {
       // systemFont might not be present; other values are guaranteed to be
       // available based on the shorthand check at the beginning of the
       // function, as long as the prop is enabled
       const nsCSSValue *systemFont =
         data->ValueFor(eCSSProperty__x_system_font);
       const nsCSSValue *style =
--- a/layout/style/Declaration.h
+++ b/layout/style/Declaration.h
@@ -370,16 +370,21 @@ private:
   void AppendVariableAndValueToString(const nsAString& aName,
                                       nsAString& aResult) const;
 
   void GetImageLayerValue(nsCSSCompressedDataBlock *data,
                           nsAString& aValue,
                           nsCSSValue::Serialization aSerialization,
                           const nsCSSProperty aTable[]) const;
 
+  void GetImageLayerPositionValue(nsCSSCompressedDataBlock *data,
+                                  nsAString& aValue,
+                                  nsCSSValue::Serialization aSerialization,
+                                  const nsCSSProperty aTable[]) const;
+
 public:
   /**
    * Returns the property at the given index in the ordered list of
    * declarations.  For custom properties, eCSSPropertyExtra_variable
    * is returned.
    */
   nsCSSProperty GetPropertyAt(uint32_t aIndex) const {
     uint32_t value = mOrder[aIndex];
--- a/layout/style/nsCSSParser.cpp
+++ b/layout/style/nsCSSParser.cpp
@@ -860,51 +860,57 @@ protected:
 
   struct ImageLayersShorthandParseState {
     nsCSSValue&  mColor;
     nsCSSValueList* mImage;
     nsCSSValuePairList* mRepeat;
     nsCSSValueList* mAttachment;   // A property for background layer only
     nsCSSValueList* mClip;
     nsCSSValueList* mOrigin;
-    nsCSSValueList* mPosition;
+    nsCSSValueList* mPositionX;
+    nsCSSValueList* mPositionY;
     nsCSSValuePairList* mSize;
     nsCSSValueList* mComposite;    // A property for mask layer only
     nsCSSValueList* mMode;         // A property for mask layer only
     ImageLayersShorthandParseState(
         nsCSSValue& aColor, nsCSSValueList* aImage, nsCSSValuePairList* aRepeat,
         nsCSSValueList* aAttachment, nsCSSValueList* aClip,
-        nsCSSValueList* aOrigin, nsCSSValueList* aPosition,
+        nsCSSValueList* aOrigin,
+        nsCSSValueList* aPositionX, nsCSSValueList* aPositionY,
         nsCSSValuePairList* aSize, nsCSSValueList* aComposite,
         nsCSSValueList* aMode) :
         mColor(aColor), mImage(aImage), mRepeat(aRepeat),
         mAttachment(aAttachment), mClip(aClip), mOrigin(aOrigin),
-        mPosition(aPosition), mSize(aSize), mComposite(aComposite),
+        mPositionX(aPositionX), mPositionY(aPositionY),
+        mSize(aSize), mComposite(aComposite),
         mMode(aMode) {};
   };
 
   bool IsFunctionTokenValidForImageLayerImage(const nsCSSToken& aToken) const;
   bool ParseImageLayersItem(ImageLayersShorthandParseState& aState,
                             const nsCSSProperty aTable[]);
 
   bool ParseValueList(nsCSSProperty aPropID); // a single value prop-id
   bool ParseImageLayerRepeat(nsCSSProperty aPropID);
   bool ParseImageLayerRepeatValues(nsCSSValuePair& aValue);
-  bool ParseImageLayerPosition(nsCSSProperty aPropID);
+  bool ParseImageLayerPosition(const nsCSSProperty aTable[]);
+  bool ParseImageLayerPositionCoord(nsCSSProperty aPropID, bool aIsHorizontal);
 
   // ParseBoxPositionValues parses the CSS 2.1 background-position syntax,
   // which is still used by some properties. See ParsePositionValue
   // for the css3-background syntax.
   bool ParseBoxPositionValues(nsCSSValuePair& aOut, bool aAcceptsInherit,
                               bool aAllowExplicitCenter = true); // deprecated
 
   // ParsePositionValue parses a CSS <position> value, which is used by
   // the 'background-position' property.
   bool ParsePositionValue(nsCSSValue& aOut);
-
+  bool ParsePositionValueSeparateCoords(nsCSSValue& aOutX, nsCSSValue& aOutY);
+
+  bool ParseImageLayerPositionCoordItem(nsCSSValue& aOut, bool aIsHorizontal);
   bool ParseImageLayerSize(nsCSSProperty aPropID);
   bool ParseImageLayerSizeValues(nsCSSValuePair& aOut);
   bool ParseBorderColor();
   bool ParseBorderColors(nsCSSProperty aProperty);
   void SetBorderImageInitialValues();
   bool ParseBorderImageRepeat(bool aAcceptsInherit);
   // If ParseBorderImageSlice returns false, aConsumedTokens indicates
   // whether or not any tokens were consumed (in other words, was the property
@@ -11421,17 +11427,21 @@ bool
 CSSParserImpl::ParsePropertyByFunction(nsCSSProperty aPropID)
 {
   switch (aPropID) {  // handle shorthand or multiple properties
   case eCSSProperty_background:
     return ParseImageLayers(nsStyleImageLayers::kBackgroundLayerTable);
   case eCSSProperty_background_repeat:
     return ParseImageLayerRepeat(eCSSProperty_background_repeat);
   case eCSSProperty_background_position:
-    return ParseImageLayerPosition(eCSSProperty_background_position);
+    return ParseImageLayerPosition(nsStyleImageLayers::kBackgroundLayerTable);
+  case eCSSProperty_background_position_x:
+  case eCSSProperty_background_position_y:
+    return ParseImageLayerPositionCoord(aPropID,
+               aPropID == eCSSProperty_background_position_x);
   case eCSSProperty_background_size:
     return ParseImageLayerSize(eCSSProperty_background_size);
   case eCSSProperty_border:
     return ParseBorderSide(kBorderTopIDs, true);
   case eCSSProperty_border_color:
     return ParseBorderColor();
   case eCSSProperty_border_spacing:
     return ParseBorderSpacing();
@@ -11603,17 +11613,21 @@ CSSParserImpl::ParsePropertyByFunction(n
   case eCSSProperty_scroll_snap_type:
     return ParseScrollSnapType();
 #ifdef MOZ_ENABLE_MASK_AS_SHORTHAND
   case eCSSProperty_mask:
     return ParseImageLayers(nsStyleImageLayers::kMaskLayerTable);
   case eCSSProperty_mask_repeat:
     return ParseImageLayerRepeat(eCSSProperty_mask_repeat);
   case eCSSProperty_mask_position:
-    return ParseImageLayerPosition(eCSSProperty_mask_position);
+    return ParseImageLayerPosition(nsStyleImageLayers::kMaskLayerTable);
+  case eCSSProperty_mask_position_x:
+  case eCSSProperty_mask_position_y:
+    return ParseImageLayerPositionCoord(aPropID,
+               aPropID == eCSSProperty_mask_position_x);
   case eCSSProperty_mask_size:
     return ParseImageLayerSize(eCSSProperty_mask_size);
 #endif
   case eCSSProperty__webkit_text_stroke:
     return ParseWebkitTextStroke();
   case eCSSProperty_all:
     return ParseAll();
   default:
@@ -11884,22 +11898,23 @@ CSSParserImpl::ParseImageLayers(const ns
     for (const nsCSSProperty* subprops =
            nsCSSProps::SubpropertyEntryFor(aTable[nsStyleImageLayers::shorthand]);
          *subprops != eCSSProperty_UNKNOWN; ++subprops) {
       AppendValue(*subprops, color);
     }
     return true;
   }
 
-  nsCSSValue image, repeat, attachment, clip, origin, position, size,
+  nsCSSValue image, repeat, attachment, clip, origin, positionX, positionY, size,
              composite, maskMode;
   ImageLayersShorthandParseState state(color, image.SetListValue(),
                                        repeat.SetPairListValue(),
                                        attachment.SetListValue(), clip.SetListValue(),
-                                       origin.SetListValue(), position.SetListValue(),
+                                       origin.SetListValue(),
+                                       positionX.SetListValue(), positionY.SetListValue(),
                                        size.SetPairListValue(), composite.SetListValue(),
                                        maskMode.SetListValue());
 
   for (;;) {
     if (!ParseImageLayersItem(state, aTable)) {
       return false;
     }
 
@@ -11923,17 +11938,19 @@ CSSParserImpl::ParseImageLayers(const ns
     APPENDNEXT(nsStyleImageLayers::image, state.mImage,
                nsCSSValueList);
     APPENDNEXT(nsStyleImageLayers::repeat, state.mRepeat,
                nsCSSValuePairList);
     APPENDNEXT(nsStyleImageLayers::clip, state.mClip,
                nsCSSValueList);
     APPENDNEXT(nsStyleImageLayers::origin, state.mOrigin,
                nsCSSValueList);
-    APPENDNEXT(nsStyleImageLayers::position, state.mPosition,
+    APPENDNEXT(nsStyleImageLayers::positionX, state.mPositionX,
+               nsCSSValueList);
+    APPENDNEXT(nsStyleImageLayers::positionY, state.mPositionY,
                nsCSSValueList);
     APPENDNEXT(nsStyleImageLayers::size, state.mSize,
                nsCSSValuePairList);
     APPENDNEXT(nsStyleImageLayers::attachment, state.mAttachment,
                nsCSSValueList);
     APPENDNEXT(nsStyleImageLayers::maskMode, state.mMode,
                nsCSSValueList);
     APPENDNEXT(nsStyleImageLayers::composite, state.mComposite,
@@ -11952,17 +11969,18 @@ CSSParserImpl::ParseImageLayers(const ns
   if (propID_ != eCSSProperty_UNKNOWN) { \
     AppendValue(propID_,  propValue_); \
   }
 
   APPENDVALUE(aTable[nsStyleImageLayers::image],      image);
   APPENDVALUE(aTable[nsStyleImageLayers::repeat],     repeat);
   APPENDVALUE(aTable[nsStyleImageLayers::clip],       clip);
   APPENDVALUE(aTable[nsStyleImageLayers::origin],     origin);
-  APPENDVALUE(aTable[nsStyleImageLayers::position],   position);
+  APPENDVALUE(aTable[nsStyleImageLayers::positionX],  positionX);
+  APPENDVALUE(aTable[nsStyleImageLayers::positionY],  positionY);
   APPENDVALUE(aTable[nsStyleImageLayers::size],       size);
   APPENDVALUE(aTable[nsStyleImageLayers::color],      color);
   APPENDVALUE(aTable[nsStyleImageLayers::attachment], attachment);
   APPENDVALUE(aTable[nsStyleImageLayers::maskMode],   maskMode);
   APPENDVALUE(aTable[nsStyleImageLayers::composite],  composite);
 
 #undef APPENDVALUE
 
@@ -12011,20 +12029,23 @@ CSSParserImpl::ParseImageLayersItem(
                                       eCSSUnit_Enumerated);
   aState.mRepeat->mYValue.Reset();
   aState.mAttachment->mValue.SetIntValue(NS_STYLE_IMAGELAYER_ATTACHMENT_SCROLL,
                                          eCSSUnit_Enumerated);
   aState.mClip->mValue.SetIntValue(NS_STYLE_IMAGELAYER_CLIP_BORDER,
                                    eCSSUnit_Enumerated);
   aState.mOrigin->mValue.SetIntValue(NS_STYLE_IMAGELAYER_ORIGIN_PADDING,
                                      eCSSUnit_Enumerated);
-  RefPtr<nsCSSValue::Array> positionArr = nsCSSValue::Array::Create(4);
-  aState.mPosition->mValue.SetArrayValue(positionArr, eCSSUnit_Array);
-  positionArr->Item(1).SetPercentValue(0.0f);
-  positionArr->Item(3).SetPercentValue(0.0f);
+
+  RefPtr<nsCSSValue::Array> positionXArr = nsCSSValue::Array::Create(2);
+  RefPtr<nsCSSValue::Array> positionYArr = nsCSSValue::Array::Create(2);
+  aState.mPositionX->mValue.SetArrayValue(positionXArr, eCSSUnit_Array);
+  aState.mPositionY->mValue.SetArrayValue(positionYArr, eCSSUnit_Array);
+  positionXArr->Item(1).SetPercentValue(0.0f);
+  positionYArr->Item(1).SetPercentValue(0.0f);
   aState.mSize->mXValue.SetAutoValue();
   aState.mSize->mYValue.SetAutoValue();
   aState.mComposite->mValue.SetIntValue(NS_STYLE_MASK_COMPOSITE_ADD,
                                         eCSSUnit_Enumerated);
   aState.mMode->mValue.SetIntValue(NS_STYLE_MASK_MODE_MATCH_SOURCE,
                                    eCSSUnit_Enumerated);
   bool haveColor = false,
        haveImage = false,
@@ -12087,17 +12108,19 @@ CSSParserImpl::ParseImageLayersItem(
         }
         aState.mRepeat->mXValue = scratch.mXValue;
         aState.mRepeat->mYValue = scratch.mYValue;
       } else if (nsCSSProps::FindKeyword(keyword,
                    nsCSSProps::kImageLayerPositionKTable, dummy)) {
         if (havePositionAndSize)
           return false;
         havePositionAndSize = true;
-        if (!ParsePositionValue(aState.mPosition->mValue)) {
+
+        if (!ParsePositionValueSeparateCoords(aState.mPositionX->mValue,
+                                              aState.mPositionY->mValue)) {
           return false;
         }
         if (ExpectSymbol('/', true)) {
           nsCSSValuePair scratch;
           if (!ParseImageLayerSizeValues(scratch)) {
             return false;
           }
           aState.mSize->mXValue = scratch.mXValue;
@@ -12199,17 +12222,18 @@ CSSParserImpl::ParseImageLayersItem(
                tt == eCSSToken_Number ||
                tt == eCSSToken_Percentage ||
                (tt == eCSSToken_Function &&
                 (mToken.mIdent.LowerCaseEqualsLiteral("calc") ||
                  mToken.mIdent.LowerCaseEqualsLiteral("-moz-calc")))) {
       if (havePositionAndSize)
         return false;
       havePositionAndSize = true;
-      if (!ParsePositionValue(aState.mPosition->mValue)) {
+      if (!ParsePositionValueSeparateCoords(aState.mPositionX->mValue,
+                                            aState.mPositionY->mValue)) {
         return false;
       }
       if (ExpectSymbol('/', true)) {
         nsCSSValuePair scratch;
         if (!ParseImageLayerSizeValues(scratch)) {
           return false;
         }
         aState.mSize->mXValue = scratch.mXValue;
@@ -12308,35 +12332,73 @@ CSSParserImpl::ParseImageLayerRepeatValu
       yValue.Reset();
     }
     return true;
   }
 
   return false;
 }
 
-// This function is very similar to ParseScrollSnapCoordinate,
-// ParseImageLayers, ParseImageLayerSize.
-bool
-CSSParserImpl::ParseImageLayerPosition(nsCSSProperty aPropID)
+bool
+CSSParserImpl::ParseImageLayerPosition(const nsCSSProperty aTable[])
+{
+  // 'initial', 'inherit' and 'unset' stand alone, no list permitted.
+  nsCSSValue position;
+  if (ParseSingleTokenVariant(position, VARIANT_INHERIT, nullptr)) {
+    AppendValue(aTable[nsStyleImageLayers::positionX], position);
+    AppendValue(aTable[nsStyleImageLayers::positionY], position);
+    return true;
+  }
+
+  nsCSSValue itemValueX;
+  nsCSSValue itemValueY;
+  if (!ParsePositionValueSeparateCoords(itemValueX, itemValueY)) {
+    return false;
+  }
+
+  nsCSSValue valueX;
+  nsCSSValue valueY;
+  nsCSSValueList* itemX = valueX.SetListValue();
+  nsCSSValueList* itemY = valueY.SetListValue();
+  for (;;) {
+    itemX->mValue = itemValueX;
+    itemY->mValue = itemValueY;
+    if (!ExpectSymbol(',', true)) {
+      break;
+    }
+    if (!ParsePositionValueSeparateCoords(itemValueX, itemValueY)) {
+      return false;
+    }
+    itemX->mNext = new nsCSSValueList;
+    itemY->mNext = new nsCSSValueList;
+    itemX = itemX->mNext;
+    itemY = itemY->mNext;
+  }
+  AppendValue(aTable[nsStyleImageLayers::positionX], valueX);
+  AppendValue(aTable[nsStyleImageLayers::positionY], valueY);
+  return true;
+}
+
+bool
+CSSParserImpl::ParseImageLayerPositionCoord(nsCSSProperty aPropID, bool aIsHorizontal)
 {
   nsCSSValue value;
   // 'initial', 'inherit' and 'unset' stand alone, no list permitted.
   if (!ParseSingleTokenVariant(value, VARIANT_INHERIT, nullptr)) {
     nsCSSValue itemValue;
-    if (!ParsePositionValue(itemValue)) {
+    if (!ParseImageLayerPositionCoordItem(itemValue, aIsHorizontal)) {
       return false;
     }
     nsCSSValueList* item = value.SetListValue();
     for (;;) {
       item->mValue = itemValue;
       if (!ExpectSymbol(',', true)) {
         break;
       }
-      if (!ParsePositionValue(itemValue)) {
+      if (!ParseImageLayerPositionCoordItem(itemValue, aIsHorizontal)) {
         return false;
       }
       item->mNext = new nsCSSValueList;
       item = item->mNext;
     }
   }
   AppendValue(aPropID, value);
   return true;
@@ -12626,16 +12688,90 @@ CSSParserImpl::ParsePositionValue(nsCSSV
     xOffset = yOffset;
     yEdge = swapEdge;
     yOffset = swapOffset;
   }
 
   return true;
 }
 
+bool
+CSSParserImpl::ParsePositionValueSeparateCoords(nsCSSValue& aOutX, nsCSSValue& aOutY)
+{
+  nsCSSValue scratch;
+  if (!ParsePositionValue(scratch)) {
+    return false;
+  }
+
+  // Separate the four values into two pairs of two values for X and Y.
+  RefPtr<nsCSSValue::Array> valueX = nsCSSValue::Array::Create(2);
+  RefPtr<nsCSSValue::Array> valueY = nsCSSValue::Array::Create(2);
+  aOutX.SetArrayValue(valueX, eCSSUnit_Array);
+  aOutY.SetArrayValue(valueY, eCSSUnit_Array);
+
+  RefPtr<nsCSSValue::Array> value = scratch.GetArrayValue();
+  valueX->Item(0) = value->Item(0);
+  valueX->Item(1) = value->Item(1);
+  valueY->Item(0) = value->Item(2);
+  valueY->Item(1) = value->Item(3);
+  return true;
+}
+
+// Parses one item in a list of values for the 'background-position-x' or
+// 'background-position-y' property. Does not support the start/end keywords.
+// Spec reference: https://drafts.csswg.org/css-backgrounds-4/#propdef-background-position-x
+bool
+CSSParserImpl::ParseImageLayerPositionCoordItem(nsCSSValue& aOut, bool aIsHorizontal)
+{
+  RefPtr<nsCSSValue::Array> value = nsCSSValue::Array::Create(2);
+  aOut.SetArrayValue(value, eCSSUnit_Array);
+
+  nsCSSValue &edge   = value->Item(0),
+             &offset = value->Item(1);
+
+  nsCSSValue edgeOrOffset;
+  CSSParseResult result =
+    ParseVariant(edgeOrOffset, VARIANT_LPCALC | VARIANT_KEYWORD,
+                 nsCSSProps::kImageLayerPositionKTable);
+  if (result != CSSParseResult::Ok) {
+    return false;
+  }
+
+  if (edgeOrOffset.GetUnit() == eCSSUnit_Enumerated) {
+    edge = edgeOrOffset;
+
+    // The edge can be followed by an optional offset.
+    result = ParseVariant(offset, VARIANT_LPCALC, nullptr);
+    if (result == CSSParseResult::Error) {
+      return false;
+    }
+  } else {
+    offset = edgeOrOffset;
+  }
+
+  // Keywords for horizontal properties cannot be vertical keywords, and
+  // keywords for vertical properties cannot be horizontal keywords.
+  // Also, if an offset is specified, the edge cannot be center.
+  int32_t edgeEnum =
+          edge.GetUnit() == eCSSUnit_Enumerated ? edge.GetIntValue() : 0;
+  int32_t allowedKeywords =
+    (aIsHorizontal ? (BG_LEFT | BG_RIGHT) : (BG_TOP | BG_BOTTOM)) |
+    (offset.GetUnit() == eCSSUnit_Null ? BG_CENTER : 0);
+  if (edgeEnum & ~allowedKeywords) {
+    return false;
+  }
+
+  NS_ASSERTION((eCSSUnit_Enumerated == edge.GetUnit() ||
+                eCSSUnit_Null       == edge.GetUnit()) &&
+               eCSSUnit_Enumerated != offset.GetUnit(),
+               "Unexpected units");
+
+  return true;
+}
+
 // This function is very similar to ParseScrollSnapCoordinate,
 // ParseImageLayers, and ParseImageLayerPosition.
 bool
 CSSParserImpl::ParseImageLayerSize(nsCSSProperty aPropID)
 {
   nsCSSValue value;
   // 'initial', 'inherit' and 'unset' stand alone, no list permitted.
   if (!ParseSingleTokenVariant(value, VARIANT_INHERIT, nullptr)) {
--- a/layout/style/nsCSSPropAliasList.h
+++ b/layout/style/nsCSSPropAliasList.h
@@ -378,16 +378,24 @@ CSS_PROP_ALIAS(-webkit-mask-image,
 CSS_PROP_ALIAS(-webkit-mask-origin,
                mask_origin,
                WebkitMaskOrigin,
                WEBKIT_PREFIX_PREF)
 CSS_PROP_ALIAS(-webkit-mask-position,
                mask_position,
                WebkitMaskPosition,
                WEBKIT_PREFIX_PREF)
+CSS_PROP_ALIAS(-webkit-mask-position-x,
+               mask_position_x,
+               WebkitMaskPositionX,
+               WEBKIT_PREFIX_PREF)
+CSS_PROP_ALIAS(-webkit-mask-position-y,
+               mask_position_y,
+               WebkitMaskPositionY,
+               WEBKIT_PREFIX_PREF)
 CSS_PROP_ALIAS(-webkit-mask-repeat,
                mask_repeat,
                WebkitMaskRepeat,
                WEBKIT_PREFIX_PREF)
 CSS_PROP_ALIAS(-webkit-mask-size,
                mask_size,
                WebkitMaskSize,
                WEBKIT_PREFIX_PREF)
--- a/layout/style/nsCSSPropList.h
+++ b/layout/style/nsCSSPropList.h
@@ -562,25 +562,45 @@ CSS_PROP_BACKGROUND(
         CSS_PROPERTY_APPLIES_TO_FIRST_LETTER_AND_FIRST_LINE |
         CSS_PROPERTY_APPLIES_TO_PLACEHOLDER |
         CSS_PROPERTY_VALUE_LIST_USES_COMMAS,
     "",
     VARIANT_KEYWORD, // used by list parsing
     kImageLayerOriginKTable,
     CSS_PROP_NO_OFFSET,
     eStyleAnimType_None)
-CSS_PROP_BACKGROUND(
+CSS_PROP_SHORTHAND(
     background-position,
     background_position,
     BackgroundPosition,
     CSS_PROPERTY_PARSE_FUNCTION |
+        CSS_PROPERTY_UNITLESS_LENGTH_QUIRK,
+    "")
+CSS_PROP_BACKGROUND(
+    background-position-x,
+    background_position_x,
+    BackgroundPositionX,
+    CSS_PROPERTY_PARSE_FUNCTION |
         CSS_PROPERTY_APPLIES_TO_FIRST_LETTER_AND_FIRST_LINE |
         CSS_PROPERTY_APPLIES_TO_PLACEHOLDER |
         CSS_PROPERTY_VALUE_LIST_USES_COMMAS |
-        CSS_PROPERTY_UNITLESS_LENGTH_QUIRK |
+        CSS_PROPERTY_STORES_CALC,
+    "",
+    0,
+    kImageLayerPositionKTable,
+    CSS_PROP_NO_OFFSET,
+    eStyleAnimType_Custom)
+CSS_PROP_BACKGROUND(
+    background-position-y,
+    background_position_y,
+    BackgroundPositionY,
+    CSS_PROPERTY_PARSE_FUNCTION |
+        CSS_PROPERTY_APPLIES_TO_FIRST_LETTER_AND_FIRST_LINE |
+        CSS_PROPERTY_APPLIES_TO_PLACEHOLDER |
+        CSS_PROPERTY_VALUE_LIST_USES_COMMAS |
         CSS_PROPERTY_STORES_CALC,
     "",
     0,
     kImageLayerPositionKTable,
     CSS_PROP_NO_OFFSET,
     eStyleAnimType_Custom)
 CSS_PROP_BACKGROUND(
     background-repeat,
@@ -2709,21 +2729,40 @@ CSS_PROP_SVGRESET(
     MaskOrigin,
     CSS_PROPERTY_PARSE_VALUE_LIST |
         CSS_PROPERTY_VALUE_LIST_USES_COMMAS,
     "",
     VARIANT_KEYWORD, // used by list parsing
     kImageLayerOriginKTable,
     CSS_PROP_NO_OFFSET,
     eStyleAnimType_None)
-CSS_PROP_SVGRESET(
+CSS_PROP_SHORTHAND(
     mask-position,
     mask_position,
     MaskPosition,
     CSS_PROPERTY_PARSE_FUNCTION |
+        CSS_PROPERTY_UNITLESS_LENGTH_QUIRK,
+    "")
+CSS_PROP_SVGRESET(
+    mask-position-x,
+    mask_position_x,
+    MaskPositionX,
+    CSS_PROPERTY_PARSE_FUNCTION |
+        CSS_PROPERTY_VALUE_LIST_USES_COMMAS |
+        CSS_PROPERTY_STORES_CALC,
+    "",
+    0,
+    kImageLayerPositionKTable,
+    CSS_PROP_NO_OFFSET,
+    eStyleAnimType_Custom)
+CSS_PROP_SVGRESET(
+    mask-position-y,
+    mask_position_y,
+    MaskPositionY,
+    CSS_PROPERTY_PARSE_FUNCTION |
         CSS_PROPERTY_VALUE_LIST_USES_COMMAS |
         CSS_PROPERTY_STORES_CALC,
     "",
     0,
     kImageLayerPositionKTable,
     CSS_PROP_NO_OFFSET,
     eStyleAnimType_Custom)
 CSS_PROP_SVGRESET(
--- a/layout/style/nsCSSProps.cpp
+++ b/layout/style/nsCSSProps.cpp
@@ -2620,23 +2620,30 @@ static const nsCSSProperty gOutlineRadiu
   eCSSProperty_UNKNOWN
 };
 
 static const nsCSSProperty gBackgroundSubpropTable[] = {
   eCSSProperty_background_color,
   eCSSProperty_background_image,
   eCSSProperty_background_repeat,
   eCSSProperty_background_attachment,
-  eCSSProperty_background_position,
   eCSSProperty_background_clip,
   eCSSProperty_background_origin,
+  eCSSProperty_background_position_x,
+  eCSSProperty_background_position_y,
   eCSSProperty_background_size,
   eCSSProperty_UNKNOWN
 };
 
+static const nsCSSProperty gBackgroundPositionSubpropTable[] = {
+  eCSSProperty_background_position_x,
+  eCSSProperty_background_position_y,
+  eCSSProperty_UNKNOWN
+};
+
 static const nsCSSProperty gBorderSubpropTable[] = {
   eCSSProperty_border_top_width,
   eCSSProperty_border_right_width,
   eCSSProperty_border_bottom_width,
   eCSSProperty_border_left_width,
   eCSSProperty_border_top_style,
   eCSSProperty_border_right_style,
   eCSSProperty_border_bottom_style,
@@ -2960,24 +2967,30 @@ static const nsCSSProperty gScrollSnapTy
   eCSSProperty_scroll_snap_type_x,
   eCSSProperty_scroll_snap_type_y,
   eCSSProperty_UNKNOWN
 };
 #ifdef MOZ_ENABLE_MASK_AS_SHORTHAND
 static const nsCSSProperty gMaskSubpropTable[] = {
   eCSSProperty_mask_image,
   eCSSProperty_mask_repeat,
-  eCSSProperty_mask_position,
+  eCSSProperty_mask_position_x,
+  eCSSProperty_mask_position_y,
   eCSSProperty_mask_clip,
   eCSSProperty_mask_origin,
   eCSSProperty_mask_size,
   eCSSProperty_mask_composite,
   eCSSProperty_mask_mode,
   eCSSProperty_UNKNOWN
 };
+static const nsCSSProperty gMaskPositionSubpropTable[] = {
+  eCSSProperty_mask_position_x,
+  eCSSProperty_mask_position_y,
+  eCSSProperty_UNKNOWN
+};
 #endif
 // FIXME: mask-border tables should be added when we implement
 // mask-border properties.
 
 const nsCSSProperty *const
 nsCSSProps::kSubpropertyTable[eCSSProperty_COUNT - eCSSProperty_COUNT_no_shorthands] = {
 #define CSS_PROP_PUBLIC_OR_PRIVATE(publicname_, privatename_) privatename_
 // Need an extra level of macro nesting to force expansion of method_
--- a/layout/style/nsCSSValue.cpp
+++ b/layout/style/nsCSSValue.cpp
@@ -929,17 +929,17 @@ nsCSSValue::AppendCircleOrEllipseToStrin
                "unexpected value");
     return;
   }
 
   if (hasRadii) {
     aResult.Append(' ');
   }
   aResult.AppendLiteral("at ");
-  array->Item(count).AppendToString(eCSSProperty_background_position,
+  array->Item(count).AppendToString(eCSSProperty_object_position,
                                     aResult, aSerialization);
 }
 
 // Helper to append |aString| with the shorthand sides notation used in e.g.
 // 'padding'. |aProperties| and |aValues| are expected to have 4 elements.
 /*static*/ void
 nsCSSValue::AppendSidesShorthandToString(const nsCSSProperty aProperties[],
                                          const nsCSSValue* aValues[],
@@ -1544,22 +1544,22 @@ nsCSSValue::AppendToString(nsCSSProperty
           gradient->mBgPos.mYValue.GetUnit() != eCSSUnit_None) {
         MOZ_ASSERT(gradient->mAngle.GetUnit() == eCSSUnit_None);
         MOZ_ASSERT(gradient->mBgPos.mXValue.GetUnit() == eCSSUnit_Enumerated &&
                    gradient->mBgPos.mYValue.GetUnit() == eCSSUnit_Enumerated,
                    "unexpected unit");
         aResult.AppendLiteral("to");
         if (!(gradient->mBgPos.mXValue.GetIntValue() & NS_STYLE_IMAGELAYER_POSITION_CENTER)) {
           aResult.Append(' ');
-          gradient->mBgPos.mXValue.AppendToString(eCSSProperty_background_position,
+          gradient->mBgPos.mXValue.AppendToString(eCSSProperty_background_position_x,
                                                   aResult, aSerialization);
         }
         if (!(gradient->mBgPos.mYValue.GetIntValue() & NS_STYLE_IMAGELAYER_POSITION_CENTER)) {
           aResult.Append(' ');
-          gradient->mBgPos.mYValue.AppendToString(eCSSProperty_background_position,
+          gradient->mBgPos.mYValue.AppendToString(eCSSProperty_background_position_y,
                                                   aResult, aSerialization);
         }
         needSep = true;
       } else if (gradient->mAngle.GetUnit() != eCSSUnit_None) {
         gradient->mAngle.AppendToString(aProperty, aResult, aSerialization);
         needSep = true;
       }
     } else if (gradient->mBgPos.mXValue.GetUnit() != eCSSUnit_None ||
@@ -1567,22 +1567,22 @@ nsCSSValue::AppendToString(nsCSSProperty
         gradient->mAngle.GetUnit() != eCSSUnit_None) {
       if (needSep) {
         aResult.Append(' ');
       }
       if (gradient->mIsRadial && !gradient->mIsLegacySyntax) {
         aResult.AppendLiteral("at ");
       }
       if (gradient->mBgPos.mXValue.GetUnit() != eCSSUnit_None) {
-        gradient->mBgPos.mXValue.AppendToString(eCSSProperty_background_position,
+        gradient->mBgPos.mXValue.AppendToString(eCSSProperty_background_position_x,
                                                 aResult, aSerialization);
         aResult.Append(' ');
       }
       if (gradient->mBgPos.mYValue.GetUnit() != eCSSUnit_None) {
-        gradient->mBgPos.mYValue.AppendToString(eCSSProperty_background_position,
+        gradient->mBgPos.mYValue.AppendToString(eCSSProperty_background_position_y,
                                                 aResult, aSerialization);
         aResult.Append(' ');
       }
       if (gradient->mAngle.GetUnit() != eCSSUnit_None) {
         MOZ_ASSERT(gradient->mIsLegacySyntax,
                    "angle is allowed only for legacy syntax");
         gradient->mAngle.AppendToString(aProperty, aResult, aSerialization);
       }
--- a/layout/style/nsComputedDOMStyle.cpp
+++ b/layout/style/nsComputedDOMStyle.cpp
@@ -2154,19 +2154,24 @@ nsComputedDOMStyle::DoGetImageLayerImage
   }
 
   return valueList.forget();
 }
 
 already_AddRefed<CSSValue>
 nsComputedDOMStyle::DoGetImageLayerPosition(const nsStyleImageLayers& aLayers)
 {
+  if (aLayers.mPositionXCount != aLayers.mPositionYCount) {
+    // No value to return.  We can't express this combination of
+    // values as a shorthand.
+    return nullptr;
+  }
+
   RefPtr<nsDOMCSSValueList> valueList = GetROCSSValueList(true);
-
-  for (uint32_t i = 0, i_end = aLayers.mPositionCount; i < i_end; ++i) {
+  for (uint32_t i = 0, i_end = aLayers.mPositionXCount; i < i_end; ++i) {
     RefPtr<nsDOMCSSValueList> itemList = GetROCSSValueList(false);
 
     SetValueToPosition(aLayers.mLayers[i].mPosition, itemList);
     valueList->AppendCSSValue(itemList.forget());
   }
 
   return valueList.forget();
 }
--- a/layout/style/nsRuleNode.cpp
+++ b/layout/style/nsRuleNode.cpp
@@ -6622,16 +6622,19 @@ ComputePositionValue(nsStyleContext* aSt
                      const nsCSSValue& aValue,
                      nsStyleImageLayers::Position& aComputedValue,
                      RuleNodeCacheConditions& aConditions)
 {
   NS_ASSERTION(aValue.GetUnit() == eCSSUnit_Array,
                "unexpected unit for CSS <position> value");
 
   RefPtr<nsCSSValue::Array> positionArray = aValue.GetArrayValue();
+  NS_ASSERTION(positionArray->Count() == 4,
+               "unexpected number of values in CSS <position> value");
+
   const nsCSSValue &xEdge   = positionArray->Item(0);
   const nsCSSValue &xOffset = positionArray->Item(1);
   const nsCSSValue &yEdge   = positionArray->Item(2);
   const nsCSSValue &yOffset = positionArray->Item(3);
 
   NS_ASSERTION((eCSSUnit_Enumerated == xEdge.GetUnit()  ||
                 eCSSUnit_Null       == xEdge.GetUnit()) &&
                (eCSSUnit_Enumerated == yEdge.GetUnit()  ||
@@ -6644,29 +6647,43 @@ ComputePositionValue(nsStyleContext* aSt
                        &aComputedValue.mXPosition,
                        aConditions);
 
   ComputePositionCoord(aStyleContext, yEdge, yOffset,
                        &aComputedValue.mYPosition,
                        aConditions);
 }
 
-template <>
-struct BackgroundItemComputer<nsCSSValueList, nsStyleImageLayers::Position>
-{
-  static void ComputeValue(nsStyleContext* aStyleContext,
-                           const nsCSSValueList* aSpecifiedValue,
-                           nsStyleImageLayers::Position& aComputedValue,
-                           RuleNodeCacheConditions& aConditions)
-  {
-    ComputePositionValue(aStyleContext, aSpecifiedValue->mValue,
-                         aComputedValue, aConditions);
-  }
-};
-
+/* Helper function to convert the -x or -y part of a CSS <position> specified
+ * value into its computed-style form. */
+static void
+ComputePositionCoordValue(nsStyleContext* aStyleContext,
+                          const nsCSSValue& aValue,
+                          nsStyleImageLayers::Position::PositionCoord& aComputedValue,
+                          RuleNodeCacheConditions& aConditions)
+{
+  NS_ASSERTION(aValue.GetUnit() == eCSSUnit_Array,
+               "unexpected unit for position coord value");
+
+  RefPtr<nsCSSValue::Array> positionArray = aValue.GetArrayValue();
+  NS_ASSERTION(positionArray->Count() == 2,
+               "unexpected number of values, expecting one edge and one offset");
+
+  const nsCSSValue &edge   = positionArray->Item(0);
+  const nsCSSValue &offset = positionArray->Item(1);
+
+  NS_ASSERTION((eCSSUnit_Enumerated == edge.GetUnit() ||
+                eCSSUnit_Null       == edge.GetUnit()) &&
+               eCSSUnit_Enumerated != offset.GetUnit(),
+               "Invalid background position");
+
+  ComputePositionCoord(aStyleContext, edge, offset,
+                       &aComputedValue,
+                       aConditions);
+}
 
 struct BackgroundSizeAxis {
   nsCSSValue nsCSSValuePairList::* specified;
   nsStyleImageLayers::Size::Dimension nsStyleImageLayers::Size::* result;
   uint8_t nsStyleImageLayers::Size::* type;
 };
 
 static const BackgroundSizeAxis gBGSizeAxes[] = {
@@ -6822,16 +6839,86 @@ SetImageLayerList(nsStyleContext* aStyle
   default:
     MOZ_ASSERT(false, "unexpected unit");
   }
 
   if (aItemCount > aMaxItemCount)
     aMaxItemCount = aItemCount;
 }
 
+// The same as SetImageLayerList, but for values stored in
+// layer.mPosition.*aResultLocation instead of layer.*aResultLocation.
+// This code is duplicated because it would be annoying to make
+// SetImageLayerList generic enough to handle both cases.
+static void
+SetImageLayerPositionCoordList(
+                  nsStyleContext* aStyleContext,
+                  const nsCSSValue& aValue,
+                  nsStyleAutoArray<nsStyleImageLayers::Layer>& aLayers,
+                  const nsStyleAutoArray<nsStyleImageLayers::Layer>& aParentLayers,
+                  nsStyleImageLayers::Position::PositionCoord
+                      nsStyleImageLayers::Position::* aResultLocation,
+                  nsStyleImageLayers::Position::PositionCoord aInitialValue,
+                  uint32_t aParentItemCount,
+                  uint32_t& aItemCount,
+                  uint32_t& aMaxItemCount,
+                  bool& aRebuild,
+                  RuleNodeCacheConditions& aConditions)
+{
+  switch (aValue.GetUnit()) {
+  case eCSSUnit_Null:
+    break;
+
+  case eCSSUnit_Inherit:
+    aRebuild = true;
+    aConditions.SetUncacheable();
+    aLayers.EnsureLengthAtLeast(aParentItemCount);
+    aItemCount = aParentItemCount;
+    for (uint32_t i = 0; i < aParentItemCount; ++i) {
+      aLayers[i].mPosition.*aResultLocation = aParentLayers[i].mPosition.*aResultLocation;
+    }
+    break;
+
+  case eCSSUnit_Initial:
+  case eCSSUnit_Unset:
+    aRebuild = true;
+    aItemCount = 1;
+    aLayers[0].mPosition.*aResultLocation = aInitialValue;
+    break;
+
+  case eCSSUnit_List:
+  case eCSSUnit_ListDep: {
+    aRebuild = true;
+    aItemCount = 0;
+    const nsCSSValueList* item = aValue.GetListValue();
+    do {
+      NS_ASSERTION(item->mValue.GetUnit() != eCSSUnit_Null &&
+                   item->mValue.GetUnit() != eCSSUnit_Inherit &&
+                   item->mValue.GetUnit() != eCSSUnit_Initial &&
+                   item->mValue.GetUnit() != eCSSUnit_Unset,
+                   "unexpected unit");
+      ++aItemCount;
+      aLayers.EnsureLengthAtLeast(aItemCount);
+
+      ComputePositionCoordValue(aStyleContext, item->mValue,
+                                aLayers[aItemCount-1].mPosition.*aResultLocation,
+                                aConditions);
+      item = item->mNext;
+    } while (item);
+    break;
+  }
+
+  default:
+    MOZ_ASSERT(false, "unexpected unit");
+  }
+
+  if (aItemCount > aMaxItemCount)
+    aMaxItemCount = aItemCount;
+}
+
 template <class ComputedValueItem>
 static void
 SetImageLayerPairList(nsStyleContext* aStyleContext,
                       const nsCSSValue& aValue,
                       nsStyleAutoArray<nsStyleImageLayers::Layer>& aLayers,
                       const nsStyleAutoArray<nsStyleImageLayers::Layer>& aParentLayers,
                       ComputedValueItem nsStyleImageLayers::Layer::*
                                                                 aResultLocation,
@@ -6906,16 +6993,34 @@ FillBackgroundList(
   for (uint32_t sourceLayer = 0, destLayer = aItemCount;
        destLayer < aFillCount;
        ++sourceLayer, ++destLayer) {
     aLayers[destLayer].*aResultLocation =
       aLayers[sourceLayer].*aResultLocation;
   }
 }
 
+// The same as FillBackgroundList, but for values stored in
+// layer.mPosition.*aResultLocation instead of layer.*aResultLocation.
+static void
+FillBackgroundPositionCoordList(
+    nsStyleAutoArray<nsStyleImageLayers::Layer>& aLayers,
+    nsStyleImageLayers::Position::PositionCoord
+        nsStyleImageLayers::Position::* aResultLocation,
+    uint32_t aItemCount, uint32_t aFillCount)
+{
+  NS_PRECONDITION(aFillCount <= aLayers.Length(), "unexpected array length");
+  for (uint32_t sourceLayer = 0, destLayer = aItemCount;
+       destLayer < aFillCount;
+       ++sourceLayer, ++destLayer) {
+    aLayers[destLayer].mPosition.*aResultLocation =
+      aLayers[sourceLayer].mPosition.*aResultLocation;
+  }
+}
+
 const void*
 nsRuleNode::ComputeBackgroundData(void* aStartStruct,
                                   const nsRuleData* aRuleData,
                                   nsStyleContext* aContext,
                                   nsRuleNode* aHighestNode,
                                   const RuleDetail aRuleDetail,
                                   const RuleNodeCacheConditions aConditions)
 {
@@ -6990,25 +7095,37 @@ nsRuleNode::ComputeBackgroundData(void* 
                     bg->mImage.mLayers,
                     parentBG->mImage.mLayers,
                     &nsStyleImageLayers::Layer::mOrigin,
                     uint8_t(NS_STYLE_IMAGELAYER_ORIGIN_PADDING),
                     parentBG->mImage.mOriginCount,
                     bg->mImage.mOriginCount, maxItemCount, rebuild,
                     conditions);
 
-  // background-position: enum, length, percent (flags), inherit [pair list]
-  nsStyleImageLayers::Position initialPosition;
-  initialPosition.SetInitialPercentValues(0.0f);
-  SetImageLayerList(aContext, *aRuleData->ValueForBackgroundPosition(),
+  // background-position-x/y: enum, length, percent (flags), inherit [list]
+  nsStyleImageLayers::Position::PositionCoord initialPositionCoord;
+  initialPositionCoord.mPercent = 0.0f;
+  initialPositionCoord.mLength = 0;
+  initialPositionCoord.mHasPercent = true;
+
+  SetImageLayerPositionCoordList(
+                    aContext, *aRuleData->ValueForBackgroundPositionX(),
                     bg->mImage.mLayers,
                     parentBG->mImage.mLayers,
-                    &nsStyleImageLayers::Layer::mPosition,
-                    initialPosition, parentBG->mImage.mPositionCount,
-                    bg->mImage.mPositionCount, maxItemCount, rebuild,
+                    &nsStyleImageLayers::Position::mXPosition,
+                    initialPositionCoord, parentBG->mImage.mPositionXCount,
+                    bg->mImage.mPositionXCount, maxItemCount, rebuild,
+                    conditions);
+  SetImageLayerPositionCoordList(
+                    aContext, *aRuleData->ValueForBackgroundPositionY(),
+                    bg->mImage.mLayers,
+                    parentBG->mImage.mLayers,
+                    &nsStyleImageLayers::Position::mYPosition,
+                    initialPositionCoord, parentBG->mImage.mPositionYCount,
+                    bg->mImage.mPositionYCount, maxItemCount, rebuild,
                     conditions);
 
   // background-size: enum, length, auto, inherit, initial [pair list]
   nsStyleImageLayers::Size initialSize;
   initialSize.SetInitialValues();
   SetImageLayerPairList(aContext, *aRuleData->ValueForBackgroundSize(),
                         bg->mImage.mLayers,
                         parentBG->mImage.mLayers,
@@ -7036,19 +7153,22 @@ nsRuleNode::ComputeBackgroundData(void* 
                        &nsStyleImageLayers::Layer::mClip,
                        bg->mImage.mClipCount, fillCount);
     FillBackgroundList(bg->mImage.mLayers,
                        &nsStyleImageLayers::Layer::mBlendMode,
                        bg->mImage.mBlendModeCount, fillCount);
     FillBackgroundList(bg->mImage.mLayers,
                        &nsStyleImageLayers::Layer::mOrigin,
                        bg->mImage.mOriginCount, fillCount);
-    FillBackgroundList(bg->mImage.mLayers,
-                       &nsStyleImageLayers::Layer::mPosition,
-                       bg->mImage.mPositionCount, fillCount);
+    FillBackgroundPositionCoordList(bg->mImage.mLayers,
+                                    &nsStyleImageLayers::Position::mXPosition,
+                                    bg->mImage.mPositionXCount, fillCount);
+    FillBackgroundPositionCoordList(bg->mImage.mLayers,
+                                    &nsStyleImageLayers::Position::mYPosition,
+                                    bg->mImage.mPositionYCount, fillCount);
     FillBackgroundList(bg->mImage.mLayers,
                        &nsStyleImageLayers::Layer::mSize,
                        bg->mImage.mSizeCount, fillCount);
   }
 
   // Now that the dust has settled, register the images with the document
   bg->mImage.TrackImages(aContext->PresContext());
 
@@ -9787,25 +9907,37 @@ nsRuleNode::ComputeSVGResetData(void* aS
                     svgReset->mMask.mLayers,
                     parentSVGReset->mMask.mLayers,
                     &nsStyleImageLayers::Layer::mOrigin,
                     uint8_t(NS_STYLE_IMAGELAYER_ORIGIN_PADDING),
                     parentSVGReset->mMask.mOriginCount,
                     svgReset->mMask.mOriginCount, maxItemCount, rebuild,
                     conditions);
 
-  // mask-position: enum, length, percent (flags), inherit [pair list]
-  nsStyleImageLayers::Position initialPosition;
-  initialPosition.SetInitialPercentValues(0.0f);
-  SetImageLayerList(aContext, *aRuleData->ValueForMaskPosition(),
+  // mask-position-x/y: enum, length, percent (flags), inherit [list]
+  nsStyleImageLayers::Position::PositionCoord initialPositionCoord;
+  initialPositionCoord.mPercent = 0.0f;
+  initialPositionCoord.mLength = 0;
+  initialPositionCoord.mHasPercent = true;
+
+  SetImageLayerPositionCoordList(
+                    aContext, *aRuleData->ValueForMaskPositionX(),
                     svgReset->mMask.mLayers,
                     parentSVGReset->mMask.mLayers,
-                    &nsStyleImageLayers::Layer::mPosition,
-                    initialPosition, parentSVGReset->mMask.mPositionCount,
-                    svgReset->mMask.mPositionCount, maxItemCount, rebuild,
+                    &nsStyleImageLayers::Position::mXPosition,
+                    initialPositionCoord, parentSVGReset->mMask.mPositionXCount,
+                    svgReset->mMask.mPositionXCount, maxItemCount, rebuild,
+                    conditions);
+  SetImageLayerPositionCoordList(
+                    aContext, *aRuleData->ValueForMaskPositionY(),
+                    svgReset->mMask.mLayers,
+                    parentSVGReset->mMask.mLayers,
+                    &nsStyleImageLayers::Position::mYPosition,
+                    initialPositionCoord, parentSVGReset->mMask.mPositionYCount,
+                    svgReset->mMask.mPositionYCount, maxItemCount, rebuild,
                     conditions);
 
   // mask-size: enum, length, auto, inherit, initial [pair list]
   nsStyleImageLayers::Size initialSize;
   initialSize.SetInitialValues();
   SetImageLayerPairList(aContext, *aRuleData->ValueForMaskSize(),
                         svgReset->mMask.mLayers,
                         parentSVGReset->mMask.mLayers,
@@ -9849,19 +9981,22 @@ nsRuleNode::ComputeSVGResetData(void* aS
                        &nsStyleImageLayers::Layer::mRepeat,
                        svgReset->mMask.mRepeatCount, fillCount);
     FillBackgroundList(svgReset->mMask.mLayers,
                        &nsStyleImageLayers::Layer::mClip,
                        svgReset->mMask.mClipCount, fillCount);
     FillBackgroundList(svgReset->mMask.mLayers,
                        &nsStyleImageLayers::Layer::mOrigin,
                        svgReset->mMask.mOriginCount, fillCount);
-    FillBackgroundList(svgReset->mMask.mLayers,
-                       &nsStyleImageLayers::Layer::mPosition,
-                       svgReset->mMask.mPositionCount, fillCount);
+    FillBackgroundPositionCoordList(svgReset->mMask.mLayers,
+                                    &nsStyleImageLayers::Position::mXPosition,
+                                    svgReset->mMask.mPositionXCount, fillCount);
+    FillBackgroundPositionCoordList(svgReset->mMask.mLayers,
+                                    &nsStyleImageLayers::Position::mYPosition,
+                                    svgReset->mMask.mPositionYCount, fillCount);
     FillBackgroundList(svgReset->mMask.mLayers,
                        &nsStyleImageLayers::Layer::mSize,
                        svgReset->mMask.mSizeCount, fillCount);
     FillBackgroundList(svgReset->mMask.mLayers,
                        &nsStyleImageLayers::Layer::mMaskMode,
                        svgReset->mMask.mMaskModeCount, fillCount);
     FillBackgroundList(svgReset->mMask.mLayers,
                        &nsStyleImageLayers::Layer::mComposite,
--- a/layout/style/nsStyleStruct.cpp
+++ b/layout/style/nsStyleStruct.cpp
@@ -2276,80 +2276,85 @@ nsStyleImage::operator==(const nsStyleIm
 // nsStyleImageLayers
 //
 
 const nsCSSProperty nsStyleImageLayers::kBackgroundLayerTable[] = {
   eCSSProperty_background,                // shorthand
   eCSSProperty_background_color,          // color
   eCSSProperty_background_image,          // image
   eCSSProperty_background_repeat,         // repeat
-  eCSSProperty_background_position,       // position
+  eCSSProperty_background_position_x,     // positionX
+  eCSSProperty_background_position_y,     // positionY
   eCSSProperty_background_clip,           // clip
   eCSSProperty_background_origin,         // origin
   eCSSProperty_background_size,           // size
   eCSSProperty_background_attachment,     // attachment
   eCSSProperty_UNKNOWN,                   // maskMode
   eCSSProperty_UNKNOWN                    // composite
 };
 
 #ifdef MOZ_ENABLE_MASK_AS_SHORTHAND
 const nsCSSProperty nsStyleImageLayers::kMaskLayerTable[] = {
   eCSSProperty_mask,                      // shorthand
   eCSSProperty_UNKNOWN,                   // color
   eCSSProperty_mask_image,                // image
   eCSSProperty_mask_repeat,               // repeat
-  eCSSProperty_mask_position,             // position
+  eCSSProperty_mask_position_x,           // positionX
+  eCSSProperty_mask_position_y,           // positionY
   eCSSProperty_mask_clip,                 // clip
   eCSSProperty_mask_origin,               // origin
   eCSSProperty_mask_size,                 // size
   eCSSProperty_UNKNOWN,                   // attachment
   eCSSProperty_mask_mode,                 // maskMode
   eCSSProperty_mask_composite             // composite
 };
 #endif
 
 nsStyleImageLayers::nsStyleImageLayers()
   : mAttachmentCount(1)
   , mClipCount(1)
   , mOriginCount(1)
   , mRepeatCount(1)
-  , mPositionCount(1)
+  , mPositionXCount(1)
+  , mPositionYCount(1)
   , mImageCount(1)
   , mSizeCount(1)
   , mMaskModeCount(1)
   , mBlendModeCount(1)
   , mCompositeCount(1)
   , mLayers(nsStyleAutoArray<Layer>::WITH_SINGLE_INITIAL_ELEMENT)
 {
   MOZ_COUNT_CTOR(nsStyleImageLayers);
 }
 
 nsStyleImageLayers::nsStyleImageLayers(const nsStyleImageLayers &aSource)
   : mAttachmentCount(aSource.mAttachmentCount)
   , mClipCount(aSource.mClipCount)
   , mOriginCount(aSource.mOriginCount)
   , mRepeatCount(aSource.mRepeatCount)
-  , mPositionCount(aSource.mPositionCount)
+  , mPositionXCount(aSource.mPositionXCount)
+  , mPositionYCount(aSource.mPositionYCount)
   , mImageCount(aSource.mImageCount)
   , mSizeCount(aSource.mSizeCount)
   , mMaskModeCount(aSource.mMaskModeCount)
   , mBlendModeCount(aSource.mBlendModeCount)
   , mCompositeCount(aSource.mCompositeCount)
   , mLayers(aSource.mLayers) // deep copy
 {
   MOZ_COUNT_CTOR(nsStyleImageLayers);
   // If the deep copy of mLayers failed, truncate the counts.
   uint32_t count = mLayers.Length();
   if (count != aSource.mLayers.Length()) {
     NS_WARNING("truncating counts due to out-of-memory");
     mAttachmentCount = std::max(mAttachmentCount, count);
     mClipCount = std::max(mClipCount, count);
     mOriginCount = std::max(mOriginCount, count);
     mRepeatCount = std::max(mRepeatCount, count);
-    mPositionCount = std::max(mPositionCount, count);
+    mPositionXCount = std::max(mPositionXCount, count);
+    mPositionYCount = std::max(mPositionYCount, count);
     mImageCount = std::max(mImageCount, count);
     mSizeCount = std::max(mSizeCount, count);
     mMaskModeCount = std::max(mMaskModeCount, count);
     mBlendModeCount = std::max(mBlendModeCount, count);
     mCompositeCount = std::max(mCompositeCount, count);
   }
 }
 
@@ -2389,17 +2394,18 @@ nsStyleImageLayers::CalcDifference(const
 
   if (mAttachmentCount != aOther.mAttachmentCount ||
       mBlendModeCount != aOther.mBlendModeCount ||
       mClipCount != aOther.mClipCount ||
       mCompositeCount != aOther.mCompositeCount ||
       mMaskModeCount != aOther.mMaskModeCount ||
       mOriginCount != aOther.mOriginCount ||
       mRepeatCount != aOther.mRepeatCount ||
-      mPositionCount != aOther.mPositionCount ||
+      mPositionXCount != aOther.mPositionXCount ||
+      mPositionYCount != aOther.mPositionYCount ||
       mSizeCount != aOther.mSizeCount) {
     NS_UpdateHint(hint, nsChangeHint_NeutralChange);
   }
 
   return hint;
 }
 
 bool
--- a/layout/style/nsStyleStruct.h
+++ b/layout/style/nsStyleStruct.h
@@ -522,17 +522,18 @@ struct nsStyleImageLayers {
   }
 
   // Indices into kBackgroundLayerTable and kMaskLayerTable
   enum {
     shorthand = 0,
     color,
     image,
     repeat,
-    position,
+    positionX,
+    positionY,
     clip,
     origin,
     size,
     attachment,
     maskMode,
     composite
   };
 
@@ -732,17 +733,18 @@ struct nsStyleImageLayers {
   };
 
   // The (positive) number of computed values of each property, since
   // the lengths of the lists are independent.
   uint32_t mAttachmentCount,
            mClipCount,
            mOriginCount,
            mRepeatCount,
-           mPositionCount,
+           mPositionXCount,
+           mPositionYCount,
            mImageCount,
            mSizeCount,
            mMaskModeCount,
            mBlendModeCount,
            mCompositeCount;
 
   // Layers are stored in an array, matching the top-to-bottom order in
   // which they are specified in CSS.  The number of layers to be used
--- a/layout/style/test/test_shorthand_property_getters.html
+++ b/layout/style/test/test_shorthand_property_getters.html
@@ -153,16 +153,53 @@ e.setAttribute("style", "background-clip
 is(e.style.background, "", "should not have background shorthand (background-attachment too short)");
 e.setAttribute("style", "background-clip: border-box, padding-box, border-box; background-origin: border-box, padding-box, padding-box; background-size: cover, auto, contain; background-color: blue; background-image: url(404.png), none, url(404-2.png); background-attachment: fixed, scroll, scroll; background-position: top left, center, 30px 50px, bottom; background-repeat: repeat-x, repeat, no-repeat");
 is(e.style.background, "", "should not have background shorthand (background-position too long)");
 e.setAttribute("style", "background-clip: border-box, padding-box, border-box; background-origin: border-box, padding-box, padding-box; background-size: cover, auto, contain; background-color: blue; background-image: url(404.png), none, url(404-2.png); background-attachment: fixed, scroll, scroll; background-position: top left, center, 30px 50px; background-repeat: repeat-x, repeat");
 is(e.style.background, "", "should not have background shorthand (background-repeat too short)");
 e.setAttribute("style", "background-clip: border-box, padding-box, border-box; background-origin: border-box, padding-box, padding-box; background-size: cover, auto, contain, contain; background-color: blue; background-image: url(404.png), none, url(404-2.png); background-attachment: fixed, scroll, scroll; background-position: top left, center, 30px 50px; background-repeat: repeat-x, repeat, no-repeat");
 is(e.style.background, "", "should not have background shorthand (background-size too long)");
 
+// Check that we only serialize background-position when the lists (of layers) for
+// the -x/-y subproperties are the same length.
+e.setAttribute("style", "background-position-x: 10%, left 2em, right; background-position-y: top 2em, bottom, 10%");
+is(e.style.backgroundPosition, "left 10% top 2em, left 2em bottom, right 10%", "should have background-position shorthand (both lists length 3)");
+e.setAttribute("style", "background-position-x: 10%, left 2em; background-position-y: top 2em, bottom, 10%");
+is(e.style.backgroundPosition, "", "should not have background-position shorthand (background-position-x too short)");
+e.setAttribute("style", "background-position-x: 10%, left 2em, right; background-position-y: top 2em");
+is(e.style.backgroundPosition, "", "should not have background-position shorthand (background-position-y too short)");
+
+// Check that background-position serialization doesn't produce invalid values.
+e.setAttribute("style", "background-position: 0px");
+is(e.style.backgroundPosition, "0px center", "1-value form should be accepted, with implied center value for background-position-y");
+e.setAttribute("style", "background-position: 0px center");
+is(e.style.backgroundPosition, "0px center", "2-value form 'x-offset' 'y-edge' should be accepted, and serialize to 2-value form");
+e.setAttribute("style", "background-position: left 0px center");
+is(e.style.backgroundPosition, "left 0px center", "3-value form 'x-edge' 'x-offset' 'y-edge' should be accepted and serialize to 3-value form");
+e.setAttribute("style", "background-position: left top 0px");
+is(e.style.backgroundPosition, "left top 0px", "3-value form 'x-edge' 'y-edge' 'y-offset' should be accepted and serialize to 3-value form");
+e.setAttribute("style", "background-position: left 0px top 0px");
+is(e.style.backgroundPosition, "left 0px top 0px", "4-value form should be accepted and serialize to 4-value form");
+e.setAttribute("style", "background-position-x: 0px; background-position-y: center");
+is(e.style.backgroundPosition, "0px center", "should always serialize to 2-value form if setting -x and -y with the 1-value form");
+e.setAttribute("style", "background-position-x: 0px; background-position-y: 0px");
+is(e.style.backgroundPosition, "0px 0px", "should always serialize to 2-value form if setting -x and -y with the 1-value form");
+e.setAttribute("style", "background-position-x: center; background-position-y: 0px");
+is(e.style.backgroundPosition, "center 0px", "should always serialize to 2-value form if setting -x and -y with the 1-value form");
+e.setAttribute("style", "background-position-x: left; background-position-y: top");
+is(e.style.backgroundPosition, "left top", "should always serialize to 2-value form if setting -x and -y with the 1-value form");
+e.setAttribute("style", "background-position-x: left 0px; background-position-y: center");
+is(e.style.backgroundPosition, "left 0px center", "should always serialize to 3-value form if both -x and -y specified an edge");
+e.setAttribute("style", "background-position-x: right; background-position-y: top 0px");
+is(e.style.backgroundPosition, "right top 0px", "should always serialize to 3-value form if both -x and -y specified an edge");
+e.setAttribute("style", "background-position-x: left 0px; background-position-y: 0px");
+is(e.style.backgroundPosition, "left 0px top 0px", "should serialize to 4-value form if 3-value form would only have one edge");
+e.setAttribute("style", "background-position-x: 0px; background-position-y: top 0px");
+is(e.style.backgroundPosition, "left 0px top 0px", "should serialize to 4-value form if 3-value form would only have one edge");
+
 // Check that we only serialize transition when the lists are the same length.
 e.setAttribute("style", "transition-property: color, width; transition-duration: 1s, 200ms; transition-timing-function: ease-in, linear; transition-delay: 0s, 1s");
 isnot(e.style.transition, "", "should have transition shorthand (lists same length)");
 e.setAttribute("style", "transition-property: color, width, left; transition-duration: 1s, 200ms; transition-timing-function: ease-in, linear; transition-delay: 0s, 1s");
 is(e.style.transition, "", "should not have transition shorthand (lists different lengths)");
 e.setAttribute("style", "transition-property: all; transition-duration: 1s, 200ms; transition-timing-function: ease-in, linear; transition-delay: 0s, 1s");
 is(e.style.transition, "", "should not have transition shorthand (lists different lengths)");
 e.setAttribute("style", "transition-property: color, width; transition-duration: 1s, 200ms, 300ms; transition-timing-function: ease-in, linear; transition-delay: 0s, 1s");