1 // Copyright 2019 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4
5 #include "ui/accessibility/ax_range.h"
6
7 #include <memory>
8 #include <vector>
9
10 #include "base/strings/string16.h"
11 #include "testing/gmock/include/gmock/gmock.h"
12 #include "testing/gtest/include/gtest/gtest.h"
13 #include "ui/accessibility/ax_enums.mojom.h"
14 #include "ui/accessibility/ax_node.h"
15 #include "ui/accessibility/ax_node_data.h"
16 #include "ui/accessibility/ax_node_position.h"
17 #include "ui/accessibility/ax_tree.h"
18 #include "ui/accessibility/ax_tree_id.h"
19 #include "ui/accessibility/ax_tree_update.h"
20 #include "ui/accessibility/test_ax_node_helper.h"
21 #include "ui/accessibility/test_ax_tree_manager.h"
22
23 namespace ui {
24
25 using TestPositionInstance =
26 std::unique_ptr<AXPosition<AXNodePosition, AXNode>>;
27 using TestPositionRange = AXRange<AXPosition<AXNodePosition, AXNode>>;
28
29 namespace {
30
31 constexpr AXNode::AXID ROOT_ID = 1;
32 constexpr AXNode::AXID DIV1_ID = 2;
33 constexpr AXNode::AXID BUTTON_ID = 3;
34 constexpr AXNode::AXID DIV2_ID = 4;
35 constexpr AXNode::AXID CHECK_BOX1_ID = 5;
36 constexpr AXNode::AXID CHECK_BOX2_ID = 6;
37 constexpr AXNode::AXID TEXT_FIELD_ID = 7;
38 constexpr AXNode::AXID STATIC_TEXT1_ID = 8;
39 constexpr AXNode::AXID INLINE_BOX1_ID = 9;
40 constexpr AXNode::AXID LINE_BREAK1_ID = 10;
41 constexpr AXNode::AXID STATIC_TEXT2_ID = 11;
42 constexpr AXNode::AXID INLINE_BOX2_ID = 12;
43 constexpr AXNode::AXID LINE_BREAK2_ID = 13;
44 constexpr AXNode::AXID PARAGRAPH_ID = 14;
45 constexpr AXNode::AXID STATIC_TEXT3_ID = 15;
46 constexpr AXNode::AXID INLINE_BOX3_ID = 16;
47
48 class TestAXRangeScreenRectDelegate : public AXRangeRectDelegate {
49 public:
TestAXRangeScreenRectDelegate(TestAXTreeManager * tree_manager)50 explicit TestAXRangeScreenRectDelegate(TestAXTreeManager* tree_manager)
51 : tree_manager_(tree_manager) {}
52 virtual ~TestAXRangeScreenRectDelegate() = default;
53 TestAXRangeScreenRectDelegate(const TestAXRangeScreenRectDelegate& delegate) =
54 delete;
55 TestAXRangeScreenRectDelegate& operator=(
56 const TestAXRangeScreenRectDelegate& delegate) = delete;
57
GetInnerTextRangeBoundsRect(AXTreeID tree_id,AXNode::AXID node_id,int start_offset,int end_offset,AXOffscreenResult * offscreen_result)58 gfx::Rect GetInnerTextRangeBoundsRect(
59 AXTreeID tree_id,
60 AXNode::AXID node_id,
61 int start_offset,
62 int end_offset,
63 AXOffscreenResult* offscreen_result) override {
64 if (tree_manager_->GetTreeID() != tree_id)
65 return gfx::Rect();
66
67 AXNode* node = tree_manager_->GetNodeFromTree(node_id);
68 if (!node)
69 return gfx::Rect();
70
71 TestAXNodeHelper* wrapper =
72 TestAXNodeHelper::GetOrCreate(tree_manager_->GetTree(), node);
73 return wrapper->GetInnerTextRangeBoundsRect(
74 start_offset, end_offset, AXCoordinateSystem::kScreenDIPs,
75 AXClippingBehavior::kClipped, offscreen_result);
76 }
77
GetBoundsRect(AXTreeID tree_id,AXNode::AXID node_id,AXOffscreenResult * offscreen_result)78 gfx::Rect GetBoundsRect(AXTreeID tree_id,
79 AXNode::AXID node_id,
80 AXOffscreenResult* offscreen_result) override {
81 if (tree_manager_->GetTreeID() != tree_id)
82 return gfx::Rect();
83
84 AXNode* node = tree_manager_->GetNodeFromTree(node_id);
85 if (!node)
86 return gfx::Rect();
87
88 TestAXNodeHelper* wrapper =
89 TestAXNodeHelper::GetOrCreate(tree_manager_->GetTree(), node);
90 return wrapper->GetBoundsRect(AXCoordinateSystem::kScreenDIPs,
91 AXClippingBehavior::kClipped,
92 offscreen_result);
93 }
94
95 private:
96 TestAXTreeManager* const tree_manager_;
97 };
98
99 class AXRangeTest : public testing::Test, public TestAXTreeManager {
100 public:
101 const base::string16 EMPTY = base::ASCIIToUTF16("");
102 const base::string16 NEWLINE = base::ASCIIToUTF16("\n");
103 const base::string16 BUTTON = base::ASCIIToUTF16("Button");
104 const base::string16 LINE_1 = base::ASCIIToUTF16("Line 1");
105 const base::string16 LINE_2 = base::ASCIIToUTF16("Line 2");
106 const base::string16 TEXT_FIELD =
107 LINE_1.substr().append(NEWLINE).append(LINE_2).append(NEWLINE);
108 const base::string16 AFTER_LINE = base::ASCIIToUTF16("After");
109 const base::string16 ALL_TEXT =
110 BUTTON.substr().append(TEXT_FIELD).append(AFTER_LINE);
111
112 AXRangeTest() = default;
113 ~AXRangeTest() override = default;
114
115 protected:
116 void SetUp() override;
117
118 AXNodeData root_;
119 AXNodeData div1_;
120 AXNodeData div2_;
121 AXNodeData button_;
122 AXNodeData check_box1_;
123 AXNodeData check_box2_;
124 AXNodeData text_field_;
125 AXNodeData line_break1_;
126 AXNodeData line_break2_;
127 AXNodeData static_text1_;
128 AXNodeData static_text2_;
129 AXNodeData static_text3_;
130 AXNodeData inline_box1_;
131 AXNodeData inline_box2_;
132 AXNodeData inline_box3_;
133 AXNodeData paragraph_;
134
135 private:
136 DISALLOW_COPY_AND_ASSIGN(AXRangeTest);
137 };
138
SetUp()139 void AXRangeTest::SetUp() {
140 // Most tests use kSuppressCharacter behavior.
141 g_ax_embedded_object_behavior = AXEmbeddedObjectBehavior::kSuppressCharacter;
142
143 root_.id = ROOT_ID;
144 div1_.id = DIV1_ID;
145 div2_.id = DIV2_ID;
146 button_.id = BUTTON_ID;
147 check_box1_.id = CHECK_BOX1_ID;
148 check_box2_.id = CHECK_BOX2_ID;
149 text_field_.id = TEXT_FIELD_ID;
150 line_break1_.id = LINE_BREAK1_ID;
151 line_break2_.id = LINE_BREAK2_ID;
152 static_text1_.id = STATIC_TEXT1_ID;
153 static_text2_.id = STATIC_TEXT2_ID;
154 static_text3_.id = STATIC_TEXT3_ID;
155 inline_box1_.id = INLINE_BOX1_ID;
156 inline_box2_.id = INLINE_BOX2_ID;
157 inline_box3_.id = INLINE_BOX3_ID;
158 paragraph_.id = PARAGRAPH_ID;
159
160 root_.role = ax::mojom::Role::kDialog;
161 root_.AddState(ax::mojom::State::kFocusable);
162 root_.SetName(ALL_TEXT);
163 root_.relative_bounds.bounds = gfx::RectF(0, 0, 800, 600);
164
165 div1_.role = ax::mojom::Role::kGenericContainer;
166 div1_.AddBoolAttribute(ax::mojom::BoolAttribute::kIsLineBreakingObject, true);
167 div1_.child_ids.push_back(button_.id);
168 div1_.child_ids.push_back(div2_.id);
169 root_.child_ids.push_back(div1_.id);
170
171 button_.role = ax::mojom::Role::kButton;
172 button_.SetHasPopup(ax::mojom::HasPopup::kMenu);
173 button_.SetName(BUTTON);
174 button_.SetNameFrom(ax::mojom::NameFrom::kValue);
175 button_.relative_bounds.bounds = gfx::RectF(20, 20, 100, 30);
176 button_.AddIntAttribute(ax::mojom::IntAttribute::kNextOnLineId,
177 check_box1_.id);
178
179 div2_.role = ax::mojom::Role::kGenericContainer;
180 div2_.child_ids.push_back(check_box1_.id);
181 div2_.child_ids.push_back(check_box2_.id);
182
183 check_box1_.role = ax::mojom::Role::kCheckBox;
184 check_box1_.SetCheckedState(ax::mojom::CheckedState::kTrue);
185 check_box1_.SetName("Checkbox 1");
186 check_box1_.relative_bounds.bounds = gfx::RectF(120, 20, 30, 30);
187 check_box1_.AddIntAttribute(ax::mojom::IntAttribute::kPreviousOnLineId,
188 button_.id);
189 check_box1_.AddIntAttribute(ax::mojom::IntAttribute::kNextOnLineId,
190 check_box2_.id);
191
192 check_box2_.role = ax::mojom::Role::kCheckBox;
193 check_box2_.SetCheckedState(ax::mojom::CheckedState::kTrue);
194 check_box2_.SetName("Checkbox 2");
195 check_box2_.relative_bounds.bounds = gfx::RectF(150, 20, 30, 30);
196 check_box2_.AddIntAttribute(ax::mojom::IntAttribute::kPreviousOnLineId,
197 check_box1_.id);
198
199 text_field_.role = ax::mojom::Role::kTextField;
200 text_field_.AddState(ax::mojom::State::kEditable);
201 text_field_.SetValue(TEXT_FIELD);
202 text_field_.AddIntListAttribute(
203 ax::mojom::IntListAttribute::kCachedLineStarts,
204 std::vector<int32_t>{0, 7});
205 text_field_.child_ids.push_back(static_text1_.id);
206 text_field_.child_ids.push_back(line_break1_.id);
207 text_field_.child_ids.push_back(static_text2_.id);
208 text_field_.child_ids.push_back(line_break2_.id);
209 root_.child_ids.push_back(text_field_.id);
210
211 static_text1_.role = ax::mojom::Role::kStaticText;
212 static_text1_.AddState(ax::mojom::State::kEditable);
213 static_text1_.SetName(LINE_1);
214 static_text1_.child_ids.push_back(inline_box1_.id);
215
216 inline_box1_.role = ax::mojom::Role::kInlineTextBox;
217 inline_box1_.AddState(ax::mojom::State::kEditable);
218 inline_box1_.SetName(LINE_1);
219 inline_box1_.relative_bounds.bounds = gfx::RectF(20, 50, 30, 30);
220 std::vector<int32_t> character_offsets1;
221 // The width of each character is 5px.
222 character_offsets1.push_back(25); // "L" {20, 50, 5x30}
223 character_offsets1.push_back(30); // "i" {25, 50, 5x30}
224 character_offsets1.push_back(35); // "n" {30, 50, 5x30}
225 character_offsets1.push_back(40); // "e" {35, 50, 5x30}
226 character_offsets1.push_back(45); // " " {40, 50, 5x30}
227 character_offsets1.push_back(50); // "1" {45, 50, 5x30}
228 inline_box1_.AddIntListAttribute(
229 ax::mojom::IntListAttribute::kCharacterOffsets, character_offsets1);
230 inline_box1_.AddIntListAttribute(ax::mojom::IntListAttribute::kWordStarts,
231 std::vector<int32_t>{0, 5});
232 inline_box1_.AddIntListAttribute(ax::mojom::IntListAttribute::kWordEnds,
233 std::vector<int32_t>{4, 6});
234 inline_box1_.AddIntAttribute(ax::mojom::IntAttribute::kNextOnLineId,
235 line_break1_.id);
236
237 line_break1_.role = ax::mojom::Role::kLineBreak;
238 line_break1_.AddState(ax::mojom::State::kEditable);
239 line_break1_.SetName(NEWLINE);
240 line_break1_.relative_bounds.bounds = gfx::RectF(50, 50, 0, 30);
241 line_break1_.AddIntAttribute(ax::mojom::IntAttribute::kPreviousOnLineId,
242 inline_box1_.id);
243
244 static_text2_.role = ax::mojom::Role::kStaticText;
245 static_text2_.AddState(ax::mojom::State::kEditable);
246 static_text2_.SetName(LINE_2);
247 static_text2_.child_ids.push_back(inline_box2_.id);
248
249 inline_box2_.role = ax::mojom::Role::kInlineTextBox;
250 inline_box2_.AddState(ax::mojom::State::kEditable);
251 inline_box2_.SetName(LINE_2);
252 inline_box2_.relative_bounds.bounds = gfx::RectF(20, 80, 42, 30);
253 std::vector<int32_t> character_offsets2;
254 // The width of each character is 7 px.
255 character_offsets2.push_back(27); // "L" {20, 80, 7x30}
256 character_offsets2.push_back(34); // "i" {27, 80, 7x30}
257 character_offsets2.push_back(41); // "n" {34, 80, 7x30}
258 character_offsets2.push_back(48); // "e" {41, 80, 7x30}
259 character_offsets2.push_back(55); // " " {48, 80, 7x30}
260 character_offsets2.push_back(62); // "2" {55, 80, 7x30}
261 inline_box2_.AddIntListAttribute(
262 ax::mojom::IntListAttribute::kCharacterOffsets, character_offsets2);
263 inline_box2_.AddIntListAttribute(ax::mojom::IntListAttribute::kWordStarts,
264 std::vector<int32_t>{0, 5});
265 inline_box2_.AddIntListAttribute(ax::mojom::IntListAttribute::kWordEnds,
266 std::vector<int32_t>{4, 6});
267 inline_box2_.AddIntAttribute(ax::mojom::IntAttribute::kNextOnLineId,
268 line_break2_.id);
269
270 line_break2_.role = ax::mojom::Role::kLineBreak;
271 line_break2_.AddState(ax::mojom::State::kEditable);
272 line_break2_.SetName(NEWLINE);
273 line_break2_.relative_bounds.bounds = gfx::RectF(62, 80, 0, 30);
274 line_break2_.AddIntAttribute(ax::mojom::IntAttribute::kPreviousOnLineId,
275 inline_box2_.id);
276
277 paragraph_.role = ax::mojom::Role::kParagraph;
278 paragraph_.AddBoolAttribute(ax::mojom::BoolAttribute::kIsLineBreakingObject,
279 true);
280 paragraph_.child_ids.push_back(static_text3_.id);
281 root_.child_ids.push_back(paragraph_.id);
282
283 static_text3_.role = ax::mojom::Role::kStaticText;
284 static_text3_.SetName(AFTER_LINE);
285 static_text3_.child_ids.push_back(inline_box3_.id);
286
287 inline_box3_.role = ax::mojom::Role::kInlineTextBox;
288 inline_box3_.SetName(AFTER_LINE);
289 inline_box3_.relative_bounds.bounds = gfx::RectF(20, 110, 50, 30);
290 std::vector<int32_t> character_offsets3;
291 // The width of each character is 10 px.
292 character_offsets3.push_back(30); // "A" {20, 110, 10x30}
293 character_offsets3.push_back(40); // "f" {30, 110, 10x30}
294 character_offsets3.push_back(50); // "t" {40, 110, 10x30}
295 character_offsets3.push_back(60); // "e" {50, 110, 10x30}
296 character_offsets3.push_back(70); // "r" {60, 110, 10x30}
297 inline_box3_.AddIntListAttribute(
298 ax::mojom::IntListAttribute::kCharacterOffsets, character_offsets3);
299 inline_box3_.AddIntListAttribute(ax::mojom::IntListAttribute::kWordStarts,
300 std::vector<int32_t>{0});
301 inline_box3_.AddIntListAttribute(ax::mojom::IntListAttribute::kWordEnds,
302 std::vector<int32_t>{5});
303
304 AXTreeUpdate initial_state;
305 initial_state.root_id = 1;
306 initial_state.nodes = {
307 root_, div1_, button_, div2_,
308 check_box1_, check_box2_, text_field_, static_text1_,
309 inline_box1_, line_break1_, static_text2_, inline_box2_,
310 line_break2_, paragraph_, static_text3_, inline_box3_};
311 initial_state.has_tree_data = true;
312 initial_state.tree_data.tree_id = AXTreeID::CreateNewAXTreeID();
313 initial_state.tree_data.title = "Dialog title";
314
315 SetTree(std::make_unique<AXTree>(initial_state));
316 }
317
318 } // namespace
319
TEST_F(AXRangeTest,EqualityOperators)320 TEST_F(AXRangeTest, EqualityOperators) {
321 TestPositionInstance null_position = AXNodePosition::CreateNullPosition();
322 TestPositionInstance test_position1 = AXNodePosition::CreateTextPosition(
323 GetTreeID(), button_.id, 0 /* text_offset */,
324 ax::mojom::TextAffinity::kDownstream);
325 TestPositionInstance test_position2 = AXNodePosition::CreateTextPosition(
326 GetTreeID(), line_break1_.id, 1 /* text_offset */,
327 ax::mojom::TextAffinity::kDownstream);
328 TestPositionInstance test_position3 = AXNodePosition::CreateTextPosition(
329 GetTreeID(), inline_box2_.id, 0 /* text_offset */,
330 ax::mojom::TextAffinity::kDownstream);
331
332 // Invalid ranges (with at least one null endpoint).
333 TestPositionRange null_position_and_nullptr(null_position->Clone(), nullptr);
334 TestPositionRange nullptr_and_test_position(nullptr, test_position1->Clone());
335 TestPositionRange test_position_and_null_position(test_position2->Clone(),
336 null_position->Clone());
337
338 TestPositionRange test_positions_1_and_2(test_position1->Clone(),
339 test_position2->Clone());
340 TestPositionRange test_positions_2_and_1(test_position2->Clone(),
341 test_position1->Clone());
342 TestPositionRange test_positions_1_and_3(test_position1->Clone(),
343 test_position3->Clone());
344 TestPositionRange test_positions_2_and_3(test_position2->Clone(),
345 test_position3->Clone());
346 TestPositionRange test_positions_3_and_2(test_position3->Clone(),
347 test_position2->Clone());
348
349 EXPECT_EQ(null_position_and_nullptr, nullptr_and_test_position);
350 EXPECT_EQ(nullptr_and_test_position, test_position_and_null_position);
351 EXPECT_NE(null_position_and_nullptr, test_positions_2_and_1);
352 EXPECT_NE(test_positions_2_and_1, test_position_and_null_position);
353 EXPECT_EQ(test_positions_1_and_2, test_positions_1_and_2);
354 EXPECT_NE(test_positions_2_and_1, test_positions_1_and_2);
355 EXPECT_EQ(test_positions_3_and_2, test_positions_2_and_3);
356 EXPECT_NE(test_positions_1_and_2, test_positions_2_and_3);
357 EXPECT_EQ(test_positions_1_and_2, test_positions_1_and_3);
358 }
359
TEST_F(AXRangeTest,AsForwardRange)360 TEST_F(AXRangeTest, AsForwardRange) {
361 TestPositionRange null_range(AXNodePosition::CreateNullPosition(),
362 AXNodePosition::CreateNullPosition());
363 null_range = null_range.AsForwardRange();
364 EXPECT_TRUE(null_range.IsNull());
365
366 TestPositionInstance tree_position = AXNodePosition::CreateTreePosition(
367 GetTreeID(), button_.id, 0 /* child_index */);
368 TestPositionInstance text_position1 = AXNodePosition::CreateTextPosition(
369 GetTreeID(), line_break1_.id, 1 /* text_offset */,
370 ax::mojom::TextAffinity::kDownstream);
371 TestPositionInstance text_position2 = AXNodePosition::CreateTextPosition(
372 GetTreeID(), inline_box2_.id, 0 /* text_offset */,
373 ax::mojom::TextAffinity::kDownstream);
374
375 TestPositionRange tree_to_text_range(text_position1->Clone(),
376 tree_position->Clone());
377 tree_to_text_range = tree_to_text_range.AsForwardRange();
378 EXPECT_EQ(*tree_position, *tree_to_text_range.anchor());
379 EXPECT_EQ(*text_position1, *tree_to_text_range.focus());
380
381 TestPositionRange text_to_text_range(text_position2->Clone(),
382 text_position1->Clone());
383 text_to_text_range = text_to_text_range.AsForwardRange();
384 EXPECT_EQ(*text_position1, *text_to_text_range.anchor());
385 EXPECT_EQ(*text_position2, *text_to_text_range.focus());
386 }
387
TEST_F(AXRangeTest,IsCollapsed)388 TEST_F(AXRangeTest, IsCollapsed) {
389 TestPositionRange null_range(AXNodePosition::CreateNullPosition(),
390 AXNodePosition::CreateNullPosition());
391 null_range = null_range.AsForwardRange();
392 EXPECT_FALSE(null_range.IsCollapsed());
393
394 TestPositionInstance tree_position1 = AXNodePosition::CreateTreePosition(
395 GetTreeID(), text_field_.id, 0 /* child_index */);
396 // Since there are no children in inline_box1_, the following is essentially
397 // an "after text" position which should not compare as equivalent to the
398 // above tree position which is a "before text" position inside the text
399 // field.
400 TestPositionInstance tree_position2 = AXNodePosition::CreateTreePosition(
401 GetTreeID(), inline_box1_.id, 0 /* child_index */);
402
403 TestPositionInstance text_position1 = AXNodePosition::CreateTextPosition(
404 GetTreeID(), static_text1_.id, 0 /* text_offset */,
405 ax::mojom::TextAffinity::kDownstream);
406 TestPositionInstance text_position2 = AXNodePosition::CreateTextPosition(
407 GetTreeID(), inline_box1_.id, 0 /* text_offset */,
408 ax::mojom::TextAffinity::kDownstream);
409 TestPositionInstance text_position3 = AXNodePosition::CreateTextPosition(
410 GetTreeID(), inline_box2_.id, 1 /* text_offset */,
411 ax::mojom::TextAffinity::kDownstream);
412
413 TestPositionRange tree_to_null_range(tree_position1->Clone(),
414 AXNodePosition::CreateNullPosition());
415 EXPECT_TRUE(tree_to_null_range.IsNull());
416 EXPECT_FALSE(tree_to_null_range.IsCollapsed());
417
418 TestPositionRange null_to_text_range(AXNodePosition::CreateNullPosition(),
419 text_position1->Clone());
420 EXPECT_TRUE(null_to_text_range.IsNull());
421 EXPECT_FALSE(null_to_text_range.IsCollapsed());
422
423 TestPositionRange tree_to_tree_range(tree_position2->Clone(),
424 tree_position1->Clone());
425 EXPECT_TRUE(tree_to_tree_range.IsCollapsed());
426
427 // A tree and a text position that essentially point to the same text offset
428 // are equivalent, even if they are anchored to a different node.
429 TestPositionRange tree_to_text_range(tree_position1->Clone(),
430 text_position1->Clone());
431 EXPECT_TRUE(tree_to_text_range.IsCollapsed());
432
433 // The following positions are not equivalent since tree_position2 is an
434 // "after text" position.
435 tree_to_text_range =
436 TestPositionRange(tree_position2->Clone(), text_position2->Clone());
437 EXPECT_FALSE(tree_to_text_range.IsCollapsed());
438
439 TestPositionRange text_to_text_range(text_position1->Clone(),
440 text_position1->Clone());
441 EXPECT_TRUE(text_to_text_range.IsCollapsed());
442
443 // Two text positions that essentially point to the same text offset are
444 // equivalent, even if they are anchored to a different node.
445 text_to_text_range =
446 TestPositionRange(text_position1->Clone(), text_position2->Clone());
447 EXPECT_TRUE(text_to_text_range.IsCollapsed());
448
449 text_to_text_range =
450 TestPositionRange(text_position1->Clone(), text_position3->Clone());
451 EXPECT_FALSE(text_to_text_range.IsCollapsed());
452 }
453
TEST_F(AXRangeTest,BeginAndEndIterators)454 TEST_F(AXRangeTest, BeginAndEndIterators) {
455 TestPositionInstance null_position = AXNodePosition::CreateNullPosition();
456 TestPositionInstance test_position1 = AXNodePosition::CreateTextPosition(
457 GetTreeID(), button_.id, 3 /* text_offset */,
458 ax::mojom::TextAffinity::kDownstream);
459 TestPositionInstance test_position2 = AXNodePosition::CreateTextPosition(
460 GetTreeID(), check_box1_.id, 0 /* text_offset */,
461 ax::mojom::TextAffinity::kDownstream);
462 TestPositionInstance test_position3 = AXNodePosition::CreateTextPosition(
463 GetTreeID(), check_box2_.id, 0 /* text_offset */,
464 ax::mojom::TextAffinity::kDownstream);
465 TestPositionInstance test_position4 = AXNodePosition::CreateTextPosition(
466 GetTreeID(), inline_box1_.id, 0 /* text_offset */,
467 ax::mojom::TextAffinity::kDownstream);
468
469 TestPositionRange nullptr_and_null_position(nullptr, null_position->Clone());
470 EXPECT_EQ(TestPositionRange::Iterator(), nullptr_and_null_position.begin());
471 EXPECT_EQ(TestPositionRange::Iterator(), nullptr_and_null_position.end());
472
473 TestPositionRange test_position1_and_nullptr(test_position1->Clone(),
474 nullptr);
475 EXPECT_EQ(TestPositionRange::Iterator(), test_position1_and_nullptr.begin());
476 EXPECT_EQ(TestPositionRange::Iterator(), test_position1_and_nullptr.end());
477
478 TestPositionRange null_position_and_test_position2(null_position->Clone(),
479 test_position2->Clone());
480 EXPECT_EQ(TestPositionRange::Iterator(),
481 null_position_and_test_position2.begin());
482 EXPECT_EQ(TestPositionRange::Iterator(),
483 null_position_and_test_position2.end());
484
485 TestPositionRange test_position1_and_test_position2(test_position1->Clone(),
486 test_position2->Clone());
487 EXPECT_NE(TestPositionRange::Iterator(test_position1->Clone(),
488 test_position4->Clone()),
489 test_position1_and_test_position2.begin());
490 EXPECT_NE(TestPositionRange::Iterator(test_position1->Clone(),
491 test_position3->Clone()),
492 test_position1_and_test_position2.begin());
493 EXPECT_EQ(TestPositionRange::Iterator(test_position1->Clone(),
494 test_position2->Clone()),
495 test_position1_and_test_position2.begin());
496 EXPECT_EQ(TestPositionRange::Iterator(nullptr, test_position2->Clone()),
497 test_position1_and_test_position2.end());
498
499 TestPositionRange test_position3_and_test_position4(test_position3->Clone(),
500 test_position4->Clone());
501 EXPECT_NE(TestPositionRange::Iterator(test_position1->Clone(),
502 test_position4->Clone()),
503 test_position3_and_test_position4.begin());
504 EXPECT_NE(TestPositionRange::Iterator(test_position2->Clone(),
505 test_position4->Clone()),
506 test_position3_and_test_position4.begin());
507 EXPECT_EQ(TestPositionRange::Iterator(test_position3->Clone(),
508 test_position4->Clone()),
509 test_position3_and_test_position4.begin());
510 EXPECT_NE(TestPositionRange::Iterator(nullptr, test_position2->Clone()),
511 test_position3_and_test_position4.end());
512 EXPECT_NE(TestPositionRange::Iterator(nullptr, test_position3->Clone()),
513 test_position3_and_test_position4.end());
514 EXPECT_EQ(TestPositionRange::Iterator(nullptr, test_position4->Clone()),
515 test_position3_and_test_position4.end());
516 }
517
TEST_F(AXRangeTest,LeafTextRangeIteration)518 TEST_F(AXRangeTest, LeafTextRangeIteration) {
519 TestPositionInstance button_start = AXNodePosition::CreateTextPosition(
520 GetTreeID(), button_.id, 0 /* text_offset */,
521 ax::mojom::TextAffinity::kDownstream);
522 TestPositionInstance button_middle = AXNodePosition::CreateTextPosition(
523 GetTreeID(), button_.id, 3 /* text_offset */,
524 ax::mojom::TextAffinity::kDownstream);
525 TestPositionInstance button_end = AXNodePosition::CreateTextPosition(
526 GetTreeID(), button_.id, 6 /* text_offset */,
527 ax::mojom::TextAffinity::kDownstream);
528
529 // Since a check box is not visible to the text representation, it spans an
530 // empty anchor whose start and end positions are the same.
531 TestPositionInstance check_box1 = AXNodePosition::CreateTextPosition(
532 GetTreeID(), check_box1_.id, 0 /* text_offset */,
533 ax::mojom::TextAffinity::kDownstream);
534 TestPositionInstance check_box2 = AXNodePosition::CreateTextPosition(
535 GetTreeID(), check_box2_.id, 0 /* text_offset */,
536 ax::mojom::TextAffinity::kDownstream);
537
538 TestPositionInstance line1_start = AXNodePosition::CreateTextPosition(
539 GetTreeID(), inline_box1_.id, 0 /* text_offset */,
540 ax::mojom::TextAffinity::kDownstream);
541 TestPositionInstance line1_middle = AXNodePosition::CreateTextPosition(
542 GetTreeID(), inline_box1_.id, 3 /* text_offset */,
543 ax::mojom::TextAffinity::kDownstream);
544 TestPositionInstance line1_end = AXNodePosition::CreateTextPosition(
545 GetTreeID(), inline_box1_.id, 6 /* text_offset */,
546 ax::mojom::TextAffinity::kDownstream);
547
548 TestPositionInstance line_break1_start = AXNodePosition::CreateTextPosition(
549 GetTreeID(), line_break1_.id, 0 /* text_offset */,
550 ax::mojom::TextAffinity::kDownstream);
551 TestPositionInstance line_break1_end = AXNodePosition::CreateTextPosition(
552 GetTreeID(), line_break1_.id, 1 /* text_offset */,
553 ax::mojom::TextAffinity::kDownstream);
554
555 TestPositionInstance line2_start = AXNodePosition::CreateTextPosition(
556 GetTreeID(), inline_box2_.id, 0 /* text_offset */,
557 ax::mojom::TextAffinity::kDownstream);
558 TestPositionInstance line2_middle = AXNodePosition::CreateTextPosition(
559 GetTreeID(), inline_box2_.id, 3 /* text_offset */,
560 ax::mojom::TextAffinity::kDownstream);
561 TestPositionInstance line2_end = AXNodePosition::CreateTextPosition(
562 GetTreeID(), inline_box2_.id, 6 /* text_offset */,
563 ax::mojom::TextAffinity::kDownstream);
564
565 TestPositionInstance line_break2_start = AXNodePosition::CreateTextPosition(
566 GetTreeID(), line_break2_.id, 0 /* text_offset */,
567 ax::mojom::TextAffinity::kDownstream);
568 TestPositionInstance line_break2_end = AXNodePosition::CreateTextPosition(
569 GetTreeID(), line_break2_.id, 1 /* text_offset */,
570 ax::mojom::TextAffinity::kDownstream);
571
572 TestPositionInstance after_line_start = AXNodePosition::CreateTextPosition(
573 GetTreeID(), inline_box3_.id, 0 /* text_offset */,
574 ax::mojom::TextAffinity::kDownstream);
575 TestPositionInstance after_line_end = AXNodePosition::CreateTextPosition(
576 GetTreeID(), inline_box3_.id, 5 /* text_offset */,
577 ax::mojom::TextAffinity::kDownstream);
578
579 std::vector<TestPositionRange> expected_ranges;
580 auto TestRangeIterator =
581 [&expected_ranges](const TestPositionRange& test_range) {
582 std::vector<TestPositionRange> actual_ranges;
583 for (TestPositionRange leaf_text_range : test_range) {
584 EXPECT_TRUE(leaf_text_range.IsLeafTextRange());
585 actual_ranges.emplace_back(std::move(leaf_text_range));
586 }
587
588 EXPECT_EQ(expected_ranges.size(), actual_ranges.size());
589 size_t element_count =
590 std::min(expected_ranges.size(), actual_ranges.size());
591 for (size_t i = 0; i < element_count; ++i) {
592 EXPECT_EQ(expected_ranges[i], actual_ranges[i]);
593 EXPECT_EQ(expected_ranges[i].anchor()->GetAnchor(),
594 actual_ranges[i].anchor()->GetAnchor());
595 }
596 };
597
598 // Iterating over a null range; note that expected_ranges is empty.
599 TestRangeIterator(TestPositionRange(nullptr, nullptr));
600
601 TestPositionRange non_null_degenerate_range(check_box1->Clone(),
602 check_box1->Clone());
603 expected_ranges.clear();
604 expected_ranges.emplace_back(check_box1->Clone(), check_box1->Clone());
605 TestRangeIterator(non_null_degenerate_range);
606
607 TestPositionRange empty_text_forward_range(button_end->Clone(),
608 line1_start->Clone());
609 TestPositionRange empty_text_backward_range(line1_start->Clone(),
610 button_end->Clone());
611 expected_ranges.clear();
612 expected_ranges.emplace_back(button_end->Clone(), button_end->Clone());
613 expected_ranges.emplace_back(check_box1->Clone(), check_box1->Clone());
614 expected_ranges.emplace_back(check_box2->Clone(), check_box2->Clone());
615 expected_ranges.emplace_back(line1_start->Clone(), line1_start->Clone());
616 TestRangeIterator(empty_text_forward_range);
617 TestRangeIterator(empty_text_backward_range);
618
619 TestPositionRange entire_anchor_forward_range(button_start->Clone(),
620 button_end->Clone());
621 TestPositionRange entire_anchor_backward_range(button_end->Clone(),
622 button_start->Clone());
623 expected_ranges.clear();
624 expected_ranges.emplace_back(button_start->Clone(), button_end->Clone());
625 TestRangeIterator(entire_anchor_forward_range);
626 TestRangeIterator(entire_anchor_backward_range);
627
628 TestPositionRange across_anchors_forward_range(button_middle->Clone(),
629 line1_middle->Clone());
630 TestPositionRange across_anchors_backward_range(line1_middle->Clone(),
631 button_middle->Clone());
632 expected_ranges.clear();
633 expected_ranges.emplace_back(button_middle->Clone(), button_end->Clone());
634 expected_ranges.emplace_back(check_box1->Clone(), check_box1->Clone());
635 expected_ranges.emplace_back(check_box2->Clone(), check_box2->Clone());
636 expected_ranges.emplace_back(line1_start->Clone(), line1_middle->Clone());
637 TestRangeIterator(across_anchors_forward_range);
638 TestRangeIterator(across_anchors_backward_range);
639
640 TestPositionRange starting_at_end_position_forward_range(
641 line1_end->Clone(), line2_middle->Clone());
642 TestPositionRange starting_at_end_position_backward_range(
643 line2_middle->Clone(), line1_end->Clone());
644 expected_ranges.clear();
645 expected_ranges.emplace_back(line1_end->Clone(), line1_end->Clone());
646 expected_ranges.emplace_back(line_break1_start->Clone(),
647 line_break1_end->Clone());
648 expected_ranges.emplace_back(line2_start->Clone(), line2_middle->Clone());
649 TestRangeIterator(starting_at_end_position_forward_range);
650 TestRangeIterator(starting_at_end_position_backward_range);
651
652 TestPositionRange ending_at_start_position_forward_range(
653 line1_middle->Clone(), line2_start->Clone());
654 TestPositionRange ending_at_start_position_backward_range(
655 line2_start->Clone(), line1_middle->Clone());
656 expected_ranges.clear();
657 expected_ranges.emplace_back(line1_middle->Clone(), line1_end->Clone());
658 expected_ranges.emplace_back(line_break1_start->Clone(),
659 line_break1_end->Clone());
660 expected_ranges.emplace_back(line2_start->Clone(), line2_start->Clone());
661 TestRangeIterator(ending_at_start_position_forward_range);
662 TestRangeIterator(ending_at_start_position_backward_range);
663
664 TestPositionInstance range_start = AXNodePosition::CreateTreePosition(
665 GetTreeID(), root_.id, 0 /* child_index */);
666 TestPositionInstance range_end = AXNodePosition::CreateTextPosition(
667 GetTreeID(), root_.id, ALL_TEXT.length() /* text_offset */,
668 ax::mojom::TextAffinity::kDownstream);
669
670 TestPositionRange entire_test_forward_range(range_start->Clone(),
671 range_end->Clone());
672 TestPositionRange entire_test_backward_range(range_end->Clone(),
673 range_start->Clone());
674 expected_ranges.clear();
675 expected_ranges.emplace_back(button_start->Clone(), button_end->Clone());
676 expected_ranges.emplace_back(check_box1->Clone(), check_box1->Clone());
677 expected_ranges.emplace_back(check_box2->Clone(), check_box2->Clone());
678 expected_ranges.emplace_back(line1_start->Clone(), line1_end->Clone());
679 expected_ranges.emplace_back(line_break1_start->Clone(),
680 line_break1_end->Clone());
681 expected_ranges.emplace_back(line2_start->Clone(), line2_end->Clone());
682 expected_ranges.emplace_back(line_break2_start->Clone(),
683 line_break2_end->Clone());
684 expected_ranges.emplace_back(after_line_start->Clone(),
685 after_line_end->Clone());
686 TestRangeIterator(entire_test_forward_range);
687 TestRangeIterator(entire_test_backward_range);
688 }
689
TEST_F(AXRangeTest,GetTextWithWholeObjects)690 TEST_F(AXRangeTest, GetTextWithWholeObjects) {
691 // Create a range starting from the button object and ending at the last
692 // character of the root, i.e. at the last character of the second line in the
693 // text field.
694 TestPositionInstance start = AXNodePosition::CreateTreePosition(
695 GetTreeID(), root_.id, 0 /* child_index */);
696 TestPositionInstance end = AXNodePosition::CreateTextPosition(
697 GetTreeID(), root_.id, ALL_TEXT.length() /* text_offset */,
698 ax::mojom::TextAffinity::kDownstream);
699 ASSERT_TRUE(end->IsTextPosition());
700 TestPositionRange forward_range(start->Clone(), end->Clone());
701 EXPECT_EQ(ALL_TEXT, forward_range.GetText());
702 TestPositionRange backward_range(std::move(end), std::move(start));
703 EXPECT_EQ(ALL_TEXT, backward_range.GetText());
704
705 // Button
706 start = AXNodePosition::CreateTextPosition(
707 GetTreeID(), button_.id, 0 /* text_offset */,
708 ax::mojom::TextAffinity::kDownstream);
709 ASSERT_TRUE(start->IsTextPosition());
710 end = AXNodePosition::CreateTextPosition(
711 GetTreeID(), button_.id, BUTTON.length() /* text_offset */,
712 ax::mojom::TextAffinity::kDownstream);
713 ASSERT_TRUE(end->IsTextPosition());
714 TestPositionRange button_range(start->Clone(), end->Clone());
715 EXPECT_EQ(BUTTON, button_range.GetText());
716 TestPositionRange button_range_backward(std::move(end), std::move(start));
717 EXPECT_EQ(BUTTON, button_range_backward.GetText());
718
719 // text_field_
720 start = AXNodePosition::CreateTextPosition(
721 GetTreeID(), text_field_.id, 0 /* text_offset */,
722 ax::mojom::TextAffinity::kDownstream);
723 end = AXNodePosition::CreateTextPosition(
724 GetTreeID(), text_field_.id, TEXT_FIELD.length() /* text_offset */,
725 ax::mojom::TextAffinity::kDownstream);
726 ASSERT_TRUE(start->IsTextPosition());
727 ASSERT_TRUE(end->IsTextPosition());
728 TestPositionRange text_field_range(start->Clone(), end->Clone());
729 EXPECT_EQ(TEXT_FIELD, text_field_range.GetText());
730 TestPositionRange text_field_range_backward(std::move(end), std::move(start));
731 EXPECT_EQ(TEXT_FIELD, text_field_range_backward.GetText());
732
733 // static_text1_
734 start = AXNodePosition::CreateTextPosition(
735 GetTreeID(), static_text1_.id, 0 /* text_offset */,
736 ax::mojom::TextAffinity::kDownstream);
737 ASSERT_TRUE(start->IsTextPosition());
738 end = AXNodePosition::CreateTextPosition(
739 GetTreeID(), static_text1_.id, LINE_1.length() /* text_offset */,
740 ax::mojom::TextAffinity::kDownstream);
741 ASSERT_TRUE(end->IsTextPosition());
742 TestPositionRange static_text1_range(start->Clone(), end->Clone());
743 EXPECT_EQ(LINE_1, static_text1_range.GetText());
744 TestPositionRange static_text1_range_backward(std::move(end),
745 std::move(start));
746 EXPECT_EQ(LINE_1, static_text1_range_backward.GetText());
747
748 // static_text2_
749 start = AXNodePosition::CreateTextPosition(
750 GetTreeID(), static_text2_.id, 0 /* text_offset */,
751 ax::mojom::TextAffinity::kDownstream);
752 ASSERT_TRUE(start->IsTextPosition());
753 end = AXNodePosition::CreateTextPosition(
754 GetTreeID(), static_text2_.id, LINE_2.length() /* text_offset */,
755 ax::mojom::TextAffinity::kDownstream);
756 ASSERT_TRUE(end->IsTextPosition());
757 TestPositionRange static_text2_range(start->Clone(), end->Clone());
758 EXPECT_EQ(LINE_2, static_text2_range.GetText());
759 TestPositionRange static_text2_range_backward(std::move(end),
760 std::move(start));
761 EXPECT_EQ(LINE_2, static_text2_range_backward.GetText());
762
763 // static_text1_ to static_text2_
764 base::string16 text_between_text1_start_and_text2_end =
765 LINE_1.substr().append(NEWLINE).append(LINE_2);
766 start = AXNodePosition::CreateTextPosition(
767 GetTreeID(), static_text1_.id, 0 /* text_offset */,
768 ax::mojom::TextAffinity::kDownstream);
769 ASSERT_TRUE(start->IsTextPosition());
770 end = AXNodePosition::CreateTextPosition(
771 GetTreeID(), static_text2_.id, LINE_2.length() /* text_offset */,
772 ax::mojom::TextAffinity::kDownstream);
773 ASSERT_TRUE(end->IsTextPosition());
774 TestPositionRange static_text_range(start->Clone(), end->Clone());
775 EXPECT_EQ(text_between_text1_start_and_text2_end,
776 static_text_range.GetText());
777 TestPositionRange static_text_range_backward(std::move(end),
778 std::move(start));
779 EXPECT_EQ(text_between_text1_start_and_text2_end,
780 static_text_range_backward.GetText());
781
782 // root_ to static_text2_'s end
783 base::string16 text_up_to_text2_end =
784 BUTTON.substr(0).append(LINE_1).append(NEWLINE).append(LINE_2);
785 start = AXNodePosition::CreateTreePosition(GetTreeID(), root_.id,
786 0 /* child_index */);
787 end = AXNodePosition::CreateTextPosition(
788 GetTreeID(), static_text2_.id, LINE_2.length() /* text_offset */,
789 ax::mojom::TextAffinity::kDownstream);
790 ASSERT_TRUE(end->IsTextPosition());
791 TestPositionRange root_to_static2_text_range(start->Clone(), end->Clone());
792 EXPECT_EQ(text_up_to_text2_end, root_to_static2_text_range.GetText());
793 TestPositionRange root_to_static2_text_range_backward(std::move(end),
794 std::move(start));
795 EXPECT_EQ(text_up_to_text2_end,
796 root_to_static2_text_range_backward.GetText());
797
798 // root_ to static_text2_'s start
799 base::string16 text_up_to_text2_start =
800 BUTTON.substr(0).append(LINE_1).append(NEWLINE);
801 start = AXNodePosition::CreateTreePosition(GetTreeID(), root_.id,
802 0 /* child_index */);
803 end = AXNodePosition::CreateTreePosition(GetTreeID(), static_text2_.id,
804 0 /* child_index */);
805 TestPositionRange root_to_static2_tree_range(start->Clone(), end->Clone());
806 EXPECT_EQ(text_up_to_text2_start, root_to_static2_tree_range.GetText());
807 TestPositionRange root_to_static2_tree_range_backward(std::move(end),
808 std::move(start));
809 EXPECT_EQ(text_up_to_text2_start,
810 root_to_static2_tree_range_backward.GetText());
811 }
812
TEST_F(AXRangeTest,GetTextWithTextOffsets)813 TEST_F(AXRangeTest, GetTextWithTextOffsets) {
814 base::string16 most_text = BUTTON.substr(2).append(TEXT_FIELD.substr(0, 11));
815 // Create a range starting from the button object and ending two characters
816 // before the end of the root.
817 TestPositionInstance start = AXNodePosition::CreateTextPosition(
818 GetTreeID(), button_.id, 2 /* text_offset */,
819 ax::mojom::TextAffinity::kDownstream);
820 ASSERT_TRUE(start->IsTextPosition());
821 TestPositionInstance end = AXNodePosition::CreateTextPosition(
822 GetTreeID(), static_text2_.id, 4 /* text_offset */,
823 ax::mojom::TextAffinity::kDownstream);
824 ASSERT_TRUE(end->IsTextPosition());
825 TestPositionRange forward_range(start->Clone(), end->Clone());
826 EXPECT_EQ(most_text, forward_range.GetText());
827 TestPositionRange backward_range(std::move(end), std::move(start));
828 EXPECT_EQ(most_text, backward_range.GetText());
829
830 // root_ to static_text2_'s start with offsets
831 base::string16 text_up_to_text2_tree_start =
832 BUTTON.substr(0).append(TEXT_FIELD.substr(0, 10));
833 start = AXNodePosition::CreateTreePosition(GetTreeID(), root_.id,
834 0 /* child_index */);
835 end = AXNodePosition::CreateTextPosition(
836 GetTreeID(), static_text2_.id, 3 /* text_offset */,
837 ax::mojom::TextAffinity::kDownstream);
838 ASSERT_TRUE(end->IsTextPosition());
839 TestPositionRange root_to_static2_tree_range(start->Clone(), end->Clone());
840 EXPECT_EQ(text_up_to_text2_tree_start, root_to_static2_tree_range.GetText());
841 TestPositionRange root_to_static2_tree_range_backward(std::move(end),
842 std::move(start));
843 EXPECT_EQ(text_up_to_text2_tree_start,
844 root_to_static2_tree_range_backward.GetText());
845 }
846
TEST_F(AXRangeTest,GetTextWithEmptyRanges)847 TEST_F(AXRangeTest, GetTextWithEmptyRanges) {
848 // empty string with non-leaf tree position
849 TestPositionInstance start = AXNodePosition::CreateTreePosition(
850 GetTreeID(), root_.id, 0 /* child_index */);
851 TestPositionRange non_leaf_tree_range(start->Clone(), start->Clone());
852 EXPECT_EQ(EMPTY, non_leaf_tree_range.GetText());
853
854 // empty string with leaf tree position
855 start = AXNodePosition::CreateTreePosition(GetTreeID(), inline_box1_.id,
856 0 /* child_index */);
857 TestPositionRange leaf_empty_range(start->Clone(), start->Clone());
858 EXPECT_EQ(EMPTY, leaf_empty_range.GetText());
859
860 // empty string with leaf text position and no offset
861 start = AXNodePosition::CreateTextPosition(
862 GetTreeID(), inline_box1_.id, 0 /* text_offset */,
863 ax::mojom::TextAffinity::kDownstream);
864 TestPositionRange leaf_text_no_offset(start->Clone(), start->Clone());
865 EXPECT_EQ(EMPTY, leaf_text_no_offset.GetText());
866
867 // empty string with leaf text position with offset
868 start = AXNodePosition::CreateTextPosition(
869 GetTreeID(), inline_box1_.id, 3 /* text_offset */,
870 ax::mojom::TextAffinity::kDownstream);
871 TestPositionRange leaf_text_offset(start->Clone(), start->Clone());
872 EXPECT_EQ(EMPTY, leaf_text_offset.GetText());
873
874 // empty string with non-leaf text with no offset
875 start = AXNodePosition::CreateTextPosition(
876 GetTreeID(), root_.id, 0 /* text_offset */,
877 ax::mojom::TextAffinity::kDownstream);
878 TestPositionRange non_leaf_text_no_offset(start->Clone(), start->Clone());
879 EXPECT_EQ(EMPTY, non_leaf_text_no_offset.GetText());
880
881 // empty string with non-leaf text position with offset
882 start = AXNodePosition::CreateTextPosition(
883 GetTreeID(), root_.id, 3 /* text_offset */,
884 ax::mojom::TextAffinity::kDownstream);
885 TestPositionRange non_leaf_text_offset(start->Clone(), start->Clone());
886 EXPECT_EQ(EMPTY, non_leaf_text_offset.GetText());
887
888 // empty string with same position between two anchors, but different offsets
889 TestPositionInstance after_end = AXNodePosition::CreateTextPosition(
890 GetTreeID(), line_break1_.id, 1 /* text_offset */,
891 ax::mojom::TextAffinity::kDownstream);
892 TestPositionInstance before_start = AXNodePosition::CreateTextPosition(
893 GetTreeID(), static_text2_.id, 0 /* text_offset */,
894 ax::mojom::TextAffinity::kDownstream);
895
896 TestPositionRange same_position_different_anchors_forward(
897 after_end->Clone(), before_start->Clone());
898 EXPECT_EQ(EMPTY, same_position_different_anchors_forward.GetText());
899 TestPositionRange same_position_different_anchors_backward(
900 before_start->Clone(), after_end->Clone());
901 EXPECT_EQ(EMPTY, same_position_different_anchors_backward.GetText());
902 }
903
TEST_F(AXRangeTest,GetTextAddingNewlineBetweenParagraphs)904 TEST_F(AXRangeTest, GetTextAddingNewlineBetweenParagraphs) {
905 TestPositionInstance button_start = AXNodePosition::CreateTextPosition(
906 GetTreeID(), button_.id, 0 /* text_offset */,
907 ax::mojom::TextAffinity::kDownstream);
908 TestPositionInstance button_end = AXNodePosition::CreateTextPosition(
909 GetTreeID(), button_.id, 6 /* text_offset */,
910 ax::mojom::TextAffinity::kDownstream);
911
912 TestPositionInstance line1_start = AXNodePosition::CreateTextPosition(
913 GetTreeID(), inline_box1_.id, 0 /* text_offset */,
914 ax::mojom::TextAffinity::kDownstream);
915 TestPositionInstance line1_end = AXNodePosition::CreateTextPosition(
916 GetTreeID(), inline_box1_.id, 6 /* text_offset */,
917 ax::mojom::TextAffinity::kDownstream);
918
919 TestPositionInstance line2_start = AXNodePosition::CreateTextPosition(
920 GetTreeID(), inline_box2_.id, 0 /* text_offset */,
921 ax::mojom::TextAffinity::kDownstream);
922 TestPositionInstance line2_end = AXNodePosition::CreateTextPosition(
923 GetTreeID(), inline_box2_.id, 6 /* text_offset */,
924 ax::mojom::TextAffinity::kDownstream);
925
926 TestPositionInstance after_line_start = AXNodePosition::CreateTextPosition(
927 GetTreeID(), inline_box3_.id, 0 /* text_offset */,
928 ax::mojom::TextAffinity::kDownstream);
929 TestPositionInstance after_line_end = AXNodePosition::CreateTextPosition(
930 GetTreeID(), inline_box3_.id, 5 /* text_offset */,
931 ax::mojom::TextAffinity::kDownstream);
932
933 auto TestGetTextForRange = [](TestPositionInstance range_start,
934 TestPositionInstance range_end,
935 const base::string16& expected_text,
936 const size_t expected_appended_newlines_count) {
937 TestPositionRange forward_test_range(range_start->Clone(),
938 range_end->Clone());
939 TestPositionRange backward_test_range(std::move(range_end),
940 std::move(range_start));
941 size_t appended_newlines_count = 0;
942 EXPECT_EQ(expected_text, forward_test_range.GetText(
943 AXTextConcatenationBehavior::kAsInnerText, -1,
944 false, &appended_newlines_count));
945 EXPECT_EQ(expected_appended_newlines_count, appended_newlines_count);
946 EXPECT_EQ(expected_text, backward_test_range.GetText(
947 AXTextConcatenationBehavior::kAsInnerText, -1,
948 false, &appended_newlines_count));
949 EXPECT_EQ(expected_appended_newlines_count, appended_newlines_count);
950 };
951
952 base::string16 button_start_to_line1_end =
953 BUTTON.substr().append(NEWLINE).append(LINE_1);
954 TestGetTextForRange(button_start->Clone(), line1_end->Clone(),
955 button_start_to_line1_end, 1);
956 base::string16 button_start_to_line1_start = BUTTON.substr().append(NEWLINE);
957 TestGetTextForRange(button_start->Clone(), line1_start->Clone(),
958 button_start_to_line1_start, 1);
959 base::string16 button_end_to_line1_end = NEWLINE.substr().append(LINE_1);
960 TestGetTextForRange(button_end->Clone(), line1_end->Clone(),
961 button_end_to_line1_end, 1);
962 base::string16 button_end_to_line1_start = NEWLINE;
963 TestGetTextForRange(button_end->Clone(), line1_start->Clone(),
964 button_end_to_line1_start, 1);
965
966 base::string16 line2_start_to_after_line_end =
967 LINE_2.substr().append(NEWLINE).append(AFTER_LINE);
968 TestGetTextForRange(line2_start->Clone(), after_line_end->Clone(),
969 line2_start_to_after_line_end, 0);
970 base::string16 line2_start_to_after_line_start =
971 LINE_2.substr().append(NEWLINE);
972 TestGetTextForRange(line2_start->Clone(), after_line_start->Clone(),
973 line2_start_to_after_line_start, 0);
974 base::string16 line2_end_to_after_line_end =
975 NEWLINE.substr().append(AFTER_LINE);
976 TestGetTextForRange(line2_end->Clone(), after_line_end->Clone(),
977 line2_end_to_after_line_end, 0);
978 base::string16 line2_end_to_after_line_start = NEWLINE;
979 TestGetTextForRange(line2_end->Clone(), after_line_start->Clone(),
980 line2_end_to_after_line_start, 0);
981
982 base::string16 all_text =
983 BUTTON.substr().append(NEWLINE).append(TEXT_FIELD).append(AFTER_LINE);
984 TestPositionInstance start = AXNodePosition::CreateTextPosition(
985 GetTreeID(), root_.id, 0 /* text_offset */,
986 ax::mojom::TextAffinity::kDownstream);
987 TestPositionInstance end = AXNodePosition::CreateTextPosition(
988 GetTreeID(), root_.id, ALL_TEXT.length() /* text_offset */,
989 ax::mojom::TextAffinity::kDownstream);
990 TestGetTextForRange(std::move(start), std::move(end), all_text, 1);
991 }
992
TEST_F(AXRangeTest,GetTextWithMaxCount)993 TEST_F(AXRangeTest, GetTextWithMaxCount) {
994 TestPositionInstance line1_start = AXNodePosition::CreateTextPosition(
995 GetTreeID(), inline_box1_.id, 0 /* text_offset */,
996 ax::mojom::TextAffinity::kDownstream);
997 TestPositionInstance line2_end = AXNodePosition::CreateTextPosition(
998 GetTreeID(), inline_box2_.id, 6 /* text_offset */,
999 ax::mojom::TextAffinity::kDownstream);
1000
1001 TestPositionRange test_range(line1_start->Clone(), line2_end->Clone());
1002 EXPECT_EQ(LINE_1.substr(0, 2),
1003 test_range.GetText(AXTextConcatenationBehavior::kAsInnerText, 2));
1004
1005 // Test the case where an appended newline falls right at max_count.
1006 EXPECT_EQ(LINE_1.substr().append(NEWLINE),
1007 test_range.GetText(AXTextConcatenationBehavior::kAsInnerText, 7));
1008
1009 // Test passing -1 for max_count.
1010 EXPECT_EQ(LINE_1.substr().append(NEWLINE).append(LINE_2),
1011 test_range.GetText(AXTextConcatenationBehavior::kAsInnerText, -1));
1012 }
1013
TEST_F(AXRangeTest,GetTextWithList)1014 TEST_F(AXRangeTest, GetTextWithList) {
1015 const base::string16 kListMarker1 = base::ASCIIToUTF16("1. ");
1016 const base::string16 kListItemContent = base::ASCIIToUTF16("List item 1");
1017 const base::string16 kListMarker2 = base::ASCIIToUTF16("2. ");
1018 const base::string16 kAfterList = base::ASCIIToUTF16("After list");
1019 const base::string16 kAllText = kListMarker1.substr()
1020 .append(kListItemContent)
1021 .append(NEWLINE)
1022 .append(kListMarker2)
1023 .append(NEWLINE)
1024 .append(kAfterList);
1025 // This test expects:
1026 // "1. List item 1
1027 // 2.
1028 // After list"
1029 // for the following AXTree:
1030 // ++1 kRootWebArea
1031 // ++++2 kList
1032 // ++++++3 kListItem
1033 // ++++++++4 kListMarker
1034 // ++++++++++5 kStaticText
1035 // ++++++++++++6 kInlineTextBox "1. "
1036 // ++++++++7 kStaticText
1037 // ++++++++++8 kInlineTextBox "List item 1"
1038 // ++++++9 kListItem
1039 // ++++++++10 kListMarker
1040 // +++++++++++11 kStaticText
1041 // ++++++++++++++12 kInlineTextBox "2. "
1042 // ++++13 kStaticText
1043 // +++++++14 kInlineTextBox "After list"
1044 AXNodeData root;
1045 AXNodeData list;
1046 AXNodeData list_item1;
1047 AXNodeData list_item2;
1048 AXNodeData list_marker1;
1049 AXNodeData list_marker2;
1050 AXNodeData inline_box1;
1051 AXNodeData inline_box2;
1052 AXNodeData inline_box3;
1053 AXNodeData inline_box4;
1054 AXNodeData static_text1;
1055 AXNodeData static_text2;
1056 AXNodeData static_text3;
1057 AXNodeData static_text4;
1058
1059 root.id = 1;
1060 list.id = 2;
1061 list_item1.id = 3;
1062 list_marker1.id = 4;
1063 static_text1.id = 5;
1064 inline_box1.id = 6;
1065 static_text2.id = 7;
1066 inline_box2.id = 8;
1067 list_item2.id = 9;
1068 list_marker2.id = 10;
1069 static_text3.id = 11;
1070 inline_box3.id = 12;
1071 static_text4.id = 13;
1072 inline_box4.id = 14;
1073
1074 root.role = ax::mojom::Role::kRootWebArea;
1075 root.child_ids = {list.id, static_text4.id};
1076
1077 list.role = ax::mojom::Role::kList;
1078 list.child_ids = {list_item1.id, list_item2.id};
1079
1080 list_item1.role = ax::mojom::Role::kListItem;
1081 list_item1.child_ids = {list_marker1.id, static_text2.id};
1082 list_item1.AddBoolAttribute(ax::mojom::BoolAttribute::kIsLineBreakingObject,
1083 true);
1084
1085 list_marker1.role = ax::mojom::Role::kListMarker;
1086 list_marker1.child_ids = {static_text1.id};
1087
1088 static_text1.role = ax::mojom::Role::kStaticText;
1089 static_text1.SetName(kListMarker1);
1090 static_text1.child_ids = {inline_box1.id};
1091
1092 inline_box1.role = ax::mojom::Role::kInlineTextBox;
1093 inline_box1.SetName(kListMarker1);
1094
1095 static_text2.role = ax::mojom::Role::kStaticText;
1096 static_text2.SetName(kListItemContent);
1097 static_text2.child_ids = {inline_box2.id};
1098
1099 inline_box2.role = ax::mojom::Role::kInlineTextBox;
1100 inline_box2.SetName(kListItemContent);
1101
1102 list_item2.role = ax::mojom::Role::kListItem;
1103 list_item2.child_ids = {list_marker2.id};
1104 list_item2.AddBoolAttribute(ax::mojom::BoolAttribute::kIsLineBreakingObject,
1105 true);
1106
1107 list_marker2.role = ax::mojom::Role::kListMarker;
1108 list_marker2.child_ids = {static_text3.id};
1109
1110 static_text3.role = ax::mojom::Role::kStaticText;
1111 static_text3.SetName(kListMarker2);
1112 static_text3.child_ids = {inline_box3.id};
1113
1114 inline_box3.role = ax::mojom::Role::kInlineTextBox;
1115 inline_box3.SetName(kListMarker2);
1116
1117 static_text4.role = ax::mojom::Role::kStaticText;
1118 static_text4.SetName(kAfterList);
1119 static_text4.child_ids = {inline_box4.id};
1120
1121 inline_box4.role = ax::mojom::Role::kInlineTextBox;
1122 inline_box4.SetName(kAfterList);
1123
1124 AXTreeUpdate initial_state;
1125 initial_state.root_id = root.id;
1126 initial_state.nodes = {root, list, list_item1, list_marker1,
1127 static_text1, inline_box1, static_text2, inline_box2,
1128 list_item2, list_marker2, static_text3, inline_box3,
1129 static_text4, inline_box4};
1130 initial_state.has_tree_data = true;
1131 initial_state.tree_data.tree_id = AXTreeID::CreateNewAXTreeID();
1132 initial_state.tree_data.title = "Dialog title";
1133
1134 SetTree(std::make_unique<AXTree>(initial_state));
1135
1136 TestPositionInstance start = AXNodePosition::CreateTextPosition(
1137 GetTreeID(), inline_box1.id, 0 /* text_offset */,
1138 ax::mojom::TextAffinity::kDownstream);
1139 ASSERT_TRUE(start->IsTextPosition());
1140 TestPositionInstance end = AXNodePosition::CreateTextPosition(
1141 GetTreeID(), inline_box4.id, 10 /* text_offset */,
1142 ax::mojom::TextAffinity::kDownstream);
1143 ASSERT_TRUE(end->IsTextPosition());
1144 TestPositionRange forward_range(start->Clone(), end->Clone());
1145 EXPECT_EQ(kAllText,
1146 forward_range.GetText(AXTextConcatenationBehavior::kAsInnerText));
1147 TestPositionRange backward_range(std::move(end), std::move(start));
1148 EXPECT_EQ(kAllText,
1149 backward_range.GetText(AXTextConcatenationBehavior::kAsInnerText));
1150 }
1151
TEST_F(AXRangeTest,GetRects)1152 TEST_F(AXRangeTest, GetRects) {
1153 TestAXRangeScreenRectDelegate delegate(this);
1154
1155 // Setting up ax ranges for testing.
1156 TestPositionInstance button = AXNodePosition::CreateTextPosition(
1157 GetTreeID(), button_.id, 0 /* text_offset */,
1158 ax::mojom::TextAffinity::kDownstream);
1159
1160 TestPositionInstance check_box1 = AXNodePosition::CreateTextPosition(
1161 GetTreeID(), check_box1_.id, 0 /* text_offset */,
1162 ax::mojom::TextAffinity::kDownstream);
1163 TestPositionInstance check_box2 = AXNodePosition::CreateTextPosition(
1164 GetTreeID(), check_box2_.id, 0 /* text_offset */,
1165 ax::mojom::TextAffinity::kDownstream);
1166
1167 TestPositionInstance line1_start = AXNodePosition::CreateTextPosition(
1168 GetTreeID(), inline_box1_.id, 0 /* text_offset */,
1169 ax::mojom::TextAffinity::kDownstream);
1170 TestPositionInstance line1_second_char = AXNodePosition::CreateTextPosition(
1171 GetTreeID(), inline_box1_.id, 1 /* text_offset */,
1172 ax::mojom::TextAffinity::kDownstream);
1173 TestPositionInstance line1_middle = AXNodePosition::CreateTextPosition(
1174 GetTreeID(), inline_box1_.id, 3 /* text_offset */,
1175 ax::mojom::TextAffinity::kDownstream);
1176 TestPositionInstance line1_second_to_last_char =
1177 AXNodePosition::CreateTextPosition(GetTreeID(), inline_box1_.id,
1178 5 /* text_offset */,
1179 ax::mojom::TextAffinity::kDownstream);
1180 TestPositionInstance line1_end = AXNodePosition::CreateTextPosition(
1181 GetTreeID(), inline_box1_.id, 6 /* text_offset */,
1182 ax::mojom::TextAffinity::kDownstream);
1183
1184 TestPositionInstance line2_start = AXNodePosition::CreateTextPosition(
1185 GetTreeID(), inline_box2_.id, 0 /* text_offset */,
1186 ax::mojom::TextAffinity::kDownstream);
1187 TestPositionInstance line2_second_char = AXNodePosition::CreateTextPosition(
1188 GetTreeID(), inline_box2_.id, 1 /* text_offset */,
1189 ax::mojom::TextAffinity::kDownstream);
1190 TestPositionInstance line2_middle = AXNodePosition::CreateTextPosition(
1191 GetTreeID(), inline_box2_.id, 3 /* text_offset */,
1192 ax::mojom::TextAffinity::kDownstream);
1193 TestPositionInstance line2_second_to_last_char =
1194 AXNodePosition::CreateTextPosition(GetTreeID(), inline_box2_.id,
1195 5 /* text_offset */,
1196 ax::mojom::TextAffinity::kDownstream);
1197 TestPositionInstance line2_end = AXNodePosition::CreateTextPosition(
1198 GetTreeID(), inline_box2_.id, 6 /* text_offset */,
1199 ax::mojom::TextAffinity::kDownstream);
1200
1201 TestPositionInstance after_line_end = AXNodePosition::CreateTextPosition(
1202 GetTreeID(), inline_box3_.id, 5 /* text_offset */,
1203 ax::mojom::TextAffinity::kDownstream);
1204
1205 // Since a button is not visible to the text representation, it spans an
1206 // empty anchor whose start and end positions are the same.
1207 TestPositionRange button_range(button->Clone(), button->Clone());
1208 std::vector<gfx::Rect> expected_screen_rects = {gfx::Rect(20, 20, 100, 30)};
1209 EXPECT_THAT(button_range.GetRects(&delegate),
1210 testing::ContainerEq(expected_screen_rects));
1211
1212 // Since a check box is not visible to the text representation, it spans an
1213 // empty anchor whose start and end positions are the same.
1214 TestPositionRange check_box1_range(check_box1->Clone(), check_box1->Clone());
1215 expected_screen_rects = {gfx::Rect(120, 20, 30, 30)};
1216 EXPECT_THAT(check_box1_range.GetRects(&delegate),
1217 testing::ContainerEq(expected_screen_rects));
1218
1219 // Retrieving bounding boxes of the button and both checkboxes.
1220 TestPositionRange button_check_box2_range(button->Clone(),
1221 check_box2->Clone());
1222 expected_screen_rects = {gfx::Rect(20, 20, 100, 30),
1223 gfx::Rect(120, 20, 30, 30),
1224 gfx::Rect(150, 20, 30, 30)};
1225 EXPECT_THAT(button_check_box2_range.GetRects(&delegate),
1226 testing::ContainerEq(expected_screen_rects));
1227
1228 // Retrieving bounding box of text line 1, its whole range.
1229 // 0 1 2 3 4 5
1230 // |L|i|n|e| |1|
1231 // |-----------|
1232 TestPositionRange line1_whole_range(line1_start->Clone(), line1_end->Clone());
1233 expected_screen_rects = {gfx::Rect(20, 50, 30, 30)};
1234 EXPECT_THAT(line1_whole_range.GetRects(&delegate),
1235 testing::ContainerEq(expected_screen_rects));
1236
1237 // Retrieving bounding box of text line 1, its first half range.
1238 // 0 1 2 3 4 5
1239 // |L|i|n|e| |1|
1240 // |-----|
1241 TestPositionRange line1_first_half_range(line1_start->Clone(),
1242 line1_middle->Clone());
1243 expected_screen_rects = {gfx::Rect(20, 50, 15, 30)};
1244 EXPECT_THAT(line1_first_half_range.GetRects(&delegate),
1245 testing::ContainerEq(expected_screen_rects));
1246
1247 // Retrieving bounding box of text line 1, its second half range.
1248 // 0 1 2 3 4 5
1249 // |L|i|n|e| |1|
1250 // |-----|
1251 TestPositionRange line1_second_half_range(line1_middle->Clone(),
1252 line1_end->Clone());
1253 expected_screen_rects = {gfx::Rect(35, 50, 15, 30)};
1254 EXPECT_THAT(line1_second_half_range.GetRects(&delegate),
1255 testing::ContainerEq(expected_screen_rects));
1256
1257 // Retrieving bounding box of text line 1, its mid range.
1258 // 0 1 2 3 4 5
1259 // |L|i|n|e| |1|
1260 // |-------|
1261 TestPositionRange line1_mid_range(line1_second_char->Clone(),
1262 line1_second_to_last_char->Clone());
1263 expected_screen_rects = {gfx::Rect(25, 50, 20, 30)};
1264 EXPECT_THAT(line1_mid_range.GetRects(&delegate),
1265 testing::ContainerEq(expected_screen_rects));
1266
1267 // Retrieving bounding box of text line 2, its whole range.
1268 // 0 1 2 3 4 5
1269 // |L|i|n|e| |2|
1270 // |-----------|
1271 TestPositionRange line2_whole_range(line2_start->Clone(), line2_end->Clone());
1272 expected_screen_rects = {gfx::Rect(20, 80, 42, 30)};
1273 EXPECT_THAT(line2_whole_range.GetRects(&delegate),
1274 testing::ContainerEq(expected_screen_rects));
1275
1276 // Retrieving bounding box of text line 2, its first half range.
1277 // 0 1 2 3 4 5
1278 // |L|i|n|e| |2|
1279 // |-----|
1280 TestPositionRange line2_first_half_range(line2_start->Clone(),
1281 line2_middle->Clone());
1282 expected_screen_rects = {gfx::Rect(20, 80, 21, 30)};
1283 EXPECT_THAT(line2_first_half_range.GetRects(&delegate),
1284 testing::ContainerEq(expected_screen_rects));
1285
1286 // Retrieving bounding box of text line 2, its second half range.
1287 // 0 1 2 3 4 5
1288 // |L|i|n|e| |2|
1289 // |-----|
1290 TestPositionRange line2_second_half_range(line2_middle->Clone(),
1291 line2_end->Clone());
1292 expected_screen_rects = {gfx::Rect(41, 80, 21, 30)};
1293 EXPECT_THAT(line2_second_half_range.GetRects(&delegate),
1294 testing::ContainerEq(expected_screen_rects));
1295
1296 // Retrieving bounding box of text line 2, its mid range.
1297 // 0 1 2 3 4 5
1298 // |L|i|n|e| |2|
1299 // |-------|
1300 TestPositionRange line2_mid_range(line2_second_char->Clone(),
1301 line2_second_to_last_char->Clone());
1302 expected_screen_rects = {gfx::Rect(27, 80, 28, 30)};
1303 EXPECT_THAT(line2_mid_range.GetRects(&delegate),
1304 testing::ContainerEq(expected_screen_rects));
1305
1306 // Retrieving bounding boxes of text line 1 and line 2, the entire range.
1307 // |L|i|n|e| |1|\n|L|i|n|e| |2|\n|
1308 // |--------------------------|
1309 TestPositionRange line1_line2_whole_range(line1_start->Clone(),
1310 line2_end->Clone());
1311 expected_screen_rects = {gfx::Rect(20, 50, 30, 30),
1312 gfx::Rect(20, 80, 42, 30)};
1313 EXPECT_THAT(line1_line2_whole_range.GetRects(&delegate),
1314 testing::ContainerEq(expected_screen_rects));
1315
1316 // Retrieving bounding boxes of the range that spans from the middle of text
1317 // line 1 to the middle of text line 2.
1318 // |L|i|n|e| |1|\n|L|i|n|e| |2|\n|
1319 // |--------------|
1320 TestPositionRange line1_line2_mid_range(line1_middle->Clone(),
1321 line2_middle->Clone());
1322 expected_screen_rects = {gfx::Rect(35, 50, 15, 30),
1323 gfx::Rect(20, 80, 21, 30)};
1324 EXPECT_THAT(line1_line2_mid_range.GetRects(&delegate),
1325 testing::ContainerEq(expected_screen_rects));
1326
1327 // Retrieving bounding boxes of the range that spans from the checkbox 2
1328 // ("invisible" in the text representation) to the middle of text line 2.
1329 // |[Button][Checkbox 1][Checkbox 2]L|i|n|e| |1|\n|L|i|n|e| |2|\n|A|f|t|e|r|
1330 // |-------------------------------|
1331 TestPositionRange check_box2_line2_mid_range(check_box2->Clone(),
1332 line2_middle->Clone());
1333 expected_screen_rects = {gfx::Rect(150, 20, 30, 30),
1334 gfx::Rect(20, 50, 30, 30),
1335 gfx::Rect(20, 80, 21, 30)};
1336 EXPECT_THAT(check_box2_line2_mid_range.GetRects(&delegate),
1337 testing::ContainerEq(expected_screen_rects));
1338
1339 // Retrieving bounding boxes of the range spanning the entire document.
1340 // |[Button][Checkbox 1][Checkbox 2]L|i|n|e| |1|\n|L|i|n|e| |2|\n|A|f|t|e|r|
1341 // |-----------------------------------------------------------------------|
1342 TestPositionRange entire_test_range(button->Clone(), after_line_end->Clone());
1343 expected_screen_rects = {
1344 gfx::Rect(20, 20, 100, 30), gfx::Rect(120, 20, 30, 30),
1345 gfx::Rect(150, 20, 30, 30), gfx::Rect(20, 50, 30, 30),
1346 gfx::Rect(20, 80, 42, 30), gfx::Rect(20, 110, 50, 30)};
1347 EXPECT_THAT(entire_test_range.GetRects(&delegate),
1348 testing::ContainerEq(expected_screen_rects));
1349 }
1350
TEST_F(AXRangeTest,GetRectsOffscreen)1351 TEST_F(AXRangeTest, GetRectsOffscreen) {
1352 // Set up root node bounds/viewport size to {0, 50, 800x60}, so that only
1353 // some text will be onscreen the rest will be offscreen.
1354 AXNodeData old_root_node_data = GetRootAsAXNode()->data();
1355 AXNodeData new_root_node_data = old_root_node_data;
1356 new_root_node_data.relative_bounds.bounds = gfx::RectF(0, 50, 800, 60);
1357 GetRootAsAXNode()->SetData(new_root_node_data);
1358
1359 TestAXRangeScreenRectDelegate delegate(this);
1360
1361 TestPositionInstance button = AXNodePosition::CreateTextPosition(
1362 GetTreeID(), button_.id, 0 /* text_offset */,
1363 ax::mojom::TextAffinity::kDownstream);
1364
1365 TestPositionInstance after_line_end = AXNodePosition::CreateTextPosition(
1366 GetTreeID(), inline_box3_.id, 5 /* text_offset */,
1367 ax::mojom::TextAffinity::kDownstream);
1368
1369 // [Button] [Checkbox 1] [Checkbox 2]
1370 // {20, 20, 100x30}, {120, 20, 30x30} {150, 20, 30x30}
1371 // ---
1372 // [Line 1] |
1373 // {20, 50, 30x30} | view port, onscreen
1374 // | {0, 50, 800x60}
1375 // [Line 2] |
1376 // {20, 80, 42x30} |
1377 // ---
1378 // [After]
1379 // {20, 110, 50x30}
1380 //
1381 // Retrieving bounding boxes of the range spanning the entire document.
1382 // |[Button][Checkbox 1][Checkbox 2]L|i|n|e| |1|\n|L|i|n|e| |2|\n|A|f|t|e|r|
1383 // |-----------------------------------------------------------------------|
1384 TestPositionRange entire_test_range(button->Clone(), after_line_end->Clone());
1385 std::vector<gfx::Rect> expected_screen_rects = {gfx::Rect(20, 50, 30, 30),
1386 gfx::Rect(20, 80, 42, 30)};
1387 EXPECT_THAT(entire_test_range.GetRects(&delegate),
1388 testing::ContainerEq(expected_screen_rects));
1389
1390 // Reset the root node bounds/viewport size back to {0, 0, 800x600}, and
1391 // verify all elements should be onscreen.
1392 GetRootAsAXNode()->SetData(old_root_node_data);
1393 expected_screen_rects = {
1394 gfx::Rect(20, 20, 100, 30), gfx::Rect(120, 20, 30, 30),
1395 gfx::Rect(150, 20, 30, 30), gfx::Rect(20, 50, 30, 30),
1396 gfx::Rect(20, 80, 42, 30), gfx::Rect(20, 110, 50, 30)};
1397 EXPECT_THAT(entire_test_range.GetRects(&delegate),
1398 testing::ContainerEq(expected_screen_rects));
1399 }
1400
1401 } // namespace ui
1402