root/chrome/test/remoting/remote_desktop_browsertest.cc

/* [<][>][^][v][top][bottom][index][help] */

DEFINITIONS

This source file includes following definitions.
  1. SetUp
  2. SetUpOnMainThread
  3. SetUpInProcessBrowserTestFixture
  4. TearDownInProcessBrowserTestFixture
  5. VerifyInternetAccess
  6. OpenClientBrowserPage
  7. HtmlElementVisible
  8. InstallChromotingAppCrx
  9. InstallChromotingAppUnpacked
  10. UninstallChromotingApp
  11. VerifyChromotingLoaded
  12. LaunchChromotingApp
  13. Authorize
  14. Authenticate
  15. Approve
  16. ExpandMe2Me
  17. DisconnectMe2Me
  18. SimulateKeyPressWithCode
  19. SimulateKeyPressWithCode
  20. SimulateCharInput
  21. SimulateStringInput
  22. SimulateMouseLeftClickAt
  23. SimulateMouseClickAt
  24. Install
  25. Cleanup
  26. Auth
  27. ConnectToLocalHost
  28. ConnectToRemoteHost
  29. EnableDNSLookupForThisTest
  30. DisableDNSLookupForThisTest
  31. ParseCommandLine
  32. ExecuteScript
  33. ExecuteScriptAndWaitForAnyPageLoad
  34. ExecuteScriptAndExtractBool
  35. ExecuteScriptAndExtractInt
  36. ExecuteScriptAndExtractString
  37. ClickOnControl
  38. EnterPin
  39. WaitForConnection
  40. IsLocalHostReady
  41. IsSessionConnected
  42. IsPinFormVisible
  43. DismissHostVersionWarningIfVisible
  44. IsAuthenticatedInWindow
  45. IsHostActionComplete

// 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 "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/common/extensions/extension_file_util.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(
      chrome::NOTIFICATION_EXTENSION_LOADED,
      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, 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);

  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()));

    ExecuteScript(
        "lso.approveButtonAction();"
        "document.forms[\"connect-approve\"].submit();");

    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"));

  // Wait until localHost is initialized. This can take a while.
  ConditionalTimeoutWaiter waiter(
      base::TimeDelta::FromSeconds(3),
      base::TimeDelta::FromSeconds(1),
      base::Bind(&RemoteDesktopBrowserTest::IsLocalHostReady, this));
  EXPECT_TRUE(waiter.Wait());

  EXPECT_TRUE(ExecuteScriptAndExtractBool(
      "remoting.hostList.localHost_.hostName && "
      "remoting.hostList.localHost_.hostId && "
      "remoting.hostList.localHost_.status && "
      "remoting.hostList.localHost_.status == 'ONLINE'"));
}

void RemoteDesktopBrowserTest::DisconnectMe2Me() {
  // The chromoting extension should be installed.
  ASSERT_TRUE(extension_);

  // The active tab should have the chromoting app loaded.
  ASSERT_EQ(Chromoting_Main_URL(), GetCurrentURL());
  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::Auth() {
  Authorize();
  Authenticate();
  Approve();
}

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);
  }

  username_ = command_line->GetSwitchValueASCII(kUsername);
  password_ = command_line->GetSwitchValueASCII(kkPassword);
  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;
}

void RemoteDesktopBrowserTest::ClickOnControl(const std::string& name) {
  ASSERT_TRUE(HtmlElementVisible(name));

  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(5),
      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(4),
      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");
}

// 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);
}

}  // namespace remoting

/* [<][>][^][v][top][bottom][index][help] */