Commit c37c0da5 authored by Kevin Babbitt's avatar Kevin Babbitt Committed by Commit Bot

Eliminate string conversions and concatenation in MaxTextOffset

MaxTextOffset was computing the total text string for a given node in
UTF-16 just to obtain its length. Taking the length of std::string is
a constant-time operation, so eliminating this effort reduces the scale
factor from number of characters to number of nodes. On a simple test
page this led to a 24% speedup in MaxTextOffset which is used by a
variety of TextPattern methods.

Bug: 970297
Change-Id: Ie40e8032b72a45510a2a9ee6bc0e4c4cf5564627
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/1730094
Commit-Queue: Kevin Babbitt <kbabbitt@microsoft.com>
Reviewed-by: default avatarNektarios Paisios <nektar@chromium.org>
Reviewed-by: default avatarKurt Catti-Schmidt <kschmi@microsoft.com>
Cr-Commit-Position: refs/heads/master@{#683359}
parent c185b2ab
...@@ -43,6 +43,30 @@ base::string16 AXNodePosition::GetText() const { ...@@ -43,6 +43,30 @@ base::string16 AXNodePosition::GetText() const {
return text; return text;
} }
int AXNodePosition::MaxTextOffset() const {
if (IsNullPosition())
return INVALID_INDEX;
const AXNode* anchor = GetAnchor();
DCHECK(anchor);
const std::string& value =
anchor->data().GetStringAttribute(ax::mojom::StringAttribute::kValue);
if (!value.empty())
return value.length();
if (anchor->IsText()) {
return anchor->data()
.GetStringAttribute(ax::mojom::StringAttribute::kName)
.length();
}
int max_text_offset = 0;
for (int i = 0; i < AnchorChildCount(); ++i)
max_text_offset += CreateChildPositionAt(i)->MaxTextOffset();
return max_text_offset;
}
void AXNodePosition::AnchorChild(int child_index, void AXNodePosition::AnchorChild(int child_index,
AXTreeID* tree_id, AXTreeID* tree_id,
int32_t* child_id) const { int32_t* child_id) const {
......
...@@ -27,6 +27,7 @@ class AX_EXPORT AXNodePosition : public AXPosition<AXNodePosition, AXNode> { ...@@ -27,6 +27,7 @@ class AX_EXPORT AXNodePosition : public AXPosition<AXNodePosition, AXNode> {
AXPositionInstance Clone() const override; AXPositionInstance Clone() const override;
int MaxTextOffset() const override;
bool IsInLineBreak() const override; bool IsInLineBreak() const override;
bool IsInTextObject() const override; bool IsInTextObject() const override;
bool IsInWhiteSpace() const override; bool IsInWhiteSpace() const override;
......
...@@ -50,6 +50,38 @@ class AXPositionTest : public testing::Test { ...@@ -50,6 +50,38 @@ class AXPositionTest : public testing::Test {
void SetUp() override; void SetUp() override;
void TearDown() override; void TearDown() override;
void AssertTextLengthEquals(const AXTree* tree,
int32_t node_id,
int expected_text_length) {
TestPositionType text_position = AXNodePosition::CreateTextPosition(
tree->data().tree_id, node_id, 0 /* text_offset */,
ax::mojom::TextAffinity::kUpstream);
ASSERT_NE(nullptr, text_position);
ASSERT_TRUE(text_position->IsTextPosition());
ASSERT_EQ(expected_text_length, text_position->MaxTextOffset());
ASSERT_EQ(expected_text_length,
static_cast<int>(text_position->GetText().length()));
}
// Creates a new AXTree from a vector of nodes.
// Assumes the first node in the vector is the root.
std::unique_ptr<AXTree> CreateAXTree(
const std::vector<ui::AXNodeData>& nodes) {
ui::AXTreeUpdate update;
ui::AXTreeData tree_data;
tree_data.tree_id = ui::AXTreeID::CreateNewAXTreeID();
update.tree_data = tree_data;
update.has_tree_data = true;
update.root_id = nodes[0].id;
update.nodes = nodes;
tree_data.tree_id = ui::AXTreeID::CreateNewAXTreeID();
update.tree_data = tree_data;
std::unique_ptr<AXTree> tree = std::make_unique<AXTree>(update);
AXNodePosition::SetTreeForTesting(tree.get());
return tree;
}
AXNodeData root_; AXNodeData root_;
AXNodeData button_; AXNodeData button_;
AXNodeData check_box_; AXNodeData check_box_;
...@@ -416,6 +448,49 @@ TEST_F(AXPositionTest, GetMaxTextOffsetFromLineBreak) { ...@@ -416,6 +448,49 @@ TEST_F(AXPositionTest, GetMaxTextOffsetFromLineBreak) {
ASSERT_EQ(1, text_position->MaxTextOffset()); ASSERT_EQ(1, text_position->MaxTextOffset());
} }
TEST_F(AXPositionTest, GetMaxTextOffsetUpdate) {
AXNodePosition::SetTreeForTesting(nullptr);
ui::AXNodeData root_data;
root_data.id = 1;
root_data.role = ax::mojom::Role::kRootWebArea;
ui::AXNodeData text_data;
text_data.id = 2;
text_data.role = ax::mojom::Role::kStaticText;
text_data.SetName("some text");
ui::AXNodeData more_text_data;
more_text_data.id = 3;
more_text_data.role = ax::mojom::Role::kStaticText;
more_text_data.SetName("more text");
root_data.child_ids = {2, 3};
std::unique_ptr<AXTree> new_tree =
CreateAXTree({root_data, text_data, more_text_data});
AssertTextLengthEquals(new_tree.get(), text_data.id, 9);
AssertTextLengthEquals(new_tree.get(), root_data.id, 18);
text_data.SetName("Adjusted line 1");
new_tree = CreateAXTree({root_data, text_data, more_text_data});
AssertTextLengthEquals(new_tree.get(), text_data.id, 15);
AssertTextLengthEquals(new_tree.get(), root_data.id, 24);
// Value should override name
text_data.SetValue("Value should override name");
new_tree = CreateAXTree({root_data, text_data, more_text_data});
AssertTextLengthEquals(new_tree.get(), text_data.id, 26);
AssertTextLengthEquals(new_tree.get(), root_data.id, 35);
// An empty value should fall back to name
text_data.SetValue("");
new_tree = CreateAXTree({root_data, text_data, more_text_data});
AssertTextLengthEquals(new_tree.get(), text_data.id, 15);
AssertTextLengthEquals(new_tree.get(), root_data.id, 24);
}
TEST_F(AXPositionTest, AtStartOfAnchorWithNullPosition) { TEST_F(AXPositionTest, AtStartOfAnchorWithNullPosition) {
TestPositionType null_position = AXNodePosition::CreateNullPosition(); TestPositionType null_position = AXNodePosition::CreateNullPosition();
ASSERT_NE(nullptr, null_position); ASSERT_NE(nullptr, null_position);
......
...@@ -1777,7 +1777,7 @@ class AXPosition { ...@@ -1777,7 +1777,7 @@ class AXPosition {
// Returns the length of the text that is present inside the anchor node, // Returns the length of the text that is present inside the anchor node,
// including any text found in descendant text nodes. // including any text found in descendant text nodes.
int MaxTextOffset() const { virtual int MaxTextOffset() const {
if (IsNullPosition()) if (IsNullPosition())
return INVALID_INDEX; return INVALID_INDEX;
return static_cast<int>(GetText().length()); return static_cast<int>(GetText().length());
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment