Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(727)

Side by Side Diff: chrome/browser/extensions/api/automation/automation_apitest.cc

Issue 1231603009: Re-land: Reimplement automation API on top of C++-backed AXTree. (Closed) Base URL: https://chromium.googlesource.com/chromium/src.git@master
Patch Set: Fix nested event sending in AutomationManagerAura Created 5 years, 5 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
« no previous file with comments | « no previous file | chrome/browser/extensions/api/automation_internal/automation_event_router.cc » ('j') | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
OLDNEW
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
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
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 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
543 } // namespace extensions 215 } // namespace extensions
OLDNEW
« no previous file with comments | « no previous file | chrome/browser/extensions/api/automation_internal/automation_event_router.cc » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698