Skip to content
Permalink
Browse files
REGRESSION(253764@main): Disconnecting a subtree makes :lang pseudo c…
…lass to never match

https://bugs.webkit.org/show_bug.cgi?id=244484

Reviewed by Antti Koivisto.

The bug was caused by removedFromAncestor always clearing the effective lang in ElementRareData
when the element itself doesn't have a lang content attribute specified. This is wrong; We need
to keep the effective lang when an ancestor of "this" element still has a lang content attribute.

This patch also fixes a bug in insertedIntoAncestor that the code to inherit the effective lang
from a parent node was not running when the subtree is not inside a document or a shadow root.
To this end, insertedIntoAncestor has been refactored to match the structure of removedFromAncestor.

Finally, this patch also removes the code in insertedIntoAncestor which was trying to clear
the effective lang when "this" element doesn't have a lang attribute. This is also clearly wrong
as any ancestor with a valid lang attribute should continue to apply the same effective lang.

* LayoutTests/fast/css/lang-pseudo-disconnected-expected.txt: Added.
* LayoutTests/fast/css/lang-pseudo-disconnected.html: Added.
* Source/WebCore/dom/Element.cpp:
(WebCore::Element::insertedIntoAncestor):
(WebCore::Element::removedFromAncestor):

Canonical link: https://commits.webkit.org/253915@main
  • Loading branch information
rniwa committed Aug 29, 2022
1 parent f6c76b3 commit 06479a26a2b932682f098b397b14a05e18a89db3
Show file tree
Hide file tree
Showing 3 changed files with 72 additions and 49 deletions.
@@ -0,0 +1,13 @@
:lang pseudo class should work in a disconnected subtree

On success, you will see a series of "PASS" messages, followed by "TEST COMPLETE".


PASS target.matches(":lang(zh)") is true
PASS target.parentNode.remove(); target.matches(":lang(zh)") is true
PASS target.matches(":lang(fr)") is true
PASS document.body.append(target.parentNode); target.matches(":lang(fr)") is true
PASS successfullyParsed is true

TEST COMPLETE

@@ -0,0 +1,20 @@
<!DOCTYPE html>
<html>
<body>
<div id="container" lang="zh"><span></span></div>
<script src="../../resources/js-test.js"></script>
<script>
description(':lang pseudo class should work in a disconnected subtree');
let target = container.querySelector('span');
shouldBeTrue('target.matches(":lang(zh)")');
shouldBeTrue('target.parentNode.remove(); target.matches(":lang(zh)")');

const anotherContainer = document.createElement('div');
anotherContainer.innerHTML = '<div lang="fr"><span></span></div>';
target = anotherContainer.querySelector('span');
shouldBeTrue('target.matches(":lang(fr)")');
shouldBeTrue('document.body.append(target.parentNode); target.matches(":lang(fr)")');

</script>
</body>
</html>
@@ -2494,64 +2494,53 @@ Node::InsertedIntoAncestorResult Element::insertedIntoAncestor(InsertionType ins
setContainsFullScreenElementOnAncestorsCrossingFrameBoundaries(true);
#endif

auto hostChildElementDidChange = makeScopeExit([&] () {
if (parentNode() == &parentOfInsertedTree) {
if (auto* shadowRoot = parentNode()->shadowRoot())
shadowRoot->hostChildElementDidChange(*this);
if (parentOfInsertedTree.isInTreeScope()) {
bool becomeConnected = insertionType.connectedToDocument;
TreeScope* newScope = &parentOfInsertedTree.treeScope();
auto* newDocument = becomeConnected ? dynamicDowncast<HTMLDocument>(newScope->documentScope()) : nullptr;
if (!insertionType.treeScopeChanged)
newScope = nullptr;

const AtomString& idValue = getIdAttribute();
if (!idValue.isNull()) {
if (newScope)
updateIdForTreeScope(*newScope, nullAtom(), idValue);
if (newDocument)
updateIdForDocument(*newDocument, nullAtom(), idValue, AlwaysUpdateHTMLDocumentNamedItemMaps);
}
});

if (!parentOfInsertedTree.isInTreeScope())
return InsertedIntoAncestorResult::Done;
const AtomString& nameValue = getNameAttribute();
if (!nameValue.isNull()) {
if (newScope)
updateNameForTreeScope(*newScope, nullAtom(), nameValue);
if (newDocument)
updateNameForDocument(*newDocument, nullAtom(), nameValue);
}

bool becomeConnected = insertionType.connectedToDocument;
TreeScope* newScope = &parentOfInsertedTree.treeScope();
auto* newDocument = becomeConnected ? dynamicDowncast<HTMLDocument>(newScope->documentScope()) : nullptr;
if (!insertionType.treeScopeChanged)
newScope = nullptr;
if (becomeConnected) {
if (UNLIKELY(isCustomElementUpgradeCandidate())) {
ASSERT(isConnected());
CustomElementReactionQueue::tryToUpgradeElement(*this);
}
if (UNLIKELY(isDefinedCustomElement()))
CustomElementReactionQueue::enqueueConnectedCallbackIfNeeded(*this);
}

const AtomString& idValue = getIdAttribute();
if (!idValue.isNull()) {
if (newScope)
updateIdForTreeScope(*newScope, nullAtom(), idValue);
if (newDocument)
updateIdForDocument(*newDocument, nullAtom(), idValue, AlwaysUpdateHTMLDocumentNamedItemMaps);
if (shouldAutofocus(*this))
document().topDocument().appendAutofocusCandidate(*this);
}

const AtomString& nameValue = getNameAttribute();
if (!nameValue.isNull()) {
if (newScope)
updateNameForTreeScope(*newScope, nullAtom(), nameValue);
if (newDocument)
updateNameForDocument(*newDocument, nullAtom(), nameValue);
if (parentNode() == &parentOfInsertedTree) {
if (auto* shadowRoot = parentNode()->shadowRoot())
shadowRoot->hostChildElementDidChange(*this);
}

if (becomeConnected) {
if (UNLIKELY(isCustomElementUpgradeCandidate())) {
ASSERT(isConnected());
CustomElementReactionQueue::tryToUpgradeElement(*this);
}
if (UNLIKELY(isDefinedCustomElement()))
CustomElementReactionQueue::enqueueConnectedCallbackIfNeeded(*this);
if (auto* parent = parentOrShadowHostElement(); parent && parent != document().documentElement() && UNLIKELY(parent->hasRareData())) {
auto lang = parent->elementRareData()->effectiveLang();
if (!lang.isNull() && langFromAttribute().isNull())
ensureElementRareData().setEffectiveLang(lang);
}

[&]() {
if (auto* parent = parentOrShadowHostElement(); parent && parent != document().documentElement() && UNLIKELY(parent->hasRareData())) {
auto lang = parent->elementRareData()->effectiveLang();
if (!lang.isNull() && langFromAttribute().isNull()) {
ensureElementRareData().setEffectiveLang(lang);
return;
}
}
if (UNLIKELY(hasRareData())) {
if (!elementRareData()->effectiveLang().isNull() && langFromAttribute().isNull())
ensureElementRareData().setEffectiveLang(nullAtom());
}
}();

if (shouldAutofocus(*this))
document().topDocument().appendAutofocusCandidate(*this);

return InsertedIntoAncestorResult::Done;
}

@@ -2614,7 +2603,8 @@ void Element::removedFromAncestor(RemovalType removalType, ContainerNode& oldPar
ContainerNode::removedFromAncestor(removalType, oldParentOfRemovedTree);

if (UNLIKELY(hasRareData()) && !elementRareData()->effectiveLang().isNull()) {
if (langFromAttribute().isNull())
if (auto* parent = parentOrShadowHostElement(); langFromAttribute().isNull()
&& !(parent && UNLIKELY(parent->hasRareData()) && !parent->elementRareData()->effectiveLang().isNull()))
elementRareData()->setEffectiveLang(nullAtom());
}

0 comments on commit 06479a2

Please sign in to comment.