Bug 1370034 - Add CSS properties -moz-window-transform and -moz-window-transform-origin, style system parts (excluding stylo). r?dbaron draft
authorMarkus Stange <mstange@themasta.com>
Mon, 19 Jun 2017 01:44:22 -0400
changeset 596345 a8b292491888ede3a5d75a5f19aa775b5697e8ca
parent 596313 9a1c79a11ad6ceeb30fb53c08accb3769e3a147e
child 596346 db46a233c5d9f40f957daed476795ed76aaa300f
push id64588
push userbmo:mstange@themasta.com
push dateMon, 19 Jun 2017 05:46:08 +0000
reviewersdbaron
bugs1370034
milestone56.0a1
Bug 1370034 - Add CSS properties -moz-window-transform and -moz-window-transform-origin, style system parts (excluding stylo). r?dbaron MozReview-Commit-ID: BlLMswF0bKO
devtools/shared/css/generated/properties-db.js
layout/inspector/inDOMUtils.cpp
layout/style/StyleAnimationValue.cpp
layout/style/generate-stylestructlist.py
layout/style/nsCSSParser.cpp
layout/style/nsCSSPropList.h
layout/style/nsComputedDOMStyle.cpp
layout/style/nsComputedDOMStyle.h
layout/style/nsComputedDOMStylePropertyList.h
layout/style/nsRuleNode.cpp
layout/style/nsStyleStruct.cpp
layout/style/nsStyleStruct.h
layout/style/test/ListCSSProperties.cpp
layout/style/test/property_database.js
--- a/devtools/shared/css/generated/properties-db.js
+++ b/devtools/shared/css/generated/properties-db.js
@@ -1510,16 +1510,48 @@ exports.CSS_PROPERTIES = {
       7
     ],
     "values": [
       "inherit",
       "initial",
       "unset"
     ]
   },
+  "-moz-window-transform": {
+    "isInherited": false,
+    "subproperties": [
+      "-moz-window-transform"
+    ],
+    "supports": [],
+    "values": [
+      "inherit",
+      "initial",
+      "unset"
+    ]
+  },
+  "-moz-window-transform-origin": {
+    "isInherited": false,
+    "subproperties": [
+      "-moz-window-transform-origin"
+    ],
+    "supports": [
+      6,
+      8
+    ],
+    "values": [
+      "bottom",
+      "center",
+      "inherit",
+      "initial",
+      "left",
+      "right",
+      "top",
+      "unset"
+    ]
+  },
   "-webkit-align-content": {
     "isInherited": false,
     "subproperties": [
       "align-content"
     ],
     "supports": [],
     "values": [
       "baseline",
@@ -3160,16 +3192,18 @@ exports.CSS_PROPERTIES = {
       "vertical-align",
       "visibility",
       "white-space",
       "width",
       "will-change",
       "-moz-window-dragging",
       "-moz-window-shadow",
       "-moz-window-opacity",
+      "-moz-window-transform",
+      "-moz-window-transform-origin",
       "word-break",
       "word-spacing",
       "overflow-wrap",
       "writing-mode",
       "z-index"
     ],
     "supports": [
       1,
--- a/layout/inspector/inDOMUtils.cpp
+++ b/layout/inspector/inDOMUtils.cpp
@@ -781,16 +781,17 @@ PropertySupportsVariant(nsCSSPropertyID 
       case eCSSProperty_scroll_snap_coordinate:
       case eCSSProperty_scroll_snap_destination:
       case eCSSProperty_transform_origin:
       case eCSSProperty_perspective_origin:
       case eCSSProperty__moz_outline_radius_topleft:
       case eCSSProperty__moz_outline_radius_topright:
       case eCSSProperty__moz_outline_radius_bottomleft:
       case eCSSProperty__moz_outline_radius_bottomright:
+      case eCSSProperty__moz_window_transform_origin:
         supported = VARIANT_LP;
         break;
 
       case eCSSProperty__moz_border_bottom_colors:
       case eCSSProperty__moz_border_left_colors:
       case eCSSProperty__moz_border_right_colors:
       case eCSSProperty__moz_border_top_colors:
         supported = VARIANT_COLOR;
--- a/layout/style/StyleAnimationValue.cpp
+++ b/layout/style/StyleAnimationValue.cpp
@@ -4312,16 +4312,31 @@ StyleAnimationValue::ExtractComputedValu
                                     pair->mYValue)) {
             return false;
           }
           aComputedValue.SetAndAdoptCSSValuePairValue(pair.forget(),
                                                       eUnit_CSSValuePair);
           break;
         }
 
+        case eCSSProperty__moz_window_transform_origin: {
+          const nsStyleUIReset *styleUIReset =
+            static_cast<const nsStyleUIReset*>(styleStruct);
+          nsAutoPtr<nsCSSValuePair> pair(new nsCSSValuePair);
+          if (!StyleCoordToCSSValue(styleUIReset->mWindowTransformOrigin[0],
+                                    pair->mXValue) ||
+              !StyleCoordToCSSValue(styleUIReset->mWindowTransformOrigin[1],
+                                    pair->mYValue)) {
+            return false;
+          }
+          aComputedValue.SetAndAdoptCSSValuePairValue(pair.forget(),
+                                                      eUnit_CSSValuePair);
+          break;
+        }
+
         case eCSSProperty_stroke_dasharray: {
           const nsStyleSVG *svg = static_cast<const nsStyleSVG*>(styleStruct);
           if (!svg->mStrokeDasharray.IsEmpty()) {
             nsAutoPtr<nsCSSValueList> result;
             nsCSSValueList **resultTail = getter_Transfers(result);
             for (uint32_t i = 0, i_end = svg->mStrokeDasharray.Length();
                  i != i_end; ++i) {
               nsCSSValueList *item = new nsCSSValueList;
@@ -4589,16 +4604,41 @@ StyleAnimationValue::ExtractComputedValu
             result->mValue.SetNoneValue();
           }
 
           aComputedValue.SetTransformValue(
               new nsCSSValueSharedList(result.forget()));
           break;
         }
 
+        case eCSSProperty__moz_window_transform: {
+          const nsStyleUIReset *uiReset =
+            static_cast<const nsStyleUIReset*>(styleStruct);
+          nsAutoPtr<nsCSSValueList> result;
+          if (uiReset->mSpecifiedWindowTransform) {
+            // Clone, and convert all lengths (not percents) to pixels.
+            nsCSSValueList **resultTail = getter_Transfers(result);
+            for (const nsCSSValueList *l = uiReset->mSpecifiedWindowTransform->mHead;
+                 l; l = l->mNext) {
+              nsCSSValueList *clone = new nsCSSValueList;
+              *resultTail = clone;
+              resultTail = &clone->mNext;
+
+              SubstitutePixelValues(aStyleContext, l->mValue, clone->mValue);
+            }
+          } else {
+            result = new nsCSSValueList();
+            result->mValue.SetNoneValue();
+          }
+
+          aComputedValue.SetTransformValue(
+              new nsCSSValueSharedList(result.forget()));
+          break;
+        }
+
         case eCSSProperty_font_variation_settings: {
           auto font = static_cast<const nsStyleFont*>(styleStruct);
           UniquePtr<nsCSSValuePairList> result;
           if (!font->mFont.fontVariationSettings.IsEmpty()) {
             // Make a new list that clones the current settings
             nsCSSValuePairList* tail = nullptr;
             for (auto v : font->mFont.fontVariationSettings) {
               auto clone = MakeUnique<nsCSSValuePairList>();
--- a/layout/style/generate-stylestructlist.py
+++ b/layout/style/generate-stylestructlist.py
@@ -34,17 +34,17 @@ STYLE_STRUCTS = [("INHERITED",) + x for 
     ("Variables",      "CheckVariablesCallback",[]),
 ]] + [("RESET",) + x for x in [
     # Reset style structs.
     ("Background",     "nullptr",   NORMAL_DEP + LENGTH_DEP + COLOR_DEP),
     ("Position",       "nullptr",   NORMAL_DEP + LENGTH_DEP),
     ("TextReset",      "nullptr",   NORMAL_DEP + LENGTH_DEP + COLOR_DEP),
     ("Display",        "nullptr",   NORMAL_DEP + LENGTH_DEP),
     ("Content",        "nullptr",   NORMAL_DEP + LENGTH_DEP),
-    ("UIReset",        "nullptr",   NORMAL_DEP),
+    ("UIReset",        "nullptr",   NORMAL_DEP + LENGTH_DEP),
     ("Table",          "nullptr",   NORMAL_DEP),
     ("Margin",         "nullptr",   NORMAL_DEP + LENGTH_DEP),
     ("Padding",        "nullptr",   NORMAL_DEP + LENGTH_DEP),
     ("Border",         "nullptr",   NORMAL_DEP + LENGTH_DEP + COLOR_DEP),
     ("Outline",        "nullptr",   NORMAL_DEP + LENGTH_DEP + COLOR_DEP),
     ("XUL",            "nullptr",   NORMAL_DEP),
     ("SVGReset",       "nullptr",   NORMAL_DEP + LENGTH_DEP + COLOR_DEP),
     ("Column",         "nullptr",   NORMAL_DEP + LENGTH_DEP + COLOR_DEP),
--- a/layout/style/nsCSSParser.cpp
+++ b/layout/style/nsCSSParser.cpp
@@ -1037,17 +1037,18 @@ protected:
   bool ParseFontVariationSettings(nsCSSValue& aValue);
   bool ParseFontSrc(nsCSSValue& aValue);
   bool ParseFontSrcFormat(InfallibleTArray<nsCSSValue>& values);
   bool ParseFontRanges(nsCSSValue& aValue);
   bool ParseListStyle();
   bool ParseListStyleType(nsCSSValue& aValue);
   bool ParseMargin();
   bool ParseClipPath(nsCSSValue& aValue);
-  bool ParseTransform(bool aIsPrefixed, bool aDisallowRelativeValues = false);
+  bool ParseTransform(bool aIsPrefixed, nsCSSPropertyID aProperty,
+                      bool aDisallowRelativeValues = false);
   bool ParseObjectPosition();
   bool ParseOutline();
   bool ParseOverflow();
   bool ParsePadding();
   bool ParseQuotes();
   bool ParseTextAlign(nsCSSValue& aValue,
                       const KTableEntry aTable[]);
   bool ParseTextAlign(nsCSSValue& aValue);
@@ -1380,17 +1381,17 @@ protected:
                      uint16_t aMaxElems, nsCSSValue &aValue);
   bool ParseFunctionInternals(const uint32_t aVariantMask[],
                               uint32_t aVariantMaskAll,
                               uint16_t aMinElems,
                               uint16_t aMaxElems,
                               InfallibleTArray<nsCSSValue>& aOutput);
 
   /* Functions for transform-origin/perspective-origin Parsing */
-  bool ParseTransformOrigin(bool aPerspective);
+  bool ParseTransformOrigin(nsCSSPropertyID aProperty);
 
   /* Functions for filter parsing */
   bool ParseFilter();
   bool ParseSingleFilter(nsCSSValue* aValue);
   bool ParseDropShadow(nsCSSValue* aValue);
 
   /* Find and return the namespace ID associated with aPrefix.
      If aPrefix has not been declared in an @namespace rule, returns
@@ -1941,17 +1942,18 @@ CSSParserImpl::ParseTransformProperty(co
 
   mData.AssertInitialState();
   mTempData.AssertInitialState();
 
   nsCSSScanner scanner(aPropValue, 0);
   css::ErrorReporter reporter(scanner, mSheet, mChildLoader, nullptr);
   InitScanner(scanner, reporter, nullptr, nullptr, nullptr);
 
-  bool parsedOK = ParseTransform(false, aDisallowRelativeValues);
+  bool parsedOK = ParseTransform(false, eCSSProperty_transform,
+                                 aDisallowRelativeValues);
   // We should now be at EOF
   if (parsedOK && GetToken(true)) {
     parsedOK = false;
   }
 
   bool changed = false;
   if (parsedOK) {
     declaration->ExpandTo(&mData);
@@ -11862,23 +11864,24 @@ CSSParserImpl::ParsePropertyByFunction(n
     return ParseQuotes();
   case eCSSProperty_text_decoration:
     return ParseTextDecoration();
   case eCSSProperty_text_emphasis:
     return ParseTextEmphasis();
   case eCSSProperty_will_change:
     return ParseWillChange();
   case eCSSProperty_transform:
-    return ParseTransform(false);
+  case eCSSProperty__moz_window_transform:
+    return ParseTransform(false, aPropID);
   case eCSSProperty__moz_transform:
-    return ParseTransform(true);
+    return ParseTransform(true, eCSSProperty_transform);
   case eCSSProperty_transform_origin:
-    return ParseTransformOrigin(false);
   case eCSSProperty_perspective_origin:
-    return ParseTransformOrigin(true);
+  case eCSSProperty__moz_window_transform_origin:
+    return ParseTransformOrigin(aPropID);
   case eCSSProperty_transition:
     return ParseTransition();
   case eCSSProperty_animation:
     return ParseAnimation();
   case eCSSProperty_transition_property:
     return ParseTransitionProperty();
   case eCSSProperty_fill:
   case eCSSProperty_stroke:
@@ -16227,19 +16230,25 @@ CSSParserImpl::ParseSingleTransform(bool
                                    variantMask))
     return false;
 
   return ParseFunction(keyword, variantMask, 0, minElems, maxElems, aValue);
 }
 
 /* Parses a transform property list by continuously reading in properties
  * and constructing a matrix from it.
+ * aProperty can be transform or -moz-window-transform.
+ * FIXME: For -moz-window-transform, it would be nice to reject non-2d
+ * transforms at parse time, because the implementation only supports 2d
+ * transforms. Instead, at the moment, non-2d transforms are treated as the
+ * identity transform very late in the pipeline.
  */
 bool
-CSSParserImpl::ParseTransform(bool aIsPrefixed, bool aDisallowRelativeValues)
+CSSParserImpl::ParseTransform(bool aIsPrefixed, nsCSSPropertyID aProperty,
+                              bool aDisallowRelativeValues)
 {
   nsCSSValue value;
   // 'inherit', 'initial', 'unset' and 'none' must be alone
   if (!ParseSingleTokenVariant(value, VARIANT_INHERIT | VARIANT_NONE,
                                nullptr)) {
     nsCSSValueSharedList* list = new nsCSSValueSharedList;
     value.SetSharedListValue(list);
     list->mHead = new nsCSSValueList;
@@ -16251,17 +16260,17 @@ CSSParserImpl::ParseTransform(bool aIsPr
       }
       if (CheckEndProperty()) {
         break;
       }
       cur->mNext = new nsCSSValueList;
       cur = cur->mNext;
     }
   }
-  AppendValue(eCSSProperty_transform, value);
+  AppendValue(aProperty, value);
   return true;
 }
 
 /* Reads a polygon function's argument list.
  */
 bool
 CSSParserImpl::ParsePolygonFunction(nsCSSValue& aValue)
 {
@@ -16526,53 +16535,48 @@ CSSParserImpl::ParseShapeOutside(nsCSSVa
     // 'inherit', 'initial', 'unset', 'none', and <image> url must be alone.
     return true;
   }
 
   return ParseReferenceBoxAndBasicShape(
     aValue, nsCSSProps::kShapeOutsideShapeBoxKTable);
 }
 
-bool CSSParserImpl::ParseTransformOrigin(bool aPerspective)
+bool CSSParserImpl::ParseTransformOrigin(nsCSSPropertyID aProperty)
 {
   nsCSSValuePair position;
   if (!ParseBoxPositionValues(position, true))
     return false;
 
-  nsCSSPropertyID prop = eCSSProperty_transform_origin;
-  if (aPerspective) {
-    prop = eCSSProperty_perspective_origin;
-  }
-
   // Unlike many other uses of pairs, this position should always be stored
   // as a pair, even if the values are the same, so it always serializes as
   // a pair, and to keep the computation code simple.
   if (position.mXValue.GetUnit() == eCSSUnit_Inherit ||
       position.mXValue.GetUnit() == eCSSUnit_Initial ||
       position.mXValue.GetUnit() == eCSSUnit_Unset) {
     MOZ_ASSERT(position.mXValue == position.mYValue,
                "inherit/initial/unset only half?");
-    AppendValue(prop, position.mXValue);
+    AppendValue(aProperty, position.mXValue);
   } else {
     nsCSSValue value;
-    if (aPerspective) {
+    if (aProperty != eCSSProperty_transform_origin) {
       value.SetPairValue(position.mXValue, position.mYValue);
     } else {
       nsCSSValue depth;
       CSSParseResult result =
         ParseVariant(depth, VARIANT_LENGTH | VARIANT_CALC, nullptr);
       if (result == CSSParseResult::Error) {
         return false;
       } else if (result == CSSParseResult::NotFound) {
         depth.SetFloatValue(0.0f, eCSSUnit_Pixel);
       }
       value.SetTripletValue(position.mXValue, position.mYValue, depth);
     }
 
-    AppendValue(prop, value);
+    AppendValue(aProperty, value);
   }
   return true;
 }
 
 /**
  * Reads a drop-shadow value. At the moment the Filter Effects specification
  * just expects one shadow item. Should this ever change to a list of shadow
  * items, use ParseShadowList instead.
--- a/layout/style/nsCSSPropList.h
+++ b/layout/style/nsCSSPropList.h
@@ -4499,16 +4499,41 @@ CSS_PROP_UIRESET(
     _moz_window_opacity,
     CSS_PROP_DOMPROP_PREFIXED(WindowOpacity),
     CSS_PROPERTY_INTERNAL | CSS_PROPERTY_PARSE_VALUE,
     "",
     VARIANT_HN,
     nullptr,
     offsetof(nsStyleUIReset, mWindowOpacity),
     eStyleAnimType_float)
+CSS_PROP_UIRESET(
+    -moz-window-transform,
+    _moz_window_transform,
+    CSS_PROP_DOMPROP_PREFIXED(WindowTransform),
+    CSS_PROPERTY_INTERNAL |
+        CSS_PROPERTY_PARSE_FUNCTION |
+        CSS_PROPERTY_GETCS_NEEDS_LAYOUT_FLUSH,
+    "",
+    0,
+    nullptr,
+    offsetof(nsStyleUIReset, mSpecifiedWindowTransform),
+    eStyleAnimType_Custom)
+CSS_PROP_UIRESET(
+    -moz-window-transform-origin,
+    _moz_window_transform_origin,
+    CSS_PROP_DOMPROP_PREFIXED(WindowTransformOrigin),
+    CSS_PROPERTY_INTERNAL |
+        CSS_PROPERTY_PARSE_FUNCTION |
+        CSS_PROPERTY_STORES_CALC |
+        CSS_PROPERTY_GETCS_NEEDS_LAYOUT_FLUSH,
+    "",
+    0,
+    kImageLayerPositionKTable,
+    CSS_PROP_NO_OFFSET,
+    eStyleAnimType_Custom)
 #endif // CSS_PROP_LIST_EXCLUDE_INTERNAL
 CSS_PROP_TEXT(
     word-break,
     word_break,
     WordBreak,
     CSS_PROPERTY_PARSE_VALUE,
     "",
     VARIANT_HK,
--- a/layout/style/nsComputedDOMStyle.cpp
+++ b/layout/style/nsComputedDOMStyle.cpp
@@ -1531,67 +1531,21 @@ nsComputedDOMStyle::DoGetTransformStyle(
 {
   RefPtr<nsROCSSPrimitiveValue> val = new nsROCSSPrimitiveValue;
   val->SetIdent(
       nsCSSProps::ValueToKeywordEnum(StyleDisplay()->mTransformStyle,
                                      nsCSSProps::kTransformStyleKTable));
   return val.forget();
 }
 
-/* If the property is "none", hand back "none" wrapped in a value.
- * Otherwise, compute the aggregate transform matrix and hands it back in a
- * "matrix" wrapper.
- */
 already_AddRefed<CSSValue>
 nsComputedDOMStyle::DoGetTransform()
 {
-  /* First, get the display data.  We'll need it. */
   const nsStyleDisplay* display = StyleDisplay();
-
-  /* If there are no transforms, then we should construct a single-element
-   * entry and hand it back.
-   */
-  if (!display->mSpecifiedTransform) {
-    RefPtr<nsROCSSPrimitiveValue> val = new nsROCSSPrimitiveValue;
-
-    /* Set it to "none." */
-    val->SetIdent(eCSSKeyword_none);
-    return val.forget();
-  }
-
-  /* Otherwise, we need to compute the current value of the transform matrix,
-   * store it in a string, and hand it back to the caller.
-   */
-
-  /* Use the inner frame for the reference box.  If we don't have an inner
-   * frame we use empty dimensions to allow us to continue (and percentage
-   * values in the transform will simply give broken results).
-   * TODO: There is no good way for us to represent the case where there's no
-   * frame, which is problematic.  The reason is that when we have percentage
-   * transforms, there are a total of four stored matrix entries that influence
-   * the transform based on the size of the element.  However, this poses a
-   * problem, because only two of these values can be explicitly referenced
-   * using the named transforms.  Until a real solution is found, we'll just
-   * use this approach.
-   */
-  nsStyleTransformMatrix::TransformReferenceBox refBox(mInnerFrame,
-                                                       nsSize(0, 0));
-
-   RuleNodeCacheConditions dummy;
-   bool dummyBool;
-   gfx::Matrix4x4 matrix =
-     nsStyleTransformMatrix::ReadTransforms(display->mSpecifiedTransform->mHead,
-                                            mStyleContext,
-                                            mStyleContext->PresContext(),
-                                            dummy,
-                                            refBox,
-                                            float(mozilla::AppUnitsPerCSSPixel()),
-                                            &dummyBool);
-
-  return MatrixToCSSValue(matrix);
+  return GetTransformValue(display->mSpecifiedTransform);
 }
 
 already_AddRefed<CSSValue>
 nsComputedDOMStyle::DoGetTransformBox()
 {
   RefPtr<nsROCSSPrimitiveValue> val = new nsROCSSPrimitiveValue;
   val->SetIdent(
       nsCSSProps::ValueToKeywordEnum(StyleDisplay()->mTransformBox,
@@ -4230,16 +4184,43 @@ already_AddRefed<CSSValue>
 nsComputedDOMStyle::DoGetWindowOpacity()
 {
   RefPtr<nsROCSSPrimitiveValue> val = new nsROCSSPrimitiveValue;
   val->SetNumber(StyleUIReset()->mWindowOpacity);
   return val.forget();
 }
 
 already_AddRefed<CSSValue>
+nsComputedDOMStyle::DoGetWindowTransform()
+{
+  const nsStyleUIReset* uiReset = StyleUIReset();
+  return GetTransformValue(uiReset->mSpecifiedWindowTransform);
+}
+
+already_AddRefed<CSSValue>
+nsComputedDOMStyle::DoGetWindowTransformOrigin()
+{
+  RefPtr<nsDOMCSSValueList> valueList = GetROCSSValueList(false);
+
+  const nsStyleUIReset* uiReset = StyleUIReset();
+
+  RefPtr<nsROCSSPrimitiveValue> originX = new nsROCSSPrimitiveValue;
+  SetValueToCoord(originX, uiReset->mWindowTransformOrigin[0], false,
+                  &nsComputedDOMStyle::GetFrameBoundsWidthForTransform);
+  valueList->AppendCSSValue(originX.forget());
+
+  RefPtr<nsROCSSPrimitiveValue> originY = new nsROCSSPrimitiveValue;
+  SetValueToCoord(originY, uiReset->mWindowTransformOrigin[1], false,
+                  &nsComputedDOMStyle::GetFrameBoundsHeightForTransform);
+  valueList->AppendCSSValue(originY.forget());
+
+  return valueList.forget();
+}
+
+already_AddRefed<CSSValue>
 nsComputedDOMStyle::DoGetWordBreak()
 {
   RefPtr<nsROCSSPrimitiveValue> val = new nsROCSSPrimitiveValue;
   val->SetIdent(
     nsCSSProps::ValueToKeywordEnum(StyleText()->mWordBreak,
                                    nsCSSProps::kWordBreakKTable));
   return val.forget();
 }
@@ -5866,16 +5847,66 @@ nsComputedDOMStyle::GetSVGPaintFor(bool 
       }
       break;
     }
   }
 
   return val.forget();
 }
 
+/* If the property is "none", hand back "none" wrapped in a value.
+ * Otherwise, compute the aggregate transform matrix and hands it back in a
+ * "matrix" wrapper.
+ */
+already_AddRefed<CSSValue>
+nsComputedDOMStyle::GetTransformValue(nsCSSValueSharedList* aSpecifiedTransform)
+{
+  /* If there are no transforms, then we should construct a single-element
+   * entry and hand it back.
+   */
+  if (!aSpecifiedTransform) {
+    RefPtr<nsROCSSPrimitiveValue> val = new nsROCSSPrimitiveValue;
+
+    /* Set it to "none." */
+    val->SetIdent(eCSSKeyword_none);
+    return val.forget();
+  }
+
+  /* Otherwise, we need to compute the current value of the transform matrix,
+   * store it in a string, and hand it back to the caller.
+   */
+
+  /* Use the inner frame for the reference box.  If we don't have an inner
+   * frame we use empty dimensions to allow us to continue (and percentage
+   * values in the transform will simply give broken results).
+   * TODO: There is no good way for us to represent the case where there's no
+   * frame, which is problematic.  The reason is that when we have percentage
+   * transforms, there are a total of four stored matrix entries that influence
+   * the transform based on the size of the element.  However, this poses a
+   * problem, because only two of these values can be explicitly referenced
+   * using the named transforms.  Until a real solution is found, we'll just
+   * use this approach.
+   */
+  nsStyleTransformMatrix::TransformReferenceBox refBox(mInnerFrame,
+                                                       nsSize(0, 0));
+
+   RuleNodeCacheConditions dummy;
+   bool dummyBool;
+   gfx::Matrix4x4 matrix =
+     nsStyleTransformMatrix::ReadTransforms(aSpecifiedTransform->mHead,
+                                            mStyleContext,
+                                            mStyleContext->PresContext(),
+                                            dummy,
+                                            refBox,
+                                            float(mozilla::AppUnitsPerCSSPixel()),
+                                            &dummyBool);
+
+  return MatrixToCSSValue(matrix);
+}
+
 already_AddRefed<CSSValue>
 nsComputedDOMStyle::DoGetFill()
 {
   return GetSVGPaintFor(true);
 }
 
 already_AddRefed<CSSValue>
 nsComputedDOMStyle::DoGetStroke()
--- a/layout/style/nsComputedDOMStyle.h
+++ b/layout/style/nsComputedDOMStyle.h
@@ -209,16 +209,18 @@ private:
   already_AddRefed<CSSValue> GetBorderColorFor(mozilla::Side aSide);
 
   already_AddRefed<CSSValue> GetMarginWidthFor(mozilla::Side aSide);
 
   already_AddRefed<CSSValue> GetFallbackValue(const nsStyleSVGPaint* aPaint);
 
   already_AddRefed<CSSValue> GetSVGPaintFor(bool aFill);
 
+  already_AddRefed<CSSValue> GetTransformValue(nsCSSValueSharedList* aSpecifiedTransform);
+
   // Appends all aLineNames (may be empty) space-separated to aResult.
   void AppendGridLineNames(nsString& aResult,
                            const nsTArray<nsString>& aLineNames);
   // Appends aLineNames as a CSSValue* to aValueList.  If aLineNames is empty
   // a value ("[]") is only appended if aSuppressEmptyList is false.
   void AppendGridLineNames(nsDOMCSSValueList* aValueList,
                            const nsTArray<nsString>& aLineNames,
                            bool aSuppressEmptyList = true);
@@ -524,16 +526,18 @@ private:
   already_AddRefed<CSSValue> DoGetForceBrokenImageIcon();
   already_AddRefed<CSSValue> DoGetIMEMode();
   already_AddRefed<CSSValue> DoGetUserFocus();
   already_AddRefed<CSSValue> DoGetUserInput();
   already_AddRefed<CSSValue> DoGetUserModify();
   already_AddRefed<CSSValue> DoGetUserSelect();
   already_AddRefed<CSSValue> DoGetWindowDragging();
   already_AddRefed<CSSValue> DoGetWindowOpacity();
+  already_AddRefed<CSSValue> DoGetWindowTransform();
+  already_AddRefed<CSSValue> DoGetWindowTransformOrigin();
 
   /* Column properties */
   already_AddRefed<CSSValue> DoGetColumnCount();
   already_AddRefed<CSSValue> DoGetColumnFill();
   already_AddRefed<CSSValue> DoGetColumnSpan();
   already_AddRefed<CSSValue> DoGetColumnWidth();
   already_AddRefed<CSSValue> DoGetColumnGap();
   already_AddRefed<CSSValue> DoGetColumnRuleWidth();
--- a/layout/style/nsComputedDOMStylePropertyList.h
+++ b/layout/style/nsComputedDOMStylePropertyList.h
@@ -301,16 +301,18 @@ COMPUTED_STYLE_PROP(_moz_tab_size,      
 COMPUTED_STYLE_PROP(_moz_text_size_adjust,         TextSizeAdjust)
 COMPUTED_STYLE_PROP(_moz_user_focus,               UserFocus)
 COMPUTED_STYLE_PROP(_moz_user_input,               UserInput)
 COMPUTED_STYLE_PROP(_moz_user_modify,              UserModify)
 COMPUTED_STYLE_PROP(_moz_user_select,              UserSelect)
 COMPUTED_STYLE_PROP(_moz_window_dragging,          WindowDragging)
 COMPUTED_STYLE_PROP(_moz_window_shadow,            WindowShadow)
 COMPUTED_STYLE_PROP(_moz_window_opacity,           WindowOpacity)
+COMPUTED_STYLE_PROP(_moz_window_transform,         WindowTransform)
+COMPUTED_STYLE_PROP(_moz_window_transform_origin,  WindowTransformOrigin)
 
 /* ********************************** *\
  * Implementations of -webkit- styles *
 \* ********************************** */
 
 COMPUTED_STYLE_PROP(_webkit_text_fill_color,       WebkitTextFillColor)
 //// COMPUTED_STYLE_PROP(webkit-text-stroke,       WebkitTextStroke)
 COMPUTED_STYLE_PROP(_webkit_text_stroke_color,     WebkitTextStrokeColor)
--- a/layout/style/nsRuleNode.cpp
+++ b/layout/style/nsRuleNode.cpp
@@ -1735,16 +1735,57 @@ SetFactor(const nsCSSValue& aValue, floa
 
   default:
     break;
   }
 
   NS_NOTREACHED("SetFactor: inappropriate unit");
 }
 
+static void
+SetTransformValue(const nsCSSValue& aValue,
+                  RefPtr<nsCSSValueSharedList>& aField,
+                  RuleNodeCacheConditions& aConditions,
+                  nsCSSValueSharedList* const aParentValue)
+{
+  /* Convert the nsCSSValueList into an nsTArray<nsTransformFunction *>. */
+  switch (aValue.GetUnit()) {
+  case eCSSUnit_Null:
+    break;
+
+  case eCSSUnit_Initial:
+  case eCSSUnit_Unset:
+  case eCSSUnit_None:
+    aField = nullptr;
+    break;
+
+  case eCSSUnit_Inherit:
+    aField = aParentValue;
+    aConditions.SetUncacheable();
+    break;
+
+  case eCSSUnit_SharedList: {
+    nsCSSValueSharedList* list = aValue.GetSharedListValue();
+    nsCSSValueList* head = list->mHead;
+    MOZ_ASSERT(head, "transform list must have at least one item");
+    // can get a _None in here from transform animation
+    if (head->mValue.GetUnit() == eCSSUnit_None) {
+      MOZ_ASSERT(head->mNext == nullptr, "none must be alone");
+      aField = nullptr;
+    } else {
+      aField = list;
+    }
+    break;
+  }
+
+  default:
+    MOZ_ASSERT(false, "unrecognized transform unit");
+  }
+}
+
 void*
 nsRuleNode::operator new(size_t sz, nsPresContext* aPresContext)
 {
   // Check the recycle list first.
   return aPresContext->PresShell()->AllocateByObjectID(eArenaObjectID_nsRuleNode, sz);
 }
 
 // Overridden to prevent the global delete from being called, since the memory
@@ -5276,16 +5317,38 @@ nsRuleNode::ComputeUIResetData(void* aSt
            NS_STYLE_WINDOW_SHADOW_DEFAULT);
 
   // -moz-window-opacity: factor, inherit, initial
   SetFactor(*aRuleData->ValueForWindowOpacity(),
             ui->mWindowOpacity, conditions,
             parentUI->mWindowOpacity, 1.0f,
             SETFCT_OPACITY | SETFCT_UNSET_INITIAL);
 
+  // -moz-window-transform
+  SetTransformValue(*aRuleData->ValueForWindowTransform(),
+                    ui->mSpecifiedWindowTransform, conditions,
+                    parentUI->mSpecifiedWindowTransform);
+
+  // -moz-window-transform-origin
+  const nsCSSValue* windowTransformOriginValue =
+    aRuleData->ValueForWindowTransformOrigin();
+  if (windowTransformOriginValue->GetUnit() != eCSSUnit_Null) {
+    mozilla::DebugOnly<bool> result =
+      SetPairCoords(*windowTransformOriginValue,
+                    ui->mWindowTransformOrigin[0],
+                    ui->mWindowTransformOrigin[1],
+                    parentUI->mWindowTransformOrigin[0],
+                    parentUI->mWindowTransformOrigin[1],
+                    SETCOORD_LPH | SETCOORD_INITIAL_HALF |
+                      SETCOORD_BOX_POSITION | SETCOORD_STORE_CALC |
+                      SETCOORD_UNSET_INITIAL,
+                    aContext, mPresContext, conditions);
+    NS_ASSERTION(result, "Malformed -moz-window-transform-origin parse!");
+  }
+
   COMPUTE_END_RESET(UIReset, ui)
 }
 
 // Information about each transition or animation property that is
 // constant.
 struct TransitionPropInfo {
   nsCSSPropertyID property;
   // Location of the count of the property's computed value.
@@ -6343,50 +6406,19 @@ nsRuleNode::ComputeDisplayData(void* aSt
       // It's okay to cache this change in the rule tree for the same
       // reasons as floats in the previous condition.
       if (display->mDisplay == StyleDisplay::Inline) {
         display->mDisplay = StyleDisplay::InlineBlock;
       }
     }
   }
 
-  /* Convert the nsCSSValueList into an nsTArray<nsTransformFunction *>. */
-  const nsCSSValue* transformValue = aRuleData->ValueForTransform();
-  switch (transformValue->GetUnit()) {
-  case eCSSUnit_Null:
-    break;
-
-  case eCSSUnit_Initial:
-  case eCSSUnit_Unset:
-  case eCSSUnit_None:
-    display->mSpecifiedTransform = nullptr;
-    break;
-
-  case eCSSUnit_Inherit:
-    display->mSpecifiedTransform = parentDisplay->mSpecifiedTransform;
-    conditions.SetUncacheable();
-    break;
-
-  case eCSSUnit_SharedList: {
-    nsCSSValueSharedList* list = transformValue->GetSharedListValue();
-    nsCSSValueList* head = list->mHead;
-    MOZ_ASSERT(head, "transform list must have at least one item");
-    // can get a _None in here from transform animation
-    if (head->mValue.GetUnit() == eCSSUnit_None) {
-      MOZ_ASSERT(head->mNext == nullptr, "none must be alone");
-      display->mSpecifiedTransform = nullptr;
-    } else {
-      display->mSpecifiedTransform = list;
-    }
-    break;
-  }
-
-  default:
-    MOZ_ASSERT(false, "unrecognized transform unit");
-  }
+  SetTransformValue(*aRuleData->ValueForTransform(),
+                    display->mSpecifiedTransform, conditions,
+                    parentDisplay->mSpecifiedTransform);
 
   /* Convert the nsCSSValueList into a will-change bitfield for fast lookup */
   const nsCSSValue* willChangeValue = aRuleData->ValueForWillChange();
   switch (willChangeValue->GetUnit()) {
   case eCSSUnit_Null:
     break;
 
   case eCSSUnit_List:
--- a/layout/style/nsStyleStruct.cpp
+++ b/layout/style/nsStyleStruct.cpp
@@ -4322,34 +4322,51 @@ nsStyleUserInterface::CalcDifference(con
 
 nsStyleUIReset::nsStyleUIReset(const nsPresContext* aContext)
   : mUserSelect(StyleUserSelect::Auto)
   , mForceBrokenImageIcon(0)
   , mIMEMode(NS_STYLE_IME_MODE_AUTO)
   , mWindowDragging(StyleWindowDragging::Default)
   , mWindowShadow(NS_STYLE_WINDOW_SHADOW_DEFAULT)
   , mWindowOpacity(1.0)
+  , mSpecifiedWindowTransform(nullptr)
+  , mWindowTransformOrigin{ {0.5f, eStyleUnit_Percent}, // Transform is centered on origin
+                            {0.5f, eStyleUnit_Percent} }
 {
   MOZ_COUNT_CTOR(nsStyleUIReset);
 }
 
 nsStyleUIReset::nsStyleUIReset(const nsStyleUIReset& aSource)
   : mUserSelect(aSource.mUserSelect)
   , mForceBrokenImageIcon(aSource.mForceBrokenImageIcon)
   , mIMEMode(aSource.mIMEMode)
   , mWindowDragging(aSource.mWindowDragging)
   , mWindowShadow(aSource.mWindowShadow)
   , mWindowOpacity(aSource.mWindowOpacity)
+  , mSpecifiedWindowTransform(aSource.mSpecifiedWindowTransform)
+  , mWindowTransformOrigin{ aSource.mWindowTransformOrigin[0],
+                            aSource.mWindowTransformOrigin[1] }
 {
   MOZ_COUNT_CTOR(nsStyleUIReset);
 }
 
 nsStyleUIReset::~nsStyleUIReset()
 {
   MOZ_COUNT_DTOR(nsStyleUIReset);
+
+  // See the nsStyleDisplay destructor for why we're doing this.
+  if (mSpecifiedWindowTransform && ServoStyleSet::IsInServoTraversal()) {
+    bool alwaysProxy =
+#ifdef DEBUG
+      true;
+#else
+      false;
+#endif
+    NS_ReleaseOnMainThread(mSpecifiedWindowTransform.forget(), alwaysProxy);
+  }
 }
 
 nsChangeHint
 nsStyleUIReset::CalcDifference(const nsStyleUIReset& aNewData) const
 {
   nsChangeHint hint = nsChangeHint(0);
 
   if (mForceBrokenImageIcon != aNewData.mForceBrokenImageIcon) {
@@ -4364,18 +4381,29 @@ nsStyleUIReset::CalcDifference(const nsS
   if (mUserSelect != aNewData.mUserSelect) {
     hint |= NS_STYLE_HINT_VISUAL;
   }
 
   if (mWindowDragging != aNewData.mWindowDragging) {
     hint |= nsChangeHint_SchedulePaint;
   }
 
-  if (mWindowOpacity != aNewData.mWindowOpacity) {
+  if (mWindowOpacity != aNewData.mWindowOpacity ||
+      !mSpecifiedWindowTransform != !aNewData.mSpecifiedWindowTransform ||
+      (mSpecifiedWindowTransform &&
+       *mSpecifiedWindowTransform != *aNewData.mSpecifiedWindowTransform)) {
     hint |= nsChangeHint_UpdateWidgetProperties;
+  } else {
+    for (uint8_t index = 0; index < 2; ++index) {
+      if (mWindowTransformOrigin[index] !=
+            aNewData.mWindowTransformOrigin[index]) {
+        hint |= nsChangeHint_UpdateWidgetProperties;
+        break;
+      }
+    }
   }
 
   if (!hint &&
       mIMEMode != aNewData.mIMEMode) {
     hint |= nsChangeHint_NeutralChange;
   }
 
   return hint;
--- a/layout/style/nsStyleStruct.h
+++ b/layout/style/nsStyleStruct.h
@@ -3224,16 +3224,18 @@ struct MOZ_NEEDS_MEMMOVABLE_MEMBERS nsSt
   nsChangeHint CalcDifference(const nsStyleUIReset& aNewData) const;
 
   mozilla::StyleUserSelect     mUserSelect;     // [reset](selection-style)
   uint8_t mForceBrokenImageIcon; // [reset] (0 if not forcing, otherwise forcing)
   uint8_t                      mIMEMode;        // [reset]
   mozilla::StyleWindowDragging mWindowDragging; // [reset]
   uint8_t                      mWindowShadow;   // [reset]
   float                        mWindowOpacity;  // [reset]
+  RefPtr<nsCSSValueSharedList> mSpecifiedWindowTransform; // [reset]
+  nsStyleCoord                 mWindowTransformOrigin[2]; // [reset] percent, coord, calc
 };
 
 struct nsCursorImage
 {
   bool mHaveHotspot;
   float mHotspotX, mHotspotY;
   RefPtr<nsStyleImageRequest> mImage;
 
--- a/layout/style/test/ListCSSProperties.cpp
+++ b/layout/style/test/ListCSSProperties.cpp
@@ -111,16 +111,18 @@ const char *gInaccessibleProperties[] = 
     "-moz-script-level", // parsed by UA sheets only
     "-moz-script-size-multiplier",
     "-moz-script-min-size",
     "-moz-math-variant",
     "-moz-math-display", // parsed by UA sheets only
     "-moz-top-layer", // parsed by UA sheets only
     "-moz-min-font-size-ratio", // parsed by UA sheets only
     "-moz-window-opacity", // chrome-only internal properties
+    "-moz-window-transform", // chrome-only internal properties
+    "-moz-window-transform-origin", // chrome-only internal properties
     "-moz-window-shadow" // chrome-only internal properties
 };
 
 inline int
 is_inaccessible(const char* aPropName)
 {
     for (unsigned j = 0; j < MOZ_ARRAY_LENGTH(gInaccessibleProperties); ++j) {
         if (strcmp(aPropName, gInaccessibleProperties[j]) == 0)
--- a/layout/style/test/property_database.js
+++ b/layout/style/test/property_database.js
@@ -7909,16 +7909,112 @@ if (false) {
     // domProp: "MozWindowOpacity",
     inherited: false,
     type: CSS_TYPE_LONGHAND,
     initial_values: [ "1", "17", "397.376", "3e1", "3e+1", "3e0", "3e+0", "3e-0" ],
     other_values: [ "0", "0.4", "0.0000", "-3", "3e-1" ],
     invalid_values: [ "0px", "1px", "20%", "default", "auto" ]
   };
 
+  gCSSProperties["-moz-window-transform"] = {
+    // domProp: "MozWindowTransform",
+    inherited: false,
+    type: CSS_TYPE_LONGHAND,
+    prerequisites: { "width": "300px", "height": "50px" },
+    initial_values: [ "none" ],
+    other_values: [ "translatex(1px)", "translatex(4em)",
+      "translatex(-4px)", "translatex(3px)",
+      "translatex(0px) translatex(1px) translatex(2px) translatex(3px) translatex(4px)",
+      "translatey(4em)", "translate(3px)", "translate(10px, -3px)",
+      "rotate(45deg)", "rotate(45grad)", "rotate(45rad)",
+      "rotate(0.25turn)", "rotate(0)", "scalex(10)", "scaley(10)",
+      "scale(10)", "scale(10, 20)", "skewx(30deg)", "skewx(0)",
+      "skewy(0)", "skewx(30grad)", "skewx(30rad)", "skewx(0.08turn)",
+      "skewy(30deg)", "skewy(30grad)", "skewy(30rad)", "skewy(0.08turn)",
+      "rotate(45deg) scale(2, 1)", "skewx(45deg) skewx(-50grad)",
+      "translate(0, 0) scale(1, 1) skewx(0) skewy(0) matrix(1, 0, 0, 1, 0, 0)",
+      "translatex(50%)", "translatey(50%)", "translate(50%)",
+      "translate(3%, 5px)", "translate(5px, 3%)",
+      "matrix(1, 2, 3, 4, 5, 6)",
+      /* valid calc() values */
+      "translatex(calc(5px + 10%))",
+      "translatey(calc(0.25 * 5px + 10% / 3))",
+      "translate(calc(5px - 10% * 3))",
+      "translate(calc(5px - 3 * 10%), 50px)",
+      "translate(-50px, calc(5px - 10% * 3))",
+      "translatez(1px)", "translatez(4em)", "translatez(-4px)",
+      "translatez(0px)", "translatez(2px) translatez(5px)",
+      "translate3d(3px, 4px, 5px)", "translate3d(2em, 3px, 1em)",
+      "translatex(2px) translate3d(4px, 5px, 6px) translatey(1px)",
+      "scale3d(4, 4, 4)", "scale3d(-2, 3, -7)", "scalez(4)",
+      "scalez(-6)", "rotate3d(2, 3, 4, 45deg)",
+      "rotate3d(-3, 7, 0, 12rad)", "rotatex(15deg)", "rotatey(-12grad)",
+      "rotatez(72rad)", "rotatex(0.125turn)",
+      "perspective(0px)", "perspective(1000px)",
+      "matrix3d(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16)",
+    ],
+    invalid_values: ["1px", "#0000ff", "red", "auto",
+      "translatex(1)", "translatey(1)", "translate(2)",
+      "translate(-3, -4)",
+      "translatex(1px 1px)", "translatex(translatex(1px))",
+      "translatex(#0000ff)", "translatex(red)", "translatey()",
+      "matrix(1px, 2px, 3px, 4px, 5px, 6px)", "scale(150%)",
+      "skewx(red)", "matrix(1%, 0, 0, 0, 0px, 0px)",
+      "matrix(0, 1%, 2, 3, 4px,5px)", "matrix(0, 1, 2%, 3, 4px, 5px)",
+      "matrix(0, 1, 2, 3%, 4%, 5%)", "matrix(1, 2, 3, 4, 5px, 6%)",
+      "matrix(1, 2, 3, 4, 5%, 6px)", "matrix(1, 2, 3, 4, 5%, 6%)",
+      "matrix(1, 2, 3, 4, 5px, 6em)",
+      /* invalid calc() values */
+      "translatey(-moz-min(5px,10%))",
+      "translatex(-moz-max(5px,10%))",
+      "translate(10px, calc(min(5px,10%)))",
+      "translate(calc(max(5px,10%)), 10%)",
+      "matrix(1, 0, 0, 1, max(5px * 3), calc(10% - 3px))",
+      "perspective(-10px)", "matrix3d(dinosaur)",
+      "matrix3d(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17)",
+      "matrix3d(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15)",
+      "matrix3d(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15%, 16)",
+      "matrix3d(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16px)",
+      "rotatey(words)", "rotatex(7)", "translate3d(3px, 4px, 1px, 7px)",
+      "matrix3d(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13px, 14em, 15px, 16)",
+      "matrix3d(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 20%, 10%, 15, 16)"
+    ],
+  };
+
+  gCSSProperties["-moz-window-transform-origin"] = {
+    // domProp: "MozWindowTransformOrigin",
+    inherited: false,
+    type: CSS_TYPE_LONGHAND,
+    /* no subproperties */
+    prerequisites: { "width": "10px", "height": "10px", "display": "block"},
+    initial_values: [ "50% 50%", "center", "center center" ],
+    other_values: [ "25% 25%", "6px 5px", "20% 3em", "0 0", "0in 1in",
+            "top", "bottom","top left", "top right",
+            "top center", "center left", "center right",
+            "bottom left", "bottom right", "bottom center",
+            "20% center", "6px center", "13in bottom",
+            "left 50px", "right 13%", "center 40px",
+      "calc(20px)",
+      "calc(20px) 10px",
+      "10px calc(20px)",
+      "calc(20px) 25%",
+      "25% calc(20px)",
+      "calc(20px) calc(20px)",
+      "calc(20px + 1em) calc(20px / 2)",
+      "calc(20px + 50%) calc(50% - 10px)",
+      "calc(-20px) calc(-50%)",
+      "calc(-20%) calc(-50%)"
+    ],
+    invalid_values: ["red", "auto", "none", "0.5 0.5", "40px #0000ff",
+             "border", "center red", "right diagonal",
+             "#00ffff bottom", "0px calc(0px + rubbish)",
+             "0px 0px calc(0px + rubbish)", "6px 5px 5px",
+             "top center 10px"]
+  };
+
   gCSSProperties["-moz-context-properties"] = {
     //domProp: "MozContextProperties",
     inherited: true,
     type: CSS_TYPE_LONGHAND,
     initial_values: [ "none" ],
     other_values: [ "fill", "stroke", "fill, stroke", "fill, stroke, fill", "fill, foo", "foo" ],
     invalid_values: [ "default", "fill, auto", "all, stroke", "none, fill", "fill, none", "fill, default", "2px" ]
   };