diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-19 01:13:27 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-19 01:13:27 +0000 |
commit | 40a355a42d4a9444dc753c04c6608dade2f06a23 (patch) | |
tree | 871fc667d2de662f171103ce5ec067014ef85e61 /dom/base/DirectionalityUtils.cpp | |
parent | Adding upstream version 124.0.1. (diff) | |
download | firefox-40a355a42d4a9444dc753c04c6608dade2f06a23.tar.xz firefox-40a355a42d4a9444dc753c04c6608dade2f06a23.zip |
Adding upstream version 125.0.1.upstream/125.0.1
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'dom/base/DirectionalityUtils.cpp')
-rw-r--r-- | dom/base/DirectionalityUtils.cpp | 745 |
1 files changed, 185 insertions, 560 deletions
diff --git a/dom/base/DirectionalityUtils.cpp b/dom/base/DirectionalityUtils.cpp index 46229cfbd3..dd427c61b1 100644 --- a/dom/base/DirectionalityUtils.cpp +++ b/dom/base/DirectionalityUtils.cpp @@ -5,8 +5,6 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ /* - Implementation description from https://etherpad.mozilla.org/dir-auto - Static case =========== When we see a new content node with @dir=auto from the parser, we set the @@ -45,23 +43,8 @@ I will call this algorithm "upward propagation". - Each text node should maintain a list of elements which have their - directionality determined by the first strong character of that text node. - This is useful to make dynamic changes more efficient. One way to implement - this is to have a per-document hash table mapping a text node to a set of - elements. I'll call this data structure TextNodeDirectionalityMap. The - algorithm for appending a new text node above needs to update this data - structure. - - *IMPLEMENTATION NOTE* - In practice, the implementation uses two per-node properties: - - dirAutoSetBy, which is set on a node with auto-directionality, and points to - the textnode that contains the strong character which determines the - directionality of the node. - - textNodeDirectionalityMap, which is set on a text node and points to a hash - table listing the nodes whose directionality is determined by the text node. + Each text node keeps a flag if it might determine the directionality of any + ancestor. This is useful to make dynamic changes more efficient. Handling dynamic changes ======================== @@ -90,16 +73,15 @@ (I'll call this the "downward propagation algorithm".) by walking the child subtree in tree order. Note that an element with @dir=auto should not affect other elements in its document with @dir=auto. So there is no need to walk up - the parent chain in this case. TextNodeDirectionalityMap needs to be updated - as appropriate. + the parent chain in this case. 3a. When the dir attribute is set to any valid value on an element that didn't have a valid dir attribute before, this means that any descendant of that element will not affect the directionality of any of its ancestors. So we need - to check whether any text node descendants of the element are listed in - TextNodeDirectionalityMap, and whether the elements whose direction they set - are ancestors of the element. If so, we need to rerun the downward propagation - algorithm for those ancestors. + to check whether any text node descendants of the element can set the dir of + any ancestor, and whether the elements whose direction they set are ancestors + of the element. If so, we need to rerun the downward propagation algorithm for + those ancestors. That's done by OnSetDirAttr. 4. When the dir attribute is changed from auto to something else (including the case where it gets removed) on a textarea or an input element with @@ -118,80 +100,29 @@ should still retain the same flag.) * We resolve the directionality of the element based on the value of the @dir attribute on the element itself or its parent element. - TextNodeDirectionalityMap needs to be updated as appropriate. 5a. When the dir attribute is removed or set to an invalid value on any element (except a bdi element) with the NodeAncestorHasDirAuto flag which previously had a valid dir attribute, it might have a text node descendant that did not previously affect the directionality of any of its ancestors but - should now begin to affect them. We run the following algorithm: - * Walk up the parent chain from the element. - * For any element that appears in the TextNodeDirectionalityMap, remove the - element from the map and rerun the downward propagation algorithm - (see section 3). - * If we reach an element without either of the NodeHasDirAuto or - NodeAncestorHasDirAuto flags, abort the parent chain walk. + should now begin to affect them. We run OnSetDirAttr. 6. When an element with @dir=auto is added to the document, we should handle it similar to the case 2/3 above. 7. When an element with NodeHasDirAuto or NodeAncestorHasDirAuto is removed from the document, we should handle it similar to the case 4/5 above, - except that we don't need to handle anything in the child subtree. We should - also remove all of the occurrences of that node and its descendants from - TextNodeDirectionalityMap. (This is the conceptual description of what needs - to happen but in the implementation UnbindFromTree is going to be called on - all of the descendants so we don't need to descend into the child subtree). + except that we don't need to handle anything in the child subtree. 8. When the contents of a text node is changed either from script or by the - user, we need to run the following algorithm: - * If the change has happened after the first character with strong - directionality in the text node, do nothing. - * If the text node is a child of a bdi, script or style element, do nothing. - * If the text node belongs to a textarea with NodeHasDirAuto, we need to - update the directionality of the textarea. - * Grab a list of elements affected by this text node from - TextNodeDirectionalityMap and re-resolve the directionality of each one of - them based on the new contents of the text node. - * If the text node does not exist in TextNodeDirectionalityMap, and it has the - NodeAncestorHasDirAuto flag set, this could potentially be a text node - which is going to start affecting the directionality of its parent @dir=auto - elements. In this case, we need to fall back to the (potentially expensive) - "upward propagation algorithm". The TextNodeDirectionalityMap data structure - needs to be update during this algorithm. - * If the new contents of the text node do not have any strong characters, and - the old contents used to, and the text node used to exist in - TextNodeDirectionalityMap and it has the NodeAncestorHasDirAuto flag set, - the elements associated with this text node inside TextNodeDirectionalityMap - will now get their directionality from another text node. In this case, for - each element in the list retrieved from TextNodeDirectionalityMap, run the - downward propagation algorithm (section 3), and remove the text node from - TextNodeDirectionalityMap. - - 9. When a new text node is injected into a document, we need to run the - following algorithm: - * If the contents of the text node do not have any characters with strong - direction, do nothing. - * If the text node is a child of a bdi, script or style element, do nothing. - * If the text node is appended to a textarea element with NodeHasDirAuto, we - need to update the directionality of the textarea. - * If the text node has NodeAncestorHasDirAuto, we need to run the "upward - propagation algorithm". The TextNodeDirectionalityMap data structure needs to - be update during this algorithm. - - 10. When a text node is removed from a document, we need to run the following - algorithm: - * If the contents of the text node do not have any characters with strong - direction, do nothing. - * If the text node is a child of a bdi, script or style element, do nothing. - * If the text node is removed from a textarea element with NodeHasDirAuto, - set the directionality to "ltr". (This is what the spec currently says, but - I'm filing a spec bug to get it fixed -- the directionality should depend on - the parent element here.) - * If the text node has NodeAncestorHasDirAuto, we need to look at the list - of elements being affected by this text node from TextNodeDirectionalityMap, - run the "downward propagation algorithm" (section 3) for each one of them, - while updating TextNodeDirectionalityMap along the way. + user, we need to run TextNode{WillChange,Changed}Direction, see inline docs + for details. + + 9. When a new text node is injected into a document, we need to run + SetDirectionFromNewTextNode. + + 10. When a text node is removed from a document, we need to run + ResetDirectionSetByTextNode. 11. If the value of the @dir attribute on a bdi element is changed to an invalid value (or if it's removed), determine the new directionality similar @@ -210,18 +141,16 @@ #include "nsIContent.h" #include "nsIContentInlines.h" #include "mozilla/dom/Document.h" -#include "mozilla/AutoRestore.h" -#include "mozilla/DebugOnly.h" #include "mozilla/dom/Element.h" #include "mozilla/dom/HTMLInputElement.h" #include "mozilla/dom/HTMLSlotElement.h" #include "mozilla/dom/ShadowRoot.h" +#include "mozilla/dom/UnbindContext.h" #include "mozilla/intl/UnicodeProperties.h" #include "nsUnicodeProperties.h" #include "nsTextFragment.h" #include "nsAttrValue.h" #include "nsTextNode.h" -#include "nsCheapSets.h" namespace mozilla { @@ -230,75 +159,53 @@ using mozilla::dom::HTMLInputElement; using mozilla::dom::HTMLSlotElement; using mozilla::dom::ShadowRoot; -static nsIContent* GetParentOrHostOrSlot( - const nsIContent* aContent, bool* aCrossedShadowBoundary = nullptr) { +static nsIContent* GetParentOrHostOrSlot(const nsIContent* aContent) { if (HTMLSlotElement* slot = aContent->GetAssignedSlot()) { - if (aCrossedShadowBoundary) { - *aCrossedShadowBoundary = true; - } return slot; } - - nsIContent* parent = aContent->GetParent(); - if (parent) { + if (nsIContent* parent = aContent->GetParent()) { return parent; } - - const ShadowRoot* sr = ShadowRoot::FromNode(aContent); - if (sr) { - if (aCrossedShadowBoundary) { - *aCrossedShadowBoundary = true; - } - return sr->Host(); + if (const ShadowRoot* sr = ShadowRoot::FromNode(aContent)) { + return sr->GetHost(); } - return nullptr; } -static bool AncestorChainCrossesShadowBoundary(nsIContent* aDescendant, - nsIContent* aAncestor) { - bool crossedShadowBoundary = false; - nsIContent* content = aDescendant; - while (content && content != aAncestor) { - content = GetParentOrHostOrSlot(content, &crossedShadowBoundary); - if (crossedShadowBoundary) { - return true; - } - } - - return false; -} - /** - * Returns true if aElement is one of the elements whose text content should not - * affect its own direction, nor the direction of ancestors with dir=auto. + * Returns true if aElement is one of the elements whose text content should + * affect its own direction, or the direction of ancestors with dir=auto. * * Note that this does not include <bdi>, whose content does affect its own * direction when it has dir=auto (which it has by default), so one needs to - * test for it separately, e.g. with DoesNotAffectDirectionOfAncestors. + * test for it separately, e.g. with AffectsDirectionOfAncestors. * It *does* include textarea, because even if a textarea has dir=auto, it has * unicode-bidi: plaintext and is handled automatically in bidi resolution. * It also includes `input`, because it takes the `dir` value from its value * attribute, instead of the child nodes. */ -static bool DoesNotParticipateInAutoDirection(const nsIContent* aContent) { - mozilla::dom::NodeInfo* nodeInfo = aContent->NodeInfo(); - return ((!aContent->IsHTMLElement() || nodeInfo->Equals(nsGkAtoms::script) || - nodeInfo->Equals(nsGkAtoms::style) || - nodeInfo->Equals(nsGkAtoms::input) || - nodeInfo->Equals(nsGkAtoms::textarea) || - aContent->IsInNativeAnonymousSubtree())) && - !aContent->IsShadowRoot(); +static bool ParticipatesInAutoDirection(const nsIContent* aContent) { + if (aContent->IsInNativeAnonymousSubtree()) { + return false; + } + if (aContent->IsShadowRoot()) { + return true; + } + dom::NodeInfo* ni = aContent->NodeInfo(); + return ni->NamespaceID() == kNameSpaceID_XHTML && + !ni->Equals(nsGkAtoms::script) && !ni->Equals(nsGkAtoms::style) && + !ni->Equals(nsGkAtoms::input) && !ni->Equals(nsGkAtoms::textarea); } /** - * Returns true if aElement is one of the element whose text content should not - * affect the direction of ancestors with dir=auto (though it may affect its own - * direction, e.g. <bdi>) + * Returns true if aElement is one of the element whose text should affect the + * direction of ancestors with dir=auto (though note that even if it returns + * false it may affect its own direction, e.g. <bdi> or dir=auto itself) */ -static bool DoesNotAffectDirectionOfAncestors(const Element* aElement) { - return (DoesNotParticipateInAutoDirection(aElement) || - aElement->IsHTMLElement(nsGkAtoms::bdi) || aElement->HasFixedDir()); +static bool AffectsDirectionOfAncestors(const Element* aElement) { + return ParticipatesInAutoDirection(aElement) && + !aElement->IsHTMLElement(nsGkAtoms::bdi) && !aElement->HasFixedDir() && + !aElement->HasDirAuto(); } /** @@ -318,11 +225,15 @@ static Directionality GetDirectionFromChar(uint32_t ch) { } } -inline static bool NodeAffectsDirAutoAncestor(nsIContent* aTextNode) { +inline static bool TextChildrenAffectDirAutoAncestor(nsIContent* aContent) { + return ParticipatesInAutoDirection(aContent) && + aContent->NodeOrAncestorHasDirAuto(); +} + +inline static bool NodeAffectsDirAutoAncestor(nsTextNode* aTextNode) { nsIContent* parent = GetParentOrHostOrSlot(aTextNode); - return (parent && !DoesNotParticipateInAutoDirection(parent) && - parent->NodeOrAncestorHasDirAuto() && - !aTextNode->IsInNativeAnonymousSubtree()); + return parent && TextChildrenAffectDirAutoAncestor(parent) && + !aTextNode->IsInNativeAnonymousSubtree(); } Directionality GetDirectionFromText(const char16_t* aText, @@ -394,11 +305,11 @@ static Directionality GetDirectionFromText(const mozilla::dom::Text* aTextNode, } static nsTextNode* WalkDescendantsAndGetDirectionFromText( - nsINode* aRoot, nsINode* aSkip, Directionality* aDirectionality) { + nsINode* aRoot, Directionality* aDirectionality) { nsIContent* child = aRoot->GetFirstChild(); while (child) { if ((child->IsElement() && - DoesNotAffectDirectionOfAncestors(child->AsElement())) || + !AffectsDirectionOfAncestors(child->AsElement())) || child->GetAssignedSlot()) { child = child->GetNextNonChildNode(aRoot); continue; @@ -409,19 +320,16 @@ static nsTextNode* WalkDescendantsAndGetDirectionFromText( for (uint32_t i = 0; i < assignedNodes.Length(); ++i) { nsIContent* assignedNode = assignedNodes[i]->AsContent(); if (assignedNode->NodeType() == nsINode::TEXT_NODE) { - auto text = static_cast<nsTextNode*>(assignedNode); - if (assignedNode != aSkip) { - Directionality textNodeDir = GetDirectionFromText(text); - if (textNodeDir != Directionality::Unset) { - *aDirectionality = textNodeDir; - return text; - } + auto* text = static_cast<nsTextNode*>(assignedNode); + Directionality textNodeDir = GetDirectionFromText(text); + if (textNodeDir != Directionality::Unset) { + *aDirectionality = textNodeDir; + return text; } } else if (assignedNode->IsElement() && - !DoesNotAffectDirectionOfAncestors( - assignedNode->AsElement())) { + AffectsDirectionOfAncestors(assignedNode->AsElement())) { nsTextNode* text = WalkDescendantsAndGetDirectionFromText( - assignedNode, aSkip, aDirectionality); + assignedNode, aDirectionality); if (text) { return text; } @@ -429,8 +337,8 @@ static nsTextNode* WalkDescendantsAndGetDirectionFromText( } } - if (child->NodeType() == nsINode::TEXT_NODE && child != aSkip) { - auto text = static_cast<nsTextNode*>(child); + if (child->NodeType() == nsINode::TEXT_NODE) { + auto* text = static_cast<nsTextNode*>(child); Directionality textNodeDir = GetDirectionFromText(text); if (textNodeDir != Directionality::Unset) { *aDirectionality = textNodeDir; @@ -447,17 +355,14 @@ static nsTextNode* WalkDescendantsAndGetDirectionFromText( * Set the directionality of a node with dir=auto as defined in * http://www.whatwg.org/specs/web-apps/current-work/multipage/elements.html#the-directionality * - * @param[in] changedNode If we call this method because the content of a text - * node is about to change, pass in the changed node, so that we - * know not to return it * @return the text node containing the character that determined the direction */ -static nsTextNode* WalkDescendantsSetDirectionFromText( - Element* aElement, bool aNotify, nsINode* aChangedNode = nullptr) { +static nsTextNode* WalkDescendantsSetDirectionFromText(Element* aElement, + bool aNotify) { MOZ_ASSERT(aElement, "Must have an element"); MOZ_ASSERT(aElement->HasDirAuto(), "Element must have dir=auto"); - if (DoesNotParticipateInAutoDirection(aElement)) { + if (!ParticipatesInAutoDirection(aElement)) { return nullptr; } @@ -465,8 +370,8 @@ static nsTextNode* WalkDescendantsSetDirectionFromText( // Check the text in Shadow DOM. if (ShadowRoot* shadowRoot = aElement->GetShadowRoot()) { - nsTextNode* text = WalkDescendantsAndGetDirectionFromText( - shadowRoot, aChangedNode, &textNodeDir); + nsTextNode* text = + WalkDescendantsAndGetDirectionFromText(shadowRoot, &textNodeDir); if (text) { aElement->SetDirectionality(textNodeDir, aNotify); return text; @@ -474,8 +379,8 @@ static nsTextNode* WalkDescendantsSetDirectionFromText( } // Check the text in light DOM. - nsTextNode* text = WalkDescendantsAndGetDirectionFromText( - aElement, aChangedNode, &textNodeDir); + nsTextNode* text = + WalkDescendantsAndGetDirectionFromText(aElement, &textNodeDir); if (text) { aElement->SetDirectionality(textNodeDir, aNotify); return text; @@ -487,194 +392,6 @@ static nsTextNode* WalkDescendantsSetDirectionFromText( return nullptr; } -class nsTextNodeDirectionalityMap { - static void nsTextNodeDirectionalityMapDtor(void* aObject, - nsAtom* aPropertyName, - void* aPropertyValue, - void* aData) { - nsINode* textNode = static_cast<nsINode*>(aObject); - textNode->ClearHasTextNodeDirectionalityMap(); - - nsTextNodeDirectionalityMap* map = - reinterpret_cast<nsTextNodeDirectionalityMap*>(aPropertyValue); - map->EnsureMapIsClear(); - delete map; - } - - public: - explicit nsTextNodeDirectionalityMap(nsINode* aTextNode) - : mElementToBeRemoved(nullptr) { - MOZ_ASSERT(aTextNode, "Null text node"); - MOZ_COUNT_CTOR(nsTextNodeDirectionalityMap); - aTextNode->SetProperty(nsGkAtoms::textNodeDirectionalityMap, this, - nsTextNodeDirectionalityMapDtor); - aTextNode->SetHasTextNodeDirectionalityMap(); - } - - MOZ_COUNTED_DTOR(nsTextNodeDirectionalityMap) - - static void nsTextNodeDirectionalityMapPropertyDestructor( - void* aObject, nsAtom* aProperty, void* aPropertyValue, void* aData) { - nsTextNode* textNode = static_cast<nsTextNode*>(aPropertyValue); - nsTextNodeDirectionalityMap* map = GetDirectionalityMap(textNode); - if (map) { - map->RemoveEntryForProperty(static_cast<Element*>(aObject)); - } - NS_RELEASE(textNode); - } - - void AddEntry(nsTextNode* aTextNode, Element* aElement) { - if (!mElements.Contains(aElement)) { - mElements.Put(aElement); - NS_ADDREF(aTextNode); - aElement->SetProperty(nsGkAtoms::dirAutoSetBy, aTextNode, - nsTextNodeDirectionalityMapPropertyDestructor); - aElement->SetHasDirAutoSet(); - } - } - - void RemoveEntry(nsTextNode* aTextNode, Element* aElement) { - NS_ASSERTION(mElements.Contains(aElement), - "element already removed from map"); - - mElements.Remove(aElement); - aElement->ClearHasDirAutoSet(); - aElement->RemoveProperty(nsGkAtoms::dirAutoSetBy); - } - - void RemoveEntryForProperty(Element* aElement) { - if (mElementToBeRemoved != aElement) { - mElements.Remove(aElement); - } - aElement->ClearHasDirAutoSet(); - } - - private: - nsCheapSet<nsPtrHashKey<Element>> mElements; - // Only used for comparison. - Element* mElementToBeRemoved; - - static nsTextNodeDirectionalityMap* GetDirectionalityMap(nsINode* aTextNode) { - MOZ_ASSERT(aTextNode->NodeType() == nsINode::TEXT_NODE, - "Must be a text node"); - nsTextNodeDirectionalityMap* map = nullptr; - - if (aTextNode->HasTextNodeDirectionalityMap()) { - map = static_cast<nsTextNodeDirectionalityMap*>( - aTextNode->GetProperty(nsGkAtoms::textNodeDirectionalityMap)); - } - - return map; - } - - static nsCheapSetOperator SetNodeDirection(nsPtrHashKey<Element>* aEntry, - void* aDir) { - aEntry->GetKey()->SetDirectionality( - *reinterpret_cast<Directionality*>(aDir), true); - return OpNext; - } - - struct nsTextNodeDirectionalityMapAndElement { - nsTextNodeDirectionalityMap* mMap; - nsCOMPtr<nsINode> mNode; - }; - - static nsCheapSetOperator ResetNodeDirection(nsPtrHashKey<Element>* aEntry, - void* aData) { - // run the downward propagation algorithm - // and remove the text node from the map - nsTextNodeDirectionalityMapAndElement* data = - static_cast<nsTextNodeDirectionalityMapAndElement*>(aData); - nsINode* oldTextNode = data->mNode; - Element* rootNode = aEntry->GetKey(); - nsTextNode* newTextNode = nullptr; - if (rootNode->GetParentNode() && rootNode->HasDirAuto()) { - newTextNode = - WalkDescendantsSetDirectionFromText(rootNode, true, oldTextNode); - } - - AutoRestore<Element*> restore(data->mMap->mElementToBeRemoved); - data->mMap->mElementToBeRemoved = rootNode; - if (newTextNode) { - nsINode* oldDirAutoSetBy = static_cast<nsTextNode*>( - rootNode->GetProperty(nsGkAtoms::dirAutoSetBy)); - if (oldDirAutoSetBy == newTextNode) { - // We're already registered. - return OpNext; - } - nsTextNodeDirectionalityMap::AddEntryToMap(newTextNode, rootNode); - } else { - rootNode->ClearHasDirAutoSet(); - rootNode->RemoveProperty(nsGkAtoms::dirAutoSetBy); - } - return OpRemove; - } - - static nsCheapSetOperator TakeEntries(nsPtrHashKey<Element>* aEntry, - void* aData) { - AutoTArray<Element*, 8>* entries = - static_cast<AutoTArray<Element*, 8>*>(aData); - entries->AppendElement(aEntry->GetKey()); - return OpRemove; - } - - public: - uint32_t UpdateAutoDirection(Directionality aDir) { - return mElements.EnumerateEntries(SetNodeDirection, &aDir); - } - - void ResetAutoDirection(nsINode* aTextNode) { - nsTextNodeDirectionalityMapAndElement data = {this, aTextNode}; - mElements.EnumerateEntries(ResetNodeDirection, &data); - } - - void EnsureMapIsClear() { - AutoRestore<Element*> restore(mElementToBeRemoved); - AutoTArray<Element*, 8> entries; - mElements.EnumerateEntries(TakeEntries, &entries); - for (Element* el : entries) { - el->ClearHasDirAutoSet(); - el->RemoveProperty(nsGkAtoms::dirAutoSetBy); - } - } - - static void RemoveElementFromMap(nsTextNode* aTextNode, Element* aElement) { - if (aTextNode->HasTextNodeDirectionalityMap()) { - GetDirectionalityMap(aTextNode)->RemoveEntry(aTextNode, aElement); - } - } - - static void AddEntryToMap(nsTextNode* aTextNode, Element* aElement) { - nsTextNodeDirectionalityMap* map = GetDirectionalityMap(aTextNode); - if (!map) { - map = new nsTextNodeDirectionalityMap(aTextNode); - } - - map->AddEntry(aTextNode, aElement); - } - - static uint32_t UpdateTextNodeDirection(nsINode* aTextNode, - Directionality aDir) { - MOZ_ASSERT(aTextNode->HasTextNodeDirectionalityMap(), - "Map missing in UpdateTextNodeDirection"); - return GetDirectionalityMap(aTextNode)->UpdateAutoDirection(aDir); - } - - static void ResetTextNodeDirection(nsTextNode* aTextNode, - nsTextNode* aChangedTextNode) { - MOZ_ASSERT(aTextNode->HasTextNodeDirectionalityMap(), - "Map missing in ResetTextNodeDirection"); - RefPtr<nsTextNode> textNode = aTextNode; - GetDirectionalityMap(textNode)->ResetAutoDirection(aChangedTextNode); - } - - static void EnsureMapIsClearFor(nsINode* aTextNode) { - if (aTextNode->HasTextNodeDirectionalityMap()) { - GetDirectionalityMap(aTextNode)->EnsureMapIsClear(); - } - } -}; - Directionality GetParentDirectionality(const Element* aElement) { if (nsIContent* parent = GetParentOrHostOrSlot(aElement)) { if (ShadowRoot* shadow = ShadowRoot::FromNode(parent)) { @@ -729,7 +446,7 @@ static inline bool IsBoundary(const Element& aElement) { static void SetDirectionalityOnDescendantsInternal(nsINode* aNode, Directionality aDir, bool aNotify) { - if (Element* element = Element::FromNode(aNode)) { + if (auto* element = Element::FromNode(aNode)) { if (ShadowRoot* shadow = element->GetShadowRoot()) { SetDirectionalityOnDescendantsInternal(shadow, aDir, aNotify); } @@ -778,66 +495,37 @@ void SetDirectionalityOnDescendants(Element* aElement, Directionality aDir, } static void ResetAutoDirection(Element* aElement, bool aNotify) { - if (aElement->HasDirAutoSet()) { - // If the parent has the DirAutoSet flag, its direction is determined by - // some text node descendant. - // Remove it from the map and reset its direction by the downward - // propagation algorithm - nsTextNode* setByNode = static_cast<nsTextNode*>( - aElement->GetProperty(nsGkAtoms::dirAutoSetBy)); - if (setByNode) { - nsTextNodeDirectionalityMap::RemoveElementFromMap(setByNode, aElement); - } - } - - if (aElement->HasDirAuto()) { - nsTextNode* setByNode = - WalkDescendantsSetDirectionFromText(aElement, aNotify); - if (setByNode) { - nsTextNodeDirectionalityMap::AddEntryToMap(setByNode, aElement); - } - SetDirectionalityOnDescendants(aElement, aElement->GetDirectionality(), - aNotify); + MOZ_ASSERT(aElement->HasDirAuto()); + nsTextNode* setByNode = + WalkDescendantsSetDirectionFromText(aElement, aNotify); + if (setByNode) { + setByNode->SetMaySetDirAuto(); } + SetDirectionalityOnDescendants(aElement, aElement->GetDirectionality(), + aNotify); } /** - * Walk the parent chain of a text node whose dir attribute has been removed and - * reset the direction of any of its ancestors which have dir=auto and whose - * directionality is determined by a text node descendant. + * Walk the parent chain of a text node whose dir attribute has been removed or + * added and reset the direction of any of its ancestors which have dir=auto and + * whose directionality is determined by a text node descendant. */ void WalkAncestorsResetAutoDirection(Element* aElement, bool aNotify) { - nsTextNode* setByNode; - nsIContent* parent = GetParentOrHostOrSlot(aElement); - while (parent && parent->NodeOrAncestorHasDirAuto()) { - if (!parent->IsElement()) { - parent = GetParentOrHostOrSlot(parent); + for (nsIContent* parent = GetParentOrHostOrSlot(aElement); + parent && parent->NodeOrAncestorHasDirAuto(); + parent = GetParentOrHostOrSlot(parent)) { + auto* parentElement = Element::FromNode(*parent); + if (!parentElement || !parentElement->HasDirAuto()) { continue; } - - Element* parentElement = parent->AsElement(); - if (parent->HasDirAutoSet()) { - // If the parent has the DirAutoSet flag, its direction is determined by - // some text node descendant. - // Remove it from the map and reset its direction by the downward - // propagation algorithm - setByNode = static_cast<nsTextNode*>( - parent->GetProperty(nsGkAtoms::dirAutoSetBy)); - if (setByNode) { - nsTextNodeDirectionalityMap::RemoveElementFromMap(setByNode, - parentElement); - } - } - if (parentElement->HasDirAuto()) { - setByNode = WalkDescendantsSetDirectionFromText(parentElement, aNotify); - if (setByNode) { - nsTextNodeDirectionalityMap::AddEntryToMap(setByNode, parentElement); - } - SetDirectionalityOnDescendants( - parentElement, parentElement->GetDirectionality(), aNotify); - break; + nsTextNode* setByNode = + WalkDescendantsSetDirectionFromText(parentElement, aNotify); + if (setByNode) { + setByNode->SetMaySetDirAuto(); } - parent = GetParentOrHostOrSlot(parent); + SetDirectionalityOnDescendants(parentElement, + parentElement->GetDirectionality(), aNotify); + break; } } @@ -901,26 +589,6 @@ void SlotStateChanged(HTMLSlotElement* aSlot, bool aAllAssignedNodesChanged) { } } -void WalkDescendantsResetAutoDirection(Element* aElement) { - nsIContent* child = aElement->GetFirstChild(); - while (child) { - if (child->IsElement() && child->AsElement()->HasDirAuto()) { - child = child->GetNextNonChildNode(aElement); - continue; - } - - if (child->NodeType() == nsINode::TEXT_NODE && - child->HasTextNodeDirectionalityMap()) { - nsTextNodeDirectionalityMap::ResetTextNodeDirection( - static_cast<nsTextNode*>(child), nullptr); - // Don't call nsTextNodeDirectionalityMap::EnsureMapIsClearFor(child) - // since ResetTextNodeDirection may have kept elements in child's - // DirectionalityMap. - } - child = child->GetNextNode(aElement); - } -} - static void SetAncestorHasDirAutoOnDescendants(nsINode* aRoot); static void MaybeSetAncestorHasDirAutoOnShadowDOM(nsINode* aNode) { @@ -938,7 +606,7 @@ static void SetAncestorHasDirAutoOnDescendants(nsINode* aRoot) { nsIContent* child = aRoot->GetFirstChild(); while (child) { if (child->IsElement() && - DoesNotAffectDirectionOfAncestors(child->AsElement())) { + !AffectsDirectionOfAncestors(child->AsElement())) { child = child->GetNextNonChildNode(aRoot); continue; } @@ -961,20 +629,20 @@ static void SetAncestorHasDirAutoOnDescendants(nsINode* aRoot) { } void WalkDescendantsSetDirAuto(Element* aElement, bool aNotify) { - // Only test for DoesNotParticipateInAutoDirection -- in other words, if - // aElement is a <bdi> which is having its dir attribute set to auto (or + // Only test for ParticipatesInAutoDirection -- in other words, if aElement is + // a <bdi> which is having its dir attribute set to auto (or // removed or set to an invalid value, which are equivalent to dir=auto for // <bdi>, we *do* want to set AncestorHasDirAuto on its descendants, unlike // in SetDirOnBind where we don't propagate AncestorHasDirAuto to a <bdi> // being bound to an existing node with dir=auto. - if (!DoesNotParticipateInAutoDirection(aElement) && + if (ParticipatesInAutoDirection(aElement) && !aElement->AncestorHasDirAuto()) { SetAncestorHasDirAutoOnDescendants(aElement); } nsTextNode* textNode = WalkDescendantsSetDirectionFromText(aElement, aNotify); if (textNode) { - nsTextNodeDirectionalityMap::AddEntryToMap(textNode, aElement); + textNode->SetMaySetDirAuto(); } } @@ -1022,98 +690,67 @@ void WalkDescendantsClearAncestorDirAuto(nsIContent* aContent) { } } -void SetAncestorDirectionIfAuto(nsTextNode* aTextNode, Directionality aDir, - bool aNotify = true) { - MOZ_ASSERT(aTextNode->NodeType() == nsINode::TEXT_NODE, - "Must be a text node"); +struct DirAutoElementResult { + Element* mElement = nullptr; + // This is false when we hit the top of the ancestor chain without finding a + // dir=auto element or an element with a fixed direction. This is useful when + // processing node removals, since we might need to look at the subtree we're + // removing from. + bool mAnswerIsDefinitive = false; +}; - bool crossedShadowBoundary = false; - nsIContent* parent = GetParentOrHostOrSlot(aTextNode, &crossedShadowBoundary); - while (parent && parent->NodeOrAncestorHasDirAuto()) { - if (!parent->IsElement()) { - parent = GetParentOrHostOrSlot(parent, &crossedShadowBoundary); +static DirAutoElementResult FindDirAutoElementFrom(nsIContent* aContent) { + for (nsIContent* parent = aContent; + parent && parent->NodeOrAncestorHasDirAuto(); + parent = GetParentOrHostOrSlot(parent)) { + auto* parentElement = Element::FromNode(*parent); + if (!parentElement) { continue; } - - Element* parentElement = parent->AsElement(); - if (DoesNotParticipateInAutoDirection(parentElement) || + if (!ParticipatesInAutoDirection(parentElement) || parentElement->HasFixedDir()) { - break; + return {nullptr, true}; } - if (parentElement->HasDirAuto()) { - bool resetDirection = false; - nsTextNode* directionWasSetByTextNode = static_cast<nsTextNode*>( - parent->GetProperty(nsGkAtoms::dirAutoSetBy)); - - if (!parent->HasDirAutoSet()) { - // Fast path if parent's direction is not yet set by any descendant - MOZ_ASSERT(!directionWasSetByTextNode, - "dirAutoSetBy property should be null"); - resetDirection = true; - } else { - // If parent's direction is already set, we need to know if - // aTextNode is before or after the text node that had set it. - // We will walk parent's descendants in tree order starting from - // aTextNode to optimize for the most common case where text nodes are - // being appended to tree. - if (!directionWasSetByTextNode) { - resetDirection = true; - } else if (directionWasSetByTextNode != aTextNode) { - if (crossedShadowBoundary || AncestorChainCrossesShadowBoundary( - directionWasSetByTextNode, parent)) { - // Need to take the slow path when the path from either the old or - // new text node to the dir=auto element crosses shadow boundary. - ResetAutoDirection(parentElement, aNotify); - return; - } - - nsIContent* child = aTextNode->GetNextNode(parent); - while (child) { - if (child->IsElement() && - DoesNotAffectDirectionOfAncestors(child->AsElement())) { - child = child->GetNextNonChildNode(parent); - continue; - } - - if (child == directionWasSetByTextNode) { - // we found the node that set the element's direction after our - // text node, so we need to reset the direction - resetDirection = true; - break; - } - - child = child->GetNextNode(parent); - } - } - } - - if (resetDirection) { - if (directionWasSetByTextNode) { - nsTextNodeDirectionalityMap::RemoveElementFromMap( - directionWasSetByTextNode, parentElement); - } - parentElement->SetDirectionality(aDir, aNotify); - nsTextNodeDirectionalityMap::AddEntryToMap(aTextNode, parentElement); - SetDirectionalityOnDescendants(parentElement, aDir, aNotify); - } + return {parentElement, true}; + } + } + return {nullptr, false}; +} - // Since we found an element with dir=auto, we can stop walking the - // parent chain: none of its ancestors will have their direction set by - // any of its descendants. - return; +static DirAutoElementResult FindDirAutoElementForText(nsTextNode* aTextNode) { + MOZ_ASSERT(aTextNode->NodeType() == nsINode::TEXT_NODE, + "Must be a text node"); + return FindDirAutoElementFrom(GetParentOrHostOrSlot(aTextNode)); +} + +static DirAutoElementResult SetAncestorDirectionIfAuto(nsTextNode* aTextNode, + Directionality aDir, + bool aNotify = true) { + auto result = FindDirAutoElementForText(aTextNode); + if (Element* parentElement = result.mElement) { + if (parentElement->GetDirectionality() == aDir) { + // If we know that the directionality is already correct, we don't need to + // reset it. But we might be responsible for the directionality of + // parentElement. + MOZ_ASSERT(aDir != Directionality::Unset); + aTextNode->SetMaySetDirAuto(); + } else { + // Otherwise recompute the directionality of parentElement. + ResetAutoDirection(parentElement, aNotify); } - parent = GetParentOrHostOrSlot(parent, &crossedShadowBoundary); } + return result; } bool TextNodeWillChangeDirection(nsTextNode* aTextNode, Directionality* aOldDir, uint32_t aOffset) { if (!NodeAffectsDirAutoAncestor(aTextNode)) { - nsTextNodeDirectionalityMap::EnsureMapIsClearFor(aTextNode); return false; } + // If the change has happened after the first character with strong + // directionality in the text node, do nothing. uint32_t firstStrong; *aOldDir = GetDirectionFromText(aTextNode, &firstStrong); return (aOffset <= firstStrong); @@ -1121,29 +758,17 @@ bool TextNodeWillChangeDirection(nsTextNode* aTextNode, Directionality* aOldDir, void TextNodeChangedDirection(nsTextNode* aTextNode, Directionality aOldDir, bool aNotify) { + MOZ_ASSERT(NodeAffectsDirAutoAncestor(aTextNode), "Caller should check"); Directionality newDir = GetDirectionFromText(aTextNode); - if (newDir == Directionality::Unset) { - if (aOldDir != Directionality::Unset && - aTextNode->HasTextNodeDirectionalityMap()) { - // This node used to have a strong directional character but no - // longer does. ResetTextNodeDirection() will re-resolve the - // directionality of any elements whose directionality was - // determined by this node. - nsTextNodeDirectionalityMap::ResetTextNodeDirection(aTextNode, aTextNode); - } - } else { - // This node has a strong directional character. If it has a - // TextNodeDirectionalityMap property, it already determines the - // directionality of some element(s), so call UpdateTextNodeDirection to - // reresolve their directionality. If it has no map, or if - // UpdateTextNodeDirection returns zero, indicating that the map is - // empty, call SetAncestorDirectionIfAuto to find ancestor elements - // which should have their directionality determined by this node. - if (aTextNode->HasTextNodeDirectionalityMap() && - nsTextNodeDirectionalityMap::UpdateTextNodeDirection(aTextNode, - newDir)) { - return; - } + if (newDir == aOldDir) { + return; + } + // If the old directionality is Unset, we might determine now dir=auto + // ancestor direction now, even if we don't have the MaySetDirAuto flag. + // + // Otherwise we used to have a strong directionality and either no longer + // does, or it changed. We might need to reset the direction. + if (aOldDir == Directionality::Unset || aTextNode->MaySetDirAuto()) { SetAncestorDirectionIfAuto(aTextNode, newDir, aNotify); } } @@ -1164,17 +789,39 @@ void SetDirectionFromNewTextNode(nsTextNode* aTextNode) { } } -void ResetDirectionSetByTextNode(nsTextNode* aTextNode) { - if (!NodeAffectsDirAutoAncestor(aTextNode)) { - nsTextNodeDirectionalityMap::EnsureMapIsClearFor(aTextNode); +void ResetDirectionSetByTextNode(nsTextNode* aTextNode, + dom::UnbindContext& aContext) { + MOZ_ASSERT(!aTextNode->IsInComposedDoc(), "Should be disconnected already"); + if (!aTextNode->MaySetDirAuto()) { + return; + } + auto result = FindDirAutoElementForText(aTextNode); + if (result.mAnswerIsDefinitive) { + // The dir=auto element is in our (now detached) subtree. We're done, as + // nothing really changed for our purposes. + return; + } + MOZ_ASSERT(!result.mElement); + // The dir=auto element might have been on the element we're unbinding from. + // In any case, this text node is clearly no longer what determines its + // directionality. + aTextNode->ClearMaySetDirAuto(); + auto* unboundFrom = + nsIContent::FromNodeOrNull(aContext.GetOriginalSubtreeParent()); + if (!unboundFrom || !TextChildrenAffectDirAutoAncestor(unboundFrom)) { return; } Directionality dir = GetDirectionFromText(aTextNode); - if (dir != Directionality::Unset && - aTextNode->HasTextNodeDirectionalityMap()) { - nsTextNodeDirectionalityMap::ResetTextNodeDirection(aTextNode, aTextNode); + if (dir == Directionality::Unset) { + return; + } + + result = FindDirAutoElementFrom(unboundFrom); + if (!result.mElement || result.mElement->GetDirectionality() != dir) { + return; } + ResetAutoDirection(result.mElement, /* aNotify = */ true); } void SetDirectionalityFromValue(Element* aElement, const nsAString& value, @@ -1192,26 +839,15 @@ void SetDirectionalityFromValue(Element* aElement, const nsAString& value, void OnSetDirAttr(Element* aElement, const nsAttrValue* aNewValue, bool hadValidDir, bool hadDirAuto, bool aNotify) { - if (aElement->IsHTMLElement(nsGkAtoms::input) || - aElement->IsHTMLElement(nsGkAtoms::textarea)) { + if (aElement->IsAnyOfHTMLElements(nsGkAtoms::input, nsGkAtoms::textarea)) { return; } if (aElement->AncestorHasDirAuto()) { - if (!hadValidDir) { - // The element is a descendant of an element with dir = auto, is - // having its dir attribute set, and previously didn't have a valid dir - // attribute. - // Check whether any of its text node descendants determine the - // direction of any of its ancestors, and redetermine their direction - WalkDescendantsResetAutoDirection(aElement); - } else if (!aElement->HasValidDir()) { - // The element is a descendant of an element with dir = auto and is - // having its dir attribute removed or set to an invalid value. - // Reset the direction of any of its ancestors whose direction is - // determined by a text node descendant - WalkAncestorsResetAutoDirection(aElement, aNotify); - } + // The element is a descendant of an element with dir = auto, is having its + // dir attribute changed. Reset the direction of any of its ancestors whose + // direction might be determined by a text node descendant + WalkAncestorsResetAutoDirection(aElement, aNotify); } else if (hadDirAuto && !aElement->HasDirAuto()) { // The element isn't a descendant of an element with dir = auto, and is // having its dir attribute set to something other than auto. @@ -1231,11 +867,6 @@ void OnSetDirAttr(Element* aElement, const nsAttrValue* aNewValue, if (aElement->HasDirAuto()) { WalkDescendantsSetDirAuto(aElement, aNotify); } else { - if (aElement->HasDirAutoSet()) { - nsTextNode* setByNode = static_cast<nsTextNode*>( - aElement->GetProperty(nsGkAtoms::dirAutoSetBy)); - nsTextNodeDirectionalityMap::RemoveElementFromMap(setByNode, aElement); - } SetDirectionalityOnDescendants( aElement, RecomputeDirectionality(aElement, aNotify), aNotify); } @@ -1244,7 +875,7 @@ void OnSetDirAttr(Element* aElement, const nsAttrValue* aNewValue, void SetDirOnBind(Element* aElement, nsIContent* aParent) { // Set the AncestorHasDirAuto flag, unless this element shouldn't affect // ancestors that have dir=auto - if (!DoesNotParticipateInAutoDirection(aElement) && + if (ParticipatesInAutoDirection(aElement) && !aElement->IsHTMLElement(nsGkAtoms::bdi) && aParent && aParent->NodeOrAncestorHasDirAuto()) { aElement->SetAncestorHasDirAuto(); @@ -1265,12 +896,6 @@ void SetDirOnBind(Element* aElement, nsIContent* aParent) { } void ResetDir(Element* aElement) { - if (aElement->HasDirAutoSet()) { - nsTextNode* setByNode = static_cast<nsTextNode*>( - aElement->GetProperty(nsGkAtoms::dirAutoSetBy)); - nsTextNodeDirectionalityMap::RemoveElementFromMap(setByNode, aElement); - } - if (!aElement->HasDirAuto()) { RecomputeDirectionality(aElement, false); } |