Bug 1273706 - Part 25: Implement CSS.registerProperty and CSS.unregisterProperty. r?dbaron draft
authorJonathan Chan <jyc@eqv.io>
Thu, 18 Aug 2016 15:30:38 -0700
changeset 402914 af03727192c0a8703e2d6833f962cb07b0750fd3
parent 402913 7004b9c2decdac3c331351f6c764f63bcf17182d
child 402915 57f2da27a4b95fc6f2b59505da543022ce5e8af3
push id26775
push userjchan@mozilla.com
push dateThu, 18 Aug 2016 22:38:41 +0000
reviewersdbaron
bugs1273706
milestone51.0a1
Bug 1273706 - Part 25: Implement CSS.registerProperty and CSS.unregisterProperty. r?dbaron Note that although this patch series modifies a lot of code related to CSS variables in order to support custom properties, behavior if a variable is unregistered is exactly the same as before. This is why merely hiding (un)?registerProperty behind a pref is sufficient to disable the Properties & Values API functionality (hopefully!). MozReview-Commit-ID: 9gGfhFsOKjA
layout/style/CSS.cpp
--- a/layout/style/CSS.cpp
+++ b/layout/style/CSS.cpp
@@ -11,55 +11,90 @@
 #include "mozilla/ServoBindings.h"
 #include "nsCSSParser.h"
 #include "nsGlobalWindow.h"
 #include "nsIDocument.h"
 #include "nsIURI.h"
 #include "nsStyleUtil.h"
 #include "xpcpublic.h"
 
+// For custom properties support.
+#include "mozilla/DebugOnly.h"
+#include "mozilla/dom/PropertyDescriptorDictBinding.h"
+#include "mozilla/CSSVariableRegistration.h"
+#include "mozilla/CSSVariableSyntax.h"
+#include "nsCSSParser.h"
+#include "nsCSSProps.h"
+#include "nsCSSScanner.h"
+
 namespace mozilla {
 namespace dom {
 
-struct SupportsParsingInfo
+struct ParsingInfo
 {
+  CSSVariableRegistrations* mRegistrations;
   nsIURI* mDocURI;
   nsIURI* mBaseURI;
   nsIPrincipal* mPrincipal;
   StyleBackendType mStyleBackendType;
 };
 
 static nsresult
 GetParsingInfo(const GlobalObject& aGlobal,
-               SupportsParsingInfo& aInfo)
+               ParsingInfo& aInfo)
 {
   nsGlobalWindow* win = xpc::WindowOrNull(aGlobal.Get());
   if (!win) {
     return NS_ERROR_FAILURE;
   }
 
   nsCOMPtr<nsIDocument> doc = win->GetDoc();
   if (!doc) {
     return NS_ERROR_FAILURE;
   }
 
+  aInfo.mRegistrations = doc->GetCSSVariableRegistrations();
   aInfo.mDocURI = nsCOMPtr<nsIURI>(doc->GetDocumentURI()).get();
   aInfo.mBaseURI = nsCOMPtr<nsIURI>(doc->GetBaseURI()).get();
   aInfo.mPrincipal = win->GetPrincipal();
   aInfo.mStyleBackendType = doc->GetStyleBackendType();
   return NS_OK;
 }
 
+static nsresult
+RebuildAllStyleData(const GlobalObject& aGlobal)
+{
+  // The inner window (the script compilation context)
+  nsGlobalWindow* win = xpc::WindowOrNull(aGlobal.Get());
+  if (!win) {
+    return NS_ERROR_FAILURE;
+  }
+
+  nsCOMPtr<nsIDocument> doc = win->GetDoc();
+  if (!doc) {
+    return NS_ERROR_FAILURE;
+  }
+
+  nsIPresShell* shell;
+  nsPresContext* pcx;
+  if ((shell = doc->GetShell()) && (pcx = shell->GetPresContext())) {
+    pcx->RebuildAllStyleData(NS_STYLE_HINT_REFLOW, eRestyle_Subtree);
+    return NS_OK;
+  }
+
+  return NS_ERROR_FAILURE;
+}
+
 /* static */ bool
 CSS::Supports(const GlobalObject& aGlobal,
               const nsAString& aProperty,
               const nsAString& aValue,
-              ErrorResult& aRv)
+              mozilla::ErrorResult& aRv)
 {
-  SupportsParsingInfo info;
+  ParsingInfo info;
 
   nsresult rv = GetParsingInfo(aGlobal, info);
   if (NS_FAILED(rv)) {
     aRv.Throw(rv);
     return false;
   }
 
   if (info.mStyleBackendType == StyleBackendType::Servo) {
@@ -68,60 +103,255 @@ CSS::Supports(const GlobalObject& aGloba
 
     return Servo_CSSSupports(reinterpret_cast<const uint8_t*>(property.get()),
                              property.Length(),
                              reinterpret_cast<const uint8_t*>(value.get()),
                              value.Length());
   }
 
   nsCSSParser parser;
+  parser.SetVariableRegistrations(info.mRegistrations);
   return parser.EvaluateSupportsDeclaration(aProperty, aValue, info.mDocURI,
                                             info.mBaseURI, info.mPrincipal);
 }
 
 /* static */ bool
 CSS::Supports(const GlobalObject& aGlobal,
               const nsAString& aCondition,
-              ErrorResult& aRv)
+              mozilla::ErrorResult& aRv)
 {
-  SupportsParsingInfo info;
+  ParsingInfo info;
 
   nsresult rv = GetParsingInfo(aGlobal, info);
   if (NS_FAILED(rv)) {
     aRv.Throw(rv);
     return false;
   }
 
   if (info.mStyleBackendType == StyleBackendType::Servo) {
     MOZ_CRASH("stylo: CSS.supports() with arguments is not yet implemented");
   }
 
   nsCSSParser parser;
+  parser.SetVariableRegistrations(info.mRegistrations);
   return parser.EvaluateSupportsCondition(aCondition, info.mDocURI,
                                           info.mBaseURI, info.mPrincipal);
 }
 
 /* static */ void
 CSS::Escape(const GlobalObject& aGlobal,
             const nsAString& aIdent,
             nsAString& aReturn)
 {
   nsStyleUtil::AppendEscapedCSSIdent(aIdent, aReturn);
 }
 
+static bool
+IsIdempotentValue(const nsCSSValue& aValue)
+{
+  if (aValue.IsRelativeLengthUnit()) {
+    // Initial values need to be computationally idempotent.
+    // NB: Variables are not computationally idempotent!
+    // That's caught by ParseTypedValue, which will parse them as a var
+    // function (which doesn't exist). Variables would have been resolved by
+    // ResolveVariableValue (see CSSVariableResolver).
+    return false;
+  }
+
+  if (aValue.GetUnit() >= eCSSUnit_Null &&
+      aValue.GetUnit() <= eCSSUnit_All) {
+    // Same reason. Can't have 'inherit', 'unset', 'revert', etc. -- not
+    // computationally idempotent.
+    return false;
+  }
+
+  if (aValue.GetUnit() == eCSSUnit_Calc) {
+    const nsCSSValue::Array* arr = aValue.GetArrayValue();
+    MOZ_ASSERT(arr->Count() == 1, "unexpected length");
+    return IsIdempotentValue(arr->Item(0));
+  }
+
+  if (aValue.GetUnit() >= eCSSUnit_Calc_Plus &&
+      aValue.GetUnit() <= eCSSUnit_Calc_Divided) {
+    const nsCSSValue::Array* arr = aValue.GetArrayValue();
+    MOZ_ASSERT(arr->Count() == 2, "unexpected length");
+    return IsIdempotentValue(arr->Item(0)) &&
+           IsIdempotentValue(arr->Item(1));
+  }
+
+  if (aValue.GetUnit() == eCSSUnit_Function) {
+    const nsCSSValue::Array* func = aValue.GetArrayValue();
+    for (size_t argID = 0; argID < func->Count() - 1; argID++) {
+      const nsCSSValue& arg = func->Item(1 + argID);
+      if (!IsIdempotentValue(arg)) {
+        return false;
+      }
+    }
+    return true;
+  }
+
+  if (aValue.GetUnit() == eCSSUnit_List) {
+    const nsCSSValueList* list = aValue.GetListValue();
+    while (list) {
+      if (!IsIdempotentValue(list->mValue)) {
+        return false;
+      }
+      list = list->mNext;
+    }
+  }
+
+  return true;
+}
+
 /* static */ void
 CSS::RegisterProperty(const GlobalObject& aGlobal,
                       const mozilla::dom::PropertyDescriptorDict& aDescriptor,
                       mozilla::ErrorResult& aRv)
 {
-  // STUB: populated by a later patch in this series
+  ParsingInfo info;
+  nsresult rv = GetParsingInfo(aGlobal, info);
+  if (NS_FAILED(rv)) {
+    aRv.Throw(rv);
+    return;
+  }
+
+  if (!nsCSSProps::IsCustomPropertyName(aDescriptor.mName)) {
+    aRv.ThrowDOMException(NS_ERROR_DOM_SYNTAX_ERR);
+    return;
+  }
+
+  // First two characters are guaranteed to be -- by the previous guard.
+  const nsDependentSubstring name =
+    Substring(aDescriptor.mName, CSS_CUSTOM_NAME_PREFIX_LENGTH);
+
+  // Verify that the part after -- is a valid ident, as required.
+  nsCSSScanner scanner(name, /* aLineNumber = */ 0);
+  nsCSSToken token;
+  if (!scanner.Next(token, eCSSScannerExclude_None) ||
+      token.mType != eCSSToken_Ident) {
+    aRv.ThrowDOMException(NS_ERROR_DOM_SYNTAX_ERR);
+    return;
+  }
+
+  if (info.mRegistrations->mData.Contains(name)) {
+    aRv.ThrowDOMException(NS_ERROR_DOM_INVALID_MODIFICATION_ERR);
+    return;
+  }
+
+  nsAutoPtr<CSSVariableRegistration> registration(new CSSVariableRegistration);
+  registration->mInitialContext =
+    CSSVariableExprContext(info.mDocURI, info.mBaseURI, info.mPrincipal);
+
+  if (aDescriptor.mSyntax.WasPassed()) {
+    if (!registration->mSyntax.SetSyntax(aDescriptor.mSyntax.Value())) {
+      aRv.ThrowDOMException(NS_ERROR_DOM_SYNTAX_ERR);
+      return;
+    }
+  } else {
+    DebugOnly<bool> ok = registration->mSyntax.SetSyntax(NS_LITERAL_STRING("*"));
+    MOZ_ASSERT(ok);
+  }
+
+  registration->mInherited = (aDescriptor.mInherits.WasPassed() &&
+                              aDescriptor.mInherits.Value()) ||
+                             false;
+  registration->mAtom = NS_Atomize(name);
+
+  // If no initialValue is provided and the syntax is *, then a special initial
+  // value used [sic]. This initial value must be considered parseable by
+  // registerProperty() but invalid at computed value time.
+  if (aDescriptor.mInitialValue.WasPassed()) {
+    nsCSSParser parser;
+
+    // The initial value, if specified, should type properly.
+    nsCSSValue value;
+    CSSValueType type;
+    if (!parser.ParseTypedValue(registration->mSyntax,
+                                aDescriptor.mInitialValue.Value(),
+                                info.mDocURI, info.mBaseURI, info.mPrincipal,
+                                value, type)) {
+      aRv.ThrowDOMException(NS_ERROR_DOM_SYNTAX_ERR);
+      return;
+    }
+
+    if (!IsIdempotentValue(value)) {
+      aRv.ThrowDOMException(NS_ERROR_DOM_SYNTAX_ERR);
+      return;
+    }
+
+    registration->mInitialExpr = aDescriptor.mInitialValue.Value();
+    registration->mInitialValue = value;
+    registration->mInitialType = type;
+
+    nsAutoString _result;
+    MOZ_ASSERT(parser.ResolveVariableValue(aDescriptor.mInitialValue.Value(),
+                                           // There should be no variables!
+                                           // Caught by ParseTypedValue.
+                                           nullptr, _result,
+                                           registration->mInitialFirstToken,
+                                           registration->mInitialLastToken));
+  } else {
+    // The special 'empty string' initial expression is not valid for variable
+    // declarations (see CSS Variables, need at least one character of
+    // whitespace) so we check for this in CSSVariableResolver.
+    registration->mInitialExpr = EmptyString();
+    registration->mInitialFirstToken = eCSSTokenSerialization_Nothing;
+    registration->mInitialLastToken = eCSSTokenSerialization_Nothing;
+  }
+
+  info.mRegistrations->mGeneration++;
+  info.mRegistrations->mData.Put(name, registration.forget());
+
+  rv = RebuildAllStyleData(aGlobal);
+  if (NS_FAILED(rv)) {
+    aRv.Throw(rv);
+  }
+
+  return;
 }
 
 /* static */ void
 CSS::UnregisterProperty(const GlobalObject& aGlobal,
                         const nsAString& aName,
                         mozilla::ErrorResult& aRv)
 {
-  // STUB: populated by a later patch in this series
+  ParsingInfo info;
+  nsresult rv = GetParsingInfo(aGlobal, info);
+  if (NS_FAILED(rv)) {
+    aRv.Throw(rv);
+    return;
+  }
+
+  if (!nsCSSProps::IsCustomPropertyName(aName)) {
+    aRv.ThrowDOMException(NS_ERROR_DOM_SYNTAX_ERR);
+    return;
+  }
+
+  // First two characters are guaranteed to be -- by the previous guard.
+  const nsDependentSubstring name = Substring(aName, CSS_CUSTOM_NAME_PREFIX_LENGTH);
+
+  // Verify that the part after -- is a valid ident, as required.
+  nsCSSScanner scanner(name, /* aLineNumber = */ 0);
+  nsCSSToken token;
+  if (!scanner.Next(token, eCSSScannerExclude_None) ||
+      token.mType != eCSSToken_Ident) {
+    aRv.ThrowDOMException(NS_ERROR_DOM_SYNTAX_ERR);
+    return;
+  }
+
+  if (!info.mRegistrations->mData.Contains(name)) {
+    aRv.ThrowDOMException(NS_ERROR_DOM_NOT_FOUND_ERR);
+    return;
+  }
+
+  info.mRegistrations->mGeneration++;
+  info.mRegistrations->mData.Remove(name);
+
+  rv = RebuildAllStyleData(aGlobal);
+  if (NS_FAILED(rv)) {
+    aRv.Throw(rv);
+  }
+
+  return;
 }
 
 } // namespace dom
 } // namespace mozilla