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