| // Copyright 2018 The Chromium Authors |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #ifndef THIRD_PARTY_BLINK_RENDERER_MODULES_ACCESSIBILITY_AX_POSITION_H_ |
| #define THIRD_PARTY_BLINK_RENDERER_MODULES_ACCESSIBILITY_AX_POSITION_H_ |
| |
| #include <stdint.h> |
| |
| #include <ostream> |
| |
| #include "base/dcheck_is_on.h" |
| #include "base/logging.h" |
| #include "third_party/blink/renderer/core/editing/forward.h" |
| #include "third_party/blink/renderer/core/editing/text_affinity.h" |
| #include "third_party/blink/renderer/core/layout/inline/offset_mapping.h" |
| #include "third_party/blink/renderer/modules/modules_export.h" |
| #include "third_party/blink/renderer/platform/heap/persistent.h" |
| #include "third_party/blink/renderer/platform/wtf/allocator/allocator.h" |
| #include "third_party/blink/renderer/platform/wtf/text/wtf_string.h" |
| |
| namespace blink { |
| |
| class AXObject; |
| class ContainerNode; |
| class Document; |
| class Node; |
| |
| // When converting to a DOM position from an |AXPosition| or vice versa, and the |
| // corresponding position is invalid, doesn't exist, or is inside an ignored |
| // object or a range of ignored objects, determines how to adjust the new |
| // position in order to make it valid. |
| enum class AXPositionAdjustmentBehavior { kMoveLeft, kMoveRight }; |
| |
| // Describes a position in the Blink accessibility tree. |
| // A position is either anchored to before or after a child object inside a |
| // container object, or is anchored to a character inside a text object. |
| // The former are called tree positions, and the latter text positions. |
| // Tree positions are never located on a specific |AXObject|. Rather, they are |
| // always between two objects, or an object and the start / end of their |
| // container's children, known as "before children" and "after children" |
| // positions respectively. They should be thought of like a caret that is always |
| // between two characters. Another way of calling these types of positions is |
| // object anchored and text anchored. |
| class MODULES_EXPORT AXPosition final { |
| DISALLOW_NEW(); |
| |
| public: |
| // |
| // Convert between DOM and AX positions and vice versa. |
| // |Create...| and |FromPosition| methods will by default skip over any |
| // ignored object and return the next unignored position to the right of that |
| // object. |
| // |
| |
| static const AXPosition CreatePositionBeforeObject( |
| const AXObject& child, |
| const AXPositionAdjustmentBehavior = |
| AXPositionAdjustmentBehavior::kMoveRight); |
| static const AXPosition CreatePositionAfterObject( |
| const AXObject& child, |
| const AXPositionAdjustmentBehavior = |
| AXPositionAdjustmentBehavior::kMoveRight); |
| static const AXPosition CreateFirstPositionInObject( |
| const AXObject& container, |
| const AXPositionAdjustmentBehavior = |
| AXPositionAdjustmentBehavior::kMoveRight); |
| static const AXPosition CreateLastPositionInObject( |
| const AXObject& container, |
| const AXPositionAdjustmentBehavior = |
| AXPositionAdjustmentBehavior::kMoveRight); |
| static const AXPosition CreatePositionInTextObject( |
| const AXObject& container, |
| const int offset, |
| const TextAffinity = TextAffinity::kDownstream, |
| const AXPositionAdjustmentBehavior = |
| AXPositionAdjustmentBehavior::kMoveRight); |
| static const AXPosition FromPosition( |
| const Position&, |
| const TextAffinity = TextAffinity::kDownstream, |
| const AXPositionAdjustmentBehavior = |
| AXPositionAdjustmentBehavior::kMoveRight); |
| static const AXPosition FromPosition( |
| const PositionWithAffinity&, |
| const AXPositionAdjustmentBehavior = |
| AXPositionAdjustmentBehavior::kMoveRight); |
| |
| // Creates an empty position. |IsValid| will return false. |
| AXPosition(); |
| |
| AXPosition(const AXPosition&) = default; |
| AXPosition& operator=(const AXPosition&) = default; |
| ~AXPosition() = default; |
| |
| // The |AXObject| in which the tree position is located, or in whose text the |
| // text position is found. |
| const AXObject* ContainerObject() const { return container_object_; } |
| |
| // Returns |nullptr| for text, and "after children" or equivalent positions. |
| const AXObject* ChildAfterTreePosition() const; |
| |
| // Only valid for tree positions. |
| int ChildIndex() const; |
| |
| // Only valid for text positions. |
| int TextOffset() const; |
| |
| // If this is a text position, the length of the text in its container object. |
| int MaxTextOffset() const; |
| |
| // When the same character offset could correspond to two possible caret |
| // positions, upstream means it's on the previous line rather than the next |
| // line. |
| // Only valid for text positions. |
| TextAffinity Affinity() const; |
| |
| // Verifies if the anchor is present and if it's set to a live object with a |
| // connected node. |
| bool IsValid(String* failure_reason = nullptr) const; |
| |
| operator bool() const { return IsValid(); } |
| |
| // Returns whether this is a position anchored to a character inside a text |
| // object. |
| bool IsTextPosition() const; |
| |
| const AXPosition CreateNextPosition() const; |
| const AXPosition CreatePreviousPosition() const; |
| |
| // Returns an adjusted position by skipping over any ignored objects in the |
| // case of a "before object" or "after object" position, or skipping over any |
| // ignored children in the case of a "before children" or "after children" |
| // position. If a text object is ignored, returns a position anchored at the |
| // nearest object, which might not be a text object. If the container object |
| // is ignored, tries to find if an equivalent position exists in its unignored |
| // parent, since all the children of an ignored object in the accessibility |
| // tree appear as children of its immediate unignored parent. |
| const AXPosition AsUnignoredPosition( |
| const AXPositionAdjustmentBehavior = |
| AXPositionAdjustmentBehavior::kMoveRight) const; |
| |
| // Adjusts the position by skipping over any objects that don't have a |
| // corresponding |node| in the DOM tree, e.g. list bullets. |
| const AXPosition AsValidDOMPosition( |
| const AXPositionAdjustmentBehavior = |
| AXPositionAdjustmentBehavior::kMoveRight) const; |
| |
| // Converts to a DOM position. |
| const PositionWithAffinity ToPositionWithAffinity( |
| const AXPositionAdjustmentBehavior = |
| AXPositionAdjustmentBehavior::kMoveLeft) const; |
| const Position ToPosition(const AXPositionAdjustmentBehavior = |
| AXPositionAdjustmentBehavior::kMoveLeft) const; |
| |
| // Returns a string representation of this object. |
| String ToString() const; |
| |
| private: |
| // Only used by static Create... methods. |
| explicit AXPosition(const AXObject& container); |
| |
| // Searches the DOM tree starting from a particular child node within a |
| // particular container node, and in the direction indicated by the adjustment |
| // behavior, until it finds a node whose corresponding AX object is not |
| // ignored. Returns nullptr if an unignored object is not found within the |
| // provided container node. The container node could be nullptr if the whole |
| // DOM tree needs to be searched. |
| static const AXObject* FindNeighboringUnignoredObject( |
| const Document& document, |
| const Node& child_node, |
| const ContainerNode* container_node, |
| const AXPositionAdjustmentBehavior adjustment_behavior); |
| |
| // Returns true if `character` is not included in the accessible text. |
| // Ignored characters include zero-width space and isolate characters. |
| static bool IsIgnoredCharacter(UChar character); |
| |
| // Returns the number of characters before `content_offset` that are ignored. |
| // OffsetMappingUnits have offsets based on characters that we may exclude |
| // from the text we expose to assistive technologies, such as: |
| // * break opportunities inserted after preliminary whitespace in elements |
| // with `style= "whitespace: pre-wrap;"` |
| // * isolate characters inserted in the content of SVG `text` and tspan` |
| // elements when `x` coordinates are specified. |
| // Examples: |
| // <div contenteditable="true" style="white-space: pre-wrap;"> Bar</div> |
| // * Number of characters in the accessible text: 6 (" Bar") |
| // * Number of characters in the content: 7 |
| // |
| // <text x="0 10 20 30 40 50 60 70 80 90 100 110" |
| // y="20">Hel<tspan>lo </tspan><tspan>world</tspan>!</text> |
| // * Number of characters in the accessible text: 12 ("Hello world!") |
| // * Number of characters in the content: 36 |
| // |
| // The location of these ignored characters can be identified by checking |
| // the OffsetMapping for non-contiguous units. For instance, in the case of |
| // the SVG text, the "H" has a content range of 1-2, the "e" next to it a |
| // content range of 4-5. |
| // |
| // Note that `<wbr>`, whose zero-width-space character is also ignored, does |
| // have a mapping unit and corresponding node. As a result, its character |
| // would not be included in the count returned here. Because it has a node, |
| // we are already associating its offsets with the ignored accessible object. |
| int GetLeadingIgnoredCharacterCount(const OffsetMapping* mapping, |
| const Node* node, |
| int container_offset, |
| int content_offset) const; |
| |
| // The |AXObject| in which the position is present. |
| // Only valid during a single document lifecycle hence no need to maintain a |
| // strong reference to it. |
| WeakPersistent<const AXObject> container_object_; |
| |
| // If the position is anchored to before or after an object, the number of |
| // child objects in |container_object_| that come before the position. |
| // If this is a text position, the number of characters in the canonical text |
| // of |container_object_| before the position. The canonical text is the DOM |
| // node's text but with, e.g., whitespace collapsed and any transformations |
| // applied. |
| int text_offset_or_child_index_; |
| |
| // When the same character offset could correspond to two possible caret |
| // positions. |
| TextAffinity affinity_; |
| |
| #if DCHECK_IS_ON() |
| // TODO(nektar): Use layout tree version in place of DOM and style versions. |
| uint64_t dom_tree_version_; |
| uint64_t style_version_; |
| #endif |
| |
| friend class AXSelection; |
| }; |
| |
| MODULES_EXPORT bool operator==(const AXPosition&, const AXPosition&); |
| MODULES_EXPORT bool operator!=(const AXPosition&, const AXPosition&); |
| MODULES_EXPORT bool operator<(const AXPosition&, const AXPosition&); |
| MODULES_EXPORT bool operator<=(const AXPosition&, const AXPosition&); |
| MODULES_EXPORT bool operator>(const AXPosition&, const AXPosition&); |
| MODULES_EXPORT bool operator>=(const AXPosition&, const AXPosition&); |
| MODULES_EXPORT std::ostream& operator<<(std::ostream&, const AXPosition&); |
| |
| } // namespace blink |
| |
| #endif // THIRD_PARTY_BLINK_RENDERER_MODULES_ACCESSIBILITY_AX_POSITION_H_ |