mirror of
https://gitlab.winehq.org/wine/wine-gecko.git
synced 2024-09-13 09:24:08 -07:00
Implementation of HTML5 dir=auto. Bug 548206, r=ehsan, peterv
This commit is contained in:
parent
d4949513f7
commit
e3a9ed59c0
@ -7,9 +7,15 @@
|
||||
#ifndef DirectionalityUtils_h___
|
||||
#define DirectionalityUtils_h___
|
||||
|
||||
#include "prtypes.h"
|
||||
#include "mozilla/StandardInteger.h"
|
||||
|
||||
class nsIContent;
|
||||
class nsIDocument;
|
||||
class nsINode;
|
||||
class nsAString;
|
||||
class nsAttrValue;
|
||||
class nsTextNode;
|
||||
|
||||
namespace mozilla {
|
||||
namespace dom {
|
||||
@ -19,12 +25,11 @@ class Element;
|
||||
|
||||
namespace mozilla {
|
||||
|
||||
namespace directionality {
|
||||
|
||||
enum Directionality {
|
||||
eDir_NotSet = 0,
|
||||
eDir_RTL = 1,
|
||||
eDir_LTR = 2
|
||||
eDir_NotSet,
|
||||
eDir_RTL,
|
||||
eDir_LTR,
|
||||
eDir_Auto
|
||||
};
|
||||
|
||||
/**
|
||||
@ -44,11 +49,84 @@ Directionality RecomputeDirectionality(mozilla::dom::Element* aElement,
|
||||
* of setting the dir attribute, rather than walking up the ancestor tree in
|
||||
* the much more common case of getting the element's directionality.
|
||||
*/
|
||||
void SetDirectionalityOnDescendants(mozilla::dom::Element* aElement,
|
||||
void SetDirectionalityOnDescendants(mozilla::dom::Element* aElement,
|
||||
Directionality aDir,
|
||||
bool aNotify = true);
|
||||
|
||||
} // end namespace directionality
|
||||
/**
|
||||
* Walk the descendants of a node in tree order and, for any text node
|
||||
* descendant that determines the directionality of some element and is not a
|
||||
* descendant of another descendant of the original node with dir=auto,
|
||||
* redetermine that element's directionality
|
||||
*/
|
||||
void WalkDescendantsResetAutoDirection(mozilla::dom::Element* aElement);
|
||||
|
||||
/**
|
||||
* After setting dir=auto on an element, walk its descendants in tree order.
|
||||
* If the node doesn't have the NODE_ANCESTOR_HAS_DIR_AUTO flag, set the
|
||||
* NODE_ANCESTOR_HAS_DIR_AUTO flag on all of its descendants.
|
||||
* Resolve the directionality of the element by the "downward propagation
|
||||
* algorithm" (defined in section 3 in the comments at the beginning of
|
||||
* DirectionalityUtils.cpp)
|
||||
*/
|
||||
void WalkDescendantsSetDirAuto(mozilla::dom::Element* aElement,
|
||||
bool aNotify = true);
|
||||
|
||||
/**
|
||||
* After unsetting dir=auto on an element, walk its descendants in tree order,
|
||||
* skipping any that have dir=auto themselves, and unset the
|
||||
* NODE_ANCESTOR_HAS_DIR_AUTO flag
|
||||
*/
|
||||
void WalkDescendantsClearAncestorDirAuto(mozilla::dom::Element* aElement);
|
||||
|
||||
/**
|
||||
* 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.
|
||||
*/
|
||||
void WalkAncestorsResetAutoDirection(mozilla::dom::Element* aElement,
|
||||
bool aNotify = true);
|
||||
|
||||
/**
|
||||
* When the contents of a text node have changed, deal with any elements whose
|
||||
* directionality needs to change
|
||||
*/
|
||||
void SetDirectionFromChangedTextNode(nsIContent* aTextNode, uint32_t aOffset,
|
||||
const PRUnichar* aBuffer, uint32_t aLength,
|
||||
bool aNotify);
|
||||
|
||||
/**
|
||||
* When a text node is appended to an element, find any ancestors with dir=auto
|
||||
* whose directionality will be determined by the text node
|
||||
*/
|
||||
void SetDirectionFromNewTextNode(nsTextNode* aTextNode);
|
||||
|
||||
/**
|
||||
* When a text node is removed from a document, find any ancestors whose
|
||||
* directionality it determined and redetermine their directionality
|
||||
*/
|
||||
void ResetDirectionSetByTextNode(nsTextNode* aTextNode);
|
||||
|
||||
/**
|
||||
* Set the directionality of an element according to the directionality of the
|
||||
* text in aValue
|
||||
*/
|
||||
void SetDirectionalityFromValue(mozilla::dom::Element* aElement,
|
||||
const nsAString& aValue,
|
||||
bool aNotify);
|
||||
|
||||
/**
|
||||
* Called when setting the dir attribute on an element, immediately after
|
||||
* AfterSetAttr. This is instead of using BeforeSetAttr or AfterSetAttr, because
|
||||
* in AfterSetAttr we don't know the old value, so we can't identify all cases
|
||||
* where we need to walk up or down the document tree and reset the direction;
|
||||
* and in BeforeSetAttr we can't do the walk because this element hasn't had the
|
||||
* value set yet so the results will be wrong.
|
||||
*/
|
||||
void OnSetDirAttr(mozilla::dom::Element* aElement,
|
||||
const nsAttrValue* aNewValue,
|
||||
bool hadValidDir,
|
||||
bool aNotify);
|
||||
|
||||
} // end namespace mozilla
|
||||
|
||||
|
@ -282,34 +282,33 @@ public:
|
||||
*/
|
||||
virtual nsIAtom *GetClassAttributeName() const;
|
||||
|
||||
inline directionality::Directionality GetDirectionality() const {
|
||||
inline Directionality GetDirectionality() const {
|
||||
if (HasFlag(NODE_HAS_DIRECTION_RTL)) {
|
||||
return directionality::eDir_RTL;
|
||||
return eDir_RTL;
|
||||
}
|
||||
|
||||
if (HasFlag(NODE_HAS_DIRECTION_LTR)) {
|
||||
return directionality::eDir_LTR;
|
||||
return eDir_LTR;
|
||||
}
|
||||
|
||||
return directionality::eDir_NotSet;
|
||||
return eDir_NotSet;
|
||||
}
|
||||
|
||||
inline void SetDirectionality(directionality::Directionality aDir,
|
||||
bool aNotify) {
|
||||
inline void SetDirectionality(Directionality aDir, bool aNotify) {
|
||||
UnsetFlags(NODE_ALL_DIRECTION_FLAGS);
|
||||
if (!aNotify) {
|
||||
RemoveStatesSilently(DIRECTION_STATES);
|
||||
}
|
||||
|
||||
switch (aDir) {
|
||||
case (directionality::eDir_RTL):
|
||||
case (eDir_RTL):
|
||||
SetFlags(NODE_HAS_DIRECTION_RTL);
|
||||
if (!aNotify) {
|
||||
AddStatesSilently(NS_EVENT_STATE_RTL);
|
||||
}
|
||||
break;
|
||||
|
||||
case(directionality::eDir_LTR):
|
||||
case(eDir_LTR):
|
||||
SetFlags(NODE_HAS_DIRECTION_LTR);
|
||||
if (!aNotify) {
|
||||
AddStatesSilently(NS_EVENT_STATE_LTR);
|
||||
@ -332,6 +331,15 @@ public:
|
||||
|
||||
bool GetBindingURL(nsIDocument *aDocument, css::URLValue **aResult);
|
||||
|
||||
// The bdi element defaults to dir=auto if it has no dir attribute set.
|
||||
// Other elements will only have dir=auto if they have an explicit dir=auto,
|
||||
// which will mean that HasValidDir() returns true but HasFixedDir() returns
|
||||
// false
|
||||
inline bool HasDirAuto() const {
|
||||
return (!HasFixedDir() &&
|
||||
(HasValidDir() || NodeInfo()->Equals(nsGkAtoms::bdi)));
|
||||
}
|
||||
|
||||
protected:
|
||||
/**
|
||||
* Method to get the _intrinsic_ content state of this element. This is the
|
||||
|
@ -419,7 +419,7 @@ public:
|
||||
mSandboxFlags = sandboxFlags;
|
||||
}
|
||||
|
||||
inline mozilla::directionality::Directionality GetDocumentDirectionality() {
|
||||
inline mozilla::Directionality GetDocumentDirectionality() {
|
||||
return mDirectionality;
|
||||
}
|
||||
|
||||
@ -1916,7 +1916,7 @@ protected:
|
||||
uint32_t mSandboxFlags;
|
||||
|
||||
// The root directionality of this document.
|
||||
mozilla::directionality::Directionality mDirectionality;
|
||||
mozilla::Directionality mDirectionality;
|
||||
|
||||
nsCString mContentLanguage;
|
||||
private:
|
||||
|
@ -1295,8 +1295,21 @@ private:
|
||||
NodeIsContent,
|
||||
// Set if the node has animations or transitions
|
||||
ElementHasAnimations,
|
||||
// Set if node has a dir attribute with a valid value (ltr or rtl)
|
||||
// Set if node has a dir attribute with a valid value (ltr, rtl, or auto)
|
||||
NodeHasValidDirAttribute,
|
||||
// Set if node has a dir attribute with a fixed value (ltr or rtl, NOT auto)
|
||||
NodeHasFixedDir,
|
||||
// Set if the node has dir=auto and has a property pointing to the text
|
||||
// node that determines its direction
|
||||
NodeHasDirAutoSet,
|
||||
// Set if the node is a text node descendant of a node with dir=auto
|
||||
// and has a TextNodeDirectionalityMap property listing the elements whose
|
||||
// direction it determines.
|
||||
NodeHasTextNodeDirectionalityMap,
|
||||
// Set if the node has dir=auto.
|
||||
NodeHasDirAuto,
|
||||
// Set if a node in the node's parent chain has dir=auto.
|
||||
NodeAncestorHasDirAuto,
|
||||
// Guard value
|
||||
BooleanFlagCount
|
||||
};
|
||||
@ -1367,6 +1380,52 @@ public:
|
||||
void SetHasValidDir() { SetBoolFlag(NodeHasValidDirAttribute); }
|
||||
void ClearHasValidDir() { ClearBoolFlag(NodeHasValidDirAttribute); }
|
||||
bool HasValidDir() const { return GetBoolFlag(NodeHasValidDirAttribute); }
|
||||
void SetHasFixedDir() {
|
||||
MOZ_ASSERT(NodeType() != nsIDOMNode::TEXT_NODE,
|
||||
"SetHasFixedDir on text node");
|
||||
SetBoolFlag(NodeHasFixedDir);
|
||||
}
|
||||
void ClearHasFixedDir() {
|
||||
MOZ_ASSERT(NodeType() != nsIDOMNode::TEXT_NODE,
|
||||
"ClearHasFixedDir on text node");
|
||||
ClearBoolFlag(NodeHasFixedDir);
|
||||
}
|
||||
bool HasFixedDir() const { return GetBoolFlag(NodeHasFixedDir); }
|
||||
void SetHasDirAutoSet() {
|
||||
MOZ_ASSERT(NodeType() != nsIDOMNode::TEXT_NODE,
|
||||
"SetHasDirAutoSet on text node");
|
||||
SetBoolFlag(NodeHasDirAutoSet);
|
||||
}
|
||||
void ClearHasDirAutoSet() {
|
||||
MOZ_ASSERT(NodeType() != nsIDOMNode::TEXT_NODE,
|
||||
"ClearHasDirAutoSet on text node");
|
||||
ClearBoolFlag(NodeHasDirAutoSet);
|
||||
}
|
||||
bool HasDirAutoSet() const
|
||||
{ return GetBoolFlag(NodeHasDirAutoSet); }
|
||||
void SetHasTextNodeDirectionalityMap() {
|
||||
MOZ_ASSERT(NodeType() == nsIDOMNode::TEXT_NODE,
|
||||
"SetHasTextNodeDirectionalityMap on non-text node");
|
||||
SetBoolFlag(NodeHasTextNodeDirectionalityMap);
|
||||
}
|
||||
void ClearHasTextNodeDirectionalityMap() {
|
||||
MOZ_ASSERT(NodeType() == nsIDOMNode::TEXT_NODE,
|
||||
"ClearHasTextNodeDirectionalityMap on non-text node");
|
||||
ClearBoolFlag(NodeHasTextNodeDirectionalityMap);
|
||||
}
|
||||
bool HasTextNodeDirectionalityMap() const
|
||||
{ return GetBoolFlag(NodeHasTextNodeDirectionalityMap); }
|
||||
|
||||
void SetHasDirAuto() { SetBoolFlag(NodeHasDirAuto); }
|
||||
void ClearHasDirAuto() { ClearBoolFlag(NodeHasDirAuto); }
|
||||
bool HasDirAuto() const { return GetBoolFlag(NodeHasDirAuto); }
|
||||
|
||||
void SetAncestorHasDirAuto() { SetBoolFlag(NodeAncestorHasDirAuto); }
|
||||
void ClearAncestorHasDirAuto() { ClearBoolFlag(NodeAncestorHasDirAuto); }
|
||||
bool AncestorHasDirAuto() const { return GetBoolFlag(NodeAncestorHasDirAuto); }
|
||||
|
||||
bool NodeOrAncestorHasDirAuto() const
|
||||
{ return HasDirAuto() || AncestorHasDirAuto(); }
|
||||
protected:
|
||||
void SetParentIsContent(bool aValue) { SetBoolFlag(ParentIsContent, aValue); }
|
||||
void SetInDocument() { SetBoolFlag(IsInDocument); }
|
||||
|
@ -4,25 +4,554 @@
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* 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
|
||||
NodeHasDirAuto flag on the node. We won't have enough information to
|
||||
decide the directionality of the node at this point.
|
||||
|
||||
When we bind a new content node to the document, if its parent has either of
|
||||
the NodeAncestorHasDirAuto or NodeHasDirAuto flags, we set the
|
||||
NodeAncestorHasDirAuto flag on the node.
|
||||
|
||||
When a new input with @type=text/search/tel/url/email and @dir=auto is added
|
||||
from the parser, we resolve the directionality based on its @value.
|
||||
|
||||
When a new text node with non-neutral content is appended to a textarea
|
||||
element with NodeHasDirAuto, if the directionality of the textarea element
|
||||
is still unresolved, it is resolved based on the value of the text node.
|
||||
Elements with unresolved directionality behave as LTR.
|
||||
|
||||
When a new text node with non-neutral content is appended to an element that
|
||||
is not a textarea but has either of the NodeAncestorHasDirAuto or
|
||||
NodeHasDirAuto flags, we walk up the parent chain while the
|
||||
NodeAncestorHasDirAuto flag is present, and when we reach an element with
|
||||
NodeHasDirAuto and no resolved directionality, we resolve the directionality
|
||||
based on the contents of the text node and cease walking the parent chain.
|
||||
Note that we should ignore elements with NodeHasDirAuto with resolved
|
||||
directionality, so that the second text node in this example tree doesn't
|
||||
affect the directionality of the div:
|
||||
|
||||
<div dir=auto>
|
||||
<span>foo</span>
|
||||
<span>بار</span>
|
||||
</div>
|
||||
|
||||
The parent chain walk will be aborted if we hit a script or style element, or
|
||||
if we hit an element with @dir=ltr or @dir=rtl.
|
||||
|
||||
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.
|
||||
|
||||
Handling dynamic changes
|
||||
========================
|
||||
|
||||
We need to handle the following cases:
|
||||
|
||||
1. When the value of an input element with @type=text/search/tel/url/email is
|
||||
changed, if it has NodeHasDirAuto, we update the resolved directionality.
|
||||
|
||||
2. When the dir attribute is changed from something else (including the case
|
||||
where it doesn't exist) to auto on a textarea or an input element with
|
||||
@type=text/search/tel/url/email, we set the NodeHasDirAuto flag and resolve
|
||||
the directionality based on the value of the element.
|
||||
|
||||
3. When the dir attribute is changed from something else (including the case
|
||||
where it doesn't exist) to auto on any element except case 1 above and the bdi
|
||||
element, we run the following algorithm:
|
||||
* We set the NodeHasDirAuto flag.
|
||||
* If the element doesn't have the NodeAncestorHasDirAuto flag, we set the
|
||||
NodeAncestorHasDirAuto flag on all of its child nodes. (Note that if the
|
||||
element does have NodeAncestorHasDirAuto, all of its children should
|
||||
already have this flag too. We can assert this in debug builds.)
|
||||
* To resolve the directionality of the element, we run the algorithm explained
|
||||
in http://www.whatwg.org/specs/web-apps/current-work/multipage/elements.html#the-dir-attribute
|
||||
(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.
|
||||
|
||||
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.
|
||||
|
||||
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
|
||||
@type=text/search/tel/url/email, we unset the NodeHasDirAuto flag and
|
||||
resolve the directionality based on the directionality of the value of the @dir
|
||||
attribute on element itself or its parent element.
|
||||
|
||||
5. When the dir attribute is changed from auto to something else (including the
|
||||
case where it gets removed) on any element except case 4 above and the bdi
|
||||
element, we run the following algorithm:
|
||||
* We unset the NodeHasDirAuto flag.
|
||||
* If the element does not have the NodeAncestorHasDirAuto flag, we unset
|
||||
the NodeAncestorHasDirAuto flag on all of its child nodes, except those
|
||||
who are a descendant of another element with NodeHasDirAuto. (Note that if
|
||||
the element has the NodeAncestorHasDirAuto flag, all of its child nodes
|
||||
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.
|
||||
|
||||
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).
|
||||
|
||||
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.
|
||||
|
||||
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
|
||||
to the case 3 above.
|
||||
|
||||
== Implemention Notes ==
|
||||
When a new node gets bound to the tree, the BindToTree function gets called.
|
||||
The reverse case is UnbindFromTree.
|
||||
When the contents of a text node change, nsGenericDOMDataNode::SetTextInternal
|
||||
gets called.
|
||||
*/
|
||||
|
||||
#include "mozilla/dom/DirectionalityUtils.h"
|
||||
#include "nsINode.h"
|
||||
#include "nsIContent.h"
|
||||
#include "nsIDocument.h"
|
||||
#include "mozilla/dom/Element.h"
|
||||
#include "nsIDOMNodeFilter.h"
|
||||
#include "nsTreeWalker.h"
|
||||
#include "nsIDOMHTMLDocument.h"
|
||||
|
||||
#include "nsUnicodeProperties.h"
|
||||
#include "nsTextFragment.h"
|
||||
#include "nsAttrValue.h"
|
||||
#include "nsContentUtils.h"
|
||||
#include "nsTextNode.h"
|
||||
#include "nsCheapSets.h"
|
||||
|
||||
namespace mozilla {
|
||||
|
||||
namespace directionality {
|
||||
|
||||
typedef mozilla::dom::Element Element;
|
||||
|
||||
/**
|
||||
* Returns true if aNode is one of the elements whose text content should not
|
||||
* affect its own direction, nor 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.
|
||||
* It *does* include textarea, because even if a textarea has dir=auto, it has
|
||||
* unicode-bidi: plaintext and is handled automatically in bidi resolution.
|
||||
*/
|
||||
static bool
|
||||
DoesNotParticipateInAutoDirection(const Element* aElement)
|
||||
{
|
||||
nsINodeInfo* nodeInfo = aElement->NodeInfo();
|
||||
return (aElement->IsHTML() &&
|
||||
(nodeInfo->Equals(nsGkAtoms::script) ||
|
||||
nodeInfo->Equals(nsGkAtoms::style) ||
|
||||
nodeInfo->Equals(nsGkAtoms::textarea)));
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the directionality of a Unicode character
|
||||
*/
|
||||
static Directionality
|
||||
GetDirectionFromChar(uint32_t ch)
|
||||
{
|
||||
switch(mozilla::unicode::GetBidiCat(ch)) {
|
||||
case eCharType_RightToLeft:
|
||||
case eCharType_RightToLeftArabic:
|
||||
return eDir_RTL;
|
||||
|
||||
case eCharType_LeftToRight:
|
||||
return eDir_LTR;
|
||||
|
||||
default:
|
||||
return eDir_NotSet;
|
||||
}
|
||||
}
|
||||
|
||||
inline static bool NodeAffectsDirAutoAncestor(nsINode* aTextNode)
|
||||
{
|
||||
Element* parent = aTextNode->GetElementParent();
|
||||
return (parent &&
|
||||
!DoesNotParticipateInAutoDirection(parent) &&
|
||||
parent->NodeOrAncestorHasDirAuto());
|
||||
}
|
||||
|
||||
/**
|
||||
* Various methods for returning the directionality of a string using the
|
||||
* first-strong algorithm defined in http://unicode.org/reports/tr9/#P2
|
||||
*
|
||||
* @param[out] aFirstStrong the offset to the first character in the string with
|
||||
* strong directionality, or PR_UINT32_MAX if there is none (return
|
||||
value is eDir_NotSet).
|
||||
* @return the directionality of the string
|
||||
*/
|
||||
static Directionality
|
||||
GetDirectionFromText(const PRUnichar* aText, const uint32_t aLength,
|
||||
uint32_t* aFirstStrong = nullptr)
|
||||
{
|
||||
const PRUnichar* start = aText;
|
||||
const PRUnichar* end = aText + aLength;
|
||||
|
||||
while (start < end) {
|
||||
uint32_t current = start - aText;
|
||||
uint32_t ch = *start++;
|
||||
|
||||
if (NS_IS_HIGH_SURROGATE(ch) &&
|
||||
start < end &&
|
||||
NS_IS_LOW_SURROGATE(*start)) {
|
||||
ch = SURROGATE_TO_UCS4(ch, *start++);
|
||||
}
|
||||
|
||||
Directionality dir = GetDirectionFromChar(ch);
|
||||
if (dir != eDir_NotSet) {
|
||||
if (aFirstStrong) {
|
||||
*aFirstStrong = current;
|
||||
}
|
||||
return dir;
|
||||
}
|
||||
}
|
||||
|
||||
if (aFirstStrong) {
|
||||
*aFirstStrong = PR_UINT32_MAX;
|
||||
}
|
||||
return eDir_NotSet;
|
||||
}
|
||||
|
||||
static Directionality
|
||||
GetDirectionFromText(const char* aText, const uint32_t aLength,
|
||||
uint32_t* aFirstStrong = nullptr)
|
||||
{
|
||||
const char* start = aText;
|
||||
const char* end = aText + aLength;
|
||||
|
||||
while (start < end) {
|
||||
uint32_t current = start - aText;
|
||||
unsigned char ch = (unsigned char)*start++;
|
||||
|
||||
Directionality dir = GetDirectionFromChar(ch);
|
||||
if (dir != eDir_NotSet) {
|
||||
if (aFirstStrong) {
|
||||
*aFirstStrong = current;
|
||||
}
|
||||
return dir;
|
||||
}
|
||||
}
|
||||
|
||||
if (aFirstStrong) {
|
||||
*aFirstStrong = PR_UINT32_MAX;
|
||||
}
|
||||
return eDir_NotSet;
|
||||
}
|
||||
|
||||
static Directionality
|
||||
GetDirectionFromText(const nsTextFragment* aFrag,
|
||||
uint32_t* aFirstStrong = nullptr)
|
||||
{
|
||||
if (aFrag->Is2b()) {
|
||||
return GetDirectionFromText(aFrag->Get2b(), aFrag->GetLength(),
|
||||
aFirstStrong);
|
||||
}
|
||||
|
||||
return GetDirectionFromText(aFrag->Get1b(), aFrag->GetLength(),
|
||||
aFirstStrong);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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] aStartAfterNode as an optimization, a caller may pass in a node
|
||||
* from which to begin walking the descendants of aElement, if it is
|
||||
* known that all text nodes before this node do not contain any
|
||||
* strong directional characters
|
||||
* @return the text node containing the character that determined the direction
|
||||
*/
|
||||
static nsINode*
|
||||
WalkDescendantsSetDirectionFromText(Element* aElement, bool aNotify = true,
|
||||
nsINode* aStartAfterNode = nullptr)
|
||||
{
|
||||
MOZ_ASSERT(aElement, "aElement is null");
|
||||
|
||||
nsIContent* child;
|
||||
if (aStartAfterNode &&
|
||||
nsContentUtils::ContentIsDescendantOf(aStartAfterNode, aElement)) {
|
||||
#ifdef DEBUG
|
||||
child = aElement->GetFirstChild();
|
||||
while (child && child != aStartAfterNode) {
|
||||
if (child->NodeType() == nsIDOMNode::TEXT_NODE) {
|
||||
MOZ_ASSERT(GetDirectionFromText(child->GetText()) == eDir_NotSet,
|
||||
"Strong directional characters before aStartAfterNode");
|
||||
}
|
||||
child = child->GetNextNode(aElement);
|
||||
}
|
||||
#endif
|
||||
child = aStartAfterNode->GetNextNode(aElement);
|
||||
} else {
|
||||
child = aElement->GetFirstChild();
|
||||
}
|
||||
|
||||
while (child) {
|
||||
if (child->IsElement() &&
|
||||
(DoesNotParticipateInAutoDirection(child->AsElement()) ||
|
||||
child->NodeInfo()->Equals(nsGkAtoms::bdi) ||
|
||||
child->HasFixedDir())) {
|
||||
child = child->GetNextNonChildNode(aElement);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (child->NodeType() == nsIDOMNode::TEXT_NODE) {
|
||||
Directionality textNodeDir = GetDirectionFromText(child->GetText());
|
||||
if (textNodeDir != eDir_NotSet) {
|
||||
// We found a descendant text node with strong directional characters.
|
||||
// Set the directionality of aElement to the corresponding value.
|
||||
aElement->SetDirectionality(textNodeDir, aNotify);
|
||||
return child;
|
||||
}
|
||||
}
|
||||
child = child->GetNextNode(aElement);
|
||||
}
|
||||
|
||||
// We walked all the descendants without finding a text node with strong
|
||||
// directional characters. Set the directionality to LTR
|
||||
aElement->SetDirectionality(eDir_LTR, aNotify);
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
class nsTextNodeDirectionalityMap
|
||||
{
|
||||
static void
|
||||
nsTextNodeDirectionalityMapDtor(void *aObject, nsIAtom* aPropertyName,
|
||||
void *aPropertyValue, void* aData)
|
||||
{
|
||||
nsTextNodeDirectionalityMap* map =
|
||||
reinterpret_cast<nsTextNodeDirectionalityMap * >(aPropertyValue);
|
||||
delete map;
|
||||
}
|
||||
|
||||
public:
|
||||
nsTextNodeDirectionalityMap(nsINode* aTextNode)
|
||||
{
|
||||
MOZ_ASSERT(aTextNode, "Null text node");
|
||||
MOZ_COUNT_CTOR(nsTextNodeDirectionalityMap);
|
||||
aTextNode->SetProperty(nsGkAtoms::textNodeDirectionalityMap, this,
|
||||
nsTextNodeDirectionalityMapDtor);
|
||||
aTextNode->SetHasTextNodeDirectionalityMap();
|
||||
}
|
||||
|
||||
~nsTextNodeDirectionalityMap()
|
||||
{
|
||||
MOZ_COUNT_DTOR(nsTextNodeDirectionalityMap);
|
||||
}
|
||||
|
||||
void AddEntry(nsINode* aTextNode, Element* aElement)
|
||||
{
|
||||
if (!mElements.Contains(aElement)) {
|
||||
mElements.Put(aElement);
|
||||
aElement->SetProperty(nsGkAtoms::dirAutoSetBy, aTextNode);
|
||||
aElement->SetHasDirAutoSet();
|
||||
}
|
||||
}
|
||||
|
||||
void RemoveEntry(nsINode* aTextNode, Element* aElement)
|
||||
{
|
||||
if (mElements.Contains(aElement)) {
|
||||
mElements.Remove(aElement);
|
||||
|
||||
aElement->ClearHasDirAutoSet();
|
||||
aElement->UnsetProperty(nsGkAtoms::dirAutoSetBy);
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
nsCheapSet<nsPtrHashKey<Element> > mElements;
|
||||
|
||||
static nsTextNodeDirectionalityMap* GetDirectionalityMap(nsINode* aTextNode)
|
||||
{
|
||||
MOZ_ASSERT(aTextNode->NodeType() == nsIDOMNode::TEXT_NODE,
|
||||
"Must be a text node");
|
||||
nsTextNodeDirectionalityMap* map = nullptr;
|
||||
|
||||
if (aTextNode->HasTextNodeDirectionalityMap()) {
|
||||
map = static_cast<nsTextNodeDirectionalityMap * >
|
||||
(aTextNode->GetProperty(nsGkAtoms::textNodeDirectionalityMap));
|
||||
}
|
||||
|
||||
return map;
|
||||
}
|
||||
|
||||
static PLDHashOperator SetNodeDirection(nsPtrHashKey<Element>* aEntry, void* aDir)
|
||||
{
|
||||
MOZ_ASSERT(aEntry->GetKey()->IsElement(), "Must be an Element");
|
||||
aEntry->GetKey()->SetDirectionality(*reinterpret_cast<Directionality*>(aDir),
|
||||
true);
|
||||
return PL_DHASH_NEXT;
|
||||
}
|
||||
|
||||
static PLDHashOperator ResetNodeDirection(nsPtrHashKey<Element>* aEntry, void* aData)
|
||||
{
|
||||
MOZ_ASSERT(aEntry->GetKey()->IsElement(), "Must be an Element");
|
||||
// run the downward propagation algorithm
|
||||
// and remove the text node from the map
|
||||
nsINode* startAfterNode = static_cast<Element*>(aData);
|
||||
Element* rootNode = aEntry->GetKey();
|
||||
nsINode* textNode = WalkDescendantsSetDirectionFromText(rootNode, true,
|
||||
startAfterNode);
|
||||
if (textNode) {
|
||||
nsTextNodeDirectionalityMap::AddEntryToMap(textNode, rootNode);
|
||||
}
|
||||
return PL_DHASH_REMOVE;
|
||||
}
|
||||
|
||||
public:
|
||||
void UpdateAutoDirection(Directionality aDir)
|
||||
{
|
||||
mElements.EnumerateEntries(SetNodeDirection, &aDir);
|
||||
}
|
||||
|
||||
void ResetAutoDirection(nsINode* aTextNode, nsINode* aStartAfterNode)
|
||||
{
|
||||
mElements.EnumerateEntries(ResetNodeDirection, aStartAfterNode);
|
||||
}
|
||||
|
||||
static void RemoveElementFromMap(nsINode* aTextNode, Element* aElement)
|
||||
{
|
||||
if (aTextNode->HasTextNodeDirectionalityMap()) {
|
||||
GetDirectionalityMap(aTextNode)->RemoveEntry(aTextNode, aElement);
|
||||
}
|
||||
}
|
||||
|
||||
static void AddEntryToMap(nsINode* aTextNode, Element* aElement)
|
||||
{
|
||||
nsTextNodeDirectionalityMap* map = GetDirectionalityMap(aTextNode);
|
||||
if (!map) {
|
||||
map = new nsTextNodeDirectionalityMap(aTextNode);
|
||||
}
|
||||
|
||||
map->AddEntry(aTextNode, aElement);
|
||||
}
|
||||
|
||||
static void UpdateTextNodeDirection(nsINode* aTextNode, Directionality aDir)
|
||||
{
|
||||
MOZ_ASSERT(aTextNode->HasTextNodeDirectionalityMap(),
|
||||
"Map missing in UpdateTextNodeDirection");
|
||||
GetDirectionalityMap(aTextNode)->UpdateAutoDirection(aDir);
|
||||
}
|
||||
|
||||
static void ResetTextNodeDirection(nsINode* aTextNode,
|
||||
nsINode* aStartAfterNode = nullptr)
|
||||
{
|
||||
MOZ_ASSERT(aTextNode->HasTextNodeDirectionalityMap(),
|
||||
"Map missing in ResetTextNodeDirection");
|
||||
GetDirectionalityMap(aTextNode)->ResetAutoDirection(aTextNode,
|
||||
aStartAfterNode);
|
||||
}
|
||||
};
|
||||
|
||||
Directionality
|
||||
RecomputeDirectionality(Element* aElement, bool aNotify)
|
||||
{
|
||||
MOZ_ASSERT(!aElement->HasDirAuto(),
|
||||
"RecomputeDirectionality called with dir=auto");
|
||||
if (aElement->HasDirAutoSet()) {
|
||||
nsINode* setByNode =
|
||||
static_cast<nsINode*>(aElement->GetProperty(nsGkAtoms::dirAutoSetBy));
|
||||
if (setByNode) {
|
||||
nsTextNodeDirectionalityMap::RemoveElementFromMap(setByNode, aElement);
|
||||
}
|
||||
}
|
||||
|
||||
Directionality dir = eDir_LTR;
|
||||
|
||||
if (aElement->HasValidDir()) {
|
||||
@ -46,7 +575,7 @@ RecomputeDirectionality(Element* aElement, bool aNotify)
|
||||
dir = documentDir;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
aElement->SetDirectionality(dir, aNotify);
|
||||
}
|
||||
return dir;
|
||||
@ -63,7 +592,7 @@ SetDirectionalityOnDescendants(Element* aElement, Directionality aDir,
|
||||
}
|
||||
|
||||
Element* element = child->AsElement();
|
||||
if (element->HasValidDir()) {
|
||||
if (element->HasValidDir() || element->HasDirAuto()) {
|
||||
child = child->GetNextNonChildNode(aElement);
|
||||
continue;
|
||||
}
|
||||
@ -72,7 +601,272 @@ SetDirectionalityOnDescendants(Element* aElement, Directionality aDir,
|
||||
}
|
||||
}
|
||||
|
||||
} // end namespace directionality
|
||||
void
|
||||
WalkAncestorsResetAutoDirection(Element* aElement, bool aNotify)
|
||||
{
|
||||
nsINode* setByNode;
|
||||
Element* parent = aElement->GetElementParent();
|
||||
|
||||
while (parent && parent->NodeOrAncestorHasDirAuto()) {
|
||||
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<nsINode*>(parent->GetProperty(nsGkAtoms::dirAutoSetBy));
|
||||
if (setByNode) {
|
||||
nsTextNodeDirectionalityMap::RemoveElementFromMap(setByNode, parent);
|
||||
}
|
||||
}
|
||||
if (parent->HasDirAuto()) {
|
||||
setByNode = WalkDescendantsSetDirectionFromText(parent, aNotify);
|
||||
if (setByNode) {
|
||||
nsTextNodeDirectionalityMap::AddEntryToMap(setByNode, parent);
|
||||
}
|
||||
break;
|
||||
}
|
||||
parent = parent->GetElementParent();
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
WalkDescendantsResetAutoDirection(Element* aElement)
|
||||
{
|
||||
nsIContent* child = aElement->GetFirstChild();
|
||||
while (child) {
|
||||
if (child->HasDirAuto()) {
|
||||
child = child->GetNextNonChildNode(aElement);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (child->HasTextNodeDirectionalityMap()) {
|
||||
nsTextNodeDirectionalityMap::ResetTextNodeDirection(child, child);
|
||||
}
|
||||
child = child->GetNextNode(aElement);
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
WalkDescendantsSetDirAuto(Element* aElement, bool aNotify)
|
||||
{
|
||||
bool setAncestorDirAutoFlag =
|
||||
#ifdef DEBUG
|
||||
true;
|
||||
#else
|
||||
!aElement->AncestorHasDirAuto();
|
||||
#endif
|
||||
|
||||
if (setAncestorDirAutoFlag) {
|
||||
nsIContent* child = aElement->GetFirstChild();
|
||||
while (child) {
|
||||
MOZ_ASSERT(!aElement->AncestorHasDirAuto() ||
|
||||
child->AncestorHasDirAuto(),
|
||||
"AncestorHasDirAuto set on node but not its children");
|
||||
child->SetHasDirAuto();
|
||||
child = child->GetNextNode(aElement);
|
||||
}
|
||||
}
|
||||
|
||||
nsINode* textNode = WalkDescendantsSetDirectionFromText(aElement, aNotify);
|
||||
if (textNode) {
|
||||
nsTextNodeDirectionalityMap::AddEntryToMap(textNode, aElement);
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
WalkDescendantsClearAncestorDirAuto(Element* aElement)
|
||||
{
|
||||
nsIContent* child = aElement->GetFirstChild();
|
||||
while (child) {
|
||||
if (child->HasDirAuto()) {
|
||||
child = child->GetNextNonChildNode(aElement);
|
||||
continue;
|
||||
}
|
||||
|
||||
child->ClearAncestorHasDirAuto();
|
||||
child = child->GetNextNode(aElement);
|
||||
}
|
||||
}
|
||||
|
||||
void SetAncestorDirectionIfAuto(nsINode* aTextNode, Directionality aDir,
|
||||
bool aNotify = true)
|
||||
{
|
||||
MOZ_ASSERT(aTextNode->NodeType() == nsIDOMNode::TEXT_NODE,
|
||||
"Must be a text node");
|
||||
|
||||
Element* parent = aTextNode->GetElementParent();
|
||||
while (parent && parent->NodeOrAncestorHasDirAuto()) {
|
||||
if (DoesNotParticipateInAutoDirection(parent) || parent->HasFixedDir()) {
|
||||
break;
|
||||
}
|
||||
|
||||
if (parent->HasDirAuto()) {
|
||||
bool resetDirection = false;
|
||||
|
||||
if (!parent->HasDirAutoSet()) {
|
||||
// Fast path if parent's direction is not yet set by any descendant
|
||||
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.
|
||||
nsINode* directionWasSetByTextNode =
|
||||
static_cast<nsINode*>(parent->GetProperty(nsGkAtoms::dirAutoSetBy));
|
||||
if (!directionWasSetByTextNode) {
|
||||
resetDirection = true;
|
||||
} else if (directionWasSetByTextNode != aTextNode) {
|
||||
nsIContent* child = aTextNode->GetNextNode(parent);
|
||||
while (child) {
|
||||
if (child->IsElement() &&
|
||||
(DoesNotParticipateInAutoDirection(child->AsElement()) ||
|
||||
child->NodeInfo()->Equals(nsGkAtoms::bdi) ||
|
||||
child->HasFixedDir())) {
|
||||
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) {
|
||||
parent->SetDirectionality(aDir, aNotify);
|
||||
nsTextNodeDirectionalityMap::AddEntryToMap(aTextNode, parent);
|
||||
SetDirectionalityOnDescendants(parent, aDir, aNotify);
|
||||
}
|
||||
|
||||
// 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;
|
||||
}
|
||||
parent = parent->GetElementParent();
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
SetDirectionFromChangedTextNode(nsIContent* aTextNode, uint32_t aOffset,
|
||||
const PRUnichar* aBuffer, uint32_t aLength,
|
||||
bool aNotify)
|
||||
{
|
||||
if (!NodeAffectsDirAutoAncestor(aTextNode)) {
|
||||
return;
|
||||
}
|
||||
|
||||
uint32_t firstStrong;
|
||||
Directionality oldDir = GetDirectionFromText(aTextNode->GetText(),
|
||||
&firstStrong);
|
||||
if (aOffset > firstStrong) {
|
||||
return;
|
||||
}
|
||||
|
||||
Directionality newDir = GetDirectionFromText(aBuffer, aLength);
|
||||
if (newDir == eDir_NotSet) {
|
||||
if (oldDir != eDir_NotSet && 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);
|
||||
}
|
||||
} 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. Otherwise call
|
||||
// SetAncestorDirectionIfAuto to find ancestor elements which should
|
||||
// have their directionality determined by this node.
|
||||
if (aTextNode->HasTextNodeDirectionalityMap()) {
|
||||
nsTextNodeDirectionalityMap::UpdateTextNodeDirection(aTextNode, newDir);
|
||||
} else {
|
||||
SetAncestorDirectionIfAuto(aTextNode, newDir, aNotify);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
SetDirectionFromNewTextNode(nsTextNode* aTextNode)
|
||||
{
|
||||
if (!NodeAffectsDirAutoAncestor(aTextNode)) {
|
||||
return;
|
||||
}
|
||||
|
||||
Directionality dir = GetDirectionFromText(aTextNode->GetText());
|
||||
if (dir != eDir_NotSet) {
|
||||
SetAncestorDirectionIfAuto(aTextNode, dir);
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
ResetDirectionSetByTextNode(nsTextNode* aTextNode)
|
||||
{
|
||||
if (!NodeAffectsDirAutoAncestor(aTextNode)) {
|
||||
return;
|
||||
}
|
||||
|
||||
Directionality dir = GetDirectionFromText(aTextNode->GetText());
|
||||
if (dir != eDir_NotSet && aTextNode->HasTextNodeDirectionalityMap()) {
|
||||
nsTextNodeDirectionalityMap::ResetTextNodeDirection(aTextNode);
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
SetDirectionalityFromValue(Element* aElement, const nsAString& value,
|
||||
bool aNotify)
|
||||
{
|
||||
Directionality dir = GetDirectionFromText(PromiseFlatString(value).get(),
|
||||
value.Length());
|
||||
if (dir == eDir_NotSet) {
|
||||
dir = eDir_LTR;
|
||||
}
|
||||
|
||||
aElement->SetDirectionality(dir, aNotify);
|
||||
}
|
||||
|
||||
void
|
||||
OnSetDirAttr(Element* aElement, const nsAttrValue* aNewValue,
|
||||
bool hadValidDir, bool aNotify)
|
||||
{
|
||||
if (aElement->IsHTML() && aElement->NodeInfo()->Equals(nsGkAtoms::input)) {
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
if (aElement->HasDirAuto()) {
|
||||
WalkDescendantsSetDirAuto(aElement, aNotify);
|
||||
} else {
|
||||
SetDirectionalityOnDescendants(aElement,
|
||||
RecomputeDirectionality(aElement, aNotify),
|
||||
aNotify);
|
||||
}
|
||||
}
|
||||
|
||||
} // end namespace mozilla
|
||||
|
||||
|
@ -130,7 +130,6 @@
|
||||
|
||||
using namespace mozilla;
|
||||
using namespace mozilla::dom;
|
||||
using namespace mozilla::directionality;
|
||||
|
||||
nsEventStates
|
||||
Element::IntrinsicState() const
|
||||
@ -1163,7 +1162,21 @@ Element::BindToTree(nsIDocument* aDocument, nsIContent* aParent,
|
||||
// because it has to happen after updating the parent pointer, but before
|
||||
// recursively binding the kids.
|
||||
if (IsHTML()) {
|
||||
RecomputeDirectionality(this, false);
|
||||
if (aParent && aParent->NodeOrAncestorHasDirAuto()) {
|
||||
SetAncestorHasDirAuto();
|
||||
// if we are binding an element to the tree that already has descendants,
|
||||
// and the parent has NodeHasDirAuto or NodeAncestorHasDirAuto, we may
|
||||
// need to reset the direction of an ancestor with dir=auto
|
||||
if (GetFirstChild()) {
|
||||
WalkAncestorsResetAutoDirection(this);
|
||||
}
|
||||
}
|
||||
|
||||
if (!HasDirAuto()) {
|
||||
// if the element doesn't have dir=auto, set its directionality from
|
||||
// the dir attribute or by inheriting from its ancestors.
|
||||
RecomputeDirectionality(this, false);
|
||||
}
|
||||
}
|
||||
|
||||
// If NODE_FORCE_XBL_BINDINGS was set we might have anonymous children
|
||||
@ -1354,7 +1367,7 @@ Element::UnbindFromTree(bool aDeep, bool aNullParent)
|
||||
// This has to be here, rather than in nsGenericHTMLElement::UnbindFromTree,
|
||||
// because it has to happen after unsetting the parent pointer, but before
|
||||
// recursively unbinding the kids.
|
||||
if (IsHTML()) {
|
||||
if (IsHTML() && !HasDirAuto()) {
|
||||
RecomputeDirectionality(this, false);
|
||||
}
|
||||
|
||||
@ -1827,7 +1840,13 @@ Element::SetAttrAndNotify(int32_t aNamespaceID,
|
||||
aValueForAfterSetAttr.SetTo(aParsedValue);
|
||||
}
|
||||
|
||||
bool hadValidDir = false;
|
||||
|
||||
if (aNamespaceID == kNameSpaceID_None) {
|
||||
if (aName == nsGkAtoms::dir) {
|
||||
hadValidDir = HasValidDir() || NodeInfo()->Equals(nsGkAtoms::bdi);
|
||||
}
|
||||
|
||||
// XXXbz Perhaps we should push up the attribute mapping function
|
||||
// stuff to Element?
|
||||
if (!IsAttributeMapped(aName) ||
|
||||
@ -1863,6 +1882,10 @@ Element::SetAttrAndNotify(int32_t aNamespaceID,
|
||||
if (aCallAfterSetAttr) {
|
||||
rv = AfterSetAttr(aNamespaceID, aName, &aValueForAfterSetAttr, aNotify);
|
||||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
|
||||
if (aNamespaceID == kNameSpaceID_None && aName == nsGkAtoms::dir) {
|
||||
OnSetDirAttr(this, &aValueForAfterSetAttr, hadValidDir, aNotify);
|
||||
}
|
||||
}
|
||||
|
||||
if (aFireMutation) {
|
||||
@ -2065,6 +2088,12 @@ Element::UnsetAttr(int32_t aNameSpaceID, nsIAtom* aName,
|
||||
// react to unexpected attribute changes.
|
||||
nsMutationGuard::DidMutate();
|
||||
|
||||
bool hadValidDir = false;
|
||||
|
||||
if (aNameSpaceID == kNameSpaceID_None && aName == nsGkAtoms::dir) {
|
||||
hadValidDir = HasValidDir() || NodeInfo()->Equals(nsGkAtoms::bdi);
|
||||
}
|
||||
|
||||
nsAttrValue oldValue;
|
||||
rv = mAttrsAndChildren.RemoveAttrAt(index, oldValue);
|
||||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
@ -2087,6 +2116,10 @@ Element::UnsetAttr(int32_t aNameSpaceID, nsIAtom* aName,
|
||||
rv = AfterSetAttr(aNameSpaceID, aName, nullptr, aNotify);
|
||||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
|
||||
if (aNameSpaceID == kNameSpaceID_None && aName == nsGkAtoms::dir) {
|
||||
OnSetDirAttr(this, nullptr, hadValidDir, aNotify);
|
||||
}
|
||||
|
||||
if (hasMutationListeners) {
|
||||
nsCOMPtr<nsIDOMEventTarget> node = do_QueryObject(this);
|
||||
nsMutationEvent mutation(true, NS_MUTATION_ATTRMODIFIED);
|
||||
|
@ -177,7 +177,6 @@
|
||||
|
||||
using namespace mozilla;
|
||||
using namespace mozilla::dom;
|
||||
using namespace mozilla::directionality;
|
||||
|
||||
typedef nsTArray<Link*> LinkArray;
|
||||
|
||||
|
@ -1053,7 +1053,7 @@ protected:
|
||||
nsresult SetFirstBaseNodeWithHref(nsIContent *node);
|
||||
|
||||
inline void
|
||||
SetDocumentDirectionality(mozilla::directionality::Directionality aDir)
|
||||
SetDocumentDirectionality(mozilla::Directionality aDir)
|
||||
{
|
||||
mDirectionality = aDir;
|
||||
}
|
||||
|
@ -26,6 +26,7 @@
|
||||
#include "nsEventDispatcher.h"
|
||||
#include "nsCOMArray.h"
|
||||
#include "nsNodeUtils.h"
|
||||
#include "mozilla/dom/DirectionalityUtils.h"
|
||||
#include "nsBindingManager.h"
|
||||
#include "nsCCUncollectableMarker.h"
|
||||
#include "mozAutoDocUpdate.h"
|
||||
@ -279,6 +280,10 @@ nsGenericDOMDataNode::SetTextInternal(uint32_t aOffset, uint32_t aCount,
|
||||
nsNodeUtils::CharacterDataWillChange(this, &info);
|
||||
}
|
||||
|
||||
if (NodeType() == nsIDOMNode::TEXT_NODE) {
|
||||
SetDirectionFromChangedTextNode(this, aOffset, aBuffer, aLength, aNotify);
|
||||
}
|
||||
|
||||
if (aOffset == 0 && endOffset == textLength) {
|
||||
// Replacing whole text or old text was empty. Don't bother to check for
|
||||
// bidi in this string if the document already has bidi enabled.
|
||||
|
@ -276,6 +276,7 @@ GK_ATOM(dialog, "dialog")
|
||||
GK_ATOM(difference, "difference")
|
||||
GK_ATOM(digit, "digit")
|
||||
GK_ATOM(dir, "dir")
|
||||
GK_ATOM(dirAutoSetBy, "dirAutoSetBy")
|
||||
GK_ATOM(directionality, "directionality")
|
||||
GK_ATOM(disableOutputEscaping, "disable-output-escaping")
|
||||
GK_ATOM(disabled, "disabled")
|
||||
@ -1033,6 +1034,7 @@ GK_ATOM(text, "text")
|
||||
GK_ATOM(textarea, "textarea")
|
||||
GK_ATOM(textbox, "textbox")
|
||||
GK_ATOM(textnode, "textnode")
|
||||
GK_ATOM(textNodeDirectionalityMap, "textNodeDirectionalityMap")
|
||||
GK_ATOM(tfoot, "tfoot")
|
||||
GK_ATOM(th, "th")
|
||||
GK_ATOM(thead, "thead")
|
||||
|
@ -9,6 +9,7 @@
|
||||
|
||||
#include "nsTextNode.h"
|
||||
#include "nsContentUtils.h"
|
||||
#include "mozilla/dom/DirectionalityUtils.h"
|
||||
#include "nsIDOMEventListener.h"
|
||||
#include "nsIDOMMutationEvent.h"
|
||||
#include "nsIDocument.h"
|
||||
@ -18,6 +19,7 @@
|
||||
#include "nsRange.h"
|
||||
#endif
|
||||
|
||||
using namespace mozilla;
|
||||
using namespace mozilla::dom;
|
||||
|
||||
/**
|
||||
@ -162,6 +164,27 @@ nsTextNode::AppendTextForNormalize(const PRUnichar* aBuffer, uint32_t aLength,
|
||||
return SetTextInternal(mText.GetLength(), 0, aBuffer, aLength, aNotify, &details);
|
||||
}
|
||||
|
||||
nsresult
|
||||
nsTextNode::BindToTree(nsIDocument* aDocument, nsIContent* aParent,
|
||||
nsIContent* aBindingParent, bool aCompileEventHandlers)
|
||||
{
|
||||
nsresult rv = nsGenericDOMDataNode::BindToTree(aDocument, aParent,
|
||||
aBindingParent,
|
||||
aCompileEventHandlers);
|
||||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
|
||||
SetDirectionFromNewTextNode(this);
|
||||
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
void nsTextNode::UnbindFromTree(bool aDeep, bool aNullParent)
|
||||
{
|
||||
ResetDirectionSetByTextNode(this);
|
||||
|
||||
nsGenericDOMDataNode::UnbindFromTree(aDeep, aNullParent);
|
||||
}
|
||||
|
||||
#ifdef DEBUG
|
||||
void
|
||||
nsTextNode::List(FILE* out, int32_t aIndent) const
|
||||
|
@ -40,6 +40,12 @@ public:
|
||||
|
||||
virtual nsXPCClassInfo* GetClassInfo();
|
||||
|
||||
virtual nsresult BindToTree(nsIDocument* aDocument, nsIContent* aParent,
|
||||
nsIContent* aBindingParent,
|
||||
bool aCompileEventHandlers);
|
||||
virtual void UnbindFromTree(bool aDeep = true,
|
||||
bool aNullParent = true);
|
||||
|
||||
nsresult AppendTextForNormalize(const PRUnichar* aBuffer, uint32_t aLength,
|
||||
bool aNotify, nsIContent* aNextSibling);
|
||||
|
||||
|
@ -68,6 +68,7 @@
|
||||
#include "nsIDOMHTMLFormElement.h"
|
||||
#include "nsHTMLFormElement.h"
|
||||
#include "nsFocusManager.h"
|
||||
#include "nsAttrValueOrString.h"
|
||||
|
||||
#include "nsMutationEvent.h"
|
||||
|
||||
@ -102,7 +103,6 @@
|
||||
|
||||
using namespace mozilla;
|
||||
using namespace mozilla::dom;
|
||||
using namespace mozilla::directionality;
|
||||
|
||||
class nsINodeInfo;
|
||||
class nsIDOMNodeList;
|
||||
@ -347,8 +347,9 @@ nsGenericHTMLElement::ClearDataset()
|
||||
}
|
||||
|
||||
static const nsAttrValue::EnumTable kDirTable[] = {
|
||||
{ "ltr", NS_STYLE_DIRECTION_LTR },
|
||||
{ "rtl", NS_STYLE_DIRECTION_RTL },
|
||||
{ "ltr", eDir_LTR },
|
||||
{ "rtl", eDir_RTL },
|
||||
{ "auto", eDir_Auto },
|
||||
{ 0 }
|
||||
};
|
||||
|
||||
@ -1698,6 +1699,23 @@ nsGenericHTMLElement::GetHrefURIForAnchors() const
|
||||
return uri.forget();
|
||||
}
|
||||
|
||||
nsresult
|
||||
nsGenericHTMLElement::BeforeSetAttr(int32_t aNamespaceID, nsIAtom* aName,
|
||||
const nsAttrValueOrString* aValue,
|
||||
bool aNotify)
|
||||
{
|
||||
if (aNamespaceID == kNameSpaceID_None &&
|
||||
aName == nsGkAtoms::dir &&
|
||||
HasDirAuto()) {
|
||||
// setting dir on an element that currently has dir=auto
|
||||
WalkDescendantsClearAncestorDirAuto(this);
|
||||
SetHasDirAuto();
|
||||
}
|
||||
|
||||
return nsGenericHTMLElementBase::BeforeSetAttr(aNamespaceID, aName,
|
||||
aValue, aNotify);
|
||||
}
|
||||
|
||||
nsresult
|
||||
nsGenericHTMLElement::AfterSetAttr(int32_t aNamespaceID, nsIAtom* aName,
|
||||
const nsAttrValue* aValue, bool aNotify)
|
||||
@ -1719,16 +1737,28 @@ nsGenericHTMLElement::AfterSetAttr(int32_t aNamespaceID, nsIAtom* aName,
|
||||
SyncEditorsOnSubtree(this);
|
||||
}
|
||||
else if (aName == nsGkAtoms::dir) {
|
||||
Directionality dir;
|
||||
if (aValue &&
|
||||
(aValue->Equals(nsGkAtoms::ltr, eIgnoreCase) ||
|
||||
aValue->Equals(nsGkAtoms::rtl, eIgnoreCase))) {
|
||||
Directionality dir = eDir_LTR;
|
||||
if (aValue && aValue->Type() == nsAttrValue::eEnum) {
|
||||
SetHasValidDir();
|
||||
dir = aValue->Equals(nsGkAtoms::rtl, eIgnoreCase) ? eDir_RTL : eDir_LTR;
|
||||
SetDirectionality(dir, aNotify);
|
||||
Directionality dirValue = (Directionality)aValue->GetEnumValue();
|
||||
if (dirValue == eDir_Auto) {
|
||||
SetHasDirAuto();
|
||||
ClearHasFixedDir();
|
||||
} else {
|
||||
dir = dirValue;
|
||||
SetDirectionality(dir, aNotify);
|
||||
ClearHasDirAuto();
|
||||
ClearHasDirAutoSet();
|
||||
SetHasFixedDir();
|
||||
}
|
||||
} else {
|
||||
ClearHasValidDir();
|
||||
dir = RecomputeDirectionality(this, aNotify);
|
||||
ClearHasFixedDir();
|
||||
if (NodeInfo()->Equals(nsGkAtoms::bdi)) {
|
||||
SetHasDirAuto();
|
||||
} else {
|
||||
dir = RecomputeDirectionality(this, aNotify);
|
||||
}
|
||||
}
|
||||
SetDirectionalityOnDescendants(this, dir, aNotify);
|
||||
}
|
||||
|
@ -777,6 +777,10 @@ protected:
|
||||
*/
|
||||
bool IsEventName(nsIAtom* aName);
|
||||
|
||||
virtual nsresult BeforeSetAttr(int32_t aNamespaceID, nsIAtom* aName,
|
||||
const nsAttrValueOrString* aValue,
|
||||
bool aNotify);
|
||||
|
||||
virtual nsresult AfterSetAttr(int32_t aNamespaceID, nsIAtom* aName,
|
||||
const nsAttrValue* aValue, bool aNotify);
|
||||
|
||||
|
@ -81,6 +81,7 @@
|
||||
#include "mozAutoDocUpdate.h"
|
||||
#include "nsContentCreatorFunctions.h"
|
||||
#include "nsContentUtils.h"
|
||||
#include "mozilla/dom/DirectionalityUtils.h"
|
||||
#include "nsRadioVisitor.h"
|
||||
|
||||
#include "mozilla/LookAndFeel.h"
|
||||
@ -760,6 +761,10 @@ nsHTMLInputElement::BeforeSetAttr(int32_t aNameSpaceID, nsIAtom* aName,
|
||||
}
|
||||
} else if (aNotify && aName == nsGkAtoms::disabled) {
|
||||
mDisabledChanged = true;
|
||||
} else if (aName == nsGkAtoms::dir &&
|
||||
AttrValueIs(kNameSpaceID_None, nsGkAtoms::dir,
|
||||
nsGkAtoms::_auto, eIgnoreCase)) {
|
||||
SetDirectionIfAuto(false, aNotify);
|
||||
}
|
||||
}
|
||||
|
||||
@ -866,6 +871,9 @@ nsHTMLInputElement::AfterSetAttr(int32_t aNameSpaceID, nsIAtom* aName,
|
||||
UpdateStepMismatchValidityState();
|
||||
} else if (aName == nsGkAtoms::step) {
|
||||
UpdateStepMismatchValidityState();
|
||||
} else if (aName == nsGkAtoms::dir &&
|
||||
aValue && aValue->Equals(nsGkAtoms::_auto, eIgnoreCase)) {
|
||||
SetDirectionIfAuto(true, aNotify);
|
||||
}
|
||||
|
||||
UpdateState(aNotify);
|
||||
@ -2582,6 +2590,9 @@ nsHTMLInputElement::BindToTree(nsIDocument* aDocument, nsIContent* aParent,
|
||||
AddedToRadioGroup();
|
||||
}
|
||||
|
||||
// Set direction based on value if dir=auto
|
||||
SetDirectionIfAuto(HasDirAuto(), false);
|
||||
|
||||
// An element can't suffer from value missing if it is not in a document.
|
||||
// We have to check if we suffer from that as we are now in a document.
|
||||
UpdateValueMissingValidityState();
|
||||
@ -3197,6 +3208,21 @@ nsHTMLInputElement::SetDefaultValueAsValue()
|
||||
return SetValueInternal(resetVal, false, false);
|
||||
}
|
||||
|
||||
void
|
||||
nsHTMLInputElement::SetDirectionIfAuto(bool aAuto, bool aNotify)
|
||||
{
|
||||
if (aAuto) {
|
||||
SetHasDirAuto();
|
||||
if (IsSingleLineTextControl(true)) {
|
||||
nsAutoString value;
|
||||
GetValue(value);
|
||||
SetDirectionalityFromValue(this, value, aNotify);
|
||||
}
|
||||
} else {
|
||||
ClearHasDirAuto();
|
||||
}
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
nsHTMLInputElement::Reset()
|
||||
{
|
||||
@ -4606,6 +4632,10 @@ NS_IMETHODIMP_(void)
|
||||
nsHTMLInputElement::OnValueChanged(bool aNotify)
|
||||
{
|
||||
UpdateAllValidityStates(aNotify);
|
||||
|
||||
if (HasDirAuto()) {
|
||||
SetDirectionIfAuto(true, aNotify);
|
||||
}
|
||||
}
|
||||
|
||||
NS_IMETHODIMP_(bool)
|
||||
|
@ -511,6 +511,8 @@ protected:
|
||||
*/
|
||||
nsresult SetDefaultValueAsValue();
|
||||
|
||||
virtual void SetDirectionIfAuto(bool aAuto, bool aNotify);
|
||||
|
||||
/**
|
||||
* Return if an element should have a specific validity UI
|
||||
* (with :-moz-ui-invalid and :-moz-ui-valid pseudo-classes).
|
||||
|
@ -15,6 +15,9 @@ public:
|
||||
nsHTMLUnknownElement(already_AddRefed<nsINodeInfo> aNodeInfo)
|
||||
: nsGenericHTMLElement(aNodeInfo)
|
||||
{
|
||||
if (NodeInfo()->Equals(nsGkAtoms::bdi)) {
|
||||
SetHasDirAuto();
|
||||
}
|
||||
}
|
||||
|
||||
// nsISupports
|
||||
|
@ -16,6 +16,9 @@
|
||||
unicode-bidi: embed;
|
||||
}
|
||||
|
||||
bdi:-moz-dir(ltr), [dir="auto"]:-moz-dir(ltr) { direction: ltr; }
|
||||
bdi:-moz-dir(rtl), [dir="auto"]:-moz-dir(rtl) { direction: rtl; }
|
||||
|
||||
/* To ensure http://www.w3.org/TR/REC-html40/struct/dirlang.html#style-bidi:
|
||||
*
|
||||
* "When a block element that does not have a dir attribute is transformed to
|
||||
|
Loading…
Reference in New Issue
Block a user