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#if defined(OS_WIN)
10#include "content/browser/accessibility/browser_accessibility_win.h"
11#endif
12#include "content/common/accessibility_messages.h"
13#include "testing/gtest/include/gtest/gtest.h"
14
15namespace content {
16namespace {
17
18// Subclass of BrowserAccessibility that counts the number of instances.
19class CountedBrowserAccessibility : public BrowserAccessibility {
20 public:
21  CountedBrowserAccessibility() {
22    global_obj_count_++;
23    native_ref_count_ = 1;
24  }
25  virtual ~CountedBrowserAccessibility() {
26    global_obj_count_--;
27  }
28
29  virtual void NativeAddReference() OVERRIDE {
30    native_ref_count_++;
31  }
32
33  virtual void NativeReleaseReference() OVERRIDE {
34    native_ref_count_--;
35    if (native_ref_count_ == 0)
36      delete this;
37  }
38
39  int native_ref_count_;
40  static int global_obj_count_;
41
42#if defined(OS_WIN)
43  // Adds some padding to prevent a heap-buffer-overflow when an instance of
44  // this class is casted into a BrowserAccessibilityWin pointer.
45  // http://crbug.com/235508
46  // TODO(dmazzoni): Fix this properly.
47  static const size_t kDataSize = sizeof(int) + sizeof(BrowserAccessibility);
48  uint8 padding_[sizeof(BrowserAccessibilityWin) - kDataSize];
49#endif
50};
51
52int CountedBrowserAccessibility::global_obj_count_ = 0;
53
54// Factory that creates a CountedBrowserAccessibility.
55class CountedBrowserAccessibilityFactory
56    : public BrowserAccessibilityFactory {
57 public:
58  virtual ~CountedBrowserAccessibilityFactory() {}
59  virtual BrowserAccessibility* Create() OVERRIDE {
60    return new CountedBrowserAccessibility();
61  }
62};
63
64class TestBrowserAccessibilityDelegate
65    : public BrowserAccessibilityDelegate {
66 public:
67  TestBrowserAccessibilityDelegate()
68      : got_fatal_error_(false) {}
69
70  virtual void AccessibilitySetFocus(int acc_obj_id) OVERRIDE {}
71  virtual void AccessibilityDoDefaultAction(int acc_obj_id) OVERRIDE {}
72  virtual void AccessibilityShowMenu(const gfx::Point& point) OVERRIDE {}
73  virtual void AccessibilityScrollToMakeVisible(
74      int acc_obj_id, const gfx::Rect& subfocus) OVERRIDE {}
75  virtual void AccessibilityScrollToPoint(
76      int acc_obj_id, const gfx::Point& point) OVERRIDE {}
77  virtual void AccessibilitySetTextSelection(
78      int acc_obj_id, int start_offset, int end_offset) OVERRIDE {}
79  virtual bool AccessibilityViewHasFocus() const OVERRIDE {
80    return false;
81  }
82  virtual gfx::Rect AccessibilityGetViewBounds() const OVERRIDE {
83    return gfx::Rect();
84  }
85  virtual gfx::Point AccessibilityOriginInScreen(
86      const gfx::Rect& bounds) const OVERRIDE {
87    return gfx::Point();
88  }
89  virtual void AccessibilityHitTest(const gfx::Point& point) OVERRIDE {}
90  virtual void AccessibilityFatalError() OVERRIDE {
91    got_fatal_error_ = true;
92  }
93  virtual gfx::AcceleratedWidget AccessibilityGetAcceleratedWidget() OVERRIDE {
94    return gfx::kNullAcceleratedWidget;
95  }
96  virtual gfx::NativeViewAccessible AccessibilityGetNativeViewAccessible()
97      OVERRIDE {
98    return NULL;
99  }
100  virtual BrowserAccessibilityManager* AccessibilityGetChildFrame(
101      int accessibility_node_id) OVERRIDE {
102    return NULL;
103  }
104  virtual BrowserAccessibility* AccessibilityGetParentFrame() OVERRIDE {
105    return NULL;
106  }
107
108  bool got_fatal_error() const { return got_fatal_error_; }
109  void reset_got_fatal_error() { got_fatal_error_ = false; }
110
111private:
112  bool got_fatal_error_;
113};
114
115}  // anonymous namespace
116
117TEST(BrowserAccessibilityManagerTest, TestNoLeaks) {
118  // Create ui::AXNodeData objects for a simple document tree,
119  // representing the accessibility information used to initialize
120  // BrowserAccessibilityManager.
121  ui::AXNodeData button;
122  button.id = 2;
123  button.SetName("Button");
124  button.role = ui::AX_ROLE_BUTTON;
125  button.state = 0;
126
127  ui::AXNodeData checkbox;
128  checkbox.id = 3;
129  checkbox.SetName("Checkbox");
130  checkbox.role = ui::AX_ROLE_CHECK_BOX;
131  checkbox.state = 0;
132
133  ui::AXNodeData root;
134  root.id = 1;
135  root.SetName("Document");
136  root.role = ui::AX_ROLE_ROOT_WEB_AREA;
137  root.state = 0;
138  root.child_ids.push_back(2);
139  root.child_ids.push_back(3);
140
141  // Construct a BrowserAccessibilityManager with this
142  // ui::AXNodeData tree and a factory for an instance-counting
143  // BrowserAccessibility, and ensure that exactly 3 instances were
144  // created. Note that the manager takes ownership of the factory.
145  CountedBrowserAccessibility::global_obj_count_ = 0;
146  BrowserAccessibilityManager* manager =
147      BrowserAccessibilityManager::Create(
148          MakeAXTreeUpdate(root, button, checkbox),
149          NULL,
150          new CountedBrowserAccessibilityFactory());
151
152  ASSERT_EQ(3, CountedBrowserAccessibility::global_obj_count_);
153
154  // Delete the manager and test that all 3 instances are deleted.
155  delete manager;
156  ASSERT_EQ(0, CountedBrowserAccessibility::global_obj_count_);
157
158  // Construct a manager again, and this time save references to two of
159  // the three nodes in the tree.
160  manager =
161      BrowserAccessibilityManager::Create(
162          MakeAXTreeUpdate(root, button, checkbox),
163          NULL,
164          new CountedBrowserAccessibilityFactory());
165  ASSERT_EQ(3, CountedBrowserAccessibility::global_obj_count_);
166
167  CountedBrowserAccessibility* root_accessible =
168      static_cast<CountedBrowserAccessibility*>(manager->GetRoot());
169  root_accessible->NativeAddReference();
170  CountedBrowserAccessibility* child1_accessible =
171      static_cast<CountedBrowserAccessibility*>(
172          root_accessible->PlatformGetChild(1));
173  child1_accessible->NativeAddReference();
174
175  // Now delete the manager, and only one of the three nodes in the tree
176  // should be released.
177  delete manager;
178  ASSERT_EQ(2, CountedBrowserAccessibility::global_obj_count_);
179
180  // Release each of our references and make sure that each one results in
181  // the instance being deleted as its reference count hits zero.
182  root_accessible->NativeReleaseReference();
183  ASSERT_EQ(1, CountedBrowserAccessibility::global_obj_count_);
184  child1_accessible->NativeReleaseReference();
185  ASSERT_EQ(0, CountedBrowserAccessibility::global_obj_count_);
186}
187
188TEST(BrowserAccessibilityManagerTest, TestReuseBrowserAccessibilityObjects) {
189  // Make sure that changes to a subtree reuse as many objects as possible.
190
191  // Tree 1:
192  //
193  // root
194  //   child1
195  //   child2
196  //   child3
197
198  ui::AXNodeData tree1_child1;
199  tree1_child1.id = 2;
200  tree1_child1.SetName("Child1");
201  tree1_child1.role = ui::AX_ROLE_BUTTON;
202  tree1_child1.state = 0;
203
204  ui::AXNodeData tree1_child2;
205  tree1_child2.id = 3;
206  tree1_child2.SetName("Child2");
207  tree1_child2.role = ui::AX_ROLE_BUTTON;
208  tree1_child2.state = 0;
209
210  ui::AXNodeData tree1_child3;
211  tree1_child3.id = 4;
212  tree1_child3.SetName("Child3");
213  tree1_child3.role = ui::AX_ROLE_BUTTON;
214  tree1_child3.state = 0;
215
216  ui::AXNodeData tree1_root;
217  tree1_root.id = 1;
218  tree1_root.SetName("Document");
219  tree1_root.role = ui::AX_ROLE_ROOT_WEB_AREA;
220  tree1_root.state = 0;
221  tree1_root.child_ids.push_back(2);
222  tree1_root.child_ids.push_back(3);
223  tree1_root.child_ids.push_back(4);
224
225  // Tree 2:
226  //
227  // root
228  //   child0  <-- inserted
229  //   child1
230  //   child2
231  //           <-- child3 deleted
232
233  ui::AXNodeData tree2_child0;
234  tree2_child0.id = 5;
235  tree2_child0.SetName("Child0");
236  tree2_child0.role = ui::AX_ROLE_BUTTON;
237  tree2_child0.state = 0;
238
239  ui::AXNodeData tree2_root;
240  tree2_root.id = 1;
241  tree2_root.SetName("DocumentChanged");
242  tree2_root.role = ui::AX_ROLE_ROOT_WEB_AREA;
243  tree2_root.state = 0;
244  tree2_root.child_ids.push_back(5);
245  tree2_root.child_ids.push_back(2);
246  tree2_root.child_ids.push_back(3);
247
248  // Construct a BrowserAccessibilityManager with tree1.
249  CountedBrowserAccessibility::global_obj_count_ = 0;
250  BrowserAccessibilityManager* manager =
251      BrowserAccessibilityManager::Create(
252          MakeAXTreeUpdate(tree1_root,
253                           tree1_child1, tree1_child2, tree1_child3),
254          NULL,
255          new CountedBrowserAccessibilityFactory());
256  ASSERT_EQ(4, CountedBrowserAccessibility::global_obj_count_);
257
258  // Save references to all of the objects.
259  CountedBrowserAccessibility* root_accessible =
260      static_cast<CountedBrowserAccessibility*>(manager->GetRoot());
261  root_accessible->NativeAddReference();
262  CountedBrowserAccessibility* child1_accessible =
263      static_cast<CountedBrowserAccessibility*>(
264          root_accessible->PlatformGetChild(0));
265  child1_accessible->NativeAddReference();
266  CountedBrowserAccessibility* child2_accessible =
267      static_cast<CountedBrowserAccessibility*>(
268          root_accessible->PlatformGetChild(1));
269  child2_accessible->NativeAddReference();
270  CountedBrowserAccessibility* child3_accessible =
271      static_cast<CountedBrowserAccessibility*>(
272          root_accessible->PlatformGetChild(2));
273  child3_accessible->NativeAddReference();
274
275  // Check the index in parent.
276  EXPECT_EQ(0, child1_accessible->GetIndexInParent());
277  EXPECT_EQ(1, child2_accessible->GetIndexInParent());
278  EXPECT_EQ(2, child3_accessible->GetIndexInParent());
279
280  // Process a notification containing the changed subtree.
281  std::vector<AccessibilityHostMsg_EventParams> params;
282  params.push_back(AccessibilityHostMsg_EventParams());
283  AccessibilityHostMsg_EventParams* msg = &params[0];
284  msg->event_type = ui::AX_EVENT_CHILDREN_CHANGED;
285  msg->update.nodes.push_back(tree2_root);
286  msg->update.nodes.push_back(tree2_child0);
287  msg->id = tree2_root.id;
288  manager->OnAccessibilityEvents(params);
289
290  // There should be 5 objects now: the 4 from the new tree, plus the
291  // reference to child3 we kept.
292  EXPECT_EQ(5, CountedBrowserAccessibility::global_obj_count_);
293
294  // Check that our references to the root, child1, and child2 are still valid,
295  // but that the reference to child3 is now invalid.
296  EXPECT_TRUE(root_accessible->instance_active());
297  EXPECT_TRUE(child1_accessible->instance_active());
298  EXPECT_TRUE(child2_accessible->instance_active());
299  EXPECT_FALSE(child3_accessible->instance_active());
300
301  // Check that the index in parent has been updated.
302  EXPECT_EQ(1, child1_accessible->GetIndexInParent());
303  EXPECT_EQ(2, child2_accessible->GetIndexInParent());
304
305  // Release our references. The object count should only decrease by 1
306  // for child3.
307  root_accessible->NativeReleaseReference();
308  child1_accessible->NativeReleaseReference();
309  child2_accessible->NativeReleaseReference();
310  child3_accessible->NativeReleaseReference();
311
312  EXPECT_EQ(4, CountedBrowserAccessibility::global_obj_count_);
313
314  // Delete the manager and make sure all memory is cleaned up.
315  delete manager;
316  ASSERT_EQ(0, CountedBrowserAccessibility::global_obj_count_);
317}
318
319TEST(BrowserAccessibilityManagerTest, TestReuseBrowserAccessibilityObjects2) {
320  // Similar to the test above, but with a more complicated tree.
321
322  // Tree 1:
323  //
324  // root
325  //   container
326  //     child1
327  //       grandchild1
328  //     child2
329  //       grandchild2
330  //     child3
331  //       grandchild3
332
333  ui::AXNodeData tree1_grandchild1;
334  tree1_grandchild1.id = 4;
335  tree1_grandchild1.SetName("GrandChild1");
336  tree1_grandchild1.role = ui::AX_ROLE_BUTTON;
337  tree1_grandchild1.state = 0;
338
339  ui::AXNodeData tree1_child1;
340  tree1_child1.id = 3;
341  tree1_child1.SetName("Child1");
342  tree1_child1.role = ui::AX_ROLE_BUTTON;
343  tree1_child1.state = 0;
344  tree1_child1.child_ids.push_back(4);
345
346  ui::AXNodeData tree1_grandchild2;
347  tree1_grandchild2.id = 6;
348  tree1_grandchild2.SetName("GrandChild1");
349  tree1_grandchild2.role = ui::AX_ROLE_BUTTON;
350  tree1_grandchild2.state = 0;
351
352  ui::AXNodeData tree1_child2;
353  tree1_child2.id = 5;
354  tree1_child2.SetName("Child2");
355  tree1_child2.role = ui::AX_ROLE_BUTTON;
356  tree1_child2.state = 0;
357  tree1_child2.child_ids.push_back(6);
358
359  ui::AXNodeData tree1_grandchild3;
360  tree1_grandchild3.id = 8;
361  tree1_grandchild3.SetName("GrandChild3");
362  tree1_grandchild3.role = ui::AX_ROLE_BUTTON;
363  tree1_grandchild3.state = 0;
364
365  ui::AXNodeData tree1_child3;
366  tree1_child3.id = 7;
367  tree1_child3.SetName("Child3");
368  tree1_child3.role = ui::AX_ROLE_BUTTON;
369  tree1_child3.state = 0;
370  tree1_child3.child_ids.push_back(8);
371
372  ui::AXNodeData tree1_container;
373  tree1_container.id = 2;
374  tree1_container.SetName("Container");
375  tree1_container.role = ui::AX_ROLE_GROUP;
376  tree1_container.state = 0;
377  tree1_container.child_ids.push_back(3);
378  tree1_container.child_ids.push_back(5);
379  tree1_container.child_ids.push_back(7);
380
381  ui::AXNodeData tree1_root;
382  tree1_root.id = 1;
383  tree1_root.SetName("Document");
384  tree1_root.role = ui::AX_ROLE_ROOT_WEB_AREA;
385  tree1_root.state = 0;
386  tree1_root.child_ids.push_back(2);
387
388  // Tree 2:
389  //
390  // root
391  //   container
392  //     child0         <-- inserted
393  //       grandchild0  <--
394  //     child1
395  //       grandchild1
396  //     child2
397  //       grandchild2
398  //                    <-- child3 (and grandchild3) deleted
399
400  ui::AXNodeData tree2_grandchild0;
401  tree2_grandchild0.id = 9;
402  tree2_grandchild0.SetName("GrandChild0");
403  tree2_grandchild0.role = ui::AX_ROLE_BUTTON;
404  tree2_grandchild0.state = 0;
405
406  ui::AXNodeData tree2_child0;
407  tree2_child0.id = 10;
408  tree2_child0.SetName("Child0");
409  tree2_child0.role = ui::AX_ROLE_BUTTON;
410  tree2_child0.state = 0;
411  tree2_child0.child_ids.push_back(9);
412
413  ui::AXNodeData tree2_container;
414  tree2_container.id = 2;
415  tree2_container.SetName("Container");
416  tree2_container.role = ui::AX_ROLE_GROUP;
417  tree2_container.state = 0;
418  tree2_container.child_ids.push_back(10);
419  tree2_container.child_ids.push_back(3);
420  tree2_container.child_ids.push_back(5);
421
422  // Construct a BrowserAccessibilityManager with tree1.
423  CountedBrowserAccessibility::global_obj_count_ = 0;
424  BrowserAccessibilityManager* manager =
425      BrowserAccessibilityManager::Create(
426          MakeAXTreeUpdate(tree1_root, tree1_container,
427                           tree1_child1, tree1_grandchild1,
428                           tree1_child2, tree1_grandchild2,
429                           tree1_child3, tree1_grandchild3),
430          NULL,
431          new CountedBrowserAccessibilityFactory());
432  ASSERT_EQ(8, CountedBrowserAccessibility::global_obj_count_);
433
434  // Save references to some objects.
435  CountedBrowserAccessibility* root_accessible =
436      static_cast<CountedBrowserAccessibility*>(manager->GetRoot());
437  root_accessible->NativeAddReference();
438  CountedBrowserAccessibility* container_accessible =
439      static_cast<CountedBrowserAccessibility*>(
440          root_accessible->PlatformGetChild(0));
441  container_accessible->NativeAddReference();
442  CountedBrowserAccessibility* child2_accessible =
443      static_cast<CountedBrowserAccessibility*>(
444          container_accessible->PlatformGetChild(1));
445  child2_accessible->NativeAddReference();
446  CountedBrowserAccessibility* child3_accessible =
447      static_cast<CountedBrowserAccessibility*>(
448          container_accessible->PlatformGetChild(2));
449  child3_accessible->NativeAddReference();
450
451  // Check the index in parent.
452  EXPECT_EQ(1, child2_accessible->GetIndexInParent());
453  EXPECT_EQ(2, child3_accessible->GetIndexInParent());
454
455  // Process a notification containing the changed subtree rooted at
456  // the container.
457  std::vector<AccessibilityHostMsg_EventParams> params;
458  params.push_back(AccessibilityHostMsg_EventParams());
459  AccessibilityHostMsg_EventParams* msg = &params[0];
460  msg->event_type = ui::AX_EVENT_CHILDREN_CHANGED;
461  msg->update.nodes.push_back(tree2_container);
462  msg->update.nodes.push_back(tree2_child0);
463  msg->update.nodes.push_back(tree2_grandchild0);
464  msg->id = tree2_container.id;
465  manager->OnAccessibilityEvents(params);
466
467  // There should be 9 objects now: the 8 from the new tree, plus the
468  // reference to child3 we kept.
469  EXPECT_EQ(9, CountedBrowserAccessibility::global_obj_count_);
470
471  // Check that our references to the root and container and child2 are
472  // still valid, but that the reference to child3 is now invalid.
473  EXPECT_TRUE(root_accessible->instance_active());
474  EXPECT_TRUE(container_accessible->instance_active());
475  EXPECT_TRUE(child2_accessible->instance_active());
476  EXPECT_FALSE(child3_accessible->instance_active());
477
478  // Ensure that we retain the parent of the detached subtree.
479  EXPECT_EQ(root_accessible, container_accessible->GetParent());
480  EXPECT_EQ(0, container_accessible->GetIndexInParent());
481
482  // Check that the index in parent has been updated.
483  EXPECT_EQ(2, child2_accessible->GetIndexInParent());
484
485  // Release our references. The object count should only decrease by 1
486  // for child3.
487  root_accessible->NativeReleaseReference();
488  container_accessible->NativeReleaseReference();
489  child2_accessible->NativeReleaseReference();
490  child3_accessible->NativeReleaseReference();
491
492  EXPECT_EQ(8, CountedBrowserAccessibility::global_obj_count_);
493
494  // Delete the manager and make sure all memory is cleaned up.
495  delete manager;
496  ASSERT_EQ(0, CountedBrowserAccessibility::global_obj_count_);
497}
498
499TEST(BrowserAccessibilityManagerTest, TestMoveChildUp) {
500  // Tree 1:
501  //
502  // 1
503  //   2
504  //   3
505  //     4
506
507  ui::AXNodeData tree1_4;
508  tree1_4.id = 4;
509  tree1_4.state = 0;
510
511  ui::AXNodeData tree1_3;
512  tree1_3.id = 3;
513  tree1_3.state = 0;
514  tree1_3.child_ids.push_back(4);
515
516  ui::AXNodeData tree1_2;
517  tree1_2.id = 2;
518  tree1_2.state = 0;
519
520  ui::AXNodeData tree1_1;
521  tree1_1.id = 1;
522  tree1_1.role = ui::AX_ROLE_ROOT_WEB_AREA;
523  tree1_1.state = 0;
524  tree1_1.child_ids.push_back(2);
525  tree1_1.child_ids.push_back(3);
526
527  // Tree 2:
528  //
529  // 1
530  //   4    <-- moves up a level and gains child
531  //     6  <-- new
532  //   5    <-- new
533
534  ui::AXNodeData tree2_6;
535  tree2_6.id = 6;
536  tree2_6.state = 0;
537
538  ui::AXNodeData tree2_5;
539  tree2_5.id = 5;
540  tree2_5.state = 0;
541
542  ui::AXNodeData tree2_4;
543  tree2_4.id = 4;
544  tree2_4.state = 0;
545  tree2_4.child_ids.push_back(6);
546
547  ui::AXNodeData tree2_1;
548  tree2_1.id = 1;
549  tree2_1.state = 0;
550  tree2_1.child_ids.push_back(4);
551  tree2_1.child_ids.push_back(5);
552
553  // Construct a BrowserAccessibilityManager with tree1.
554  CountedBrowserAccessibility::global_obj_count_ = 0;
555  BrowserAccessibilityManager* manager =
556      BrowserAccessibilityManager::Create(
557          MakeAXTreeUpdate(tree1_1, tree1_2, tree1_3, tree1_4),
558          NULL,
559          new CountedBrowserAccessibilityFactory());
560  ASSERT_EQ(4, CountedBrowserAccessibility::global_obj_count_);
561
562  // Process a notification containing the changed subtree.
563  std::vector<AccessibilityHostMsg_EventParams> params;
564  params.push_back(AccessibilityHostMsg_EventParams());
565  AccessibilityHostMsg_EventParams* msg = &params[0];
566  msg->event_type = ui::AX_EVENT_CHILDREN_CHANGED;
567  msg->update.nodes.push_back(tree2_1);
568  msg->update.nodes.push_back(tree2_4);
569  msg->update.nodes.push_back(tree2_5);
570  msg->update.nodes.push_back(tree2_6);
571  msg->id = tree2_1.id;
572  manager->OnAccessibilityEvents(params);
573
574  // There should be 4 objects now.
575  EXPECT_EQ(4, CountedBrowserAccessibility::global_obj_count_);
576
577  // Delete the manager and make sure all memory is cleaned up.
578  delete manager;
579  ASSERT_EQ(0, CountedBrowserAccessibility::global_obj_count_);
580}
581
582TEST(BrowserAccessibilityManagerTest, TestFatalError) {
583  // Test that BrowserAccessibilityManager raises a fatal error
584  // (which will crash the renderer) if the same id is used in
585  // two places in the tree.
586
587  ui::AXNodeData root;
588  root.id = 1;
589  root.role = ui::AX_ROLE_ROOT_WEB_AREA;
590  root.child_ids.push_back(2);
591  root.child_ids.push_back(2);
592
593  CountedBrowserAccessibilityFactory* factory =
594      new CountedBrowserAccessibilityFactory();
595  scoped_ptr<TestBrowserAccessibilityDelegate> delegate(
596      new TestBrowserAccessibilityDelegate());
597  scoped_ptr<BrowserAccessibilityManager> manager;
598  ASSERT_FALSE(delegate->got_fatal_error());
599  manager.reset(BrowserAccessibilityManager::Create(
600      MakeAXTreeUpdate(root),
601      delegate.get(),
602      factory));
603  ASSERT_TRUE(delegate->got_fatal_error());
604
605  ui::AXNodeData root2;
606  root2.id = 1;
607  root2.role = ui::AX_ROLE_ROOT_WEB_AREA;
608  root2.child_ids.push_back(2);
609  root2.child_ids.push_back(3);
610
611  ui::AXNodeData child1;
612  child1.id = 2;
613  child1.child_ids.push_back(4);
614  child1.child_ids.push_back(5);
615
616  ui::AXNodeData child2;
617  child2.id = 3;
618  child2.child_ids.push_back(6);
619  child2.child_ids.push_back(5);  // Duplicate
620
621  ui::AXNodeData grandchild4;
622  grandchild4.id = 4;
623
624  ui::AXNodeData grandchild5;
625  grandchild5.id = 5;
626
627  ui::AXNodeData grandchild6;
628  grandchild6.id = 6;
629
630  delegate->reset_got_fatal_error();
631  factory = new CountedBrowserAccessibilityFactory();
632  manager.reset(BrowserAccessibilityManager::Create(
633      MakeAXTreeUpdate(root2, child1, child2,
634                       grandchild4, grandchild5, grandchild6),
635      delegate.get(),
636      factory));
637  ASSERT_TRUE(delegate->got_fatal_error());
638}
639
640TEST(BrowserAccessibilityManagerTest, BoundsForRange) {
641  ui::AXNodeData root;
642  root.id = 1;
643  root.role = ui::AX_ROLE_ROOT_WEB_AREA;
644
645  ui::AXNodeData static_text;
646  static_text.id = 2;
647  static_text.SetValue("Hello, world.");
648  static_text.role = ui::AX_ROLE_STATIC_TEXT;
649  static_text.location = gfx::Rect(100, 100, 29, 18);
650  root.child_ids.push_back(2);
651
652  ui::AXNodeData inline_text1;
653  inline_text1.id = 3;
654  inline_text1.SetValue("Hello, ");
655  inline_text1.role = ui::AX_ROLE_INLINE_TEXT_BOX;
656  inline_text1.location = gfx::Rect(100, 100, 29, 9);
657  inline_text1.AddIntAttribute(ui::AX_ATTR_TEXT_DIRECTION,
658                               ui::AX_TEXT_DIRECTION_LR);
659  std::vector<int32> character_offsets1;
660  character_offsets1.push_back(6);   // 0
661  character_offsets1.push_back(11);  // 1
662  character_offsets1.push_back(16);  // 2
663  character_offsets1.push_back(21);  // 3
664  character_offsets1.push_back(26);  // 4
665  character_offsets1.push_back(29);  // 5
666  character_offsets1.push_back(29);  // 6 (note that the space has no width)
667  inline_text1.AddIntListAttribute(
668      ui::AX_ATTR_CHARACTER_OFFSETS, character_offsets1);
669  static_text.child_ids.push_back(3);
670
671  ui::AXNodeData inline_text2;
672  inline_text2.id = 4;
673  inline_text2.SetValue("world.");
674  inline_text2.role = ui::AX_ROLE_INLINE_TEXT_BOX;
675  inline_text2.location = gfx::Rect(100, 109, 28, 9);
676  inline_text2.AddIntAttribute(ui::AX_ATTR_TEXT_DIRECTION,
677                               ui::AX_TEXT_DIRECTION_LR);
678  std::vector<int32> character_offsets2;
679  character_offsets2.push_back(5);
680  character_offsets2.push_back(10);
681  character_offsets2.push_back(15);
682  character_offsets2.push_back(20);
683  character_offsets2.push_back(25);
684  character_offsets2.push_back(28);
685  inline_text2.AddIntListAttribute(
686      ui::AX_ATTR_CHARACTER_OFFSETS, character_offsets2);
687  static_text.child_ids.push_back(4);
688
689  scoped_ptr<BrowserAccessibilityManager> manager(
690      BrowserAccessibilityManager::Create(
691          MakeAXTreeUpdate(root, static_text, inline_text1, inline_text2),
692          NULL,
693          new CountedBrowserAccessibilityFactory()));
694
695  BrowserAccessibility* root_accessible = manager->GetRoot();
696  BrowserAccessibility* static_text_accessible =
697      root_accessible->PlatformGetChild(0);
698
699  EXPECT_EQ(gfx::Rect(100, 100, 6, 9).ToString(),
700            static_text_accessible->GetLocalBoundsForRange(0, 1).ToString());
701
702  EXPECT_EQ(gfx::Rect(100, 100, 26, 9).ToString(),
703            static_text_accessible->GetLocalBoundsForRange(0, 5).ToString());
704
705  EXPECT_EQ(gfx::Rect(100, 109, 5, 9).ToString(),
706            static_text_accessible->GetLocalBoundsForRange(7, 1).ToString());
707
708  EXPECT_EQ(gfx::Rect(100, 109, 25, 9).ToString(),
709            static_text_accessible->GetLocalBoundsForRange(7, 5).ToString());
710
711  EXPECT_EQ(gfx::Rect(100, 100, 29, 18).ToString(),
712            static_text_accessible->GetLocalBoundsForRange(5, 3).ToString());
713
714  EXPECT_EQ(gfx::Rect(100, 100, 29, 18).ToString(),
715            static_text_accessible->GetLocalBoundsForRange(0, 13).ToString());
716
717  // Test range that's beyond the text.
718  EXPECT_EQ(gfx::Rect(100, 100, 29, 18).ToString(),
719            static_text_accessible->GetLocalBoundsForRange(-1, 999).ToString());
720
721  // Test that we can call bounds for range on the parent element, too,
722  // and it still works.
723  EXPECT_EQ(gfx::Rect(100, 100, 29, 18).ToString(),
724            root_accessible->GetLocalBoundsForRange(0, 13).ToString());
725}
726
727TEST(BrowserAccessibilityManagerTest, BoundsForRangeBiDi) {
728  // In this example, we assume that the string "123abc" is rendered with
729  // "123" going left-to-right and "abc" going right-to-left. In other
730  // words, on-screen it would look like "123cba". This is possible to
731  // acheive if the source string had unicode control characters
732  // to switch directions. This test doesn't worry about how, though - it just
733  // tests that if something like that were to occur, GetLocalBoundsForRange
734  // returns the correct bounds for different ranges.
735
736  ui::AXNodeData root;
737  root.id = 1;
738  root.role = ui::AX_ROLE_ROOT_WEB_AREA;
739
740  ui::AXNodeData static_text;
741  static_text.id = 2;
742  static_text.SetValue("123abc");
743  static_text.role = ui::AX_ROLE_STATIC_TEXT;
744  static_text.location = gfx::Rect(100, 100, 60, 20);
745  root.child_ids.push_back(2);
746
747  ui::AXNodeData inline_text1;
748  inline_text1.id = 3;
749  inline_text1.SetValue("123");
750  inline_text1.role = ui::AX_ROLE_INLINE_TEXT_BOX;
751  inline_text1.location = gfx::Rect(100, 100, 30, 20);
752  inline_text1.AddIntAttribute(ui::AX_ATTR_TEXT_DIRECTION,
753                               ui::AX_TEXT_DIRECTION_LR);
754  std::vector<int32> character_offsets1;
755  character_offsets1.push_back(10);  // 0
756  character_offsets1.push_back(20);  // 1
757  character_offsets1.push_back(30);  // 2
758  inline_text1.AddIntListAttribute(
759      ui::AX_ATTR_CHARACTER_OFFSETS, character_offsets1);
760  static_text.child_ids.push_back(3);
761
762  ui::AXNodeData inline_text2;
763  inline_text2.id = 4;
764  inline_text2.SetValue("abc");
765  inline_text2.role = ui::AX_ROLE_INLINE_TEXT_BOX;
766  inline_text2.location = gfx::Rect(130, 100, 30, 20);
767  inline_text2.AddIntAttribute(ui::AX_ATTR_TEXT_DIRECTION,
768                               ui::AX_TEXT_DIRECTION_RL);
769  std::vector<int32> character_offsets2;
770  character_offsets2.push_back(10);
771  character_offsets2.push_back(20);
772  character_offsets2.push_back(30);
773  inline_text2.AddIntListAttribute(
774      ui::AX_ATTR_CHARACTER_OFFSETS, character_offsets2);
775  static_text.child_ids.push_back(4);
776
777  scoped_ptr<BrowserAccessibilityManager> manager(
778      BrowserAccessibilityManager::Create(
779          MakeAXTreeUpdate(root, static_text, inline_text1, inline_text2),
780          NULL,
781          new CountedBrowserAccessibilityFactory()));
782
783  BrowserAccessibility* root_accessible = manager->GetRoot();
784  BrowserAccessibility* static_text_accessible =
785      root_accessible->PlatformGetChild(0);
786
787  EXPECT_EQ(gfx::Rect(100, 100, 60, 20).ToString(),
788            static_text_accessible->GetLocalBoundsForRange(0, 6).ToString());
789
790  EXPECT_EQ(gfx::Rect(100, 100, 10, 20).ToString(),
791            static_text_accessible->GetLocalBoundsForRange(0, 1).ToString());
792
793  EXPECT_EQ(gfx::Rect(100, 100, 30, 20).ToString(),
794            static_text_accessible->GetLocalBoundsForRange(0, 3).ToString());
795
796  EXPECT_EQ(gfx::Rect(150, 100, 10, 20).ToString(),
797            static_text_accessible->GetLocalBoundsForRange(3, 1).ToString());
798
799  EXPECT_EQ(gfx::Rect(130, 100, 30, 20).ToString(),
800            static_text_accessible->GetLocalBoundsForRange(3, 3).ToString());
801
802  // This range is only two characters, but because of the direction switch
803  // the bounds are as wide as four characters.
804  EXPECT_EQ(gfx::Rect(120, 100, 40, 20).ToString(),
805            static_text_accessible->GetLocalBoundsForRange(2, 2).ToString());
806}
807
808#if defined(OS_WIN)
809#define MAYBE_BoundsForRangeOnParentElement \
810  DISABLED_BoundsForRangeOnParentElement
811#else
812#define MAYBE_BoundsForRangeOnParentElement BoundsForRangeOnParentElement
813#endif
814TEST(BrowserAccessibilityManagerTest, MAYBE_BoundsForRangeOnParentElement) {
815  ui::AXNodeData root;
816  root.id = 1;
817  root.role = ui::AX_ROLE_ROOT_WEB_AREA;
818  root.child_ids.push_back(2);
819
820  ui::AXNodeData div;
821  div.id = 2;
822  div.role = ui::AX_ROLE_DIV;
823  div.location = gfx::Rect(100, 100, 100, 20);
824  div.child_ids.push_back(3);
825  div.child_ids.push_back(4);
826  div.child_ids.push_back(5);
827
828  ui::AXNodeData static_text1;
829  static_text1.id = 3;
830  static_text1.SetValue("AB");
831  static_text1.role = ui::AX_ROLE_STATIC_TEXT;
832  static_text1.location = gfx::Rect(100, 100, 40, 20);
833  static_text1.child_ids.push_back(6);
834
835  ui::AXNodeData img;
836  img.id = 4;
837  img.role = ui::AX_ROLE_IMAGE;
838  img.location = gfx::Rect(140, 100, 20, 20);
839
840  ui::AXNodeData static_text2;
841  static_text2.id = 5;
842  static_text2.SetValue("CD");
843  static_text2.role = ui::AX_ROLE_STATIC_TEXT;
844  static_text2.location = gfx::Rect(160, 100, 40, 20);
845  static_text2.child_ids.push_back(7);
846
847  ui::AXNodeData inline_text1;
848  inline_text1.id = 6;
849  inline_text1.SetValue("AB");
850  inline_text1.role = ui::AX_ROLE_INLINE_TEXT_BOX;
851  inline_text1.location = gfx::Rect(100, 100, 40, 20);
852  inline_text1.AddIntAttribute(ui::AX_ATTR_TEXT_DIRECTION,
853                               ui::AX_TEXT_DIRECTION_LR);
854  std::vector<int32> character_offsets1;
855  character_offsets1.push_back(20);  // 0
856  character_offsets1.push_back(40);  // 1
857  inline_text1.AddIntListAttribute(
858      ui::AX_ATTR_CHARACTER_OFFSETS, character_offsets1);
859
860  ui::AXNodeData inline_text2;
861  inline_text2.id = 7;
862  inline_text2.SetValue("CD");
863  inline_text2.role = ui::AX_ROLE_INLINE_TEXT_BOX;
864  inline_text2.location = gfx::Rect(160, 100, 40, 20);
865  inline_text2.AddIntAttribute(ui::AX_ATTR_TEXT_DIRECTION,
866                               ui::AX_TEXT_DIRECTION_LR);
867  std::vector<int32> character_offsets2;
868  character_offsets2.push_back(20);  // 0
869  character_offsets2.push_back(40);  // 1
870  inline_text2.AddIntListAttribute(
871      ui::AX_ATTR_CHARACTER_OFFSETS, character_offsets2);
872
873  scoped_ptr<BrowserAccessibilityManager> manager(
874      BrowserAccessibilityManager::Create(
875          MakeAXTreeUpdate(
876              root, div, static_text1, img,
877              static_text2, inline_text1, inline_text2),
878          NULL,
879          new CountedBrowserAccessibilityFactory()));
880  BrowserAccessibility* root_accessible = manager->GetRoot();
881
882  EXPECT_EQ(gfx::Rect(100, 100, 20, 20).ToString(),
883            root_accessible->GetLocalBoundsForRange(0, 1).ToString());
884
885  EXPECT_EQ(gfx::Rect(100, 100, 40, 20).ToString(),
886            root_accessible->GetLocalBoundsForRange(0, 2).ToString());
887
888  EXPECT_EQ(gfx::Rect(100, 100, 80, 20).ToString(),
889            root_accessible->GetLocalBoundsForRange(0, 3).ToString());
890
891  EXPECT_EQ(gfx::Rect(120, 100, 60, 20).ToString(),
892            root_accessible->GetLocalBoundsForRange(1, 2).ToString());
893
894  EXPECT_EQ(gfx::Rect(120, 100, 80, 20).ToString(),
895            root_accessible->GetLocalBoundsForRange(1, 3).ToString());
896
897  EXPECT_EQ(gfx::Rect(100, 100, 100, 20).ToString(),
898            root_accessible->GetLocalBoundsForRange(0, 4).ToString());
899}
900
901TEST(BrowserAccessibilityManagerTest, NextPreviousInTreeOrder) {
902  ui::AXNodeData root;
903  root.id = 1;
904  root.role = ui::AX_ROLE_ROOT_WEB_AREA;
905
906  ui::AXNodeData node2;
907  node2.id = 2;
908  root.child_ids.push_back(2);
909
910  ui::AXNodeData node3;
911  node3.id = 3;
912  root.child_ids.push_back(3);
913
914  ui::AXNodeData node4;
915  node4.id = 4;
916  node3.child_ids.push_back(4);
917
918  ui::AXNodeData node5;
919  node5.id = 5;
920  root.child_ids.push_back(5);
921
922  scoped_ptr<BrowserAccessibilityManager> manager(
923      BrowserAccessibilityManager::Create(
924          MakeAXTreeUpdate(root, node2, node3, node4, node5),
925          NULL,
926          new CountedBrowserAccessibilityFactory()));
927
928  BrowserAccessibility* root_accessible = manager->GetRoot();
929  BrowserAccessibility* node2_accessible = root_accessible->PlatformGetChild(0);
930  BrowserAccessibility* node3_accessible = root_accessible->PlatformGetChild(1);
931  BrowserAccessibility* node4_accessible =
932      node3_accessible->PlatformGetChild(0);
933  BrowserAccessibility* node5_accessible = root_accessible->PlatformGetChild(2);
934
935  ASSERT_EQ(NULL, manager->NextInTreeOrder(NULL));
936  ASSERT_EQ(node2_accessible, manager->NextInTreeOrder(root_accessible));
937  ASSERT_EQ(node3_accessible, manager->NextInTreeOrder(node2_accessible));
938  ASSERT_EQ(node4_accessible, manager->NextInTreeOrder(node3_accessible));
939  ASSERT_EQ(node5_accessible, manager->NextInTreeOrder(node4_accessible));
940  ASSERT_EQ(NULL, manager->NextInTreeOrder(node5_accessible));
941
942  ASSERT_EQ(NULL, manager->PreviousInTreeOrder(NULL));
943  ASSERT_EQ(node4_accessible, manager->PreviousInTreeOrder(node5_accessible));
944  ASSERT_EQ(node3_accessible, manager->PreviousInTreeOrder(node4_accessible));
945  ASSERT_EQ(node2_accessible, manager->PreviousInTreeOrder(node3_accessible));
946  ASSERT_EQ(root_accessible, manager->PreviousInTreeOrder(node2_accessible));
947}
948
949}  // namespace content
950