Updating trunk VERSION from 2139.0 to 2140.0
[chromium-blink-merge.git] / content / renderer / accessibility / renderer_accessibility_browsertest.cc
blob6bb5c7936da38abd101fb1d1edf16313d49780e8
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.
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"
20 using blink::WebAXObject;
21 using blink::WebDocument;
23 namespace content {
25 class TestRendererAccessibilityComplete : public RendererAccessibilityComplete {
26 public:
27 explicit TestRendererAccessibilityComplete(RenderFrameImpl* render_frame)
28 : RendererAccessibilityComplete(render_frame) {
31 void SendPendingAccessibilityEvents() {
32 RendererAccessibilityComplete::SendPendingAccessibilityEvents();
36 class RendererAccessibilityTest : public RenderViewTest {
37 public:
38 RendererAccessibilityTest() {}
40 RenderViewImpl* view() {
41 return static_cast<RenderViewImpl*>(view_);
44 RenderFrameImpl* frame() {
45 return static_cast<RenderFrameImpl*>(view()->GetMainRenderFrame());
48 virtual void SetUp() {
49 RenderViewTest::SetUp();
50 sink_ = &render_thread_->sink();
53 void SetMode(AccessibilityMode mode) {
54 frame()->OnSetAccessibilityMode(mode);
57 void GetLastAccEvent(
58 AccessibilityHostMsg_EventParams* params) {
59 const IPC::Message* message =
60 sink_->GetUniqueMessageMatching(AccessibilityHostMsg_Events::ID);
61 ASSERT_TRUE(message);
62 Tuple1<std::vector<AccessibilityHostMsg_EventParams> > param;
63 AccessibilityHostMsg_Events::Read(message, &param);
64 ASSERT_GE(param.a.size(), 1U);
65 *params = param.a[0];
68 int CountAccessibilityNodesSentToBrowser() {
69 AccessibilityHostMsg_EventParams event;
70 GetLastAccEvent(&event);
71 return event.update.nodes.size();
74 protected:
75 IPC::TestSink* sink_;
77 DISALLOW_COPY_AND_ASSIGN(RendererAccessibilityTest);
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);
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);
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>";
102 // Load the test page.
103 LoadHTML(html.c_str());
105 // We should have sent a message to the browser with the initial focus
106 // on the document.
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);
125 // Now focus the input element, and check everything again.
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));
150 // Check other editable text nodes.
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));
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));
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));
187 // Try focusing things that aren't editable text.
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));
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));
214 // Clear focus.
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);
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());
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());
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());
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);
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());
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());
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
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;
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());
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()->main_render_frame()->OnSwapOut(kProxyRoutingId);
328 accessibility->SendPendingAccessibilityEvents();
329 EXPECT_FALSE(sink_->GetUniqueMessageMatching(
330 AccessibilityHostMsg_Events::ID));
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 = 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));
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());
367 scoped_ptr<TestRendererAccessibilityComplete> accessibility(
368 new TestRendererAccessibilityComplete(frame()));
369 accessibility->SendPendingAccessibilityEvents();
370 EXPECT_EQ(4, CountAccessibilityNodesSentToBrowser());
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);
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;");
384 // Send a childrenChanged on 'A'.
385 sink_->ClearMessages();
386 accessibility->HandleAXEvent(
387 node_a,
388 ui::AX_EVENT_CHILDREN_CHANGED);
390 accessibility->SendPendingAccessibilityEvents();
391 AccessibilityHostMsg_EventParams event;
392 GetLastAccEvent(&event);
393 ASSERT_EQ(2U, event.update.nodes.size());
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());
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());
419 scoped_ptr<TestRendererAccessibilityComplete> accessibility(
420 new TestRendererAccessibilityComplete(frame()));
421 accessibility->SendPendingAccessibilityEvents();
422 EXPECT_EQ(3, CountAccessibilityNodesSentToBrowser());
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;");
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);
436 accessibility->HandleAXEvent(
437 node_a,
438 ui::AX_EVENT_CHILDREN_CHANGED);
440 accessibility->SendPendingAccessibilityEvents();
441 AccessibilityHostMsg_EventParams event;
442 GetLastAccEvent(&event);
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());
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());
463 scoped_ptr<TestRendererAccessibilityComplete> accessibility(
464 new TestRendererAccessibilityComplete(frame()));
465 accessibility->SendPendingAccessibilityEvents();
466 EXPECT_EQ(7, CountAccessibilityNodesSentToBrowser());
468 // Initially, the accessibility tree looks like this:
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);
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;");
491 // Send a childrenChanged on the body.
492 sink_->ClearMessages();
493 accessibility->HandleAXEvent(
494 body,
495 ui::AX_EVENT_CHILDREN_CHANGED);
497 accessibility->SendPendingAccessibilityEvents();
499 // Afterwards, the accessibility tree looks like this:
501 // Document
502 // +--Body
503 // +--Static Text "1"
504 // +--Inline Text Box "1"
505 // +--Static Text "2"
506 // +--Inline Text Box "2"
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).
513 AccessibilityHostMsg_EventParams event;
514 GetLastAccEvent(&event);
515 ASSERT_EQ(5U, event.update.nodes.size());
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.
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());
531 scoped_ptr<TestRendererAccessibilityComplete> accessibility(
532 new TestRendererAccessibilityComplete(frame()));
533 accessibility->SendPendingAccessibilityEvents();
534 EXPECT_EQ(3, CountAccessibilityNodesSentToBrowser());
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());
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);
547 accessibility->SendPendingAccessibilityEvents();
549 const IPC::Message* message =
550 sink_->GetUniqueMessageMatching(AccessibilityHostMsg_Events::ID);
551 ASSERT_TRUE(message);
552 Tuple1<std::vector<AccessibilityHostMsg_EventParams> > param;
553 AccessibilityHostMsg_Events::Read(message, &param);
554 ASSERT_EQ(0U, param.a.size());
557 } // namespace content