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