content/base/src/DirectionalityUtils.cpp

Thu, 15 Jan 2015 21:03:48 +0100

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Thu, 15 Jan 2015 21:03:48 +0100
branch
TOR_BUG_9701
changeset 11
deefc01c0e14
permissions
-rw-r--r--

Integrate friendly tips from Tor colleagues to make (or not) 4.5 alpha 3;
This includes removal of overloaded (but unused) methods, and addition of
a overlooked call to DataStruct::SetData(nsISupports, uint32_t, bool.)

michael@0 1 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
michael@0 2 /* vim: set ts=2 sw=2 et tw=78: */
michael@0 3 /* This Source Code Form is subject to the terms of the Mozilla Public
michael@0 4 * License, v. 2.0. If a copy of the MPL was not distributed with this
michael@0 5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
michael@0 6
michael@0 7 /*
michael@0 8 Implementation description from https://etherpad.mozilla.org/dir-auto
michael@0 9
michael@0 10 Static case
michael@0 11 ===========
michael@0 12 When we see a new content node with @dir=auto from the parser, we set the
michael@0 13 NodeHasDirAuto flag on the node. We won't have enough information to
michael@0 14 decide the directionality of the node at this point.
michael@0 15
michael@0 16 When we bind a new content node to the document, if its parent has either of
michael@0 17 the NodeAncestorHasDirAuto or NodeHasDirAuto flags, we set the
michael@0 18 NodeAncestorHasDirAuto flag on the node.
michael@0 19
michael@0 20 When a new input with @type=text/search/tel/url/email and @dir=auto is added
michael@0 21 from the parser, we resolve the directionality based on its @value.
michael@0 22
michael@0 23 When a new text node with non-neutral content is appended to a textarea
michael@0 24 element with NodeHasDirAuto, if the directionality of the textarea element
michael@0 25 is still unresolved, it is resolved based on the value of the text node.
michael@0 26 Elements with unresolved directionality behave as LTR.
michael@0 27
michael@0 28 When a new text node with non-neutral content is appended to an element that
michael@0 29 is not a textarea but has either of the NodeAncestorHasDirAuto or
michael@0 30 NodeHasDirAuto flags, we walk up the parent chain while the
michael@0 31 NodeAncestorHasDirAuto flag is present, and when we reach an element with
michael@0 32 NodeHasDirAuto and no resolved directionality, we resolve the directionality
michael@0 33 based on the contents of the text node and cease walking the parent chain.
michael@0 34 Note that we should ignore elements with NodeHasDirAuto with resolved
michael@0 35 directionality, so that the second text node in this example tree doesn't
michael@0 36 affect the directionality of the div:
michael@0 37
michael@0 38 <div dir=auto>
michael@0 39 <span>foo</span>
michael@0 40 <span>بار</span>
michael@0 41 </div>
michael@0 42
michael@0 43 The parent chain walk will be aborted if we hit a script or style element, or
michael@0 44 if we hit an element with @dir=ltr or @dir=rtl.
michael@0 45
michael@0 46 I will call this algorithm "upward propagation".
michael@0 47
michael@0 48 Each text node should maintain a list of elements which have their
michael@0 49 directionality determined by the first strong character of that text node.
michael@0 50 This is useful to make dynamic changes more efficient. One way to implement
michael@0 51 this is to have a per-document hash table mapping a text node to a set of
michael@0 52 elements. I'll call this data structure TextNodeDirectionalityMap. The
michael@0 53 algorithm for appending a new text node above needs to update this data
michael@0 54 structure.
michael@0 55
michael@0 56 *IMPLEMENTATION NOTE*
michael@0 57 In practice, the implementation uses two per-node properties:
michael@0 58
michael@0 59 dirAutoSetBy, which is set on a node with auto-directionality, and points to
michael@0 60 the textnode that contains the strong character which determines the
michael@0 61 directionality of the node.
michael@0 62
michael@0 63 textNodeDirectionalityMap, which is set on a text node and points to a hash
michael@0 64 table listing the nodes whose directionality is determined by the text node.
michael@0 65
michael@0 66 Handling dynamic changes
michael@0 67 ========================
michael@0 68
michael@0 69 We need to handle the following cases:
michael@0 70
michael@0 71 1. When the value of an input element with @type=text/search/tel/url/email is
michael@0 72 changed, if it has NodeHasDirAuto, we update the resolved directionality.
michael@0 73
michael@0 74 2. When the dir attribute is changed from something else (including the case
michael@0 75 where it doesn't exist) to auto on a textarea or an input element with
michael@0 76 @type=text/search/tel/url/email, we set the NodeHasDirAuto flag and resolve
michael@0 77 the directionality based on the value of the element.
michael@0 78
michael@0 79 3. When the dir attribute is changed from something else (including the case
michael@0 80 where it doesn't exist) to auto on any element except case 1 above and the bdi
michael@0 81 element, we run the following algorithm:
michael@0 82 * We set the NodeHasDirAuto flag.
michael@0 83 * If the element doesn't have the NodeAncestorHasDirAuto flag, we set the
michael@0 84 NodeAncestorHasDirAuto flag on all of its child nodes. (Note that if the
michael@0 85 element does have NodeAncestorHasDirAuto, all of its children should
michael@0 86 already have this flag too. We can assert this in debug builds.)
michael@0 87 * To resolve the directionality of the element, we run the algorithm explained
michael@0 88 in http://www.whatwg.org/specs/web-apps/current-work/multipage/elements.html#the-dir-attribute
michael@0 89 (I'll call this the "downward propagation algorithm".) by walking the child
michael@0 90 subtree in tree order. Note that an element with @dir=auto should not affect
michael@0 91 other elements in its document with @dir=auto. So there is no need to walk up
michael@0 92 the parent chain in this case. TextNodeDirectionalityMap needs to be updated
michael@0 93 as appropriate.
michael@0 94
michael@0 95 3a. When the dir attribute is set to any valid value on an element that didn't
michael@0 96 have a valid dir attribute before, this means that any descendant of that
michael@0 97 element will not affect the directionality of any of its ancestors. So we need
michael@0 98 to check whether any text node descendants of the element are listed in
michael@0 99 TextNodeDirectionalityMap, and whether the elements whose direction they set
michael@0 100 are ancestors of the element. If so, we need to rerun the downward propagation
michael@0 101 algorithm for those ancestors.
michael@0 102
michael@0 103 4. When the dir attribute is changed from auto to something else (including
michael@0 104 the case where it gets removed) on a textarea or an input element with
michael@0 105 @type=text/search/tel/url/email, we unset the NodeHasDirAuto flag and
michael@0 106 resolve the directionality based on the directionality of the value of the @dir
michael@0 107 attribute on element itself or its parent element.
michael@0 108
michael@0 109 5. When the dir attribute is changed from auto to something else (including the
michael@0 110 case where it gets removed) on any element except case 4 above and the bdi
michael@0 111 element, we run the following algorithm:
michael@0 112 * We unset the NodeHasDirAuto flag.
michael@0 113 * If the element does not have the NodeAncestorHasDirAuto flag, we unset
michael@0 114 the NodeAncestorHasDirAuto flag on all of its child nodes, except those
michael@0 115 who are a descendant of another element with NodeHasDirAuto. (Note that if
michael@0 116 the element has the NodeAncestorHasDirAuto flag, all of its child nodes
michael@0 117 should still retain the same flag.)
michael@0 118 * We resolve the directionality of the element based on the value of the @dir
michael@0 119 attribute on the element itself or its parent element.
michael@0 120 TextNodeDirectionalityMap needs to be updated as appropriate.
michael@0 121
michael@0 122 5a. When the dir attribute is removed or set to an invalid value on any
michael@0 123 element (except a bdi element) with the NodeAncestorHasDirAuto flag which
michael@0 124 previously had a valid dir attribute, it might have a text node descendant that
michael@0 125 did not previously affect the directionality of any of its ancestors but should
michael@0 126 now begin to affect them.
michael@0 127 We run the following algorithm:
michael@0 128 * Walk up the parent chain from the element.
michael@0 129 * For any element that appears in the TextNodeDirectionalityMap, remove the
michael@0 130 element from the map and rerun the downward propagation algorithm
michael@0 131 (see section 3).
michael@0 132 * If we reach an element without either of the NodeHasDirAuto or
michael@0 133 NodeAncestorHasDirAuto flags, abort the parent chain walk.
michael@0 134
michael@0 135 6. When an element with @dir=auto is added to the document, we should handle it
michael@0 136 similar to the case 2/3 above.
michael@0 137
michael@0 138 7. When an element with NodeHasDirAuto or NodeAncestorHasDirAuto is
michael@0 139 removed from the document, we should handle it similar to the case 4/5 above,
michael@0 140 except that we don't need to handle anything in the child subtree. We should
michael@0 141 also remove all of the occurrences of that node and its descendants from
michael@0 142 TextNodeDirectionalityMap. (This is the conceptual description of what needs to
michael@0 143 happen but in the implementation UnbindFromTree is going to be called on all of
michael@0 144 the descendants so we don't need to descend into the child subtree).
michael@0 145
michael@0 146 8. When the contents of a text node is changed either from script or by the
michael@0 147 user, we need to run the following algorithm:
michael@0 148 * If the change has happened after the first character with strong
michael@0 149 directionality in the text node, do nothing.
michael@0 150 * If the text node is a child of a bdi, script or style element, do nothing.
michael@0 151 * If the text node belongs to a textarea with NodeHasDirAuto, we need to
michael@0 152 update the directionality of the textarea.
michael@0 153 * Grab a list of elements affected by this text node from
michael@0 154 TextNodeDirectionalityMap and re-resolve the directionality of each one of them
michael@0 155 based on the new contents of the text node.
michael@0 156 * If the text node does not exist in TextNodeDirectionalityMap, and it has the
michael@0 157 NodeAncestorHasDirAuto flag set, this could potentially be a text node
michael@0 158 which is going to start affecting the directionality of its parent @dir=auto
michael@0 159 elements. In this case, we need to fall back to the (potentially expensive)
michael@0 160 "upward propagation algorithm". The TextNodeDirectionalityMap data structure
michael@0 161 needs to be update during this algorithm.
michael@0 162 * If the new contents of the text node do not have any strong characters, and
michael@0 163 the old contents used to, and the text node used to exist in
michael@0 164 TextNodeDirectionalityMap and it has the NodeAncestorHasDirAuto flag set,
michael@0 165 the elements associated with this text node inside TextNodeDirectionalityMap
michael@0 166 will now get their directionality from another text node. In this case, for
michael@0 167 each element in the list retrieved from TextNodeDirectionalityMap, run the
michael@0 168 downward propagation algorithm (section 3), and remove the text node from
michael@0 169 TextNodeDirectionalityMap.
michael@0 170
michael@0 171 9. When a new text node is injected into a document, we need to run the
michael@0 172 following algorithm:
michael@0 173 * If the contents of the text node do not have any characters with strong
michael@0 174 direction, do nothing.
michael@0 175 * If the text node is a child of a bdi, script or style element, do nothing.
michael@0 176 * If the text node is appended to a textarea element with NodeHasDirAuto, we
michael@0 177 need to update the directionality of the textarea.
michael@0 178 * If the text node has NodeAncestorHasDirAuto, we need to run the "upward
michael@0 179 propagation algorithm". The TextNodeDirectionalityMap data structure needs to
michael@0 180 be update during this algorithm.
michael@0 181
michael@0 182 10. When a text node is removed from a document, we need to run the following
michael@0 183 algorithm:
michael@0 184 * If the contents of the text node do not have any characters with strong
michael@0 185 direction, do nothing.
michael@0 186 * If the text node is a child of a bdi, script or style element, do nothing.
michael@0 187 * If the text node is removed from a textarea element with NodeHasDirAuto,
michael@0 188 set the directionality to "ltr". (This is what the spec currently says, but I'm
michael@0 189 filing a spec bug to get it fixed -- the directionality should depend on the
michael@0 190 parent element here.)
michael@0 191 * If the text node has NodeAncestorHasDirAuto, we need to look at the list
michael@0 192 of elements being affected by this text node from TextNodeDirectionalityMap,
michael@0 193 run the "downward propagation algorithm" (section 3) for each one of them,
michael@0 194 while updating TextNodeDirectionalityMap along the way.
michael@0 195
michael@0 196 11. If the value of the @dir attribute on a bdi element is changed to an
michael@0 197 invalid value (or if it's removed), determine the new directionality similar
michael@0 198 to the case 3 above.
michael@0 199
michael@0 200 == Implemention Notes ==
michael@0 201 When a new node gets bound to the tree, the BindToTree function gets called.
michael@0 202 The reverse case is UnbindFromTree.
michael@0 203 When the contents of a text node change, nsGenericDOMDataNode::SetTextInternal
michael@0 204 gets called.
michael@0 205 */
michael@0 206
michael@0 207 #include "mozilla/dom/DirectionalityUtils.h"
michael@0 208
michael@0 209 #include "nsINode.h"
michael@0 210 #include "nsIContent.h"
michael@0 211 #include "nsIDocument.h"
michael@0 212 #include "mozilla/DebugOnly.h"
michael@0 213 #include "mozilla/dom/Element.h"
michael@0 214 #include "nsIDOMHTMLDocument.h"
michael@0 215 #include "nsUnicodeProperties.h"
michael@0 216 #include "nsTextFragment.h"
michael@0 217 #include "nsAttrValue.h"
michael@0 218 #include "nsTextNode.h"
michael@0 219 #include "nsCheapSets.h"
michael@0 220
michael@0 221 namespace mozilla {
michael@0 222
michael@0 223 using mozilla::dom::Element;
michael@0 224
michael@0 225 /**
michael@0 226 * Returns true if aElement is one of the elements whose text content should not
michael@0 227 * affect its own direction, nor the direction of ancestors with dir=auto.
michael@0 228 *
michael@0 229 * Note that this does not include <bdi>, whose content does affect its own
michael@0 230 * direction when it has dir=auto (which it has by default), so one needs to
michael@0 231 * test for it separately, e.g. with DoesNotAffectDirectionOfAncestors.
michael@0 232 * It *does* include textarea, because even if a textarea has dir=auto, it has
michael@0 233 * unicode-bidi: plaintext and is handled automatically in bidi resolution.
michael@0 234 */
michael@0 235 static bool
michael@0 236 DoesNotParticipateInAutoDirection(const Element* aElement)
michael@0 237 {
michael@0 238 nsINodeInfo* nodeInfo = aElement->NodeInfo();
michael@0 239 return (!aElement->IsHTML() ||
michael@0 240 nodeInfo->Equals(nsGkAtoms::script) ||
michael@0 241 nodeInfo->Equals(nsGkAtoms::style) ||
michael@0 242 nodeInfo->Equals(nsGkAtoms::textarea) ||
michael@0 243 aElement->IsInAnonymousSubtree());
michael@0 244 }
michael@0 245
michael@0 246 static inline bool
michael@0 247 IsBdiWithoutDirAuto(const Element* aElement)
michael@0 248 {
michael@0 249 // We are testing for bdi elements without explicit dir="auto", so we can't
michael@0 250 // use the HasDirAuto() flag, since that will return true for bdi element with
michael@0 251 // no dir attribute or an invalid dir attribute
michael@0 252 return (aElement->IsHTML(nsGkAtoms::bdi) &&
michael@0 253 (!aElement->HasValidDir() || aElement->HasFixedDir()));
michael@0 254 }
michael@0 255
michael@0 256 /**
michael@0 257 * Returns true if aElement is one of the element whose text content should not
michael@0 258 * affect the direction of ancestors with dir=auto (though it may affect its own
michael@0 259 * direction, e.g. <bdi>)
michael@0 260 */
michael@0 261 static bool
michael@0 262 DoesNotAffectDirectionOfAncestors(const Element* aElement)
michael@0 263 {
michael@0 264 return (DoesNotParticipateInAutoDirection(aElement) ||
michael@0 265 IsBdiWithoutDirAuto(aElement) ||
michael@0 266 aElement->HasFixedDir());
michael@0 267 }
michael@0 268
michael@0 269 /**
michael@0 270 * Returns the directionality of a Unicode character
michael@0 271 */
michael@0 272 static Directionality
michael@0 273 GetDirectionFromChar(uint32_t ch)
michael@0 274 {
michael@0 275 switch(mozilla::unicode::GetBidiCat(ch)) {
michael@0 276 case eCharType_RightToLeft:
michael@0 277 case eCharType_RightToLeftArabic:
michael@0 278 return eDir_RTL;
michael@0 279
michael@0 280 case eCharType_LeftToRight:
michael@0 281 return eDir_LTR;
michael@0 282
michael@0 283 default:
michael@0 284 return eDir_NotSet;
michael@0 285 }
michael@0 286 }
michael@0 287
michael@0 288 inline static bool NodeAffectsDirAutoAncestor(nsINode* aTextNode)
michael@0 289 {
michael@0 290 Element* parent = aTextNode->GetParentElement();
michael@0 291 return (parent &&
michael@0 292 !DoesNotParticipateInAutoDirection(parent) &&
michael@0 293 parent->NodeOrAncestorHasDirAuto());
michael@0 294 }
michael@0 295
michael@0 296 /**
michael@0 297 * Various methods for returning the directionality of a string using the
michael@0 298 * first-strong algorithm defined in http://unicode.org/reports/tr9/#P2
michael@0 299 *
michael@0 300 * @param[out] aFirstStrong the offset to the first character in the string with
michael@0 301 * strong directionality, or UINT32_MAX if there is none (return
michael@0 302 value is eDir_NotSet).
michael@0 303 * @return the directionality of the string
michael@0 304 */
michael@0 305 static Directionality
michael@0 306 GetDirectionFromText(const char16_t* aText, const uint32_t aLength,
michael@0 307 uint32_t* aFirstStrong = nullptr)
michael@0 308 {
michael@0 309 const char16_t* start = aText;
michael@0 310 const char16_t* end = aText + aLength;
michael@0 311
michael@0 312 while (start < end) {
michael@0 313 uint32_t current = start - aText;
michael@0 314 uint32_t ch = *start++;
michael@0 315
michael@0 316 if (NS_IS_HIGH_SURROGATE(ch) &&
michael@0 317 start < end &&
michael@0 318 NS_IS_LOW_SURROGATE(*start)) {
michael@0 319 ch = SURROGATE_TO_UCS4(ch, *start++);
michael@0 320 current++;
michael@0 321 }
michael@0 322
michael@0 323 // Just ignore lone surrogates
michael@0 324 if (!IS_SURROGATE(ch)) {
michael@0 325 Directionality dir = GetDirectionFromChar(ch);
michael@0 326 if (dir != eDir_NotSet) {
michael@0 327 if (aFirstStrong) {
michael@0 328 *aFirstStrong = current;
michael@0 329 }
michael@0 330 return dir;
michael@0 331 }
michael@0 332 }
michael@0 333 }
michael@0 334
michael@0 335 if (aFirstStrong) {
michael@0 336 *aFirstStrong = UINT32_MAX;
michael@0 337 }
michael@0 338 return eDir_NotSet;
michael@0 339 }
michael@0 340
michael@0 341 static Directionality
michael@0 342 GetDirectionFromText(const char* aText, const uint32_t aLength,
michael@0 343 uint32_t* aFirstStrong = nullptr)
michael@0 344 {
michael@0 345 const char* start = aText;
michael@0 346 const char* end = aText + aLength;
michael@0 347
michael@0 348 while (start < end) {
michael@0 349 uint32_t current = start - aText;
michael@0 350 unsigned char ch = (unsigned char)*start++;
michael@0 351
michael@0 352 Directionality dir = GetDirectionFromChar(ch);
michael@0 353 if (dir != eDir_NotSet) {
michael@0 354 if (aFirstStrong) {
michael@0 355 *aFirstStrong = current;
michael@0 356 }
michael@0 357 return dir;
michael@0 358 }
michael@0 359 }
michael@0 360
michael@0 361 if (aFirstStrong) {
michael@0 362 *aFirstStrong = UINT32_MAX;
michael@0 363 }
michael@0 364 return eDir_NotSet;
michael@0 365 }
michael@0 366
michael@0 367 static Directionality
michael@0 368 GetDirectionFromText(const nsTextFragment* aFrag,
michael@0 369 uint32_t* aFirstStrong = nullptr)
michael@0 370 {
michael@0 371 if (aFrag->Is2b()) {
michael@0 372 return GetDirectionFromText(aFrag->Get2b(), aFrag->GetLength(),
michael@0 373 aFirstStrong);
michael@0 374 }
michael@0 375
michael@0 376 return GetDirectionFromText(aFrag->Get1b(), aFrag->GetLength(),
michael@0 377 aFirstStrong);
michael@0 378 }
michael@0 379
michael@0 380 /**
michael@0 381 * Set the directionality of a node with dir=auto as defined in
michael@0 382 * http://www.whatwg.org/specs/web-apps/current-work/multipage/elements.html#the-directionality
michael@0 383 *
michael@0 384 * @param[in] changedNode If we call this method because the content of a text
michael@0 385 * node is about to change, pass in the changed node, so that we
michael@0 386 * know not to return it
michael@0 387 * @return the text node containing the character that determined the direction
michael@0 388 */
michael@0 389 static nsINode*
michael@0 390 WalkDescendantsSetDirectionFromText(Element* aElement, bool aNotify = true,
michael@0 391 nsINode* aChangedNode = nullptr)
michael@0 392 {
michael@0 393 MOZ_ASSERT(aElement, "Must have an element");
michael@0 394 MOZ_ASSERT(aElement->HasDirAuto(), "Element must have dir=auto");
michael@0 395
michael@0 396 if (DoesNotParticipateInAutoDirection(aElement)) {
michael@0 397 return nullptr;
michael@0 398 }
michael@0 399
michael@0 400 nsIContent* child = aElement->GetFirstChild();
michael@0 401 while (child) {
michael@0 402 if (child->IsElement() &&
michael@0 403 DoesNotAffectDirectionOfAncestors(child->AsElement())) {
michael@0 404 child = child->GetNextNonChildNode(aElement);
michael@0 405 continue;
michael@0 406 }
michael@0 407
michael@0 408 if (child->NodeType() == nsIDOMNode::TEXT_NODE &&
michael@0 409 child != aChangedNode) {
michael@0 410 Directionality textNodeDir = GetDirectionFromText(child->GetText());
michael@0 411 if (textNodeDir != eDir_NotSet) {
michael@0 412 // We found a descendant text node with strong directional characters.
michael@0 413 // Set the directionality of aElement to the corresponding value.
michael@0 414 aElement->SetDirectionality(textNodeDir, aNotify);
michael@0 415 return child;
michael@0 416 }
michael@0 417 }
michael@0 418 child = child->GetNextNode(aElement);
michael@0 419 }
michael@0 420
michael@0 421 // We walked all the descendants without finding a text node with strong
michael@0 422 // directional characters. Set the directionality to LTR
michael@0 423 aElement->SetDirectionality(eDir_LTR, aNotify);
michael@0 424 return nullptr;
michael@0 425 }
michael@0 426
michael@0 427 class nsTextNodeDirectionalityMap
michael@0 428 {
michael@0 429 static void
michael@0 430 nsTextNodeDirectionalityMapDtor(void *aObject, nsIAtom* aPropertyName,
michael@0 431 void *aPropertyValue, void* aData)
michael@0 432 {
michael@0 433 nsINode* textNode = static_cast<nsINode * >(aObject);
michael@0 434 textNode->ClearHasTextNodeDirectionalityMap();
michael@0 435
michael@0 436 nsTextNodeDirectionalityMap* map =
michael@0 437 reinterpret_cast<nsTextNodeDirectionalityMap * >(aPropertyValue);
michael@0 438 map->EnsureMapIsClear(textNode);
michael@0 439 delete map;
michael@0 440 }
michael@0 441
michael@0 442 public:
michael@0 443 nsTextNodeDirectionalityMap(nsINode* aTextNode)
michael@0 444 {
michael@0 445 MOZ_ASSERT(aTextNode, "Null text node");
michael@0 446 MOZ_COUNT_CTOR(nsTextNodeDirectionalityMap);
michael@0 447 aTextNode->SetProperty(nsGkAtoms::textNodeDirectionalityMap, this,
michael@0 448 nsTextNodeDirectionalityMapDtor);
michael@0 449 aTextNode->SetHasTextNodeDirectionalityMap();
michael@0 450 }
michael@0 451
michael@0 452 ~nsTextNodeDirectionalityMap()
michael@0 453 {
michael@0 454 MOZ_COUNT_DTOR(nsTextNodeDirectionalityMap);
michael@0 455 }
michael@0 456
michael@0 457 void AddEntry(nsINode* aTextNode, Element* aElement)
michael@0 458 {
michael@0 459 if (!mElements.Contains(aElement)) {
michael@0 460 mElements.Put(aElement);
michael@0 461 aElement->SetProperty(nsGkAtoms::dirAutoSetBy, aTextNode);
michael@0 462 aElement->SetHasDirAutoSet();
michael@0 463 }
michael@0 464 }
michael@0 465
michael@0 466 void RemoveEntry(nsINode* aTextNode, Element* aElement)
michael@0 467 {
michael@0 468 NS_ASSERTION(mElements.Contains(aElement),
michael@0 469 "element already removed from map");
michael@0 470
michael@0 471 mElements.Remove(aElement);
michael@0 472 aElement->ClearHasDirAutoSet();
michael@0 473 aElement->UnsetProperty(nsGkAtoms::dirAutoSetBy);
michael@0 474 }
michael@0 475
michael@0 476 private:
michael@0 477 nsCheapSet<nsPtrHashKey<Element> > mElements;
michael@0 478
michael@0 479 static nsTextNodeDirectionalityMap* GetDirectionalityMap(nsINode* aTextNode)
michael@0 480 {
michael@0 481 MOZ_ASSERT(aTextNode->NodeType() == nsIDOMNode::TEXT_NODE,
michael@0 482 "Must be a text node");
michael@0 483 nsTextNodeDirectionalityMap* map = nullptr;
michael@0 484
michael@0 485 if (aTextNode->HasTextNodeDirectionalityMap()) {
michael@0 486 map = static_cast<nsTextNodeDirectionalityMap * >
michael@0 487 (aTextNode->GetProperty(nsGkAtoms::textNodeDirectionalityMap));
michael@0 488 }
michael@0 489
michael@0 490 return map;
michael@0 491 }
michael@0 492
michael@0 493 static PLDHashOperator SetNodeDirection(nsPtrHashKey<Element>* aEntry, void* aDir)
michael@0 494 {
michael@0 495 MOZ_ASSERT(aEntry->GetKey()->IsElement(), "Must be an Element");
michael@0 496 aEntry->GetKey()->SetDirectionality(*reinterpret_cast<Directionality*>(aDir),
michael@0 497 true);
michael@0 498 return PL_DHASH_NEXT;
michael@0 499 }
michael@0 500
michael@0 501 static PLDHashOperator ResetNodeDirection(nsPtrHashKey<Element>* aEntry, void* aData)
michael@0 502 {
michael@0 503 MOZ_ASSERT(aEntry->GetKey()->IsElement(), "Must be an Element");
michael@0 504 // run the downward propagation algorithm
michael@0 505 // and remove the text node from the map
michael@0 506 nsINode* oldTextNode = static_cast<Element*>(aData);
michael@0 507 Element* rootNode = aEntry->GetKey();
michael@0 508 nsINode* newTextNode = nullptr;
michael@0 509 if (oldTextNode && rootNode->HasDirAuto()) {
michael@0 510 newTextNode = WalkDescendantsSetDirectionFromText(rootNode, true,
michael@0 511 oldTextNode);
michael@0 512 }
michael@0 513 if (newTextNode) {
michael@0 514 nsTextNodeDirectionalityMap::AddEntryToMap(newTextNode, rootNode);
michael@0 515 } else {
michael@0 516 rootNode->ClearHasDirAutoSet();
michael@0 517 rootNode->UnsetProperty(nsGkAtoms::dirAutoSetBy);
michael@0 518 }
michael@0 519 return PL_DHASH_REMOVE;
michael@0 520 }
michael@0 521
michael@0 522 static PLDHashOperator ClearEntry(nsPtrHashKey<Element>* aEntry, void* aData)
michael@0 523 {
michael@0 524 Element* rootNode = aEntry->GetKey();
michael@0 525 rootNode->ClearHasDirAutoSet();
michael@0 526 rootNode->UnsetProperty(nsGkAtoms::dirAutoSetBy);
michael@0 527 return PL_DHASH_REMOVE;
michael@0 528 }
michael@0 529
michael@0 530 public:
michael@0 531 void UpdateAutoDirection(Directionality aDir)
michael@0 532 {
michael@0 533 mElements.EnumerateEntries(SetNodeDirection, &aDir);
michael@0 534 }
michael@0 535
michael@0 536 void ClearAutoDirection()
michael@0 537 {
michael@0 538 mElements.EnumerateEntries(ResetNodeDirection, nullptr);
michael@0 539 }
michael@0 540
michael@0 541 void ResetAutoDirection(nsINode* aTextNode)
michael@0 542 {
michael@0 543 mElements.EnumerateEntries(ResetNodeDirection, aTextNode);
michael@0 544 }
michael@0 545
michael@0 546 void EnsureMapIsClear(nsINode* aTextNode)
michael@0 547 {
michael@0 548 DebugOnly<uint32_t> clearedEntries =
michael@0 549 mElements.EnumerateEntries(ClearEntry, aTextNode);
michael@0 550 MOZ_ASSERT(clearedEntries == 0, "Map should be empty already");
michael@0 551 }
michael@0 552
michael@0 553 static void RemoveElementFromMap(nsINode* aTextNode, Element* aElement)
michael@0 554 {
michael@0 555 if (aTextNode->HasTextNodeDirectionalityMap()) {
michael@0 556 GetDirectionalityMap(aTextNode)->RemoveEntry(aTextNode, aElement);
michael@0 557 }
michael@0 558 }
michael@0 559
michael@0 560 static void AddEntryToMap(nsINode* aTextNode, Element* aElement)
michael@0 561 {
michael@0 562 nsTextNodeDirectionalityMap* map = GetDirectionalityMap(aTextNode);
michael@0 563 if (!map) {
michael@0 564 map = new nsTextNodeDirectionalityMap(aTextNode);
michael@0 565 }
michael@0 566
michael@0 567 map->AddEntry(aTextNode, aElement);
michael@0 568 }
michael@0 569
michael@0 570 static void UpdateTextNodeDirection(nsINode* aTextNode, Directionality aDir)
michael@0 571 {
michael@0 572 MOZ_ASSERT(aTextNode->HasTextNodeDirectionalityMap(),
michael@0 573 "Map missing in UpdateTextNodeDirection");
michael@0 574 GetDirectionalityMap(aTextNode)->UpdateAutoDirection(aDir);
michael@0 575 }
michael@0 576
michael@0 577 static void ClearTextNodeDirection(nsINode* aTextNode)
michael@0 578 {
michael@0 579 MOZ_ASSERT(aTextNode->HasTextNodeDirectionalityMap(),
michael@0 580 "Map missing in ResetTextNodeDirection");
michael@0 581 GetDirectionalityMap(aTextNode)->ClearAutoDirection();
michael@0 582 }
michael@0 583
michael@0 584 static void ResetTextNodeDirection(nsINode* aTextNode)
michael@0 585 {
michael@0 586 MOZ_ASSERT(aTextNode->HasTextNodeDirectionalityMap(),
michael@0 587 "Map missing in ResetTextNodeDirection");
michael@0 588 GetDirectionalityMap(aTextNode)->ResetAutoDirection(aTextNode);
michael@0 589 }
michael@0 590
michael@0 591 static void EnsureMapIsClearFor(nsINode* aTextNode)
michael@0 592 {
michael@0 593 if (aTextNode->HasTextNodeDirectionalityMap()) {
michael@0 594 GetDirectionalityMap(aTextNode)->EnsureMapIsClear(aTextNode);
michael@0 595 }
michael@0 596 }
michael@0 597 };
michael@0 598
michael@0 599 Directionality
michael@0 600 RecomputeDirectionality(Element* aElement, bool aNotify)
michael@0 601 {
michael@0 602 MOZ_ASSERT(!aElement->HasDirAuto(),
michael@0 603 "RecomputeDirectionality called with dir=auto");
michael@0 604
michael@0 605 Directionality dir = eDir_LTR;
michael@0 606
michael@0 607 if (aElement->HasValidDir()) {
michael@0 608 dir = aElement->GetDirectionality();
michael@0 609 } else {
michael@0 610 Element* parent = aElement->GetParentElement();
michael@0 611 if (parent) {
michael@0 612 // If the element doesn't have an explicit dir attribute with a valid
michael@0 613 // value, the directionality is the same as the parent element (but
michael@0 614 // don't propagate the parent directionality if it isn't set yet).
michael@0 615 Directionality parentDir = parent->GetDirectionality();
michael@0 616 if (parentDir != eDir_NotSet) {
michael@0 617 dir = parentDir;
michael@0 618 }
michael@0 619 } else {
michael@0 620 // If there is no parent element and no dir attribute, the directionality
michael@0 621 // is LTR.
michael@0 622 dir = eDir_LTR;
michael@0 623 }
michael@0 624
michael@0 625 aElement->SetDirectionality(dir, aNotify);
michael@0 626 }
michael@0 627 return dir;
michael@0 628 }
michael@0 629
michael@0 630 void
michael@0 631 SetDirectionalityOnDescendants(Element* aElement, Directionality aDir,
michael@0 632 bool aNotify)
michael@0 633 {
michael@0 634 for (nsIContent* child = aElement->GetFirstChild(); child; ) {
michael@0 635 if (!child->IsElement()) {
michael@0 636 child = child->GetNextNode(aElement);
michael@0 637 continue;
michael@0 638 }
michael@0 639
michael@0 640 Element* element = child->AsElement();
michael@0 641 if (element->HasValidDir() || element->HasDirAuto()) {
michael@0 642 child = child->GetNextNonChildNode(aElement);
michael@0 643 continue;
michael@0 644 }
michael@0 645 element->SetDirectionality(aDir, aNotify);
michael@0 646 child = child->GetNextNode(aElement);
michael@0 647 }
michael@0 648 }
michael@0 649
michael@0 650 /**
michael@0 651 * Walk the parent chain of a text node whose dir attribute has been removed and
michael@0 652 * reset the direction of any of its ancestors which have dir=auto and whose
michael@0 653 * directionality is determined by a text node descendant.
michael@0 654 */
michael@0 655 void
michael@0 656 WalkAncestorsResetAutoDirection(Element* aElement, bool aNotify)
michael@0 657 {
michael@0 658 nsINode* setByNode;
michael@0 659 Element* parent = aElement->GetParentElement();
michael@0 660
michael@0 661 while (parent && parent->NodeOrAncestorHasDirAuto()) {
michael@0 662 if (parent->HasDirAutoSet()) {
michael@0 663 // If the parent has the DirAutoSet flag, its direction is determined by
michael@0 664 // some text node descendant.
michael@0 665 // Remove it from the map and reset its direction by the downward
michael@0 666 // propagation algorithm
michael@0 667 setByNode =
michael@0 668 static_cast<nsINode*>(parent->GetProperty(nsGkAtoms::dirAutoSetBy));
michael@0 669 if (setByNode) {
michael@0 670 nsTextNodeDirectionalityMap::RemoveElementFromMap(setByNode, parent);
michael@0 671 }
michael@0 672 }
michael@0 673 if (parent->HasDirAuto()) {
michael@0 674 setByNode = WalkDescendantsSetDirectionFromText(parent, aNotify);
michael@0 675 if (setByNode) {
michael@0 676 nsTextNodeDirectionalityMap::AddEntryToMap(setByNode, parent);
michael@0 677 }
michael@0 678 break;
michael@0 679 }
michael@0 680 parent = parent->GetParentElement();
michael@0 681 }
michael@0 682 }
michael@0 683
michael@0 684 void
michael@0 685 WalkDescendantsResetAutoDirection(Element* aElement)
michael@0 686 {
michael@0 687 nsIContent* child = aElement->GetFirstChild();
michael@0 688 while (child) {
michael@0 689 if (child->HasDirAuto()) {
michael@0 690 child = child->GetNextNonChildNode(aElement);
michael@0 691 continue;
michael@0 692 }
michael@0 693
michael@0 694 if (child->HasTextNodeDirectionalityMap()) {
michael@0 695 nsTextNodeDirectionalityMap::ResetTextNodeDirection(child);
michael@0 696 nsTextNodeDirectionalityMap::EnsureMapIsClearFor(child);
michael@0 697 }
michael@0 698 child = child->GetNextNode(aElement);
michael@0 699 }
michael@0 700 }
michael@0 701
michael@0 702 void
michael@0 703 WalkDescendantsSetDirAuto(Element* aElement, bool aNotify)
michael@0 704 {
michael@0 705 // Only test for DoesNotParticipateInAutoDirection -- in other words, if
michael@0 706 // aElement is a <bdi> which is having its dir attribute set to auto (or
michael@0 707 // removed or set to an invalid value, which are equivalent to dir=auto for
michael@0 708 // <bdi>, we *do* want to set AncestorHasDirAuto on its descendants, unlike
michael@0 709 // in SetDirOnBind where we don't propagate AncestorHasDirAuto to a <bdi>
michael@0 710 // being bound to an existing node with dir=auto.
michael@0 711 if (!DoesNotParticipateInAutoDirection(aElement)) {
michael@0 712
michael@0 713 bool setAncestorDirAutoFlag =
michael@0 714 #ifdef DEBUG
michael@0 715 true;
michael@0 716 #else
michael@0 717 !aElement->AncestorHasDirAuto();
michael@0 718 #endif
michael@0 719
michael@0 720 if (setAncestorDirAutoFlag) {
michael@0 721 nsIContent* child = aElement->GetFirstChild();
michael@0 722 while (child) {
michael@0 723 if (child->IsElement() &&
michael@0 724 DoesNotAffectDirectionOfAncestors(child->AsElement())) {
michael@0 725 child = child->GetNextNonChildNode(aElement);
michael@0 726 continue;
michael@0 727 }
michael@0 728
michael@0 729 MOZ_ASSERT(!aElement->AncestorHasDirAuto() ||
michael@0 730 child->AncestorHasDirAuto(),
michael@0 731 "AncestorHasDirAuto set on node but not its children");
michael@0 732 child->SetAncestorHasDirAuto();
michael@0 733 child = child->GetNextNode(aElement);
michael@0 734 }
michael@0 735 }
michael@0 736 }
michael@0 737
michael@0 738 nsINode* textNode = WalkDescendantsSetDirectionFromText(aElement, aNotify);
michael@0 739 if (textNode) {
michael@0 740 nsTextNodeDirectionalityMap::AddEntryToMap(textNode, aElement);
michael@0 741 }
michael@0 742 }
michael@0 743
michael@0 744 void
michael@0 745 WalkDescendantsClearAncestorDirAuto(Element* aElement)
michael@0 746 {
michael@0 747 nsIContent* child = aElement->GetFirstChild();
michael@0 748 while (child) {
michael@0 749 if (child->HasDirAuto()) {
michael@0 750 child = child->GetNextNonChildNode(aElement);
michael@0 751 continue;
michael@0 752 }
michael@0 753
michael@0 754 child->ClearAncestorHasDirAuto();
michael@0 755 child = child->GetNextNode(aElement);
michael@0 756 }
michael@0 757 }
michael@0 758
michael@0 759 void SetAncestorDirectionIfAuto(nsINode* aTextNode, Directionality aDir,
michael@0 760 bool aNotify = true)
michael@0 761 {
michael@0 762 MOZ_ASSERT(aTextNode->NodeType() == nsIDOMNode::TEXT_NODE,
michael@0 763 "Must be a text node");
michael@0 764
michael@0 765 Element* parent = aTextNode->GetParentElement();
michael@0 766 while (parent && parent->NodeOrAncestorHasDirAuto()) {
michael@0 767 if (DoesNotParticipateInAutoDirection(parent) || parent->HasFixedDir()) {
michael@0 768 break;
michael@0 769 }
michael@0 770
michael@0 771 if (parent->HasDirAuto()) {
michael@0 772 bool resetDirection = false;
michael@0 773 nsINode* directionWasSetByTextNode =
michael@0 774 static_cast<nsINode*>(parent->GetProperty(nsGkAtoms::dirAutoSetBy));
michael@0 775
michael@0 776 if (!parent->HasDirAutoSet()) {
michael@0 777 // Fast path if parent's direction is not yet set by any descendant
michael@0 778 MOZ_ASSERT(!directionWasSetByTextNode,
michael@0 779 "dirAutoSetBy property should be null");
michael@0 780 resetDirection = true;
michael@0 781 } else {
michael@0 782 // If parent's direction is already set, we need to know if
michael@0 783 // aTextNode is before or after the text node that had set it.
michael@0 784 // We will walk parent's descendants in tree order starting from
michael@0 785 // aTextNode to optimize for the most common case where text nodes are
michael@0 786 // being appended to tree.
michael@0 787 if (!directionWasSetByTextNode) {
michael@0 788 resetDirection = true;
michael@0 789 } else if (directionWasSetByTextNode != aTextNode) {
michael@0 790 nsIContent* child = aTextNode->GetNextNode(parent);
michael@0 791 while (child) {
michael@0 792 if (child->IsElement() &&
michael@0 793 DoesNotAffectDirectionOfAncestors(child->AsElement())) {
michael@0 794 child = child->GetNextNonChildNode(parent);
michael@0 795 continue;
michael@0 796 }
michael@0 797
michael@0 798 if (child == directionWasSetByTextNode) {
michael@0 799 // we found the node that set the element's direction after our
michael@0 800 // text node, so we need to reset the direction
michael@0 801 resetDirection = true;
michael@0 802 break;
michael@0 803 }
michael@0 804
michael@0 805 child = child->GetNextNode(parent);
michael@0 806 }
michael@0 807 }
michael@0 808 }
michael@0 809
michael@0 810 if (resetDirection) {
michael@0 811 if (directionWasSetByTextNode) {
michael@0 812 nsTextNodeDirectionalityMap::RemoveElementFromMap(
michael@0 813 directionWasSetByTextNode, parent
michael@0 814 );
michael@0 815 }
michael@0 816 parent->SetDirectionality(aDir, aNotify);
michael@0 817 nsTextNodeDirectionalityMap::AddEntryToMap(aTextNode, parent);
michael@0 818 SetDirectionalityOnDescendants(parent, aDir, aNotify);
michael@0 819 }
michael@0 820
michael@0 821 // Since we found an element with dir=auto, we can stop walking the
michael@0 822 // parent chain: none of its ancestors will have their direction set by
michael@0 823 // any of its descendants.
michael@0 824 return;
michael@0 825 }
michael@0 826 parent = parent->GetParentElement();
michael@0 827 }
michael@0 828 }
michael@0 829
michael@0 830 bool
michael@0 831 TextNodeWillChangeDirection(nsIContent* aTextNode, Directionality* aOldDir,
michael@0 832 uint32_t aOffset)
michael@0 833 {
michael@0 834 if (!NodeAffectsDirAutoAncestor(aTextNode)) {
michael@0 835 nsTextNodeDirectionalityMap::EnsureMapIsClearFor(aTextNode);
michael@0 836 return false;
michael@0 837 }
michael@0 838
michael@0 839 uint32_t firstStrong;
michael@0 840 *aOldDir = GetDirectionFromText(aTextNode->GetText(), &firstStrong);
michael@0 841 return (aOffset <= firstStrong);
michael@0 842 }
michael@0 843
michael@0 844 void
michael@0 845 TextNodeChangedDirection(nsIContent* aTextNode, Directionality aOldDir,
michael@0 846 bool aNotify)
michael@0 847 {
michael@0 848 Directionality newDir = GetDirectionFromText(aTextNode->GetText());
michael@0 849 if (newDir == eDir_NotSet) {
michael@0 850 if (aOldDir != eDir_NotSet && aTextNode->HasTextNodeDirectionalityMap()) {
michael@0 851 // This node used to have a strong directional character but no
michael@0 852 // longer does. ResetTextNodeDirection() will re-resolve the
michael@0 853 // directionality of any elements whose directionality was
michael@0 854 // determined by this node.
michael@0 855 nsTextNodeDirectionalityMap::ResetTextNodeDirection(aTextNode);
michael@0 856 }
michael@0 857 } else {
michael@0 858 // This node has a strong directional character. If it has a
michael@0 859 // TextNodeDirectionalityMap property, it already determines the
michael@0 860 // directionality of some element(s), so call UpdateTextNodeDirection to
michael@0 861 // reresolve their directionality. Otherwise call
michael@0 862 // SetAncestorDirectionIfAuto to find ancestor elements which should
michael@0 863 // have their directionality determined by this node.
michael@0 864 if (aTextNode->HasTextNodeDirectionalityMap()) {
michael@0 865 nsTextNodeDirectionalityMap::UpdateTextNodeDirection(aTextNode, newDir);
michael@0 866 } else {
michael@0 867 SetAncestorDirectionIfAuto(aTextNode, newDir, aNotify);
michael@0 868 }
michael@0 869 }
michael@0 870 }
michael@0 871
michael@0 872 void
michael@0 873 SetDirectionFromNewTextNode(nsIContent* aTextNode)
michael@0 874 {
michael@0 875 if (!NodeAffectsDirAutoAncestor(aTextNode)) {
michael@0 876 return;
michael@0 877 }
michael@0 878
michael@0 879 Element* parent = aTextNode->GetParentElement();
michael@0 880 if (parent && parent->NodeOrAncestorHasDirAuto()) {
michael@0 881 aTextNode->SetAncestorHasDirAuto();
michael@0 882 }
michael@0 883
michael@0 884 Directionality dir = GetDirectionFromText(aTextNode->GetText());
michael@0 885 if (dir != eDir_NotSet) {
michael@0 886 SetAncestorDirectionIfAuto(aTextNode, dir);
michael@0 887 }
michael@0 888 }
michael@0 889
michael@0 890 void
michael@0 891 ResetDirectionSetByTextNode(nsTextNode* aTextNode, bool aNullParent)
michael@0 892 {
michael@0 893 if (!NodeAffectsDirAutoAncestor(aTextNode)) {
michael@0 894 nsTextNodeDirectionalityMap::EnsureMapIsClearFor(aTextNode);
michael@0 895 return;
michael@0 896 }
michael@0 897
michael@0 898 Directionality dir = GetDirectionFromText(aTextNode->GetText());
michael@0 899 if (dir != eDir_NotSet && aTextNode->HasTextNodeDirectionalityMap()) {
michael@0 900 if (aNullParent) {
michael@0 901 nsTextNodeDirectionalityMap::ClearTextNodeDirection(aTextNode);
michael@0 902 } else {
michael@0 903 nsTextNodeDirectionalityMap::ResetTextNodeDirection(aTextNode);
michael@0 904 }
michael@0 905 }
michael@0 906 }
michael@0 907
michael@0 908 void
michael@0 909 SetDirectionalityFromValue(Element* aElement, const nsAString& value,
michael@0 910 bool aNotify)
michael@0 911 {
michael@0 912 Directionality dir = GetDirectionFromText(PromiseFlatString(value).get(),
michael@0 913 value.Length());
michael@0 914 if (dir == eDir_NotSet) {
michael@0 915 dir = eDir_LTR;
michael@0 916 }
michael@0 917
michael@0 918 aElement->SetDirectionality(dir, aNotify);
michael@0 919 }
michael@0 920
michael@0 921 void
michael@0 922 OnSetDirAttr(Element* aElement, const nsAttrValue* aNewValue,
michael@0 923 bool hadValidDir, bool hadDirAuto, bool aNotify)
michael@0 924 {
michael@0 925 if (aElement->IsHTML(nsGkAtoms::input)) {
michael@0 926 return;
michael@0 927 }
michael@0 928
michael@0 929 if (aElement->AncestorHasDirAuto()) {
michael@0 930 if (!hadValidDir) {
michael@0 931 // The element is a descendant of an element with dir = auto, is
michael@0 932 // having its dir attribute set, and previously didn't have a valid dir
michael@0 933 // attribute.
michael@0 934 // Check whether any of its text node descendants determine the
michael@0 935 // direction of any of its ancestors, and redetermine their direction
michael@0 936 WalkDescendantsResetAutoDirection(aElement);
michael@0 937 } else if (!aElement->HasValidDir()) {
michael@0 938 // The element is a descendant of an element with dir = auto and is
michael@0 939 // having its dir attribute removed or set to an invalid value.
michael@0 940 // Reset the direction of any of its ancestors whose direction is
michael@0 941 // determined by a text node descendant
michael@0 942 WalkAncestorsResetAutoDirection(aElement, aNotify);
michael@0 943 }
michael@0 944 } else if (hadDirAuto && !aElement->HasDirAuto()) {
michael@0 945 // The element isn't a descendant of an element with dir = auto, and is
michael@0 946 // having its dir attribute set to something other than auto.
michael@0 947 // Walk the descendant tree and clear the AncestorHasDirAuto flag.
michael@0 948 //
michael@0 949 // N.B: For elements other than <bdi> it would be enough to test that the
michael@0 950 // current value of dir was "auto" in BeforeSetAttr to know that we
michael@0 951 // were unsetting dir="auto". For <bdi> things are more complicated,
michael@0 952 // since it behaves like dir="auto" whenever the dir attribute is
michael@0 953 // empty or invalid, so we would have to check whether the old value
michael@0 954 // was not either "ltr" or "rtl", and the new value was either "ltr"
michael@0 955 // or "rtl". Element::HasDirAuto() encapsulates all that, so doing it
michael@0 956 // here is simpler.
michael@0 957 WalkDescendantsClearAncestorDirAuto(aElement);
michael@0 958 }
michael@0 959
michael@0 960 if (aElement->HasDirAuto()) {
michael@0 961 WalkDescendantsSetDirAuto(aElement, aNotify);
michael@0 962 } else {
michael@0 963 if (aElement->HasDirAutoSet()) {
michael@0 964 nsINode* setByNode =
michael@0 965 static_cast<nsINode*>(aElement->GetProperty(nsGkAtoms::dirAutoSetBy));
michael@0 966 nsTextNodeDirectionalityMap::RemoveElementFromMap(setByNode, aElement);
michael@0 967 }
michael@0 968 SetDirectionalityOnDescendants(aElement,
michael@0 969 RecomputeDirectionality(aElement, aNotify),
michael@0 970 aNotify);
michael@0 971 }
michael@0 972 }
michael@0 973
michael@0 974 void
michael@0 975 SetDirOnBind(mozilla::dom::Element* aElement, nsIContent* aParent)
michael@0 976 {
michael@0 977 // Set the AncestorHasDirAuto flag, unless this element shouldn't affect
michael@0 978 // ancestors that have dir=auto
michael@0 979 if (!DoesNotParticipateInAutoDirection(aElement) &&
michael@0 980 !aElement->IsHTML(nsGkAtoms::bdi) &&
michael@0 981 aParent && aParent->NodeOrAncestorHasDirAuto()) {
michael@0 982 aElement->SetAncestorHasDirAuto();
michael@0 983
michael@0 984 nsIContent* child = aElement->GetFirstChild();
michael@0 985 if (child) {
michael@0 986 // If we are binding an element to the tree that already has descendants,
michael@0 987 // and the parent has NodeHasDirAuto or NodeAncestorHasDirAuto, we need
michael@0 988 // to set NodeAncestorHasDirAuto on all the element's descendants, except
michael@0 989 // for nodes that don't affect the direction of their ancestors.
michael@0 990 do {
michael@0 991 if (child->IsElement() &&
michael@0 992 DoesNotAffectDirectionOfAncestors(child->AsElement())) {
michael@0 993 child = child->GetNextNonChildNode(aElement);
michael@0 994 continue;
michael@0 995 }
michael@0 996
michael@0 997 child->SetAncestorHasDirAuto();
michael@0 998 child = child->GetNextNode(aElement);
michael@0 999 } while (child);
michael@0 1000
michael@0 1001 // We may also need to reset the direction of an ancestor with dir=auto
michael@0 1002 WalkAncestorsResetAutoDirection(aElement, true);
michael@0 1003 }
michael@0 1004 }
michael@0 1005
michael@0 1006 if (!aElement->HasDirAuto()) {
michael@0 1007 // if the element doesn't have dir=auto, set its own directionality from
michael@0 1008 // the dir attribute or by inheriting from its ancestors.
michael@0 1009 RecomputeDirectionality(aElement, false);
michael@0 1010 }
michael@0 1011 }
michael@0 1012
michael@0 1013 void ResetDir(mozilla::dom::Element* aElement)
michael@0 1014 {
michael@0 1015 if (aElement->HasDirAutoSet()) {
michael@0 1016 nsINode* setByNode =
michael@0 1017 static_cast<nsINode*>(aElement->GetProperty(nsGkAtoms::dirAutoSetBy));
michael@0 1018 nsTextNodeDirectionalityMap::RemoveElementFromMap(setByNode, aElement);
michael@0 1019 }
michael@0 1020
michael@0 1021 if (!aElement->HasDirAuto()) {
michael@0 1022 RecomputeDirectionality(aElement, false);
michael@0 1023 }
michael@0 1024 }
michael@0 1025
michael@0 1026 } // end namespace mozilla
michael@0 1027

mercurial