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