Bug 1062578 - Call BindToTree/UnbindFromTree on shadow root children when host is bound/unbound from tree. r=smaug
authorWilliam Chen <wchen@mozilla.com>
Fri, 26 Sep 2014 15:07:40 -0700
changeset 207537 82d07982831cd90dfb468e278ceb137ef6040fa8
parent 207536 398bdeea30b07712656cb016612375ca983ed61b
child 207538 ba2438fdc98e6b26a925e008a6053a3b53e24f13
push id49703
push userwchen@mozilla.com
push dateFri, 26 Sep 2014 22:07:43 +0000
treeherdermozilla-inbound@82d07982831c [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewerssmaug
bugs1062578
milestone35.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 1062578 - Call BindToTree/UnbindFromTree on shadow root children when host is bound/unbound from tree. r=smaug
content/base/src/Element.cpp
content/base/src/FragmentOrElement.cpp
content/base/src/nsGenericDOMDataNode.cpp
content/base/src/nsINode.cpp
content/base/src/nsStyleLinkElement.cpp
content/html/content/src/HTMLContentElement.cpp
content/html/content/src/HTMLShadowElement.cpp
content/html/content/src/HTMLStyleElement.cpp
dom/tests/mochitest/webcomponents/mochitest.ini
dom/tests/mochitest/webcomponents/test_detached_style.html
dom/xbl/nsXBLBinding.cpp
dom/xbl/nsXBLBinding.h
testing/web-platform/meta/shadow-dom/styles/test-003.html.ini
--- a/content/base/src/Element.cpp
+++ b/content/base/src/Element.cpp
@@ -829,16 +829,23 @@ Element::CreateShadowRoot(ErrorResult& a
   // Replace the old ShadowRoot with the new one and let the old
   // ShadowRoot know about the younger ShadowRoot because the old
   // ShadowRoot is projected into the younger ShadowRoot's shadow
   // insertion point (if it exists).
   ShadowRoot* olderShadow = GetShadowRoot();
   SetShadowRoot(shadowRoot);
   if (olderShadow) {
     olderShadow->SetYoungerShadow(shadowRoot);
+
+    // Unbind children of older shadow root because they
+    // are no longer in the composed tree.
+    for (nsIContent* child = olderShadow->GetFirstChild(); child;
+         child = child->GetNextSibling()) {
+      child->UnbindFromTree(true, false);
+    }
   }
 
   // xblBinding takes ownership of docInfo.
   nsRefPtr<nsXBLBinding> xblBinding = new nsXBLBinding(shadowRoot, protoBinding);
   shadowRoot->SetAssociatedBinding(xblBinding);
   xblBinding->SetBoundElement(this);
 
   SetXBLBinding(xblBinding);
@@ -1379,16 +1386,28 @@ Element::BindToTree(nsIDocument* aDocume
     // it'll just do a small bit of unnecessary work.  But most elements in
     // practice are mapped-attribute elements.
     nsHTMLStyleSheet* sheet = aDocument->GetAttributeStyleSheet();
     if (sheet) {
       mAttrsAndChildren.SetMappedAttrStyleSheet(sheet);
     }
   }
 
+  // Call BindToTree on shadow root children.
+  ShadowRoot* shadowRoot = GetShadowRoot();
+  if (shadowRoot) {
+    for (nsIContent* child = shadowRoot->GetFirstChild(); child;
+         child = child->GetNextSibling()) {
+      rv = child->BindToTree(nullptr, shadowRoot,
+                             shadowRoot->GetBindingParent(),
+                             aCompileEventHandlers);
+      NS_ENSURE_SUCCESS(rv, rv);
+    }
+  }
+
   // XXXbz script execution during binding can trigger some of these
   // postcondition asserts....  But we do want that, since things will
   // generally be quite broken when that happens.
   NS_POSTCONDITION(aDocument == GetCurrentDoc(), "Bound to wrong document");
   NS_POSTCONDITION(aParent == GetParent(), "Bound to wrong parent");
   NS_POSTCONDITION(aBindingParent == GetBindingParent(),
                    "Bound to wrong binding parent");
 
@@ -1457,20 +1476,23 @@ Element::UnbindFromTree(bool aDeep, bool
       mParent = nullptr;
       NS_RELEASE(p);
     } else {
       mParent = nullptr;
     }
     SetParentIsContent(false);
   }
   ClearInDocument();
-  UnsetFlags(NODE_IS_IN_SHADOW_TREE);
-
-  // Begin keeping track of our subtree root.
-  SetSubtreeRootPointer(aNullParent ? this : mParent->SubtreeRoot());
+
+  if (aNullParent || !mParent->IsInShadowTree()) {
+    UnsetFlags(NODE_IS_IN_SHADOW_TREE);
+
+    // Begin keeping track of our subtree root.
+    SetSubtreeRootPointer(aNullParent ? this : mParent->SubtreeRoot());
+  }
 
   if (document) {
     // Notify XBL- & nsIAnonymousContentCreator-generated
     // anonymous content that the document is changing.
     if (HasFlag(NODE_MAY_BE_IN_BINDING_MNGR)) {
       nsContentUtils::AddScriptRunner(
         new RemoveFromBindingManagerRunnable(document->BindingManager(), this,
                                              document));
@@ -1509,17 +1531,19 @@ Element::UnbindFromTree(bool aDeep, bool
   }
 #endif
 
   nsDOMSlots* slots = GetExistingDOMSlots();
   if (slots) {
     if (clearBindingParent) {
       slots->mBindingParent = nullptr;
     }
-    slots->mContainingShadow = nullptr;
+    if (aNullParent || !mParent->IsInShadowTree()) {
+      slots->mContainingShadow = nullptr;
+    }
   }
 
   // This has to be here, rather than in nsGenericHTMLElement::UnbindFromTree, 
   //  because it has to happen after unsetting the parent pointer, but before
   //  recursively unbinding the kids.
   if (IsHTML()) {
     ResetDir(this);
   }
@@ -1534,16 +1558,25 @@ Element::UnbindFromTree(bool aDeep, bool
       // Note that we pass false for aNullParent here, since we don't want
       // the kids to forget us.  We _do_ want them to forget their binding
       // parent, though, since this only walks non-anonymous kids.
       mAttrsAndChildren.ChildAt(i)->UnbindFromTree(true, false);
     }
   }
 
   nsNodeUtils::ParentChainChanged(this);
+
+  // Unbind children of shadow root.
+  ShadowRoot* shadowRoot = GetShadowRoot();
+  if (shadowRoot) {
+    for (nsIContent* child = shadowRoot->GetFirstChild(); child;
+         child = child->GetNextSibling()) {
+      child->UnbindFromTree(true, false);
+    }
+  }
 }
 
 nsICSSDeclaration*
 Element::GetSMILOverrideStyle()
 {
   Element::nsDOMSlots *slots = DOMSlots();
 
   if (!slots->mSMILOverrideStyle) {
--- a/content/base/src/FragmentOrElement.cpp
+++ b/content/base/src/FragmentOrElement.cpp
@@ -1388,16 +1388,20 @@ NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(Fr
     }
   } else if (!tmp->GetParent() && tmp->mAttrsAndChildren.ChildCount()) {
     ContentUnbinder::Append(tmp);
   } /* else {
     The subtree root will end up to a ContentUnbinder, and that will
     unbind the child nodes.
   } */
 
+  // Clear flag here because unlinking slots will clear the
+  // containing shadow root pointer.
+  tmp->UnsetFlags(NODE_IS_IN_SHADOW_TREE);
+
   // Unlink any DOM slots of interest.
   {
     nsDOMSlots *slots = tmp->GetExistingDOMSlots();
     if (slots) {
       slots->Unlink(tmp->IsXUL());
     }
   }
 
--- a/content/base/src/nsGenericDOMDataNode.cpp
+++ b/content/base/src/nsGenericDOMDataNode.cpp
@@ -577,25 +577,30 @@ nsGenericDOMDataNode::UnbindFromTree(boo
     if (GetParent()) {
       NS_RELEASE(mParent);
     } else {
       mParent = nullptr;
     }
     SetParentIsContent(false);
   }
   ClearInDocument();
-  UnsetFlags(NODE_IS_IN_SHADOW_TREE);
+
+  if (aNullParent || !mParent->IsInShadowTree()) {
+    UnsetFlags(NODE_IS_IN_SHADOW_TREE);
 
-  // Begin keeping track of our subtree root.
-  SetSubtreeRootPointer(aNullParent ? this : mParent->SubtreeRoot());
+    // Begin keeping track of our subtree root.
+    SetSubtreeRootPointer(aNullParent ? this : mParent->SubtreeRoot());
+  }
 
   nsDataSlots *slots = GetExistingDataSlots();
   if (slots) {
     slots->mBindingParent = nullptr;
-    slots->mContainingShadow = nullptr;
+    if (aNullParent || !mParent->IsInShadowTree()) {
+      slots->mContainingShadow = nullptr;
+    }
   }
 
   nsNodeUtils::ParentChainChanged(this);
 }
 
 already_AddRefed<nsINodeList>
 nsGenericDOMDataNode::GetChildren(uint32_t aFilter)
 {
--- a/content/base/src/nsINode.cpp
+++ b/content/base/src/nsINode.cpp
@@ -387,17 +387,25 @@ nsINode::GetTextContentInternal(nsAStrin
 nsIDocument*
 nsINode::GetComposedDocInternal() const
 {
   MOZ_ASSERT(HasFlag(NODE_IS_IN_SHADOW_TREE) && IsContent(),
              "Should only be caled on nodes in the shadow tree.");
 
   // Cross ShadowRoot boundary.
   ShadowRoot* containingShadow = AsContent()->GetContainingShadow();
-  return containingShadow->GetHost()->GetCrossShadowCurrentDoc();
+
+  nsIContent* poolHost = containingShadow->GetPoolHost();
+  if (!poolHost) {
+    // This node is in an older shadow root that does not get projected into
+    // an insertion point, thus this node can not be in the composed document.
+    return nullptr;
+  }
+
+  return poolHost->GetComposedDoc();
 }
 
 #ifdef DEBUG
 void
 nsINode::CheckNotNativeAnonymous() const
 {
   if (!IsNodeOfType(eCONTENT))
     return;
--- a/content/base/src/nsStyleLinkElement.cpp
+++ b/content/base/src/nsStyleLinkElement.cpp
@@ -203,17 +203,18 @@ nsStyleLinkElement::UpdateStyleSheet(nsI
                                      bool* aWillNotify,
                                      bool* aIsAlternate,
                                      bool aForceReload)
 {
   if (aForceReload) {
     // We remove this stylesheet from the cache to load a new version.
     nsCOMPtr<nsIContent> thisContent;
     CallQueryInterface(this, getter_AddRefs(thisContent));
-    nsIDocument* doc = thisContent->GetCrossShadowCurrentDoc();
+    nsCOMPtr<nsIDocument> doc = thisContent->IsInShadowTree() ?
+      thisContent->OwnerDoc() : thisContent->GetUncomposedDoc();
     if (doc && doc->CSSLoader()->GetEnabled() &&
         mStyleSheet && mStyleSheet->GetOriginalURI()) {
       doc->CSSLoader()->ObsoleteSheet(mStyleSheet->GetOriginalURI());
     }
   }
   return DoUpdateStyleSheet(nullptr, nullptr, aObserver, aWillNotify,
                             aIsAlternate, aForceReload);
 }
@@ -339,17 +340,18 @@ nsStyleLinkElement::DoUpdateStyleSheet(n
   }
 
   // When static documents are created, stylesheets are cloned manually.
   if (mDontLoadStyle || !mUpdatesEnabled ||
       thisContent->OwnerDoc()->IsStaticDocument()) {
     return NS_OK;
   }
 
-  nsCOMPtr<nsIDocument> doc = thisContent->GetCrossShadowCurrentDoc();
+  nsCOMPtr<nsIDocument> doc = thisContent->IsInShadowTree() ?
+    thisContent->OwnerDoc() : thisContent->GetUncomposedDoc();
   if (!doc || !doc->CSSLoader()->GetEnabled()) {
     return NS_OK;
   }
 
   bool isInline;
   nsCOMPtr<nsIURI> uri = GetStyleSheetURL(&isInline);
 
   if (!aForceUpdate && mStyleSheet && !isInline && uri) {
--- a/content/html/content/src/HTMLContentElement.cpp
+++ b/content/html/content/src/HTMLContentElement.cpp
@@ -50,23 +50,25 @@ HTMLContentElement::WrapNode(JSContext *
 }
 
 nsresult
 HTMLContentElement::BindToTree(nsIDocument* aDocument,
                                nsIContent* aParent,
                                nsIContent* aBindingParent,
                                bool aCompileEventHandlers)
 {
+  nsRefPtr<ShadowRoot> oldContainingShadow = GetContainingShadow();
+
   nsresult rv = nsGenericHTMLElement::BindToTree(aDocument, aParent,
                                                  aBindingParent,
                                                  aCompileEventHandlers);
   NS_ENSURE_SUCCESS(rv, rv);
 
   ShadowRoot* containingShadow = GetContainingShadow();
-  if (containingShadow) {
+  if (containingShadow && !oldContainingShadow) {
     nsINode* parentNode = nsINode::GetParentNode();
     while (parentNode && parentNode != containingShadow) {
       if (parentNode->IsElement() &&
           parentNode->AsElement()->IsHTML(nsGkAtoms::content)) {
         // Content element in fallback content is not an insertion point.
         return NS_OK;
       }
       parentNode = parentNode->GetParentNode();
@@ -80,33 +82,30 @@ HTMLContentElement::BindToTree(nsIDocume
   }
 
   return NS_OK;
 }
 
 void
 HTMLContentElement::UnbindFromTree(bool aDeep, bool aNullParent)
 {
-  if (mIsInsertionPoint) {
-    ShadowRoot* containingShadow = GetContainingShadow();
-    // Make sure that containingShadow exists, it may have been nulled
-    // during unlinking in which case the ShadowRoot is going away.
-    if (containingShadow) {
-      containingShadow->RemoveInsertionPoint(this);
+  nsRefPtr<ShadowRoot> oldContainingShadow = GetContainingShadow();
+
+  nsGenericHTMLElement::UnbindFromTree(aDeep, aNullParent);
 
-      // Remove all the matched nodes now that the
-      // insertion point is no longer an insertion point.
-      ClearMatchedNodes();
-      containingShadow->SetInsertionPointChanged();
-    }
+  if (oldContainingShadow && !GetContainingShadow() && mIsInsertionPoint) {
+    oldContainingShadow->RemoveInsertionPoint(this);
+
+    // Remove all the matched nodes now that the
+    // insertion point is no longer an insertion point.
+    ClearMatchedNodes();
+    oldContainingShadow->SetInsertionPointChanged();
 
     mIsInsertionPoint = false;
   }
-
-  nsGenericHTMLElement::UnbindFromTree(aDeep, aNullParent);
 }
 
 void
 HTMLContentElement::AppendMatchedNode(nsIContent* aContent)
 {
   mMatchedNodes.AppendElement(aContent);
   nsTArray<nsIContent*>& destInsertionPoint = aContent->DestInsertionPoints();
   destInsertionPoint.AppendElement(this);
--- a/content/html/content/src/HTMLShadowElement.cpp
+++ b/content/html/content/src/HTMLShadowElement.cpp
@@ -106,23 +106,25 @@ IsInFallbackContent(nsIContent* aContent
 }
 
 nsresult
 HTMLShadowElement::BindToTree(nsIDocument* aDocument,
                               nsIContent* aParent,
                               nsIContent* aBindingParent,
                               bool aCompileEventHandlers)
 {
+  nsRefPtr<ShadowRoot> oldContainingShadow = GetContainingShadow();
+
   nsresult rv = nsGenericHTMLElement::BindToTree(aDocument, aParent,
                                                  aBindingParent,
                                                  aCompileEventHandlers);
   NS_ENSURE_SUCCESS(rv, rv);
 
   ShadowRoot* containingShadow = GetContainingShadow();
-  if (containingShadow) {
+  if (containingShadow && !oldContainingShadow) {
     // Keep track of all descendant <shadow> elements in tree order so
     // that when the current shadow insertion point is removed, the next
     // one can be found quickly.
     TreeOrderComparator comparator;
     containingShadow->ShadowDescendants().InsertElementSorted(this, comparator);
 
     if (containingShadow->ShadowDescendants()[0] != this) {
       // Only the first <shadow> (in tree order) of a ShadowRoot can be an insertion point.
@@ -136,45 +138,68 @@ HTMLShadowElement::BindToTree(nsIDocumen
     } else {
       mIsInsertionPoint = true;
       containingShadow->SetShadowElement(this);
     }
 
     containingShadow->SetInsertionPointChanged();
   }
 
+  if (mIsInsertionPoint && containingShadow) {
+    // Propagate BindToTree calls to projected shadow root children.
+    ShadowRoot* projectedShadow = containingShadow->GetOlderShadow();
+    if (projectedShadow) {
+      for (nsIContent* child = projectedShadow->GetFirstChild(); child;
+           child = child->GetNextSibling()) {
+        rv = child->BindToTree(nullptr, projectedShadow,
+                               projectedShadow->GetBindingParent(),
+                               aCompileEventHandlers);
+        NS_ENSURE_SUCCESS(rv, rv);
+      }
+    }
+  }
+
   return NS_OK;
 }
 
 void
 HTMLShadowElement::UnbindFromTree(bool aDeep, bool aNullParent)
 {
-  if (mIsInsertionPoint) {
-    ShadowRoot* containingShadow = GetContainingShadow();
-    // Make sure that containingShadow exists, it may have been nulled
-    // during unlinking in which case the ShadowRoot is going away.
-    if (containingShadow) {
-      nsTArray<HTMLShadowElement*>& shadowDescendants =
-        containingShadow->ShadowDescendants();
-      shadowDescendants.RemoveElement(this);
-      containingShadow->SetShadowElement(nullptr);
+  nsRefPtr<ShadowRoot> oldContainingShadow = GetContainingShadow();
+
+  if (mIsInsertionPoint && oldContainingShadow) {
+    // Propagate UnbindFromTree call to previous projected shadow
+    // root children.
+    ShadowRoot* projectedShadow = oldContainingShadow->GetOlderShadow();
+    if (projectedShadow) {
+      for (nsIContent* child = projectedShadow->GetFirstChild(); child;
+           child = child->GetNextSibling()) {
+        child->UnbindFromTree(true, false);
+      }
+    }
+  }
 
-      // Find the next shadow insertion point.
-      if (shadowDescendants.Length() > 0 &&
-          !IsInFallbackContent(shadowDescendants[0])) {
-        containingShadow->SetShadowElement(shadowDescendants[0]);
-      }
+  nsGenericHTMLElement::UnbindFromTree(aDeep, aNullParent);
+
+  if (oldContainingShadow && !GetContainingShadow() && mIsInsertionPoint) {
+    nsTArray<HTMLShadowElement*>& shadowDescendants =
+      oldContainingShadow->ShadowDescendants();
+    shadowDescendants.RemoveElement(this);
+    oldContainingShadow->SetShadowElement(nullptr);
 
-      containingShadow->SetInsertionPointChanged();
+    // Find the next shadow insertion point.
+    if (shadowDescendants.Length() > 0 &&
+        !IsInFallbackContent(shadowDescendants[0])) {
+      oldContainingShadow->SetShadowElement(shadowDescendants[0]);
     }
 
+    oldContainingShadow->SetInsertionPointChanged();
+
     mIsInsertionPoint = false;
   }
-
-  nsGenericHTMLElement::UnbindFromTree(aDeep, aNullParent);
 }
 
 void
 HTMLShadowElement::DistributeSingleNode(nsIContent* aContent)
 {
   if (aContent->DestInsertionPoints().Contains(this)) {
     // Node has already been distrbuted this this node,
     // we are done.
--- a/content/html/content/src/HTMLStyleElement.cpp
+++ b/content/html/content/src/HTMLStyleElement.cpp
@@ -151,19 +151,29 @@ HTMLStyleElement::BindToTree(nsIDocument
   nsContentUtils::AddScriptRunner(NS_NewRunnableMethod(this, update));
 
   return rv;  
 }
 
 void
 HTMLStyleElement::UnbindFromTree(bool aDeep, bool aNullParent)
 {
-  nsCOMPtr<nsIDocument> oldDoc = GetCurrentDoc();
+  nsCOMPtr<nsIDocument> oldDoc = GetUncomposedDoc();
+  nsCOMPtr<nsIDocument> oldComposedDoc = GetComposedDoc();
   ShadowRoot* oldShadow = GetContainingShadow();
+
   nsGenericHTMLElement::UnbindFromTree(aDeep, aNullParent);
+
+  if (GetContainingShadow() && !oldComposedDoc) {
+    // The style is in a shadow tree and was already not
+    // in the composed document. Thus the sheet does not
+    // need to be updated.
+    return;
+  }
+
   UpdateStyleSheetInternal(oldDoc, oldShadow);
 }
 
 nsresult
 HTMLStyleElement::SetAttr(int32_t aNameSpaceID, nsIAtom* aName,
                           nsIAtom* aPrefix, const nsAString& aValue,
                           bool aNotify)
 {
--- a/dom/tests/mochitest/webcomponents/mochitest.ini
+++ b/dom/tests/mochitest/webcomponents/mochitest.ini
@@ -4,16 +4,17 @@ support-files =
 
 [test_bug900724.html]
 [test_bug1017896.html]
 [test_content_element.html]
 [test_nested_content_element.html]
 [test_dest_insertion_points.html]
 [test_dest_insertion_points_shadow.html]
 [test_fallback_dest_insertion_points.html]
+[test_detached_style.html]
 [test_dynamic_content_element_matching.html]
 [test_document_register.html]
 [test_document_register_base_queue.html]
 [test_document_register_lifecycle.html]
 [test_document_register_parser.html]
 [test_document_register_stack.html]
 [test_document_shared_registry.html]
 [test_event_dispatch.html]
new file mode 100644
--- /dev/null
+++ b/dom/tests/mochitest/webcomponents/test_detached_style.html
@@ -0,0 +1,25 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=1062578
+-->
+<head>
+  <title>Test for creating style in shadow root of host not in document.</title>
+  <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+  <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<div id="grabme"></div>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1062578">Bug 1062578</a>
+<script>
+var host = document.createElement("div");
+var shadow = host.createShadowRoot();
+shadow.innerHTML = '<style> #inner { height: 200px; } </style><div id="inner">Hello</div>';
+
+grabme.appendChild(host);
+
+var inner = shadow.getElementById("inner");
+is(getComputedStyle(inner, null).getPropertyValue("height"), "200px", "Style in shadow root should take effect.");
+</script>
+</body>
+</html>
--- a/dom/xbl/nsXBLBinding.cpp
+++ b/dom/xbl/nsXBLBinding.cpp
@@ -101,50 +101,55 @@ static const JSClass gPrototypeJSClass =
 };
 
 // Implementation /////////////////////////////////////////////////////////////////
 
 // Constructors/Destructors
 nsXBLBinding::nsXBLBinding(nsXBLPrototypeBinding* aBinding)
   : mMarkedForDeath(false)
   , mUsingContentXBLScope(false)
+  , mIsShadowRootBinding(false)
   , mPrototypeBinding(aBinding)
 {
   NS_ASSERTION(mPrototypeBinding, "Must have a prototype binding!");
   // Grab a ref to the document info so the prototype binding won't die
   NS_ADDREF(mPrototypeBinding->XBLDocumentInfo());
 }
 
 // Constructor used by web components.
 nsXBLBinding::nsXBLBinding(ShadowRoot* aShadowRoot, nsXBLPrototypeBinding* aBinding)
   : mMarkedForDeath(false),
     mUsingContentXBLScope(false),
+    mIsShadowRootBinding(true),
     mPrototypeBinding(aBinding),
     mContent(aShadowRoot)
 {
   NS_ASSERTION(mPrototypeBinding, "Must have a prototype binding!");
   // Grab a ref to the document info so the prototype binding won't die
   NS_ADDREF(mPrototypeBinding->XBLDocumentInfo());
 }
 
 nsXBLBinding::~nsXBLBinding(void)
 {
-  if (mContent) {
+  if (mContent && !mIsShadowRootBinding) {
+    // It is unnecessary to uninstall anonymous content in a shadow tree
+    // because the ShadowRoot itself is a DocumentFragment and does not
+    // need any additional cleanup.
     nsXBLBinding::UninstallAnonymousContent(mContent->OwnerDoc(), mContent);
   }
   nsXBLDocumentInfo* info = mPrototypeBinding->XBLDocumentInfo();
   NS_RELEASE(info);
 }
 
 NS_IMPL_CYCLE_COLLECTION_CLASS(nsXBLBinding)
 
 NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(nsXBLBinding)
   // XXX Probably can't unlink mPrototypeBinding->XBLDocumentInfo(), because
   //     mPrototypeBinding is weak.
-  if (tmp->mContent) {
+  if (tmp->mContent && !tmp->mIsShadowRootBinding) {
     nsXBLBinding::UninstallAnonymousContent(tmp->mContent->OwnerDoc(),
                                             tmp->mContent);
   }
   NS_IMPL_CYCLE_COLLECTION_UNLINK(mContent)
   NS_IMPL_CYCLE_COLLECTION_UNLINK(mNextBinding)
   NS_IMPL_CYCLE_COLLECTION_UNLINK(mDefaultInsertionPoint)
   NS_IMPL_CYCLE_COLLECTION_UNLINK(mInsertionPoints)
   NS_IMPL_CYCLE_COLLECTION_UNLINK(mAnonymousContentList)
@@ -229,23 +234,16 @@ nsXBLBinding::InstallAnonymousContent(ns
 #endif
   }
 }
 
 void
 nsXBLBinding::UninstallAnonymousContent(nsIDocument* aDocument,
                                         nsIContent* aAnonParent)
 {
-  if (aAnonParent->HasFlag(NODE_IS_IN_SHADOW_TREE)) {
-    // It is unnecessary to uninstall anonymous content in a shadow tree
-    // because the ShadowRoot itself is a DocumentFragment and does not
-    // need any additional cleanup.
-    return;
-  }
-
   nsAutoScriptBlocker scriptBlocker;
   // Hold a strong ref while doing this, just in case.
   nsCOMPtr<nsIContent> anonParent = aAnonParent;
 #ifdef MOZ_XUL
   nsCOMPtr<nsIXULDocument> xuldoc =
     do_QueryInterface(aDocument);
 #endif
   for (nsIContent* child = aAnonParent->GetFirstChild();
@@ -806,17 +804,17 @@ nsXBLBinding::ChangeDocument(nsIDocument
     // Then do our ancestors.  This reverses the construction order, so that at
     // all times things are consistent as far as everyone is concerned.
     if (mNextBinding) {
       mNextBinding->ChangeDocument(aOldDocument, aNewDocument);
     }
 
     // Update the anonymous content.
     // XXXbz why not only for style bindings?
-    if (mContent) {
+    if (mContent && !mIsShadowRootBinding) {
       nsXBLBinding::UninstallAnonymousContent(aOldDocument, mContent);
     }
 
     // Now that we've unbound our anonymous content from the tree and updated
     // its binding parent, update the insertion parent for content inserted
     // into our <children> elements.
     if (mDefaultInsertionPoint) {
       UpdateInsertionParent(mDefaultInsertionPoint, mBoundElement);
--- a/dom/xbl/nsXBLBinding.h
+++ b/dom/xbl/nsXBLBinding.h
@@ -157,16 +157,17 @@ public:
   // by this binding.
   nsAnonymousContentList* GetAnonymousNodeList();
 
 // MEMBER VARIABLES
 protected:
 
   bool mMarkedForDeath;
   bool mUsingContentXBLScope;
+  bool mIsShadowRootBinding;
 
   nsXBLPrototypeBinding* mPrototypeBinding; // Weak, but we're holding a ref to the docinfo
   nsCOMPtr<nsIContent> mContent; // Strong. Our anonymous content stays around with us.
   nsRefPtr<nsXBLBinding> mNextBinding; // Strong. The derived binding owns the base class bindings.
 
   nsIContent* mBoundElement; // [WEAK] We have a reference, but we don't own it.
 
   // The <xbl:children> elements that we found in our <xbl:content> when we
deleted file mode 100644
--- a/testing/web-platform/meta/shadow-dom/styles/test-003.html.ini
+++ /dev/null
@@ -1,8 +0,0 @@
-[test-003.html]
-  type: testharness
-  [A_06_00_03_T04]
-    expected: FAIL
-
-  [A_06_00_03_T03]
-    expected: FAIL
-