OLD | NEW |
1 // Copyright 2014 The Chromium Authors. All rights reserved. | 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 | 2 // Use of this source code is governed by a BSD-style license that can be |
3 // found in the LICENSE file. | 3 // found in the LICENSE file. |
4 | 4 |
5 #include "base/files/file_path.h" | 5 #include "base/files/file_path.h" |
6 #include "base/location.h" | 6 #include "base/location.h" |
7 #include "base/path_service.h" | 7 #include "base/path_service.h" |
8 #include "base/single_thread_task_runner.h" | 8 #include "base/single_thread_task_runner.h" |
9 #include "base/strings/string_number_conversions.h" | 9 #include "base/strings/string_number_conversions.h" |
10 #include "base/thread_task_runner_handle.h" | 10 #include "base/thread_task_runner_handle.h" |
11 #include "chrome/browser/accessibility/ax_tree_id_registry.h" | 11 #include "chrome/browser/extensions/api/automation_internal/automation_util.h" |
12 #include "chrome/browser/extensions/api/automation_internal/automation_event_rou
ter.h" | |
13 #include "chrome/browser/extensions/chrome_extension_function.h" | 12 #include "chrome/browser/extensions/chrome_extension_function.h" |
14 #include "chrome/browser/extensions/extension_apitest.h" | 13 #include "chrome/browser/extensions/extension_apitest.h" |
15 #include "chrome/browser/ui/tabs/tab_strip_model.h" | 14 #include "chrome/browser/ui/tabs/tab_strip_model.h" |
16 #include "chrome/common/chrome_paths.h" | 15 #include "chrome/common/chrome_paths.h" |
17 #include "chrome/common/chrome_switches.h" | 16 #include "chrome/common/chrome_switches.h" |
18 #include "chrome/common/extensions/api/automation_internal.h" | 17 #include "chrome/common/extensions/api/automation_internal.h" |
19 #include "chrome/common/extensions/chrome_extension_messages.h" | |
20 #include "chrome/test/base/ui_test_utils.h" | 18 #include "chrome/test/base/ui_test_utils.h" |
21 #include "content/public/browser/ax_event_notification_details.h" | 19 #include "content/public/browser/ax_event_notification_details.h" |
22 #include "content/public/browser/render_widget_host.h" | 20 #include "content/public/browser/render_widget_host.h" |
23 #include "content/public/browser/render_widget_host_view.h" | 21 #include "content/public/browser/render_widget_host_view.h" |
24 #include "content/public/browser/web_contents.h" | 22 #include "content/public/browser/web_contents.h" |
25 #include "extensions/test/extension_test_message_listener.h" | 23 #include "extensions/test/extension_test_message_listener.h" |
26 #include "net/dns/mock_host_resolver.h" | 24 #include "net/dns/mock_host_resolver.h" |
27 #include "net/test/embedded_test_server/embedded_test_server.h" | 25 #include "net/test/embedded_test_server/embedded_test_server.h" |
28 #include "testing/gtest/include/gtest/gtest.h" | 26 #include "testing/gtest/include/gtest/gtest.h" |
29 #include "ui/accessibility/ax_node.h" | 27 #include "ui/accessibility/ax_node.h" |
(...skipping 64 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
94 ASSERT_FALSE(tab->IsFullAccessibilityModeForTesting()); | 92 ASSERT_FALSE(tab->IsFullAccessibilityModeForTesting()); |
95 ASSERT_TRUE(tab->IsTreeOnlyAccessibilityModeForTesting()); | 93 ASSERT_TRUE(tab->IsTreeOnlyAccessibilityModeForTesting()); |
96 } | 94 } |
97 | 95 |
98 IN_PROC_BROWSER_TEST_F(AutomationApiTest, SanityCheck) { | 96 IN_PROC_BROWSER_TEST_F(AutomationApiTest, SanityCheck) { |
99 StartEmbeddedTestServer(); | 97 StartEmbeddedTestServer(); |
100 ASSERT_TRUE(RunExtensionSubtest("automation/tests/tabs", "sanity_check.html")) | 98 ASSERT_TRUE(RunExtensionSubtest("automation/tests/tabs", "sanity_check.html")) |
101 << message_; | 99 << message_; |
102 } | 100 } |
103 | 101 |
| 102 IN_PROC_BROWSER_TEST_F(AutomationApiTest, Unit) { |
| 103 ASSERT_TRUE(RunExtensionSubtest("automation/tests/unit", "unit.html")) |
| 104 << message_; |
| 105 } |
| 106 |
104 IN_PROC_BROWSER_TEST_F(AutomationApiTest, GetTreeByTabId) { | 107 IN_PROC_BROWSER_TEST_F(AutomationApiTest, GetTreeByTabId) { |
105 StartEmbeddedTestServer(); | 108 StartEmbeddedTestServer(); |
106 ASSERT_TRUE(RunExtensionSubtest("automation/tests/tabs", "tab_id.html")) | 109 ASSERT_TRUE(RunExtensionSubtest("automation/tests/tabs", "tab_id.html")) |
107 << message_; | 110 << message_; |
108 } | 111 } |
109 | 112 |
110 IN_PROC_BROWSER_TEST_F(AutomationApiTest, Events) { | 113 IN_PROC_BROWSER_TEST_F(AutomationApiTest, Events) { |
111 StartEmbeddedTestServer(); | 114 StartEmbeddedTestServer(); |
112 ASSERT_TRUE(RunExtensionSubtest("automation/tests/tabs", "events.html")) | 115 ASSERT_TRUE(RunExtensionSubtest("automation/tests/tabs", "events.html")) |
113 << message_; | 116 << message_; |
(...skipping 79 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
193 RunExtensionSubtest("automation/tests/tabs", "queryselector.html")) | 196 RunExtensionSubtest("automation/tests/tabs", "queryselector.html")) |
194 << message_; | 197 << message_; |
195 } | 198 } |
196 | 199 |
197 IN_PROC_BROWSER_TEST_F(AutomationApiTest, Find) { | 200 IN_PROC_BROWSER_TEST_F(AutomationApiTest, Find) { |
198 StartEmbeddedTestServer(); | 201 StartEmbeddedTestServer(); |
199 ASSERT_TRUE(RunExtensionSubtest("automation/tests/tabs", "find.html")) | 202 ASSERT_TRUE(RunExtensionSubtest("automation/tests/tabs", "find.html")) |
200 << message_; | 203 << message_; |
201 } | 204 } |
202 | 205 |
203 IN_PROC_BROWSER_TEST_F(AutomationApiTest, Attributes) { | 206 // Flaky. http://crbug.com/467921 |
| 207 IN_PROC_BROWSER_TEST_F(AutomationApiTest, DISABLED_Mixins) { |
204 StartEmbeddedTestServer(); | 208 StartEmbeddedTestServer(); |
205 ASSERT_TRUE(RunExtensionSubtest("automation/tests/tabs", "attributes.html")) | 209 ASSERT_TRUE(RunExtensionSubtest("automation/tests/tabs", "mixins.html")) |
206 << message_; | 210 << message_; |
207 } | 211 } |
208 | 212 |
209 IN_PROC_BROWSER_TEST_F(AutomationApiTest, TreeChange) { | 213 IN_PROC_BROWSER_TEST_F(AutomationApiTest, TreeChange) { |
210 StartEmbeddedTestServer(); | 214 StartEmbeddedTestServer(); |
211 ASSERT_TRUE(RunExtensionSubtest("automation/tests/tabs", "tree_change.html")) | 215 ASSERT_TRUE(RunExtensionSubtest("automation/tests/tabs", "tree_change.html")) |
212 << message_; | 216 << message_; |
213 } | 217 } |
214 | 218 |
| 219 |
| 220 static const int kPid = 1; |
| 221 static const int kTab0Rid = 1; |
| 222 static const int kTab1Rid = 2; |
| 223 |
| 224 using content::BrowserContext; |
| 225 |
| 226 typedef ui::AXTreeSerializer<const ui::AXNode*> TreeSerializer; |
| 227 typedef ui::AXTreeSource<const ui::AXNode*> TreeSource; |
| 228 |
| 229 #define AX_EVENT_ASSERT_EQUAL ui::AX_EVENT_LOAD_COMPLETE |
| 230 #define AX_EVENT_ASSERT_NOT_EQUAL ui::AX_EVENT_ACTIVEDESCENDANTCHANGED |
| 231 #define AX_EVENT_IGNORE ui::AX_EVENT_CHILDREN_CHANGED |
| 232 #define AX_EVENT_TEST_COMPLETE ui::AX_EVENT_BLUR |
| 233 |
| 234 // This test is based on ui/accessibility/ax_generated_tree_unittest.cc |
| 235 // However, because the tree updates need to be sent to the extension, we can't |
| 236 // use a straightforward set of nested loops as that test does, so this class |
| 237 // keeps track of where we're up to in our imaginary loops, while the extension |
| 238 // function classes below do the work of actually incrementing the state when |
| 239 // appropriate. |
| 240 // The actual deserialization and comparison happens in the API bindings and the |
| 241 // test extension respectively: see |
| 242 // c/t/data/extensions/api_test/automation/tests/generated/generated_trees.js |
| 243 class TreeSerializationState { |
| 244 public: |
| 245 TreeSerializationState() |
| 246 #ifdef NDEBUG |
| 247 : tree_size(3), |
| 248 #else |
| 249 : tree_size(2), |
| 250 #endif |
| 251 generator(tree_size, true), |
| 252 num_trees(generator.UniqueTreeCount()), |
| 253 tree0_version(0), |
| 254 tree1_version(0) { |
| 255 } |
| 256 |
| 257 // Serializes tree and sends it as an accessibility event to the extension. |
| 258 void SendDataForTree(const ui::AXTree* tree, |
| 259 TreeSerializer* serializer, |
| 260 int routing_id, |
| 261 BrowserContext* browser_context) { |
| 262 ui::AXTreeUpdate update; |
| 263 serializer->SerializeChanges(tree->root(), &update); |
| 264 SendUpdate(update, |
| 265 ui::AX_EVENT_LAYOUT_COMPLETE, |
| 266 tree->root()->id(), |
| 267 routing_id, |
| 268 browser_context); |
| 269 } |
| 270 |
| 271 // Sends the given AXTreeUpdate to the extension as an accessibility event. |
| 272 void SendUpdate(ui::AXTreeUpdate update, |
| 273 ui::AXEvent event, |
| 274 int node_id, |
| 275 int routing_id, |
| 276 BrowserContext* browser_context) { |
| 277 content::AXEventNotificationDetails detail(update.node_id_to_clear, |
| 278 update.nodes, |
| 279 event, |
| 280 node_id, |
| 281 std::map<int32, int>(), |
| 282 kPid, |
| 283 routing_id); |
| 284 std::vector<content::AXEventNotificationDetails> details; |
| 285 details.push_back(detail); |
| 286 automation_util::DispatchAccessibilityEventsToAutomation( |
| 287 details, browser_context, gfx::Vector2d()); |
| 288 } |
| 289 |
| 290 // Notify the extension bindings to destroy the tree for the given tab |
| 291 // (identified by routing_id) |
| 292 void SendTreeDestroyedEvent(int routing_id, BrowserContext* browser_context) { |
| 293 automation_util::DispatchTreeDestroyedEventToAutomation( |
| 294 kPid, routing_id, browser_context); |
| 295 } |
| 296 |
| 297 // Reset tree0 to a new generated tree based on tree0_version, reset |
| 298 // tree0_source accordingly. |
| 299 void ResetTree0() { |
| 300 tree0.reset(new ui::AXSerializableTree); |
| 301 tree0_source.reset(tree0->CreateTreeSource()); |
| 302 generator.BuildUniqueTree(tree0_version, tree0.get()); |
| 303 if (!serializer0.get()) |
| 304 serializer0.reset(new TreeSerializer(tree0_source.get())); |
| 305 } |
| 306 |
| 307 // Reset tree0, set up serializer0, send down the initial tree data to create |
| 308 // the tree in the extension |
| 309 void InitializeTree0(BrowserContext* browser_context) { |
| 310 ResetTree0(); |
| 311 serializer0->ChangeTreeSourceForTesting(tree0_source.get()); |
| 312 serializer0->Reset(); |
| 313 SendDataForTree(tree0.get(), serializer0.get(), kTab0Rid, browser_context); |
| 314 } |
| 315 |
| 316 // Reset tree1 to a new generated tree based on tree1_version, reset |
| 317 // tree1_source accordingly. |
| 318 void ResetTree1() { |
| 319 tree1.reset(new ui::AXSerializableTree); |
| 320 tree1_source.reset(tree1->CreateTreeSource()); |
| 321 generator.BuildUniqueTree(tree1_version, tree1.get()); |
| 322 if (!serializer1.get()) |
| 323 serializer1.reset(new TreeSerializer(tree1_source.get())); |
| 324 } |
| 325 |
| 326 // Reset tree1, set up serializer1, send down the initial tree data to create |
| 327 // the tree in the extension |
| 328 void InitializeTree1(BrowserContext* browser_context) { |
| 329 ResetTree1(); |
| 330 serializer1->ChangeTreeSourceForTesting(tree1_source.get()); |
| 331 serializer1->Reset(); |
| 332 SendDataForTree(tree1.get(), serializer1.get(), kTab1Rid, browser_context); |
| 333 } |
| 334 |
| 335 const int tree_size; |
| 336 const ui::TreeGenerator generator; |
| 337 |
| 338 // The loop variables: comments indicate which variables in |
| 339 // ax_generated_tree_unittest they correspond to. |
| 340 const int num_trees; // n |
| 341 int tree0_version; // i |
| 342 int tree1_version; // j |
| 343 int starting_node; // k |
| 344 |
| 345 // Tree infrastructure; tree0 and tree1 need to be regenerated whenever |
| 346 // tree0_version and tree1_version change, respectively; tree0_source and |
| 347 // tree1_source need to be reset whenever that happens. |
| 348 scoped_ptr<ui::AXSerializableTree> tree0, tree1; |
| 349 scoped_ptr<TreeSource> tree0_source, tree1_source; |
| 350 scoped_ptr<TreeSerializer> serializer0, serializer1; |
| 351 |
| 352 // Whether tree0 needs to be destroyed after the extension has performed its |
| 353 // checks |
| 354 bool destroy_tree0; |
| 355 }; |
| 356 |
| 357 static TreeSerializationState state; |
| 358 |
| 359 // Override for chrome.automationInternal.enableTab |
| 360 // This fakes out the process and routing IDs for two "tabs", which contain the |
| 361 // source and target trees, respectively, and sends down the current tree for |
| 362 // the requested tab - tab 1 always has tree1, and tab 0 starts with tree0 |
| 363 // and then has a series of updates intended to translate tree0 to tree1. |
| 364 // Once all the updates have been sent, the extension asserts that both trees |
| 365 // are equivalent, and then one or both of the trees are reset to a new version. |
| 366 class FakeAutomationInternalEnableTabFunction |
| 367 : public UIThreadExtensionFunction { |
| 368 public: |
| 369 FakeAutomationInternalEnableTabFunction() {} |
| 370 |
| 371 ExtensionFunction::ResponseAction Run() override { |
| 372 using api::automation_internal::EnableTab::Params; |
| 373 scoped_ptr<Params> params(Params::Create(*args_)); |
| 374 EXTENSION_FUNCTION_VALIDATE(params.get()); |
| 375 if (!params->args.tab_id.get()) |
| 376 return RespondNow(Error("tab_id not specified")); |
| 377 int tab_id = *params->args.tab_id; |
| 378 if (tab_id == 0) { |
| 379 // tab 0 <--> tree0 |
| 380 base::ThreadTaskRunnerHandle::Get()->PostTask( |
| 381 FROM_HERE, base::Bind(&TreeSerializationState::InitializeTree0, |
| 382 base::Unretained(&state), |
| 383 base::Unretained(browser_context()))); |
| 384 // TODO(aboxhall): Need to rewrite this test in terms of tree ids. |
| 385 return RespondNow(ArgumentList( |
| 386 api::automation_internal::EnableTab::Results::Create(0))); |
| 387 } |
| 388 if (tab_id == 1) { |
| 389 // tab 1 <--> tree1 |
| 390 base::ThreadTaskRunnerHandle::Get()->PostTask( |
| 391 FROM_HERE, base::Bind(&TreeSerializationState::InitializeTree1, |
| 392 base::Unretained(&state), |
| 393 base::Unretained(browser_context()))); |
| 394 return RespondNow(ArgumentList( |
| 395 api::automation_internal::EnableTab::Results::Create(0))); |
| 396 } |
| 397 return RespondNow(Error("Unrecognised tab_id")); |
| 398 } |
| 399 }; |
| 400 |
| 401 // Factory method for use in OverrideFunction() |
| 402 ExtensionFunction* FakeAutomationInternalEnableTabFunctionFactory() { |
| 403 return new FakeAutomationInternalEnableTabFunction(); |
| 404 } |
| 405 |
| 406 // Helper method to serialize a series of updates via source_serializer to |
| 407 // transform the tree which source_serializer was initialized from into |
| 408 // target_tree, and then trigger the test code to assert the two tabs contain |
| 409 // the same tree. |
| 410 void TransformTree(TreeSerializer* source_serializer, |
| 411 ui::AXTree* target_tree, |
| 412 TreeSource* target_tree_source, |
| 413 content::BrowserContext* browser_context) { |
| 414 source_serializer->ChangeTreeSourceForTesting(target_tree_source); |
| 415 for (int node_delta = 0; node_delta < state.tree_size; ++node_delta) { |
| 416 int id = 1 + (state.starting_node + node_delta) % state.tree_size; |
| 417 ui::AXTreeUpdate update; |
| 418 source_serializer->SerializeChanges(target_tree->GetFromId(id), &update); |
| 419 bool is_last_update = node_delta == state.tree_size - 1; |
| 420 ui::AXEvent event = |
| 421 is_last_update ? AX_EVENT_ASSERT_EQUAL : AX_EVENT_IGNORE; |
| 422 state.SendUpdate( |
| 423 update, event, target_tree->root()->id(), kTab0Rid, browser_context); |
| 424 } |
| 425 } |
| 426 |
| 427 // Helper method to send a no-op tree update to tab 0 with the given event. |
| 428 void SendEvent(ui::AXEvent event, content::BrowserContext* browser_context) { |
| 429 ui::AXTreeUpdate update; |
| 430 ui::AXNode* root = state.tree0->root(); |
| 431 state.serializer0->SerializeChanges(root, &update); |
| 432 state.SendUpdate(update, event, root->id(), kTab0Rid, browser_context); |
| 433 } |
| 434 |
| 435 // Override for chrome.automationInternal.performAction |
| 436 // This is used as a synchronization mechanism; the general flow is: |
| 437 // 1. The extension requests tree0 and tree1 (on tab 0 and tab 1 respectively) |
| 438 // 2. FakeAutomationInternalEnableTabFunction sends down the trees |
| 439 // 3. When the callback for getTree(0) fires, the extension calls doDefault() on |
| 440 // the root node of tree0, which calls into this class's Run() method. |
| 441 // 4. In the normal case, we're in the "inner loop" (iterating over |
| 442 // starting_node). For each value of starting_node, we do the following: |
| 443 // a. Serialize a sequence of updates which should transform tree0 into |
| 444 // tree1. Each of these updates is sent as a childrenChanged event, |
| 445 // except for the last which is sent as a loadComplete event. |
| 446 // b. state.destroy_tree0 is set to true |
| 447 // c. state.starting_node gets incremented |
| 448 // d. The loadComplete event triggers an assertion in the extension. |
| 449 // e. The extension performs another doDefault() on the root node of the |
| 450 // tree. |
| 451 // f. This time, we send a destroy event to tab0, so that the tree can be |
| 452 // reset. |
| 453 // g. The extension is notified of the tree's destruction and requests the |
| 454 // tree for tab 0 again, returning to step 2. |
| 455 // 5. When starting_node exceeds state.tree_size, we increment tree0_version if |
| 456 // it would not exceed state.num_trees, or increment tree1_version and reset |
| 457 // tree0_version to 0 otherwise, and reset starting_node to 0. |
| 458 // Then we reset one or both trees as appropriate, and send down destroyed |
| 459 // events similarly, causing the extension to re-request the tree and going |
| 460 // back to step 2 again. |
| 461 // 6. When tree1_version has gone through all possible values, we send a blur |
| 462 // event, signaling the extension to call chrome.test.succeed() and finish |
| 463 // the test. |
| 464 class FakeAutomationInternalPerformActionFunction |
| 465 : public UIThreadExtensionFunction { |
| 466 public: |
| 467 FakeAutomationInternalPerformActionFunction() {} |
| 468 |
| 469 ExtensionFunction::ResponseAction Run() override { |
| 470 if (state.destroy_tree0) { |
| 471 // Step 4.f: tell the extension to destroy the tree and re-request it. |
| 472 state.SendTreeDestroyedEvent(kTab0Rid, browser_context()); |
| 473 state.destroy_tree0 = false; |
| 474 return RespondNow(NoArguments()); |
| 475 } |
| 476 |
| 477 TreeSerializer* serializer0 = state.serializer0.get(); |
| 478 if (state.starting_node < state.tree_size) { |
| 479 // As a sanity check, if the trees are not equal, assert that they are not |
| 480 // equal before serializing changes. |
| 481 if (state.tree0_version != state.tree1_version) |
| 482 SendEvent(AX_EVENT_ASSERT_NOT_EQUAL, browser_context()); |
| 483 |
| 484 // Step 4.a: pretend that tree0 turned into tree1, and serialize |
| 485 // a sequence of updates to tab 0 to match. |
| 486 TransformTree(serializer0, |
| 487 state.tree1.get(), |
| 488 state.tree1_source.get(), |
| 489 browser_context()); |
| 490 |
| 491 // Step 4.b: remember that we need to tell the extension to destroy and |
| 492 // re-request the tree on the next action. |
| 493 state.destroy_tree0 = true; |
| 494 |
| 495 // Step 4.c: increment starting_node. |
| 496 state.starting_node++; |
| 497 } else if (state.tree0_version < state.num_trees - 1) { |
| 498 // Step 5: Increment tree0_version and reset starting_node |
| 499 state.tree0_version++; |
| 500 state.starting_node = 0; |
| 501 |
| 502 // Step 5: Reset tree0 and tell the extension to destroy and re-request it |
| 503 state.SendTreeDestroyedEvent(kTab0Rid, browser_context()); |
| 504 } else if (state.tree1_version < state.num_trees - 1) { |
| 505 // Step 5: Increment tree1_version and reset tree0_version and |
| 506 // starting_node |
| 507 state.tree1_version++; |
| 508 state.tree0_version = 0; |
| 509 state.starting_node = 0; |
| 510 |
| 511 // Step 5: Reset tree0 and tell the extension to destroy and re-request it |
| 512 state.SendTreeDestroyedEvent(kTab0Rid, browser_context()); |
| 513 |
| 514 // Step 5: Reset tree1 and tell the extension to destroy and re-request it |
| 515 state.SendTreeDestroyedEvent(kTab1Rid, browser_context()); |
| 516 } else { |
| 517 // Step 6: Send a TEST_COMPLETE (blur) event to signal the extension to |
| 518 // call chrome.test.succeed(). |
| 519 SendEvent(AX_EVENT_TEST_COMPLETE, browser_context()); |
| 520 } |
| 521 |
| 522 return RespondNow(NoArguments()); |
| 523 } |
| 524 }; |
| 525 |
| 526 // Factory method for use in OverrideFunction() |
| 527 ExtensionFunction* FakeAutomationInternalPerformActionFunctionFactory() { |
| 528 return new FakeAutomationInternalPerformActionFunction(); |
| 529 } |
| 530 |
| 531 // http://crbug.com/396353 |
| 532 IN_PROC_BROWSER_TEST_F(AutomationApiTest, DISABLED_GeneratedTree) { |
| 533 ASSERT_TRUE(extensions::ExtensionFunctionDispatcher::OverrideFunction( |
| 534 "automationInternal.enableTab", |
| 535 FakeAutomationInternalEnableTabFunctionFactory)); |
| 536 ASSERT_TRUE(extensions::ExtensionFunctionDispatcher::OverrideFunction( |
| 537 "automationInternal.performAction", |
| 538 FakeAutomationInternalPerformActionFunctionFactory)); |
| 539 ASSERT_TRUE(RunExtensionSubtest("automation/tests/generated", |
| 540 "generated_trees.html")) << message_; |
| 541 } |
| 542 |
215 } // namespace extensions | 543 } // namespace extensions |
OLD | NEW |