Bug 1318542 - part1: Allow JS to create NAC pseudo-elements. r? draft
authorCameron McCormack <cam@mcc.id.au>
Thu, 29 Dec 2016 10:59:24 +0800
changeset 454415 874b6852d7f81af54c538600503d43072efbaf21
parent 454414 16bf9f2ab863a619bb59ac2f168f0430ee5a3da9
child 454416 0d7e21f3a6bb71f6a034ec7e8fd8af420065d31f
push id39921
push userbechen@mozilla.com
push dateThu, 29 Dec 2016 08:25:36 +0000
bugs1318542
milestone53.0a1
Bug 1318542 - part1: Allow JS to create NAC pseudo-elements. r? MozReview-Commit-ID: 2aBPoCOsT6R
dom/base/nsDocument.cpp
dom/base/nsGkAtomList.h
dom/webidl/Document.webidl
layout/base/nsCSSFrameConstructor.cpp
layout/style/nsComputedDOMStyle.cpp
--- a/dom/base/nsDocument.cpp
+++ b/dom/base/nsDocument.cpp
@@ -5463,16 +5463,34 @@ nsDocument::GetCustomElementRegistry()
   RefPtr<CustomElementRegistry> registry = window->CustomElements();
   if (!registry) {
     return nullptr;
   }
 
   return registry.forget();
 }
 
+// We only support pseudo-elements with two colons in this function.
+static CSSPseudoElementType
+GetPseudoElementType(const nsString& aString, ErrorResult& aRv)
+{
+  if (aString.IsEmpty()) {
+    return CSSPseudoElementType::NotPseudo;
+  }
+
+  if (aString.Length() <= 2 || aString[0] != ':' || aString[1] != ':') {
+    aRv.Throw(NS_ERROR_DOM_SYNTAX_ERR);
+    return CSSPseudoElementType::NotPseudo;
+  }
+
+  nsCOMPtr<nsIAtom> pseudo = NS_Atomize(Substring(aString, 1));
+  return nsCSSPseudoElements::GetPseudoType(pseudo,
+      nsCSSProps::EnabledState::eInUASheets);
+}
+
 already_AddRefed<Element>
 nsDocument::CreateElement(const nsAString& aTagName,
                           const ElementCreationOptionsOrString& aOptions,
                           ErrorResult& rv)
 {
   rv = nsContentUtils::CheckQName(aTagName, false);
   if (rv.Failed()) {
     return nullptr;
@@ -5480,28 +5498,55 @@ nsDocument::CreateElement(const nsAStrin
 
   bool needsLowercase = IsHTMLDocument() && !IsLowercaseASCII(aTagName);
   nsAutoString lcTagName;
   if (needsLowercase) {
     nsContentUtils::ASCIIToLower(aTagName, lcTagName);
   }
 
   const nsString* is = nullptr;
+  CSSPseudoElementType pseudoType = CSSPseudoElementType::NotPseudo;
   if (aOptions.IsElementCreationOptions()) {
+    const ElementCreationOptions& options =
+      aOptions.GetAsElementCreationOptions();
+
     // Throw NotFoundError if 'is' is not-null and definition is null
-    is = CheckCustomElementName(aOptions.GetAsElementCreationOptions(),
+    is = CheckCustomElementName(options,
       needsLowercase ? lcTagName : aTagName, mDefaultElementType, rv);
     if (rv.Failed()) {
       return nullptr;
     }
+
+    // Check 'pseudo' and throw an exception if it's not a recognized
+    // pseduo-element name, or if it's one of the structural pseudo-elements
+    // we don't want to support.
+    if (options.mPseudo.WasPassed()) {
+      pseudoType = GetPseudoElementType(options.mPseudo.Value(), rv);
+      if (rv.Failed()) {
+        return nullptr;
+      }
+      if (pseudoType != CSSPseudoElementType::NotPseudo) {
+        if (nsCSSPseudoElements::PseudoElementContainsElements(pseudoType) ||
+            pseudoType == CSSPseudoElementType::before ||
+            pseudoType == CSSPseudoElementType::after) {
+          rv.Throw(NS_ERROR_DOM_NOT_SUPPORTED_ERR);
+          return nullptr;
+        }
+      }
+    }
   }
 
   RefPtr<Element> elem = CreateElem(
     needsLowercase ? lcTagName : aTagName, nullptr, mDefaultElementType, is);
 
+  if (pseudoType != CSSPseudoElementType::NotPseudo) {
+    elem->SetProperty(nsGkAtoms::pseudoProperty,
+                      reinterpret_cast<void*>(pseudoType));
+  }
+
   return elem.forget();
 }
 
 NS_IMETHODIMP
 nsDocument::CreateElementNS(const nsAString& aNamespaceURI,
                             const nsAString& aQualifiedName,
                             nsIDOMElement** aReturn)
 {
--- a/dom/base/nsGkAtomList.h
+++ b/dom/base/nsGkAtomList.h
@@ -2144,16 +2144,17 @@ GK_ATOM(transitionsOfBeforeProperty, "Tr
 GK_ATOM(transitionsOfAfterProperty, "TransitionsOfAfterProperty") // FrameTransitions*
 GK_ATOM(genConInitializerProperty, "QuoteNodeProperty")
 GK_ATOM(labelMouseDownPtProperty, "LabelMouseDownPtProperty")
 GK_ATOM(baseURIProperty, "baseURIProperty")
 GK_ATOM(lockedStyleStates, "lockedStyleStates")
 GK_ATOM(apzCallbackTransform, "apzCallbackTransform")
 GK_ATOM(restylableAnonymousNode, "restylableAnonymousNode")
 GK_ATOM(paintRequestTime, "PaintRequestTime")
+GK_ATOM(pseudoProperty, "PseudoProperty")  // CSSPseudoElementType
 
 // Languages for lang-specific transforms
 GK_ATOM(Japanese, "ja")
 GK_ATOM(Chinese, "zh-CN")
 GK_ATOM(Taiwanese, "zh-TW")
 GK_ATOM(HongKongChinese, "zh-HK")
 GK_ATOM(Unicode, "x-unicode")
 
--- a/dom/webidl/Document.webidl
+++ b/dom/webidl/Document.webidl
@@ -12,16 +12,19 @@ interface URI;
 interface nsIDocShell;
 interface nsILoadGroup;
 
 enum VisibilityState { "hidden", "visible", "prerender" };
 
 /* https://dom.spec.whatwg.org/#dictdef-elementcreationoptions */
 dictionary ElementCreationOptions {
   DOMString is;
+
+  [ChromeOnly]
+  DOMString pseudo;
 };
 
 /* http://dom.spec.whatwg.org/#interface-document */
 [Constructor]
 interface Document : Node {
   [Throws]
   readonly attribute DOMImplementation implementation;
   [Pure, Throws, BinaryName="documentURIFromJS", NeedsCallerType]
--- a/layout/base/nsCSSFrameConstructor.cpp
+++ b/layout/base/nsCSSFrameConstructor.cpp
@@ -5039,27 +5039,47 @@ nsCSSFrameConstructor::ResolveStyleConte
                                            nsIContent* aContent,
                                            nsFrameConstructorState* aState)
 {
   StyleSetHandle styleSet = mPresShell->StyleSet();
   aContent->OwnerDoc()->FlushPendingLinkUpdates();
 
   RefPtr<nsStyleContext> result;
   if (aContent->IsElement()) {
-    if (aState) {
-      result = styleSet->ResolveStyleFor(aContent->AsElement(),
-                                         aParentStyleContext,
-                                         ConsumeStyleBehavior::Consume,
-                                         LazyComputeBehavior::Assert,
-                                         aState->mTreeMatchContext);
+    auto pseudoType = CSSPseudoElementType(reinterpret_cast<uintptr_t>(
+        aContent->GetProperty(nsGkAtoms::pseudoProperty)));
+    if (pseudoType == CSSPseudoElementType(0)) {
+      // Although CSSPseudoElementType::after == 0, we disallow that specifying
+      // that pseudo in nsDocument::CreateElement, so we can just check for 0
+      // rather than passing the nsresult argument to GetProperty.
+      if (aState) {
+        result = styleSet->ResolveStyleFor(aContent->AsElement(),
+                                           aParentStyleContext,
+                                           ConsumeStyleBehavior::Consume,
+                                           LazyComputeBehavior::Assert,
+                                           aState->mTreeMatchContext);
+      } else {
+        result = styleSet->ResolveStyleFor(aContent->AsElement(),
+                                           aParentStyleContext,
+                                           ConsumeStyleBehavior::Consume,
+                                           LazyComputeBehavior::Assert);
+      }
     } else {
-      result = styleSet->ResolveStyleFor(aContent->AsElement(),
-                                         aParentStyleContext,
-                                         ConsumeStyleBehavior::Consume,
-                                         LazyComputeBehavior::Assert);
+      MOZ_ASSERT(aContent->GetParentElement() &&
+                 aContent->GetParentElement()->IsInNativeAnonymousSubtree(),
+                 "only use nsGkAtoms::pseudoProperty for elements in native "
+                 "anonymous subtrees");
+      Element* parent = aContent->GetParentElement();
+      while (!parent->IsRootOfNativeAnonymousSubtree()) {
+        parent = parent->GetParentElement();
+      }
+      result = styleSet->ResolvePseudoElementStyle(parent,
+                                                   pseudoType,
+                                                   aParentStyleContext,
+                                                   aContent->AsElement());
     }
   } else {
     NS_ASSERTION(aContent->IsNodeOfType(nsINode::eTEXT),
                  "shouldn't waste time creating style contexts for "
                  "comments and processing instructions");
     result = styleSet->ResolveStyleForText(aContent, aParentStyleContext);
   }
 
--- a/layout/style/nsComputedDOMStyle.cpp
+++ b/layout/style/nsComputedDOMStyle.cpp
@@ -677,17 +677,18 @@ nsComputedDOMStyle::UpdateCurrentStyleSo
 
       SetFrameStyleContext(mInnerFrame->StyleContext());
       NS_ASSERTION(mStyleContext, "Frame without style context?");
     }
   }
 
   if (!mStyleContext || mStyleContext->HasPseudoElementData()) {
 #ifdef DEBUG
-    if (mStyleContext) {
+    if (mStyleContext &&
+        !mContent->AsElement()->GetProperty(nsGkAtoms::pseudoProperty)) {
       // We want to check that going through this path because of
       // HasPseudoElementData is rare, because it slows us down a good
       // bit.  So check that we're really inside something associated
       // with a pseudo-element that contains elements.
       nsStyleContext* topWithPseudoElementData = mStyleContext;
       while (topWithPseudoElementData->GetParent()->HasPseudoElementData()) {
         topWithPseudoElementData = topWithPseudoElementData->GetParent();
       }