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
30namespace extensions {
31
32namespace {
33static const char kDomain[] = "a.com";
34static const char kSitesDir[] = "automation/sites";
35static const char kGotTree[] = "got_tree";
36}  // anonymous namespace
37
38class 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
72IN_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
91IN_PROC_BROWSER_TEST_F(AutomationApiTest, SanityCheck) {
92  StartEmbeddedTestServer();
93  ASSERT_TRUE(RunExtensionSubtest("automation/tests/tabs", "sanity_check.html"))
94      << message_;
95}
96
97IN_PROC_BROWSER_TEST_F(AutomationApiTest, Unit) {
98  ASSERT_TRUE(RunExtensionSubtest("automation/tests/unit", "unit.html"))
99      << message_;
100}
101
102IN_PROC_BROWSER_TEST_F(AutomationApiTest, GetTreeByTabId) {
103  StartEmbeddedTestServer();
104  ASSERT_TRUE(RunExtensionSubtest("automation/tests/tabs", "tab_id.html"))
105      << message_;
106}
107
108IN_PROC_BROWSER_TEST_F(AutomationApiTest, Events) {
109  StartEmbeddedTestServer();
110  ASSERT_TRUE(RunExtensionSubtest("automation/tests/tabs", "events.html"))
111      << message_;
112}
113
114IN_PROC_BROWSER_TEST_F(AutomationApiTest, Actions) {
115  StartEmbeddedTestServer();
116  ASSERT_TRUE(RunExtensionSubtest("automation/tests/tabs", "actions.html"))
117      << message_;
118}
119
120IN_PROC_BROWSER_TEST_F(AutomationApiTest, Location) {
121  StartEmbeddedTestServer();
122  ASSERT_TRUE(RunExtensionSubtest("automation/tests/tabs", "location.html"))
123      << message_;
124}
125
126IN_PROC_BROWSER_TEST_F(AutomationApiTest, TabsAutomationBooleanPermissions) {
127  StartEmbeddedTestServer();
128  ASSERT_TRUE(RunExtensionSubtest(
129          "automation/tests/tabs_automation_boolean", "permissions.html"))
130      << message_;
131}
132
133IN_PROC_BROWSER_TEST_F(AutomationApiTest, TabsAutomationBooleanActions) {
134  StartEmbeddedTestServer();
135  ASSERT_TRUE(RunExtensionSubtest(
136          "automation/tests/tabs_automation_boolean", "actions.html"))
137      << message_;
138}
139
140IN_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)
148IN_PROC_BROWSER_TEST_F(AutomationApiTest, Desktop) {
149  ASSERT_TRUE(RunExtensionSubtest("automation/tests/desktop", "desktop.html"))
150      << message_;
151}
152
153IN_PROC_BROWSER_TEST_F(AutomationApiTest, DesktopNotRequested) {
154  ASSERT_TRUE(RunExtensionSubtest("automation/tests/tabs",
155                                  "desktop_not_requested.html")) << message_;
156}
157
158IN_PROC_BROWSER_TEST_F(AutomationApiTest, DesktopActions) {
159  ASSERT_TRUE(RunExtensionSubtest("automation/tests/desktop", "actions.html"))
160      << message_;
161}
162#else
163IN_PROC_BROWSER_TEST_F(AutomationApiTest, DesktopNotSupported) {
164  ASSERT_TRUE(RunExtensionSubtest("automation/tests/desktop",
165                                  "desktop_not_supported.html")) << message_;
166}
167#endif
168
169IN_PROC_BROWSER_TEST_F(AutomationApiTest, CloseTab) {
170  StartEmbeddedTestServer();
171  ASSERT_TRUE(RunExtensionSubtest("automation/tests/tabs", "close_tab.html"))
172      << message_;
173}
174
175static const int kPid = 1;
176static const int kTab0Rid = 1;
177static const int kTab1Rid = 2;
178
179using content::BrowserContext;
180
181typedef ui::AXTreeSerializer<const ui::AXNode*> TreeSerializer;
182typedef 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
198class 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
311static 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.
320class 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()
359ExtensionFunction* 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.
367void 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.
385void 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.
421class 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()
484ExtensionFunction* FakeAutomationInternalPerformActionFunctionFactory() {
485  return new FakeAutomationInternalPerformActionFunction();
486}
487
488// http://crbug.com/396353
489IN_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