1 // Copyright 2018 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 "third_party/blink/renderer/modules/accessibility/ax_position.h"
6 
7 #include "testing/gtest/include/gtest/gtest.h"
8 #include "third_party/blink/renderer/core/dom/element.h"
9 #include "third_party/blink/renderer/core/dom/node.h"
10 #include "third_party/blink/renderer/core/editing/position.h"
11 #include "third_party/blink/renderer/core/editing/text_affinity.h"
12 #include "third_party/blink/renderer/core/html/html_element.h"
13 #include "third_party/blink/renderer/modules/accessibility/ax_object.h"
14 #include "third_party/blink/renderer/modules/accessibility/testing/accessibility_test.h"
15 #include "third_party/blink/renderer/platform/testing/runtime_enabled_features_test_helpers.h"
16 
17 namespace blink {
18 namespace test {
19 
20 namespace {
21 
22 constexpr char kCSSBeforeAndAfter[] = R"HTML(
23     <style>
24       q::before {
25         content: "«";
26         color: blue;
27       }
28       q::after {
29         content: "»";
30         color: red;
31       }
32     </style>
33     <q id="quote">Hello there,</q> she said.
34     )HTML";
35 
36 constexpr char kHTMLTable[] = R"HTML(
37     <p id="before">Before table.</p>
38     <table id="table" border="1">
39       <thead id="thead">
40         <tr id="headerRow">
41           <th id="firstHeaderCell">Number</th>
42           <th>Month</th>
43           <th id="lastHeaderCell">Expenses</th>
44         </tr>
45       </thead>
46       <tbody id="tbody">
47         <tr id="firstRow">
48           <th id="firstCell">1</th>
49           <td>Jan</td>
50           <td>100</td>
51         </tr>
52         <tr>
53           <th>2</th>
54           <td>Feb</td>
55           <td>150</td>
56         </tr>
57         <tr id="lastRow">
58           <th>3</th>
59           <td>Mar</td>
60           <td id="lastCell">200</td>
61         </tr>
62       </tbody>
63     </table>
64     <p id="after">After table.</p>
65     )HTML";
66 
67 constexpr char kAOM[] = R"HTML(
68     <p id="before">Before virtual AOM node.</p>
69     <div id="aomParent"></div>
70     <p id="after">After virtual AOM node.</p>
71     <script>
72       let parent = document.getElementById("aomParent");
73       let node = MakeGarbageCollected<AccessibleNode>();
74       node.role = "button";
75       node.label = "Button";
76       parent.accessibleNode.appendChild(node);
77     </script>
78     )HTML";
79 
80 constexpr char kMap[] = R"HTML(
81     <br id="br">
82     <map id="map">
83       <area shape="rect" coords="0,0,10,10" href="about:blank">
84     </map>
85     )HTML";
86 }  // namespace
87 
88 //
89 // Basic tests.
90 //
91 
TEST_F(AccessibilityTest,PositionInText)92 TEST_F(AccessibilityTest, PositionInText) {
93   SetBodyInnerHTML(R"HTML(<p id="paragraph">Hello</p>)HTML");
94   const Node* text = GetElementById("paragraph")->firstChild();
95   ASSERT_NE(nullptr, text);
96   ASSERT_TRUE(text->IsTextNode());
97   const AXObject* ax_static_text =
98       GetAXObjectByElementId("paragraph")->FirstChildIncludingIgnored();
99   ASSERT_NE(nullptr, ax_static_text);
100   ASSERT_EQ(ax::mojom::Role::kStaticText, ax_static_text->RoleValue());
101 
102   const auto ax_position =
103       AXPosition::CreatePositionInTextObject(*ax_static_text, 3);
104   const auto position = ax_position.ToPositionWithAffinity();
105   EXPECT_EQ(text, position.AnchorNode());
106   EXPECT_EQ(3, position.GetPosition().OffsetInContainerNode());
107 
108   const auto ax_position_from_dom = AXPosition::FromPosition(position);
109   EXPECT_EQ(ax_position, ax_position_from_dom);
110   EXPECT_EQ(nullptr, ax_position_from_dom.ChildAfterTreePosition());
111 }
112 
113 // To prevent surprises when comparing equality of two |AXPosition|s, position
114 // before text object should be the same as position in text object at offset 0.
TEST_F(AccessibilityTest,PositionBeforeText)115 TEST_F(AccessibilityTest, PositionBeforeText) {
116   SetBodyInnerHTML(R"HTML(<p id="paragraph">Hello</p>)HTML");
117   const Node* text = GetElementById("paragraph")->firstChild();
118   ASSERT_NE(nullptr, text);
119   ASSERT_TRUE(text->IsTextNode());
120   const AXObject* ax_static_text =
121       GetAXObjectByElementId("paragraph")->FirstChildIncludingIgnored();
122   ASSERT_NE(nullptr, ax_static_text);
123   ASSERT_EQ(ax::mojom::Role::kStaticText, ax_static_text->RoleValue());
124 
125   const auto ax_position =
126       AXPosition::CreatePositionBeforeObject(*ax_static_text);
127   const auto position = ax_position.ToPositionWithAffinity();
128   EXPECT_EQ(text, position.AnchorNode());
129   EXPECT_EQ(0, position.GetPosition().OffsetInContainerNode());
130 
131   const auto ax_position_from_dom = AXPosition::FromPosition(position);
132   EXPECT_EQ(ax_position, ax_position_from_dom);
133   EXPECT_EQ(nullptr, ax_position_from_dom.ChildAfterTreePosition());
134 }
135 
TEST_F(AccessibilityTest,PositionBeforeTextWithFirstLetterCSSRule)136 TEST_F(AccessibilityTest, PositionBeforeTextWithFirstLetterCSSRule) {
137   SetBodyInnerHTML(
138       R"HTML(<style>p ::first-letter { color: red; font-size: 200%; }</style>
139       <p id="paragraph">Hello</p>)HTML");
140   const Node* text = GetElementById("paragraph")->firstChild();
141   ASSERT_NE(nullptr, text);
142   ASSERT_TRUE(text->IsTextNode());
143   const AXObject* ax_static_text =
144       GetAXObjectByElementId("paragraph")->FirstChildIncludingIgnored();
145   ASSERT_NE(nullptr, ax_static_text);
146   ASSERT_EQ(ax::mojom::Role::kStaticText, ax_static_text->RoleValue());
147 
148   const auto ax_position =
149       AXPosition::CreatePositionBeforeObject(*ax_static_text);
150   const auto position = ax_position.ToPositionWithAffinity();
151   EXPECT_EQ(text, position.AnchorNode());
152   EXPECT_EQ(0, position.GetPosition().OffsetInContainerNode());
153 
154   const auto ax_position_from_dom = AXPosition::FromPosition(position);
155   EXPECT_EQ(ax_position, ax_position_from_dom);
156   EXPECT_EQ(nullptr, ax_position_from_dom.ChildAfterTreePosition());
157 }
158 
159 // To prevent surprises when comparing equality of two |AXPosition|s, position
160 // after text object should be the same as position in text object at offset
161 // text length.
TEST_F(AccessibilityTest,PositionAfterText)162 TEST_F(AccessibilityTest, PositionAfterText) {
163   SetBodyInnerHTML(R"HTML(<p id="paragraph">Hello</p>)HTML");
164   const Node* text = GetElementById("paragraph")->firstChild();
165   ASSERT_NE(nullptr, text);
166   ASSERT_TRUE(text->IsTextNode());
167   const AXObject* ax_static_text =
168       GetAXObjectByElementId("paragraph")->FirstChildIncludingIgnored();
169   ASSERT_NE(nullptr, ax_static_text);
170   ASSERT_EQ(ax::mojom::Role::kStaticText, ax_static_text->RoleValue());
171 
172   const auto ax_position =
173       AXPosition::CreatePositionAfterObject(*ax_static_text);
174   const auto position = ax_position.ToPositionWithAffinity();
175   EXPECT_EQ(text, position.AnchorNode());
176   EXPECT_EQ(5, position.GetPosition().OffsetInContainerNode());
177 
178   const auto ax_position_from_dom = AXPosition::FromPosition(position);
179   EXPECT_EQ(ax_position, ax_position_from_dom);
180   EXPECT_EQ(nullptr, ax_position_from_dom.ChildAfterTreePosition());
181 }
182 
TEST_F(AccessibilityTest,PositionBeforeLineBreak)183 TEST_F(AccessibilityTest, PositionBeforeLineBreak) {
184   SetBodyInnerHTML(R"HTML(Hello<br id="br">there)HTML");
185   const AXObject* ax_br = GetAXObjectByElementId("br");
186   ASSERT_NE(nullptr, ax_br);
187   ASSERT_EQ(ax::mojom::Role::kLineBreak, ax_br->RoleValue());
188   const AXObject* ax_div = ax_br->ParentObjectUnignored();
189   ASSERT_NE(nullptr, ax_div);
190   ASSERT_EQ(ax::mojom::Role::kGenericContainer, ax_div->RoleValue());
191 
192   const auto ax_position = AXPosition::CreatePositionBeforeObject(*ax_br);
193   EXPECT_FALSE(ax_position.IsTextPosition());
194   EXPECT_EQ(ax_div, ax_position.ContainerObject());
195   EXPECT_EQ(1, ax_position.ChildIndex());
196   EXPECT_EQ(ax_br, ax_position.ChildAfterTreePosition());
197 
198   const auto position = ax_position.ToPositionWithAffinity();
199   EXPECT_EQ(GetDocument().body(), position.AnchorNode());
200   EXPECT_EQ(1, position.GetPosition().OffsetInContainerNode());
201 
202   const auto ax_position_from_dom = AXPosition::FromPosition(position);
203   EXPECT_EQ(ax_position, ax_position_from_dom);
204 }
205 
TEST_F(AccessibilityTest,PositionAfterLineBreak)206 TEST_F(AccessibilityTest, PositionAfterLineBreak) {
207   SetBodyInnerHTML(R"HTML(Hello<br id="br">there)HTML");
208   const AXObject* ax_br = GetAXObjectByElementId("br");
209   ASSERT_NE(nullptr, ax_br);
210   ASSERT_EQ(ax::mojom::Role::kLineBreak, ax_br->RoleValue());
211   const AXObject* ax_static_text =
212       GetAXRootObject()->DeepestLastChildIncludingIgnored();
213   ASSERT_NE(nullptr, ax_static_text);
214   ASSERT_EQ(ax::mojom::Role::kStaticText, ax_static_text->RoleValue());
215 
216   const auto ax_position = AXPosition::CreatePositionAfterObject(*ax_br);
217   EXPECT_EQ(ax_static_text, ax_position.ContainerObject());
218   EXPECT_TRUE(ax_position.IsTextPosition());
219   EXPECT_EQ(0, ax_position.TextOffset());
220 
221   const auto position = ax_position.ToPositionWithAffinity();
222   EXPECT_EQ(ax_static_text->GetNode(), position.AnchorNode());
223   EXPECT_TRUE(position.GetPosition().IsOffsetInAnchor());
224   EXPECT_EQ(0, position.GetPosition().OffsetInContainerNode());
225 
226   const auto ax_position_from_dom = AXPosition::FromPosition(position);
227   EXPECT_EQ(ax_position, ax_position_from_dom);
228 }
229 
TEST_F(AccessibilityTest,FirstPositionInDivContainer)230 TEST_F(AccessibilityTest, FirstPositionInDivContainer) {
231   SetBodyInnerHTML(R"HTML(<div id="div">Hello<br>there</div>)HTML");
232   const Element* div = GetElementById("div");
233   ASSERT_NE(nullptr, div);
234   const AXObject* ax_div = GetAXObjectByElementId("div");
235   ASSERT_NE(nullptr, ax_div);
236   ASSERT_EQ(ax::mojom::Role::kGenericContainer, ax_div->RoleValue());
237   const AXObject* ax_static_text =
238       GetAXRootObject()->DeepestFirstChildIncludingIgnored();
239   ASSERT_NE(nullptr, ax_static_text);
240   ASSERT_EQ(ax::mojom::Role::kStaticText, ax_static_text->RoleValue());
241 
242   // "Before object" positions that are anchored to before a text object are
243   // always converted to a "text position" before the object's first unignored
244   // character.
245   const auto ax_position = AXPosition::CreateFirstPositionInObject(*ax_div);
246   const auto position = ax_position.ToPositionWithAffinity();
247   EXPECT_EQ(div->firstChild(), position.AnchorNode());
248   EXPECT_TRUE(position.GetPosition().IsOffsetInAnchor());
249   EXPECT_EQ(0, position.GetPosition().OffsetInContainerNode());
250 
251   const auto ax_position_from_dom = AXPosition::FromPosition(position);
252   EXPECT_EQ(ax_position, ax_position_from_dom);
253   EXPECT_TRUE(ax_position_from_dom.IsTextPosition());
254   EXPECT_EQ(ax_static_text, ax_position_from_dom.ContainerObject());
255   EXPECT_EQ(0, ax_position_from_dom.TextOffset());
256   EXPECT_EQ(nullptr, ax_position_from_dom.ChildAfterTreePosition());
257 }
258 
TEST_F(AccessibilityTest,LastPositionInDivContainer)259 TEST_F(AccessibilityTest, LastPositionInDivContainer) {
260   SetBodyInnerHTML(R"HTML(<div id="div">Hello<br>there</div>
261                    <div>Next div</div>)HTML");
262   const Element* div = GetElementById("div");
263   ASSERT_NE(nullptr, div);
264   const AXObject* ax_div = GetAXObjectByElementId("div");
265   ASSERT_NE(nullptr, ax_div);
266   ASSERT_EQ(ax::mojom::Role::kGenericContainer, ax_div->RoleValue());
267 
268   const auto ax_position = AXPosition::CreateLastPositionInObject(*ax_div);
269   const auto position = ax_position.ToPositionWithAffinity();
270   EXPECT_EQ(div, position.AnchorNode());
271   EXPECT_TRUE(position.GetPosition().IsAfterChildren());
272 
273   const auto ax_position_from_dom = AXPosition::FromPosition(position);
274   EXPECT_EQ(ax_position, ax_position_from_dom);
275   EXPECT_EQ(nullptr, ax_position_from_dom.ChildAfterTreePosition());
276 }
277 
TEST_F(AccessibilityTest,FirstPositionInTextContainer)278 TEST_F(AccessibilityTest, FirstPositionInTextContainer) {
279   SetBodyInnerHTML(R"HTML(<div id="div">Hello</div>)HTML");
280   const Node* text = GetElementById("div")->firstChild();
281   ASSERT_NE(nullptr, text);
282   ASSERT_TRUE(text->IsTextNode());
283   const AXObject* ax_static_text =
284       GetAXObjectByElementId("div")->FirstChildIncludingIgnored();
285   ASSERT_NE(nullptr, ax_static_text);
286   ASSERT_EQ(ax::mojom::Role::kStaticText, ax_static_text->RoleValue());
287 
288   const auto ax_position =
289       AXPosition::CreateFirstPositionInObject(*ax_static_text);
290   const auto position = ax_position.ToPositionWithAffinity();
291   EXPECT_EQ(text, position.AnchorNode());
292   EXPECT_EQ(0, position.GetPosition().OffsetInContainerNode());
293 
294   const auto ax_position_from_dom = AXPosition::FromPosition(position);
295   EXPECT_EQ(ax_position, ax_position_from_dom);
296   EXPECT_EQ(nullptr, ax_position_from_dom.ChildAfterTreePosition());
297 }
298 
TEST_F(AccessibilityTest,LastPositionInTextContainer)299 TEST_F(AccessibilityTest, LastPositionInTextContainer) {
300   SetBodyInnerHTML(R"HTML(<div id="div">Hello</div>)HTML");
301   const Node* text = GetElementById("div")->lastChild();
302   ASSERT_NE(nullptr, text);
303   ASSERT_TRUE(text->IsTextNode());
304   const AXObject* ax_static_text =
305       GetAXObjectByElementId("div")->LastChildIncludingIgnored();
306   ASSERT_NE(nullptr, ax_static_text);
307   ASSERT_EQ(ax::mojom::Role::kStaticText, ax_static_text->RoleValue());
308 
309   const auto ax_position =
310       AXPosition::CreateLastPositionInObject(*ax_static_text);
311   const auto position = ax_position.ToPositionWithAffinity();
312   EXPECT_EQ(text, position.AnchorNode());
313   EXPECT_EQ(5, position.GetPosition().OffsetInContainerNode());
314 
315   const auto ax_position_from_dom = AXPosition::FromPosition(position);
316   EXPECT_EQ(ax_position, ax_position_from_dom);
317   EXPECT_EQ(nullptr, ax_position_from_dom.ChildAfterTreePosition());
318 }
319 
320 //
321 // Test comparing two AXPosition objects based on their position in the
322 // accessibility tree.
323 //
324 
TEST_F(AccessibilityTest,AXPositionComparisonOperators)325 TEST_F(AccessibilityTest, AXPositionComparisonOperators) {
326   SetBodyInnerHTML(R"HTML(<input id="input" type="text" value="value">
327                    <p id="paragraph">hello<br>there</p>)HTML");
328 
329   const AXObject* body = GetAXBodyObject();
330   ASSERT_NE(nullptr, body);
331   const auto root_first = AXPosition::CreateFirstPositionInObject(*body);
332   const auto root_last = AXPosition::CreateLastPositionInObject(*body);
333 
334   const AXObject* input = GetAXObjectByElementId("input");
335   ASSERT_NE(nullptr, input);
336   const auto input_before = AXPosition::CreatePositionBeforeObject(*input);
337   const auto input_after = AXPosition::CreatePositionAfterObject(*input);
338 
339   const AXObject* paragraph = GetAXObjectByElementId("paragraph");
340   ASSERT_NE(nullptr, paragraph);
341   ASSERT_NE(nullptr, paragraph->FirstChildIncludingIgnored());
342   ASSERT_NE(nullptr, paragraph->LastChildIncludingIgnored());
343   const auto paragraph_before = AXPosition::CreatePositionBeforeObject(
344       *paragraph->FirstChildIncludingIgnored());
345   const auto paragraph_after = AXPosition::CreatePositionAfterObject(
346       *paragraph->LastChildIncludingIgnored());
347   const auto paragraph_start = AXPosition::CreatePositionInTextObject(
348       *paragraph->FirstChildIncludingIgnored(), 0);
349   const auto paragraph_end = AXPosition::CreatePositionInTextObject(
350       *paragraph->LastChildIncludingIgnored(), 5);
351 
352   EXPECT_TRUE(root_first == root_first);
353   EXPECT_TRUE(root_last == root_last);
354   EXPECT_FALSE(root_first != root_first);
355   EXPECT_TRUE(root_first != root_last);
356 
357   EXPECT_TRUE(root_first < root_last);
358   EXPECT_TRUE(root_first <= root_first);
359   EXPECT_TRUE(root_last > root_first);
360   EXPECT_TRUE(root_last >= root_last);
361 
362   EXPECT_TRUE(input_before == root_first);
363   EXPECT_TRUE(input_after > root_first);
364   EXPECT_TRUE(input_after >= root_first);
365   EXPECT_FALSE(input_before < root_first);
366   EXPECT_TRUE(input_before <= root_first);
367 
368   //
369   // Text positions.
370   //
371 
372   EXPECT_TRUE(paragraph_before == paragraph_start);
373   EXPECT_TRUE(paragraph_after == paragraph_end);
374   EXPECT_TRUE(paragraph_start < paragraph_end);
375 }
376 
TEST_F(AccessibilityTest,AXPositionOperatorBool)377 TEST_F(AccessibilityTest, AXPositionOperatorBool) {
378   SetBodyInnerHTML(R"HTML(Hello)HTML");
379   const AXObject* root = GetAXRootObject();
380   ASSERT_NE(nullptr, root);
381   const auto root_first = AXPosition::CreateFirstPositionInObject(*root);
382   EXPECT_TRUE(static_cast<bool>(root_first));
383   // The following should create an after children position on the root so it
384   // should be valid.
385   EXPECT_TRUE(static_cast<bool>(root_first.CreateNextPosition()));
386   EXPECT_FALSE(static_cast<bool>(root_first.CreatePreviousPosition()));
387 }
388 
389 //
390 // Test converting to and from visible text with white space.
391 // The accessibility tree is based on visible text with white space compressed,
392 // vs. the DOM tree where white space is preserved.
393 //
394 
TEST_F(AccessibilityTest,PositionInTextWithWhiteSpace)395 TEST_F(AccessibilityTest, PositionInTextWithWhiteSpace) {
396   SetBodyInnerHTML(R"HTML(<p id="paragraph">     Hello     </p>)HTML");
397   const Node* text = GetElementById("paragraph")->firstChild();
398   ASSERT_NE(nullptr, text);
399   ASSERT_TRUE(text->IsTextNode());
400   const AXObject* ax_static_text =
401       GetAXObjectByElementId("paragraph")->FirstChildIncludingIgnored();
402   ASSERT_NE(nullptr, ax_static_text);
403   ASSERT_EQ(ax::mojom::Role::kStaticText, ax_static_text->RoleValue());
404 
405   const auto ax_position =
406       AXPosition::CreatePositionInTextObject(*ax_static_text, 3);
407   const auto position = ax_position.ToPositionWithAffinity();
408   EXPECT_EQ(text, position.AnchorNode());
409   EXPECT_EQ(8, position.GetPosition().OffsetInContainerNode());
410 
411   const auto ax_position_from_dom = AXPosition::FromPosition(position);
412   EXPECT_EQ(ax_position, ax_position_from_dom);
413   EXPECT_EQ(nullptr, ax_position_from_dom.ChildAfterTreePosition());
414 }
415 
TEST_F(AccessibilityTest,PositionBeforeTextWithWhiteSpace)416 TEST_F(AccessibilityTest, PositionBeforeTextWithWhiteSpace) {
417   SetBodyInnerHTML(R"HTML(<p id="paragraph">     Hello     </p>)HTML");
418   const Node* text = GetElementById("paragraph")->firstChild();
419   ASSERT_NE(nullptr, text);
420   ASSERT_TRUE(text->IsTextNode());
421   const AXObject* ax_static_text =
422       GetAXObjectByElementId("paragraph")->FirstChildIncludingIgnored();
423   ASSERT_NE(nullptr, ax_static_text);
424   ASSERT_EQ(ax::mojom::Role::kStaticText, ax_static_text->RoleValue());
425 
426   const auto ax_position =
427       AXPosition::CreatePositionBeforeObject(*ax_static_text);
428   const auto position = ax_position.ToPositionWithAffinity();
429   EXPECT_EQ(text, position.AnchorNode());
430   EXPECT_EQ(5, position.GetPosition().OffsetInContainerNode());
431 
432   const auto ax_position_from_dom = AXPosition::FromPosition(position);
433   EXPECT_EQ(ax_position, ax_position_from_dom);
434   EXPECT_EQ(nullptr, ax_position_from_dom.ChildAfterTreePosition());
435 }
436 
TEST_F(AccessibilityTest,PositionAfterTextWithWhiteSpace)437 TEST_F(AccessibilityTest, PositionAfterTextWithWhiteSpace) {
438   SetBodyInnerHTML(R"HTML(<p id="paragraph">     Hello     </p>)HTML");
439   const Node* text = GetElementById("paragraph")->lastChild();
440   ASSERT_NE(nullptr, text);
441   ASSERT_TRUE(text->IsTextNode());
442   const AXObject* ax_static_text =
443       GetAXObjectByElementId("paragraph")->LastChildIncludingIgnored();
444   ASSERT_NE(nullptr, ax_static_text);
445   ASSERT_EQ(ax::mojom::Role::kStaticText, ax_static_text->RoleValue());
446 
447   const auto ax_position =
448       AXPosition::CreatePositionAfterObject(*ax_static_text);
449   const auto position = ax_position.ToPositionWithAffinity();
450   EXPECT_EQ(text, position.AnchorNode());
451   EXPECT_EQ(10, position.GetPosition().OffsetInContainerNode());
452 
453   const auto ax_position_from_dom = AXPosition::FromPosition(position);
454   EXPECT_EQ(ax_position, ax_position_from_dom);
455   EXPECT_EQ(nullptr, ax_position_from_dom.ChildAfterTreePosition());
456 }
457 
TEST_F(AccessibilityTest,PositionBeforeLineBreakWithWhiteSpace)458 TEST_F(AccessibilityTest, PositionBeforeLineBreakWithWhiteSpace) {
459   SetBodyInnerHTML(R"HTML(Hello     <br id="br">     there)HTML");
460   const AXObject* ax_br = GetAXObjectByElementId("br");
461   ASSERT_NE(nullptr, ax_br);
462   ASSERT_EQ(ax::mojom::Role::kLineBreak, ax_br->RoleValue());
463   const AXObject* ax_div = ax_br->ParentObjectUnignored();
464   ASSERT_NE(nullptr, ax_div);
465   ASSERT_EQ(ax::mojom::Role::kGenericContainer, ax_div->RoleValue());
466 
467   const auto ax_position = AXPosition::CreatePositionBeforeObject(*ax_br);
468   EXPECT_FALSE(ax_position.IsTextPosition());
469   EXPECT_EQ(ax_div, ax_position.ContainerObject());
470   EXPECT_EQ(1, ax_position.ChildIndex());
471   EXPECT_EQ(ax_br, ax_position.ChildAfterTreePosition());
472 
473   const auto position = ax_position.ToPositionWithAffinity();
474   EXPECT_EQ(GetDocument().body(), position.AnchorNode());
475   EXPECT_EQ(1, position.GetPosition().OffsetInContainerNode());
476 
477   const auto ax_position_from_dom = AXPosition::FromPosition(position);
478   EXPECT_EQ(ax_position, ax_position_from_dom);
479 }
480 
TEST_F(AccessibilityTest,PositionAfterLineBreakWithWhiteSpace)481 TEST_F(AccessibilityTest, PositionAfterLineBreakWithWhiteSpace) {
482   SetBodyInnerHTML(R"HTML(Hello     <br id="br">     there)HTML");
483   const AXObject* ax_br = GetAXObjectByElementId("br");
484   ASSERT_NE(nullptr, ax_br);
485   ASSERT_EQ(ax::mojom::Role::kLineBreak, ax_br->RoleValue());
486   const AXObject* ax_static_text =
487       GetAXRootObject()->DeepestLastChildIncludingIgnored();
488   ASSERT_NE(nullptr, ax_static_text);
489   ASSERT_EQ(ax::mojom::Role::kStaticText, ax_static_text->RoleValue());
490 
491   const auto ax_position = AXPosition::CreatePositionAfterObject(*ax_br);
492   EXPECT_EQ(ax_static_text, ax_position.ContainerObject());
493   EXPECT_TRUE(ax_position.IsTextPosition());
494   EXPECT_EQ(0, ax_position.TextOffset());
495 
496   const auto position = ax_position.ToPositionWithAffinity();
497   EXPECT_EQ(ax_static_text->GetNode(), position.AnchorNode());
498   EXPECT_TRUE(position.GetPosition().IsOffsetInAnchor());
499   // Any white space in the DOM should have been skipped.
500   EXPECT_EQ(5, position.GetPosition().OffsetInContainerNode());
501 
502   const auto ax_position_from_dom = AXPosition::FromPosition(position);
503   EXPECT_EQ(ax_position, ax_position_from_dom);
504 }
505 
TEST_F(AccessibilityTest,FirstPositionInDivContainerWithWhiteSpace)506 TEST_F(AccessibilityTest, FirstPositionInDivContainerWithWhiteSpace) {
507   SetBodyInnerHTML(R"HTML(<div id="div">     Hello<br>there     </div>)HTML");
508   const Element* div = GetElementById("div");
509   ASSERT_NE(nullptr, div);
510   const AXObject* ax_div = GetAXObjectByElementId("div");
511   ASSERT_NE(nullptr, ax_div);
512   ASSERT_EQ(ax::mojom::Role::kGenericContainer, ax_div->RoleValue());
513   const AXObject* ax_static_text =
514       GetAXRootObject()->DeepestFirstChildIncludingIgnored();
515   ASSERT_NE(nullptr, ax_static_text);
516   ASSERT_EQ(ax::mojom::Role::kStaticText, ax_static_text->RoleValue());
517 
518   // "Before object" positions that are anchored to before a text object are
519   // always converted to a "text position" before the object's first unignored
520   // character.
521   const auto ax_position = AXPosition::CreateFirstPositionInObject(*ax_div);
522   const auto position = ax_position.ToPositionWithAffinity();
523   EXPECT_EQ(div->firstChild(), position.AnchorNode());
524   EXPECT_TRUE(position.GetPosition().IsOffsetInAnchor());
525   // Any white space in the DOM should have been skipped.
526   EXPECT_EQ(5, position.GetPosition().OffsetInContainerNode());
527 
528   const auto ax_position_from_dom = AXPosition::FromPosition(position);
529   EXPECT_EQ(ax_position, ax_position_from_dom);
530   EXPECT_TRUE(ax_position_from_dom.IsTextPosition());
531   EXPECT_EQ(ax_static_text, ax_position_from_dom.ContainerObject());
532   EXPECT_EQ(0, ax_position_from_dom.TextOffset());
533   EXPECT_EQ(nullptr, ax_position_from_dom.ChildAfterTreePosition());
534 }
535 
TEST_F(AccessibilityTest,LastPositionInDivContainerWithWhiteSpace)536 TEST_F(AccessibilityTest, LastPositionInDivContainerWithWhiteSpace) {
537   SetBodyInnerHTML(R"HTML(<div id="div">     Hello<br>there     </div>
538                    <div>Next div</div>)HTML");
539   const Element* div = GetElementById("div");
540   ASSERT_NE(nullptr, div);
541   const AXObject* ax_div = GetAXObjectByElementId("div");
542   ASSERT_NE(nullptr, ax_div);
543   ASSERT_EQ(ax::mojom::Role::kGenericContainer, ax_div->RoleValue());
544 
545   const auto ax_position = AXPosition::CreateLastPositionInObject(*ax_div);
546   const auto position = ax_position.ToPositionWithAffinity();
547   EXPECT_EQ(div, position.AnchorNode());
548   EXPECT_TRUE(position.GetPosition().IsAfterChildren());
549 
550   const auto ax_position_from_dom = AXPosition::FromPosition(position);
551   EXPECT_EQ(ax_position, ax_position_from_dom);
552   EXPECT_EQ(nullptr, ax_position_from_dom.ChildAfterTreePosition());
553 }
554 
TEST_F(AccessibilityTest,FirstPositionInTextContainerWithWhiteSpace)555 TEST_F(AccessibilityTest, FirstPositionInTextContainerWithWhiteSpace) {
556   SetBodyInnerHTML(R"HTML(<div id="div">     Hello     </div>)HTML");
557   const Node* text = GetElementById("div")->firstChild();
558   ASSERT_NE(nullptr, text);
559   ASSERT_TRUE(text->IsTextNode());
560   const AXObject* ax_static_text =
561       GetAXObjectByElementId("div")->FirstChildIncludingIgnored();
562   ASSERT_NE(nullptr, ax_static_text);
563   ASSERT_EQ(ax::mojom::Role::kStaticText, ax_static_text->RoleValue());
564 
565   const auto ax_position =
566       AXPosition::CreateFirstPositionInObject(*ax_static_text);
567   const auto position = ax_position.ToPositionWithAffinity();
568   EXPECT_EQ(text, position.AnchorNode());
569   // Any white space in the DOM should have been skipped.
570   EXPECT_EQ(5, position.GetPosition().OffsetInContainerNode());
571 
572   const auto ax_position_from_dom = AXPosition::FromPosition(position);
573   EXPECT_EQ(ax_position, ax_position_from_dom);
574   EXPECT_EQ(nullptr, ax_position_from_dom.ChildAfterTreePosition());
575 }
576 
TEST_F(AccessibilityTest,LastPositionInTextContainerWithWhiteSpace)577 TEST_F(AccessibilityTest, LastPositionInTextContainerWithWhiteSpace) {
578   SetBodyInnerHTML(R"HTML(<div id="div">     Hello     </div>)HTML");
579   const Node* text = GetElementById("div")->lastChild();
580   ASSERT_NE(nullptr, text);
581   ASSERT_TRUE(text->IsTextNode());
582   const AXObject* ax_static_text =
583       GetAXObjectByElementId("div")->LastChildIncludingIgnored();
584   ASSERT_NE(nullptr, ax_static_text);
585   ASSERT_EQ(ax::mojom::Role::kStaticText, ax_static_text->RoleValue());
586 
587   const auto ax_position =
588       AXPosition::CreateLastPositionInObject(*ax_static_text);
589   const auto position = ax_position.ToPositionWithAffinity();
590   EXPECT_EQ(text, position.AnchorNode());
591   EXPECT_EQ(10, position.GetPosition().OffsetInContainerNode());
592 
593   const auto ax_position_from_dom = AXPosition::FromPosition(position);
594   EXPECT_EQ(ax_position, ax_position_from_dom);
595   EXPECT_EQ(nullptr, ax_position_from_dom.ChildAfterTreePosition());
596 }
597 
598 // Test that DOM positions in white space will be collapsed to the first or last
599 // valid offset in an |AXPosition|.
TEST_F(AccessibilityTest,AXPositionFromDOMPositionWithWhiteSpace)600 TEST_F(AccessibilityTest, AXPositionFromDOMPositionWithWhiteSpace) {
601   SetBodyInnerHTML(R"HTML(<div id="div">     Hello     </div>)HTML");
602   const Node* text = GetElementById("div")->firstChild();
603   ASSERT_NE(nullptr, text);
604   ASSERT_TRUE(text->IsTextNode());
605   ASSERT_EQ(15U, text->textContent().length());
606   const AXObject* ax_static_text =
607       GetAXObjectByElementId("div")->FirstChildIncludingIgnored();
608   ASSERT_NE(nullptr, ax_static_text);
609   ASSERT_EQ(ax::mojom::Role::kStaticText, ax_static_text->RoleValue());
610 
611   const Position position_at_start(*text, 0);
612   const auto ax_position_at_start = AXPosition::FromPosition(position_at_start);
613   EXPECT_TRUE(ax_position_at_start.IsTextPosition());
614   EXPECT_EQ(ax_static_text, ax_position_at_start.ContainerObject());
615   EXPECT_EQ(0, ax_position_at_start.TextOffset());
616   EXPECT_EQ(nullptr, ax_position_at_start.ChildAfterTreePosition());
617 
618   const Position position_after_white_space(*text, 5);
619   const auto ax_position_after_white_space =
620       AXPosition::FromPosition(position_after_white_space);
621   EXPECT_TRUE(ax_position_after_white_space.IsTextPosition());
622   EXPECT_EQ(ax_static_text, ax_position_after_white_space.ContainerObject());
623   EXPECT_EQ(0, ax_position_after_white_space.TextOffset());
624   EXPECT_EQ(nullptr, ax_position_after_white_space.ChildAfterTreePosition());
625 
626   const Position position_at_end(*text, 15);
627   const auto ax_position_at_end = AXPosition::FromPosition(position_at_end);
628   EXPECT_TRUE(ax_position_at_end.IsTextPosition());
629   EXPECT_EQ(ax_static_text, ax_position_at_end.ContainerObject());
630   EXPECT_EQ(5, ax_position_at_end.TextOffset());
631   EXPECT_EQ(nullptr, ax_position_at_end.ChildAfterTreePosition());
632 
633   const Position position_before_white_space(*text, 10);
634   const auto ax_position_before_white_space =
635       AXPosition::FromPosition(position_before_white_space);
636   EXPECT_TRUE(ax_position_before_white_space.IsTextPosition());
637   EXPECT_EQ(ax_static_text, ax_position_before_white_space.ContainerObject());
638   EXPECT_EQ(5, ax_position_before_white_space.TextOffset());
639   EXPECT_EQ(nullptr, ax_position_before_white_space.ChildAfterTreePosition());
640 }
641 
642 //
643 // Test affinity.
644 // We need to distinguish between the caret at the end of one line and the
645 // beginning of the next.
646 //
647 
TEST_F(AccessibilityTest,PositionInTextWithAffinity)648 TEST_F(AccessibilityTest, PositionInTextWithAffinity) {
649   SetBodyInnerHTML(R"HTML(<p id="paragraph">Hello</p>)HTML");
650   const Node* text = GetElementById("paragraph")->firstChild();
651   ASSERT_NE(nullptr, text);
652   ASSERT_TRUE(text->IsTextNode());
653   const AXObject* ax_static_text =
654       GetAXObjectByElementId("paragraph")->FirstChildIncludingIgnored();
655   ASSERT_NE(nullptr, ax_static_text);
656   ASSERT_EQ(ax::mojom::Role::kStaticText, ax_static_text->RoleValue());
657 
658   // Converting from AX to DOM positions should maintain affinity.
659   const auto ax_position = AXPosition::CreatePositionInTextObject(
660       *ax_static_text, 3, TextAffinity::kUpstream);
661   const auto position = ax_position.ToPositionWithAffinity();
662   EXPECT_EQ(TextAffinity::kUpstream, position.Affinity());
663 
664   // Converting from DOM to AX positions should maintain affinity.
665   const auto ax_position_from_dom = AXPosition::FromPosition(position);
666   EXPECT_EQ(TextAffinity::kUpstream, ax_position.Affinity());
667 }
668 
669 //
670 // Test converting to and from accessibility positions with offsets in HTML
671 // labels. HTML labels are ignored in the accessibility tree when associated
672 // with checkboxes and radio buttons.
673 //
674 
TEST_F(AccessibilityTest,PositionInHTMLLabel)675 TEST_F(AccessibilityTest, PositionInHTMLLabel) {
676   SetBodyInnerHTML(R"HTML(
677       <label id="label" for="input">
678         Label text.
679       </label>
680       <p id="paragraph">Intervening paragraph.</p>
681       <input id="input" type="checkbox" checked>
682       )HTML");
683 
684   const Node* label = GetElementById("label");
685   ASSERT_NE(nullptr, label);
686   const Node* label_text = label->firstChild();
687   ASSERT_NE(nullptr, label_text);
688   ASSERT_TRUE(label_text->IsTextNode());
689   const Node* paragraph = GetElementById("paragraph");
690   ASSERT_NE(nullptr, paragraph);
691 
692   const AXObject* ax_body = GetAXBodyObject();
693   ASSERT_NE(nullptr, ax_body);
694   ASSERT_EQ(ax::mojom::Role::kGenericContainer, ax_body->RoleValue());
695 
696   // The HTML label element should be ignored.
697   const AXObject* ax_label = GetAXObjectByElementId("label");
698   ASSERT_NE(nullptr, ax_label);
699   ASSERT_TRUE(ax_label->AccessibilityIsIgnored());
700   const AXObject* ax_paragraph = GetAXObjectByElementId("paragraph");
701   ASSERT_NE(nullptr, ax_paragraph);
702   ASSERT_EQ(ax::mojom::Role::kParagraph, ax_paragraph->RoleValue());
703 
704   // All of the following DOM positions should be ignored in the accessibility
705   // tree.
706   const auto position_before = Position::BeforeNode(*label);
707   const auto position_before_text = Position::BeforeNode(*label_text);
708   const auto position_in_text = Position::FirstPositionInNode(*label_text);
709   const auto position_after = Position::AfterNode(*label);
710 
711   for (const auto& position : {position_before, position_before_text,
712                                position_in_text, position_after}) {
713     const auto ax_position =
714         AXPosition::FromPosition(position, TextAffinity::kDownstream,
715                                  AXPositionAdjustmentBehavior::kMoveLeft);
716     EXPECT_FALSE(ax_position.IsTextPosition());
717     EXPECT_EQ(ax_body, ax_position.ContainerObject());
718     EXPECT_EQ(0, ax_position.ChildIndex());
719     EXPECT_EQ(ax_paragraph, ax_position.ChildAfterTreePosition());
720 
721     const auto position_from_ax = ax_position.ToPositionWithAffinity();
722     EXPECT_EQ(GetDocument().body(), position_from_ax.AnchorNode());
723     EXPECT_EQ(3, position_from_ax.GetPosition().OffsetInContainerNode());
724     EXPECT_EQ(paragraph,
725               position_from_ax.GetPosition().ComputeNodeAfterPosition());
726   }
727 }
728 
729 //
730 // Objects with "display: none" or the "hidden" attribute are accessibility
731 // ignored.
732 //
733 
TEST_F(AccessibilityTest,PositionInIgnoredObject)734 TEST_F(AccessibilityTest, PositionInIgnoredObject) {
735   SetBodyInnerHTML(R"HTML(
736       <div id="hidden" hidden>Hidden.</div><p id="visible">Visible.</p>
737       )HTML");
738 
739   const Node* hidden = GetElementById("hidden");
740   ASSERT_NE(nullptr, hidden);
741   const Node* visible = GetElementById("visible");
742   ASSERT_NE(nullptr, visible);
743 
744   const AXObject* ax_root = GetAXRootObject();
745   ASSERT_NE(nullptr, ax_root);
746   ASSERT_EQ(ax::mojom::Role::kRootWebArea, ax_root->RoleValue());
747   ASSERT_EQ(1, ax_root->ChildCountIncludingIgnored());
748 
749   const AXObject* ax_html = ax_root->FirstChildIncludingIgnored();
750   ASSERT_NE(nullptr, ax_html);
751   ASSERT_EQ(ax::mojom::Role::kGenericContainer, ax_html->RoleValue());
752   ASSERT_EQ(1, ax_html->ChildCountIncludingIgnored());
753 
754   const AXObject* ax_body = GetAXBodyObject();
755   ASSERT_NE(nullptr, ax_body);
756   ASSERT_EQ(ax::mojom::Role::kGenericContainer, ax_body->RoleValue());
757   ASSERT_EQ(2, ax_body->ChildCountIncludingIgnored());
758 
759   const AXObject* ax_hidden = GetAXObjectByElementId("hidden");
760   ASSERT_NE(nullptr, ax_hidden);
761   ASSERT_EQ(ax::mojom::Role::kGenericContainer, ax_hidden->RoleValue());
762   ASSERT_TRUE(ax_hidden->AccessibilityIsIgnoredButIncludedInTree());
763 
764   const AXObject* ax_visible = GetAXObjectByElementId("visible");
765   ASSERT_NE(nullptr, ax_visible);
766   ASSERT_EQ(ax::mojom::Role::kParagraph, ax_visible->RoleValue());
767 
768   // The fact that there is a hidden object before |visible| should not affect
769   // setting a position before it.
770   const auto ax_position_before_visible =
771       AXPosition::CreatePositionBeforeObject(*ax_visible);
772   const auto position_before_visible =
773       ax_position_before_visible.ToPositionWithAffinity();
774   EXPECT_EQ(GetDocument().body(), position_before_visible.AnchorNode());
775   EXPECT_EQ(2, position_before_visible.GetPosition().OffsetInContainerNode());
776   EXPECT_EQ(visible,
777             position_before_visible.GetPosition().ComputeNodeAfterPosition());
778 
779   const auto ax_position_before_visible_from_dom =
780       AXPosition::FromPosition(position_before_visible);
781   EXPECT_EQ(ax_position_before_visible, ax_position_before_visible_from_dom);
782   EXPECT_EQ(ax_visible,
783             ax_position_before_visible_from_dom.ChildAfterTreePosition());
784 
785   // A position at the beginning of the body will appear to be before the hidden
786   // element in the DOM.
787   const auto ax_position_first =
788       AXPosition::CreateFirstPositionInObject(*ax_root);
789   const auto position_first = ax_position_first.ToPositionWithAffinity();
790   EXPECT_EQ(GetDocument(), position_first.AnchorNode());
791   EXPECT_TRUE(position_first.GetPosition().IsBeforeChildren());
792 
793   EXPECT_EQ(GetDocument().documentElement(),
794             position_first.GetPosition().ComputeNodeAfterPosition());
795 
796   const auto ax_position_first_from_dom =
797       AXPosition::FromPosition(position_first);
798   EXPECT_EQ(ax_position_first, ax_position_first_from_dom);
799 
800   EXPECT_EQ(ax_html, ax_position_first_from_dom.ChildAfterTreePosition());
801 
802   // A DOM position before |hidden| should convert to an accessibility position
803   // before |hidden| because the node is ignored but included in the tree.
804   const auto position_before = Position::BeforeNode(*hidden);
805   const auto ax_position_before_from_dom =
806       AXPosition::FromPosition(position_before);
807   EXPECT_EQ(ax_body, ax_position_before_from_dom.ContainerObject());
808   EXPECT_EQ(0, ax_position_before_from_dom.ChildIndex());
809   EXPECT_EQ(ax_hidden, ax_position_before_from_dom.ChildAfterTreePosition());
810 
811   // A DOM position after |hidden| should convert to an accessibility position
812   // before |visible|.
813   const auto position_after = Position::AfterNode(*hidden);
814   const auto ax_position_after_from_dom =
815       AXPosition::FromPosition(position_after);
816   EXPECT_EQ(ax_body, ax_position_after_from_dom.ContainerObject());
817   EXPECT_EQ(1, ax_position_after_from_dom.ChildIndex());
818   EXPECT_EQ(ax_visible, ax_position_after_from_dom.ChildAfterTreePosition());
819 }
820 
821 //
822 // Aria-hidden can cause things in the DOM to be hidden from accessibility.
823 //
824 
TEST_F(AccessibilityTest,BeforePositionInARIAHiddenShouldNotSkipARIAHidden)825 TEST_F(AccessibilityTest, BeforePositionInARIAHiddenShouldNotSkipARIAHidden) {
826   SetBodyInnerHTML(R"HTML(
827       <div role="main" id="container">
828         <p id="before">Before aria-hidden.</p>
829         <p id="ariaHidden" aria-hidden="true">Aria-hidden.</p>
830         <p id="after">After aria-hidden.</p>
831       </div>
832       )HTML");
833 
834   const Node* container = GetElementById("container");
835   ASSERT_NE(nullptr, container);
836   const Node* after = GetElementById("after");
837   ASSERT_NE(nullptr, after);
838   const Node* hidden = GetElementById("ariaHidden");
839   ASSERT_NE(nullptr, hidden);
840 
841   const AXObject* ax_before = GetAXObjectByElementId("before");
842   ASSERT_NE(nullptr, ax_before);
843   ASSERT_EQ(ax::mojom::Role::kParagraph, ax_before->RoleValue());
844   const AXObject* ax_after = GetAXObjectByElementId("after");
845   ASSERT_NE(nullptr, ax_after);
846   ASSERT_EQ(ax::mojom::Role::kParagraph, ax_after->RoleValue());
847   const AXObject* ax_hidden = GetAXObjectByElementId("ariaHidden");
848   ASSERT_NE(nullptr, ax_hidden);
849   ASSERT_TRUE(ax_hidden->AccessibilityIsIgnored());
850 
851   const auto ax_position = AXPosition::CreatePositionAfterObject(*ax_before);
852   const auto position = ax_position.ToPositionWithAffinity();
853   EXPECT_EQ(container, position.AnchorNode());
854   EXPECT_EQ(3, position.GetPosition().OffsetInContainerNode());
855   EXPECT_EQ(hidden, position.GetPosition().ComputeNodeAfterPosition());
856 
857   const auto ax_position_from_dom = AXPosition::FromPosition(position);
858   EXPECT_EQ(ax_position, ax_position_from_dom);
859   EXPECT_EQ(ax_hidden, ax_position_from_dom.ChildAfterTreePosition());
860 }
861 
TEST_F(AccessibilityTest,PreviousPositionAfterARIAHiddenShouldNotSkipARIAHidden)862 TEST_F(AccessibilityTest,
863        PreviousPositionAfterARIAHiddenShouldNotSkipARIAHidden) {
864   SetBodyInnerHTML(R"HTML(
865       <p id="before">Before aria-hidden.</p>
866       <p id="ariaHidden" aria-hidden="true">Aria-hidden.</p>
867       <p id="after">After aria-hidden.</p>
868       )HTML");
869 
870   const Node* hidden = GetElementById("ariaHidden");
871   ASSERT_NE(nullptr, hidden);
872   ASSERT_NE(nullptr, hidden->firstChild());
873   const Node* after = GetElementById("after");
874   ASSERT_NE(nullptr, after);
875 
876   const AXObject* ax_after = GetAXObjectByElementId("after");
877   ASSERT_NE(nullptr, ax_after);
878   ASSERT_EQ(ax::mojom::Role::kParagraph, ax_after->RoleValue());
879   ASSERT_NE(nullptr, GetAXObjectByElementId("ariaHidden"));
880   ASSERT_TRUE(GetAXObjectByElementId("ariaHidden")->AccessibilityIsIgnored());
881 
882   const auto ax_position = AXPosition::CreatePositionBeforeObject(*ax_after);
883   const auto position = ax_position.ToPositionWithAffinity();
884   EXPECT_EQ(GetDocument().body(), position.AnchorNode());
885   EXPECT_EQ(5, position.GetPosition().OffsetInContainerNode());
886   EXPECT_EQ(after, position.GetPosition().ComputeNodeAfterPosition());
887 
888   const auto ax_position_from_dom = AXPosition::FromPosition(position);
889   EXPECT_EQ(ax_position, ax_position_from_dom);
890   EXPECT_EQ(ax_after, ax_position_from_dom.ChildAfterTreePosition());
891 
892   const auto ax_position_previous = ax_position.CreatePreviousPosition();
893   const auto position_previous = ax_position_previous.ToPositionWithAffinity();
894   EXPECT_EQ(hidden->firstChild(), position_previous.AnchorNode());
895   EXPECT_EQ(12, position_previous.GetPosition().OffsetInContainerNode());
896   EXPECT_EQ(nullptr,
897             position_previous.GetPosition().ComputeNodeAfterPosition());
898 
899   const auto ax_position_previous_from_dom =
900       AXPosition::FromPosition(position_previous);
901   EXPECT_EQ(ax_position_previous, ax_position_previous_from_dom);
902   EXPECT_EQ(nullptr, ax_position_previous_from_dom.ChildAfterTreePosition());
903 }
904 
TEST_F(AccessibilityTest,FromPositionInARIAHidden)905 TEST_F(AccessibilityTest, FromPositionInARIAHidden) {
906   SetBodyInnerHTML(R"HTML(
907       <div role="main" id="container">
908         <p id="before">Before aria-hidden.</p>
909         <p id="ariaHidden" aria-hidden="true">Aria-hidden.</p>
910         <p id="after">After aria-hidden.</p>
911       </div>
912       )HTML");
913 
914   const Node* hidden = GetElementById("ariaHidden");
915   ASSERT_NE(nullptr, hidden);
916 
917   const AXObject* ax_container = GetAXObjectByElementId("container");
918   ASSERT_NE(nullptr, ax_container);
919   ASSERT_EQ(ax::mojom::Role::kMain, ax_container->RoleValue());
920   ASSERT_EQ(3, ax_container->ChildCountIncludingIgnored());
921   const AXObject* ax_before = GetAXObjectByElementId("before");
922   ASSERT_NE(nullptr, ax_before);
923   ASSERT_EQ(ax::mojom::Role::kParagraph, ax_before->RoleValue());
924   const AXObject* ax_after = GetAXObjectByElementId("after");
925   ASSERT_NE(nullptr, ax_after);
926   ASSERT_EQ(ax::mojom::Role::kParagraph, ax_after->RoleValue());
927   const AXObject* ax_hidden = GetAXObjectByElementId("ariaHidden");
928   ASSERT_NE(nullptr, ax_hidden);
929   ASSERT_TRUE(ax_hidden->AccessibilityIsIgnored());
930 
931   const auto position_first = Position::FirstPositionInNode(*hidden);
932   // Since "ax_hidden" has a static text child, the AXPosition should move to an
933   // equivalent position on the static text child.
934   auto ax_position_left =
935       AXPosition::FromPosition(position_first, TextAffinity::kDownstream,
936                                AXPositionAdjustmentBehavior::kMoveLeft);
937   EXPECT_TRUE(ax_position_left.IsValid());
938   EXPECT_TRUE(ax_position_left.IsTextPosition());
939   EXPECT_EQ(ax_hidden->FirstChildIncludingIgnored(),
940             ax_position_left.ContainerObject());
941   EXPECT_EQ(0, ax_position_left.TextOffset());
942 
943   // In this case, the adjustment behavior should not affect the outcome because
944   // there is an equivalent AXPosition in the static text child.
945   auto ax_position_right =
946       AXPosition::FromPosition(position_first, TextAffinity::kDownstream,
947                                AXPositionAdjustmentBehavior::kMoveRight);
948   EXPECT_TRUE(ax_position_right.IsValid());
949   EXPECT_TRUE(ax_position_right.IsTextPosition());
950   EXPECT_EQ(ax_hidden->FirstChildIncludingIgnored(),
951             ax_position_right.ContainerObject());
952   EXPECT_EQ(0, ax_position_right.TextOffset());
953 
954   const auto position_before = Position::BeforeNode(*hidden);
955   ax_position_left =
956       AXPosition::FromPosition(position_before, TextAffinity::kDownstream,
957                                AXPositionAdjustmentBehavior::kMoveLeft);
958   EXPECT_TRUE(ax_position_left.IsValid());
959   EXPECT_FALSE(ax_position_left.IsTextPosition());
960   EXPECT_EQ(ax_container, ax_position_left.ContainerObject());
961   EXPECT_EQ(1, ax_position_left.ChildIndex());
962   EXPECT_EQ(ax_hidden, ax_position_left.ChildAfterTreePosition());
963 
964   // Since an AXPosition before "ax_hidden" is valid, i.e. it does not need to
965   // be adjusted, then adjustment behavior should not make a difference in the
966   // outcome.
967   ax_position_right =
968       AXPosition::FromPosition(position_before, TextAffinity::kDownstream,
969                                AXPositionAdjustmentBehavior::kMoveRight);
970   EXPECT_TRUE(ax_position_right.IsValid());
971   EXPECT_FALSE(ax_position_right.IsTextPosition());
972   EXPECT_EQ(ax_container, ax_position_right.ContainerObject());
973   EXPECT_EQ(1, ax_position_right.ChildIndex());
974   EXPECT_EQ(ax_hidden, ax_position_right.ChildAfterTreePosition());
975 
976   // The DOM node right after "hidden" is accessibility ignored, so we should
977   // see an adjustment in the relevant direction.
978   const auto position_after = Position::AfterNode(*hidden);
979   ax_position_left =
980       AXPosition::FromPosition(position_after, TextAffinity::kDownstream,
981                                AXPositionAdjustmentBehavior::kMoveLeft);
982   EXPECT_TRUE(ax_position_left.IsValid());
983   EXPECT_TRUE(ax_position_left.IsTextPosition());
984   EXPECT_EQ(ax_hidden->FirstChildIncludingIgnored(),
985             ax_position_left.ContainerObject());
986   EXPECT_EQ(12, ax_position_left.TextOffset());
987 
988   ax_position_right =
989       AXPosition::FromPosition(position_after, TextAffinity::kDownstream,
990                                AXPositionAdjustmentBehavior::kMoveRight);
991   EXPECT_TRUE(ax_position_right.IsValid());
992   EXPECT_FALSE(ax_position_right.IsTextPosition());
993   EXPECT_EQ(ax_container, ax_position_right.ContainerObject());
994   EXPECT_EQ(2, ax_position_right.ChildIndex());
995   EXPECT_EQ(ax_after, ax_position_right.ChildAfterTreePosition());
996 }
997 
998 //
999 // Canvas fallback can cause things to be in the accessibility tree that are not
1000 // in the layout tree.
1001 //
1002 
TEST_F(AccessibilityTest,PositionInCanvas)1003 TEST_F(AccessibilityTest, PositionInCanvas) {
1004   SetBodyInnerHTML(R"HTML(
1005       <canvas id="canvas1" width="100" height="100">Fallback text</canvas>
1006       <canvas id="canvas2" width="100" height="100">
1007       <button id="button">Fallback button</button>
1008     </canvas>
1009     )HTML");
1010 
1011   const Node* canvas_1 = GetElementById("canvas1");
1012   ASSERT_NE(nullptr, canvas_1);
1013   const Node* text = canvas_1->firstChild();
1014   ASSERT_NE(nullptr, text);
1015   ASSERT_TRUE(text->IsTextNode());
1016   const Node* canvas_2 = GetElementById("canvas2");
1017   ASSERT_NE(nullptr, canvas_2);
1018   const Node* button = GetElementById("button");
1019   ASSERT_NE(nullptr, button);
1020 
1021   const AXObject* ax_canvas_1 = GetAXObjectByElementId("canvas1");
1022   ASSERT_NE(nullptr, ax_canvas_1);
1023   ASSERT_EQ(ax::mojom::Role::kCanvas, ax_canvas_1->RoleValue());
1024   const AXObject* ax_text = ax_canvas_1->FirstChildIncludingIgnored();
1025   ASSERT_NE(nullptr, ax_text);
1026   ASSERT_EQ(ax::mojom::Role::kStaticText, ax_text->RoleValue());
1027   const AXObject* ax_canvas_2 = GetAXObjectByElementId("canvas2");
1028   ASSERT_NE(nullptr, ax_canvas_2);
1029   ASSERT_EQ(ax::mojom::Role::kCanvas, ax_canvas_2->RoleValue());
1030   const AXObject* ax_button = GetAXObjectByElementId("button");
1031   ASSERT_NE(nullptr, ax_button);
1032   ASSERT_EQ(ax::mojom::Role::kButton, ax_button->RoleValue());
1033 
1034   // The first child of "canvas1" is a text object. Creating a "before children"
1035   // position in this canvas should return the equivalent text position anchored
1036   // to before the first character of the text object.
1037   const auto ax_position_1 =
1038       AXPosition::CreateFirstPositionInObject(*ax_canvas_1);
1039   EXPECT_TRUE(ax_position_1.IsTextPosition());
1040   EXPECT_EQ(ax_text, ax_position_1.ContainerObject());
1041   EXPECT_EQ(0, ax_position_1.TextOffset());
1042 
1043   const auto position_1 = ax_position_1.ToPositionWithAffinity();
1044   EXPECT_EQ(text, position_1.AnchorNode());
1045   EXPECT_TRUE(position_1.GetPosition().IsOffsetInAnchor());
1046   EXPECT_EQ(0, position_1.GetPosition().OffsetInContainerNode());
1047 
1048   const auto ax_position_from_dom_1 = AXPosition::FromPosition(position_1);
1049   EXPECT_EQ(ax_position_1, ax_position_from_dom_1);
1050 
1051   const auto ax_position_2 = AXPosition::CreatePositionBeforeObject(*ax_text);
1052   EXPECT_TRUE(ax_position_2.IsTextPosition());
1053   EXPECT_EQ(ax_text, ax_position_2.ContainerObject());
1054   EXPECT_EQ(0, ax_position_2.TextOffset());
1055 
1056   const auto position_2 = ax_position_2.ToPositionWithAffinity();
1057   EXPECT_EQ(text, position_2.AnchorNode());
1058   EXPECT_EQ(0, position_2.GetPosition().OffsetInContainerNode());
1059 
1060   const auto ax_position_from_dom_2 = AXPosition::FromPosition(position_2);
1061   EXPECT_EQ(ax_position_2, ax_position_from_dom_2);
1062 
1063   const auto ax_position_3 =
1064       AXPosition::CreateLastPositionInObject(*ax_canvas_2);
1065   EXPECT_FALSE(ax_position_3.IsTextPosition());
1066   EXPECT_EQ(ax_canvas_2, ax_position_3.ContainerObject());
1067   EXPECT_EQ(1, ax_position_3.ChildIndex());
1068   EXPECT_EQ(nullptr, ax_position_3.ChildAfterTreePosition());
1069 
1070   const auto position_3 = ax_position_3.ToPositionWithAffinity();
1071   EXPECT_EQ(canvas_2, position_3.AnchorNode());
1072   // There is a line break between the start of the canvas and the button.
1073   EXPECT_EQ(2, position_3.GetPosition().ComputeOffsetInContainerNode());
1074 
1075   const auto ax_position_from_dom_3 = AXPosition::FromPosition(position_3);
1076   EXPECT_EQ(ax_position_3, ax_position_from_dom_3);
1077 
1078   const auto ax_position_4 = AXPosition::CreatePositionBeforeObject(*ax_button);
1079   EXPECT_FALSE(ax_position_4.IsTextPosition());
1080   EXPECT_EQ(ax_canvas_2, ax_position_4.ContainerObject());
1081   EXPECT_EQ(0, ax_position_4.ChildIndex());
1082   EXPECT_EQ(ax_button, ax_position_4.ChildAfterTreePosition());
1083 
1084   const auto position_4 = ax_position_4.ToPositionWithAffinity();
1085   EXPECT_EQ(canvas_2, position_4.AnchorNode());
1086   // There is a line break between the start of the canvas and the button.
1087   EXPECT_EQ(1, position_4.GetPosition().ComputeOffsetInContainerNode());
1088   EXPECT_EQ(button, position_4.GetPosition().ComputeNodeAfterPosition());
1089 
1090   const auto ax_position_from_dom_4 = AXPosition::FromPosition(position_4);
1091   EXPECT_EQ(ax_position_4, ax_position_from_dom_4);
1092 }
1093 
1094 //
1095 // Some layout objects, e.g. list bullets and CSS::before/after content, appear
1096 // in the accessibility tree but are not present in the DOM.
1097 //
1098 
TEST_F(AccessibilityTest,PositionBeforeListMarker)1099 TEST_F(AccessibilityTest, PositionBeforeListMarker) {
1100   SetBodyInnerHTML(R"HTML(
1101       <ul id="list">
1102         <li id="listItem">Item.</li>
1103       </ul>
1104       )HTML");
1105 
1106   const Node* list = GetElementById("list");
1107   ASSERT_NE(nullptr, list);
1108   const Node* item = GetElementById("listItem");
1109   ASSERT_NE(nullptr, item);
1110   const Node* text = item->firstChild();
1111   ASSERT_NE(nullptr, text);
1112   ASSERT_TRUE(text->IsTextNode());
1113 
1114   const AXObject* ax_item = GetAXObjectByElementId("listItem");
1115   ASSERT_NE(nullptr, ax_item);
1116   ASSERT_EQ(ax::mojom::Role::kListItem, ax_item->RoleValue());
1117   ASSERT_EQ(2, ax_item->ChildCountIncludingIgnored());
1118   const AXObject* ax_marker = ax_item->FirstChildIncludingIgnored();
1119   ASSERT_NE(nullptr, ax_marker);
1120   ASSERT_EQ(ax::mojom::Role::kListMarker, ax_marker->RoleValue());
1121 
1122   //
1123   // Test adjusting invalid DOM positions to the left.
1124   //
1125 
1126   const auto ax_position_1 = AXPosition::CreateFirstPositionInObject(*ax_item);
1127   EXPECT_EQ(ax_item, ax_position_1.ContainerObject());
1128   EXPECT_FALSE(ax_position_1.IsTextPosition());
1129   EXPECT_EQ(0, ax_position_1.ChildIndex());
1130   EXPECT_EQ(ax_marker, ax_position_1.ChildAfterTreePosition());
1131 
1132   const auto position_1 = ax_position_1.ToPositionWithAffinity(
1133       AXPositionAdjustmentBehavior::kMoveLeft);
1134   EXPECT_EQ(list, position_1.AnchorNode());
1135   // There is a line break between the start of the list and the first item.
1136   EXPECT_EQ(1, position_1.GetPosition().OffsetInContainerNode());
1137   EXPECT_EQ(item, position_1.GetPosition().ComputeNodeAfterPosition());
1138 
1139   const auto ax_position_from_dom_1 = AXPosition::FromPosition(position_1);
1140   EXPECT_EQ(
1141       ax_position_1.AsValidDOMPosition(AXPositionAdjustmentBehavior::kMoveLeft),
1142       ax_position_from_dom_1);
1143   EXPECT_EQ(ax_item, ax_position_from_dom_1.ChildAfterTreePosition());
1144 
1145   const auto ax_position_2 = AXPosition::CreatePositionBeforeObject(*ax_marker);
1146   EXPECT_EQ(ax_item, ax_position_2.ContainerObject());
1147   EXPECT_FALSE(ax_position_2.IsTextPosition());
1148   EXPECT_EQ(0, ax_position_2.ChildIndex());
1149   EXPECT_EQ(ax_marker, ax_position_2.ChildAfterTreePosition());
1150 
1151   const auto position_2 = ax_position_2.ToPositionWithAffinity(
1152       AXPositionAdjustmentBehavior::kMoveLeft);
1153   EXPECT_EQ(list, position_2.AnchorNode());
1154   // There is a line break between the start of the list and the first item.
1155   EXPECT_EQ(1, position_2.GetPosition().OffsetInContainerNode());
1156   EXPECT_EQ(item, position_2.GetPosition().ComputeNodeAfterPosition());
1157 
1158   const auto ax_position_from_dom_2 = AXPosition::FromPosition(position_2);
1159   EXPECT_EQ(
1160       ax_position_2.AsValidDOMPosition(AXPositionAdjustmentBehavior::kMoveLeft),
1161       ax_position_from_dom_2);
1162   EXPECT_EQ(ax_item, ax_position_from_dom_2.ChildAfterTreePosition());
1163 
1164   //
1165   // Test adjusting the same invalid positions to the right.
1166   //
1167 
1168   const auto position_3 = ax_position_1.ToPositionWithAffinity(
1169       AXPositionAdjustmentBehavior::kMoveRight);
1170   EXPECT_EQ(text, position_3.AnchorNode());
1171   EXPECT_TRUE(position_3.GetPosition().IsOffsetInAnchor());
1172   EXPECT_EQ(0, position_3.GetPosition().OffsetInContainerNode());
1173 
1174   const auto position_4 = ax_position_2.ToPositionWithAffinity(
1175       AXPositionAdjustmentBehavior::kMoveRight);
1176   EXPECT_EQ(text, position_4.AnchorNode());
1177   EXPECT_TRUE(position_4.GetPosition().IsOffsetInAnchor());
1178   EXPECT_EQ(0, position_4.GetPosition().OffsetInContainerNode());
1179 }
1180 
TEST_F(AccessibilityTest,PositionAfterListMarker)1181 TEST_F(AccessibilityTest, PositionAfterListMarker) {
1182   SetBodyInnerHTML(R"HTML(
1183       <ol>
1184         <li id="listItem">Item.</li>
1185       </ol>
1186       )HTML");
1187 
1188   const Node* item = GetElementById("listItem");
1189   ASSERT_NE(nullptr, item);
1190   const Node* text = item->firstChild();
1191   ASSERT_NE(nullptr, text);
1192   ASSERT_TRUE(text->IsTextNode());
1193 
1194   const AXObject* ax_item = GetAXObjectByElementId("listItem");
1195   ASSERT_NE(nullptr, ax_item);
1196   ASSERT_EQ(ax::mojom::Role::kListItem, ax_item->RoleValue());
1197   ASSERT_EQ(2, ax_item->ChildCountIncludingIgnored());
1198   const AXObject* ax_marker = ax_item->FirstChildIncludingIgnored();
1199   ASSERT_NE(nullptr, ax_marker);
1200   ASSERT_EQ(ax::mojom::Role::kListMarker, ax_marker->RoleValue());
1201   const AXObject* ax_text = ax_item->LastChildIncludingIgnored();
1202   ASSERT_NE(nullptr, ax_text);
1203   ASSERT_EQ(ax::mojom::Role::kStaticText, ax_text->RoleValue());
1204 
1205   const auto ax_position = AXPosition::CreatePositionAfterObject(*ax_marker);
1206   const auto position = ax_position.ToPositionWithAffinity();
1207   EXPECT_EQ(text, position.AnchorNode());
1208   EXPECT_TRUE(position.GetPosition().IsOffsetInAnchor());
1209   EXPECT_EQ(0, position.GetPosition().OffsetInContainerNode());
1210 
1211   const auto ax_position_from_dom = AXPosition::FromPosition(position);
1212   EXPECT_EQ(ax_position, ax_position_from_dom);
1213   EXPECT_EQ(ax_text, ax_position_from_dom.ContainerObject());
1214   EXPECT_TRUE(ax_position_from_dom.IsTextPosition());
1215   EXPECT_EQ(0, ax_position_from_dom.TextOffset());
1216 }
1217 
TEST_F(AccessibilityTest,PositionInCSSContent)1218 TEST_F(AccessibilityTest, PositionInCSSContent) {
1219   SetBodyInnerHTML(kCSSBeforeAndAfter);
1220 
1221   const Node* quote = GetElementById("quote");
1222   ASSERT_NE(nullptr, quote);
1223   // CSS text nodes are not in the DOM tree.
1224   const Node* text = quote->firstChild();
1225   ASSERT_NE(nullptr, text);
1226   ASSERT_FALSE(text->IsPseudoElement());
1227   ASSERT_TRUE(text->IsTextNode());
1228 
1229   const AXObject* ax_quote = GetAXObjectByElementId("quote");
1230   ASSERT_NE(nullptr, ax_quote);
1231   ASSERT_TRUE(ax_quote->AccessibilityIsIgnored());
1232   const AXObject* ax_quote_parent = ax_quote->ParentObjectUnignored();
1233   ASSERT_NE(nullptr, ax_quote_parent);
1234   ASSERT_EQ(4, ax_quote_parent->UnignoredChildCount());
1235   const AXObject* ax_css_before = ax_quote_parent->UnignoredChildAt(0);
1236   ASSERT_NE(nullptr, ax_css_before);
1237   ASSERT_EQ(ax::mojom::Role::kStaticText, ax_css_before->RoleValue());
1238   const AXObject* ax_text = ax_quote_parent->UnignoredChildAt(1);
1239   ASSERT_NE(nullptr, ax_text);
1240   ASSERT_EQ(ax::mojom::Role::kStaticText, ax_text->RoleValue());
1241   const AXObject* ax_css_after = ax_quote_parent->UnignoredChildAt(2);
1242   ASSERT_NE(nullptr, ax_css_after);
1243   ASSERT_EQ(ax::mojom::Role::kStaticText, ax_css_after->RoleValue());
1244 
1245   const auto ax_position_before =
1246       AXPosition::CreateFirstPositionInObject(*ax_css_before);
1247   EXPECT_TRUE(ax_position_before.IsTextPosition());
1248   EXPECT_EQ(0, ax_position_before.TextOffset());
1249   EXPECT_EQ(nullptr, ax_position_before.ChildAfterTreePosition());
1250   const auto position_before = ax_position_before.ToPositionWithAffinity(
1251       AXPositionAdjustmentBehavior::kMoveRight);
1252   EXPECT_EQ(text, position_before.AnchorNode());
1253   EXPECT_EQ(0, position_before.GetPosition().OffsetInContainerNode());
1254 
1255   const auto ax_position_after =
1256       AXPosition::CreateLastPositionInObject(*ax_css_after);
1257   EXPECT_TRUE(ax_position_after.IsTextPosition());
1258   EXPECT_EQ(2, ax_position_after.TextOffset());
1259   EXPECT_EQ(nullptr, ax_position_after.ChildAfterTreePosition());
1260   const auto position_after = ax_position_after.ToPositionWithAffinity(
1261       AXPositionAdjustmentBehavior::kMoveLeft);
1262   EXPECT_EQ(text, position_after.AnchorNode());
1263   EXPECT_EQ(12, position_after.GetPosition().OffsetInContainerNode());
1264 }
1265 
TEST_F(AccessibilityTest,PositionInCSSImageContent)1266 TEST_F(AccessibilityTest, PositionInCSSImageContent) {
1267   constexpr char css_content_no_text[] = R"HTML(
1268    <style>
1269    .heading::before {
1270     content: url(data:image/gif;base64,);
1271    }
1272    </style>
1273    <h1 id="heading" class="heading">Heading</h1>)HTML";
1274   SetBodyInnerHTML(css_content_no_text);
1275 
1276   const Node* heading = GetElementById("heading");
1277   ASSERT_NE(nullptr, heading);
1278 
1279   const AXObject* ax_heading = GetAXObjectByElementId("heading");
1280   ASSERT_NE(nullptr, ax_heading);
1281   ASSERT_EQ(ax::mojom::Role::kHeading, ax_heading->RoleValue());
1282   ASSERT_EQ(2, ax_heading->ChildCountIncludingIgnored());
1283 
1284   const AXObject* ax_css_before = ax_heading->FirstChildIncludingIgnored();
1285   ASSERT_NE(nullptr, ax_css_before);
1286   ASSERT_EQ(ax::mojom::Role::kImage, ax_css_before->RoleValue());
1287 
1288   const auto ax_position_before =
1289       AXPosition::CreateFirstPositionInObject(*ax_css_before);
1290   const auto position = ax_position_before.ToPositionWithAffinity(
1291       AXPositionAdjustmentBehavior::kMoveLeft);
1292   EXPECT_EQ(GetDocument().body(), position.AnchorNode());
1293   EXPECT_EQ(3, position.GetPosition().OffsetInContainerNode());
1294 }
1295 
TEST_F(AccessibilityTest,PositionInTableWithCSSContent)1296 TEST_F(AccessibilityTest, PositionInTableWithCSSContent) {
1297   SetBodyInnerHTML(kHTMLTable);
1298 
1299   // Add some CSS content, i.e. a plus symbol before and a colon after each
1300   // table header cell.
1301   Element* const style_element =
1302       GetDocument().CreateRawElement(html_names::kStyleTag);
1303   ASSERT_NE(nullptr, style_element);
1304   style_element->setTextContent(R"STYLE(
1305       th::before {
1306         content: "+";
1307       }
1308       th::after {
1309         content: ":";
1310       }
1311       )STYLE");
1312   GetDocument().body()->insertBefore(style_element,
1313                                      GetDocument().body()->firstChild());
1314   UpdateAllLifecyclePhasesForTest();
1315 
1316   const Node* first_header_cell = GetElementById("firstHeaderCell");
1317   ASSERT_NE(nullptr, first_header_cell);
1318   const Node* last_header_cell = GetElementById("lastHeaderCell");
1319   ASSERT_NE(nullptr, last_header_cell);
1320 
1321   // CSS text nodes are not in the DOM tree.
1322   const Node* first_header_cell_text = first_header_cell->firstChild();
1323   ASSERT_NE(nullptr, first_header_cell_text);
1324   ASSERT_FALSE(first_header_cell_text->IsPseudoElement());
1325   ASSERT_TRUE(first_header_cell_text->IsTextNode());
1326   const Node* last_header_cell_text = last_header_cell->firstChild();
1327   ASSERT_NE(nullptr, last_header_cell_text);
1328   ASSERT_FALSE(last_header_cell_text->IsPseudoElement());
1329   ASSERT_TRUE(last_header_cell_text->IsTextNode());
1330 
1331   const AXObject* ax_first_header_cell =
1332       GetAXObjectByElementId("firstHeaderCell");
1333   ASSERT_NE(nullptr, ax_first_header_cell);
1334   ASSERT_EQ(ax::mojom::Role::kColumnHeader, ax_first_header_cell->RoleValue());
1335   const AXObject* ax_last_header_cell =
1336       GetAXObjectByElementId("lastHeaderCell");
1337   ASSERT_NE(nullptr, ax_last_header_cell);
1338   ASSERT_EQ(ax::mojom::Role::kColumnHeader, ax_last_header_cell->RoleValue());
1339 
1340   ASSERT_EQ(3, ax_first_header_cell->ChildCountIncludingIgnored());
1341   AXObject* const ax_first_cell_css_before =
1342       ax_first_header_cell->FirstChildIncludingIgnored();
1343   ASSERT_NE(nullptr, ax_first_cell_css_before);
1344   ASSERT_EQ(ax::mojom::Role::kStaticText,
1345             ax_first_cell_css_before->RoleValue());
1346 
1347   ASSERT_EQ(3, ax_last_header_cell->ChildCountIncludingIgnored());
1348   AXObject* const ax_last_cell_css_after =
1349       ax_last_header_cell->LastChildIncludingIgnored();
1350   ASSERT_NE(nullptr, ax_last_cell_css_after);
1351   ASSERT_EQ(ax::mojom::Role::kStaticText, ax_last_cell_css_after->RoleValue());
1352 
1353   // The first position inside the first header cell should be before the plus
1354   // symbol inside the CSS content. It should be valid in the accessibility tree
1355   // but not valid in the DOM tree.
1356   auto ax_position_before =
1357       AXPosition::CreateFirstPositionInObject(*ax_first_header_cell);
1358   EXPECT_TRUE(ax_position_before.IsTextPosition());
1359   EXPECT_EQ(0, ax_position_before.TextOffset());
1360   auto position_before = ax_position_before.ToPositionWithAffinity(
1361       AXPositionAdjustmentBehavior::kMoveRight);
1362   EXPECT_EQ(first_header_cell_text, position_before.AnchorNode());
1363   EXPECT_EQ(0, position_before.GetPosition().OffsetInContainerNode());
1364 
1365   // Same situation as above, but explicitly create a text position inside the
1366   // CSS content, instead of having it implicitly created by
1367   // CreateFirstPositionInObject.
1368   ax_position_before =
1369       AXPosition::CreateFirstPositionInObject(*ax_first_cell_css_before);
1370   EXPECT_TRUE(ax_position_before.IsTextPosition());
1371   EXPECT_EQ(0, ax_position_before.TextOffset());
1372   position_before = ax_position_before.ToPositionWithAffinity(
1373       AXPositionAdjustmentBehavior::kMoveRight);
1374   EXPECT_EQ(first_header_cell_text, position_before.AnchorNode());
1375   EXPECT_EQ(0, position_before.GetPosition().OffsetInContainerNode());
1376 
1377   // Same situation as above, but now create a text position inside the inline
1378   // text box representing the CSS content after the last header cell.
1379   ax_first_cell_css_before->LoadInlineTextBoxes();
1380   ASSERT_NE(nullptr, ax_first_cell_css_before->FirstChildIncludingIgnored());
1381   ax_position_before = AXPosition::CreateFirstPositionInObject(
1382       *ax_first_cell_css_before->FirstChildIncludingIgnored());
1383   EXPECT_TRUE(ax_position_before.IsTextPosition());
1384   EXPECT_EQ(0, ax_position_before.TextOffset());
1385   position_before = ax_position_before.ToPositionWithAffinity(
1386       AXPositionAdjustmentBehavior::kMoveRight);
1387   EXPECT_EQ(first_header_cell_text, position_before.AnchorNode());
1388   EXPECT_EQ(0, position_before.GetPosition().OffsetInContainerNode());
1389 
1390   // An "after children" position inside the last header cell should be after
1391   // the CSS content that displays a colon. It should be valid in the
1392   // accessibility tree but not valid in the DOM tree.
1393   auto ax_position_after =
1394       AXPosition::CreateLastPositionInObject(*ax_last_header_cell);
1395   EXPECT_FALSE(ax_position_after.IsTextPosition());
1396   EXPECT_EQ(3, ax_position_after.ChildIndex());
1397   auto position_after = ax_position_after.ToPositionWithAffinity(
1398       AXPositionAdjustmentBehavior::kMoveLeft);
1399   EXPECT_EQ(last_header_cell_text, position_after.AnchorNode());
1400   EXPECT_EQ(8, position_after.GetPosition().OffsetInContainerNode());
1401 
1402   // Similar to the last case, but explicitly create a text position inside the
1403   // CSS content after the last header cell.
1404   ax_position_after =
1405       AXPosition::CreateLastPositionInObject(*ax_last_cell_css_after);
1406   EXPECT_TRUE(ax_position_after.IsTextPosition());
1407   EXPECT_EQ(1, ax_position_after.TextOffset());
1408   position_after = ax_position_after.ToPositionWithAffinity(
1409       AXPositionAdjustmentBehavior::kMoveLeft);
1410   EXPECT_EQ(last_header_cell_text, position_after.AnchorNode());
1411   EXPECT_EQ(8, position_after.GetPosition().OffsetInContainerNode());
1412 
1413   // Same situation as above, but now create a text position inside the inline
1414   // text box representing the CSS content after the last header cell.
1415   ax_last_cell_css_after->LoadInlineTextBoxes();
1416   ASSERT_NE(nullptr, ax_last_cell_css_after->FirstChildIncludingIgnored());
1417   ax_position_after = AXPosition::CreateLastPositionInObject(
1418       *ax_last_cell_css_after->FirstChildIncludingIgnored());
1419   EXPECT_TRUE(ax_position_after.IsTextPosition());
1420   EXPECT_EQ(1, ax_position_after.TextOffset());
1421   position_after = ax_position_after.ToPositionWithAffinity(
1422       AXPositionAdjustmentBehavior::kMoveLeft);
1423   EXPECT_EQ(last_header_cell_text, position_after.AnchorNode());
1424   EXPECT_EQ(8, position_after.GetPosition().OffsetInContainerNode());
1425 }
1426 
1427 //
1428 // Objects deriving from |AXMockObject|, e.g. table columns, are in the
1429 // accessibility tree but are neither in the DOM or layout trees.
1430 // Same for virtual nodes created using the Accessibility Object Model (AOM).
1431 //
1432 
TEST_F(AccessibilityTest,PositionBeforeAndAfterTable)1433 TEST_F(AccessibilityTest, PositionBeforeAndAfterTable) {
1434   SetBodyInnerHTML(kHTMLTable);
1435   const Node* after = GetElementById("after");
1436   ASSERT_NE(nullptr, after);
1437   const AXObject* ax_table = GetAXObjectByElementId("table");
1438   ASSERT_NE(nullptr, ax_table);
1439   ASSERT_EQ(ax::mojom::Role::kTable, ax_table->RoleValue());
1440   const AXObject* ax_after = GetAXObjectByElementId("after");
1441   ASSERT_NE(nullptr, ax_after);
1442   ASSERT_EQ(ax::mojom::Role::kParagraph, ax_after->RoleValue());
1443 
1444   const auto ax_position_before =
1445       AXPosition::CreatePositionBeforeObject(*ax_table);
1446   const auto position_before = ax_position_before.ToPositionWithAffinity();
1447   EXPECT_EQ(GetDocument().body(), position_before.AnchorNode());
1448   EXPECT_EQ(3, position_before.GetPosition().OffsetInContainerNode());
1449   const Node* table = position_before.GetPosition().ComputeNodeAfterPosition();
1450   ASSERT_NE(nullptr, table);
1451   EXPECT_EQ(GetElementById("table"), table);
1452 
1453   const auto ax_position_before_from_dom =
1454       AXPosition::FromPosition(position_before);
1455   EXPECT_EQ(ax_position_before, ax_position_before_from_dom);
1456 
1457   const auto ax_position_after =
1458       AXPosition::CreatePositionAfterObject(*ax_table);
1459   const auto position_after = ax_position_after.ToPositionWithAffinity();
1460   EXPECT_EQ(GetDocument().body(), position_after.AnchorNode());
1461   EXPECT_EQ(5, position_after.GetPosition().OffsetInContainerNode());
1462   const Node* node_after =
1463       position_after.GetPosition().ComputeNodeAfterPosition();
1464   EXPECT_EQ(after, node_after);
1465 
1466   const auto ax_position_after_from_dom =
1467       AXPosition::FromPosition(position_after);
1468   EXPECT_EQ(ax_position_after, ax_position_after_from_dom);
1469   EXPECT_EQ(ax_after, ax_position_after_from_dom.ChildAfterTreePosition());
1470 }
1471 
TEST_F(AccessibilityTest,PositionAtStartAndEndOfTable)1472 TEST_F(AccessibilityTest, PositionAtStartAndEndOfTable) {
1473   SetBodyInnerHTML(kHTMLTable);
1474 
1475   // In the accessibility tree, the thead and tbody elements are ignored, but
1476   // they are used as anchors when converting an AX position to a DOM position
1477   // because they are the closest anchor to the first and last unignored AX
1478   // positions inside the table.
1479   const Node* thead = GetElementById("thead");
1480   ASSERT_NE(nullptr, thead);
1481   const Node* header_row = GetElementById("headerRow");
1482   ASSERT_NE(nullptr, header_row);
1483   const Node* tbody = GetElementById("tbody");
1484   ASSERT_NE(nullptr, tbody);
1485 
1486   const AXObject* ax_table = GetAXObjectByElementId("table");
1487   ASSERT_NE(nullptr, ax_table);
1488   ASSERT_EQ(ax::mojom::Role::kTable, ax_table->RoleValue());
1489   const AXObject* ax_header_row = GetAXObjectByElementId("headerRow");
1490   ASSERT_NE(nullptr, ax_header_row);
1491   ASSERT_EQ(ax::mojom::Role::kRow, ax_header_row->RoleValue());
1492 
1493   const auto ax_position_at_start =
1494       AXPosition::CreateFirstPositionInObject(*ax_table);
1495   const auto position_at_start = ax_position_at_start.ToPositionWithAffinity();
1496   EXPECT_EQ(thead, position_at_start.AnchorNode());
1497   EXPECT_EQ(1, position_at_start.GetPosition().OffsetInContainerNode());
1498   EXPECT_EQ(header_row,
1499             position_at_start.GetPosition().ComputeNodeAfterPosition());
1500 
1501   const auto ax_position_at_start_from_dom =
1502       AXPosition::FromPosition(position_at_start);
1503   EXPECT_EQ(ax_position_at_start, ax_position_at_start_from_dom);
1504   EXPECT_EQ(ax_header_row,
1505             ax_position_at_start_from_dom.ChildAfterTreePosition());
1506 
1507   const auto ax_position_at_end =
1508       AXPosition::CreateLastPositionInObject(*ax_table);
1509   const auto position_at_end = ax_position_at_end.ToPositionWithAffinity();
1510   EXPECT_EQ(tbody, position_at_end.AnchorNode());
1511   // There are three rows and a line break before and after each one.
1512   EXPECT_EQ(6, position_at_end.GetPosition().OffsetInContainerNode());
1513 
1514   const auto ax_position_at_end_from_dom =
1515       AXPosition::FromPosition(position_at_end);
1516   EXPECT_EQ(ax_position_at_end, ax_position_at_end_from_dom);
1517   EXPECT_EQ(nullptr, ax_position_at_end_from_dom.ChildAfterTreePosition());
1518 }
1519 
TEST_F(AccessibilityTest,PositionInTableHeader)1520 TEST_F(AccessibilityTest, PositionInTableHeader) {
1521   SetBodyInnerHTML(kHTMLTable);
1522 
1523   const Node* header_row = GetElementById("headerRow");
1524   ASSERT_NE(nullptr, header_row);
1525   const Node* first_header_cell = GetElementById("firstHeaderCell");
1526   ASSERT_NE(nullptr, first_header_cell);
1527 
1528   const AXObject* ax_first_header_cell =
1529       GetAXObjectByElementId("firstHeaderCell");
1530   ASSERT_NE(nullptr, ax_first_header_cell);
1531   ASSERT_EQ(ax::mojom::Role::kColumnHeader, ax_first_header_cell->RoleValue());
1532   const AXObject* ax_last_header_cell =
1533       GetAXObjectByElementId("lastHeaderCell");
1534   ASSERT_NE(nullptr, ax_last_header_cell);
1535   ASSERT_EQ(ax::mojom::Role::kColumnHeader, ax_last_header_cell->RoleValue());
1536 
1537   const auto ax_position_before =
1538       AXPosition::CreatePositionBeforeObject(*ax_first_header_cell);
1539   const auto position_before = ax_position_before.ToPositionWithAffinity();
1540   EXPECT_EQ(header_row, position_before.AnchorNode());
1541   EXPECT_EQ(1, position_before.GetPosition().OffsetInContainerNode());
1542   EXPECT_EQ(first_header_cell,
1543             position_before.GetPosition().ComputeNodeAfterPosition());
1544 
1545   const auto ax_position_before_from_dom =
1546       AXPosition::FromPosition(position_before);
1547   EXPECT_EQ(ax_position_before, ax_position_before_from_dom);
1548   EXPECT_EQ(ax_first_header_cell,
1549             ax_position_before_from_dom.ChildAfterTreePosition());
1550 
1551   const auto ax_position_after =
1552       AXPosition::CreatePositionAfterObject(*ax_last_header_cell);
1553   const auto position_after = ax_position_after.ToPositionWithAffinity();
1554   EXPECT_EQ(header_row, position_after.AnchorNode());
1555   // There are three header cells and a line break before and after each one.
1556   EXPECT_EQ(6, position_after.GetPosition().OffsetInContainerNode());
1557 
1558   const auto ax_position_after_from_dom =
1559       AXPosition::FromPosition(position_after);
1560   EXPECT_EQ(ax_position_after, ax_position_after_from_dom);
1561   EXPECT_EQ(nullptr, ax_position_after_from_dom.ChildAfterTreePosition());
1562 }
1563 
TEST_F(AccessibilityTest,PositionInTableRow)1564 TEST_F(AccessibilityTest, PositionInTableRow) {
1565   SetBodyInnerHTML(kHTMLTable);
1566 
1567   const Node* first_row = GetElementById("firstRow");
1568   ASSERT_NE(nullptr, first_row);
1569   const Node* first_cell = GetElementById("firstCell");
1570   ASSERT_NE(nullptr, first_cell);
1571   const Node* last_row = GetElementById("lastRow");
1572   ASSERT_NE(nullptr, last_row);
1573 
1574   const AXObject* ax_first_cell = GetAXObjectByElementId("firstCell");
1575   ASSERT_NE(nullptr, ax_first_cell);
1576   ASSERT_EQ(ax::mojom::Role::kRowHeader, ax_first_cell->RoleValue());
1577   const AXObject* ax_last_cell = GetAXObjectByElementId("lastCell");
1578   ASSERT_NE(nullptr, ax_last_cell);
1579   ASSERT_EQ(ax::mojom::Role::kCell, ax_last_cell->RoleValue());
1580 
1581   const auto ax_position_before =
1582       AXPosition::CreatePositionBeforeObject(*ax_first_cell);
1583   const auto position_before = ax_position_before.ToPositionWithAffinity();
1584   EXPECT_EQ(first_row, position_before.AnchorNode());
1585   EXPECT_EQ(1, position_before.GetPosition().OffsetInContainerNode());
1586   EXPECT_EQ(first_cell,
1587             position_before.GetPosition().ComputeNodeAfterPosition());
1588 
1589   const auto ax_position_before_from_dom =
1590       AXPosition::FromPosition(position_before);
1591   EXPECT_EQ(ax_position_before, ax_position_before_from_dom);
1592   EXPECT_EQ(ax_first_cell,
1593             ax_position_before_from_dom.ChildAfterTreePosition());
1594 
1595   const auto ax_position_after =
1596       AXPosition::CreatePositionAfterObject(*ax_last_cell);
1597   const auto position_after = ax_position_after.ToPositionWithAffinity();
1598   EXPECT_EQ(last_row, position_after.AnchorNode());
1599   // There are three cells on the last row and a line break before and after
1600   // each one.
1601   EXPECT_EQ(6, position_after.GetPosition().OffsetInContainerNode());
1602 
1603   const auto ax_position_after_from_dom =
1604       AXPosition::FromPosition(position_after);
1605   EXPECT_EQ(ax_position_after, ax_position_after_from_dom);
1606   EXPECT_EQ(nullptr, ax_position_after_from_dom.ChildAfterTreePosition());
1607 }
1608 
TEST_F(AccessibilityTest,DISABLED_PositionInVirtualAOMNode)1609 TEST_F(AccessibilityTest, DISABLED_PositionInVirtualAOMNode) {
1610   ScopedAccessibilityObjectModelForTest(true);
1611   SetBodyInnerHTML(kAOM);
1612 
1613   const Node* parent = GetElementById("aomParent");
1614   ASSERT_NE(nullptr, parent);
1615   const Node* after = GetElementById("after");
1616   ASSERT_NE(nullptr, after);
1617 
1618   const AXObject* ax_parent = GetAXObjectByElementId("aomParent");
1619   ASSERT_NE(nullptr, ax_parent);
1620   ASSERT_EQ(ax::mojom::Role::kGenericContainer, ax_parent->RoleValue());
1621   ASSERT_EQ(1, ax_parent->ChildCountIncludingIgnored());
1622   const AXObject* ax_button = ax_parent->FirstChildIncludingIgnored();
1623   ASSERT_NE(nullptr, ax_button);
1624   ASSERT_EQ(ax::mojom::Role::kButton, ax_button->RoleValue());
1625   const AXObject* ax_after = GetAXObjectByElementId("after");
1626   ASSERT_NE(nullptr, ax_after);
1627   ASSERT_EQ(ax::mojom::Role::kParagraph, ax_after->RoleValue());
1628 
1629   const auto ax_position_before =
1630       AXPosition::CreatePositionBeforeObject(*ax_button);
1631   const auto position_before = ax_position_before.ToPositionWithAffinity();
1632   EXPECT_EQ(parent, position_before.AnchorNode());
1633   EXPECT_TRUE(position_before.GetPosition().IsBeforeChildren());
1634   EXPECT_EQ(nullptr, position_before.GetPosition().ComputeNodeAfterPosition());
1635 
1636   const auto ax_position_before_from_dom =
1637       AXPosition::FromPosition(position_before);
1638   EXPECT_EQ(ax_position_before, ax_position_before_from_dom);
1639   EXPECT_EQ(ax_button, ax_position_before_from_dom.ChildAfterTreePosition());
1640 
1641   const auto ax_position_after =
1642       AXPosition::CreatePositionAfterObject(*ax_button);
1643   const auto position_after = ax_position_after.ToPositionWithAffinity();
1644   EXPECT_EQ(after, position_after.AnchorNode());
1645   EXPECT_TRUE(position_after.GetPosition().IsBeforeChildren());
1646   EXPECT_EQ(nullptr, position_after.GetPosition().ComputeNodeAfterPosition());
1647 
1648   const auto ax_position_after_from_dom =
1649       AXPosition::FromPosition(position_after);
1650   EXPECT_EQ(ax_position_after, ax_position_after_from_dom);
1651   EXPECT_EQ(ax_after, ax_position_after_from_dom.ChildAfterTreePosition());
1652 }
1653 
TEST_F(AccessibilityTest,PositionInInvalidMapLayout)1654 TEST_F(AccessibilityTest, PositionInInvalidMapLayout) {
1655   SetBodyInnerHTML(kMap);
1656 
1657   Node* br = GetElementById("br");
1658   ASSERT_NE(nullptr, br);
1659   Node* map = GetElementById("map");
1660   ASSERT_NE(nullptr, map);
1661 
1662   // Create an invalid layout by appending a child to the <br>
1663   br->appendChild(map);
1664   GetDocument().UpdateStyleAndLayoutTree();
1665 
1666   const AXObject* ax_map = GetAXObjectByElementId("map");
1667   ASSERT_NE(nullptr, ax_map);
1668   ASSERT_EQ(ax::mojom::Role::kGenericContainer, ax_map->RoleValue());
1669 
1670   const auto ax_position_before =
1671       AXPosition::CreatePositionBeforeObject(*ax_map);
1672   const auto position_before = ax_position_before.ToPositionWithAffinity();
1673   EXPECT_EQ(nullptr, position_before.AnchorNode());
1674   EXPECT_EQ(0, position_before.GetPosition().OffsetInContainerNode());
1675 
1676   const auto ax_position_after = AXPosition::CreatePositionAfterObject(*ax_map);
1677   const auto position_after = ax_position_after.ToPositionWithAffinity();
1678   EXPECT_EQ(nullptr, position_after.AnchorNode());
1679   EXPECT_EQ(0, position_after.GetPosition().OffsetInContainerNode());
1680 }
1681 
TEST_P(ParameterizedAccessibilityTest,ToPositionWithAffinityWithMultipleInlineTextBoxes)1682 TEST_P(ParameterizedAccessibilityTest,
1683        ToPositionWithAffinityWithMultipleInlineTextBoxes) {
1684   // This test expects the starting offset of the last InlineTextBox object to
1685   // equate the sum of the previous inline text boxes' length, without the
1686   // collapsed white-spaces.
1687   //
1688   // "&#10" is a Line Feed ("\n").
1689   SetBodyInnerHTML(
1690       R"HTML(<style>p { white-space: pre-line; }</style>
1691       <p id="paragraph">Hello &#10; world</p>)HTML");
1692 
1693   const Node* text = GetElementById("paragraph")->firstChild();
1694   ASSERT_NE(nullptr, text);
1695   ASSERT_TRUE(text->IsTextNode());
1696   AXObject* ax_static_text =
1697       GetAXObjectByElementId("paragraph")->FirstChildIncludingIgnored();
1698 
1699   ASSERT_NE(nullptr, ax_static_text);
1700   ASSERT_EQ(ax::mojom::Role::kStaticText, ax_static_text->RoleValue());
1701 
1702   ax_static_text->LoadInlineTextBoxes();
1703   ASSERT_EQ(3, ax_static_text->UnignoredChildCount());
1704 
1705   // The last inline text box should be:
1706   // "InlineTextBox" name="world"
1707   const AXObject* ax_last_inline_box =
1708       ax_static_text->LastChildIncludingIgnored();
1709   const auto ax_position =
1710       AXPosition::CreatePositionBeforeObject(*ax_last_inline_box);
1711   const auto position = ax_position.ToPositionWithAffinity();
1712   // The resulting DOM position should be:
1713   // DOM position #text "Hello \n world"@offsetInAnchor[8]
1714   ASSERT_TRUE(position.GetPosition().IsOffsetInAnchor());
1715   EXPECT_EQ(8, position.GetPosition().OffsetInContainerNode());
1716 }
1717 
1718 }  // namespace test
1719 }  // namespace blink
1720