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