Bug 1437956 - Pretty print XML with Shadow DOM draft
authorTimothy Guan-tin Chien <timdream@gmail.com>
Fri, 01 Jun 2018 17:45:11 +0800
changeset 809391 02ecac7a74708733a4a36b702b006f25eae9705f
parent 808264 1e2c9151a09e43613a79daa8d4a94dc3e314020c
push id113662
push userbmo:timdream@gmail.com
push dateFri, 22 Jun 2018 00:23:27 +0000
bugs1437956
milestone62.0a1
Bug 1437956 - Pretty print XML with Shadow DOM This patch puts the transformed pretty print DOM into a Shadow DOM. The stylesheet is loaded with an @import in a <style> block, so the monospace stylesheet had to be left out. The XBL binding is kept, pending removal when Shadow DOM ships. It's still needed to handle the case when Shadow DOM is pref'd off too. MozReview-Commit-ID: DQRsXB8tumF
dom/base/Element.cpp
dom/base/Element.h
dom/base/ShadowRoot.h
dom/xml/nsXMLPrettyPrinter.cpp
dom/xml/nsXMLPrettyPrinter.h
dom/xml/resources/XMLMonoPrint.css
dom/xml/resources/XMLPrettyPrint.xml
dom/xml/resources/XMLPrettyPrint.xsl
dom/xml/resources/jar.mn
--- a/dom/base/Element.cpp
+++ b/dom/base/Element.cpp
@@ -1219,16 +1219,26 @@ Element::AttachShadow(const ShadowRootIn
    * 3. If context object is a shadow host, then throw
    *    an "InvalidStateError" DOMException.
    */
   if (GetShadowRoot() || GetXBLBinding()) {
     aError.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
     return nullptr;
   }
 
+  if (StaticPrefs::dom_webcomponents_shadowdom_report_usage()) {
+    OwnerDoc()->ReportShadowDOMUsage();
+  }
+
+  return AttachShadowWithoutNameChecks(aInit.mMode);
+}
+
+already_AddRefed<ShadowRoot>
+Element::AttachShadowWithoutNameChecks(ShadowRootMode aMode)
+{
   nsAutoScriptBlocker scriptBlocker;
 
   RefPtr<mozilla::dom::NodeInfo> nodeInfo =
     mNodeInfo->NodeInfoManager()->GetNodeInfo(
       nsGkAtoms::documentFragmentNodeName, nullptr, kNameSpaceID_None,
       DOCUMENT_FRAGMENT_NODE);
 
   if (nsIDocument* doc = GetComposedDoc()) {
@@ -1239,36 +1249,53 @@ Element::AttachShadow(const ShadowRootIn
   MOZ_ASSERT(!GetPrimaryFrame());
 
   /**
    * 4. Let shadow be a new shadow root whose node document is
    *    context object’s node document, host is context object,
    *    and mode is init’s mode.
    */
   RefPtr<ShadowRoot> shadowRoot =
-    new ShadowRoot(this, aInit.mMode, nodeInfo.forget());
+    new ShadowRoot(this, aMode, nodeInfo.forget());
 
   shadowRoot->SetIsComposedDocParticipant(IsInComposedDoc());
 
-  if (StaticPrefs::dom_webcomponents_shadowdom_report_usage()) {
-    OwnerDoc()->ReportShadowDOMUsage();
-  }
-
   /**
    * 5. Set context object’s shadow root to shadow.
    */
   SetShadowRoot(shadowRoot);
 
   /**
    * 6. Return shadow.
    */
   return shadowRoot.forget();
 }
 
 void
+Element::UnattachShadow()
+{
+  if (!GetShadowRoot()) {
+    return;
+  }
+
+  nsAutoScriptBlocker scriptBlocker;
+
+  if (nsIDocument* doc = GetComposedDoc()) {
+    if (nsIPresShell* shell = doc->GetShell()) {
+      shell->DestroyFramesForAndRestyle(this);
+    }
+  }
+  MOZ_ASSERT(!GetPrimaryFrame());
+
+  // Simply unhook the shadow root from the element.
+  MOZ_ASSERT(!GetShadowRoot()->HasSlots(), "Won't work when shadow root has slots!");
+  SetShadowRoot(nullptr);
+}
+
+void
 Element::GetAttribute(const nsAString& aName, DOMString& aReturn)
 {
   const nsAttrValue* val =
     mAttrsAndChildren.GetAttr(aName,
                               IsHTMLElement() && IsInHTMLDocument() ?
                                 eIgnoreCase : eCaseMatters);
   if (val) {
     val->ToString(aReturn);
--- a/dom/base/Element.h
+++ b/dom/base/Element.h
@@ -1258,16 +1258,20 @@ public:
                                             ErrorResult& aError);
 
   MOZ_CAN_RUN_SCRIPT already_AddRefed<DOMRectList> GetClientRects();
   MOZ_CAN_RUN_SCRIPT already_AddRefed<DOMRect> GetBoundingClientRect();
 
   // Shadow DOM v1
   already_AddRefed<ShadowRoot> AttachShadow(const ShadowRootInit& aInit,
                                             ErrorResult& aError);
+
+  already_AddRefed<ShadowRoot> AttachShadowWithoutNameChecks(ShadowRootMode aMode);
+  void UnattachShadow();
+
   ShadowRoot* GetShadowRootByMode() const;
   void SetSlot(const nsAString& aName, ErrorResult& aError);
   void GetSlot(nsAString& aName);
 
   ShadowRoot* GetShadowRoot() const
   {
     const nsExtendedDOMSlots* slots = GetExistingExtendedDOMSlots();
     return slots ? slots->mShadowRoot.get() : nullptr;
--- a/dom/base/ShadowRoot.h
+++ b/dom/base/ShadowRoot.h
@@ -134,16 +134,17 @@ private:
    * It is important that this runs _before_ actually shuffling the flat tree
    * around, so that layout knows the actual tree that it needs to invalidate.
    */
   void InvalidateStyleAndLayoutOnSubtree(Element*);
 
 public:
   void AddSlot(HTMLSlotElement* aSlot);
   void RemoveSlot(HTMLSlotElement* aSlot);
+  bool HasSlots() const { return !mSlotMap.IsEmpty(); };
 
   const RawServoAuthorStyles* ServoStyles() const
   {
     return mServoStyles.get();
   }
 
   RawServoAuthorStyles* ServoStyles()
   {
--- a/dom/xml/nsXMLPrettyPrinter.cpp
+++ b/dom/xml/nsXMLPrettyPrinter.cpp
@@ -119,111 +119,133 @@ nsXMLPrettyPrinter::PrettyPrint(nsIDocum
     }
 
     RefPtr<DocumentFragment> resultFragment =
         transformer->TransformToFragment(*aDocument, *aDocument, err);
     if (NS_WARN_IF(err.Failed())) {
         return err.StealNSResult();
     }
 
-    //
-    // Apply the prettprint XBL binding.
-    //
-    // We take some shortcuts here. In particular, we don't bother invoking the
-    // contstructor (since the binding has no constructor), and we don't bother
-    // calling LoadBindingDocument because it's a chrome:// URI and thus will get
-    // sync loaded no matter what.
-    //
-
-    // Grab the XBL service.
-    nsXBLService* xblService = nsXBLService::GetInstance();
-    NS_ENSURE_TRUE(xblService, NS_ERROR_NOT_AVAILABLE);
-
-    // Compute the binding URI.
-    nsCOMPtr<nsIURI> bindingUri;
-    rv = NS_NewURI(getter_AddRefs(bindingUri),
-        NS_LITERAL_STRING("chrome://global/content/xml/XMLPrettyPrint.xml#prettyprint"));
-    NS_ENSURE_SUCCESS(rv, rv);
-
-    // Compute the bound element.
+    // Find the root element
     RefPtr<Element> rootElement = aDocument->GetRootElement();
     NS_ENSURE_TRUE(rootElement, NS_ERROR_UNEXPECTED);
 
-    // Grab the system principal.
-    nsCOMPtr<nsIPrincipal> sysPrincipal;
-    nsContentUtils::GetSecurityManager()->
-        GetSystemPrincipal(getter_AddRefs(sysPrincipal));
+    if (nsContentUtils::IsShadowDOMEnabled()) {
+        // Attach a closed shadow root on it.
+        RefPtr<ShadowRoot> shadowRoot =
+            rootElement->AttachShadowWithoutNameChecks(ShadowRootMode::Closed);
 
-    // Destroy any existing frames before we unbind anonymous content.
-    // Note that the shell might be Destroy'ed by now (see bug 1415541).
-    if (!shell->IsDestroying()) {
-        shell->DestroyFramesForAndRestyle(rootElement);
-    }
+        // Append the document fragment to the shadow dom.
+        shadowRoot->AppendChild(*resultFragment, err);
+        if (NS_WARN_IF(err.Failed())) {
+            return err.StealNSResult();
+        }
+    } else {
+        //
+        // Apply the prettprint XBL binding.
+        //
+        // We take some shortcuts here. In particular, we don't bother invoking the
+        // contstructor (since the binding has no constructor), and we don't bother
+        // calling LoadBindingDocument because it's a chrome:// URI and thus will get
+        // sync loaded no matter what.
+        //
 
-    // Load the bindings.
-    RefPtr<nsXBLBinding> unused;
-    bool ignored;
-    rv = xblService->LoadBindings(rootElement, bindingUri, sysPrincipal,
-                                  getter_AddRefs(unused), &ignored);
-    NS_ENSURE_SUCCESS(rv, rv);
+        // Grab the XBL service.
+        nsXBLService* xblService = nsXBLService::GetInstance();
+        NS_ENSURE_TRUE(xblService, NS_ERROR_NOT_AVAILABLE);
+
+        // Compute the binding URI.
+        nsCOMPtr<nsIURI> bindingUri;
+        rv = NS_NewURI(getter_AddRefs(bindingUri),
+            NS_LITERAL_STRING("chrome://global/content/xml/XMLPrettyPrint.xml#prettyprint"));
+        NS_ENSURE_SUCCESS(rv, rv);
+
+        // Grab the system principal.
+        nsCOMPtr<nsIPrincipal> sysPrincipal;
+        nsContentUtils::GetSecurityManager()->
+            GetSystemPrincipal(getter_AddRefs(sysPrincipal));
 
-    // Fire an event at the bound element to pass it |resultFragment|.
-    RefPtr<CustomEvent> event =
-      NS_NewDOMCustomEvent(rootElement, nullptr, nullptr);
-    MOZ_ASSERT(event);
-    AutoJSAPI jsapi;
-    if (!jsapi.Init(event->GetParentObject())) {
-        return NS_ERROR_UNEXPECTED;
-    }
-    JSContext* cx = jsapi.cx();
-    JS::Rooted<JS::Value> detail(cx);
-    if (!ToJSValue(cx, resultFragment, &detail)) {
-        return NS_ERROR_UNEXPECTED;
-    }
-    event->InitCustomEvent(cx, NS_LITERAL_STRING("prettyprint-dom-created"),
-                           /* bubbles = */ false, /* cancelable = */ false,
-                           detail);
+        // Destroy any existing frames before we unbind anonymous content.
+        // Note that the shell might be Destroy'ed by now (see bug 1415541).
+        if (!shell->IsDestroying()) {
+            shell->DestroyFramesForAndRestyle(rootElement);
+        }
+
+        // Load the bindings.
+        RefPtr<nsXBLBinding> unused;
+        bool ignored;
+        rv = xblService->LoadBindings(rootElement, bindingUri, sysPrincipal,
+                                      getter_AddRefs(unused), &ignored);
+        NS_ENSURE_SUCCESS(rv, rv);
 
-    event->SetTrusted(true);
-    rootElement->DispatchEvent(*event, err);
-    if (NS_WARN_IF(err.Failed())) {
-        return err.StealNSResult();
+        // Fire an event at the bound element to pass it |resultFragment|.
+        RefPtr<CustomEvent> event =
+          NS_NewDOMCustomEvent(rootElement, nullptr, nullptr);
+        MOZ_ASSERT(event);
+        AutoJSAPI jsapi;
+        if (!jsapi.Init(event->GetParentObject())) {
+            return NS_ERROR_UNEXPECTED;
+        }
+        JSContext* cx = jsapi.cx();
+        JS::Rooted<JS::Value> detail(cx);
+        if (!ToJSValue(cx, resultFragment, &detail)) {
+            return NS_ERROR_UNEXPECTED;
+        }
+        event->InitCustomEvent(cx, NS_LITERAL_STRING("prettyprint-dom-created"),
+                               /* bubbles = */ false, /* cancelable = */ false,
+                               detail);
+
+        event->SetTrusted(true);
+        rootElement->DispatchEvent(*event, err);
+        if (NS_WARN_IF(err.Failed())) {
+            return err.StealNSResult();
+        }
     }
 
     // Observe the document so we know when to switch to "normal" view
     aDocument->AddObserver(this);
     mDocument = aDocument;
 
     NS_ADDREF_THIS();
 
     return NS_OK;
 }
 
 void
 nsXMLPrettyPrinter::MaybeUnhook(nsIContent* aContent)
 {
-    // If there either aContent is null (the document-node was modified) or
-    // there isn't a binding parent we know it's non-anonymous content.
-    if ((!aContent || !aContent->GetBindingParent()) && !mUnhookPending) {
+    // If aContent is null, the document-node was modified.
+    // If it is not null but in the shadow tree, the <scrollbar> NACs,
+    // or the XBL binding, the change was in the generated content, and
+    // it should be ignored.
+    bool isGeneratedContent = !aContent ?
+        false :
+        aContent->GetBindingParent() || aContent->IsInShadowTree();
+
+    if (!isGeneratedContent && !mUnhookPending) {
         // Can't blindly to mUnhookPending after AddScriptRunner,
         // since AddScriptRunner _could_ in theory run us
         // synchronously
         mUnhookPending = true;
         nsContentUtils::AddScriptRunner(NewRunnableMethod(
           "nsXMLPrettyPrinter::Unhook", this, &nsXMLPrettyPrinter::Unhook));
     }
 }
 
 void
 nsXMLPrettyPrinter::Unhook()
 {
     mDocument->RemoveObserver(this);
     nsCOMPtr<Element> element = mDocument->GetDocumentElement();
 
     if (element) {
+        // Remove the shadow root
+        element->UnattachShadow();
+
+        // Remove the bound XBL binding
         mDocument->BindingManager()->ClearBinding(element);
     }
 
     mDocument = nullptr;
 
     NS_RELEASE_THIS();
 }
 
--- a/dom/xml/nsXMLPrettyPrinter.h
+++ b/dom/xml/nsXMLPrettyPrinter.h
@@ -39,17 +39,17 @@ public:
      * Unhook the prettyprinter
      */
     void Unhook();
 private:
     virtual ~nsXMLPrettyPrinter();
 
     /**
      * Signals for unhooking by setting mUnhookPending if the node changed is
-     * non-anonymous content.
+     * not in the shadow root tree nor in anonymous content.
      *
      * @param aContent  content that has changed
      */
     void MaybeUnhook(nsIContent* aContent);
 
     nsIDocument* mDocument; //weak. Set as long as we're observing the document
     bool mUnhookPending;
 };
deleted file mode 100644
--- a/dom/xml/resources/XMLMonoPrint.css
+++ /dev/null
@@ -1,10 +0,0 @@
-@charset "UTF-8";
-/* This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-#tree,
-.expandable-opening::-moz-list-bullet {
-  font-family: monospace;
-  white-space: pre;
-}
--- a/dom/xml/resources/XMLPrettyPrint.xml
+++ b/dom/xml/resources/XMLPrettyPrint.xml
@@ -10,16 +10,21 @@
 
     <content><html:div id="top"/>
       <html:span style="display: none;"><children/></html:span>
     </content>
 
     <handlers>
       <handler event="prettyprint-dom-created" allowuntrusted="false">
         <![CDATA[
-          document.getAnonymousNodes(this).item(0).appendChild(event.detail);
+          let container = document.getAnonymousNodes(this).item(0);
+          // Take the child nodes from the passed <div id="top">
+          // and append them to our own.
+          for (let el of event.detail.childNodes) {
+            container.appendChild(el);
+          }
         ]]>
       </handler>
     </handlers>
 
   </binding>
 
 </bindings>
--- a/dom/xml/resources/XMLPrettyPrint.xsl
+++ b/dom/xml/resources/XMLPrettyPrint.xsl
@@ -12,26 +12,29 @@
 
 <xsl:stylesheet version="1.0"
                 xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
                 xmlns="http://www.w3.org/1999/xhtml">
 
   <xsl:output method="xml"/>
 
   <xsl:template match="/">
-    <link href="chrome://global/content/xml/XMLPrettyPrint.css" type="text/css" rel="stylesheet"/>
-    <link title="Monospace" href="chrome://global/content/xml/XMLMonoPrint.css" type="text/css" rel="alternate stylesheet"/>
-    <div id="header" dir="&locale.dir;">
-      <p>
-        &xml.nostylesheet;
-      </p>
+    <div id="top">
+      <style>
+        @import url("chrome://global/content/xml/XMLPrettyPrint.css");
+      </style>
+      <div id="header" dir="&locale.dir;">
+        <p>
+          &xml.nostylesheet;
+        </p>
+      </div>
+      <main id="tree" class="highlight">
+        <xsl:apply-templates/>
+      </main>
     </div>
-    <main id="tree" class="highlight">
-      <xsl:apply-templates/>
-    </main>
   </xsl:template>
 
   <xsl:template match="*">
     <div>
       <xsl:text>&lt;</xsl:text>
       <span class="start-tag"><xsl:value-of select="name(.)"/></span>
       <xsl:apply-templates select="@*"/>
       <xsl:text>/&gt;</xsl:text>
--- a/dom/xml/resources/jar.mn
+++ b/dom/xml/resources/jar.mn
@@ -1,9 +1,8 @@
 # This Source Code Form is subject to the terms of the Mozilla Public
 # License, v. 2.0. If a copy of the MPL was not distributed with this
 # file, You can obtain one at http://mozilla.org/MPL/2.0/.
 
 toolkit.jar:
     content/global/xml/XMLPrettyPrint.css                 (XMLPrettyPrint.css)
-    content/global/xml/XMLMonoPrint.css                   (XMLMonoPrint.css)
+    content/global/xml/XMLPrettyPrint.xsl                 (XMLPrettyPrint.xsl)
     content/global/xml/XMLPrettyPrint.xml                 (XMLPrettyPrint.xml)
-    content/global/xml/XMLPrettyPrint.xsl                 (XMLPrettyPrint.xsl)