1// Copyright (c) 2014 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 "content/browser/frame_host/render_frame_host_impl.h"
6#include "content/browser/web_contents/web_contents_impl.h"
7#include "content/common/accessibility_messages.h"
8#include "content/public/test/browser_test_utils.h"
9#include "content/public/test/content_browser_test.h"
10#include "content/public/test/content_browser_test_utils.h"
11#include "content/shell/browser/shell.h"
12#include "content/test/accessibility_browser_test_utils.h"
13#include "ui/accessibility/ax_node.h"
14#include "ui/accessibility/ax_tree.h"
15
16namespace content {
17
18class AccessibilityIpcErrorBrowserTest : public ContentBrowserTest {
19 public:
20  AccessibilityIpcErrorBrowserTest() {}
21
22 protected:
23  // Convenience method to get the value of a particular AXNode
24  // attribute as a UTF-8 string.
25  std::string GetAttr(const ui::AXNode* node,
26                      const ui::AXStringAttribute attr) {
27    const ui::AXNodeData& data = node->data();
28    for (size_t i = 0; i < data.string_attributes.size(); ++i) {
29      if (data.string_attributes[i].first == attr)
30        return data.string_attributes[i].second;
31    }
32    return std::string();
33  }
34
35  DISALLOW_COPY_AND_ASSIGN(AccessibilityIpcErrorBrowserTest);
36};
37
38IN_PROC_BROWSER_TEST_F(AccessibilityIpcErrorBrowserTest,
39                       ResetBrowserAccessibilityManager) {
40  // Create a data url and load it.
41  const char url_str[] =
42      "data:text/html,"
43      "<div aria-live='polite'>"
44      "  <p id='p1'>Paragraph One</p>"
45      "  <p id='p2'>Paragraph Two</p>"
46      "</div>"
47      "<button id='button'>Button</button>";
48  GURL url(url_str);
49  NavigateToURL(shell(), url);
50
51  // Simulate a condition where the RFH can't create a
52  // BrowserAccessibilityManager - like if there's no view.
53  RenderFrameHostImpl* frame = static_cast<RenderFrameHostImpl*>(
54      shell()->web_contents()->GetMainFrame());
55  frame->set_disallow_browser_accessibility_manager_for_testing(true);
56  ASSERT_EQ(nullptr, frame->GetOrCreateBrowserAccessibilityManager());
57
58  {
59    // Enable accessibility (passing AccessibilityModeComplete to
60    // AccessibilityNotificationWaiter does this automatically) and wait for
61    // the first event.
62    AccessibilityNotificationWaiter waiter(
63        shell(), AccessibilityModeComplete, ui::AX_EVENT_LAYOUT_COMPLETE);
64    waiter.WaitForNotification();
65  }
66
67  // Make sure we still didn't create a BrowserAccessibilityManager.
68  // This means that at least one accessibility IPC was lost.
69  ASSERT_EQ(nullptr, frame->GetOrCreateBrowserAccessibilityManager());
70
71  // Now allow a BrowserAccessibilityManager, simulating what would happen
72  // if the RFH's view is created now.
73  frame->set_disallow_browser_accessibility_manager_for_testing(false);
74  ASSERT_TRUE(frame->GetOrCreateBrowserAccessibilityManager() != nullptr);
75
76  {
77    // Hide one of the elements on the page, and wait for an accessibility
78    // notification triggered by the hide.
79    AccessibilityNotificationWaiter waiter(
80        shell(), AccessibilityModeComplete, ui::AX_EVENT_LIVE_REGION_CHANGED);
81    ASSERT_TRUE(ExecuteScript(
82        shell()->web_contents(),
83        "document.getElementById('p1').style.display = 'none';"));
84    waiter.WaitForNotification();
85  }
86
87  // Show that accessibility was reset because the frame doesn't have a
88  // BrowserAccessibilityManager anymore.
89  ASSERT_EQ(nullptr, frame->browser_accessibility_manager());
90  const ui::AXTree* tree = nullptr;
91  {
92    // Ensure that we didn't kill the renderer; we can still send it messages.
93    AccessibilityNotificationWaiter waiter(
94        shell(), AccessibilityModeComplete, ui::AX_EVENT_FOCUS);
95    ASSERT_TRUE(ExecuteScript(
96        shell()->web_contents(),
97        "document.getElementById('button').focus();"));
98    waiter.WaitForNotification();
99    tree = &waiter.GetAXTree();
100  }
101
102  // Get the accessibility tree, ensure it reflects the final state of the
103  // document.
104  const ui::AXNode* root = tree->GetRoot();
105
106  // Use this for debugging if the test fails.
107  VLOG(1) << tree->ToString();
108
109  EXPECT_EQ(ui::AX_ROLE_ROOT_WEB_AREA, root->data().role);
110  ASSERT_EQ(2, root->child_count());
111
112  const ui::AXNode* live_region = root->ChildAtIndex(0);
113  ASSERT_EQ(1, live_region->child_count());
114  EXPECT_EQ(ui::AX_ROLE_DIV, live_region->data().role);
115
116  const ui::AXNode* para = live_region->ChildAtIndex(0);
117  EXPECT_EQ(ui::AX_ROLE_PARAGRAPH, para->data().role);
118
119  const ui::AXNode* button_container = root->ChildAtIndex(1);
120  EXPECT_EQ(ui::AX_ROLE_GROUP, button_container->data().role);
121  ASSERT_EQ(1, button_container->child_count());
122
123  const ui::AXNode* button = button_container->ChildAtIndex(0);
124  EXPECT_EQ(ui::AX_ROLE_BUTTON, button->data().role);
125  EXPECT_TRUE(button->data().state >> ui::AX_STATE_FOCUSED & 1);
126}
127
128IN_PROC_BROWSER_TEST_F(AccessibilityIpcErrorBrowserTest,
129                       MultipleBadAccessibilityIPCsKillsRenderer) {
130  // Create a data url and load it.
131  const char url_str[] =
132      "data:text/html,"
133      "<button id='button'>Button</button>";
134  GURL url(url_str);
135  NavigateToURL(shell(), url);
136  RenderFrameHostImpl* frame = static_cast<RenderFrameHostImpl*>(
137      shell()->web_contents()->GetMainFrame());
138
139  {
140    // Enable accessibility (passing AccessibilityModeComplete to
141    // AccessibilityNotificationWaiter does this automatically) and wait for
142    // the first event.
143    AccessibilityNotificationWaiter waiter(
144        shell(), AccessibilityModeComplete, ui::AX_EVENT_LAYOUT_COMPLETE);
145    waiter.WaitForNotification();
146  }
147
148  // Construct a bad accessibility message that BrowserAccessibilityManager
149  // will reject.
150  std::vector<AccessibilityHostMsg_EventParams> bad_accessibility_event_list;
151  bad_accessibility_event_list.push_back(AccessibilityHostMsg_EventParams());
152  bad_accessibility_event_list[0].update.node_id_to_clear = -2;
153
154  // We should be able to reset accessibility |max_iterations-1| times
155  // (see render_frame_host_impl.cc - kMaxAccessibilityResets),
156  // but the subsequent time the renderer should be killed.
157  int max_iterations = RenderFrameHostImpl::kMaxAccessibilityResets;
158
159  for (int iteration = 0; iteration < max_iterations; iteration++) {
160    // Send the browser accessibility the bad message.
161    BrowserAccessibilityManager* manager =
162        frame->GetOrCreateBrowserAccessibilityManager();
163    manager->OnAccessibilityEvents(bad_accessibility_event_list);
164
165    // Now the frame should have deleted the BrowserAccessibilityManager.
166    ASSERT_EQ(nullptr, frame->browser_accessibility_manager());
167
168    if (iteration == max_iterations - 1)
169      break;
170
171    AccessibilityNotificationWaiter waiter(
172        shell(), AccessibilityModeComplete, ui::AX_EVENT_LAYOUT_COMPLETE);
173    waiter.WaitForNotification();
174  }
175
176  // Wait for the renderer to be killed.
177  RenderProcessHostWatcher render_process_watcher(
178      frame->GetProcess(), RenderProcessHostWatcher::WATCH_FOR_PROCESS_EXIT);
179  render_process_watcher.Wait();
180  ASSERT_FALSE(frame->IsRenderFrameLive());
181}
182
183}  // namespace content
184