Bug 1881225 - Consider CDATASections for dir=auto. r=emilio

Differential Revision: https://phabricator.services.mozilla.com/D209697
This commit is contained in:
Vincent Hilla 2024-07-09 08:00:32 +00:00
parent ef27a4a21c
commit c4f8b966cb
10 changed files with 154 additions and 59 deletions

View File

@ -250,9 +250,7 @@ nsresult CharacterData::SetTextInternal(
auto oldDir = Directionality::Unset; auto oldDir = Directionality::Unset;
const bool dirAffectsAncestor = const bool dirAffectsAncestor =
NodeType() == TEXT_NODE && IsText() && TextNodeWillChangeDirection(AsText(), &oldDir, aOffset);
TextNodeWillChangeDirection(static_cast<nsTextNode*>(this), &oldDir,
aOffset);
if (aOffset == 0 && endOffset == textLength) { if (aOffset == 0 && endOffset == textLength) {
// Replacing whole text or old text was empty. // Replacing whole text or old text was empty.
@ -313,8 +311,8 @@ nsresult CharacterData::SetTextInternal(
if (dirAffectsAncestor) { if (dirAffectsAncestor) {
// dirAffectsAncestor being true implies that we have a text node, see // dirAffectsAncestor being true implies that we have a text node, see
// above. // above.
MOZ_ASSERT(NodeType() == TEXT_NODE); MOZ_ASSERT(IsText());
TextNodeChangedDirection(static_cast<nsTextNode*>(this), oldDir, aNotify); TextNodeChangedDirection(AsText(), oldDir, aNotify);
} }
// Notify observers // Notify observers

View File

@ -145,12 +145,12 @@
#include "mozilla/dom/HTMLInputElement.h" #include "mozilla/dom/HTMLInputElement.h"
#include "mozilla/dom/HTMLSlotElement.h" #include "mozilla/dom/HTMLSlotElement.h"
#include "mozilla/dom/ShadowRoot.h" #include "mozilla/dom/ShadowRoot.h"
#include "mozilla/dom/Text.h"
#include "mozilla/dom/UnbindContext.h" #include "mozilla/dom/UnbindContext.h"
#include "mozilla/intl/UnicodeProperties.h" #include "mozilla/intl/UnicodeProperties.h"
#include "nsUnicodeProperties.h" #include "nsUnicodeProperties.h"
#include "nsTextFragment.h" #include "nsTextFragment.h"
#include "nsAttrValue.h" #include "nsAttrValue.h"
#include "nsTextNode.h"
namespace mozilla { namespace mozilla {
@ -158,6 +158,7 @@ using mozilla::dom::Element;
using mozilla::dom::HTMLInputElement; using mozilla::dom::HTMLInputElement;
using mozilla::dom::HTMLSlotElement; using mozilla::dom::HTMLSlotElement;
using mozilla::dom::ShadowRoot; using mozilla::dom::ShadowRoot;
using mozilla::dom::Text;
static nsIContent* GetParentOrHostOrSlot(const nsIContent* aContent) { static nsIContent* GetParentOrHostOrSlot(const nsIContent* aContent) {
if (HTMLSlotElement* slot = aContent->GetAssignedSlot()) { if (HTMLSlotElement* slot = aContent->GetAssignedSlot()) {
@ -228,7 +229,7 @@ inline static bool TextChildrenAffectDirAutoAncestor(nsIContent* aContent) {
aContent->NodeOrAncestorHasDirAuto(); aContent->NodeOrAncestorHasDirAuto();
} }
inline static bool NodeAffectsDirAutoAncestor(nsTextNode* aTextNode) { inline static bool NodeAffectsDirAutoAncestor(Text* aTextNode) {
nsIContent* parent = GetParentOrHostOrSlot(aTextNode); nsIContent* parent = GetParentOrHostOrSlot(aTextNode);
return parent && TextChildrenAffectDirAutoAncestor(parent) && return parent && TextChildrenAffectDirAutoAncestor(parent) &&
!aTextNode->IsInNativeAnonymousSubtree(); !aTextNode->IsInNativeAnonymousSubtree();
@ -292,7 +293,7 @@ static Directionality GetDirectionFromText(const char* aText,
return Directionality::Unset; return Directionality::Unset;
} }
static Directionality GetDirectionFromText(const mozilla::dom::Text* aTextNode, static Directionality GetDirectionFromText(const Text* aTextNode,
uint32_t* aFirstStrong = nullptr) { uint32_t* aFirstStrong = nullptr) {
const nsTextFragment* frag = &aTextNode->TextFragment(); const nsTextFragment* frag = &aTextNode->TextFragment();
if (frag->Is2b()) { if (frag->Is2b()) {
@ -302,7 +303,7 @@ static Directionality GetDirectionFromText(const mozilla::dom::Text* aTextNode,
return GetDirectionFromText(frag->Get1b(), frag->GetLength(), aFirstStrong); return GetDirectionFromText(frag->Get1b(), frag->GetLength(), aFirstStrong);
} }
static nsTextNode* WalkDescendantsAndGetDirectionFromText( static Text* WalkDescendantsAndGetDirectionFromText(
nsINode* aRoot, Directionality* aDirectionality) { nsINode* aRoot, Directionality* aDirectionality) {
nsIContent* child = aRoot->GetFirstChild(); nsIContent* child = aRoot->GetFirstChild();
while (child) { while (child) {
@ -317,8 +318,7 @@ static nsTextNode* WalkDescendantsAndGetDirectionFromText(
const nsTArray<RefPtr<nsINode>>& assignedNodes = slot->AssignedNodes(); const nsTArray<RefPtr<nsINode>>& assignedNodes = slot->AssignedNodes();
for (uint32_t i = 0; i < assignedNodes.Length(); ++i) { for (uint32_t i = 0; i < assignedNodes.Length(); ++i) {
nsIContent* assignedNode = assignedNodes[i]->AsContent(); nsIContent* assignedNode = assignedNodes[i]->AsContent();
if (assignedNode->NodeType() == nsINode::TEXT_NODE) { if (auto* text = Text::FromNode(assignedNode)) {
auto* text = static_cast<nsTextNode*>(assignedNode);
Directionality textNodeDir = GetDirectionFromText(text); Directionality textNodeDir = GetDirectionFromText(text);
if (textNodeDir != Directionality::Unset) { if (textNodeDir != Directionality::Unset) {
*aDirectionality = textNodeDir; *aDirectionality = textNodeDir;
@ -326,8 +326,8 @@ static nsTextNode* WalkDescendantsAndGetDirectionFromText(
} }
} else if (assignedNode->IsElement() && } else if (assignedNode->IsElement() &&
AffectsDirectionOfAncestors(assignedNode->AsElement())) { AffectsDirectionOfAncestors(assignedNode->AsElement())) {
nsTextNode* text = WalkDescendantsAndGetDirectionFromText( Text* text = WalkDescendantsAndGetDirectionFromText(assignedNode,
assignedNode, aDirectionality); aDirectionality);
if (text) { if (text) {
return text; return text;
} }
@ -335,8 +335,7 @@ static nsTextNode* WalkDescendantsAndGetDirectionFromText(
} }
} }
if (child->NodeType() == nsINode::TEXT_NODE) { if (auto* text = Text::FromNode(child)) {
auto* text = static_cast<nsTextNode*>(child);
Directionality textNodeDir = GetDirectionFromText(text); Directionality textNodeDir = GetDirectionFromText(text);
if (textNodeDir != Directionality::Unset) { if (textNodeDir != Directionality::Unset) {
*aDirectionality = textNodeDir; *aDirectionality = textNodeDir;
@ -355,7 +354,7 @@ static nsTextNode* WalkDescendantsAndGetDirectionFromText(
* *
* @return the text node containing the character that determined the direction * @return the text node containing the character that determined the direction
*/ */
static nsTextNode* WalkDescendantsSetDirectionFromText(Element* aElement, static Text* WalkDescendantsSetDirectionFromText(Element* aElement,
bool aNotify) { bool aNotify) {
MOZ_ASSERT(aElement, "Must have an element"); MOZ_ASSERT(aElement, "Must have an element");
MOZ_ASSERT(aElement->HasDirAuto(), "Element must have dir=auto"); MOZ_ASSERT(aElement->HasDirAuto(), "Element must have dir=auto");
@ -368,7 +367,7 @@ static nsTextNode* WalkDescendantsSetDirectionFromText(Element* aElement,
// Check the text in Shadow DOM. // Check the text in Shadow DOM.
if (ShadowRoot* shadowRoot = aElement->GetShadowRoot()) { if (ShadowRoot* shadowRoot = aElement->GetShadowRoot()) {
nsTextNode* text = Text* text =
WalkDescendantsAndGetDirectionFromText(shadowRoot, &textNodeDir); WalkDescendantsAndGetDirectionFromText(shadowRoot, &textNodeDir);
if (text) { if (text) {
aElement->SetDirectionality(textNodeDir, aNotify); aElement->SetDirectionality(textNodeDir, aNotify);
@ -377,8 +376,7 @@ static nsTextNode* WalkDescendantsSetDirectionFromText(Element* aElement,
} }
// Check the text in light DOM. // Check the text in light DOM.
nsTextNode* text = Text* text = WalkDescendantsAndGetDirectionFromText(aElement, &textNodeDir);
WalkDescendantsAndGetDirectionFromText(aElement, &textNodeDir);
if (text) { if (text) {
aElement->SetDirectionality(textNodeDir, aNotify); aElement->SetDirectionality(textNodeDir, aNotify);
return text; return text;
@ -494,8 +492,7 @@ void SetDirectionalityOnDescendants(Element* aElement, Directionality aDir,
static void ResetAutoDirection(Element* aElement, bool aNotify) { static void ResetAutoDirection(Element* aElement, bool aNotify) {
MOZ_ASSERT(aElement->HasDirAuto()); MOZ_ASSERT(aElement->HasDirAuto());
nsTextNode* setByNode = Text* setByNode = WalkDescendantsSetDirectionFromText(aElement, aNotify);
WalkDescendantsSetDirectionFromText(aElement, aNotify);
if (setByNode) { if (setByNode) {
setByNode->SetMaySetDirAuto(); setByNode->SetMaySetDirAuto();
} }
@ -516,7 +513,7 @@ void WalkAncestorsResetAutoDirection(Element* aElement, bool aNotify) {
if (!parentElement || !parentElement->HasDirAuto()) { if (!parentElement || !parentElement->HasDirAuto()) {
continue; continue;
} }
nsTextNode* setByNode = Text* setByNode =
WalkDescendantsSetDirectionFromText(parentElement, aNotify); WalkDescendantsSetDirectionFromText(parentElement, aNotify);
if (setByNode) { if (setByNode) {
setByNode->SetMaySetDirAuto(); setByNode->SetMaySetDirAuto();
@ -638,7 +635,7 @@ void WalkDescendantsSetDirAuto(Element* aElement, bool aNotify) {
SetAncestorHasDirAutoOnDescendants(aElement); SetAncestorHasDirAutoOnDescendants(aElement);
} }
nsTextNode* textNode = WalkDescendantsSetDirectionFromText(aElement, aNotify); Text* textNode = WalkDescendantsSetDirectionFromText(aElement, aNotify);
if (textNode) { if (textNode) {
textNode->SetMaySetDirAuto(); textNode->SetMaySetDirAuto();
} }
@ -716,13 +713,12 @@ static DirAutoElementResult FindDirAutoElementFrom(nsIContent* aContent) {
return {nullptr, false}; return {nullptr, false};
} }
static DirAutoElementResult FindDirAutoElementForText(nsTextNode* aTextNode) { static DirAutoElementResult FindDirAutoElementForText(Text* aTextNode) {
MOZ_ASSERT(aTextNode->NodeType() == nsINode::TEXT_NODE, MOZ_ASSERT(aTextNode->IsText(), "Must be a text node");
"Must be a text node");
return FindDirAutoElementFrom(GetParentOrHostOrSlot(aTextNode)); return FindDirAutoElementFrom(GetParentOrHostOrSlot(aTextNode));
} }
static DirAutoElementResult SetAncestorDirectionIfAuto(nsTextNode* aTextNode, static DirAutoElementResult SetAncestorDirectionIfAuto(Text* aTextNode,
Directionality aDir, Directionality aDir,
bool aNotify = true) { bool aNotify = true) {
auto result = FindDirAutoElementForText(aTextNode); auto result = FindDirAutoElementForText(aTextNode);
@ -741,7 +737,7 @@ static DirAutoElementResult SetAncestorDirectionIfAuto(nsTextNode* aTextNode,
return result; return result;
} }
bool TextNodeWillChangeDirection(nsTextNode* aTextNode, Directionality* aOldDir, bool TextNodeWillChangeDirection(Text* aTextNode, Directionality* aOldDir,
uint32_t aOffset) { uint32_t aOffset) {
if (!NodeAffectsDirAutoAncestor(aTextNode)) { if (!NodeAffectsDirAutoAncestor(aTextNode)) {
return false; return false;
@ -754,7 +750,7 @@ bool TextNodeWillChangeDirection(nsTextNode* aTextNode, Directionality* aOldDir,
return (aOffset <= firstStrong); return (aOffset <= firstStrong);
} }
void TextNodeChangedDirection(nsTextNode* aTextNode, Directionality aOldDir, void TextNodeChangedDirection(Text* aTextNode, Directionality aOldDir,
bool aNotify) { bool aNotify) {
MOZ_ASSERT(NodeAffectsDirAutoAncestor(aTextNode), "Caller should check"); MOZ_ASSERT(NodeAffectsDirAutoAncestor(aTextNode), "Caller should check");
Directionality newDir = GetDirectionFromText(aTextNode); Directionality newDir = GetDirectionFromText(aTextNode);
@ -771,7 +767,7 @@ void TextNodeChangedDirection(nsTextNode* aTextNode, Directionality aOldDir,
} }
} }
void SetDirectionFromNewTextNode(nsTextNode* aTextNode) { void SetDirectionFromNewTextNode(Text* aTextNode) {
if (!NodeAffectsDirAutoAncestor(aTextNode)) { if (!NodeAffectsDirAutoAncestor(aTextNode)) {
return; return;
} }
@ -787,7 +783,7 @@ void SetDirectionFromNewTextNode(nsTextNode* aTextNode) {
} }
} }
void ResetDirectionSetByTextNode(nsTextNode* aTextNode, void ResetDirectionSetByTextNode(Text* aTextNode,
dom::UnbindContext& aContext) { dom::UnbindContext& aContext) {
MOZ_ASSERT(!aTextNode->IsInComposedDoc(), "Should be disconnected already"); MOZ_ASSERT(!aTextNode->IsInComposedDoc(), "Should be disconnected already");
if (!aTextNode->MaySetDirAuto()) { if (!aTextNode->MaySetDirAuto()) {

View File

@ -13,11 +13,11 @@
class nsIContent; class nsIContent;
class nsINode; class nsINode;
class nsAttrValue; class nsAttrValue;
class nsTextNode;
namespace mozilla::dom { namespace mozilla::dom {
class Element; class Element;
class HTMLSlotElement; class HTMLSlotElement;
class Text;
struct UnbindContext; struct UnbindContext;
} // namespace mozilla::dom } // namespace mozilla::dom
@ -115,27 +115,27 @@ void WalkDescendantsClearAncestorDirAuto(nsIContent* aContent);
* *
* @return whether the text node affects the directionality of any element * @return whether the text node affects the directionality of any element
*/ */
bool TextNodeWillChangeDirection(nsTextNode* aTextNode, Directionality* aOldDir, bool TextNodeWillChangeDirection(dom::Text* aTextNode, Directionality* aOldDir,
uint32_t aOffset); uint32_t aOffset);
/** /**
* After the contents of a text node have changed, change the directionality * After the contents of a text node have changed, change the directionality
* of any elements whose directionality is determined by that node * of any elements whose directionality is determined by that node
*/ */
void TextNodeChangedDirection(nsTextNode* aTextNode, Directionality aOldDir, void TextNodeChangedDirection(dom::Text* aTextNode, Directionality aOldDir,
bool aNotify); bool aNotify);
/** /**
* When a text node is appended to an element, find any ancestors with dir=auto * When a text node is appended to an element, find any ancestors with dir=auto
* whose directionality will be determined by the text node * whose directionality will be determined by the text node
*/ */
void SetDirectionFromNewTextNode(nsTextNode* aTextNode); void SetDirectionFromNewTextNode(dom::Text* aTextNode);
/** /**
* When a text node is removed from a document, find any ancestors whose * When a text node is removed from a document, find any ancestors whose
* directionality it determined and redetermine their directionality * directionality it determined and redetermine their directionality
*/ */
void ResetDirectionSetByTextNode(nsTextNode*, dom::UnbindContext&); void ResetDirectionSetByTextNode(dom::Text*, dom::UnbindContext&);
/** /**
* Set the directionality of an element according to the directionality of the * Set the directionality of an element according to the directionality of the

View File

@ -124,6 +124,20 @@ already_AddRefed<Text> Text::Constructor(const GlobalObject& aGlobal,
return window->GetDoc()->CreateTextNode(aData); return window->GetDoc()->CreateTextNode(aData);
} }
nsresult Text::BindToTree(BindContext& aContext, nsINode& aParent) {
nsresult rv = CharacterData::BindToTree(aContext, aParent);
NS_ENSURE_SUCCESS(rv, rv);
SetDirectionFromNewTextNode(this);
return NS_OK;
}
void Text::UnbindFromTree(UnbindContext& aContext) {
CharacterData::UnbindFromTree(aContext);
ResetDirectionSetByTextNode(this, aContext);
}
bool Text::HasTextForTranslation() { bool Text::HasTextForTranslation() {
if (mText.Is2b()) { if (mText.Is2b()) {
// The fragment contains non-8bit characters which means there // The fragment contains non-8bit characters which means there

View File

@ -29,6 +29,9 @@ class Text : public CharacterData {
const nsAString& aData, const nsAString& aData,
ErrorResult& aRv); ErrorResult& aRv);
nsresult BindToTree(BindContext&, nsINode& aParent) override;
void UnbindFromTree(UnbindContext&) override;
/** /**
* Method to see if the text node contains data that is useful * Method to see if the text node contains data that is useful
* for a translation: i.e., it consists of more than just whitespace, * for a translation: i.e., it consists of more than just whitespace,

View File

@ -2071,16 +2071,15 @@ class nsINode : public mozilla::dom::EventTarget {
void ClearHasValidDir() { ClearBoolFlag(NodeHasValidDirAttribute); } void ClearHasValidDir() { ClearBoolFlag(NodeHasValidDirAttribute); }
bool HasValidDir() const { return GetBoolFlag(NodeHasValidDirAttribute); } bool HasValidDir() const { return GetBoolFlag(NodeHasValidDirAttribute); }
void SetMaySetDirAuto() { void SetMaySetDirAuto() {
// FIXME(bug 1881225): dir=auto should probably work on CDATA too. MOZ_ASSERT(IsText());
MOZ_ASSERT(NodeType() == TEXT_NODE);
SetBoolFlag(NodeMaySetDirAuto); SetBoolFlag(NodeMaySetDirAuto);
} }
bool MaySetDirAuto() const { bool MaySetDirAuto() const {
MOZ_ASSERT(NodeType() == TEXT_NODE); MOZ_ASSERT(IsText());
return GetBoolFlag(NodeMaySetDirAuto); return GetBoolFlag(NodeMaySetDirAuto);
} }
void ClearMaySetDirAuto() { void ClearMaySetDirAuto() {
MOZ_ASSERT(NodeType() == TEXT_NODE); MOZ_ASSERT(IsText());
ClearBoolFlag(NodeMaySetDirAuto); ClearBoolFlag(NodeMaySetDirAuto);
} }
void SetAncestorHasDirAuto() { SetBoolFlag(NodeAncestorHasDirAuto); } void SetAncestorHasDirAuto() { SetBoolFlag(NodeAncestorHasDirAuto); }

View File

@ -11,7 +11,6 @@
#include "nsTextNode.h" #include "nsTextNode.h"
#include "mozilla/dom/TextBinding.h" #include "mozilla/dom/TextBinding.h"
#include "nsContentUtils.h" #include "nsContentUtils.h"
#include "mozilla/dom/DirectionalityUtils.h"
#include "mozilla/dom/Document.h" #include "mozilla/dom/Document.h"
#include "nsThreadUtils.h" #include "nsThreadUtils.h"
#include "nsStubMutationObserver.h" #include "nsStubMutationObserver.h"
@ -114,20 +113,6 @@ nsresult nsTextNode::AppendTextForNormalize(const char16_t* aBuffer,
&details); &details);
} }
nsresult nsTextNode::BindToTree(BindContext& aContext, nsINode& aParent) {
nsresult rv = CharacterData::BindToTree(aContext, aParent);
NS_ENSURE_SUCCESS(rv, rv);
SetDirectionFromNewTextNode(this);
return NS_OK;
}
void nsTextNode::UnbindFromTree(UnbindContext& aContext) {
CharacterData::UnbindFromTree(aContext);
ResetDirectionSetByTextNode(this, aContext);
}
#ifdef MOZ_DOM_LIST #ifdef MOZ_DOM_LIST
void nsTextNode::List(FILE* out, int32_t aIndent) const { void nsTextNode::List(FILE* out, int32_t aIndent) const {
int32_t index; int32_t index;

View File

@ -44,9 +44,6 @@ class nsTextNode : public mozilla::dom::Text {
already_AddRefed<CharacterData> CloneDataNode( already_AddRefed<CharacterData> CloneDataNode(
mozilla::dom::NodeInfo* aNodeInfo, bool aCloneText) const override; mozilla::dom::NodeInfo* aNodeInfo, bool aCloneText) const override;
nsresult BindToTree(BindContext&, nsINode& aParent) override;
void UnbindFromTree(UnbindContext&) override;
nsresult AppendTextForNormalize(const char16_t* aBuffer, uint32_t aLength, nsresult AppendTextForNormalize(const char16_t* aBuffer, uint32_t aLength,
bool aNotify, nsIContent* aNextSibling); bool aNotify, nsIContent* aNextSibling);

View File

@ -0,0 +1,71 @@
<!DOCTYPE html>
<head>
<meta charset="utf-8">
<link rel="author" title="Vincent Hilla" href="mailto:vhilla@mozilla.com">
<link rel="help" href="https://html.spec.whatwg.org/multipage/dom.html#the-directionality">
<script src="/resources/testharness.js"></script>
<script src="/resources/testharnessreport.js"></script>
</head>
<body>
<div id="test1" dir="auto">
<![CDATA[foo]]>اختبر
</div>
<iframe src="cdata-iframe.xhtml"></iframe>
<script>
function awaitMessage(msg) {
return new Promise(res => {
function waitAndRemove(e) {
if (e.data != msg)
return;
window.removeEventListener("message", waitAndRemove);
res();
}
window.addEventListener("message", waitAndRemove);
});
}
const subframeLoaded = awaitMessage("subframe-loaded");
async function createXHTMLCase(id) {
await subframeLoaded;
let iframe = document.querySelector("iframe");
iframe.contentWindow.postMessage(id, "*");
await awaitMessage(id);
const doc = iframe.contentDocument;
const div = doc.getElementById(id);
const cdata = div.firstChild;
return [div, cdata];
}
test(function() {
const div = document.getElementById("test1");
assert_true(div.matches(":dir(rtl)"));
}, "Content of CDATA is ignored for dir=auto in html document");
promise_test(async function() {
let [div, cdata] = await createXHTMLCase(1);
assert_true(div.matches(":dir(ltr)"));
}, "Text in CDATASection is considered when determining auto directionality");
promise_test(async function() {
let [div, cdata] = await createXHTMLCase(2);
assert_true(div.matches(":dir(ltr)"));
cdata.remove();
assert_true(div.matches(":dir(rtl)"));
}, "Directionality is updated when removing CDATASection");
promise_test(async function() {
let [div, cdata] = await createXHTMLCase(3);
assert_true(div.matches(":dir(ltr)"));
cdata.textContent = "اختبر";
assert_true(div.matches(":dir(rtl)"));
}, "Directionality is updated when changing text of CDATASection");
</script>
</body>
</html>

View File

@ -0,0 +1,32 @@
<?xml version="1.0"?>
<html xmlns="http://www.w3.org/1999/xhtml">
<body>
<div id="container"></div>
</body>
<script>
function createXHTMLCase(id) {
const container = document.getElementById("container");
const div = document.createElement("div");
div.dir = "auto";
div.id = id;
const cdata = document.createCDATASection("foo");
const text = document.createTextNode("اختبر");
div.appendChild(cdata);
div.appendChild(text);
container.appendChild(div);
return [div, cdata];
}
window.addEventListener("message", (e) => {
createXHTMLCase(e.data);
window.top.postMessage(e.data);
});
window.top.postMessage("subframe-loaded");
</script>
</html>