1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129
|
// Copyright 2022 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "ui/accessibility/ax_selection.h"
#include "ui/accessibility/ax_node_position.h"
namespace ui {
namespace {
// Helper for GetUnignoredSelection. Creates a position using |node_id|,
// |offset| and |affinity|, and if it's ignored, updates these arguments so
// that they represent a non-null non-ignored position, according to
// |adjustment_behavior|. Returns true on success, false on failure. Note that
// if the position is initially null, it's not ignored and it's a success.
bool ComputeUnignoredSelectionEndpoint(
const AXTree* tree,
AXPositionAdjustmentBehavior adjustment_behavior,
AXNodeID& node_id,
int32_t& offset,
ax::mojom::TextAffinity& affinity) {
AXNode* node = tree ? tree->GetFromId(node_id) : nullptr;
if (!node) {
node_id = kInvalidAXNodeID;
offset = AXNodePosition::INVALID_OFFSET;
affinity = ax::mojom::TextAffinity::kDownstream;
return false;
}
AXNodePosition::AXPositionInstance position =
AXNodePosition::CreatePosition(*node, offset, affinity);
// Null positions are never ignored, but must be considered successful, or
// these Android tests would fail:
// org.chromium.content.browser.accessibility.AssistViewStructureTest#*
// The reason is that |position| becomes null because no AXTreeManager is
// registered for that |tree|'s AXTreeID.
// TODO(accessibility): investigate and fix this if needed.
if (!position->IsIgnored()) {
return true; // We assume that unignored positions are already valid.
}
position =
position->AsValidPosition()->AsUnignoredPosition(adjustment_behavior);
// Moving to an unignored position might have placed the position on a leaf
// node. Any selection endpoint that is inside a leaf node is expressed as a
// text position in AXTreeData. (Note that in this context "leaf node" means
// a node with no children or with only ignored children. This does not
// refer to a platform leaf.)
if (position->IsLeafTreePosition())
position = position->AsTextPosition();
// We do not expect the selection to have an endpoint on an inline text
// box as this will create issues with parts of the code that don't use
// inline text boxes.
if (position->IsTextPosition() &&
position->GetRole() == ax::mojom::Role::kInlineTextBox) {
position = position->CreateParentPosition();
}
switch (position->kind()) {
case AXPositionKind::NULL_POSITION:
node_id = kInvalidAXNodeID;
offset = AXNodePosition::INVALID_OFFSET;
affinity = ax::mojom::TextAffinity::kDownstream;
return false;
case AXPositionKind::TREE_POSITION:
node_id = position->anchor_id();
offset = position->child_index();
affinity = ax::mojom::TextAffinity::kDownstream;
return true;
case AXPositionKind::TEXT_POSITION:
node_id = position->anchor_id();
offset = position->text_offset();
affinity = position->affinity();
return true;
}
}
} // namespace
AXSelection::AXSelection() = default;
AXSelection::AXSelection(const AXSelection&) = default;
AXSelection::~AXSelection() = default;
AXSelection::AXSelection(const AXTree& tree)
: is_backward(tree.data().sel_is_backward),
anchor_object_id(tree.data().sel_anchor_object_id),
anchor_offset(tree.data().sel_anchor_offset),
anchor_affinity(tree.data().sel_anchor_affinity),
focus_object_id(tree.data().sel_focus_object_id),
focus_offset(tree.data().sel_focus_offset),
focus_affinity(tree.data().sel_focus_affinity),
tree_id_(tree.GetAXTreeID()) {}
AXSelection& AXSelection::ToUnignoredSelection() {
// If the tree is not registered with an AXTreeManager, it
// is a initial tree with no data, do not calculate selection.
const AXTreeManager* manager = AXTreeManager::FromID(tree_id_);
if (!manager)
return *this;
// If one of the selection endpoints is invalid, then the other endpoint
// should also be unset.
if (!ComputeUnignoredSelectionEndpoint(
manager->ax_tree(),
is_backward ? AXPositionAdjustmentBehavior::kMoveForward
: AXPositionAdjustmentBehavior::kMoveBackward,
anchor_object_id, anchor_offset, anchor_affinity)) {
focus_object_id = kInvalidAXNodeID;
focus_offset = AXNodePosition::INVALID_OFFSET;
focus_affinity = ax::mojom::TextAffinity::kDownstream;
} else if (!ComputeUnignoredSelectionEndpoint(
manager->ax_tree(),
is_backward ? AXPositionAdjustmentBehavior::kMoveBackward
: AXPositionAdjustmentBehavior::kMoveForward,
focus_object_id, focus_offset, focus_affinity)) {
anchor_object_id = kInvalidAXNodeID;
anchor_offset = AXNodePosition::INVALID_OFFSET;
anchor_affinity = ax::mojom::TextAffinity::kDownstream;
}
return *this;
}
} // namespace ui
|