• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 // Copyright (c) 2012 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 "base/strings/string16.h"
6 #include "base/strings/utf_string_conversions.h"
7 #include "content/browser/accessibility/browser_accessibility.h"
8 #include "content/browser/accessibility/browser_accessibility_manager.h"
9 #include "content/common/accessibility_messages.h"
10 #include "content/common/accessibility_node_data.h"
11 #include "testing/gtest/include/gtest/gtest.h"
12 
13 namespace content {
14 namespace {
15 
16 // Subclass of BrowserAccessibility that counts the number of instances.
17 class CountedBrowserAccessibility : public BrowserAccessibility {
18  public:
CountedBrowserAccessibility()19   CountedBrowserAccessibility() {
20     global_obj_count_++;
21     native_ref_count_ = 1;
22   }
~CountedBrowserAccessibility()23   virtual ~CountedBrowserAccessibility() {
24     global_obj_count_--;
25   }
26 
NativeAddReference()27   virtual void NativeAddReference() OVERRIDE {
28     native_ref_count_++;
29   }
30 
NativeReleaseReference()31   virtual void NativeReleaseReference() OVERRIDE {
32     native_ref_count_--;
33     if (native_ref_count_ == 0)
34       delete this;
35   }
36 
37   int native_ref_count_;
38   static int global_obj_count_;
39 };
40 
41 int CountedBrowserAccessibility::global_obj_count_ = 0;
42 
43 // Factory that creates a CountedBrowserAccessibility.
44 class CountedBrowserAccessibilityFactory
45     : public BrowserAccessibilityFactory {
46  public:
~CountedBrowserAccessibilityFactory()47   virtual ~CountedBrowserAccessibilityFactory() {}
Create()48   virtual BrowserAccessibility* Create() OVERRIDE {
49     return new CountedBrowserAccessibility();
50   }
51 };
52 
53 class TestBrowserAccessibilityDelegate
54     : public BrowserAccessibilityDelegate {
55  public:
TestBrowserAccessibilityDelegate()56   TestBrowserAccessibilityDelegate()
57       : got_fatal_error_(false) {}
58 
SetAccessibilityFocus(int acc_obj_id)59   virtual void SetAccessibilityFocus(int acc_obj_id) OVERRIDE {}
AccessibilityDoDefaultAction(int acc_obj_id)60   virtual void AccessibilityDoDefaultAction(int acc_obj_id) OVERRIDE {}
AccessibilityScrollToMakeVisible(int acc_obj_id,gfx::Rect subfocus)61   virtual void AccessibilityScrollToMakeVisible(
62       int acc_obj_id, gfx::Rect subfocus) OVERRIDE {}
AccessibilityScrollToPoint(int acc_obj_id,gfx::Point point)63   virtual void AccessibilityScrollToPoint(
64       int acc_obj_id, gfx::Point point) OVERRIDE {}
AccessibilitySetTextSelection(int acc_obj_id,int start_offset,int end_offset)65   virtual void AccessibilitySetTextSelection(
66       int acc_obj_id, int start_offset, int end_offset) OVERRIDE {}
HasFocus() const67   virtual bool HasFocus() const OVERRIDE {
68     return false;
69   }
GetViewBounds() const70   virtual gfx::Rect GetViewBounds() const OVERRIDE {
71     return gfx::Rect();
72   }
GetLastTouchEventLocation() const73   virtual gfx::Point GetLastTouchEventLocation() const OVERRIDE {
74     return gfx::Point();
75   }
FatalAccessibilityTreeError()76   virtual void FatalAccessibilityTreeError() OVERRIDE {
77     got_fatal_error_ = true;
78   }
79 
got_fatal_error() const80   bool got_fatal_error() const { return got_fatal_error_; }
reset_got_fatal_error()81   void reset_got_fatal_error() { got_fatal_error_ = false; }
82 
83 private:
84   bool got_fatal_error_;
85 };
86 
87 }  // anonymous namespace
88 
TEST(BrowserAccessibilityManagerTest,TestNoLeaks)89 TEST(BrowserAccessibilityManagerTest, TestNoLeaks) {
90   // Create AccessibilityNodeData objects for a simple document tree,
91   // representing the accessibility information used to initialize
92   // BrowserAccessibilityManager.
93   AccessibilityNodeData button;
94   button.id = 2;
95   button.SetName("Button");
96   button.role = blink::WebAXRoleButton;
97   button.state = 0;
98 
99   AccessibilityNodeData checkbox;
100   checkbox.id = 3;
101   checkbox.SetName("Checkbox");
102   checkbox.role = blink::WebAXRoleCheckBox;
103   checkbox.state = 0;
104 
105   AccessibilityNodeData root;
106   root.id = 1;
107   root.SetName("Document");
108   root.role = blink::WebAXRoleRootWebArea;
109   root.state = 0;
110   root.child_ids.push_back(2);
111   root.child_ids.push_back(3);
112 
113   // Construct a BrowserAccessibilityManager with this
114   // AccessibilityNodeData tree and a factory for an instance-counting
115   // BrowserAccessibility, and ensure that exactly 3 instances were
116   // created. Note that the manager takes ownership of the factory.
117   CountedBrowserAccessibility::global_obj_count_ = 0;
118   BrowserAccessibilityManager* manager =
119       BrowserAccessibilityManager::Create(
120           root,
121           NULL,
122           new CountedBrowserAccessibilityFactory());
123   manager->UpdateNodesForTesting(button, checkbox);
124 
125   ASSERT_EQ(3, CountedBrowserAccessibility::global_obj_count_);
126 
127   // Delete the manager and test that all 3 instances are deleted.
128   delete manager;
129   ASSERT_EQ(0, CountedBrowserAccessibility::global_obj_count_);
130 
131   // Construct a manager again, and this time save references to two of
132   // the three nodes in the tree.
133   manager =
134       BrowserAccessibilityManager::Create(
135           root,
136           NULL,
137           new CountedBrowserAccessibilityFactory());
138   manager->UpdateNodesForTesting(button, checkbox);
139   ASSERT_EQ(3, CountedBrowserAccessibility::global_obj_count_);
140 
141   CountedBrowserAccessibility* root_accessible =
142       static_cast<CountedBrowserAccessibility*>(manager->GetRoot());
143   root_accessible->NativeAddReference();
144   CountedBrowserAccessibility* child1_accessible =
145       static_cast<CountedBrowserAccessibility*>(
146           root_accessible->PlatformGetChild(1));
147   child1_accessible->NativeAddReference();
148 
149   // Now delete the manager, and only one of the three nodes in the tree
150   // should be released.
151   delete manager;
152   ASSERT_EQ(2, CountedBrowserAccessibility::global_obj_count_);
153 
154   // Release each of our references and make sure that each one results in
155   // the instance being deleted as its reference count hits zero.
156   root_accessible->NativeReleaseReference();
157   ASSERT_EQ(1, CountedBrowserAccessibility::global_obj_count_);
158   child1_accessible->NativeReleaseReference();
159   ASSERT_EQ(0, CountedBrowserAccessibility::global_obj_count_);
160 }
161 
TEST(BrowserAccessibilityManagerTest,TestReuseBrowserAccessibilityObjects)162 TEST(BrowserAccessibilityManagerTest, TestReuseBrowserAccessibilityObjects) {
163   // Make sure that changes to a subtree reuse as many objects as possible.
164 
165   // Tree 1:
166   //
167   // root
168   //   child1
169   //   child2
170   //   child3
171 
172   AccessibilityNodeData tree1_child1;
173   tree1_child1.id = 2;
174   tree1_child1.SetName("Child1");
175   tree1_child1.role = blink::WebAXRoleButton;
176   tree1_child1.state = 0;
177 
178   AccessibilityNodeData tree1_child2;
179   tree1_child2.id = 3;
180   tree1_child2.SetName("Child2");
181   tree1_child2.role = blink::WebAXRoleButton;
182   tree1_child2.state = 0;
183 
184   AccessibilityNodeData tree1_child3;
185   tree1_child3.id = 4;
186   tree1_child3.SetName("Child3");
187   tree1_child3.role = blink::WebAXRoleButton;
188   tree1_child3.state = 0;
189 
190   AccessibilityNodeData tree1_root;
191   tree1_root.id = 1;
192   tree1_root.SetName("Document");
193   tree1_root.role = blink::WebAXRoleRootWebArea;
194   tree1_root.state = 0;
195   tree1_root.child_ids.push_back(2);
196   tree1_root.child_ids.push_back(3);
197   tree1_root.child_ids.push_back(4);
198 
199   // Tree 2:
200   //
201   // root
202   //   child0  <-- inserted
203   //   child1
204   //   child2
205   //           <-- child3 deleted
206 
207   AccessibilityNodeData tree2_child0;
208   tree2_child0.id = 5;
209   tree2_child0.SetName("Child0");
210   tree2_child0.role = blink::WebAXRoleButton;
211   tree2_child0.state = 0;
212 
213   AccessibilityNodeData tree2_root;
214   tree2_root.id = 1;
215   tree2_root.SetName("DocumentChanged");
216   tree2_root.role = blink::WebAXRoleRootWebArea;
217   tree2_root.state = 0;
218   tree2_root.child_ids.push_back(5);
219   tree2_root.child_ids.push_back(2);
220   tree2_root.child_ids.push_back(3);
221 
222   // Construct a BrowserAccessibilityManager with tree1.
223   CountedBrowserAccessibility::global_obj_count_ = 0;
224   BrowserAccessibilityManager* manager =
225       BrowserAccessibilityManager::Create(
226           tree1_root,
227           NULL,
228           new CountedBrowserAccessibilityFactory());
229   manager->UpdateNodesForTesting(tree1_child1, tree1_child2, tree1_child3);
230   ASSERT_EQ(4, CountedBrowserAccessibility::global_obj_count_);
231 
232   // Save references to all of the objects.
233   CountedBrowserAccessibility* root_accessible =
234       static_cast<CountedBrowserAccessibility*>(manager->GetRoot());
235   root_accessible->NativeAddReference();
236   CountedBrowserAccessibility* child1_accessible =
237       static_cast<CountedBrowserAccessibility*>(
238           root_accessible->PlatformGetChild(0));
239   child1_accessible->NativeAddReference();
240   CountedBrowserAccessibility* child2_accessible =
241       static_cast<CountedBrowserAccessibility*>(
242           root_accessible->PlatformGetChild(1));
243   child2_accessible->NativeAddReference();
244   CountedBrowserAccessibility* child3_accessible =
245       static_cast<CountedBrowserAccessibility*>(
246           root_accessible->PlatformGetChild(2));
247   child3_accessible->NativeAddReference();
248 
249   // Check the index in parent.
250   EXPECT_EQ(0, child1_accessible->index_in_parent());
251   EXPECT_EQ(1, child2_accessible->index_in_parent());
252   EXPECT_EQ(2, child3_accessible->index_in_parent());
253 
254   // Process a notification containing the changed subtree.
255   std::vector<AccessibilityHostMsg_EventParams> params;
256   params.push_back(AccessibilityHostMsg_EventParams());
257   AccessibilityHostMsg_EventParams* msg = &params[0];
258   msg->event_type = blink::WebAXEventChildrenChanged;
259   msg->nodes.push_back(tree2_root);
260   msg->nodes.push_back(tree2_child0);
261   msg->id = tree2_root.id;
262   manager->OnAccessibilityEvents(params);
263 
264   // There should be 5 objects now: the 4 from the new tree, plus the
265   // reference to child3 we kept.
266   EXPECT_EQ(5, CountedBrowserAccessibility::global_obj_count_);
267 
268   // Check that our references to the root, child1, and child2 are still valid,
269   // but that the reference to child3 is now invalid.
270   EXPECT_TRUE(root_accessible->instance_active());
271   EXPECT_TRUE(child1_accessible->instance_active());
272   EXPECT_TRUE(child2_accessible->instance_active());
273   EXPECT_FALSE(child3_accessible->instance_active());
274 
275   // Check that the index in parent has been updated.
276   EXPECT_EQ(1, child1_accessible->index_in_parent());
277   EXPECT_EQ(2, child2_accessible->index_in_parent());
278 
279   // Release our references. The object count should only decrease by 1
280   // for child3.
281   root_accessible->NativeReleaseReference();
282   child1_accessible->NativeReleaseReference();
283   child2_accessible->NativeReleaseReference();
284   child3_accessible->NativeReleaseReference();
285 
286   EXPECT_EQ(4, CountedBrowserAccessibility::global_obj_count_);
287 
288   // Delete the manager and make sure all memory is cleaned up.
289   delete manager;
290   ASSERT_EQ(0, CountedBrowserAccessibility::global_obj_count_);
291 }
292 
TEST(BrowserAccessibilityManagerTest,TestReuseBrowserAccessibilityObjects2)293 TEST(BrowserAccessibilityManagerTest, TestReuseBrowserAccessibilityObjects2) {
294   // Similar to the test above, but with a more complicated tree.
295 
296   // Tree 1:
297   //
298   // root
299   //   container
300   //     child1
301   //       grandchild1
302   //     child2
303   //       grandchild2
304   //     child3
305   //       grandchild3
306 
307   AccessibilityNodeData tree1_grandchild1;
308   tree1_grandchild1.id = 4;
309   tree1_grandchild1.SetName("GrandChild1");
310   tree1_grandchild1.role = blink::WebAXRoleButton;
311   tree1_grandchild1.state = 0;
312 
313   AccessibilityNodeData tree1_child1;
314   tree1_child1.id = 3;
315   tree1_child1.SetName("Child1");
316   tree1_child1.role = blink::WebAXRoleButton;
317   tree1_child1.state = 0;
318   tree1_child1.child_ids.push_back(4);
319 
320   AccessibilityNodeData tree1_grandchild2;
321   tree1_grandchild2.id = 6;
322   tree1_grandchild2.SetName("GrandChild1");
323   tree1_grandchild2.role = blink::WebAXRoleButton;
324   tree1_grandchild2.state = 0;
325 
326   AccessibilityNodeData tree1_child2;
327   tree1_child2.id = 5;
328   tree1_child2.SetName("Child2");
329   tree1_child2.role = blink::WebAXRoleButton;
330   tree1_child2.state = 0;
331   tree1_child2.child_ids.push_back(6);
332 
333   AccessibilityNodeData tree1_grandchild3;
334   tree1_grandchild3.id = 8;
335   tree1_grandchild3.SetName("GrandChild3");
336   tree1_grandchild3.role = blink::WebAXRoleButton;
337   tree1_grandchild3.state = 0;
338 
339   AccessibilityNodeData tree1_child3;
340   tree1_child3.id = 7;
341   tree1_child3.SetName("Child3");
342   tree1_child3.role = blink::WebAXRoleButton;
343   tree1_child3.state = 0;
344   tree1_child3.child_ids.push_back(8);
345 
346   AccessibilityNodeData tree1_container;
347   tree1_container.id = 2;
348   tree1_container.SetName("Container");
349   tree1_container.role = blink::WebAXRoleGroup;
350   tree1_container.state = 0;
351   tree1_container.child_ids.push_back(3);
352   tree1_container.child_ids.push_back(5);
353   tree1_container.child_ids.push_back(7);
354 
355   AccessibilityNodeData tree1_root;
356   tree1_root.id = 1;
357   tree1_root.SetName("Document");
358   tree1_root.role = blink::WebAXRoleRootWebArea;
359   tree1_root.state = 0;
360   tree1_root.child_ids.push_back(2);
361 
362   // Tree 2:
363   //
364   // root
365   //   container
366   //     child0         <-- inserted
367   //       grandchild0  <--
368   //     child1
369   //       grandchild1
370   //     child2
371   //       grandchild2
372   //                    <-- child3 (and grandchild3) deleted
373 
374   AccessibilityNodeData tree2_grandchild0;
375   tree2_grandchild0.id = 9;
376   tree2_grandchild0.SetName("GrandChild0");
377   tree2_grandchild0.role = blink::WebAXRoleButton;
378   tree2_grandchild0.state = 0;
379 
380   AccessibilityNodeData tree2_child0;
381   tree2_child0.id = 10;
382   tree2_child0.SetName("Child0");
383   tree2_child0.role = blink::WebAXRoleButton;
384   tree2_child0.state = 0;
385   tree2_child0.child_ids.push_back(9);
386 
387   AccessibilityNodeData tree2_container;
388   tree2_container.id = 2;
389   tree2_container.SetName("Container");
390   tree2_container.role = blink::WebAXRoleGroup;
391   tree2_container.state = 0;
392   tree2_container.child_ids.push_back(10);
393   tree2_container.child_ids.push_back(3);
394   tree2_container.child_ids.push_back(5);
395 
396   // Construct a BrowserAccessibilityManager with tree1.
397   CountedBrowserAccessibility::global_obj_count_ = 0;
398   BrowserAccessibilityManager* manager =
399       BrowserAccessibilityManager::Create(
400           tree1_root,
401           NULL,
402           new CountedBrowserAccessibilityFactory());
403   manager->UpdateNodesForTesting(tree1_container,
404                                  tree1_child1, tree1_grandchild1,
405                                  tree1_child2, tree1_grandchild2,
406                                  tree1_child3, tree1_grandchild3);
407   ASSERT_EQ(8, CountedBrowserAccessibility::global_obj_count_);
408 
409   // Save references to some objects.
410   CountedBrowserAccessibility* root_accessible =
411       static_cast<CountedBrowserAccessibility*>(manager->GetRoot());
412   root_accessible->NativeAddReference();
413   CountedBrowserAccessibility* container_accessible =
414       static_cast<CountedBrowserAccessibility*>(
415           root_accessible->PlatformGetChild(0));
416   container_accessible->NativeAddReference();
417   CountedBrowserAccessibility* child2_accessible =
418       static_cast<CountedBrowserAccessibility*>(
419           container_accessible->PlatformGetChild(1));
420   child2_accessible->NativeAddReference();
421   CountedBrowserAccessibility* child3_accessible =
422       static_cast<CountedBrowserAccessibility*>(
423           container_accessible->PlatformGetChild(2));
424   child3_accessible->NativeAddReference();
425 
426   // Check the index in parent.
427   EXPECT_EQ(1, child2_accessible->index_in_parent());
428   EXPECT_EQ(2, child3_accessible->index_in_parent());
429 
430   // Process a notification containing the changed subtree rooted at
431   // the container.
432   std::vector<AccessibilityHostMsg_EventParams> params;
433   params.push_back(AccessibilityHostMsg_EventParams());
434   AccessibilityHostMsg_EventParams* msg = &params[0];
435   msg->event_type = blink::WebAXEventChildrenChanged;
436   msg->nodes.push_back(tree2_container);
437   msg->nodes.push_back(tree2_child0);
438   msg->nodes.push_back(tree2_grandchild0);
439   msg->id = tree2_container.id;
440   manager->OnAccessibilityEvents(params);
441 
442   // There should be 9 objects now: the 8 from the new tree, plus the
443   // reference to child3 we kept.
444   EXPECT_EQ(9, CountedBrowserAccessibility::global_obj_count_);
445 
446   // Check that our references to the root and container and child2 are
447   // still valid, but that the reference to child3 is now invalid.
448   EXPECT_TRUE(root_accessible->instance_active());
449   EXPECT_TRUE(container_accessible->instance_active());
450   EXPECT_TRUE(child2_accessible->instance_active());
451   EXPECT_FALSE(child3_accessible->instance_active());
452 
453   // Ensure that we retain the parent of the detached subtree.
454   EXPECT_EQ(root_accessible, container_accessible->parent());
455   EXPECT_EQ(0, container_accessible->index_in_parent());
456 
457   // Check that the index in parent has been updated.
458   EXPECT_EQ(2, child2_accessible->index_in_parent());
459 
460   // Release our references. The object count should only decrease by 1
461   // for child3.
462   root_accessible->NativeReleaseReference();
463   container_accessible->NativeReleaseReference();
464   child2_accessible->NativeReleaseReference();
465   child3_accessible->NativeReleaseReference();
466 
467   EXPECT_EQ(8, CountedBrowserAccessibility::global_obj_count_);
468 
469   // Delete the manager and make sure all memory is cleaned up.
470   delete manager;
471   ASSERT_EQ(0, CountedBrowserAccessibility::global_obj_count_);
472 }
473 
TEST(BrowserAccessibilityManagerTest,TestMoveChildUp)474 TEST(BrowserAccessibilityManagerTest, TestMoveChildUp) {
475   // Tree 1:
476   //
477   // 1
478   //   2
479   //   3
480   //     4
481 
482   AccessibilityNodeData tree1_4;
483   tree1_4.id = 4;
484   tree1_4.state = 0;
485 
486   AccessibilityNodeData tree1_3;
487   tree1_3.id = 3;
488   tree1_3.state = 0;
489   tree1_3.child_ids.push_back(4);
490 
491   AccessibilityNodeData tree1_2;
492   tree1_2.id = 2;
493   tree1_2.state = 0;
494 
495   AccessibilityNodeData tree1_1;
496   tree1_1.id = 1;
497   tree1_1.role = blink::WebAXRoleRootWebArea;
498   tree1_1.state = 0;
499   tree1_1.child_ids.push_back(2);
500   tree1_1.child_ids.push_back(3);
501 
502   // Tree 2:
503   //
504   // 1
505   //   4    <-- moves up a level and gains child
506   //     6  <-- new
507   //   5    <-- new
508 
509   AccessibilityNodeData tree2_6;
510   tree2_6.id = 6;
511   tree2_6.state = 0;
512 
513   AccessibilityNodeData tree2_5;
514   tree2_5.id = 5;
515   tree2_5.state = 0;
516 
517   AccessibilityNodeData tree2_4;
518   tree2_4.id = 4;
519   tree2_4.state = 0;
520   tree2_4.child_ids.push_back(6);
521 
522   AccessibilityNodeData tree2_1;
523   tree2_1.id = 1;
524   tree2_1.state = 0;
525   tree2_1.child_ids.push_back(4);
526   tree2_1.child_ids.push_back(5);
527 
528   // Construct a BrowserAccessibilityManager with tree1.
529   CountedBrowserAccessibility::global_obj_count_ = 0;
530   BrowserAccessibilityManager* manager =
531       BrowserAccessibilityManager::Create(
532           tree1_1,
533           NULL,
534           new CountedBrowserAccessibilityFactory());
535   manager->UpdateNodesForTesting(tree1_2, tree1_3, tree1_4);
536   ASSERT_EQ(4, CountedBrowserAccessibility::global_obj_count_);
537 
538   // Process a notification containing the changed subtree.
539   std::vector<AccessibilityHostMsg_EventParams> params;
540   params.push_back(AccessibilityHostMsg_EventParams());
541   AccessibilityHostMsg_EventParams* msg = &params[0];
542   msg->event_type = blink::WebAXEventChildrenChanged;
543   msg->nodes.push_back(tree2_1);
544   msg->nodes.push_back(tree2_4);
545   msg->nodes.push_back(tree2_5);
546   msg->nodes.push_back(tree2_6);
547   msg->id = tree2_1.id;
548   manager->OnAccessibilityEvents(params);
549 
550   // There should be 4 objects now.
551   EXPECT_EQ(4, CountedBrowserAccessibility::global_obj_count_);
552 
553   // Delete the manager and make sure all memory is cleaned up.
554   delete manager;
555   ASSERT_EQ(0, CountedBrowserAccessibility::global_obj_count_);
556 }
557 
558 // Crashes on Windows. http://crbug.com/304130
559 #if defined(OS_WIN)
560 #define MAYBE_TestFatalError DISABLED_TestFatalError
561 #else
562 #define MAYBE_TestFatalError TestFatalError
563 #endif
TEST(BrowserAccessibilityManagerTest,TestFatalError)564 TEST(BrowserAccessibilityManagerTest, TestFatalError) {
565   // Test that BrowserAccessibilityManager raises a fatal error
566   // (which will crash the renderer) if the same id is used in
567   // two places in the tree.
568 
569   AccessibilityNodeData root;
570   root.id = 1;
571   root.role = blink::WebAXRoleRootWebArea;
572   root.child_ids.push_back(2);
573   root.child_ids.push_back(2);
574 
575   CountedBrowserAccessibilityFactory* factory =
576       new CountedBrowserAccessibilityFactory();
577   scoped_ptr<TestBrowserAccessibilityDelegate> delegate(
578       new TestBrowserAccessibilityDelegate());
579   scoped_ptr<BrowserAccessibilityManager> manager;
580   ASSERT_FALSE(delegate->got_fatal_error());
581   manager.reset(BrowserAccessibilityManager::Create(
582       root,
583       delegate.get(),
584       factory));
585   ASSERT_TRUE(delegate->got_fatal_error());
586 
587   AccessibilityNodeData root2;
588   root2.id = 1;
589   root2.role = blink::WebAXRoleRootWebArea;
590   root2.child_ids.push_back(2);
591   root2.child_ids.push_back(3);
592 
593   AccessibilityNodeData child1;
594   child1.id = 2;
595   child1.child_ids.push_back(4);
596   child1.child_ids.push_back(5);
597 
598   AccessibilityNodeData child2;
599   child2.id = 3;
600   child2.child_ids.push_back(6);
601   child2.child_ids.push_back(5);  // Duplicate
602 
603   delegate->reset_got_fatal_error();
604   factory = new CountedBrowserAccessibilityFactory();
605   manager.reset(BrowserAccessibilityManager::Create(
606       root2,
607       delegate.get(),
608       factory));
609   ASSERT_FALSE(delegate->got_fatal_error());
610   manager->UpdateNodesForTesting(child1, child2);
611   ASSERT_TRUE(delegate->got_fatal_error());
612 }
613 
TEST(BrowserAccessibilityManagerTest,BoundsForRange)614 TEST(BrowserAccessibilityManagerTest, BoundsForRange) {
615   AccessibilityNodeData root;
616   root.id = 1;
617   root.role = blink::WebAXRoleRootWebArea;
618 
619   AccessibilityNodeData static_text;
620   static_text.id = 2;
621   static_text.SetValue("Hello, world.");
622   static_text.role = blink::WebAXRoleStaticText;
623   static_text.location = gfx::Rect(100, 100, 29, 18);
624   root.child_ids.push_back(2);
625 
626   AccessibilityNodeData inline_text1;
627   inline_text1.id = 3;
628   inline_text1.SetValue("Hello, ");
629   inline_text1.role = blink::WebAXRoleInlineTextBox;
630   inline_text1.location = gfx::Rect(100, 100, 29, 9);
631   inline_text1.AddIntAttribute(AccessibilityNodeData::ATTR_TEXT_DIRECTION,
632                                blink::WebAXTextDirectionLR);
633   std::vector<int32> character_offsets1;
634   character_offsets1.push_back(6);   // 0
635   character_offsets1.push_back(11);  // 1
636   character_offsets1.push_back(16);  // 2
637   character_offsets1.push_back(21);  // 3
638   character_offsets1.push_back(26);  // 4
639   character_offsets1.push_back(29);  // 5
640   character_offsets1.push_back(29);  // 6 (note that the space has no width)
641   inline_text1.AddIntListAttribute(
642       AccessibilityNodeData::ATTR_CHARACTER_OFFSETS, character_offsets1);
643   static_text.child_ids.push_back(3);
644 
645   AccessibilityNodeData inline_text2;
646   inline_text2.id = 4;
647   inline_text2.SetValue("world.");
648   inline_text2.role = blink::WebAXRoleInlineTextBox;
649   inline_text2.location = gfx::Rect(100, 109, 28, 9);
650   inline_text2.AddIntAttribute(AccessibilityNodeData::ATTR_TEXT_DIRECTION,
651                                blink::WebAXTextDirectionLR);
652   std::vector<int32> character_offsets2;
653   character_offsets2.push_back(5);
654   character_offsets2.push_back(10);
655   character_offsets2.push_back(15);
656   character_offsets2.push_back(20);
657   character_offsets2.push_back(25);
658   character_offsets2.push_back(28);
659   inline_text2.AddIntListAttribute(
660       AccessibilityNodeData::ATTR_CHARACTER_OFFSETS, character_offsets2);
661   static_text.child_ids.push_back(4);
662 
663   scoped_ptr<BrowserAccessibilityManager> manager(
664       BrowserAccessibilityManager::Create(
665           root,
666           NULL,
667           new CountedBrowserAccessibilityFactory()));
668   manager->UpdateNodesForTesting(static_text, inline_text1, inline_text2);
669 
670   BrowserAccessibility* root_accessible = manager->GetRoot();
671   BrowserAccessibility* static_text_accessible =
672       root_accessible->PlatformGetChild(0);
673 
674   EXPECT_EQ(gfx::Rect(100, 100, 6, 9).ToString(),
675             static_text_accessible->GetLocalBoundsForRange(0, 1).ToString());
676 
677   EXPECT_EQ(gfx::Rect(100, 100, 26, 9).ToString(),
678             static_text_accessible->GetLocalBoundsForRange(0, 5).ToString());
679 
680   EXPECT_EQ(gfx::Rect(100, 109, 5, 9).ToString(),
681             static_text_accessible->GetLocalBoundsForRange(7, 1).ToString());
682 
683   EXPECT_EQ(gfx::Rect(100, 109, 25, 9).ToString(),
684             static_text_accessible->GetLocalBoundsForRange(7, 5).ToString());
685 
686   EXPECT_EQ(gfx::Rect(100, 100, 29, 18).ToString(),
687             static_text_accessible->GetLocalBoundsForRange(5, 3).ToString());
688 
689   EXPECT_EQ(gfx::Rect(100, 100, 29, 18).ToString(),
690             static_text_accessible->GetLocalBoundsForRange(0, 13).ToString());
691 
692   // Test range that's beyond the text.
693   EXPECT_EQ(gfx::Rect(100, 100, 29, 18).ToString(),
694             static_text_accessible->GetLocalBoundsForRange(-1, 999).ToString());
695 }
696 
TEST(BrowserAccessibilityManagerTest,BoundsForRangeBiDi)697 TEST(BrowserAccessibilityManagerTest, BoundsForRangeBiDi) {
698   // In this example, we assume that the string "123abc" is rendered with
699   // "123" going left-to-right and "abc" going right-to-left. In other
700   // words, on-screen it would look like "123cba". This is possible to
701   // acheive if the source string had unicode control characters
702   // to switch directions. This test doesn't worry about how, though - it just
703   // tests that if something like that were to occur, GetLocalBoundsForRange
704   // returns the correct bounds for different ranges.
705 
706   AccessibilityNodeData root;
707   root.id = 1;
708   root.role = blink::WebAXRoleRootWebArea;
709 
710   AccessibilityNodeData static_text;
711   static_text.id = 2;
712   static_text.SetValue("123abc");
713   static_text.role = blink::WebAXRoleStaticText;
714   static_text.location = gfx::Rect(100, 100, 60, 20);
715   root.child_ids.push_back(2);
716 
717   AccessibilityNodeData inline_text1;
718   inline_text1.id = 3;
719   inline_text1.SetValue("123");
720   inline_text1.role = blink::WebAXRoleInlineTextBox;
721   inline_text1.location = gfx::Rect(100, 100, 30, 20);
722   inline_text1.AddIntAttribute(AccessibilityNodeData::ATTR_TEXT_DIRECTION,
723                                blink::WebAXTextDirectionLR);
724   std::vector<int32> character_offsets1;
725   character_offsets1.push_back(10);  // 0
726   character_offsets1.push_back(20);  // 1
727   character_offsets1.push_back(30);  // 2
728   inline_text1.AddIntListAttribute(
729       AccessibilityNodeData::ATTR_CHARACTER_OFFSETS, character_offsets1);
730   static_text.child_ids.push_back(3);
731 
732   AccessibilityNodeData inline_text2;
733   inline_text2.id = 4;
734   inline_text2.SetValue("abc");
735   inline_text2.role = blink::WebAXRoleInlineTextBox;
736   inline_text2.location = gfx::Rect(130, 100, 30, 20);
737   inline_text2.AddIntAttribute(AccessibilityNodeData::ATTR_TEXT_DIRECTION,
738                                blink::WebAXTextDirectionRL);
739   std::vector<int32> character_offsets2;
740   character_offsets2.push_back(10);
741   character_offsets2.push_back(20);
742   character_offsets2.push_back(30);
743   inline_text2.AddIntListAttribute(
744       AccessibilityNodeData::ATTR_CHARACTER_OFFSETS, character_offsets2);
745   static_text.child_ids.push_back(4);
746 
747   scoped_ptr<BrowserAccessibilityManager> manager(
748       BrowserAccessibilityManager::Create(
749           root,
750           NULL,
751           new CountedBrowserAccessibilityFactory()));
752   manager->UpdateNodesForTesting(static_text, inline_text1, inline_text2);
753 
754   BrowserAccessibility* root_accessible = manager->GetRoot();
755   BrowserAccessibility* static_text_accessible =
756       root_accessible->PlatformGetChild(0);
757 
758   EXPECT_EQ(gfx::Rect(100, 100, 60, 20).ToString(),
759             static_text_accessible->GetLocalBoundsForRange(0, 6).ToString());
760 
761   EXPECT_EQ(gfx::Rect(100, 100, 10, 20).ToString(),
762             static_text_accessible->GetLocalBoundsForRange(0, 1).ToString());
763 
764   EXPECT_EQ(gfx::Rect(100, 100, 30, 20).ToString(),
765             static_text_accessible->GetLocalBoundsForRange(0, 3).ToString());
766 
767   EXPECT_EQ(gfx::Rect(150, 100, 10, 20).ToString(),
768             static_text_accessible->GetLocalBoundsForRange(3, 1).ToString());
769 
770   EXPECT_EQ(gfx::Rect(130, 100, 30, 20).ToString(),
771             static_text_accessible->GetLocalBoundsForRange(3, 3).ToString());
772 
773   // This range is only two characters, but because of the direction switch
774   // the bounds are as wide as four characters.
775   EXPECT_EQ(gfx::Rect(120, 100, 40, 20).ToString(),
776             static_text_accessible->GetLocalBoundsForRange(2, 2).ToString());
777 }
778 
779 }  // namespace content
780