- add sources.
[platform/framework/web/crosswalk.git] / src / content / renderer / accessibility / renderer_accessibility_browsertest.cc
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/utf_string_conversions.h"
6 #include "content/common/accessibility_node_data.h"
7 #include "content/common/view_messages.h"
8 #include "content/public/test/render_view_test.h"
9 #include "content/renderer/accessibility/renderer_accessibility_complete.h"
10 #include "content/renderer/render_view_impl.h"
11 #include "testing/gtest/include/gtest/gtest.h"
12 #include "third_party/WebKit/public/platform/WebSize.h"
13 #include "third_party/WebKit/public/web/WebAXObject.h"
14 #include "third_party/WebKit/public/web/WebDocument.h"
15 #include "third_party/WebKit/public/web/WebView.h"
16
17 using WebKit::WebAXObject;
18 using WebKit::WebDocument;
19
20 namespace content {
21
22 class TestRendererAccessibilityComplete : public RendererAccessibilityComplete {
23  public:
24   explicit TestRendererAccessibilityComplete(RenderViewImpl* render_view)
25     : RendererAccessibilityComplete(render_view),
26       browser_tree_node_count_(0) {
27   }
28
29   int browser_tree_node_count() { return browser_tree_node_count_; }
30
31   struct TestBrowserTreeNode : public BrowserTreeNode {
32     TestBrowserTreeNode(TestRendererAccessibilityComplete* owner)
33         : owner_(owner) {
34       owner_->browser_tree_node_count_++;
35     }
36
37     virtual ~TestBrowserTreeNode() {
38       owner_->browser_tree_node_count_--;
39     }
40
41    private:
42     TestRendererAccessibilityComplete* owner_;
43   };
44
45   virtual BrowserTreeNode* CreateBrowserTreeNode() OVERRIDE {
46     return new TestBrowserTreeNode(this);
47   }
48
49   void SendPendingAccessibilityEvents() {
50     RendererAccessibilityComplete::SendPendingAccessibilityEvents();
51   }
52
53 private:
54   int browser_tree_node_count_;
55 };
56
57 class RendererAccessibilityTest : public RenderViewTest {
58  public:
59   RendererAccessibilityTest() {}
60
61   RenderViewImpl* view() {
62     return static_cast<RenderViewImpl*>(view_);
63   }
64
65   virtual void SetUp() {
66     RenderViewTest::SetUp();
67     sink_ = &render_thread_->sink();
68   }
69
70   void SetMode(AccessibilityMode mode) {
71     view()->OnSetAccessibilityMode(mode);
72   }
73
74   void GetLastAccEvent(
75       AccessibilityHostMsg_EventParams* params) {
76     const IPC::Message* message =
77         sink_->GetUniqueMessageMatching(AccessibilityHostMsg_Events::ID);
78     ASSERT_TRUE(message);
79     Tuple1<std::vector<AccessibilityHostMsg_EventParams> > param;
80     AccessibilityHostMsg_Events::Read(message, &param);
81     ASSERT_GE(param.a.size(), 1U);
82     *params = param.a[0];
83   }
84
85   int CountAccessibilityNodesSentToBrowser() {
86     AccessibilityHostMsg_EventParams event;
87     GetLastAccEvent(&event);
88     return event.nodes.size();
89   }
90
91  protected:
92   IPC::TestSink* sink_;
93
94   DISALLOW_COPY_AND_ASSIGN(RendererAccessibilityTest);
95 };
96
97 TEST_F(RendererAccessibilityTest, EditableTextModeFocusEvents) {
98   // This is not a test of true web accessibility, it's a test of
99   // a mode used on Windows 8 in Metro mode where an extremely simplified
100   // accessibility tree containing only the current focused node is
101   // generated.
102   SetMode(AccessibilityModeEditableTextOnly);
103
104   // Set a minimum size and give focus so simulated events work.
105   view()->webwidget()->resize(WebKit::WebSize(500, 500));
106   view()->webwidget()->setFocus(true);
107
108   std::string html =
109       "<body>"
110       "  <input>"
111       "  <textarea></textarea>"
112       "  <p contentEditable>Editable</p>"
113       "  <div tabindex=0 role=textbox>Textbox</div>"
114       "  <button>Button</button>"
115       "  <a href=#>Link</a>"
116       "</body>";
117
118   // Load the test page.
119   LoadHTML(html.c_str());
120
121   // We should have sent a message to the browser with the initial focus
122   // on the document.
123   {
124     SCOPED_TRACE("Initial focus on document");
125     AccessibilityHostMsg_EventParams event;
126     GetLastAccEvent(&event);
127     EXPECT_EQ(event.event_type,
128               WebKit::WebAXEventLayoutComplete);
129     EXPECT_EQ(event.id, 1);
130     EXPECT_EQ(event.nodes.size(), 2U);
131     EXPECT_EQ(event.nodes[0].id, 1);
132     EXPECT_EQ(event.nodes[0].role,
133               WebKit::WebAXRoleRootWebArea);
134     EXPECT_EQ(event.nodes[0].state,
135               (1U << WebKit::WebAXStateReadonly) |
136               (1U << WebKit::WebAXStateFocusable) |
137               (1U << WebKit::WebAXStateFocused));
138     EXPECT_EQ(event.nodes[0].child_ids.size(), 1U);
139   }
140
141   // Now focus the input element, and check everything again.
142   {
143     SCOPED_TRACE("input");
144     sink_->ClearMessages();
145     ExecuteJavaScript("document.querySelector('input').focus();");
146     AccessibilityHostMsg_EventParams event;
147     GetLastAccEvent(&event);
148     EXPECT_EQ(event.event_type,
149               WebKit::WebAXEventFocus);
150     EXPECT_EQ(event.id, 3);
151     EXPECT_EQ(event.nodes[0].id, 1);
152     EXPECT_EQ(event.nodes[0].role,
153               WebKit::WebAXRoleRootWebArea);
154     EXPECT_EQ(event.nodes[0].state,
155               (1U << WebKit::WebAXStateReadonly) |
156               (1U << WebKit::WebAXStateFocusable));
157     EXPECT_EQ(event.nodes[0].child_ids.size(), 1U);
158     EXPECT_EQ(event.nodes[1].id, 3);
159     EXPECT_EQ(event.nodes[1].role,
160               WebKit::WebAXRoleGroup);
161     EXPECT_EQ(event.nodes[1].state,
162               (1U << WebKit::WebAXStateFocusable) |
163               (1U << WebKit::WebAXStateFocused));
164   }
165
166   // Check other editable text nodes.
167   {
168     SCOPED_TRACE("textarea");
169     sink_->ClearMessages();
170     ExecuteJavaScript("document.querySelector('textarea').focus();");
171     AccessibilityHostMsg_EventParams event;
172     GetLastAccEvent(&event);
173     EXPECT_EQ(event.id, 4);
174     EXPECT_EQ(event.nodes[1].state,
175               (1U << WebKit::WebAXStateFocusable) |
176               (1U << WebKit::WebAXStateFocused));
177   }
178
179   {
180     SCOPED_TRACE("contentEditable");
181     sink_->ClearMessages();
182     ExecuteJavaScript("document.querySelector('p').focus();");
183     AccessibilityHostMsg_EventParams event;
184     GetLastAccEvent(&event);
185     EXPECT_EQ(event.id, 5);
186     EXPECT_EQ(event.nodes[1].state,
187               (1U << WebKit::WebAXStateFocusable) |
188               (1U << WebKit::WebAXStateFocused));
189   }
190
191   {
192     SCOPED_TRACE("role=textarea");
193     sink_->ClearMessages();
194     ExecuteJavaScript("document.querySelector('div').focus();");
195     AccessibilityHostMsg_EventParams event;
196     GetLastAccEvent(&event);
197     EXPECT_EQ(event.id, 6);
198     EXPECT_EQ(event.nodes[1].state,
199               (1U << WebKit::WebAXStateFocusable) |
200               (1U << WebKit::WebAXStateFocused));
201   }
202
203   // Try focusing things that aren't editable text.
204   {
205     SCOPED_TRACE("button");
206     sink_->ClearMessages();
207     ExecuteJavaScript("document.querySelector('button').focus();");
208     AccessibilityHostMsg_EventParams event;
209     GetLastAccEvent(&event);
210     EXPECT_EQ(event.id, 7);
211     EXPECT_EQ(event.nodes[1].state,
212               (1U << WebKit::WebAXStateFocusable) |
213               (1U << WebKit::WebAXStateFocused) |
214               (1U << WebKit::WebAXStateReadonly));
215   }
216
217   {
218     SCOPED_TRACE("link");
219     sink_->ClearMessages();
220     ExecuteJavaScript("document.querySelector('a').focus();");
221     AccessibilityHostMsg_EventParams event;
222     GetLastAccEvent(&event);
223     EXPECT_EQ(event.id, 8);
224     EXPECT_EQ(event.nodes[1].state,
225               (1U << WebKit::WebAXStateFocusable) |
226               (1U << WebKit::WebAXStateFocused) |
227               (1U << WebKit::WebAXStateReadonly));
228   }
229
230   // Clear focus.
231   {
232     SCOPED_TRACE("Back to document.");
233     sink_->ClearMessages();
234     ExecuteJavaScript("document.activeElement.blur()");
235     AccessibilityHostMsg_EventParams event;
236     GetLastAccEvent(&event);
237     EXPECT_EQ(event.id, 1);
238   }
239 }
240
241 TEST_F(RendererAccessibilityTest, SendFullAccessibilityTreeOnReload) {
242   // The job of RendererAccessibilityComplete is to serialize the
243   // accessibility tree built by WebKit and send it to the browser.
244   // When the accessibility tree changes, it tries to send only
245   // the nodes that actually changed or were reparented. This test
246   // ensures that the messages sent are correct in cases when a page
247   // reloads, and that internal state is properly garbage-collected.
248   std::string html =
249       "<body>"
250       "  <div role='group' id='A'>"
251       "    <div role='group' id='A1'></div>"
252       "    <div role='group' id='A2'></div>"
253       "  </div>"
254       "</body>";
255   LoadHTML(html.c_str());
256
257   // Creating a RendererAccessibilityComplete should sent the tree
258   // to the browser.
259   scoped_ptr<TestRendererAccessibilityComplete> accessibility(
260       new TestRendererAccessibilityComplete(view()));
261   accessibility->SendPendingAccessibilityEvents();
262   EXPECT_EQ(4, accessibility->browser_tree_node_count());
263   EXPECT_EQ(4, CountAccessibilityNodesSentToBrowser());
264
265   // If we post another event but the tree doesn't change,
266   // we should only send 1 node to the browser.
267   sink_->ClearMessages();
268   WebDocument document = view()->GetWebView()->mainFrame()->document();
269   WebAXObject root_obj = document.accessibilityObject();
270   accessibility->HandleWebAccessibilityEvent(
271       root_obj,
272       WebKit::WebAXEventLayoutComplete);
273   accessibility->SendPendingAccessibilityEvents();
274   EXPECT_EQ(4, accessibility->browser_tree_node_count());
275   EXPECT_EQ(1, CountAccessibilityNodesSentToBrowser());
276   {
277     // Make sure it's the root object that was updated.
278     AccessibilityHostMsg_EventParams event;
279     GetLastAccEvent(&event);
280     EXPECT_EQ(root_obj.axID(), event.nodes[0].id);
281   }
282
283   // If we reload the page and send a event, we should send
284   // all 4 nodes to the browser. Also double-check that we didn't
285   // leak any of the old BrowserTreeNodes.
286   LoadHTML(html.c_str());
287   document = view()->GetWebView()->mainFrame()->document();
288   root_obj = document.accessibilityObject();
289   sink_->ClearMessages();
290   accessibility->HandleWebAccessibilityEvent(
291       root_obj,
292       WebKit::WebAXEventLayoutComplete);
293   accessibility->SendPendingAccessibilityEvents();
294   EXPECT_EQ(4, accessibility->browser_tree_node_count());
295   EXPECT_EQ(4, CountAccessibilityNodesSentToBrowser());
296
297   // Even if the first event is sent on an element other than
298   // the root, the whole tree should be updated because we know
299   // the browser doesn't have the root element.
300   LoadHTML(html.c_str());
301   document = view()->GetWebView()->mainFrame()->document();
302   root_obj = document.accessibilityObject();
303   sink_->ClearMessages();
304   const WebAXObject& first_child = root_obj.childAt(0);
305   accessibility->HandleWebAccessibilityEvent(
306       first_child,
307       WebKit::WebAXEventLiveRegionChanged);
308   accessibility->SendPendingAccessibilityEvents();
309   EXPECT_EQ(4, accessibility->browser_tree_node_count());
310   EXPECT_EQ(4, CountAccessibilityNodesSentToBrowser());
311 }
312
313 // http://crbug.com/253537
314 #if defined(OS_ANDROID)
315 #define MAYBE_AccessibilityMessagesQueueWhileSwappedOut \
316         DISABLED_AccessibilityMessagesQueueWhileSwappedOut
317 #else
318 #define MAYBE_AccessibilityMessagesQueueWhileSwappedOut \
319         AccessibilityMessagesQueueWhileSwappedOut
320 #endif
321
322 TEST_F(RendererAccessibilityTest,
323        MAYBE_AccessibilityMessagesQueueWhileSwappedOut) {
324   std::string html =
325       "<body>"
326       "  <p>Hello, world.</p>"
327       "</body>";
328   LoadHTML(html.c_str());
329
330   // Creating a RendererAccessibilityComplete should send the tree
331   // to the browser.
332   scoped_ptr<TestRendererAccessibilityComplete> accessibility(
333       new TestRendererAccessibilityComplete(view()));
334   accessibility->SendPendingAccessibilityEvents();
335   EXPECT_EQ(5, accessibility->browser_tree_node_count());
336   EXPECT_EQ(5, CountAccessibilityNodesSentToBrowser());
337
338   // Post a "value changed" event, but then swap out
339   // before sending it. It shouldn't send the event while
340   // swapped out.
341   sink_->ClearMessages();
342   WebDocument document = view()->GetWebView()->mainFrame()->document();
343   WebAXObject root_obj = document.accessibilityObject();
344   accessibility->HandleWebAccessibilityEvent(
345       root_obj,
346       WebKit::WebAXEventValueChanged);
347   view()->OnSwapOut();
348   accessibility->SendPendingAccessibilityEvents();
349   EXPECT_FALSE(sink_->GetUniqueMessageMatching(
350       AccessibilityHostMsg_Events::ID));
351
352   // Navigate, so we're not swapped out anymore. Now we should
353   // send accessibility events again. Note that the
354   // message that was queued up before will be quickly discarded
355   // because the element it was referring to no longer exists,
356   // so the event here is from loading this new page.
357   ViewMsg_Navigate_Params nav_params;
358   nav_params.url = GURL("data:text/html,<p>Hello, again.</p>");
359   nav_params.navigation_type = ViewMsg_Navigate_Type::NORMAL;
360   nav_params.transition = PAGE_TRANSITION_TYPED;
361   nav_params.current_history_list_length = 1;
362   nav_params.current_history_list_offset = 0;
363   nav_params.pending_history_list_offset = 1;
364   nav_params.page_id = -1;
365   view()->OnNavigate(nav_params);
366   accessibility->SendPendingAccessibilityEvents();
367   EXPECT_TRUE(sink_->GetUniqueMessageMatching(
368       AccessibilityHostMsg_Events::ID));
369 }
370
371 TEST_F(RendererAccessibilityTest, HideAccessibilityObject) {
372   // Test RendererAccessibilityComplete and make sure it sends the
373   // proper event to the browser when an object in the tree
374   // is hidden, but its children are not.
375   std::string html =
376       "<body>"
377       "  <div role='group' id='A'>"
378       "    <div role='group' id='B'>"
379       "      <div role='group' id='C' style='visibility:visible'>"
380       "      </div>"
381       "    </div>"
382       "  </div>"
383       "</body>";
384   LoadHTML(html.c_str());
385
386   scoped_ptr<TestRendererAccessibilityComplete> accessibility(
387       new TestRendererAccessibilityComplete(view()));
388   accessibility->SendPendingAccessibilityEvents();
389   EXPECT_EQ(4, accessibility->browser_tree_node_count());
390   EXPECT_EQ(4, CountAccessibilityNodesSentToBrowser());
391
392   WebDocument document = view()->GetWebView()->mainFrame()->document();
393   WebAXObject root_obj = document.accessibilityObject();
394   WebAXObject node_a = root_obj.childAt(0);
395   WebAXObject node_b = node_a.childAt(0);
396   WebAXObject node_c = node_b.childAt(0);
397
398   // Hide node 'B' ('C' stays visible).
399   ExecuteJavaScript(
400       "document.getElementById('B').style.visibility = 'hidden';");
401   // Force layout now.
402   ExecuteJavaScript("document.getElementById('B').offsetLeft;");
403
404   // Send a childrenChanged on 'A'.
405   sink_->ClearMessages();
406   accessibility->HandleWebAccessibilityEvent(
407       node_a,
408       WebKit::WebAXEventChildrenChanged);
409
410   accessibility->SendPendingAccessibilityEvents();
411   EXPECT_EQ(3, accessibility->browser_tree_node_count());
412   AccessibilityHostMsg_EventParams event;
413   GetLastAccEvent(&event);
414   ASSERT_EQ(3U, event.nodes.size());
415
416   // RendererAccessibilityComplete notices that 'C' is being reparented,
417   // so it updates 'B' first to remove 'C' as a child, then 'A' to add it,
418   // and finally it updates 'C'.
419   EXPECT_EQ(node_b.axID(), event.nodes[0].id);
420   EXPECT_EQ(node_a.axID(), event.nodes[1].id);
421   EXPECT_EQ(node_c.axID(), event.nodes[2].id);
422   EXPECT_EQ(3, CountAccessibilityNodesSentToBrowser());
423 }
424
425 TEST_F(RendererAccessibilityTest, ShowAccessibilityObject) {
426   // Test RendererAccessibilityComplete and make sure it sends the
427   // proper event to the browser when an object in the tree
428   // is shown, causing its own already-visible children to be
429   // reparented to it.
430   std::string html =
431       "<body>"
432       "  <div role='group' id='A'>"
433       "    <div role='group' id='B' style='visibility:hidden'>"
434       "      <div role='group' id='C' style='visibility:visible'>"
435       "      </div>"
436       "    </div>"
437       "  </div>"
438       "</body>";
439   LoadHTML(html.c_str());
440
441   scoped_ptr<TestRendererAccessibilityComplete> accessibility(
442       new TestRendererAccessibilityComplete(view()));
443   accessibility->SendPendingAccessibilityEvents();
444   EXPECT_EQ(3, accessibility->browser_tree_node_count());
445   EXPECT_EQ(3, CountAccessibilityNodesSentToBrowser());
446
447   // Show node 'B', then send a childrenChanged on 'A'.
448   ExecuteJavaScript(
449       "document.getElementById('B').style.visibility = 'visible';");
450   ExecuteJavaScript("document.getElementById('B').offsetLeft;");
451
452   sink_->ClearMessages();
453   WebDocument document = view()->GetWebView()->mainFrame()->document();
454   WebAXObject root_obj = document.accessibilityObject();
455   WebAXObject node_a = root_obj.childAt(0);
456   accessibility->HandleWebAccessibilityEvent(
457       node_a,
458       WebKit::WebAXEventChildrenChanged);
459
460   accessibility->SendPendingAccessibilityEvents();
461   EXPECT_EQ(4, accessibility->browser_tree_node_count());
462   AccessibilityHostMsg_EventParams event;
463   GetLastAccEvent(&event);
464   ASSERT_EQ(3U, event.nodes.size());
465   EXPECT_EQ(3, CountAccessibilityNodesSentToBrowser());
466 }
467
468 TEST_F(RendererAccessibilityTest, DetachAccessibilityObject) {
469   // Test RendererAccessibilityComplete and make sure it sends the
470   // proper event to the browser when an object in the tree
471   // is detached, but its children are not. This can happen when
472   // a layout occurs and an anonymous render block is no longer needed.
473   std::string html =
474       "<body aria-label='Body'>"
475       "<span>1</span><span style='display:block'>2</span>"
476       "</body>";
477   LoadHTML(html.c_str());
478
479   scoped_ptr<TestRendererAccessibilityComplete> accessibility(
480       new TestRendererAccessibilityComplete(view()));
481   accessibility->SendPendingAccessibilityEvents();
482   EXPECT_EQ(7, accessibility->browser_tree_node_count());
483   EXPECT_EQ(7, CountAccessibilityNodesSentToBrowser());
484
485   // Initially, the accessibility tree looks like this:
486   //
487   //   Document
488   //   +--Body
489   //      +--Anonymous Block
490   //         +--Static Text "1"
491   //            +--Inline Text Box "1"
492   //      +--Static Text "2"
493   //         +--Inline Text Box "2"
494   WebDocument document = view()->GetWebView()->mainFrame()->document();
495   WebAXObject root_obj = document.accessibilityObject();
496   WebAXObject body = root_obj.childAt(0);
497   WebAXObject anonymous_block = body.childAt(0);
498   WebAXObject text_1 = anonymous_block.childAt(0);
499   WebAXObject text_2 = body.childAt(1);
500
501   // Change the display of the second 'span' back to inline, which causes the
502   // anonymous block to be destroyed.
503   ExecuteJavaScript(
504       "document.querySelectorAll('span')[1].style.display = 'inline';");
505   // Force layout now.
506   ExecuteJavaScript("document.body.offsetLeft;");
507
508   // Send a childrenChanged on the body.
509   sink_->ClearMessages();
510   accessibility->HandleWebAccessibilityEvent(
511       body,
512       WebKit::WebAXEventChildrenChanged);
513
514   accessibility->SendPendingAccessibilityEvents();
515
516   // Afterwards, the accessibility tree looks like this:
517   //
518   //   Document
519   //   +--Body
520   //      +--Static Text "1"
521   //         +--Inline Text Box "1"
522   //      +--Static Text "2"
523   //         +--Inline Text Box "2"
524   //
525   // We just assert that there are now four nodes in the
526   // accessibility tree and that only three nodes needed
527   // to be updated (the body, the static text 1, and
528   // the static text 2).
529   EXPECT_EQ(6, accessibility->browser_tree_node_count());
530
531   AccessibilityHostMsg_EventParams event;
532   GetLastAccEvent(&event);
533   ASSERT_EQ(5U, event.nodes.size());
534
535   EXPECT_EQ(body.axID(), event.nodes[0].id);
536   EXPECT_EQ(text_1.axID(), event.nodes[1].id);
537   // The third event is to update text_2, but its id changes
538   // so we don't have a test expectation for it.
539 }
540
541 }  // namespace content