summaryrefslogtreecommitdiffstats
path: root/dom/base/DirectionalityUtils.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'dom/base/DirectionalityUtils.cpp')
-rw-r--r--dom/base/DirectionalityUtils.cpp745
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);
}