text: // Copyright 2013 The Chromium Authors. All rights reserved.
  // Use of this source code is governed by a BSD-style license that can be
  // found in the LICENSE file.
  
  #include "chrome/test/remoting/remote_desktop_browsertest.h"
  
  #include "base/command_line.h"
  #include "base/files/file_util.h"
  #include "base/json/json_reader.h"
  #include "base/path_service.h"
  #include "chrome/browser/extensions/extension_service.h"
  #include "chrome/browser/extensions/unpacked_installer.h"
  #include "chrome/browser/ui/extensions/application_launch.h"
  #include "chrome/common/chrome_switches.h"
  #include "chrome/test/remoting/key_code_conv.h"
  #include "chrome/test/remoting/page_load_notification_observer.h"
  #include "chrome/test/remoting/waiter.h"
  #include "content/public/browser/native_web_keyboard_event.h"
  #include "content/public/browser/render_view_host.h"
  #include "content/public/test/test_utils.h"
  #include "extensions/common/constants.h"
  #include "extensions/common/extension.h"
  #include "extensions/common/extension_set.h"
  #include "extensions/common/switches.h"
  #include "ui/base/window_open_disposition.h"
  
  namespace remoting {
  
  RemoteDesktopBrowserTest::RemoteDesktopBrowserTest()
      : extension_(NULL) {
  }
  
  RemoteDesktopBrowserTest::~RemoteDesktopBrowserTest() {}
  
  void RemoteDesktopBrowserTest::SetUp() {
    ParseCommandLine();
    PlatformAppBrowserTest::SetUp();
  }
  
  void RemoteDesktopBrowserTest::SetUpOnMainThread() {
    PlatformAppBrowserTest::SetUpOnMainThread();
  
    // Pushing the initial WebContents instance onto the stack before
    // RunTestOnMainThread() and after |InProcessBrowserTest::browser_|
    // is initialized in InProcessBrowserTest::RunTestOnMainThreadLoop()
    web_contents_stack_.push_back(
        browser()->tab_strip_model()->GetActiveWebContents());
  }
  
  // Change behavior of the default host resolver to avoid DNS lookup errors,
  // so we can make network calls.
  void RemoteDesktopBrowserTest::SetUpInProcessBrowserTestFixture() {
    // The resolver object lifetime is managed by sync_test_setup, not here.
    EnableDNSLookupForThisTest(
        new net::RuleBasedHostResolverProc(host_resolver()));
  }
  
  void RemoteDesktopBrowserTest::TearDownInProcessBrowserTestFixture() {
    DisableDNSLookupForThisTest();
  }
  
  void RemoteDesktopBrowserTest::VerifyInternetAccess() {
    ui_test_utils::NavigateToURLBlockUntilNavigationsComplete(
        browser(), GURL("http://www.google.com"), 1);
  
    EXPECT_EQ(GetCurrentURL().host(), "www.google.com");
  }
  
  void RemoteDesktopBrowserTest::OpenClientBrowserPage() {
    // Open the client browser page in a new tab
    ui_test_utils::NavigateToURLWithDisposition(
        browser(),
        GURL(http_server() + "/clientpage.html"),
        NEW_FOREGROUND_TAB, ui_test_utils::BROWSER_TEST_WAIT_FOR_TAB);
  
    // Save this web content for later reference
    client_web_content_ = browser()->tab_strip_model()->GetActiveWebContents();
  
    // Go back to the previous tab that has chromoting opened
    browser()->tab_strip_model()->SelectPreviousTab();
  }
  
  bool RemoteDesktopBrowserTest::HtmlElementVisible(const std::string& name) {
    _ASSERT_TRUE(HtmlElementExists(name));
  
    ExecuteScript(
        "function isElementVisible(name) {"
        "  var element = document.getElementById(name);"
        "  /* The existence of the element has already been ASSERTed. */"
        "  do {"
        "    if (element.hidden) {"
        "      return false;"
        "    }"
        "    element = element.parentNode;"
        "  } while (element != null);"
        "  return true;"
        "};");
  
    return ExecuteScriptAndExtractBool(
        "isElementVisible(\"" + name + "\")");
  }
  
  void RemoteDesktopBrowserTest::InstallChromotingAppCrx() {
    ASSERT_TRUE(!is_unpacked());
  
    base::FilePath install_dir(WebAppCrxPath());
    scoped_refptr<const Extension> extension(InstallExtensionWithUIAutoConfirm(
        install_dir, 1, browser()));
  
    EXPECT_FALSE(extension.get() == NULL);
  
    extension_ = extension.get();
  }
  
  void RemoteDesktopBrowserTest::InstallChromotingAppUnpacked() {
    ASSERT_TRUE(is_unpacked());
  
    scoped_refptr<extensions::UnpackedInstaller> installer =
        extensions::UnpackedInstaller::Create(extension_service());
    installer->set_prompt_for_plugins(false);
  
    content::WindowedNotificationObserver observer(
        extensions::NOTIFICATION_EXTENSION_LOADED_DEPRECATED,
        content::NotificationService::AllSources());
  
    installer->Load(webapp_unpacked_);
  
    observer.Wait();
  }
  
  void RemoteDesktopBrowserTest::UninstallChromotingApp() {
    UninstallExtension(ChromotingID());
    extension_ = NULL;
  }
  
  void RemoteDesktopBrowserTest::VerifyChromotingLoaded(bool expected) {
    const extensions::ExtensionSet* extensions =
        extension_service()->extensions();
    scoped_refptr<const extensions::Extension> extension;
    bool installed = false;
  
    for (extensions::ExtensionSet::const_iterator iter = extensions->begin();
         iter != extensions->end(); ++iter) {
      extension = *iter;
      // Is there a better way to recognize the chromoting extension
      // than name comparison?
      if (extension->name() == extension_name_) {
        installed = true;
        break;
      }
    }
  
    if (installed) {
      if (extension_)
        EXPECT_EQ(extension.get(), extension_);
      else
        extension_ = extension.get();
  
      // Either a V1 (TYPE_LEGACY_PACKAGED_APP) or a V2 (TYPE_PLATFORM_APP ) app.
      extensions::Manifest::Type type = extension->GetType();
      EXPECT_TRUE(type == extensions::Manifest::TYPE_PLATFORM_APP ||
                  type == extensions::Manifest::TYPE_LEGACY_PACKAGED_APP);
  
      EXPECT_TRUE(extension->ShouldDisplayInAppLauncher());
    }
  
    ASSERT_EQ(installed, expected);
  }
  
  void RemoteDesktopBrowserTest::LaunchChromotingApp() {
    ASSERT_TRUE(extension_);
  
    GURL chromoting_main = Chromoting_Main_URL();
    // We cannot simply wait for any page load because the first page
    // loaded could be the generated background page. We need to wait
    // till the chromoting main page is loaded.
    PageLoadNotificationObserver observer(chromoting_main);
  
    OpenApplication(AppLaunchParams(
        browser()->profile(),
        extension_,
        is_platform_app() ? extensions::LAUNCH_CONTAINER_NONE :
            extensions::LAUNCH_CONTAINER_TAB,
        is_platform_app() ? NEW_WINDOW : CURRENT_TAB));
  
    observer.Wait();
  
  
    // The active WebContents instance should be the source of the LOAD_STOP
    // notification.
    content::NavigationController* controller =
        content::Source<content::NavigationController>(observer.source()).ptr();
  
    content::WebContents* web_contents = controller->GetWebContents();
    if (web_contents != active_web_contents())
      web_contents_stack_.push_back(web_contents);
  
    app_web_content_ = web_contents;
  
    if (is_platform_app()) {
      EXPECT_EQ(GetFirstAppWindowWebContents(), active_web_contents());
    } else {
      // For apps v1 only, the DOMOperationObserver is not ready at the LOAD_STOP
      // event. A half second wait is necessary for the subsequent javascript
      // injection to work.
      // TODO(weitaosu): Find out whether there is a more appropriate notification
      // to wait for so we can get rid of this wait.
      ASSERT_TRUE(TimeoutWaiter(base::TimeDelta::FromSeconds(5)).Wait());
    }
  
    EXPECT_EQ(Chromoting_Main_URL(), GetCurrentURL());
  }
  
  void RemoteDesktopBrowserTest::Authorize() {
    // The chromoting extension should be installed.
    ASSERT_TRUE(extension_);
  
    // The chromoting main page should be loaded in the current tab
    // and isAuthenticated() should be false (auth dialog visible).
    ASSERT_EQ(Chromoting_Main_URL(), GetCurrentURL());
    ASSERT_FALSE(IsAuthenticated());
  
    // The second observer monitors the loading of the Google login page.
    // Unfortunately we cannot specify a source in this observer because
    // we can't get a handle of the new window until the first observer
    // has finished waiting. But we will assert that the source of the
    // load stop event is indeed the newly created browser window.
    content::WindowedNotificationObserver observer(
        content::NOTIFICATION_LOAD_STOP,
        content::NotificationService::AllSources());
  
    ClickOnControl("auth-button");
  
    observer.Wait();
  
    content::NavigationController* controller =
        content::Source<content::NavigationController>(observer.source()).ptr();
  
    web_contents_stack_.push_back(controller->GetWebContents());
  
    // Verify the active tab is at the "Google Accounts" login page.
    EXPECT_EQ("accounts.google.com", GetCurrentURL().host());
    EXPECT_TRUE(HtmlElementExists("Email"));
    EXPECT_TRUE(HtmlElementExists("Passwd"));
  }
  
  void RemoteDesktopBrowserTest::Authenticate() {
    // The chromoting extension should be installed.
    ASSERT_TRUE(extension_);
  
    // The active tab should have the "Google Accounts" login page loaded.
    ASSERT_EQ("accounts.google.com", GetCurrentURL().host());
    ASSERT_TRUE(HtmlElementExists("Email"));
    ASSERT_TRUE(HtmlElementExists("Passwd"));
  
    // Now log in using the username and password passed in from the command line.
    ExecuteScriptAndWaitForAnyPageLoad(
        "document.getElementById(\"Email\").value = \"" + username_ + "\";" +
        "document.getElementById(\"Passwd\").value = \"" + password_ +"\";" +
        "document.forms[\"gaia_loginform\"].submit();");
  
    // TODO(weitaosu): Is there a better way to verify we are on the
    // "Request for Permission" page?
    // V2 app won't ask for approval here because the chromoting test account
    // has already been granted permissions.
    if (!is_platform_app()) {
      EXPECT_EQ(GetCurrentURL().host(), "accounts.google.com");
      EXPECT_TRUE(HtmlElementExists("submit_approve_access"));
    }
  }
  
  void RemoteDesktopBrowserTest::Approve() {
    // The chromoting extension should be installed.
    ASSERT_TRUE(extension_);
  
    if (is_platform_app()) {
      // Popping the login window off the stack to return to the chromoting
      // window.
      web_contents_stack_.pop_back();
  
      // There is nothing for the V2 app to approve because the chromoting test
      // account has already been granted permissions.
      // TODO(weitaosu): Revoke the permission at the beginning of the test so
      // that we can test first-time experience here.
      ConditionalTimeoutWaiter waiter(
          base::TimeDelta::FromSeconds(7),
          base::TimeDelta::FromSeconds(1),
          base::Bind(
              &RemoteDesktopBrowserTest::IsAuthenticatedInWindow,
              active_web_contents()));
  
      ASSERT_TRUE(waiter.Wait());
    } else {
      ASSERT_EQ("accounts.google.com", GetCurrentURL().host());
  
      // Is there a better way to verify we are on the "Request for Permission"
      // page?
      ASSERT_TRUE(HtmlElementExists("submit_approve_access"));
  
      const GURL chromoting_main = Chromoting_Main_URL();
  
      // active_web_contents() is still the login window but the observer
      // should be set up to observe the chromoting window because that is
      // where we'll receive the message from the login window and reload the
      // chromoting app.
      content::WindowedNotificationObserver observer(
          content::NOTIFICATION_LOAD_STOP,
            base::Bind(
                &RemoteDesktopBrowserTest::IsAuthenticatedInWindow,
                browser()->tab_strip_model()->GetActiveWebContents()));
  
      // Click to Approve the web-app.
      ClickOnControl("submit_approve_access");
  
      observer.Wait();
  
      // Popping the login window off the stack to return to the chromoting
      // window.
      web_contents_stack_.pop_back();
    }
  
    ASSERT_TRUE(GetCurrentURL() == Chromoting_Main_URL());
  
    EXPECT_TRUE(IsAuthenticated());
  }
  
  void RemoteDesktopBrowserTest::ExpandMe2Me() {
    // The chromoting extension should be installed.
    ASSERT_TRUE(extension_);
  
    // The active tab should have the chromoting app loaded.
    ASSERT_EQ(Chromoting_Main_URL(), GetCurrentURL());
    EXPECT_TRUE(IsAuthenticated());
  
    // The Me2Me host list should be hidden.
    ASSERT_FALSE(HtmlElementVisible("me2me-content"));
    // The Me2Me "Get Start" button should be visible.
    ASSERT_TRUE(HtmlElementVisible("get-started-me2me"));
  
    // Starting Me2Me.
    ExecuteScript("remoting.showMe2MeUiAndSave();");
  
    EXPECT_TRUE(HtmlElementVisible("me2me-content"));
    EXPECT_FALSE(HtmlElementVisible("me2me-first-run"));
  }
  
  void RemoteDesktopBrowserTest::DisconnectMe2Me() {
    // The chromoting extension should be installed.
    ASSERT_TRUE(extension_);
  
    ASSERT_TRUE(RemoteDesktopBrowserTest::IsSessionConnected());
  
    ClickOnControl("toolbar-stub");
  
    EXPECT_TRUE(HtmlElementVisible("session-toolbar"));
  
    ClickOnControl("toolbar-disconnect");
  
    EXPECT_TRUE(HtmlElementVisible("client-dialog"));
    EXPECT_TRUE(HtmlElementVisible("client-reconnect-button"));
    EXPECT_TRUE(HtmlElementVisible("client-finished-me2me-button"));
  
    ClickOnControl("client-finished-me2me-button");
  
    EXPECT_FALSE(HtmlElementVisible("client-dialog"));
  }
  
  void RemoteDesktopBrowserTest::SimulateKeyPressWithCode(
      ui::KeyboardCode keyCode,
      const char* code) {
    SimulateKeyPressWithCode(keyCode, code, false, false, false, false);
  }
  
  void RemoteDesktopBrowserTest::SimulateKeyPressWithCode(
      ui::KeyboardCode keyCode,
      const char* code,
      bool control,
      bool shift,
      bool alt,
      bool command) {
    content::SimulateKeyPressWithCode(
        active_web_contents(),
        keyCode,
        code,
        control,
        shift,
        alt,
        command);
  }
  
  void RemoteDesktopBrowserTest::SimulateCharInput(char c) {
    const char* code;
    ui::KeyboardCode keyboard_code;
    bool shift;
    GetKeyValuesFromChar(c, &code, &keyboard_code, &shift);
    ASSERT_TRUE(code != NULL);
    SimulateKeyPressWithCode(keyboard_code, code, false, shift, false, false);
  }
  
  void RemoteDesktopBrowserTest::SimulateStringInput(const std::string& input) {
    for (size_t i = 0; i < input.length(); ++i)
      SimulateCharInput(input[i]);
  }
  
  void RemoteDesktopBrowserTest::SimulateMouseLeftClickAt(int x, int y) {
    SimulateMouseClickAt(0, blink::WebMouseEvent::ButtonLeft, x, y);
  }
  
  void RemoteDesktopBrowserTest::SimulateMouseClickAt(
      int modifiers, blink::WebMouseEvent::Button button, int x, int y) {
    // TODO(weitaosu): The coordinate translation doesn't work correctly for
    // apps v2.
    ExecuteScript(
        "var clientPluginElement = "
             "document.getElementById('session-client-plugin');"
        "var clientPluginRect = clientPluginElement.getBoundingClientRect();");
  
    int top = ExecuteScriptAndExtractInt("clientPluginRect.top");
    int left = ExecuteScriptAndExtractInt("clientPluginRect.left");
    int width = ExecuteScriptAndExtractInt("clientPluginRect.width");
    int height = ExecuteScriptAndExtractInt("clientPluginRect.height");
  
    ASSERT_GT(x, 0);
    ASSERT_LT(x, width);
    ASSERT_GT(y, 0);
    ASSERT_LT(y, height);
  
    content::SimulateMouseClickAt(
        browser()->tab_strip_model()->GetActiveWebContents(),
        modifiers,
        button,
        gfx::Point(left + x, top + y));
  }
  
  void RemoteDesktopBrowserTest::Install() {
    if (!NoInstall()) {
      VerifyChromotingLoaded(false);
      if (is_unpacked())
        InstallChromotingAppUnpacked();
      else
        InstallChromotingAppCrx();
    }
  
    VerifyChromotingLoaded(true);
  }
  
  void RemoteDesktopBrowserTest::Cleanup() {
    // TODO(weitaosu): Remove this hack by blocking on the appropriate
    // notification.
    // The browser may still be loading images embedded in the webapp. If we
    // uinstall it now those load will fail.
    ASSERT_TRUE(TimeoutWaiter(base::TimeDelta::FromSeconds(2)).Wait());
  
    if (!NoCleanup()) {
      UninstallChromotingApp();
      VerifyChromotingLoaded(false);
    }
  
    // TODO(chaitali): Remove this additional timeout after we figure out
    // why this is needed for the v1 app to work.
    // Without this timeout the test fail with a "CloseWebContents called for
    // tab not in our strip" error for the v1 app.
    ASSERT_TRUE(TimeoutWaiter(base::TimeDelta::FromSeconds(2)).Wait());
  }
  
  void RemoteDesktopBrowserTest::SetUpTestForMe2Me() {
    VerifyInternetAccess();
    Install();
    LaunchChromotingApp();
    Auth();
    LoadScript(app_web_content(), FILE_PATH_LITERAL("browser_test.js"));
    ExpandMe2Me();
    EnsureRemoteConnectionEnabled();
  }
  
  void RemoteDesktopBrowserTest::Auth() {
    // For this test, we must be given the user-name and password.
    ASSERT_TRUE(!username_.empty() && !password_.empty());
  
    Authorize();
    Authenticate();
    Approve();
  }
  
  void RemoteDesktopBrowserTest::EnsureRemoteConnectionEnabled() {
    // browser_test.ensureRemoteConnectionEnabled is defined in
    // browser_test.js, which must be loaded before calling this function.
    // TODO(kelvinp): This function currently only works on linux when the user is
    // already part of the chrome-remote-desktop group.  Extend this functionality
    // to Mac (https://crbug.com/397576) and Windows (https://crbug.com/397575).
    bool result;
    EXPECT_TRUE(content::ExecuteScriptAndExtractBool(
        app_web_content(),
        "browserTest.ensureRemoteConnectionEnabled(" + me2me_pin() + ")",
        &result));
    EXPECT_TRUE(result) << "Cannot start the host with Pin:" << me2me_pin();
  }
  
  void RemoteDesktopBrowserTest::ConnectToLocalHost(bool remember_pin) {
    // Verify that the local host is online.
    ASSERT_TRUE(ExecuteScriptAndExtractBool(
        "remoting.hostList.localHost_.hostName && "
        "remoting.hostList.localHost_.hostId && "
        "remoting.hostList.localHost_.status && "
        "remoting.hostList.localHost_.status == 'ONLINE'"));
  
    // Connect.
    ClickOnControl("this-host-connect");
  
    // Enter the pin # passed in from the command line.
    EnterPin(me2me_pin(), remember_pin);
  
    WaitForConnection();
  }
  
  void RemoteDesktopBrowserTest::ConnectToRemoteHost(
      const std::string& host_name, bool remember_pin) {
    std::string host_id = ExecuteScriptAndExtractString(
        "remoting.hostList.getHostIdForName('" + host_name + "')");
  
    EXPECT_FALSE(host_id.empty());
    std::string element_id = "host_" + host_id;
  
    // Verify the host is online.
    std::string host_div_class = ExecuteScriptAndExtractString(
        "document.getElementById('" + element_id + "').parentNode.className");
    EXPECT_NE(std::string::npos, host_div_class.find("host-online"));
  
    ClickOnControl(element_id);
  
    // Enter the pin # passed in from the command line.
    EnterPin(me2me_pin(), remember_pin);
  
    WaitForConnection();
  }
  
  void RemoteDesktopBrowserTest::EnableDNSLookupForThisTest(
      net::RuleBasedHostResolverProc* host_resolver) {
    // mock_host_resolver_override_ takes ownership of the resolver.
    scoped_refptr<net::RuleBasedHostResolverProc> resolver =
        new net::RuleBasedHostResolverProc(host_resolver);
    resolver->AllowDirectLookup("*.google.com");
    // On Linux, we use Chromium's NSS implementation which uses the following
    // hosts for certificate verification. Without these overrides, running the
    // integration tests on Linux causes errors as we make external DNS lookups.
    resolver->AllowDirectLookup("*.thawte.com");
    resolver->AllowDirectLookup("*.geotrust.com");
    resolver->AllowDirectLookup("*.gstatic.com");
    resolver->AllowDirectLookup("*.googleapis.com");
    mock_host_resolver_override_.reset(
        new net::ScopedDefaultHostResolverProc(resolver.get()));
  }
  
  void RemoteDesktopBrowserTest::DisableDNSLookupForThisTest() {
    mock_host_resolver_override_.reset();
  }
  
  void RemoteDesktopBrowserTest::ParseCommandLine() {
    CommandLine* command_line = CommandLine::ForCurrentProcess();
  
    // The test framework overrides any command line user-data-dir
    // argument with a /tmp/.org.chromium.Chromium.XXXXXX directory.
    // That happens in the ChromeTestLauncherDelegate, and affects
    // all unit tests (no opt out available). It intentionally erases
    // any --user-data-dir switch if present and appends a new one.
    // Re-override the default data dir if override-user-data-dir
    // is specified.
    if (command_line->HasSwitch(kOverrideUserDataDir)) {
      const base::FilePath& override_user_data_dir =
          command_line->GetSwitchValuePath(kOverrideUserDataDir);
  
      ASSERT_FALSE(override_user_data_dir.empty());
  
      command_line->AppendSwitchPath(switches::kUserDataDir,
                                     override_user_data_dir);
    }
  
    CommandLine::StringType accounts_file =
        command_line->GetSwitchValueNative(kAccountsFile);
    std::string account_type = command_line->GetSwitchValueASCII(kAccountType);
    if (!accounts_file.empty()) {
      // We've been passed in a file containing accounts information.
      // In this case, we'll obtain the user-name and password information from
      // the specified file, even if user-name and password have been specified
      // on the command-line.
      base::FilePath accounts_file_path((base::FilePath(accounts_file)));
      ASSERT_FALSE(account_type.empty());
      ASSERT_TRUE(base::PathExists((base::FilePath(accounts_file))));
      SetUserNameAndPassword((base::FilePath(accounts_file)), account_type);
    } else {
      // No file for accounts specified. Read user-name and password from command
      // line.
      username_ = command_line->GetSwitchValueASCII(kUserName);
      password_ = command_line->GetSwitchValueASCII(kUserPassword);
    }
  
    me2me_pin_ = command_line->GetSwitchValueASCII(kMe2MePin);
    remote_host_name_ = command_line->GetSwitchValueASCII(kRemoteHostName);
    extension_name_ = command_line->GetSwitchValueASCII(kExtensionName);
    http_server_ = command_line->GetSwitchValueASCII(kHttpServer);
  
    no_cleanup_ = command_line->HasSwitch(kNoCleanup);
    no_install_ = command_line->HasSwitch(kNoInstall);
  
    if (!no_install_) {
      webapp_crx_ = command_line->GetSwitchValuePath(kWebAppCrx);
      webapp_unpacked_ = command_line->GetSwitchValuePath(kWebAppUnpacked);
      // One and only one of these two arguments should be provided.
      ASSERT_NE(webapp_crx_.empty(), webapp_unpacked_.empty());
    }
  
    // Run with "enable-web-based-signin" flag to enforce web-based sign-in,
    // rather than inline signin. This ensures we use the same authentication
    // page, regardless of whether we are testing the v1 or v2 web-app.
    command_line->AppendSwitch(switches::kEnableWebBasedSignin);
  
    // Enable experimental extensions; this is to allow adding the LG extensions
    command_line->AppendSwitch(
      extensions::switches::kEnableExperimentalExtensionApis);
  }
  
  void RemoteDesktopBrowserTest::ExecuteScript(const std::string& script) {
    ASSERT_TRUE(content::ExecuteScript(active_web_contents(), script));
  }
  
  void RemoteDesktopBrowserTest::ExecuteScriptAndWaitForAnyPageLoad(
      const std::string& script) {
    content::WindowedNotificationObserver observer(
        content::NOTIFICATION_LOAD_STOP,
        content::Source<content::NavigationController>(
            &active_web_contents()->
                GetController()));
  
    ExecuteScript(script);
  
    observer.Wait();
  }
  
  // static
  bool RemoteDesktopBrowserTest::ExecuteScriptAndExtractBool(
      content::WebContents* web_contents, const std::string& script) {
    bool result;
    EXPECT_TRUE(content::ExecuteScriptAndExtractBool(
        web_contents,
        "window.domAutomationController.send(" + script + ");",
        &result));
  
    return result;
  }
  
  // static
  int RemoteDesktopBrowserTest::ExecuteScriptAndExtractInt(
      content::WebContents* web_contents, const std::string& script) {
    int result;
    _ASSERT_TRUE(content::ExecuteScriptAndExtractInt(
        web_contents,
        "window.domAutomationController.send(" + script + ");",
        &result));
  
    return result;
  }
  
  // static
  std::string RemoteDesktopBrowserTest::ExecuteScriptAndExtractString(
      content::WebContents* web_contents, const std::string& script) {
    std::string result;
    _ASSERT_TRUE(content::ExecuteScriptAndExtractString(
        web_contents,
        "window.domAutomationController.send(" + script + ");",
        &result));
  
    return result;
  }
  
  // static
  bool RemoteDesktopBrowserTest::LoadScript(
      content::WebContents* web_contents,
      const base::FilePath::StringType& path) {
    std::string script;
    base::FilePath src_dir;
    _ASSERT_TRUE(PathService::Get(base::DIR_EXE, &src_dir));
    base::FilePath script_path = src_dir.Append(path);
  
    if (!base::ReadFileToString(script_path, &script)) {
      LOG(ERROR) << "Failed to load script " << script_path.value();
      return false;
    }
  
    return content::ExecuteScript(web_contents, script);
  }
  
  // static
  void RemoteDesktopBrowserTest::RunJavaScriptTest(
      content::WebContents* web_contents,
      const std::string& testName,
      const std::string& testData) {
    std::string result;
    std::string script = "browserTest.runTest(browserTest." + testName + ", " +
                         testData + ");";
  
    LOG(INFO) << "Executing " << script;
  
    ASSERT_TRUE(
        content::ExecuteScriptAndExtractString(web_contents, script, &result));
  
    // Read in the JSON
    base::JSONReader reader;
    scoped_ptr<base::Value> value;
    value.reset(reader.Read(result, base::JSON_ALLOW_TRAILING_COMMAS));
  
    // Convert to dictionary
    base::DictionaryValue* dict_value = NULL;
    ASSERT_TRUE(value->GetAsDictionary(&dict_value));
  
    bool succeeded;
    std::string error_message;
    std::string stack_trace;
  
    // Extract the fields
    ASSERT_TRUE(dict_value->GetBoolean("succeeded", &succeeded));
    ASSERT_TRUE(dict_value->GetString("error_message", &error_message));
    ASSERT_TRUE(dict_value->GetString("stack_trace", &stack_trace));
  
    EXPECT_TRUE(succeeded) << error_message << "\n" << stack_trace;
  }
  
  void RemoteDesktopBrowserTest::ClickOnControl(const std::string& name) {
    ASSERT_TRUE(HtmlElementVisible(name));
  
    std::string has_disabled_attribute =
      "document.getElementById('" + name + "').hasAttribute('disabled')";
  
    if (ExecuteScriptAndExtractBool(active_web_contents(),
                                    has_disabled_attribute)) {
      // This element has a disabled attribute. Wait for it become enabled.
      ConditionalTimeoutWaiter waiter(
            base::TimeDelta::FromSeconds(5),
            base::TimeDelta::FromMilliseconds(500),
            base::Bind(&RemoteDesktopBrowserTest::IsEnabled,
              active_web_contents(), name));
      ASSERT_TRUE(waiter.Wait());
    }
  
    ExecuteScript("document.getElementById(\"" + name + "\").click();");
  }
  
  void RemoteDesktopBrowserTest::EnterPin(const std::string& pin,
                                          bool remember_pin) {
    // Wait for the pin-form to be displayed. This can take a while.
    // We also need to dismiss the host-needs-update dialog if it comes up.
    // TODO(weitaosu) 1: Instead of polling, can we register a callback to be
    // called when the pin-form is ready?
    // TODO(weitaosu) 2: Instead of blindly dismiss the host-needs-update dialog,
    // we should verify that it only pops up at the right circumstance. That
    // probably belongs in a separate test case though.
    ConditionalTimeoutWaiter waiter(
        base::TimeDelta::FromSeconds(30),
        base::TimeDelta::FromSeconds(1),
        base::Bind(&RemoteDesktopBrowserTest::IsPinFormVisible, this));
    EXPECT_TRUE(waiter.Wait());
  
    ExecuteScript(
        "document.getElementById(\"pin-entry\").value = \"" + pin + "\";");
  
    if (remember_pin) {
      EXPECT_TRUE(HtmlElementVisible("remember-pin"));
      EXPECT_FALSE(ExecuteScriptAndExtractBool(
          "document.getElementById('remember-pin-checkbox').checked"));
      ClickOnControl("remember-pin");
      EXPECT_TRUE(ExecuteScriptAndExtractBool(
          "document.getElementById('remember-pin-checkbox').checked"));
    }
  
    ClickOnControl("pin-connect-button");
  }
  
  void RemoteDesktopBrowserTest::WaitForConnection() {
    // Wait until the client has connected to the server.
    // This can take a while.
    // TODO(weitaosu): Instead of polling, can we register a callback to
    // remoting.clientSession.onStageChange_?
    ConditionalTimeoutWaiter waiter(
        base::TimeDelta::FromSeconds(30),
        base::TimeDelta::FromSeconds(1),
        base::Bind(&RemoteDesktopBrowserTest::IsSessionConnected, this));
    EXPECT_TRUE(waiter.Wait());
  
    // The client is not yet ready to take input when the session state becomes
    // CONNECTED. Wait for 2 seconds for the client to become ready.
    // TODO(weitaosu): Find a way to detect when the client is truly ready.
    TimeoutWaiter(base::TimeDelta::FromSeconds(2)).Wait();
  }
  
  bool RemoteDesktopBrowserTest::IsLocalHostReady() {
    // TODO(weitaosu): Instead of polling, can we register a callback to
    // remoting.hostList.setLocalHost_?
    return ExecuteScriptAndExtractBool("remoting.hostList.localHost_ != null");
  }
  
  bool RemoteDesktopBrowserTest::IsSessionConnected() {
    // If some form of PINless authentication is enabled, the host version
    // warning may appear while waiting for the session to connect.
    DismissHostVersionWarningIfVisible();
  
    return ExecuteScriptAndExtractBool(
        "remoting.clientSession != null && "
        "remoting.clientSession.getState() == "
        "remoting.ClientSession.State.CONNECTED");
  }
  
  bool RemoteDesktopBrowserTest::IsPinFormVisible() {
    DismissHostVersionWarningIfVisible();
    return HtmlElementVisible("pin-form");
  }
  
  void RemoteDesktopBrowserTest::DismissHostVersionWarningIfVisible() {
    if (HtmlElementVisible("host-needs-update-connect-button"))
      ClickOnControl("host-needs-update-connect-button");
  }
  
  void RemoteDesktopBrowserTest::SetUserNameAndPassword(
      const base::FilePath &accounts_file_path, const std::string& account_type) {
  
    // Read contents of accounts file, using its absolute path.
    base::FilePath absolute_path = base::MakeAbsoluteFilePath(accounts_file_path);
    std::string accounts_info;
    ASSERT_TRUE(base::ReadFileToString(absolute_path, &accounts_info));
  
    // Get the root dictionary from the input json file contents.
    scoped_ptr<base::Value> root(
        base::JSONReader::Read(accounts_info, base::JSON_ALLOW_TRAILING_COMMAS));
  
    const base::DictionaryValue* root_dict = NULL;
    ASSERT_TRUE(root.get() && root->GetAsDictionary(&root_dict));
  
    // Now get the dictionary for the specified account type.
    const base::DictionaryValue* account_dict = NULL;
    ASSERT_TRUE(root_dict->GetDictionary(account_type, &account_dict));
    ASSERT_TRUE(account_dict->GetString(kUserName, &username_));
    ASSERT_TRUE(account_dict->GetString(kUserPassword, &password_));
  }
  
  // static
  bool RemoteDesktopBrowserTest::IsAuthenticatedInWindow(
      content::WebContents* web_contents) {
    return ExecuteScriptAndExtractBool(
        web_contents, "remoting.identity.isAuthenticated()");
  }
  
  // static
  bool RemoteDesktopBrowserTest::IsHostActionComplete(
      content::WebContents* client_web_content,
      std::string host_action_var) {
    return ExecuteScriptAndExtractBool(
        client_web_content,
        host_action_var);
  }
  
  // static
  bool RemoteDesktopBrowserTest::IsEnabled(
      content::WebContents* client_web_content,
      const std::string& element_name) {
    return !ExecuteScriptAndExtractBool(
      client_web_content,
      "document.getElementById(\"" + element_name + "\").disabled");
  }
  
  }  // namespace remoting
  