Bug 1062578 - Call BindToTree/UnbindFromTree on shadow root children when host is bound/unbound from tree. r=smaug

This commit is contained in:
William Chen 2014-09-25 21:53:20 -07:00
parent 155b28578a
commit 4e2fd38b32
12 changed files with 165 additions and 54 deletions

View File

@ -834,6 +834,13 @@ Element::CreateShadowRoot(ErrorResult& aError)
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.
@ -1384,6 +1391,18 @@ Element::BindToTree(nsIDocument* aDocument, nsIContent* aParent,
}
}
// 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.
@ -1462,10 +1481,13 @@ Element::UnbindFromTree(bool aDeep, bool aNullParent)
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
@ -1514,7 +1536,9 @@ Element::UnbindFromTree(bool aDeep, bool aNullParent)
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,
@ -1539,6 +1563,15 @@ Element::UnbindFromTree(bool aDeep, bool aNullParent)
}
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*

View File

@ -1393,6 +1393,10 @@ NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(FragmentOrElement)
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();

View File

@ -582,15 +582,20 @@ nsGenericDOMDataNode::UnbindFromTree(bool aDeep, bool aNullParent)
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());
}
nsDataSlots *slots = GetExistingDataSlots();
if (slots) {
slots->mBindingParent = nullptr;
slots->mContainingShadow = nullptr;
if (aNullParent || !mParent->IsInShadowTree()) {
slots->mContainingShadow = nullptr;
}
}
nsNodeUtils::ParentChainChanged(this);

View File

@ -392,7 +392,15 @@ nsINode::GetComposedDocInternal() const
// 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

View File

@ -208,7 +208,8 @@ nsStyleLinkElement::UpdateStyleSheet(nsICSSLoaderObserver* aObserver,
// 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());
@ -344,7 +345,8 @@ nsStyleLinkElement::DoUpdateStyleSheet(nsIDocument* aOldDocument,
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;
}

View File

@ -55,13 +55,15 @@ HTMLContentElement::BindToTree(nsIDocument* aDocument,
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() &&
@ -85,23 +87,20 @@ HTMLContentElement::BindToTree(nsIDocument* aDocument,
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();
// Remove all the matched nodes now that the
// insertion point is no longer an insertion point.
ClearMatchedNodes();
containingShadow->SetInsertionPointChanged();
}
nsGenericHTMLElement::UnbindFromTree(aDeep, aNullParent);
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

View File

@ -111,13 +111,15 @@ HTMLShadowElement::BindToTree(nsIDocument* aDocument,
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.
@ -141,35 +143,58 @@ HTMLShadowElement::BindToTree(nsIDocument* aDocument,
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();
// Find the next shadow insertion point.
if (shadowDescendants.Length() > 0 &&
!IsInFallbackContent(shadowDescendants[0])) {
containingShadow->SetShadowElement(shadowDescendants[0]);
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);
}
containingShadow->SetInsertionPointChanged();
}
mIsInsertionPoint = false;
}
nsGenericHTMLElement::UnbindFromTree(aDeep, aNullParent);
if (oldContainingShadow && !GetContainingShadow() && mIsInsertionPoint) {
nsTArray<HTMLShadowElement*>& shadowDescendants =
oldContainingShadow->ShadowDescendants();
shadowDescendants.RemoveElement(this);
oldContainingShadow->SetShadowElement(nullptr);
// Find the next shadow insertion point.
if (shadowDescendants.Length() > 0 &&
!IsInFallbackContent(shadowDescendants[0])) {
oldContainingShadow->SetShadowElement(shadowDescendants[0]);
}
oldContainingShadow->SetInsertionPointChanged();
mIsInsertionPoint = false;
}
}
void

View File

@ -156,9 +156,19 @@ HTMLStyleElement::BindToTree(nsIDocument* aDocument, nsIContent* aParent,
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);
}

View File

@ -9,6 +9,7 @@ support-files =
[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]

View File

@ -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>

View File

@ -106,6 +106,7 @@ static const JSClass gPrototypeJSClass = {
nsXBLBinding::nsXBLBinding(nsXBLPrototypeBinding* aBinding)
: mMarkedForDeath(false)
, mUsingContentXBLScope(false)
, mIsShadowRootBinding(false)
, mPrototypeBinding(aBinding)
{
NS_ASSERTION(mPrototypeBinding, "Must have a prototype binding!");
@ -117,6 +118,7 @@ nsXBLBinding::nsXBLBinding(nsXBLPrototypeBinding* aBinding)
nsXBLBinding::nsXBLBinding(ShadowRoot* aShadowRoot, nsXBLPrototypeBinding* aBinding)
: mMarkedForDeath(false),
mUsingContentXBLScope(false),
mIsShadowRootBinding(true),
mPrototypeBinding(aBinding),
mContent(aShadowRoot)
{
@ -127,7 +129,10 @@ nsXBLBinding::nsXBLBinding(ShadowRoot* aShadowRoot, nsXBLPrototypeBinding* aBind
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();
@ -139,7 +144,7 @@ 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);
}
@ -234,13 +239,6 @@ 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;
@ -811,7 +809,7 @@ nsXBLBinding::ChangeDocument(nsIDocument* aOldDocument, nsIDocument* aNewDocumen
// Update the anonymous content.
// XXXbz why not only for style bindings?
if (mContent) {
if (mContent && !mIsShadowRootBinding) {
nsXBLBinding::UninstallAnonymousContent(aOldDocument, mContent);
}

View File

@ -162,6 +162,7 @@ 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.