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