blob: 814de46b37882f5e2fbd3e7c2a0eff9a1184e788 [file] [log] [blame]
/*
* Copyright (C) 2024 Apple Inc. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY
* EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
* PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
* OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#include "config.h"
#include "ElementTextDirection.h"
#include "ContainerNodeInlines.h"
#include "ElementAncestorIteratorInlines.h"
#include "ElementChildIteratorInlines.h"
#include "ElementRareData.h"
#include "HTMLBDIElement.h"
#include "HTMLInputElement.h"
#include "HTMLSlotElement.h"
#include "HTMLTextFormControlElement.h"
#include "NodeTraversal.h"
#include "PseudoClassChangeInvalidation.h"
#include "TypedElementDescendantIteratorInlines.h"
namespace WebCore {
TextDirectionState parseTextDirectionState(const AtomString& value)
{
if (equalLettersIgnoringASCIICase(value, "ltr"_s))
return TextDirectionState::LTR;
if (equalLettersIgnoringASCIICase(value, "rtl"_s))
return TextDirectionState::RTL;
if (equalLettersIgnoringASCIICase(value, "auto"_s))
return TextDirectionState::Auto;
return TextDirectionState::Undefined;
}
TextDirectionState elementTextDirectionState(const Element& element)
{
return parseTextDirectionState(element.attributeWithoutSynchronization(HTMLNames::dirAttr));
}
bool elementHasValidTextDirectionState(const Element& element)
{
return elementTextDirectionState(element) != TextDirectionState::Undefined;
}
bool elementHasAutoTextDirectionState(const Element& element)
{
auto textDirectionState = elementTextDirectionState(element);
return textDirectionState == TextDirectionState::Auto || (is<HTMLBDIElement>(element) && textDirectionState == TextDirectionState::Undefined);
}
static void updateHasDirAutoFlagForSubtree(Node& firstNode, TextDirectionState textDirectionState)
{
bool dirIsAuto = textDirectionState == TextDirectionState::Auto;
firstNode.setSelfOrPrecedingNodesAffectDirAuto(dirIsAuto);
for (RefPtr node = firstNode.firstChild(); node; ) {
RefPtr element = dynamicDowncast<Element>(*node);
if (element && (is<HTMLBDIElement>(*element) || elementHasValidTextDirectionState(*element))) {
node = NodeTraversal::nextSkippingChildren(*node, &firstNode);
continue;
}
node->setSelfOrPrecedingNodesAffectDirAuto(dirIsAuto);
node = NodeTraversal::next(*node, &firstNode);
}
}
static void updateElementHasDirAutoFlag(Element& element, TextDirectionState textDirectionState)
{
RefPtr parent = element.parentOrShadowHostElement();
element.protectedDocument()->setIsDirAttributeDirty();
switch (textDirectionState) {
case TextDirectionState::LTR:
case TextDirectionState::RTL:
if (element.selfOrPrecedingNodesAffectDirAuto() || (parent && parent->selfOrPrecedingNodesAffectDirAuto()))
updateHasDirAutoFlagForSubtree(element, textDirectionState);
break;
case TextDirectionState::Auto:
updateHasDirAutoFlagForSubtree(element, textDirectionState);
break;
case TextDirectionState::Undefined:
if (element.selfOrPrecedingNodesAffectDirAuto() && !(parent && parent->selfOrPrecedingNodesAffectDirAuto()) && !is<HTMLBDIElement>(element))
updateHasDirAutoFlagForSubtree(element, textDirectionState);
break;
}
}
// Specs: https://html.spec.whatwg.org/multipage/dom.html#text-node-directionality
static std::optional<TextDirection> computeTextDirectionFromText(const String& text)
{
if (auto direction = text.defaultWritingDirection())
return *direction == U_LEFT_TO_RIGHT ? TextDirection::LTR : TextDirection::RTL;
return std::nullopt;
}
// Specs: https://html.spec.whatwg.org/multipage/dom.html#attr-dir
static std::optional<TextDirection> computeTextDirection(const Element& element, TextDirectionState textDirectionState)
{
switch (textDirectionState) {
case TextDirectionState::LTR:
return TextDirection::LTR;
case TextDirectionState::RTL:
return TextDirection::RTL;
case TextDirectionState::Auto:
// Specs: Return the auto directionality of element.
// Specs: If directionality is null, then return 'ltr'.
return computeAutoDirectionality(element).value_or(TextDirection::LTR);
case TextDirectionState::Undefined:
// Specs: If element is a bdi element, then return the auto directionality of element.
// Specs: If directionality is null, then return 'ltr'.
if (is<HTMLBDIElement>(element))
return computeAutoDirectionality(element).value_or(TextDirection::LTR);
// Specs: If element is an input element whose type attribute is in the Telephone state
// then return LTR;
if (auto* input = dynamicDowncast<HTMLInputElement>(element); input && input->isTelephoneField())
return TextDirection::LTR;
// Specs: If the parent is a shadow root, then return the directionality of parent's host.
// Specs: If the parent is an element, then return the directionality of parent.
if (RefPtr parent = element.parentOrShadowHostElement())
return computeTextDirection(*parent, elementTextDirectionState(*parent));
return std::nullopt;
}
return std::nullopt;
}
// Specs: https://html.spec.whatwg.org/multipage/dom.html#contained-text-auto-directionality
static std::optional<TextDirection> computeContainedTextAutoDirection(const Element& element)
{
for (RefPtr child = element.firstChild(); child; ) {
// Specs: Skip bdi, script, style nodes.
if (child->hasTagName(HTMLNames::bdiTag) || child->hasTagName(HTMLNames::scriptTag) || child->hasTagName(HTMLNames::styleTag)) {
child = NodeTraversal::nextSkippingChildren(*child, &element);
continue;
}
if (RefPtr childElement = dynamicDowncast<Element>(*child)) {
// Specs: Skip text form controls
// Specs: Skip the element whose dir attribute is not in the undefined state
if (childElement->isTextField() || elementHasValidTextDirectionState(*childElement)) {
child = NodeTraversal::nextSkippingChildren(*child, &element);
continue;
}
}
// Specs: If child is a slot element whose root is a shadow root,
// then return the directionality of that shadow root's host.
if (RefPtr childSlotElement = dynamicDowncast<HTMLSlotElement>(*child)) {
if (RefPtr childHost = childSlotElement->shadowHost())
return computeTextDirection(*childHost, elementTextDirectionState(*childHost));
}
// Specs: If descendant is a Text node and its text direction is not null,
// then return it.
if (child->isTextNode()) {
if (auto direction = computeTextDirectionFromText(child->textContent(true)))
return direction;
}
child = NodeTraversal::next(*child, &element);
}
return std::nullopt;
}
// Specs: https://html.spec.whatwg.org/multipage/dom.html#auto-directionality
static std::optional<TextDirection> computeTextDirectionOfSlotElement(const HTMLSlotElement& slotElement)
{
// Specs: If element is a slot element whose root is a shadow root
// and element's assigned nodes are not empty, then return the
// directionality of this slot.
if (!slotElement.isInShadowTree())
return computeContainedTextAutoDirection(slotElement);
auto* nodes = slotElement.assignedNodes();
if (!nodes)
return computeContainedTextAutoDirection(slotElement);
for (auto& child : *nodes) {
// Specs: If child is a Text node and its text direction is
// not null, then return it.
if (child->isTextNode()) {
if (auto direction = computeTextDirectionFromText(child->textContent(true)))
return direction;
}
if (RefPtr element = dynamicDowncast<Element>(child.get())) {
// Specs: If child direction is not null, then return child direction.
if (auto direction = computeAutoDirectionality(*element))
return direction;
}
}
return std::nullopt;
}
// Specs: https://html.spec.whatwg.org/multipage/dom.html#auto-directionality
std::optional<TextDirection> computeAutoDirectionality(const Element& element)
{
if (auto* textFormControl = dynamicDowncast<HTMLTextFormControlElement>(element)) {
if (!textFormControl->dirAutoUsesValue())
return computeContainedTextAutoDirection(*textFormControl);
// Specs: The directionality of the auto-directionality form-associated
// element is calculated from its value() text.
// Specs: If element's value is not the empty string, then return 'ltr'.
if (auto value = textFormControl->value(); !value->isEmpty())
return computeTextDirectionFromText(value).value_or(TextDirection::LTR);
return std::nullopt;
}
if (auto* slotElement = dynamicDowncast<HTMLSlotElement>(element))
return computeTextDirectionOfSlotElement(*slotElement);
return computeContainedTextAutoDirection(element);
}
std::optional<TextDirection> computeTextDirectionIfDirIsAuto(const Element& element)
{
if (!(element.selfOrPrecedingNodesAffectDirAuto() && element.hasAutoTextDirectionState()))
return std::nullopt;
return computeTextDirection(element, elementTextDirectionState(element));
}
static void updateEffectiveTextDirectionOfElementAndShadowTree(Element& element, std::optional<TextDirection> direction, Element* initiator = nullptr)
{
bool usesEffectiveTextDirection = !!direction;
auto effectiveDirection = direction.value_or(TextDirection::LTR);
Style::PseudoClassChangeInvalidation styleInvalidation(element, CSSSelector::PseudoClass::Dir, Style::PseudoClassChangeInvalidation::AnyValue);
element.setUsesEffectiveTextDirection(usesEffectiveTextDirection);
element.setEffectiveTextDirection(effectiveDirection);
if (RefPtr shadowRoot = element.shadowRoot()) {
for (Ref child : childrenOfType<Element>(*shadowRoot)) {
if (child.ptr() == initiator)
continue;
updateEffectiveTextDirectionOfElementAndShadowTree(child, direction);
updateEffectiveTextDirectionOfDescendants(child, direction);
}
}
if (element.renderer() && element.renderer()->writingMode().computedTextDirection() != effectiveDirection)
element.invalidateStyleForSubtree();
}
static std::optional<TextDirection> updateEffectiveTextDirectionOfElementAndDescendants(Element& element, TextDirectionState textDirectionState, Element* initiator = nullptr)
{
updateElementHasDirAutoFlag(element, textDirectionState);
auto direction = computeTextDirection(element, textDirectionState);
updateEffectiveTextDirectionOfElementAndShadowTree(element, direction, initiator);
updateEffectiveTextDirectionOfDescendants(element, direction, initiator);
return direction;
}
void textDirectionStateChanged(Element& element, TextDirectionState textDirectionState)
{
updateEffectiveTextDirectionOfElementAndDescendants(element, textDirectionState);
RefPtr parent = element.parentOrShadowHostElement();
if (!parent)
return;
if (parent->selfOrPrecedingNodesAffectDirAuto())
updateEffectiveTextDirectionOfAncestors(*parent, &element);
}
void updateEffectiveTextDirectionState(Element& element, TextDirectionState textDirectionState, Element* initiator)
{
auto direction = updateEffectiveTextDirectionOfElementAndDescendants(element, textDirectionState, initiator);
RefPtr parent = element.parentOrShadowHostElement();
if (!parent)
return;
// This element won't affect its ancestors if its effective direction
// is the same as its parent's effective direction.
if (direction && parent->usesEffectiveTextDirection() && *direction == parent->effectiveTextDirection())
return;
if (parent->selfOrPrecedingNodesAffectDirAuto())
updateEffectiveTextDirectionOfAncestors(*parent, &element);
}
void updateEffectiveTextDirectionOfDescendants(Element& element, std::optional<TextDirection> direction, Element* initiator)
{
for (auto it = descendantsOfType<Element>(element).begin(); it; ) {
Ref child = *it;
if (child.ptr() == initiator || elementHasValidTextDirectionState(child)) {
it.traverseNextSkippingChildren();
continue;
}
updateEffectiveTextDirectionOfElementAndShadowTree(child, direction);
it.traverseNext();
}
}
void updateEffectiveTextDirectionOfAncestors(Element& element, Element* initiator)
{
ASSERT(element.selfOrPrecedingNodesAffectDirAuto());
for (Ref ancestor : lineageOfType<Element>(element)) {
if (!elementHasAutoTextDirectionState(ancestor))
continue;
updateEffectiveTextDirectionState(ancestor, TextDirectionState::Auto, initiator);
break;
}
}
} // namespace WebCore