Upstream version 11.40.277.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.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 TestRendererAccessibility : public RendererAccessibility {
26  public:
27   explicit TestRendererAccessibility(RenderFrameImpl* render_frame)
28     : RendererAccessibility(render_frame) {
29   }
30
31   void SendPendingAccessibilityEvents() {
32     RendererAccessibility::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   void SetUp() override {
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, SendFullAccessibilityTreeOnReload) {
82   // The job of RendererAccessibility is to serialize the
83   // accessibility tree built by WebKit and send it to the browser.
84   // When the accessibility tree changes, it tries to send only
85   // the nodes that actually changed or were reparented. This test
86   // ensures that the messages sent are correct in cases when a page
87   // reloads, and that internal state is properly garbage-collected.
88   std::string html =
89       "<body>"
90       "  <div role='group' id='A'>"
91       "    <div role='group' id='A1'></div>"
92       "    <div role='group' id='A2'></div>"
93       "  </div>"
94       "</body>";
95   LoadHTML(html.c_str());
96
97   // Creating a RendererAccessibility should sent the tree to the browser.
98   scoped_ptr<TestRendererAccessibility> accessibility(
99       new TestRendererAccessibility(frame()));
100   accessibility->SendPendingAccessibilityEvents();
101   EXPECT_EQ(4, CountAccessibilityNodesSentToBrowser());
102
103   // If we post another event but the tree doesn't change,
104   // we should only send 1 node to the browser.
105   sink_->ClearMessages();
106   WebDocument document = view()->GetWebView()->mainFrame()->document();
107   WebAXObject root_obj = document.accessibilityObject();
108   accessibility->HandleAXEvent(
109       root_obj,
110       ui::AX_EVENT_LAYOUT_COMPLETE);
111   accessibility->SendPendingAccessibilityEvents();
112   EXPECT_EQ(1, CountAccessibilityNodesSentToBrowser());
113   {
114     // Make sure it's the root object that was updated.
115     AccessibilityHostMsg_EventParams event;
116     GetLastAccEvent(&event);
117     EXPECT_EQ(root_obj.axID(), event.update.nodes[0].id);
118   }
119
120   // If we reload the page and send a event, we should send
121   // all 4 nodes to the browser. Also double-check that we didn't
122   // leak any of the old BrowserTreeNodes.
123   LoadHTML(html.c_str());
124   document = view()->GetWebView()->mainFrame()->document();
125   root_obj = document.accessibilityObject();
126   sink_->ClearMessages();
127   accessibility->HandleAXEvent(
128       root_obj,
129       ui::AX_EVENT_LAYOUT_COMPLETE);
130   accessibility->SendPendingAccessibilityEvents();
131   EXPECT_EQ(4, CountAccessibilityNodesSentToBrowser());
132
133   // Even if the first event is sent on an element other than
134   // the root, the whole tree should be updated because we know
135   // the browser doesn't have the root element.
136   LoadHTML(html.c_str());
137   document = view()->GetWebView()->mainFrame()->document();
138   root_obj = document.accessibilityObject();
139   sink_->ClearMessages();
140   const WebAXObject& first_child = root_obj.childAt(0);
141   accessibility->HandleAXEvent(
142       first_child,
143       ui::AX_EVENT_LIVE_REGION_CHANGED);
144   accessibility->SendPendingAccessibilityEvents();
145   EXPECT_EQ(4, CountAccessibilityNodesSentToBrowser());
146 }
147
148 // http://crbug.com/253537
149 #if defined(OS_ANDROID)
150 #define MAYBE_AccessibilityMessagesQueueWhileSwappedOut \
151         DISABLED_AccessibilityMessagesQueueWhileSwappedOut
152 #else
153 #define MAYBE_AccessibilityMessagesQueueWhileSwappedOut \
154         AccessibilityMessagesQueueWhileSwappedOut
155 #endif
156
157 TEST_F(RendererAccessibilityTest,
158        MAYBE_AccessibilityMessagesQueueWhileSwappedOut) {
159   std::string html =
160       "<body>"
161       "  <p>Hello, world.</p>"
162       "</body>";
163   LoadHTML(html.c_str());
164   static const int kProxyRoutingId = 13;
165
166   // Creating a RendererAccessibility should send the tree to the browser.
167   scoped_ptr<TestRendererAccessibility> accessibility(
168       new TestRendererAccessibility(frame()));
169   accessibility->SendPendingAccessibilityEvents();
170   EXPECT_EQ(5, CountAccessibilityNodesSentToBrowser());
171
172   // Post a "value changed" event, but then swap out
173   // before sending it. It shouldn't send the event while
174   // swapped out.
175   sink_->ClearMessages();
176   WebDocument document = view()->GetWebView()->mainFrame()->document();
177   WebAXObject root_obj = document.accessibilityObject();
178   accessibility->HandleAXEvent(
179       root_obj,
180       ui::AX_EVENT_VALUE_CHANGED);
181   view()->GetMainRenderFrame()->OnSwapOut(kProxyRoutingId);
182   accessibility->SendPendingAccessibilityEvents();
183   EXPECT_FALSE(sink_->GetUniqueMessageMatching(
184       AccessibilityHostMsg_Events::ID));
185
186   // Navigate, so we're not swapped out anymore. Now we should
187   // send accessibility events again. Note that the
188   // message that was queued up before will be quickly discarded
189   // because the element it was referring to no longer exists,
190   // so the event here is from loading this new page.
191   FrameMsg_Navigate_Params nav_params;
192   nav_params.common_params.url = GURL("data:text/html,<p>Hello, again.</p>");
193   nav_params.common_params.navigation_type = FrameMsg_Navigate_Type::NORMAL;
194   nav_params.common_params.transition = ui::PAGE_TRANSITION_TYPED;
195   nav_params.current_history_list_length = 1;
196   nav_params.current_history_list_offset = 0;
197   nav_params.pending_history_list_offset = 1;
198   nav_params.page_id = -1;
199   nav_params.commit_params.browser_navigation_start =
200       base::TimeTicks::FromInternalValue(1);
201   frame()->OnNavigate(nav_params);
202   accessibility->SendPendingAccessibilityEvents();
203   EXPECT_TRUE(sink_->GetUniqueMessageMatching(
204       AccessibilityHostMsg_Events::ID));
205 }
206
207 TEST_F(RendererAccessibilityTest, HideAccessibilityObject) {
208   // Test RendererAccessibility and make sure it sends the
209   // proper event to the browser when an object in the tree
210   // is hidden, but its children are not.
211   std::string html =
212       "<body>"
213       "  <div role='group' id='A'>"
214       "    <div role='group' id='B'>"
215       "      <div role='group' id='C' style='visibility:visible'>"
216       "      </div>"
217       "    </div>"
218       "  </div>"
219       "</body>";
220   LoadHTML(html.c_str());
221
222   scoped_ptr<TestRendererAccessibility> accessibility(
223       new TestRendererAccessibility(frame()));
224   accessibility->SendPendingAccessibilityEvents();
225   EXPECT_EQ(4, CountAccessibilityNodesSentToBrowser());
226
227   WebDocument document = view()->GetWebView()->mainFrame()->document();
228   WebAXObject root_obj = document.accessibilityObject();
229   WebAXObject node_a = root_obj.childAt(0);
230   WebAXObject node_b = node_a.childAt(0);
231   WebAXObject node_c = node_b.childAt(0);
232
233   // Hide node 'B' ('C' stays visible).
234   ExecuteJavaScript(
235       "document.getElementById('B').style.visibility = 'hidden';");
236   // Force layout now.
237   ExecuteJavaScript("document.getElementById('B').offsetLeft;");
238
239   // Send a childrenChanged on 'A'.
240   sink_->ClearMessages();
241   accessibility->HandleAXEvent(
242       node_a,
243       ui::AX_EVENT_CHILDREN_CHANGED);
244
245   accessibility->SendPendingAccessibilityEvents();
246   AccessibilityHostMsg_EventParams event;
247   GetLastAccEvent(&event);
248   ASSERT_EQ(2U, event.update.nodes.size());
249
250   // RendererAccessibility notices that 'C' is being reparented,
251   // so it clears the subtree rooted at 'A', then updates 'A' and then 'C'.
252   EXPECT_EQ(node_a.axID(), event.update.node_id_to_clear);
253   EXPECT_EQ(node_a.axID(), event.update.nodes[0].id);
254   EXPECT_EQ(node_c.axID(), event.update.nodes[1].id);
255   EXPECT_EQ(2, CountAccessibilityNodesSentToBrowser());
256 }
257
258 TEST_F(RendererAccessibilityTest, ShowAccessibilityObject) {
259   // Test RendererAccessibility and make sure it sends the
260   // proper event to the browser when an object in the tree
261   // is shown, causing its own already-visible children to be
262   // reparented to it.
263   std::string html =
264       "<body>"
265       "  <div role='group' id='A'>"
266       "    <div role='group' id='B' style='visibility:hidden'>"
267       "      <div role='group' id='C' style='visibility:visible'>"
268       "      </div>"
269       "    </div>"
270       "  </div>"
271       "</body>";
272   LoadHTML(html.c_str());
273
274   scoped_ptr<TestRendererAccessibility> accessibility(
275       new TestRendererAccessibility(frame()));
276   accessibility->SendPendingAccessibilityEvents();
277   EXPECT_EQ(3, CountAccessibilityNodesSentToBrowser());
278
279   // Show node 'B', then send a childrenChanged on 'A'.
280   ExecuteJavaScript(
281       "document.getElementById('B').style.visibility = 'visible';");
282   ExecuteJavaScript("document.getElementById('B').offsetLeft;");
283
284   sink_->ClearMessages();
285   WebDocument document = view()->GetWebView()->mainFrame()->document();
286   WebAXObject root_obj = document.accessibilityObject();
287   WebAXObject node_a = root_obj.childAt(0);
288   WebAXObject node_b = node_a.childAt(0);
289   WebAXObject node_c = node_b.childAt(0);
290
291   accessibility->HandleAXEvent(
292       node_a,
293       ui::AX_EVENT_CHILDREN_CHANGED);
294
295   accessibility->SendPendingAccessibilityEvents();
296   AccessibilityHostMsg_EventParams event;
297   GetLastAccEvent(&event);
298
299   ASSERT_EQ(3U, event.update.nodes.size());
300   EXPECT_EQ(node_a.axID(), event.update.node_id_to_clear);
301   EXPECT_EQ(node_a.axID(), event.update.nodes[0].id);
302   EXPECT_EQ(node_b.axID(), event.update.nodes[1].id);
303   EXPECT_EQ(node_c.axID(), event.update.nodes[2].id);
304   EXPECT_EQ(3, CountAccessibilityNodesSentToBrowser());
305 }
306
307 TEST_F(RendererAccessibilityTest, DetachAccessibilityObject) {
308   // Test RendererAccessibility and make sure it sends the
309   // proper event to the browser when an object in the tree
310   // is detached, but its children are not. This can happen when
311   // a layout occurs and an anonymous render block is no longer needed.
312   std::string html =
313       "<body aria-label='Body'>"
314       "<span>1</span><span style='display:block'>2</span>"
315       "</body>";
316   LoadHTML(html.c_str());
317
318   scoped_ptr<TestRendererAccessibility> accessibility(
319       new TestRendererAccessibility(frame()));
320   accessibility->SendPendingAccessibilityEvents();
321   EXPECT_EQ(7, CountAccessibilityNodesSentToBrowser());
322
323   // Initially, the accessibility tree looks like this:
324   //
325   //   Document
326   //   +--Body
327   //      +--Anonymous Block
328   //         +--Static Text "1"
329   //            +--Inline Text Box "1"
330   //      +--Static Text "2"
331   //         +--Inline Text Box "2"
332   WebDocument document = view()->GetWebView()->mainFrame()->document();
333   WebAXObject root_obj = document.accessibilityObject();
334   WebAXObject body = root_obj.childAt(0);
335   WebAXObject anonymous_block = body.childAt(0);
336   WebAXObject text_1 = anonymous_block.childAt(0);
337   WebAXObject text_2 = body.childAt(1);
338
339   // Change the display of the second 'span' back to inline, which causes the
340   // anonymous block to be destroyed.
341   ExecuteJavaScript(
342       "document.querySelectorAll('span')[1].style.display = 'inline';");
343   // Force layout now.
344   ExecuteJavaScript("document.body.offsetLeft;");
345
346   // Send a childrenChanged on the body.
347   sink_->ClearMessages();
348   accessibility->HandleAXEvent(
349       body,
350       ui::AX_EVENT_CHILDREN_CHANGED);
351
352   accessibility->SendPendingAccessibilityEvents();
353
354   // Afterwards, the accessibility tree looks like this:
355   //
356   //   Document
357   //   +--Body
358   //      +--Static Text "1"
359   //         +--Inline Text Box "1"
360   //      +--Static Text "2"
361   //         +--Inline Text Box "2"
362   //
363   // We just assert that there are now four nodes in the
364   // accessibility tree and that only three nodes needed
365   // to be updated (the body, the static text 1, and
366   // the static text 2).
367
368   AccessibilityHostMsg_EventParams event;
369   GetLastAccEvent(&event);
370   ASSERT_EQ(5U, event.update.nodes.size());
371
372   EXPECT_EQ(body.axID(), event.update.nodes[0].id);
373   EXPECT_EQ(text_1.axID(), event.update.nodes[1].id);
374   // The third event is to update text_2, but its id changes
375   // so we don't have a test expectation for it.
376 }
377
378 TEST_F(RendererAccessibilityTest, EventOnObjectNotInTree) {
379   // Test RendererAccessibility and make sure it doesn't send anything
380   // if we get a notification from Blink for an object that isn't in the
381   // tree, like the scroll area that's the parent of the main document,
382   // which we don't expose.
383   std::string html = "<body><input></body>";
384   LoadHTML(html.c_str());
385
386   scoped_ptr<TestRendererAccessibility> accessibility(
387       new TestRendererAccessibility(frame()));
388   accessibility->SendPendingAccessibilityEvents();
389   EXPECT_EQ(3, CountAccessibilityNodesSentToBrowser());
390
391   WebDocument document = view()->GetWebView()->mainFrame()->document();
392   WebAXObject root_obj = document.accessibilityObject();
393   WebAXObject scroll_area = root_obj.parentObject();
394   EXPECT_EQ(blink::WebAXRoleScrollArea, scroll_area.role());
395
396   // Try to fire a message on the scroll area, and assert that we just
397   // ignore it.
398   sink_->ClearMessages();
399   accessibility->HandleAXEvent(scroll_area,
400                                ui::AX_EVENT_VALUE_CHANGED);
401
402   accessibility->SendPendingAccessibilityEvents();
403
404   const IPC::Message* message =
405       sink_->GetUniqueMessageMatching(AccessibilityHostMsg_Events::ID);
406   ASSERT_TRUE(message);
407   Tuple2<std::vector<AccessibilityHostMsg_EventParams>, int> param;
408   AccessibilityHostMsg_Events::Read(message, &param);
409   ASSERT_EQ(0U, param.a.size());
410 }
411
412 }  // namespace content