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"
36 namespace extensions
{
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
{
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
);
52 embedded_test_server()->GetURL(path
).ReplaceComponents(replacements
);
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());
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"))
99 IN_PROC_BROWSER_TEST_F(AutomationApiTest
, Unit
) {
100 ASSERT_TRUE(RunExtensionSubtest("automation/tests/unit", "unit.html"))
104 IN_PROC_BROWSER_TEST_F(AutomationApiTest
, GetTreeByTabId
) {
105 StartEmbeddedTestServer();
106 ASSERT_TRUE(RunExtensionSubtest("automation/tests/tabs", "tab_id.html"))
110 IN_PROC_BROWSER_TEST_F(AutomationApiTest
, Events
) {
111 StartEmbeddedTestServer();
112 ASSERT_TRUE(RunExtensionSubtest("automation/tests/tabs", "events.html"))
116 IN_PROC_BROWSER_TEST_F(AutomationApiTest
, Actions
) {
117 StartEmbeddedTestServer();
118 ASSERT_TRUE(RunExtensionSubtest("automation/tests/tabs", "actions.html"))
122 IN_PROC_BROWSER_TEST_F(AutomationApiTest
, Location
) {
123 StartEmbeddedTestServer();
124 ASSERT_TRUE(RunExtensionSubtest("automation/tests/tabs", "location.html"))
128 IN_PROC_BROWSER_TEST_F(AutomationApiTest
, TabsAutomationBooleanPermissions
) {
129 StartEmbeddedTestServer();
130 ASSERT_TRUE(RunExtensionSubtest(
131 "automation/tests/tabs_automation_boolean", "permissions.html"))
135 IN_PROC_BROWSER_TEST_F(AutomationApiTest
, TabsAutomationBooleanActions
) {
136 StartEmbeddedTestServer();
137 ASSERT_TRUE(RunExtensionSubtest(
138 "automation/tests/tabs_automation_boolean", "actions.html"))
142 IN_PROC_BROWSER_TEST_F(AutomationApiTest
, TabsAutomationHostsPermissions
) {
143 StartEmbeddedTestServer();
144 ASSERT_TRUE(RunExtensionSubtest(
145 "automation/tests/tabs_automation_hosts", "permissions.html"))
149 #if defined(USE_AURA)
150 IN_PROC_BROWSER_TEST_F(AutomationApiTest
, Desktop
) {
151 ASSERT_TRUE(RunExtensionSubtest("automation/tests/desktop", "desktop.html"))
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(
167 ASSERT_TRUE(RunExtensionSubtest("automation/tests/desktop", "actions.html"))
171 IN_PROC_BROWSER_TEST_F(AutomationApiTest
, DesktopLoadTabs
) {
172 ASSERT_TRUE(RunExtensionSubtest("automation/tests/desktop", "load_tabs.html"))
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"))
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"))
190 IN_PROC_BROWSER_TEST_F(AutomationApiTest
, QuerySelector
) {
191 StartEmbeddedTestServer();
193 RunExtensionSubtest("automation/tests/tabs", "queryselector.html"))
197 IN_PROC_BROWSER_TEST_F(AutomationApiTest
, Find
) {
198 StartEmbeddedTestServer();
199 ASSERT_TRUE(RunExtensionSubtest("automation/tests/tabs", "find.html"))
203 // Flaky on Linux only. http://crbug.com/467921
204 #if defined(OS_LINUX)
205 #define MAYBE_Mixins DISABLED_Mixins
207 #define MAYBE_Mixins Mixins
209 IN_PROC_BROWSER_TEST_F(AutomationApiTest
, DISABLED_Mixins
) {
210 StartEmbeddedTestServer();
211 ASSERT_TRUE(RunExtensionSubtest("automation/tests/tabs", "mixins.html"))
215 IN_PROC_BROWSER_TEST_F(AutomationApiTest
, TreeChange
) {
216 StartEmbeddedTestServer();
217 ASSERT_TRUE(RunExtensionSubtest("automation/tests/tabs", "tree_change.html"))
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
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
{
247 TreeSerializationState()
253 generator(tree_size
, true),
254 num_trees(generator
.UniqueTreeCount()),
259 // Serializes tree and sends it as an accessibility event to the extension.
260 void SendDataForTree(const ui::AXTree
* tree
,
261 TreeSerializer
* serializer
,
263 BrowserContext
* browser_context
) {
264 ui::AXTreeUpdate update
;
265 serializer
->SerializeChanges(tree
->root(), &update
);
267 ui::AX_EVENT_LAYOUT_COMPLETE
,
273 // Sends the given AXTreeUpdate to the extension as an accessibility event.
274 void SendUpdate(ui::AXTreeUpdate update
,
278 BrowserContext
* browser_context
) {
279 content::AXEventNotificationDetails
detail(update
.node_id_to_clear
,
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.
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
) {
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.
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
) {
331 serializer1
->ChangeTreeSourceForTesting(tree1_source
.get());
332 serializer1
->Reset();
333 SendDataForTree(tree1
.get(), serializer1
.get(), kTab1Rid
, browser_context
);
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
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
{
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
;
381 base::MessageLoop::current()->PostTask(
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)));
392 base::MessageLoop::current()->PostTask(
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
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;
424 is_last_update
? AX_EVENT_ASSERT_EQUAL
: AX_EVENT_IGNORE
;
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
454 // f. This time, we send a destroy event to tab0, so that the tree can be
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
467 class FakeAutomationInternalPerformActionFunction
468 : public UIThreadExtensionFunction
{
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
,
491 state
.tree1_source
.get(),
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
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());
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