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