Add ENABLE_MEDIA_ROUTER define to builds other than Android and iOS.
[chromium-blink-merge.git] / chrome / browser / extensions / api / automation / automation_apitest.cc
blobaf8b507313a34f8972c6ab0bea47b6ff646c85ed
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 #if defined(OS_CHROMEOS)
31 #include "ash/accelerators/accelerator_controller.h"
32 #include "ash/shell.h"
33 #include "chrome/browser/ui/aura/accessibility/automation_manager_aura.h"
34 #endif
36 namespace extensions {
38 namespace {
39 static const char kDomain[] = "a.com";
40 static const char kSitesDir[] = "automation/sites";
41 static const char kGotTree[] = "got_tree";
42 } // anonymous namespace
44 class AutomationApiTest : public ExtensionApiTest {
45 protected:
46 GURL GetURLForPath(const std::string& host, const std::string& path) {
47 std::string port = base::IntToString(embedded_test_server()->port());
48 GURL::Replacements replacements;
49 replacements.SetHostStr(host);
50 replacements.SetPortStr(port);
51 GURL url =
52 embedded_test_server()->GetURL(path).ReplaceComponents(replacements);
53 return url;
56 void StartEmbeddedTestServer() {
57 base::FilePath test_data;
58 ASSERT_TRUE(PathService::Get(chrome::DIR_TEST_DATA, &test_data));
59 embedded_test_server()->ServeFilesFromDirectory(
60 test_data.AppendASCII("extensions/api_test")
61 .AppendASCII(kSitesDir));
62 ASSERT_TRUE(ExtensionApiTest::StartEmbeddedTestServer());
63 host_resolver()->AddRule("*", embedded_test_server()->base_url().host());
66 public:
67 virtual void SetUpInProcessBrowserTestFixture() override {
68 ExtensionApiTest::SetUpInProcessBrowserTestFixture();
72 IN_PROC_BROWSER_TEST_F(AutomationApiTest, TestRendererAccessibilityEnabled) {
73 StartEmbeddedTestServer();
74 const GURL url = GetURLForPath(kDomain, "/index.html");
75 ui_test_utils::NavigateToURL(browser(), url);
77 ASSERT_EQ(1, browser()->tab_strip_model()->count());
78 content::WebContents* const tab =
79 browser()->tab_strip_model()->GetWebContentsAt(0);
80 ASSERT_FALSE(tab->IsFullAccessibilityModeForTesting());
81 ASSERT_FALSE(tab->IsTreeOnlyAccessibilityModeForTesting());
83 base::FilePath extension_path =
84 test_data_dir_.AppendASCII("automation/tests/basic");
85 ExtensionTestMessageListener got_tree(kGotTree, false /* no reply */);
86 LoadExtension(extension_path);
87 ASSERT_TRUE(got_tree.WaitUntilSatisfied());
89 ASSERT_FALSE(tab->IsFullAccessibilityModeForTesting());
90 ASSERT_TRUE(tab->IsTreeOnlyAccessibilityModeForTesting());
93 IN_PROC_BROWSER_TEST_F(AutomationApiTest, SanityCheck) {
94 StartEmbeddedTestServer();
95 ASSERT_TRUE(RunExtensionSubtest("automation/tests/tabs", "sanity_check.html"))
96 << message_;
99 IN_PROC_BROWSER_TEST_F(AutomationApiTest, Unit) {
100 ASSERT_TRUE(RunExtensionSubtest("automation/tests/unit", "unit.html"))
101 << message_;
104 IN_PROC_BROWSER_TEST_F(AutomationApiTest, GetTreeByTabId) {
105 StartEmbeddedTestServer();
106 ASSERT_TRUE(RunExtensionSubtest("automation/tests/tabs", "tab_id.html"))
107 << message_;
110 IN_PROC_BROWSER_TEST_F(AutomationApiTest, Events) {
111 StartEmbeddedTestServer();
112 ASSERT_TRUE(RunExtensionSubtest("automation/tests/tabs", "events.html"))
113 << message_;
116 IN_PROC_BROWSER_TEST_F(AutomationApiTest, Actions) {
117 StartEmbeddedTestServer();
118 ASSERT_TRUE(RunExtensionSubtest("automation/tests/tabs", "actions.html"))
119 << message_;
122 IN_PROC_BROWSER_TEST_F(AutomationApiTest, Location) {
123 StartEmbeddedTestServer();
124 ASSERT_TRUE(RunExtensionSubtest("automation/tests/tabs", "location.html"))
125 << message_;
128 IN_PROC_BROWSER_TEST_F(AutomationApiTest, TabsAutomationBooleanPermissions) {
129 StartEmbeddedTestServer();
130 ASSERT_TRUE(RunExtensionSubtest(
131 "automation/tests/tabs_automation_boolean", "permissions.html"))
132 << message_;
135 IN_PROC_BROWSER_TEST_F(AutomationApiTest, TabsAutomationBooleanActions) {
136 StartEmbeddedTestServer();
137 ASSERT_TRUE(RunExtensionSubtest(
138 "automation/tests/tabs_automation_boolean", "actions.html"))
139 << message_;
142 IN_PROC_BROWSER_TEST_F(AutomationApiTest, TabsAutomationHostsPermissions) {
143 StartEmbeddedTestServer();
144 ASSERT_TRUE(RunExtensionSubtest(
145 "automation/tests/tabs_automation_hosts", "permissions.html"))
146 << message_;
149 #if defined(USE_AURA)
150 IN_PROC_BROWSER_TEST_F(AutomationApiTest, Desktop) {
151 ASSERT_TRUE(RunExtensionSubtest("automation/tests/desktop", "desktop.html"))
152 << message_;
155 IN_PROC_BROWSER_TEST_F(AutomationApiTest, DesktopNotRequested) {
156 ASSERT_TRUE(RunExtensionSubtest("automation/tests/tabs",
157 "desktop_not_requested.html")) << message_;
160 #if defined(OS_CHROMEOS)
161 IN_PROC_BROWSER_TEST_F(AutomationApiTest, DesktopActions) {
162 AutomationManagerAura::GetInstance()->Enable(browser()->profile());
163 // Trigger the shelf subtree to be computed.
164 ash::Shell::GetInstance()->accelerator_controller()->PerformActionIfEnabled(
165 ash::FOCUS_SHELF);
167 ASSERT_TRUE(RunExtensionSubtest("automation/tests/desktop", "actions.html"))
168 << message_;
171 IN_PROC_BROWSER_TEST_F(AutomationApiTest, DesktopLoadTabs) {
172 ASSERT_TRUE(RunExtensionSubtest("automation/tests/desktop", "load_tabs.html"))
173 << message_;
175 #endif // defined(OS_CHROMEOS)
176 #else // !defined(USE_AURA)
177 IN_PROC_BROWSER_TEST_F(AutomationApiTest, DesktopNotSupported) {
178 ASSERT_TRUE(RunExtensionSubtest("automation/tests/desktop",
179 "desktop_not_supported.html"))
180 << message_;
182 #endif // defined(USE_AURA)
184 IN_PROC_BROWSER_TEST_F(AutomationApiTest, CloseTab) {
185 StartEmbeddedTestServer();
186 ASSERT_TRUE(RunExtensionSubtest("automation/tests/tabs", "close_tab.html"))
187 << message_;
190 IN_PROC_BROWSER_TEST_F(AutomationApiTest, QuerySelector) {
191 StartEmbeddedTestServer();
192 ASSERT_TRUE(
193 RunExtensionSubtest("automation/tests/tabs", "queryselector.html"))
194 << message_;
197 IN_PROC_BROWSER_TEST_F(AutomationApiTest, Find) {
198 StartEmbeddedTestServer();
199 ASSERT_TRUE(RunExtensionSubtest("automation/tests/tabs", "find.html"))
200 << message_;
203 // Flaky on Linux only. http://crbug.com/467921
204 #if defined(OS_LINUX)
205 #define MAYBE_Mixins DISABLED_Mixins
206 #else
207 #define MAYBE_Mixins Mixins
208 #endif
209 IN_PROC_BROWSER_TEST_F(AutomationApiTest, DISABLED_Mixins) {
210 StartEmbeddedTestServer();
211 ASSERT_TRUE(RunExtensionSubtest("automation/tests/tabs", "mixins.html"))
212 << message_;
215 IN_PROC_BROWSER_TEST_F(AutomationApiTest, TreeChange) {
216 StartEmbeddedTestServer();
217 ASSERT_TRUE(RunExtensionSubtest("automation/tests/tabs", "tree_change.html"))
218 << message_;
222 static const int kPid = 1;
223 static const int kTab0Rid = 1;
224 static const int kTab1Rid = 2;
226 using content::BrowserContext;
228 typedef ui::AXTreeSerializer<const ui::AXNode*> TreeSerializer;
229 typedef ui::AXTreeSource<const ui::AXNode*> TreeSource;
231 #define AX_EVENT_ASSERT_EQUAL ui::AX_EVENT_LOAD_COMPLETE
232 #define AX_EVENT_ASSERT_NOT_EQUAL ui::AX_EVENT_ACTIVEDESCENDANTCHANGED
233 #define AX_EVENT_IGNORE ui::AX_EVENT_CHILDREN_CHANGED
234 #define AX_EVENT_TEST_COMPLETE ui::AX_EVENT_BLUR
236 // This test is based on ui/accessibility/ax_generated_tree_unittest.cc
237 // However, because the tree updates need to be sent to the extension, we can't
238 // use a straightforward set of nested loops as that test does, so this class
239 // keeps track of where we're up to in our imaginary loops, while the extension
240 // function classes below do the work of actually incrementing the state when
241 // appropriate.
242 // The actual deserialization and comparison happens in the API bindings and the
243 // test extension respectively: see
244 // c/t/data/extensions/api_test/automation/tests/generated/generated_trees.js
245 class TreeSerializationState {
246 public:
247 TreeSerializationState()
248 #ifdef NDEBUG
249 : tree_size(3),
250 #else
251 : tree_size(2),
252 #endif
253 generator(tree_size, true),
254 num_trees(generator.UniqueTreeCount()),
255 tree0_version(0),
256 tree1_version(0) {
259 // Serializes tree and sends it as an accessibility event to the extension.
260 void SendDataForTree(const ui::AXTree* tree,
261 TreeSerializer* serializer,
262 int routing_id,
263 BrowserContext* browser_context) {
264 ui::AXTreeUpdate update;
265 serializer->SerializeChanges(tree->root(), &update);
266 SendUpdate(update,
267 ui::AX_EVENT_LAYOUT_COMPLETE,
268 tree->root()->id(),
269 routing_id,
270 browser_context);
273 // Sends the given AXTreeUpdate to the extension as an accessibility event.
274 void SendUpdate(ui::AXTreeUpdate update,
275 ui::AXEvent event,
276 int node_id,
277 int routing_id,
278 BrowserContext* browser_context) {
279 content::AXEventNotificationDetails detail(update.node_id_to_clear,
280 update.nodes,
281 event,
282 node_id,
283 kPid,
284 routing_id);
285 std::vector<content::AXEventNotificationDetails> details;
286 details.push_back(detail);
287 automation_util::DispatchAccessibilityEventsToAutomation(
288 details, browser_context, gfx::Vector2d());
291 // Notify the extension bindings to destroy the tree for the given tab
292 // (identified by routing_id)
293 void SendTreeDestroyedEvent(int routing_id, BrowserContext* browser_context) {
294 automation_util::DispatchTreeDestroyedEventToAutomation(
295 kPid, routing_id, browser_context);
298 // Reset tree0 to a new generated tree based on tree0_version, reset
299 // tree0_source accordingly.
300 void ResetTree0() {
301 tree0.reset(new ui::AXSerializableTree);
302 tree0_source.reset(tree0->CreateTreeSource());
303 generator.BuildUniqueTree(tree0_version, tree0.get());
304 if (!serializer0.get())
305 serializer0.reset(new TreeSerializer(tree0_source.get()));
308 // Reset tree0, set up serializer0, send down the initial tree data to create
309 // the tree in the extension
310 void InitializeTree0(BrowserContext* browser_context) {
311 ResetTree0();
312 serializer0->ChangeTreeSourceForTesting(tree0_source.get());
313 serializer0->Reset();
314 SendDataForTree(tree0.get(), serializer0.get(), kTab0Rid, browser_context);
317 // Reset tree1 to a new generated tree based on tree1_version, reset
318 // tree1_source accordingly.
319 void ResetTree1() {
320 tree1.reset(new ui::AXSerializableTree);
321 tree1_source.reset(tree1->CreateTreeSource());
322 generator.BuildUniqueTree(tree1_version, tree1.get());
323 if (!serializer1.get())
324 serializer1.reset(new TreeSerializer(tree1_source.get()));
327 // Reset tree1, set up serializer1, send down the initial tree data to create
328 // the tree in the extension
329 void InitializeTree1(BrowserContext* browser_context) {
330 ResetTree1();
331 serializer1->ChangeTreeSourceForTesting(tree1_source.get());
332 serializer1->Reset();
333 SendDataForTree(tree1.get(), serializer1.get(), kTab1Rid, browser_context);
336 const int tree_size;
337 const ui::TreeGenerator generator;
339 // The loop variables: comments indicate which variables in
340 // ax_generated_tree_unittest they correspond to.
341 const int num_trees; // n
342 int tree0_version; // i
343 int tree1_version; // j
344 int starting_node; // k
346 // Tree infrastructure; tree0 and tree1 need to be regenerated whenever
347 // tree0_version and tree1_version change, respectively; tree0_source and
348 // tree1_source need to be reset whenever that happens.
349 scoped_ptr<ui::AXSerializableTree> tree0, tree1;
350 scoped_ptr<TreeSource> tree0_source, tree1_source;
351 scoped_ptr<TreeSerializer> serializer0, serializer1;
353 // Whether tree0 needs to be destroyed after the extension has performed its
354 // checks
355 bool destroy_tree0;
358 static TreeSerializationState state;
360 // Override for chrome.automationInternal.enableTab
361 // This fakes out the process and routing IDs for two "tabs", which contain the
362 // source and target trees, respectively, and sends down the current tree for
363 // the requested tab - tab 1 always has tree1, and tab 0 starts with tree0
364 // and then has a series of updates intended to translate tree0 to tree1.
365 // Once all the updates have been sent, the extension asserts that both trees
366 // are equivalent, and then one or both of the trees are reset to a new version.
367 class FakeAutomationInternalEnableTabFunction
368 : public UIThreadExtensionFunction {
369 public:
370 FakeAutomationInternalEnableTabFunction() {}
372 ExtensionFunction::ResponseAction Run() override {
373 using api::automation_internal::EnableTab::Params;
374 scoped_ptr<Params> params(Params::Create(*args_));
375 EXTENSION_FUNCTION_VALIDATE(params.get());
376 if (!params->tab_id.get())
377 return RespondNow(Error("tab_id not specified"));
378 int tab_id = *params->tab_id;
379 if (tab_id == 0) {
380 // tab 0 <--> tree0
381 base::MessageLoop::current()->PostTask(
382 FROM_HERE,
383 base::Bind(&TreeSerializationState::InitializeTree0,
384 base::Unretained(&state),
385 base::Unretained(browser_context())));
386 // TODO(aboxhall): Need to rewrite this test in terms of tree ids.
387 return RespondNow(ArgumentList(
388 api::automation_internal::EnableTab::Results::Create(0)));
390 if (tab_id == 1) {
391 // tab 1 <--> tree1
392 base::MessageLoop::current()->PostTask(
393 FROM_HERE,
394 base::Bind(&TreeSerializationState::InitializeTree1,
395 base::Unretained(&state),
396 base::Unretained(browser_context())));
397 return RespondNow(ArgumentList(
398 api::automation_internal::EnableTab::Results::Create(0)));
400 return RespondNow(Error("Unrecognised tab_id"));
404 // Factory method for use in OverrideFunction()
405 ExtensionFunction* FakeAutomationInternalEnableTabFunctionFactory() {
406 return new FakeAutomationInternalEnableTabFunction();
409 // Helper method to serialize a series of updates via source_serializer to
410 // transform the tree which source_serializer was initialized from into
411 // target_tree, and then trigger the test code to assert the two tabs contain
412 // the same tree.
413 void TransformTree(TreeSerializer* source_serializer,
414 ui::AXTree* target_tree,
415 TreeSource* target_tree_source,
416 content::BrowserContext* browser_context) {
417 source_serializer->ChangeTreeSourceForTesting(target_tree_source);
418 for (int node_delta = 0; node_delta < state.tree_size; ++node_delta) {
419 int id = 1 + (state.starting_node + node_delta) % state.tree_size;
420 ui::AXTreeUpdate update;
421 source_serializer->SerializeChanges(target_tree->GetFromId(id), &update);
422 bool is_last_update = node_delta == state.tree_size - 1;
423 ui::AXEvent event =
424 is_last_update ? AX_EVENT_ASSERT_EQUAL : AX_EVENT_IGNORE;
425 state.SendUpdate(
426 update, event, target_tree->root()->id(), kTab0Rid, browser_context);
430 // Helper method to send a no-op tree update to tab 0 with the given event.
431 void SendEvent(ui::AXEvent event, content::BrowserContext* browser_context) {
432 ui::AXTreeUpdate update;
433 ui::AXNode* root = state.tree0->root();
434 state.serializer0->SerializeChanges(root, &update);
435 state.SendUpdate(update, event, root->id(), kTab0Rid, browser_context);
438 // Override for chrome.automationInternal.performAction
439 // This is used as a synchronization mechanism; the general flow is:
440 // 1. The extension requests tree0 and tree1 (on tab 0 and tab 1 respectively)
441 // 2. FakeAutomationInternalEnableTabFunction sends down the trees
442 // 3. When the callback for getTree(0) fires, the extension calls doDefault() on
443 // the root node of tree0, which calls into this class's Run() method.
444 // 4. In the normal case, we're in the "inner loop" (iterating over
445 // starting_node). For each value of starting_node, we do the following:
446 // a. Serialize a sequence of updates which should transform tree0 into
447 // tree1. Each of these updates is sent as a childrenChanged event,
448 // except for the last which is sent as a loadComplete event.
449 // b. state.destroy_tree0 is set to true
450 // c. state.starting_node gets incremented
451 // d. The loadComplete event triggers an assertion in the extension.
452 // e. The extension performs another doDefault() on the root node of the
453 // tree.
454 // f. This time, we send a destroy event to tab0, so that the tree can be
455 // reset.
456 // g. The extension is notified of the tree's destruction and requests the
457 // tree for tab 0 again, returning to step 2.
458 // 5. When starting_node exceeds state.tree_size, we increment tree0_version if
459 // it would not exceed state.num_trees, or increment tree1_version and reset
460 // tree0_version to 0 otherwise, and reset starting_node to 0.
461 // Then we reset one or both trees as appropriate, and send down destroyed
462 // events similarly, causing the extension to re-request the tree and going
463 // back to step 2 again.
464 // 6. When tree1_version has gone through all possible values, we send a blur
465 // event, signaling the extension to call chrome.test.succeed() and finish
466 // the test.
467 class FakeAutomationInternalPerformActionFunction
468 : public UIThreadExtensionFunction {
469 public:
470 FakeAutomationInternalPerformActionFunction() {}
472 ExtensionFunction::ResponseAction Run() override {
473 if (state.destroy_tree0) {
474 // Step 4.f: tell the extension to destroy the tree and re-request it.
475 state.SendTreeDestroyedEvent(kTab0Rid, browser_context());
476 state.destroy_tree0 = false;
477 return RespondNow(NoArguments());
480 TreeSerializer* serializer0 = state.serializer0.get();
481 if (state.starting_node < state.tree_size) {
482 // As a sanity check, if the trees are not equal, assert that they are not
483 // equal before serializing changes.
484 if (state.tree0_version != state.tree1_version)
485 SendEvent(AX_EVENT_ASSERT_NOT_EQUAL, browser_context());
487 // Step 4.a: pretend that tree0 turned into tree1, and serialize
488 // a sequence of updates to tab 0 to match.
489 TransformTree(serializer0,
490 state.tree1.get(),
491 state.tree1_source.get(),
492 browser_context());
494 // Step 4.b: remember that we need to tell the extension to destroy and
495 // re-request the tree on the next action.
496 state.destroy_tree0 = true;
498 // Step 4.c: increment starting_node.
499 state.starting_node++;
500 } else if (state.tree0_version < state.num_trees - 1) {
501 // Step 5: Increment tree0_version and reset starting_node
502 state.tree0_version++;
503 state.starting_node = 0;
505 // Step 5: Reset tree0 and tell the extension to destroy and re-request it
506 state.SendTreeDestroyedEvent(kTab0Rid, browser_context());
507 } else if (state.tree1_version < state.num_trees - 1) {
508 // Step 5: Increment tree1_version and reset tree0_version and
509 // starting_node
510 state.tree1_version++;
511 state.tree0_version = 0;
512 state.starting_node = 0;
514 // Step 5: Reset tree0 and tell the extension to destroy and re-request it
515 state.SendTreeDestroyedEvent(kTab0Rid, browser_context());
517 // Step 5: Reset tree1 and tell the extension to destroy and re-request it
518 state.SendTreeDestroyedEvent(kTab1Rid, browser_context());
519 } else {
520 // Step 6: Send a TEST_COMPLETE (blur) event to signal the extension to
521 // call chrome.test.succeed().
522 SendEvent(AX_EVENT_TEST_COMPLETE, browser_context());
525 return RespondNow(NoArguments());
529 // Factory method for use in OverrideFunction()
530 ExtensionFunction* FakeAutomationInternalPerformActionFunctionFactory() {
531 return new FakeAutomationInternalPerformActionFunction();
534 // http://crbug.com/396353
535 IN_PROC_BROWSER_TEST_F(AutomationApiTest, DISABLED_GeneratedTree) {
536 ASSERT_TRUE(extensions::ExtensionFunctionDispatcher::OverrideFunction(
537 "automationInternal.enableTab",
538 FakeAutomationInternalEnableTabFunctionFactory));
539 ASSERT_TRUE(extensions::ExtensionFunctionDispatcher::OverrideFunction(
540 "automationInternal.performAction",
541 FakeAutomationInternalPerformActionFunctionFactory));
542 ASSERT_TRUE(RunExtensionSubtest("automation/tests/generated",
543 "generated_trees.html")) << message_;
546 } // namespace extensions