1 // Copyright 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.
5 #include "base/files/file_path.h"
6 #include "base/path_service.h"
7 #include "base/strings/string_number_conversions.h"
8 #include "chrome/browser/extensions/api/automation_internal/automation_util.h"
9 #include "chrome/browser/extensions/chrome_extension_function.h"
10 #include "chrome/browser/extensions/extension_apitest.h"
11 #include "chrome/browser/ui/tabs/tab_strip_model.h"
12 #include "chrome/common/chrome_paths.h"
13 #include "chrome/common/chrome_switches.h"
14 #include "chrome/common/extensions/api/automation_internal.h"
15 #include "chrome/test/base/ui_test_utils.h"
16 #include "content/public/browser/ax_event_notification_details.h"
17 #include "content/public/browser/render_widget_host.h"
18 #include "content/public/browser/render_widget_host_view.h"
19 #include "content/public/browser/web_contents.h"
20 #include "extensions/test/extension_test_message_listener.h"
21 #include "net/dns/mock_host_resolver.h"
22 #include "net/test/embedded_test_server/embedded_test_server.h"
23 #include "testing/gtest/include/gtest/gtest.h"
24 #include "ui/accessibility/ax_node.h"
25 #include "ui/accessibility/ax_serializable_tree.h"
26 #include "ui/accessibility/ax_tree.h"
27 #include "ui/accessibility/ax_tree_serializer.h"
28 #include "ui/accessibility/tree_generator.h"
30 namespace extensions {
33 static const char kDomain[] = "a.com";
34 static const char kSitesDir[] = "automation/sites";
35 static const char kGotTree[] = "got_tree";
36 } // anonymous namespace
38 class AutomationApiTest : public ExtensionApiTest {
40 GURL GetURLForPath(const std::string& host, const std::string& path) {
41 std::string port = base::IntToString(embedded_test_server()->port());
42 GURL::Replacements replacements;
43 replacements.SetHostStr(host);
44 replacements.SetPortStr(port);
46 embedded_test_server()->GetURL(path).ReplaceComponents(replacements);
50 void StartEmbeddedTestServer() {
51 base::FilePath test_data;
52 ASSERT_TRUE(PathService::Get(chrome::DIR_TEST_DATA, &test_data));
53 embedded_test_server()->ServeFilesFromDirectory(
54 test_data.AppendASCII("extensions/api_test")
55 .AppendASCII(kSitesDir));
56 ASSERT_TRUE(ExtensionApiTest::StartEmbeddedTestServer());
57 host_resolver()->AddRule("*", embedded_test_server()->base_url().host());
61 virtual void SetUpInProcessBrowserTestFixture() override {
62 ExtensionApiTest::SetUpInProcessBrowserTestFixture();
66 IN_PROC_BROWSER_TEST_F(AutomationApiTest, TestRendererAccessibilityEnabled) {
67 StartEmbeddedTestServer();
68 const GURL url = GetURLForPath(kDomain, "/index.html");
69 ui_test_utils::NavigateToURL(browser(), url);
71 ASSERT_EQ(1, browser()->tab_strip_model()->count());
72 content::WebContents* const tab =
73 browser()->tab_strip_model()->GetWebContentsAt(0);
74 ASSERT_FALSE(tab->IsFullAccessibilityModeForTesting());
75 ASSERT_FALSE(tab->IsTreeOnlyAccessibilityModeForTesting());
77 base::FilePath extension_path =
78 test_data_dir_.AppendASCII("automation/tests/basic");
79 ExtensionTestMessageListener got_tree(kGotTree, false /* no reply */);
80 LoadExtension(extension_path);
81 ASSERT_TRUE(got_tree.WaitUntilSatisfied());
83 ASSERT_FALSE(tab->IsFullAccessibilityModeForTesting());
84 ASSERT_TRUE(tab->IsTreeOnlyAccessibilityModeForTesting());
87 IN_PROC_BROWSER_TEST_F(AutomationApiTest, SanityCheck) {
88 StartEmbeddedTestServer();
89 ASSERT_TRUE(RunExtensionSubtest("automation/tests/tabs", "sanity_check.html"))
93 IN_PROC_BROWSER_TEST_F(AutomationApiTest, Unit) {
94 ASSERT_TRUE(RunExtensionSubtest("automation/tests/unit", "unit.html"))
98 IN_PROC_BROWSER_TEST_F(AutomationApiTest, GetTreeByTabId) {
99 StartEmbeddedTestServer();
100 ASSERT_TRUE(RunExtensionSubtest("automation/tests/tabs", "tab_id.html"))
104 IN_PROC_BROWSER_TEST_F(AutomationApiTest, Events) {
105 StartEmbeddedTestServer();
106 ASSERT_TRUE(RunExtensionSubtest("automation/tests/tabs", "events.html"))
110 IN_PROC_BROWSER_TEST_F(AutomationApiTest, Actions) {
111 StartEmbeddedTestServer();
112 ASSERT_TRUE(RunExtensionSubtest("automation/tests/tabs", "actions.html"))
116 IN_PROC_BROWSER_TEST_F(AutomationApiTest, Location) {
117 StartEmbeddedTestServer();
118 ASSERT_TRUE(RunExtensionSubtest("automation/tests/tabs", "location.html"))
122 IN_PROC_BROWSER_TEST_F(AutomationApiTest, TabsAutomationBooleanPermissions) {
123 StartEmbeddedTestServer();
124 ASSERT_TRUE(RunExtensionSubtest(
125 "automation/tests/tabs_automation_boolean", "permissions.html"))
129 IN_PROC_BROWSER_TEST_F(AutomationApiTest, TabsAutomationBooleanActions) {
130 StartEmbeddedTestServer();
131 ASSERT_TRUE(RunExtensionSubtest(
132 "automation/tests/tabs_automation_boolean", "actions.html"))
136 IN_PROC_BROWSER_TEST_F(AutomationApiTest, TabsAutomationHostsPermissions) {
137 StartEmbeddedTestServer();
138 ASSERT_TRUE(RunExtensionSubtest(
139 "automation/tests/tabs_automation_hosts", "permissions.html"))
143 #if defined(OS_CHROMEOS)
144 IN_PROC_BROWSER_TEST_F(AutomationApiTest, Desktop) {
145 ASSERT_TRUE(RunExtensionSubtest("automation/tests/desktop", "desktop.html"))
149 IN_PROC_BROWSER_TEST_F(AutomationApiTest, DesktopNotRequested) {
150 ASSERT_TRUE(RunExtensionSubtest("automation/tests/tabs",
151 "desktop_not_requested.html")) << message_;
154 IN_PROC_BROWSER_TEST_F(AutomationApiTest, DesktopActions) {
155 ASSERT_TRUE(RunExtensionSubtest("automation/tests/desktop", "actions.html"))
159 IN_PROC_BROWSER_TEST_F(AutomationApiTest, DesktopLoadTabs) {
160 ASSERT_TRUE(RunExtensionSubtest("automation/tests/desktop", "load_tabs.html"))
164 IN_PROC_BROWSER_TEST_F(AutomationApiTest, DesktopNotSupported) {
165 ASSERT_TRUE(RunExtensionSubtest("automation/tests/desktop",
166 "desktop_not_supported.html")) << message_;
170 IN_PROC_BROWSER_TEST_F(AutomationApiTest, CloseTab) {
171 StartEmbeddedTestServer();
172 ASSERT_TRUE(RunExtensionSubtest("automation/tests/tabs", "close_tab.html"))
176 IN_PROC_BROWSER_TEST_F(AutomationApiTest, QuerySelector) {
177 StartEmbeddedTestServer();
179 RunExtensionSubtest("automation/tests/tabs", "queryselector.html"))
183 static const int kPid = 1;
184 static const int kTab0Rid = 1;
185 static const int kTab1Rid = 2;
187 using content::BrowserContext;
189 typedef ui::AXTreeSerializer<const ui::AXNode*> TreeSerializer;
190 typedef ui::AXTreeSource<const ui::AXNode*> TreeSource;
192 #define AX_EVENT_ASSERT_EQUAL ui::AX_EVENT_LOAD_COMPLETE
193 #define AX_EVENT_ASSERT_NOT_EQUAL ui::AX_EVENT_ACTIVEDESCENDANTCHANGED
194 #define AX_EVENT_IGNORE ui::AX_EVENT_CHILDREN_CHANGED
195 #define AX_EVENT_TEST_COMPLETE ui::AX_EVENT_BLUR
197 // This test is based on ui/accessibility/ax_generated_tree_unittest.cc
198 // However, because the tree updates need to be sent to the extension, we can't
199 // use a straightforward set of nested loops as that test does, so this class
200 // keeps track of where we're up to in our imaginary loops, while the extension
201 // function classes below do the work of actually incrementing the state when
203 // The actual deserialization and comparison happens in the API bindings and the
204 // test extension respectively: see
205 // c/t/data/extensions/api_test/automation/tests/generated/generated_trees.js
206 class TreeSerializationState {
208 TreeSerializationState()
214 generator(tree_size),
215 num_trees(generator.UniqueTreeCount()),
220 // Serializes tree and sends it as an accessibility event to the extension.
221 void SendDataForTree(const ui::AXTree* tree,
222 TreeSerializer* serializer,
224 BrowserContext* browser_context) {
225 ui::AXTreeUpdate update;
226 serializer->SerializeChanges(tree->GetRoot(), &update);
228 ui::AX_EVENT_LAYOUT_COMPLETE,
229 tree->GetRoot()->id(),
234 // Sends the given AXTreeUpdate to the extension as an accessibility event.
235 void SendUpdate(ui::AXTreeUpdate update,
239 BrowserContext* browser_context) {
240 content::AXEventNotificationDetails detail(update.node_id_to_clear,
246 std::vector<content::AXEventNotificationDetails> details;
247 details.push_back(detail);
248 automation_util::DispatchAccessibilityEventsToAutomation(
249 details, browser_context, gfx::Vector2d());
252 // Notify the extension bindings to destroy the tree for the given tab
253 // (identified by routing_id)
254 void SendTreeDestroyedEvent(int routing_id, BrowserContext* browser_context) {
255 automation_util::DispatchTreeDestroyedEventToAutomation(
256 kPid, routing_id, browser_context);
259 // Reset tree0 to a new generated tree based on tree0_version, reset
260 // tree0_source accordingly.
262 tree0.reset(new ui::AXSerializableTree);
263 tree0_source.reset(tree0->CreateTreeSource());
264 generator.BuildUniqueTree(tree0_version, tree0.get());
265 if (!serializer0.get())
266 serializer0.reset(new TreeSerializer(tree0_source.get()));
269 // Reset tree0, set up serializer0, send down the initial tree data to create
270 // the tree in the extension
271 void InitializeTree0(BrowserContext* browser_context) {
273 serializer0->ChangeTreeSourceForTesting(tree0_source.get());
274 serializer0->Reset();
275 SendDataForTree(tree0.get(), serializer0.get(), kTab0Rid, browser_context);
278 // Reset tree1 to a new generated tree based on tree1_version, reset
279 // tree1_source accordingly.
281 tree1.reset(new ui::AXSerializableTree);
282 tree1_source.reset(tree1->CreateTreeSource());
283 generator.BuildUniqueTree(tree1_version, tree1.get());
284 if (!serializer1.get())
285 serializer1.reset(new TreeSerializer(tree1_source.get()));
288 // Reset tree1, set up serializer1, send down the initial tree data to create
289 // the tree in the extension
290 void InitializeTree1(BrowserContext* browser_context) {
292 serializer1->ChangeTreeSourceForTesting(tree1_source.get());
293 serializer1->Reset();
294 SendDataForTree(tree1.get(), serializer1.get(), kTab1Rid, browser_context);
298 const ui::TreeGenerator generator;
300 // The loop variables: comments indicate which variables in
301 // ax_generated_tree_unittest they correspond to.
302 const int num_trees; // n
303 int tree0_version; // i
304 int tree1_version; // j
305 int starting_node; // k
307 // Tree infrastructure; tree0 and tree1 need to be regenerated whenever
308 // tree0_version and tree1_version change, respectively; tree0_source and
309 // tree1_source need to be reset whenever that happens.
310 scoped_ptr<ui::AXSerializableTree> tree0, tree1;
311 scoped_ptr<TreeSource> tree0_source, tree1_source;
312 scoped_ptr<TreeSerializer> serializer0, serializer1;
314 // Whether tree0 needs to be destroyed after the extension has performed its
319 static TreeSerializationState state;
321 // Override for chrome.automationInternal.enableTab
322 // This fakes out the process and routing IDs for two "tabs", which contain the
323 // source and target trees, respectively, and sends down the current tree for
324 // the requested tab - tab 1 always has tree1, and tab 0 starts with tree0
325 // and then has a series of updates intended to translate tree0 to tree1.
326 // Once all the updates have been sent, the extension asserts that both trees
327 // are equivalent, and then one or both of the trees are reset to a new version.
328 class FakeAutomationInternalEnableTabFunction
329 : public UIThreadExtensionFunction {
331 FakeAutomationInternalEnableTabFunction() {}
333 ExtensionFunction::ResponseAction Run() override {
334 using api::automation_internal::EnableTab::Params;
335 scoped_ptr<Params> params(Params::Create(*args_));
336 EXTENSION_FUNCTION_VALIDATE(params.get());
337 if (!params->tab_id.get())
338 return RespondNow(Error("tab_id not specified"));
339 int tab_id = *params->tab_id;
342 base::MessageLoop::current()->PostTask(
344 base::Bind(&TreeSerializationState::InitializeTree0,
345 base::Unretained(&state),
346 base::Unretained(browser_context())));
347 // TODO(aboxhall): Need to rewrite this test in terms of tree ids.
348 return RespondNow(ArgumentList(
349 api::automation_internal::EnableTab::Results::Create(0)));
353 base::MessageLoop::current()->PostTask(
355 base::Bind(&TreeSerializationState::InitializeTree1,
356 base::Unretained(&state),
357 base::Unretained(browser_context())));
358 return RespondNow(ArgumentList(
359 api::automation_internal::EnableTab::Results::Create(0)));
361 return RespondNow(Error("Unrecognised tab_id"));
365 // Factory method for use in OverrideFunction()
366 ExtensionFunction* FakeAutomationInternalEnableTabFunctionFactory() {
367 return new FakeAutomationInternalEnableTabFunction();
370 // Helper method to serialize a series of updates via source_serializer to
371 // transform the tree which source_serializer was initialized from into
372 // target_tree, and then trigger the test code to assert the two tabs contain
374 void TransformTree(TreeSerializer* source_serializer,
375 ui::AXTree* target_tree,
376 TreeSource* target_tree_source,
377 content::BrowserContext* browser_context) {
378 source_serializer->ChangeTreeSourceForTesting(target_tree_source);
379 for (int node_delta = 0; node_delta < state.tree_size; ++node_delta) {
380 int id = 1 + (state.starting_node + node_delta) % state.tree_size;
381 ui::AXTreeUpdate update;
382 source_serializer->SerializeChanges(target_tree->GetFromId(id), &update);
383 bool is_last_update = node_delta == state.tree_size - 1;
385 is_last_update ? AX_EVENT_ASSERT_EQUAL : AX_EVENT_IGNORE;
387 update, event, target_tree->GetRoot()->id(), kTab0Rid, browser_context);
391 // Helper method to send a no-op tree update to tab 0 with the given event.
392 void SendEvent(ui::AXEvent event, content::BrowserContext* browser_context) {
393 ui::AXTreeUpdate update;
394 ui::AXNode* root = state.tree0->GetRoot();
395 state.serializer0->SerializeChanges(root, &update);
396 state.SendUpdate(update, event, root->id(), kTab0Rid, browser_context);
399 // Override for chrome.automationInternal.performAction
400 // This is used as a synchronization mechanism; the general flow is:
401 // 1. The extension requests tree0 and tree1 (on tab 0 and tab 1 respectively)
402 // 2. FakeAutomationInternalEnableTabFunction sends down the trees
403 // 3. When the callback for getTree(0) fires, the extension calls doDefault() on
404 // the root node of tree0, which calls into this class's Run() method.
405 // 4. In the normal case, we're in the "inner loop" (iterating over
406 // starting_node). For each value of starting_node, we do the following:
407 // a. Serialize a sequence of updates which should transform tree0 into
408 // tree1. Each of these updates is sent as a childrenChanged event,
409 // except for the last which is sent as a loadComplete event.
410 // b. state.destroy_tree0 is set to true
411 // c. state.starting_node gets incremented
412 // d. The loadComplete event triggers an assertion in the extension.
413 // e. The extension performs another doDefault() on the root node of the
415 // f. This time, we send a destroy event to tab0, so that the tree can be
417 // g. The extension is notified of the tree's destruction and requests the
418 // tree for tab 0 again, returning to step 2.
419 // 5. When starting_node exceeds state.tree_size, we increment tree0_version if
420 // it would not exceed state.num_trees, or increment tree1_version and reset
421 // tree0_version to 0 otherwise, and reset starting_node to 0.
422 // Then we reset one or both trees as appropriate, and send down destroyed
423 // events similarly, causing the extension to re-request the tree and going
424 // back to step 2 again.
425 // 6. When tree1_version has gone through all possible values, we send a blur
426 // event, signaling the extension to call chrome.test.succeed() and finish
428 class FakeAutomationInternalPerformActionFunction
429 : public UIThreadExtensionFunction {
431 FakeAutomationInternalPerformActionFunction() {}
433 ExtensionFunction::ResponseAction Run() override {
434 if (state.destroy_tree0) {
435 // Step 4.f: tell the extension to destroy the tree and re-request it.
436 state.SendTreeDestroyedEvent(kTab0Rid, browser_context());
437 state.destroy_tree0 = false;
438 return RespondNow(NoArguments());
441 TreeSerializer* serializer0 = state.serializer0.get();
442 if (state.starting_node < state.tree_size) {
443 // As a sanity check, if the trees are not equal, assert that they are not
444 // equal before serializing changes.
445 if (state.tree0_version != state.tree1_version)
446 SendEvent(AX_EVENT_ASSERT_NOT_EQUAL, browser_context());
448 // Step 4.a: pretend that tree0 turned into tree1, and serialize
449 // a sequence of updates to tab 0 to match.
450 TransformTree(serializer0,
452 state.tree1_source.get(),
455 // Step 4.b: remember that we need to tell the extension to destroy and
456 // re-request the tree on the next action.
457 state.destroy_tree0 = true;
459 // Step 4.c: increment starting_node.
460 state.starting_node++;
461 } else if (state.tree0_version < state.num_trees - 1) {
462 // Step 5: Increment tree0_version and reset starting_node
463 state.tree0_version++;
464 state.starting_node = 0;
466 // Step 5: Reset tree0 and tell the extension to destroy and re-request it
467 state.SendTreeDestroyedEvent(kTab0Rid, browser_context());
468 } else if (state.tree1_version < state.num_trees - 1) {
469 // Step 5: Increment tree1_version and reset tree0_version and
471 state.tree1_version++;
472 state.tree0_version = 0;
473 state.starting_node = 0;
475 // Step 5: Reset tree0 and tell the extension to destroy and re-request it
476 state.SendTreeDestroyedEvent(kTab0Rid, browser_context());
478 // Step 5: Reset tree1 and tell the extension to destroy and re-request it
479 state.SendTreeDestroyedEvent(kTab1Rid, browser_context());
481 // Step 6: Send a TEST_COMPLETE (blur) event to signal the extension to
482 // call chrome.test.succeed().
483 SendEvent(AX_EVENT_TEST_COMPLETE, browser_context());
486 return RespondNow(NoArguments());
490 // Factory method for use in OverrideFunction()
491 ExtensionFunction* FakeAutomationInternalPerformActionFunctionFactory() {
492 return new FakeAutomationInternalPerformActionFunction();
495 // http://crbug.com/396353
496 IN_PROC_BROWSER_TEST_F(AutomationApiTest, DISABLED_GeneratedTree) {
497 ASSERT_TRUE(extensions::ExtensionFunctionDispatcher::OverrideFunction(
498 "automationInternal.enableTab",
499 FakeAutomationInternalEnableTabFunctionFactory));
500 ASSERT_TRUE(extensions::ExtensionFunctionDispatcher::OverrideFunction(
501 "automationInternal.performAction",
502 FakeAutomationInternalPerformActionFunctionFactory));
503 ASSERT_TRUE(RunExtensionSubtest("automation/tests/generated",
504 "generated_trees.html")) << message_;
507 } // namespace extensions