From 4d1d777e706322cb9aca8ed2d5a6e50b805d3bd1 Mon Sep 17 00:00:00 2001 From: "Matt A. Tobin" Date: Fri, 17 Apr 2020 05:50:47 -0400 Subject: Bug 1373798 - Move HTML dir attribute state into event state flags * Stop calling SetHasDirAuto/ClearHasDirAuto in input element code * Introduce event state flags that track the state of an element's dir attribute * Rewrite our existing checks for the state of the dir attr on top of the new event state flags * Add pseudo-classes for matching on the dir attribute states * Use the new dir attribute pseudoclasses in html.css Tag #1375 --- dom/base/DirectionalityUtils.cpp | 4 ++-- dom/base/Element.h | 36 +++++++++++++++++++++++++++--------- dom/base/nsIContentInlines.h | 6 ++++++ dom/base/nsINode.h | 28 ++++++---------------------- dom/events/EventStates.h | 36 +++++++++++++++++++++++++++++++----- dom/html/HTMLInputElement.cpp | 27 ++++++++++----------------- dom/html/HTMLInputElement.h | 2 +- dom/html/HTMLUnknownElement.h | 3 ++- dom/html/nsGenericHTMLElement.cpp | 37 +++++++++++++++++++++++++++++-------- layout/style/nsCSSPseudoClassList.h | 13 +++++++++++++ layout/style/res/html.css | 19 ++++++++++++------- 11 files changed, 139 insertions(+), 72 deletions(-) diff --git a/dom/base/DirectionalityUtils.cpp b/dom/base/DirectionalityUtils.cpp index d9a6c4524..632abb2c8 100644 --- a/dom/base/DirectionalityUtils.cpp +++ b/dom/base/DirectionalityUtils.cpp @@ -728,7 +728,7 @@ WalkDescendantsResetAutoDirection(Element* aElement) { nsIContent* child = aElement->GetFirstChild(); while (child) { - if (child->HasDirAuto()) { + if (child->IsElement() && child->AsElement()->HasDirAuto()) { child = child->GetNextNonChildNode(aElement); continue; } @@ -791,7 +791,7 @@ WalkDescendantsClearAncestorDirAuto(Element* aElement) { nsIContent* child = aElement->GetFirstChild(); while (child) { - if (child->HasDirAuto()) { + if (child->IsElement() && child->AsElement()->HasDirAuto()) { child = child->GetNextNonChildNode(aElement); continue; } diff --git a/dom/base/Element.h b/dom/base/Element.h index df0dbcc45..76f0767e6 100644 --- a/dom/base/Element.h +++ b/dom/base/Element.h @@ -255,6 +255,23 @@ public: */ void ClearStyleStateLocks(); + /** + * Accessors for the state of our dir attribute. + */ + bool HasDirAuto() const + { + return State().HasState(NS_EVENT_STATE_DIR_ATTR_LIKE_AUTO); + } + + /** + * Elements with dir="rtl" or dir="ltr". + */ + bool HasFixedDir() const + { + return State().HasAtLeastOneOfStates(NS_EVENT_STATE_DIR_ATTR_LTR | + NS_EVENT_STATE_DIR_ATTR_RTL); + } + /** * Get the inline style declaration, if any, for this element. */ @@ -380,15 +397,6 @@ 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() || IsHTMLElement(nsGkAtoms::bdi))); - } - Directionality GetComputedDirectionality() const; inline Element* GetFlattenedTreeParentElementForStyle() const; @@ -501,6 +509,16 @@ protected: RemoveStatesSilently(aStates); NotifyStateChange(aStates); } + virtual void ToggleStates(EventStates aStates, bool aNotify) + { + NS_PRECONDITION(!aStates.HasAtLeastOneOfStates(INTRINSIC_STATES), + "Should only be removing externally-managed states here"); + mState ^= aStates; + if (aNotify) { + NotifyStateChange(aStates); + } + } + public: virtual void UpdateEditableState(bool aNotify) override; diff --git a/dom/base/nsIContentInlines.h b/dom/base/nsIContentInlines.h index 6a82f7f65..6a9bd7afa 100644 --- a/dom/base/nsIContentInlines.h +++ b/dom/base/nsIContentInlines.h @@ -87,4 +87,10 @@ nsINode::GetFlattenedTreeParentNodeForStyle() const return ::GetFlattenedTreeParentNode(this); } +inline bool +nsINode::NodeOrAncestorHasDirAuto() const +{ + return AncestorHasDirAuto() || (IsElement() && AsElement()->HasDirAuto()); +} + #endif // nsIContentInlines_h diff --git a/dom/base/nsINode.h b/dom/base/nsINode.h index 0f882445f..7c3eb9134 100644 --- a/dom/base/nsINode.h +++ b/dom/base/nsINode.h @@ -1608,10 +1608,11 @@ private: NodeIsContent, // Set if the node has animations or transitions ElementHasAnimations, - // Set if node has a dir attribute with a valid value (ltr, rtl, or auto) + // Set if node has a dir attribute with a valid value (ltr, rtl, or auto). + // Note that we cannot compute this from the dir attribute event state + // flags, because we can't use those to distinguish + // and . 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, @@ -1619,8 +1620,6 @@ private: // 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, // Set if the element is in the scope of a scoped style sheet; this flag is @@ -1715,17 +1714,6 @@ 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"); @@ -1754,16 +1742,12 @@ public: 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(); } + // Implemented in nsIContentInlines.h. + inline bool NodeOrAncestorHasDirAuto() const; void SetIsElementInStyleScope(bool aValue) { MOZ_ASSERT(IsElement(), "SetIsInStyleScope on a non-Element node"); diff --git a/dom/events/EventStates.h b/dom/events/EventStates.h index 2672d2897..bda36c040 100644 --- a/dom/events/EventStates.h +++ b/dom/events/EventStates.h @@ -292,14 +292,40 @@ private: #define NS_EVENT_STATE_RTL NS_DEFINE_EVENT_STATE_MACRO(43) // Element is highlighted (devtools inspector) #define NS_EVENT_STATE_DEVTOOLS_HIGHLIGHTED NS_DEFINE_EVENT_STATE_MACRO(45) +// States for tracking the state of the "dir" attribute for HTML elements. We +// use these to avoid having to get "dir" attributes all the time during +// selector matching for some parts of the UA stylesheet. +// +// These states are externally managed, because we also don't want to keep +// getting "dir" attributes in IntrinsicState. +// +// Element is HTML and has a "dir" attibute. This state might go away depending +// on how https://github.com/whatwg/html/issues/2769 gets resolved. The value +// could be anything. +#define NS_EVENT_STATE_HAS_DIR_ATTR NS_DEFINE_EVENT_STATE_MACRO(46) +// Element is HTML, has a "dir" attribute, and the attribute's value is +// case-insensitively equal to "ltr". +#define NS_EVENT_STATE_DIR_ATTR_LTR NS_DEFINE_EVENT_STATE_MACRO(47) +// Element is HTML, has a "dir" attribute, and the attribute's value is +// case-insensitively equal to "rtl". +#define NS_EVENT_STATE_DIR_ATTR_RTL NS_DEFINE_EVENT_STATE_MACRO(48) +// Element is HTML, and is either a element with no valid-valued "dir" +// attribute or any HTML element which has a "dir" attribute whose value is +// "auto". +#define NS_EVENT_STATE_DIR_ATTR_LIKE_AUTO NS_DEFINE_EVENT_STATE_MACRO(49) // Element is an unresolved custom element candidate -#define NS_EVENT_STATE_UNRESOLVED NS_DEFINE_EVENT_STATE_MACRO(46) +#define NS_EVENT_STATE_UNRESOLVED NS_DEFINE_EVENT_STATE_MACRO(50) // Element is transitioning for rules changed by style editor -#define NS_EVENT_STATE_STYLEEDITOR_TRANSITIONING NS_DEFINE_EVENT_STATE_MACRO(47) +#define NS_EVENT_STATE_STYLEEDITOR_TRANSITIONING NS_DEFINE_EVENT_STATE_MACRO(51) // Content shows its placeholder -#define NS_EVENT_STATE_PLACEHOLDERSHOWN NS_DEFINE_EVENT_STATE_MACRO(48) +#define NS_EVENT_STATE_PLACEHOLDERSHOWN NS_DEFINE_EVENT_STATE_MACRO(52) // Element has focus-within. -#define NS_EVENT_STATE_FOCUS_WITHIN NS_DEFINE_EVENT_STATE_MACRO(49) +#define NS_EVENT_STATE_FOCUS_WITHIN NS_DEFINE_EVENT_STATE_MACRO(53) + +#define DIR_ATTR_STATES (NS_EVENT_STATE_HAS_DIR_ATTR | \ + NS_EVENT_STATE_DIR_ATTR_LTR | \ + NS_EVENT_STATE_DIR_ATTR_RTL | \ + NS_EVENT_STATE_DIR_ATTR_LIKE_AUTO) // Event state that is used for values that need to be parsed but do nothing. #define NS_EVENT_STATE_IGNORE NS_DEFINE_EVENT_STATE_MACRO(63) @@ -310,7 +336,7 @@ private: #define DIRECTION_STATES (NS_EVENT_STATE_LTR | NS_EVENT_STATE_RTL) -#define ESM_MANAGED_STATES (NS_EVENT_STATE_ACTIVE | NS_EVENT_STATE_FOCUS | \ +#define ESM_MANAGED_STATES (DIR_ATTR_STATES | NS_EVENT_STATE_ACTIVE | NS_EVENT_STATE_FOCUS | \ NS_EVENT_STATE_HOVER | NS_EVENT_STATE_DRAGOVER | \ NS_EVENT_STATE_URLTARGET | NS_EVENT_STATE_FOCUSRING | \ NS_EVENT_STATE_FULL_SCREEN | NS_EVENT_STATE_UNRESOLVED | \ diff --git a/dom/html/HTMLInputElement.cpp b/dom/html/HTMLInputElement.cpp index 7c2688b7e..7708a60ac 100644 --- a/dom/html/HTMLInputElement.cpp +++ b/dom/html/HTMLInputElement.cpp @@ -1264,10 +1264,6 @@ HTMLInputElement::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); } else if (mType == NS_FORM_INPUT_RADIO && aName == nsGkAtoms::required) { nsCOMPtr container = GetRadioGroupContainer(); @@ -1419,7 +1415,7 @@ HTMLInputElement::AfterSetAttr(int32_t aNameSpaceID, nsIAtom* aName, "HTML5 spec does not allow underflow for type=range"); } else if (aName == nsGkAtoms::dir && aValue && aValue->Equals(nsGkAtoms::_auto, eIgnoreCase)) { - SetDirectionIfAuto(true, aNotify); + SetDirectionFromValue(aNotify); } else if (aName == nsGkAtoms::lang) { if (mType == NS_FORM_INPUT_NUMBER) { // Update the value that is displayed to the user to the new locale: @@ -4975,7 +4971,9 @@ HTMLInputElement::BindToTree(nsIDocument* aDocument, nsIContent* aParent, } // Set direction based on value if dir=auto - SetDirectionIfAuto(HasDirAuto(), false); + if (HasDirAuto()) { + SetDirectionFromValue(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. @@ -6693,17 +6691,12 @@ HTMLInputElement::SetDefaultValueAsValue() } void -HTMLInputElement::SetDirectionIfAuto(bool aAuto, bool aNotify) +HTMLInputElement::SetDirectionFromValue(bool aNotify) { - if (aAuto) { - SetHasDirAuto(); - if (IsSingleLineTextControl(true)) { - nsAutoString value; - GetValue(value); - SetDirectionalityFromValue(this, value, aNotify); - } - } else { - ClearHasDirAuto(); + if (IsSingleLineTextControl(true)) { + nsAutoString value; + GetValue(value); + SetDirectionalityFromValue(this, value, aNotify); } } @@ -8480,7 +8473,7 @@ HTMLInputElement::OnValueChanged(bool aNotify, bool aWasInteractiveUserChange) UpdateAllValidityStates(aNotify); if (HasDirAuto()) { - SetDirectionIfAuto(true, aNotify); + SetDirectionFromValue(aNotify); } // :placeholder-shown pseudo-class may change when the value changes. diff --git a/dom/html/HTMLInputElement.h b/dom/html/HTMLInputElement.h index 48807f733..55bb59ec9 100644 --- a/dom/html/HTMLInputElement.h +++ b/dom/html/HTMLInputElement.h @@ -1122,7 +1122,7 @@ protected: */ nsresult SetDefaultValueAsValue(); - virtual void SetDirectionIfAuto(bool aAuto, bool aNotify); + void SetDirectionFromValue(bool aNotify); /** * Return if an element should have a specific validity UI diff --git a/dom/html/HTMLUnknownElement.h b/dom/html/HTMLUnknownElement.h index c77fba919..6390cc576 100644 --- a/dom/html/HTMLUnknownElement.h +++ b/dom/html/HTMLUnknownElement.h @@ -7,6 +7,7 @@ #define mozilla_dom_HTMLUnknownElement_h #include "mozilla/Attributes.h" +#include "mozilla/EventStates.h" #include "nsGenericHTMLElement.h" namespace mozilla { @@ -27,7 +28,7 @@ public: : nsGenericHTMLElement(aNodeInfo) { if (NodeInfo()->Equals(nsGkAtoms::bdi)) { - SetHasDirAuto(); + AddStatesSilently(NS_EVENT_STATE_DIR_ATTR_LIKE_AUTO); } } diff --git a/dom/html/nsGenericHTMLElement.cpp b/dom/html/nsGenericHTMLElement.cpp index be78dc1cf..3cf19ea8f 100644 --- a/dom/html/nsGenericHTMLElement.cpp +++ b/dom/html/nsGenericHTMLElement.cpp @@ -708,28 +708,49 @@ nsGenericHTMLElement::AfterSetAttr(int32_t aNamespaceID, nsIAtom* aName, } else if (aName == nsGkAtoms::dir) { Directionality dir = eDir_LTR; + // A boolean tracking whether we need to recompute our directionality. + // This needs to happen after we update our internal "dir" attribute + // state but before we call SetDirectionalityOnDescendants. + bool recomputeDirectionality = false; + // We don't want to have to keep getting the "dir" attribute in + // IntrinsicState, so we manually recompute our dir-related event states + // here and send the relevant update notifications. + EventStates dirStates; if (aValue && aValue->Type() == nsAttrValue::eEnum) { SetHasValidDir(); + dirStates |= NS_EVENT_STATE_HAS_DIR_ATTR; Directionality dirValue = (Directionality)aValue->GetEnumValue(); if (dirValue == eDir_Auto) { - SetHasDirAuto(); - ClearHasFixedDir(); + dirStates |= NS_EVENT_STATE_DIR_ATTR_LIKE_AUTO; } else { dir = dirValue; SetDirectionality(dir, aNotify); - ClearHasDirAuto(); - SetHasFixedDir(); + if (dirValue == eDir_LTR) { + dirStates |= NS_EVENT_STATE_DIR_ATTR_LTR; + } else { + MOZ_ASSERT(dirValue == eDir_RTL); + dirStates |= NS_EVENT_STATE_DIR_ATTR_RTL; + } } } else { + if (aValue) { + // We have a value, just not a valid one. + dirStates |= NS_EVENT_STATE_HAS_DIR_ATTR; + } ClearHasValidDir(); - ClearHasFixedDir(); if (NodeInfo()->Equals(nsGkAtoms::bdi)) { - SetHasDirAuto(); + dirStates |= NS_EVENT_STATE_DIR_ATTR_LIKE_AUTO; } else { - ClearHasDirAuto(); - dir = RecomputeDirectionality(this, aNotify); + recomputeDirectionality = true; } } + // Now figure out what's changed about our dir states. + EventStates oldDirStates = State() & DIR_ATTR_STATES; + EventStates changedStates = dirStates ^ oldDirStates; + ToggleStates(changedStates, aNotify); + if (recomputeDirectionality) { + dir = RecomputeDirectionality(this, aNotify); + } SetDirectionalityOnDescendants(this, dir, aNotify); } else if (aName == nsGkAtoms::contenteditable) { int32_t editableCountDelta = 0; diff --git a/layout/style/nsCSSPseudoClassList.h b/layout/style/nsCSSPseudoClassList.h index 701578338..6d5b6eca1 100644 --- a/layout/style/nsCSSPseudoClassList.h +++ b/layout/style/nsCSSPseudoClassList.h @@ -211,6 +211,19 @@ CSS_STATE_PSEUDO_CLASS(mozMathIncrementScriptLevel, ":-moz-math-increment-script-level", 0, "", NS_EVENT_STATE_INCREMENT_SCRIPT_LEVEL) +CSS_STATE_PSEUDO_CLASS(mozHasDirAttr, ":-moz-has-dir-attr", + CSS_PSEUDO_CLASS_ENABLED_IN_UA_SHEETS, "", + NS_EVENT_STATE_HAS_DIR_ATTR) +CSS_STATE_PSEUDO_CLASS(mozDirAttrLTR, ":-moz-dir-attr-ltr", + CSS_PSEUDO_CLASS_ENABLED_IN_UA_SHEETS, "", + NS_EVENT_STATE_DIR_ATTR_LTR) +CSS_STATE_PSEUDO_CLASS(mozDirAttrRTL, ":-moz-dir-attr-rtl", + CSS_PSEUDO_CLASS_ENABLED_IN_UA_SHEETS, "", + NS_EVENT_STATE_DIR_ATTR_RTL) +CSS_STATE_PSEUDO_CLASS(mozDirAttrLikeAuto, ":-moz-dir-attr-like-auto", + CSS_PSEUDO_CLASS_ENABLED_IN_UA_SHEETS, "", + NS_EVENT_STATE_DIR_ATTR_LIKE_AUTO) + // CSS 3 UI // http://www.w3.org/TR/2004/CR-css3-ui-20040511/#pseudo-classes CSS_STATE_PSEUDO_CLASS(required, ":required", 0, "", NS_EVENT_STATE_REQUIRED) diff --git a/layout/style/res/html.css b/layout/style/res/html.css index 1f572467f..ea8efbe24 100644 --- a/layout/style/res/html.css +++ b/layout/style/res/html.css @@ -7,18 +7,18 @@ /* bidi */ -[dir] { +:-moz-has-dir-attr { unicode-bidi: isolate; } -[dir="rtl"] { +:-moz-dir-attr-rtl { direction: rtl; } -[dir="ltr"] { +:-moz-dir-attr-ltr { direction: ltr; } -bdi:dir(ltr), [dir="auto"]:dir(ltr) { direction: ltr; } -bdi:dir(rtl), [dir="auto"]:dir(rtl) { direction: rtl; } +:-moz-dir-attr-like-auto:dir(ltr) { direction: ltr; } +:-moz-dir-attr-like-auto:dir(rtl) { direction: rtl; } /* To ensure http://www.w3.org/TR/REC-html40/struct/dirlang.html#style-bidi: * @@ -89,10 +89,15 @@ xmp { bdi, output { unicode-bidi: isolate; } -bdo, bdo[dir] { +/* We need the "bdo:-moz-has-dir-attr" bit because "bdo" has lower + specificity than the ":-moz-has-dir-attr" selector above. */ +bdo, bdo:-moz-has-dir-attr { unicode-bidi: isolate-override; } -textarea[dir="auto"], pre[dir="auto"] { unicode-bidi: plaintext; } +textarea:-moz-dir-attr-like-auto, +pre:-moz-dir-attr-like-auto { + unicode-bidi: plaintext; +} /* blocks */ -- cgit v1.2.3