Update To 11.40.268.0
[platform/framework/web/crosswalk.git] / src / chrome / browser / extensions / api / automation / automation_apitest.cc
index 7630d16..8a3dac1 100644 (file)
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
+#include "base/files/file_path.h"
+#include "base/path_service.h"
+#include "base/strings/string_number_conversions.h"
+#include "chrome/browser/extensions/api/automation_internal/automation_util.h"
+#include "chrome/browser/extensions/chrome_extension_function.h"
 #include "chrome/browser/extensions/extension_apitest.h"
 #include "chrome/browser/ui/tabs/tab_strip_model.h"
+#include "chrome/common/chrome_paths.h"
 #include "chrome/common/chrome_switches.h"
+#include "chrome/common/extensions/api/automation_internal.h"
+#include "chrome/test/base/ui_test_utils.h"
+#include "content/public/browser/ax_event_notification_details.h"
 #include "content/public/browser/render_widget_host.h"
 #include "content/public/browser/render_widget_host_view.h"
 #include "content/public/browser/web_contents.h"
+#include "extensions/test/extension_test_message_listener.h"
+#include "net/dns/mock_host_resolver.h"
+#include "net/test/embedded_test_server/embedded_test_server.h"
 #include "testing/gtest/include/gtest/gtest.h"
+#include "ui/accessibility/ax_node.h"
+#include "ui/accessibility/ax_serializable_tree.h"
+#include "ui/accessibility/ax_tree.h"
+#include "ui/accessibility/ax_tree_serializer.h"
+#include "ui/accessibility/tree_generator.h"
 
 namespace extensions {
 
+namespace {
+static const char kDomain[] = "a.com";
+static const char kSitesDir[] = "automation/sites";
+static const char kGotTree[] = "got_tree";
+}  // anonymous namespace
+
 class AutomationApiTest : public ExtensionApiTest {
- public:
-  virtual void SetUpCommandLine(CommandLine* command_line) OVERRIDE {
-    ExtensionApiTest::SetUpCommandLine(command_line);
-    command_line->AppendSwitch(::switches::kEnableAutomationAPI);
+ protected:
+  GURL GetURLForPath(const std::string& host, const std::string& path) {
+    std::string port = base::IntToString(embedded_test_server()->port());
+    GURL::Replacements replacements;
+    replacements.SetHostStr(host);
+    replacements.SetPortStr(port);
+    GURL url =
+        embedded_test_server()->GetURL(path).ReplaceComponents(replacements);
+    return url;
+  }
+
+  void StartEmbeddedTestServer() {
+    base::FilePath test_data;
+    ASSERT_TRUE(PathService::Get(chrome::DIR_TEST_DATA, &test_data));
+    embedded_test_server()->ServeFilesFromDirectory(
+        test_data.AppendASCII("extensions/api_test")
+        .AppendASCII(kSitesDir));
+    ASSERT_TRUE(ExtensionApiTest::StartEmbeddedTestServer());
+    host_resolver()->AddRule("*", embedded_test_server()->base_url().host());
   }
 
-  virtual void SetUpInProcessBrowserTestFixture() OVERRIDE {
+ public:
+  virtual void SetUpInProcessBrowserTestFixture() override {
     ExtensionApiTest::SetUpInProcessBrowserTestFixture();
   }
 };
 
-IN_PROC_BROWSER_TEST_F(AutomationApiTest, SanityCheck) {
+IN_PROC_BROWSER_TEST_F(AutomationApiTest, TestRendererAccessibilityEnabled) {
+  StartEmbeddedTestServer();
+  const GURL url = GetURLForPath(kDomain, "/index.html");
+  ui_test_utils::NavigateToURL(browser(), url);
+
   ASSERT_EQ(1, browser()->tab_strip_model()->count());
   content::WebContents* const tab =
       browser()->tab_strip_model()->GetWebContentsAt(0);
-  content::RenderWidgetHost* rwh =
-      tab->GetRenderWidgetHostView()->GetRenderWidgetHost();
-  ASSERT_NE((content::RenderWidgetHost*)NULL, rwh)
-      << "Couldn't get RenderWidgetHost";
-  ASSERT_FALSE(rwh->IsFullAccessibilityModeForTesting());
-  ASSERT_FALSE(rwh->IsTreeOnlyAccessibilityModeForTesting());
-
-  ASSERT_TRUE(RunComponentExtensionTest("automation/sanity_check")) << message_;
-
-  rwh = tab->GetRenderWidgetHostView()->GetRenderWidgetHost();
-  ASSERT_NE((content::RenderWidgetHost*)NULL, rwh)
-      << "Couldn't get RenderWidgetHost";
-  ASSERT_FALSE(rwh->IsFullAccessibilityModeForTesting());
-  ASSERT_TRUE(rwh->IsTreeOnlyAccessibilityModeForTesting());
+  ASSERT_FALSE(tab->IsFullAccessibilityModeForTesting());
+  ASSERT_FALSE(tab->IsTreeOnlyAccessibilityModeForTesting());
+
+  base::FilePath extension_path =
+      test_data_dir_.AppendASCII("automation/tests/basic");
+  ExtensionTestMessageListener got_tree(kGotTree, false /* no reply */);
+  LoadExtension(extension_path);
+  ASSERT_TRUE(got_tree.WaitUntilSatisfied());
+
+  ASSERT_FALSE(tab->IsFullAccessibilityModeForTesting());
+  ASSERT_TRUE(tab->IsTreeOnlyAccessibilityModeForTesting());
+}
+
+IN_PROC_BROWSER_TEST_F(AutomationApiTest, SanityCheck) {
+  StartEmbeddedTestServer();
+  ASSERT_TRUE(RunExtensionSubtest("automation/tests/tabs", "sanity_check.html"))
+      << message_;
+}
+
+IN_PROC_BROWSER_TEST_F(AutomationApiTest, Unit) {
+  ASSERT_TRUE(RunExtensionSubtest("automation/tests/unit", "unit.html"))
+      << message_;
+}
+
+IN_PROC_BROWSER_TEST_F(AutomationApiTest, GetTreeByTabId) {
+  StartEmbeddedTestServer();
+  ASSERT_TRUE(RunExtensionSubtest("automation/tests/tabs", "tab_id.html"))
+      << message_;
+}
+
+IN_PROC_BROWSER_TEST_F(AutomationApiTest, Events) {
+  StartEmbeddedTestServer();
+  ASSERT_TRUE(RunExtensionSubtest("automation/tests/tabs", "events.html"))
+      << message_;
+}
+
+IN_PROC_BROWSER_TEST_F(AutomationApiTest, Actions) {
+  StartEmbeddedTestServer();
+  ASSERT_TRUE(RunExtensionSubtest("automation/tests/tabs", "actions.html"))
+      << message_;
+}
+
+IN_PROC_BROWSER_TEST_F(AutomationApiTest, Location) {
+  StartEmbeddedTestServer();
+  ASSERT_TRUE(RunExtensionSubtest("automation/tests/tabs", "location.html"))
+      << message_;
+}
+
+IN_PROC_BROWSER_TEST_F(AutomationApiTest, TabsAutomationBooleanPermissions) {
+  StartEmbeddedTestServer();
+  ASSERT_TRUE(RunExtensionSubtest(
+          "automation/tests/tabs_automation_boolean", "permissions.html"))
+      << message_;
+}
+
+IN_PROC_BROWSER_TEST_F(AutomationApiTest, TabsAutomationBooleanActions) {
+  StartEmbeddedTestServer();
+  ASSERT_TRUE(RunExtensionSubtest(
+          "automation/tests/tabs_automation_boolean", "actions.html"))
+      << message_;
+}
+
+IN_PROC_BROWSER_TEST_F(AutomationApiTest, TabsAutomationHostsPermissions) {
+  StartEmbeddedTestServer();
+  ASSERT_TRUE(RunExtensionSubtest(
+          "automation/tests/tabs_automation_hosts", "permissions.html"))
+      << message_;
+}
+
+#if defined(OS_CHROMEOS)
+IN_PROC_BROWSER_TEST_F(AutomationApiTest, Desktop) {
+  ASSERT_TRUE(RunExtensionSubtest("automation/tests/desktop", "desktop.html"))
+      << message_;
+}
+
+IN_PROC_BROWSER_TEST_F(AutomationApiTest, DesktopNotRequested) {
+  ASSERT_TRUE(RunExtensionSubtest("automation/tests/tabs",
+                                  "desktop_not_requested.html")) << message_;
+}
+
+IN_PROC_BROWSER_TEST_F(AutomationApiTest, DesktopActions) {
+  ASSERT_TRUE(RunExtensionSubtest("automation/tests/desktop", "actions.html"))
+      << message_;
+}
+
+IN_PROC_BROWSER_TEST_F(AutomationApiTest, DesktopLoadTabs) {
+  ASSERT_TRUE(RunExtensionSubtest("automation/tests/desktop", "load_tabs.html"))
+      << message_;
+}
+#else
+IN_PROC_BROWSER_TEST_F(AutomationApiTest, DesktopNotSupported) {
+  ASSERT_TRUE(RunExtensionSubtest("automation/tests/desktop",
+                                  "desktop_not_supported.html")) << message_;
+}
+#endif
+
+IN_PROC_BROWSER_TEST_F(AutomationApiTest, CloseTab) {
+  StartEmbeddedTestServer();
+  ASSERT_TRUE(RunExtensionSubtest("automation/tests/tabs", "close_tab.html"))
+      << message_;
+}
+
+IN_PROC_BROWSER_TEST_F(AutomationApiTest, QuerySelector) {
+  StartEmbeddedTestServer();
+  ASSERT_TRUE(
+      RunExtensionSubtest("automation/tests/tabs", "queryselector.html"))
+      << message_;
+}
+
+static const int kPid = 1;
+static const int kTab0Rid = 1;
+static const int kTab1Rid = 2;
+
+using content::BrowserContext;
+
+typedef ui::AXTreeSerializer<const ui::AXNode*> TreeSerializer;
+typedef ui::AXTreeSource<const ui::AXNode*> TreeSource;
+
+#define AX_EVENT_ASSERT_EQUAL ui::AX_EVENT_LOAD_COMPLETE
+#define AX_EVENT_ASSERT_NOT_EQUAL ui::AX_EVENT_ACTIVEDESCENDANTCHANGED
+#define AX_EVENT_IGNORE ui::AX_EVENT_CHILDREN_CHANGED
+#define AX_EVENT_TEST_COMPLETE ui::AX_EVENT_BLUR
+
+// This test is based on ui/accessibility/ax_generated_tree_unittest.cc
+// However, because the tree updates need to be sent to the extension, we can't
+// use a straightforward set of nested loops as that test does, so this class
+// keeps track of where we're up to in our imaginary loops, while the extension
+// function classes below do the work of actually incrementing the state when
+// appropriate.
+// The actual deserialization and comparison happens in the API bindings and the
+// test extension respectively: see
+// c/t/data/extensions/api_test/automation/tests/generated/generated_trees.js
+class TreeSerializationState {
+ public:
+  TreeSerializationState()
+#ifdef NDEBUG
+      : tree_size(3),
+#else
+      : tree_size(2),
+#endif
+        generator(tree_size),
+        num_trees(generator.UniqueTreeCount()),
+        tree0_version(0),
+        tree1_version(0) {
+  }
+
+  // Serializes tree and sends it as an accessibility event to the extension.
+  void SendDataForTree(const ui::AXTree* tree,
+                       TreeSerializer* serializer,
+                       int routing_id,
+                       BrowserContext* browser_context) {
+    ui::AXTreeUpdate update;
+    serializer->SerializeChanges(tree->GetRoot(), &update);
+    SendUpdate(update,
+               ui::AX_EVENT_LAYOUT_COMPLETE,
+               tree->GetRoot()->id(),
+               routing_id,
+               browser_context);
+  }
+
+  // Sends the given AXTreeUpdate to the extension as an accessibility event.
+  void SendUpdate(ui::AXTreeUpdate update,
+                  ui::AXEvent event,
+                  int node_id,
+                  int routing_id,
+                  BrowserContext* browser_context) {
+    content::AXEventNotificationDetails detail(update.node_id_to_clear,
+                                               update.nodes,
+                                               event,
+                                               node_id,
+                                               kPid,
+                                               routing_id);
+    std::vector<content::AXEventNotificationDetails> details;
+    details.push_back(detail);
+    automation_util::DispatchAccessibilityEventsToAutomation(
+        details, browser_context, gfx::Vector2d());
+  }
+
+  // Notify the extension bindings to destroy the tree for the given tab
+  // (identified by routing_id)
+  void SendTreeDestroyedEvent(int routing_id, BrowserContext* browser_context) {
+    automation_util::DispatchTreeDestroyedEventToAutomation(
+        kPid, routing_id, browser_context);
+  }
+
+  // Reset tree0 to a new generated tree based on tree0_version, reset
+  // tree0_source accordingly.
+  void ResetTree0() {
+    tree0.reset(new ui::AXSerializableTree);
+    tree0_source.reset(tree0->CreateTreeSource());
+    generator.BuildUniqueTree(tree0_version, tree0.get());
+    if (!serializer0.get())
+      serializer0.reset(new TreeSerializer(tree0_source.get()));
+  }
+
+  // Reset tree0, set up serializer0, send down the initial tree data to create
+  // the tree in the extension
+  void InitializeTree0(BrowserContext* browser_context) {
+    ResetTree0();
+    serializer0->ChangeTreeSourceForTesting(tree0_source.get());
+    serializer0->Reset();
+    SendDataForTree(tree0.get(), serializer0.get(), kTab0Rid, browser_context);
+  }
+
+  // Reset tree1 to a new generated tree based on tree1_version, reset
+  // tree1_source accordingly.
+  void ResetTree1() {
+    tree1.reset(new ui::AXSerializableTree);
+    tree1_source.reset(tree1->CreateTreeSource());
+    generator.BuildUniqueTree(tree1_version, tree1.get());
+    if (!serializer1.get())
+      serializer1.reset(new TreeSerializer(tree1_source.get()));
+  }
+
+  // Reset tree1, set up serializer1, send down the initial tree data to create
+  // the tree in the extension
+  void InitializeTree1(BrowserContext* browser_context) {
+    ResetTree1();
+    serializer1->ChangeTreeSourceForTesting(tree1_source.get());
+    serializer1->Reset();
+    SendDataForTree(tree1.get(), serializer1.get(), kTab1Rid, browser_context);
+  }
+
+  const int tree_size;
+  const ui::TreeGenerator generator;
+
+  // The loop variables: comments indicate which variables in
+  // ax_generated_tree_unittest they correspond to.
+  const int num_trees; // n
+  int tree0_version;   // i
+  int tree1_version;   // j
+  int starting_node;   // k
+
+  // Tree infrastructure; tree0 and tree1 need to be regenerated whenever
+  // tree0_version and tree1_version change, respectively; tree0_source and
+  // tree1_source need to be reset whenever that happens.
+  scoped_ptr<ui::AXSerializableTree> tree0, tree1;
+  scoped_ptr<TreeSource> tree0_source, tree1_source;
+  scoped_ptr<TreeSerializer> serializer0, serializer1;
+
+  // Whether tree0 needs to be destroyed after the extension has performed its
+  // checks
+  bool destroy_tree0;
+};
+
+static TreeSerializationState state;
+
+// Override for chrome.automationInternal.enableTab
+// This fakes out the process and routing IDs for two "tabs", which contain the
+// source and target trees, respectively, and sends down the current tree for
+// the requested tab - tab 1 always has tree1, and tab 0 starts with tree0
+// and then has a series of updates intended to translate tree0 to tree1.
+// Once all the updates have been sent, the extension asserts that both trees
+// are equivalent, and then one or both of the trees are reset to a new version.
+class FakeAutomationInternalEnableTabFunction
+    : public UIThreadExtensionFunction {
+ public:
+  FakeAutomationInternalEnableTabFunction() {}
+
+  ExtensionFunction::ResponseAction Run() override {
+    using api::automation_internal::EnableTab::Params;
+    scoped_ptr<Params> params(Params::Create(*args_));
+    EXTENSION_FUNCTION_VALIDATE(params.get());
+    if (!params->tab_id.get())
+      return RespondNow(Error("tab_id not specified"));
+    int tab_id = *params->tab_id;
+    if (tab_id == 0) {
+      // tab 0 <--> tree0
+      base::MessageLoop::current()->PostTask(
+          FROM_HERE,
+          base::Bind(&TreeSerializationState::InitializeTree0,
+                     base::Unretained(&state),
+                     base::Unretained(browser_context())));
+      // TODO(aboxhall): Need to rewrite this test in terms of tree ids.
+      return RespondNow(ArgumentList(
+          api::automation_internal::EnableTab::Results::Create(0)));
+    }
+    if (tab_id == 1) {
+      // tab 1 <--> tree1
+      base::MessageLoop::current()->PostTask(
+          FROM_HERE,
+          base::Bind(&TreeSerializationState::InitializeTree1,
+                     base::Unretained(&state),
+                     base::Unretained(browser_context())));
+      return RespondNow(ArgumentList(
+          api::automation_internal::EnableTab::Results::Create(0)));
+    }
+    return RespondNow(Error("Unrecognised tab_id"));
+  }
+};
+
+// Factory method for use in OverrideFunction()
+ExtensionFunction* FakeAutomationInternalEnableTabFunctionFactory() {
+  return new FakeAutomationInternalEnableTabFunction();
+}
+
+// Helper method to serialize a series of updates via source_serializer to
+// transform the tree which source_serializer was initialized from into
+// target_tree, and then trigger the test code to assert the two tabs contain
+// the same tree.
+void TransformTree(TreeSerializer* source_serializer,
+                   ui::AXTree* target_tree,
+                   TreeSource* target_tree_source,
+                   content::BrowserContext* browser_context) {
+  source_serializer->ChangeTreeSourceForTesting(target_tree_source);
+  for (int node_delta = 0; node_delta < state.tree_size; ++node_delta) {
+    int id = 1 + (state.starting_node + node_delta) % state.tree_size;
+    ui::AXTreeUpdate update;
+    source_serializer->SerializeChanges(target_tree->GetFromId(id), &update);
+    bool is_last_update = node_delta == state.tree_size - 1;
+    ui::AXEvent event =
+        is_last_update ? AX_EVENT_ASSERT_EQUAL : AX_EVENT_IGNORE;
+    state.SendUpdate(
+        update, event, target_tree->GetRoot()->id(), kTab0Rid, browser_context);
+  }
+}
+
+// Helper method to send a no-op tree update to tab 0 with the given event.
+void SendEvent(ui::AXEvent event, content::BrowserContext* browser_context) {
+  ui::AXTreeUpdate update;
+  ui::AXNode* root = state.tree0->GetRoot();
+  state.serializer0->SerializeChanges(root, &update);
+  state.SendUpdate(update, event, root->id(), kTab0Rid, browser_context);
+}
+
+// Override for chrome.automationInternal.performAction
+// This is used as a synchronization mechanism; the general flow is:
+// 1. The extension requests tree0 and tree1 (on tab 0 and tab 1 respectively)
+// 2. FakeAutomationInternalEnableTabFunction sends down the trees
+// 3. When the callback for getTree(0) fires, the extension calls doDefault() on
+//    the root node of tree0, which calls into this class's Run() method.
+// 4. In the normal case, we're in the "inner loop" (iterating over
+//    starting_node). For each value of starting_node, we do the following:
+//    a. Serialize a sequence of updates which should transform tree0 into
+//       tree1. Each of these updates is sent as a childrenChanged event,
+//       except for the last which is sent as a loadComplete event.
+//    b. state.destroy_tree0 is set to true
+//    c. state.starting_node gets incremented
+//    d. The loadComplete event triggers an assertion in the extension.
+//    e. The extension performs another doDefault() on the root node of the
+//       tree.
+//    f. This time, we send a destroy event to tab0, so that the tree can be
+//       reset.
+//    g. The extension is notified of the tree's destruction and requests the
+//       tree for tab 0 again, returning to step 2.
+// 5. When starting_node exceeds state.tree_size, we increment tree0_version if
+//    it would not exceed state.num_trees, or increment tree1_version and reset
+//    tree0_version to 0 otherwise, and reset starting_node to 0.
+//    Then we reset one or both trees as appropriate, and send down destroyed
+//    events similarly, causing the extension to re-request the tree and going
+//    back to step 2 again.
+// 6. When tree1_version has gone through all possible values, we send a blur
+//    event, signaling the extension to call chrome.test.succeed() and finish
+//    the test.
+class FakeAutomationInternalPerformActionFunction
+    : public UIThreadExtensionFunction {
+ public:
+  FakeAutomationInternalPerformActionFunction() {}
+
+  ExtensionFunction::ResponseAction Run() override {
+    if (state.destroy_tree0) {
+      // Step 4.f: tell the extension to destroy the tree and re-request it.
+      state.SendTreeDestroyedEvent(kTab0Rid, browser_context());
+      state.destroy_tree0 = false;
+      return RespondNow(NoArguments());
+    }
+
+    TreeSerializer* serializer0 = state.serializer0.get();
+    if (state.starting_node < state.tree_size) {
+      // As a sanity check, if the trees are not equal, assert that they are not
+      // equal before serializing changes.
+      if (state.tree0_version != state.tree1_version)
+        SendEvent(AX_EVENT_ASSERT_NOT_EQUAL, browser_context());
+
+      // Step 4.a: pretend that tree0 turned into tree1, and serialize
+      // a sequence of updates to tab 0 to match.
+      TransformTree(serializer0,
+                    state.tree1.get(),
+                    state.tree1_source.get(),
+                    browser_context());
+
+      // Step 4.b: remember that we need to tell the extension to destroy and
+      // re-request the tree on the next action.
+      state.destroy_tree0 = true;
+
+      // Step 4.c: increment starting_node.
+      state.starting_node++;
+    } else if (state.tree0_version < state.num_trees - 1) {
+      // Step 5: Increment tree0_version and reset starting_node
+      state.tree0_version++;
+      state.starting_node = 0;
+
+      // Step 5: Reset tree0 and tell the extension to destroy and re-request it
+      state.SendTreeDestroyedEvent(kTab0Rid, browser_context());
+    } else if (state.tree1_version < state.num_trees - 1) {
+      // Step 5: Increment tree1_version and reset tree0_version and
+      // starting_node
+      state.tree1_version++;
+      state.tree0_version = 0;
+      state.starting_node = 0;
+
+      // Step 5: Reset tree0 and tell the extension to destroy and re-request it
+      state.SendTreeDestroyedEvent(kTab0Rid, browser_context());
+
+      // Step 5: Reset tree1 and tell the extension to destroy and re-request it
+      state.SendTreeDestroyedEvent(kTab1Rid, browser_context());
+    } else {
+      // Step 6: Send a TEST_COMPLETE (blur) event to signal the extension to
+      // call chrome.test.succeed().
+      SendEvent(AX_EVENT_TEST_COMPLETE, browser_context());
+    }
+
+    return RespondNow(NoArguments());
+  }
+};
+
+// Factory method for use in OverrideFunction()
+ExtensionFunction* FakeAutomationInternalPerformActionFunctionFactory() {
+  return new FakeAutomationInternalPerformActionFunction();
+}
+
+// http://crbug.com/396353
+IN_PROC_BROWSER_TEST_F(AutomationApiTest, DISABLED_GeneratedTree) {
+  ASSERT_TRUE(extensions::ExtensionFunctionDispatcher::OverrideFunction(
+      "automationInternal.enableTab",
+      FakeAutomationInternalEnableTabFunctionFactory));
+  ASSERT_TRUE(extensions::ExtensionFunctionDispatcher::OverrideFunction(
+      "automationInternal.performAction",
+      FakeAutomationInternalPerformActionFunctionFactory));
+  ASSERT_TRUE(RunExtensionSubtest("automation/tests/generated",
+                                  "generated_trees.html")) << message_;
 }
 
 }  // namespace extensions