// Copyright 2018 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 "content/browser/keyboard_lock_browsertest.h"

#include <string>

#include "base/macros.h"
#include "base/metrics/histogram_base.h"
#include "base/test/metrics/histogram_tester.h"
#include "base/test/scoped_feature_list.h"
#include "build/build_config.h"
#include "components/network_session_configurator/common/network_switches.h"
#include "content/browser/keyboard_lock/keyboard_lock_metrics.h"
#include "content/browser/renderer_host/render_widget_host_impl.h"
#include "content/browser/web_contents/web_contents_impl.h"
#include "content/public/common/content_features.h"
#include "content/public/test/browser_test_utils.h"
#include "content/public/test/content_browser_test.h"
#include "content/public/test/content_browser_test_utils.h"
#include "content/public/test/test_utils.h"
#include "content/shell/browser/shell.h"
#include "content/test/content_browser_test_utils_internal.h"
#include "net/dns/mock_host_resolver.h"
#include "net/test/embedded_test_server/embedded_test_server.h"
#include "third_party/blink/public/web/web_fullscreen_options.h"
#include "ui/events/keycodes/dom/dom_code.h"
#include "ui/events/keycodes/dom/keycode_converter.h"
#include "ui/events/keycodes/keyboard_code_conversion.h"
#include "ui/events/keycodes/keyboard_codes.h"
#include "ui/gfx/native_widget_types.h"

#ifdef USE_AURA
#include "content/browser/renderer_host/render_widget_host_view_aura.h"
#include "content/browser/web_contents/web_contents_view_aura.h"
#endif  // USE_AURA

namespace content {

namespace {

constexpr char kFullscreenFramePath[] = "/fullscreen_frame.html";

constexpr char kHelloFramePath[] = "/hello.html";

constexpr char kInputFieldFramePath[] = "/page_with_input_field.html";

// Set up a DOM structure which contains three inner iframes for testing:
// - Same domain iframe w/ fullscreen attribute.
// - Cross domain iframe.
// - Cross domain iframe w/ fullscreen attribute.
constexpr char kCrossSiteFramePath[] =
    "/cross_site_iframe_factory.html"
    "?a(a{allowfullscreen}(),b(),c{allowfullscreen}())";

constexpr char kCrossSiteTopLevelDomain[] = "a.com";

constexpr char kChildIframeName_0[] = "child-0";

constexpr char kChildIframeName_1[] = "child-1";

constexpr char kChildIframeName_2[] = "child-2";

constexpr char kCrossSiteChildDomain1[] = "b.com";

constexpr char kCrossSiteChildDomain2[] = "c.com";

constexpr char kKeyboardLockMethodExistanceCheck[] =
    "window.domAutomationController.send("
    "  (navigator.keyboard != undefined) &&"
    "  (navigator.keyboard.lock != undefined));";

constexpr char kKeyboardLockMethodCallWithAllKeys[] =
    "navigator.keyboard.lock().then("
    "  () => { window.domAutomationController.send(true); },"
    "  () => { window.domAutomationController.send(false); },"
    ");";

constexpr char kKeyboardLockMethodCallWithSomeKeys[] =
    "navigator.keyboard.lock(['MetaLeft', 'Tab', 'AltLeft']).then("
    "  () => { window.domAutomationController.send(true); },"
    "  () => { window.domAutomationController.send(false); },"
    ");";

// Calling lock() with no valid key codes will cause the promise to be rejected.
constexpr char kKeyboardLockMethodCallWithAllInvalidKeys[] =
    "navigator.keyboard.lock(['BlerghLeft', 'BlarghRight']).then("
    "  () => { window.domAutomationController.send(false); },"
    "  () => { window.domAutomationController.send(true); },"
    ");";

// Calling lock() with some invalid key codes will reject the promise.
constexpr char kKeyboardLockMethodCallWithSomeInvalidKeys[] =
    "navigator.keyboard.lock(['Tab', 'BlarghTab', 'Space', 'BlerghLeft']).then("
    "  () => { window.domAutomationController.send(false); },"
    "  () => { window.domAutomationController.send(true); },"
    ");";

constexpr char kKeyboardUnlockMethodCall[] = "navigator.keyboard.unlock()";

constexpr char kFocusInputFieldScript[] =
    "function onInput(e) {"
    "  domAutomationController.send(getInputFieldText());"
    "}"
    "inputField = document.getElementById('text-field');"
    "inputField.addEventListener('input', onInput, false);";

void SimulateKeyPress(WebContents* web_contents,
                      const std::string& code_string,
                      const std::string& expected_result) {
  DOMMessageQueue msg_queue;
  std::string reply;
  ui::DomKey dom_key = ui::KeycodeConverter::KeyStringToDomKey(code_string);
  ui::DomCode dom_code = ui::KeycodeConverter::CodeStringToDomCode(code_string);
  SimulateKeyPress(web_contents, dom_key, dom_code,
                   ui::DomCodeToUsLayoutKeyboardCode(dom_code), false, false,
                   false, false);
  ASSERT_TRUE(msg_queue.WaitForMessage(&reply));
  ASSERT_EQ("\"" + expected_result + "\"", reply);
}

#if defined(USE_AURA)

bool g_window_has_focus = false;

class TestRenderWidgetHostView : public RenderWidgetHostViewAura {
 public:
  TestRenderWidgetHostView(RenderWidgetHost* host, bool is_guest_view_hack)
      : RenderWidgetHostViewAura(host,
                                 is_guest_view_hack,
                                 false /* is_mus_browser_plugin_guest */) {}
  ~TestRenderWidgetHostView() override {}

  bool HasFocus() const override { return g_window_has_focus; }

  void OnWindowFocused(aura::Window* gained_focus,
                       aura::Window* lost_focus) override {
    // Ignore all focus events.
  }
};

#endif  // USE_AURA

class FakeKeyboardLockWebContentsDelegate : public WebContentsDelegate {
 public:
  FakeKeyboardLockWebContentsDelegate() {}
  ~FakeKeyboardLockWebContentsDelegate() override {}

  // WebContentsDelegate overrides.
  void EnterFullscreenModeForTab(
      WebContents* web_contents,
      const GURL& origin,
      const blink::WebFullscreenOptions& options) override;
  void ExitFullscreenModeForTab(WebContents* web_contents) override;
  bool IsFullscreenForTabOrPending(
      const WebContents* web_contents) const override;
  void RequestKeyboardLock(WebContents* web_contents,
                           bool esc_key_locked) override;
  void CancelKeyboardLockRequest(WebContents* web_contents) override;

 private:
  bool is_fullscreen_ = false;
  bool keyboard_lock_requested_ = false;

  DISALLOW_COPY_AND_ASSIGN(FakeKeyboardLockWebContentsDelegate);
};

void FakeKeyboardLockWebContentsDelegate::EnterFullscreenModeForTab(
    WebContents* web_contents,
    const GURL& origin,
    const blink::WebFullscreenOptions& options) {
  is_fullscreen_ = true;
  if (keyboard_lock_requested_)
    web_contents->GotResponseToKeyboardLockRequest(/*allowed=*/true);
}

void FakeKeyboardLockWebContentsDelegate::ExitFullscreenModeForTab(
    WebContents* web_contents) {
  is_fullscreen_ = false;
  if (keyboard_lock_requested_)
    web_contents->GotResponseToKeyboardLockRequest(/*allowed=*/false);
}

bool FakeKeyboardLockWebContentsDelegate::IsFullscreenForTabOrPending(
    const WebContents* web_contents) const {
  return is_fullscreen_;
}

void FakeKeyboardLockWebContentsDelegate::RequestKeyboardLock(
    WebContents* web_contents,
    bool esc_key_locked) {
  keyboard_lock_requested_ = true;
  if (is_fullscreen_)
    web_contents->GotResponseToKeyboardLockRequest(/*allowed=*/true);
}

void FakeKeyboardLockWebContentsDelegate::CancelKeyboardLockRequest(
    WebContents* web_contents) {
  keyboard_lock_requested_ = false;
}

}  // namespace

#if defined(USE_AURA)

void SetWindowFocusForKeyboardLockBrowserTests(bool is_focused) {
  g_window_has_focus = is_focused;
}

void InstallCreateHooksForKeyboardLockBrowserTests() {
  WebContentsViewAura::InstallCreateHookForTests(
      [](RenderWidgetHost* host,
         bool is_guest_view_hack) -> RenderWidgetHostViewAura* {
        return new TestRenderWidgetHostView(host, is_guest_view_hack);
      });
}

#endif  // USE_AURA

class KeyboardLockBrowserTest : public ContentBrowserTest {
 public:
  KeyboardLockBrowserTest();
  ~KeyboardLockBrowserTest() override;

 protected:
  // ContentBrowserTest overrides.
  void SetUp() override;
  void SetUpCommandLine(base::CommandLine* command_line) override;
  void SetUpOnMainThread() override;

  virtual void SetUpFeatureList();

  // Helper methods for common tasks.
  bool KeyboardLockApiExists();
  void NavigateToTestURL(const GURL& gurl);
  void RequestKeyboardLock(const base::Location& from_here,
                           bool lock_all_keys = true);
  void CancelKeyboardLock(const base::Location& from_here);
  void EnterFullscreen(const base::Location& from_here, const GURL& gurl);
  void ExitFullscreen(const base::Location& from_here);
  void FocusContent(const base::Location& from_here);
  void BlurContent(const base::Location& from_here);
  void VerifyKeyboardLockState(const base::Location& from_here);

  WebContentsImpl* web_contents() const {
    return static_cast<WebContentsImpl*>(shell()->web_contents());
  }

  net::EmbeddedTestServer* https_test_server() { return &https_test_server_; }

  GURL https_fullscreen_frame() {
    return https_test_server()->GetURL(kFullscreenFramePath);
  }

  GURL https_cross_site_frame() {
    return https_test_server()->GetURL(kCrossSiteTopLevelDomain,
                                       kCrossSiteFramePath);
  }

  base::test::ScopedFeatureList* feature_list() {
    return &scoped_feature_list_;
  }

  WebContentsDelegate* web_contents_delegate() {
    return &web_contents_delegate_;
  }

 private:
  base::test::ScopedFeatureList scoped_feature_list_;
  net::EmbeddedTestServer https_test_server_;
  FakeKeyboardLockWebContentsDelegate web_contents_delegate_;
};

KeyboardLockBrowserTest::KeyboardLockBrowserTest()
    : https_test_server_(net::EmbeddedTestServer::TYPE_HTTPS) {}

KeyboardLockBrowserTest::~KeyboardLockBrowserTest() = default;

void KeyboardLockBrowserTest::SetUp() {
  // Assume we have focus to start with.
  SetWindowFocusForKeyboardLockBrowserTests(true);
  InstallCreateHooksForKeyboardLockBrowserTests();
  SetUpFeatureList();
  ContentBrowserTest::SetUp();
}

void KeyboardLockBrowserTest::SetUpCommandLine(
    base::CommandLine* command_line) {
  // Ignore cert errors so that the sign-in URL can be loaded from a site other
  // than localhost (the EmbeddedTestServer serves a certificate that is valid
  // for localhost).
  command_line->AppendSwitch(switches::kIgnoreCertificateErrors);
}

void KeyboardLockBrowserTest::SetUpOnMainThread() {
  web_contents()->SetDelegate(&web_contents_delegate_);

  // KeyboardLock requires a secure context (HTTPS).
  https_test_server()->AddDefaultHandlers(
      base::FilePath(FILE_PATH_LITERAL("content/test/data")));
  host_resolver()->AddRule("*", "127.0.0.1");
  SetupCrossSiteRedirector(https_test_server());
  ASSERT_TRUE(https_test_server()->Start());
}

void KeyboardLockBrowserTest::SetUpFeatureList() {
  feature_list()->InitAndEnableFeature(features::kKeyboardLockAPI);
}

bool KeyboardLockBrowserTest::KeyboardLockApiExists() {
  bool api_exists = false;
  EXPECT_TRUE(ExecuteScriptAndExtractBool(
      web_contents(), kKeyboardLockMethodExistanceCheck, &api_exists));
  return api_exists;
}

void KeyboardLockBrowserTest::NavigateToTestURL(const GURL& gurl) {
  ASSERT_TRUE(NavigateToURL(shell(), gurl));

  ASSERT_TRUE(KeyboardLockApiExists());

  // Ensure the window has focus and is in windowed mode after the navigation.
  FocusContent(FROM_HERE);
  ExitFullscreen(FROM_HERE);
}

void KeyboardLockBrowserTest::RequestKeyboardLock(
    const base::Location& from_here,
    bool lock_all_keys /*=true*/) {
  bool result;
  // keyboard.lock() is an async call which requires a promise handling dance.
  ASSERT_TRUE(ExecuteScriptAndExtractBool(
      web_contents()->GetMainFrame(),
      lock_all_keys ? kKeyboardLockMethodCallWithAllKeys
                    : kKeyboardLockMethodCallWithSomeKeys,
      &result))
      << "Location: " << from_here.ToString();

  ASSERT_TRUE(result) << "Location: " << from_here.ToString();

  ASSERT_EQ(result, web_contents()->GetKeyboardLockWidget() != nullptr)
      << "Location: " << from_here.ToString();

  VerifyKeyboardLockState(from_here);
}

void KeyboardLockBrowserTest::CancelKeyboardLock(
    const base::Location& from_here) {
  // keyboard.unlock() is a synchronous call.
  ASSERT_TRUE(
      ExecuteScript(web_contents()->GetMainFrame(), kKeyboardUnlockMethodCall));

  ASSERT_EQ(nullptr, web_contents()->GetKeyboardLockWidget())
      << "Location: " << from_here.ToString();

  VerifyKeyboardLockState(from_here);
}

void KeyboardLockBrowserTest::EnterFullscreen(const base::Location& from_here,
                                              const GURL& gurl) {
  web_contents()->EnterFullscreenMode(gurl, blink::WebFullscreenOptions());

  ASSERT_TRUE(web_contents()->IsFullscreenForCurrentTab())
      << "Location: " << from_here.ToString();

  VerifyKeyboardLockState(from_here);
}

void KeyboardLockBrowserTest::ExitFullscreen(const base::Location& from_here) {
  web_contents()->ExitFullscreenMode(/*should_resize=*/true);

  ASSERT_FALSE(web_contents()->IsFullscreenForCurrentTab())
      << "Location: " << from_here.ToString();

  VerifyKeyboardLockState(from_here);
}

void KeyboardLockBrowserTest::FocusContent(const base::Location& from_here) {
  SetWindowFocusForKeyboardLockBrowserTests(true);
  RenderWidgetHostImpl* host = RenderWidgetHostImpl::From(
      web_contents()->GetRenderWidgetHostView()->GetRenderWidgetHost());
  host->GotFocus();
  host->SetActive(true);

  ASSERT_TRUE(web_contents()->GetRenderWidgetHostView()->HasFocus())
      << "Location: " << from_here.ToString();

  VerifyKeyboardLockState(from_here);
}

void KeyboardLockBrowserTest::BlurContent(const base::Location& from_here) {
  SetWindowFocusForKeyboardLockBrowserTests(false);
  RenderWidgetHostImpl* host = RenderWidgetHostImpl::From(
      web_contents()->GetRenderWidgetHostView()->GetRenderWidgetHost());
  host->SetActive(false);
  host->LostFocus();

  ASSERT_FALSE(web_contents()->GetRenderWidgetHostView()->HasFocus())
      << "Location: " << from_here.ToString();

  VerifyKeyboardLockState(from_here);
}

void KeyboardLockBrowserTest::VerifyKeyboardLockState(
    const base::Location& from_here) {
  bool keyboard_lock_requested = !!web_contents()->GetKeyboardLockWidget();

  bool ux_conditions_satisfied =
      web_contents()->GetRenderWidgetHostView()->HasFocus() &&
      web_contents()->IsFullscreenForCurrentTab();

  bool keyboard_lock_active =
      web_contents()->GetRenderWidgetHostView()->IsKeyboardLocked();

  // Keyboard lock only active when requested and the UX is in the right state.
  ASSERT_EQ(keyboard_lock_active,
            ux_conditions_satisfied && keyboard_lock_requested)
      << "Location: " << from_here.ToString();
}

class KeyboardLockDisabledBrowserTest : public KeyboardLockBrowserTest {
 public:
  KeyboardLockDisabledBrowserTest() = default;
  ~KeyboardLockDisabledBrowserTest() override = default;

 protected:
  // KeyboardLockBrowserTest override.
  void SetUpFeatureList() override;
};

void KeyboardLockDisabledBrowserTest::SetUpFeatureList() {
  feature_list()->InitAndDisableFeature(features::kKeyboardLockAPI);
}

IN_PROC_BROWSER_TEST_F(KeyboardLockBrowserTest, SingleLockCall) {
  NavigateToTestURL(https_fullscreen_frame());
  base::HistogramTester uma;
  RequestKeyboardLock(FROM_HERE);
  // Don't explicitly call CancelKeyboardLock().

  uma.ExpectTotalCount(kKeyboardLockMethodCalledHistogramName, 1);
  uma.ExpectBucketCount(kKeyboardLockMethodCalledHistogramName,
                        static_cast<int>(KeyboardLockMethods::kRequestAllKeys),
                        1);
}

IN_PROC_BROWSER_TEST_F(KeyboardLockBrowserTest, SingleLockCallForSomeKeys) {
  NavigateToTestURL(https_fullscreen_frame());
  base::HistogramTester uma;
  RequestKeyboardLock(FROM_HERE, /*lock_all_keys=*/false);
  // Don't explicitly call CancelKeyboardLock().

  uma.ExpectTotalCount(kKeyboardLockMethodCalledHistogramName, 1);
  uma.ExpectBucketCount(kKeyboardLockMethodCalledHistogramName,
                        static_cast<int>(KeyboardLockMethods::kRequestSomeKeys),
                        1);
}

IN_PROC_BROWSER_TEST_F(KeyboardLockBrowserTest, SingleLockWithCancelCall) {
  NavigateToTestURL(https_fullscreen_frame());
  base::HistogramTester uma;
  RequestKeyboardLock(FROM_HERE);
  CancelKeyboardLock(FROM_HERE);

  uma.ExpectTotalCount(kKeyboardLockMethodCalledHistogramName, 2);
  uma.ExpectBucketCount(kKeyboardLockMethodCalledHistogramName,
                        static_cast<int>(KeyboardLockMethods::kRequestAllKeys),
                        1);
  uma.ExpectBucketCount(kKeyboardLockMethodCalledHistogramName,
                        static_cast<int>(KeyboardLockMethods::kCancelLock), 1);
}

IN_PROC_BROWSER_TEST_F(KeyboardLockBrowserTest, LockCalledBeforeFullscreen) {
  GURL url_for_test = https_fullscreen_frame();
  NavigateToTestURL(url_for_test);
  RequestKeyboardLock(FROM_HERE);
  EnterFullscreen(FROM_HERE, url_for_test);
}

IN_PROC_BROWSER_TEST_F(KeyboardLockBrowserTest, LockCalledAfterFullscreen) {
  GURL url_for_test = https_fullscreen_frame();
  NavigateToTestURL(url_for_test);
  EnterFullscreen(FROM_HERE, url_for_test);
  RequestKeyboardLock(FROM_HERE);
}

IN_PROC_BROWSER_TEST_F(KeyboardLockBrowserTest,
                       LockAndCancelCyclingNoActivation) {
  NavigateToTestURL(https_fullscreen_frame());

  base::HistogramTester uma;
  RequestKeyboardLock(FROM_HERE);
  CancelKeyboardLock(FROM_HERE);
  RequestKeyboardLock(FROM_HERE, /*lock_all_keys=*/false);
  CancelKeyboardLock(FROM_HERE);
  RequestKeyboardLock(FROM_HERE);
  CancelKeyboardLock(FROM_HERE);
  RequestKeyboardLock(FROM_HERE);
  CancelKeyboardLock(FROM_HERE);

  uma.ExpectTotalCount(kKeyboardLockMethodCalledHistogramName, 8);
  uma.ExpectBucketCount(kKeyboardLockMethodCalledHistogramName,
                        static_cast<int>(KeyboardLockMethods::kRequestAllKeys),
                        3);
  uma.ExpectBucketCount(kKeyboardLockMethodCalledHistogramName,
                        static_cast<int>(KeyboardLockMethods::kRequestSomeKeys),
                        1);
  uma.ExpectBucketCount(kKeyboardLockMethodCalledHistogramName,
                        static_cast<int>(KeyboardLockMethods::kCancelLock), 4);
}

IN_PROC_BROWSER_TEST_F(KeyboardLockBrowserTest,
                       LockAndCancelCyclingInFullscreen) {
  GURL url_for_test = https_fullscreen_frame();
  NavigateToTestURL(url_for_test);

  EnterFullscreen(FROM_HERE, url_for_test);

  RequestKeyboardLock(FROM_HERE);
  CancelKeyboardLock(FROM_HERE);
  RequestKeyboardLock(FROM_HERE, /*lock_all_keys=*/false);
  CancelKeyboardLock(FROM_HERE);
  RequestKeyboardLock(FROM_HERE, /*lock_all_keys=*/false);
  CancelKeyboardLock(FROM_HERE);
  RequestKeyboardLock(FROM_HERE);
  CancelKeyboardLock(FROM_HERE);
  RequestKeyboardLock(FROM_HERE);
  CancelKeyboardLock(FROM_HERE);
}

IN_PROC_BROWSER_TEST_F(KeyboardLockBrowserTest, CancelInFullscreen) {
  GURL url_for_test = https_fullscreen_frame();
  NavigateToTestURL(url_for_test);

  RequestKeyboardLock(FROM_HERE);
  EnterFullscreen(FROM_HERE, url_for_test);
  CancelKeyboardLock(FROM_HERE);
  ExitFullscreen(FROM_HERE);
}

IN_PROC_BROWSER_TEST_F(KeyboardLockBrowserTest, EnterAndExitFullscreenCycling) {
  GURL url_for_test = https_fullscreen_frame();
  NavigateToTestURL(url_for_test);

  RequestKeyboardLock(FROM_HERE);

  EnterFullscreen(FROM_HERE, url_for_test);
  ExitFullscreen(FROM_HERE);
  EnterFullscreen(FROM_HERE, url_for_test);
  ExitFullscreen(FROM_HERE);
  EnterFullscreen(FROM_HERE, url_for_test);
  ExitFullscreen(FROM_HERE);
  EnterFullscreen(FROM_HERE, url_for_test);
  ExitFullscreen(FROM_HERE);
}

IN_PROC_BROWSER_TEST_F(KeyboardLockBrowserTest, GainAndLoseFocusInWindowMode) {
  NavigateToTestURL(https_fullscreen_frame());

  RequestKeyboardLock(FROM_HERE);

  FocusContent(FROM_HERE);
  BlurContent(FROM_HERE);
  FocusContent(FROM_HERE);
  BlurContent(FROM_HERE);
}

IN_PROC_BROWSER_TEST_F(KeyboardLockBrowserTest, EnterFullscreenWithoutFocus) {
  GURL url_for_test = https_fullscreen_frame();
  NavigateToTestURL(url_for_test);

  RequestKeyboardLock(FROM_HERE);

  BlurContent(FROM_HERE);
  EnterFullscreen(FROM_HERE, url_for_test);
  ExitFullscreen(FROM_HERE);

  EnterFullscreen(FROM_HERE, url_for_test);
  FocusContent(FROM_HERE);
}

IN_PROC_BROWSER_TEST_F(KeyboardLockBrowserTest,
                       GainAndLoseFocusCyclingInFullscreen) {
  GURL url_for_test = https_fullscreen_frame();
  NavigateToTestURL(url_for_test);

  RequestKeyboardLock(FROM_HERE);

  BlurContent(FROM_HERE);
  EnterFullscreen(FROM_HERE, url_for_test);

  FocusContent(FROM_HERE);
  BlurContent(FROM_HERE);
  FocusContent(FROM_HERE);
  BlurContent(FROM_HERE);
  FocusContent(FROM_HERE);
  BlurContent(FROM_HERE);
  FocusContent(FROM_HERE);
  BlurContent(FROM_HERE);

  ExitFullscreen(FROM_HERE);
}

IN_PROC_BROWSER_TEST_F(KeyboardLockBrowserTest, CancelWithoutLock) {
  NavigateToTestURL(https_fullscreen_frame());
  CancelKeyboardLock(FROM_HERE);
  CancelKeyboardLock(FROM_HERE);
}

IN_PROC_BROWSER_TEST_F(KeyboardLockBrowserTest, MultipleLockCalls) {
  NavigateToTestURL(https_fullscreen_frame());

  RequestKeyboardLock(FROM_HERE);
  RequestKeyboardLock(FROM_HERE);
  RequestKeyboardLock(FROM_HERE);
}

IN_PROC_BROWSER_TEST_F(KeyboardLockBrowserTest, MultipleCancelCalls) {
  NavigateToTestURL(https_fullscreen_frame());

  RequestKeyboardLock(FROM_HERE);

  CancelKeyboardLock(FROM_HERE);
  CancelKeyboardLock(FROM_HERE);
  CancelKeyboardLock(FROM_HERE);
}

IN_PROC_BROWSER_TEST_F(KeyboardLockBrowserTest, LockCallWithAllInvalidKeys) {
  GURL url_for_test = https_fullscreen_frame();
  NavigateToTestURL(url_for_test);

  bool result;
  ASSERT_TRUE(ExecuteScriptAndExtractBool(
      web_contents(), kKeyboardLockMethodCallWithAllInvalidKeys, &result));
  ASSERT_TRUE(result);

  // If no valid Keys are passed in, then keyboard lock will not be requested.
  ASSERT_FALSE(web_contents()->GetKeyboardLockWidget());

  EnterFullscreen(FROM_HERE, url_for_test);
}

IN_PROC_BROWSER_TEST_F(KeyboardLockBrowserTest, LockCallWithSomeInvalidKeys) {
  GURL url_for_test = https_fullscreen_frame();
  NavigateToTestURL(url_for_test);

  bool result;
  ASSERT_TRUE(ExecuteScriptAndExtractBool(
      web_contents(), kKeyboardLockMethodCallWithSomeInvalidKeys, &result));
  ASSERT_TRUE(result);

  // If some valid Keys are passed in, then keyboard lock will not be requested.
  ASSERT_FALSE(web_contents()->GetKeyboardLockWidget());
}

IN_PROC_BROWSER_TEST_F(KeyboardLockBrowserTest,
                       ValidLockCallFollowedByInvalidLockCall) {
  NavigateToTestURL(https_fullscreen_frame());

  RequestKeyboardLock(FROM_HERE);
  ASSERT_TRUE(web_contents()->GetKeyboardLockWidget());

  bool result;
  ASSERT_TRUE(ExecuteScriptAndExtractBool(
      web_contents(), kKeyboardLockMethodCallWithSomeInvalidKeys, &result));
  ASSERT_TRUE(result);

  // An invalid call will cancel any previous lock request.
  ASSERT_FALSE(web_contents()->GetKeyboardLockWidget());
}

IN_PROC_BROWSER_TEST_F(KeyboardLockDisabledBrowserTest,
                       NoKeyboardLockWhenDisabled) {
  ASSERT_TRUE(NavigateToURL(shell(), https_fullscreen_frame()));
  ASSERT_TRUE(KeyboardLockApiExists());

  // KeyboardLockServiceImpl returns success from the RequestKeyboardLock()
  // call when the Chrome side of the feature is disabled.  This prevents
  // problems running the Blink web tests.
  bool result = false;
  ASSERT_TRUE(ExecuteScriptAndExtractBool(web_contents()->GetMainFrame(),
                                          kKeyboardLockMethodCallWithAllKeys,
                                          &result));
  ASSERT_TRUE(result);

  ASSERT_FALSE(web_contents()->GetKeyboardLockWidget());
}

IN_PROC_BROWSER_TEST_F(KeyboardLockBrowserTest,
                       KeyboardLockNotAllowedForSameOriginIFrame) {
  NavigateToTestURL(https_cross_site_frame());

  // The first child has the same origin as the top-level domain.
  RenderFrameHost* child_frame = ChildFrameAt(web_contents()->GetMainFrame(),
                                              /*child_index=*/0);
  ASSERT_TRUE(child_frame);

  bool api_exists = false;
  ASSERT_TRUE(ExecuteScriptAndExtractBool(
      child_frame, kKeyboardLockMethodExistanceCheck, &api_exists));
  ASSERT_TRUE(api_exists);

  bool result = false;
  ASSERT_TRUE(ExecuteScriptAndExtractBool(
      child_frame, kKeyboardLockMethodCallWithAllKeys, &result));
  ASSERT_FALSE(result);

  ASSERT_FALSE(web_contents()->GetKeyboardLockWidget());
}

IN_PROC_BROWSER_TEST_F(KeyboardLockBrowserTest,
                       KeyboardLockNotAllowedForCrossOriginIFrame) {
  NavigateToTestURL(https_cross_site_frame());

  // The second child has a different origin as the top-level domain.
  RenderFrameHost* child_frame = ChildFrameAt(web_contents()->GetMainFrame(),
                                              /*child_index=*/1);
  ASSERT_TRUE(child_frame);

  bool api_exists = false;
  ASSERT_TRUE(ExecuteScriptAndExtractBool(
      child_frame, kKeyboardLockMethodExistanceCheck, &api_exists));
  ASSERT_TRUE(api_exists);

  bool result = false;
  ASSERT_TRUE(ExecuteScriptAndExtractBool(
      child_frame, kKeyboardLockMethodCallWithAllKeys, &result));
  ASSERT_FALSE(result);

  ASSERT_FALSE(web_contents()->GetKeyboardLockWidget());
}

IN_PROC_BROWSER_TEST_F(KeyboardLockBrowserTest,
                       KeyboardUnlockedWhenNavigatingToSameUrl) {
  GURL url_for_test = https_fullscreen_frame();
  NavigateToTestURL(url_for_test);
  EnterFullscreen(FROM_HERE, url_for_test);
  RequestKeyboardLock(FROM_HERE);

  // Navigate to the same URL which will reset the keyboard lock state.
  NavigateToTestURL(url_for_test);
  ASSERT_FALSE(web_contents()->GetKeyboardLockWidget());

  // Entering fullscreen on the new page should not engage keyboard lock.
  EnterFullscreen(FROM_HERE, url_for_test);
  ASSERT_FALSE(web_contents()->GetRenderWidgetHostView()->IsKeyboardLocked());
}

IN_PROC_BROWSER_TEST_F(KeyboardLockBrowserTest,
                       KeyboardUnlockedWhenNavigatingAway) {
  GURL first_url_for_test = https_fullscreen_frame();
  NavigateToTestURL(first_url_for_test);
  EnterFullscreen(FROM_HERE, first_url_for_test);
  RequestKeyboardLock(FROM_HERE);

  // Navigate to a new URL which will reset the keyboard lock state.
  GURL second_url_for_test = https_cross_site_frame();
  NavigateToTestURL(second_url_for_test);
  ASSERT_FALSE(web_contents()->GetKeyboardLockWidget());

  // Entering fullscreen on the new page should not engage keyboard lock.
  EnterFullscreen(FROM_HERE, second_url_for_test);
  ASSERT_FALSE(web_contents()->GetRenderWidgetHostView()->IsKeyboardLocked());
}

IN_PROC_BROWSER_TEST_F(KeyboardLockBrowserTest,
                       KeyboardRemainsLockedWhenIframeNavigates) {
  NavigateToTestURL(https_cross_site_frame());
  EnterFullscreen(FROM_HERE, https_cross_site_frame());
  RequestKeyboardLock(FROM_HERE);

  ASSERT_TRUE(NavigateIframeToURL(
      web_contents(), kChildIframeName_0,
      https_test_server()->GetURL(kCrossSiteTopLevelDomain, kHelloFramePath)));
  ASSERT_TRUE(web_contents()->GetKeyboardLockWidget());
  ASSERT_TRUE(web_contents()->GetRenderWidgetHostView()->IsKeyboardLocked());

  ASSERT_TRUE(NavigateIframeToURL(
      web_contents(), kChildIframeName_1,
      https_test_server()->GetURL(kCrossSiteChildDomain1, kHelloFramePath)));
  ASSERT_TRUE(web_contents()->GetKeyboardLockWidget());
  ASSERT_TRUE(web_contents()->GetRenderWidgetHostView()->IsKeyboardLocked());

  ASSERT_TRUE(NavigateIframeToURL(
      web_contents(), kChildIframeName_2,
      https_test_server()->GetURL(kCrossSiteChildDomain2, kHelloFramePath)));
  ASSERT_TRUE(web_contents()->GetKeyboardLockWidget());
  ASSERT_TRUE(web_contents()->GetRenderWidgetHostView()->IsKeyboardLocked());

  ASSERT_TRUE(
      NavigateIframeToURL(web_contents(), kChildIframeName_0,
                          https_test_server()->GetURL(kCrossSiteChildDomain2,
                                                      kInputFieldFramePath)));
  ASSERT_TRUE(web_contents()->GetKeyboardLockWidget());
  ASSERT_TRUE(web_contents()->GetRenderWidgetHostView()->IsKeyboardLocked());

  ASSERT_TRUE(
      NavigateIframeToURL(web_contents(), kChildIframeName_1,
                          https_test_server()->GetURL(kCrossSiteTopLevelDomain,
                                                      kInputFieldFramePath)));
  ASSERT_TRUE(web_contents()->GetKeyboardLockWidget());
  ASSERT_TRUE(web_contents()->GetRenderWidgetHostView()->IsKeyboardLocked());

  ASSERT_TRUE(
      NavigateIframeToURL(web_contents(), kChildIframeName_2,
                          https_test_server()->GetURL(kCrossSiteChildDomain1,
                                                      kInputFieldFramePath)));
  ASSERT_TRUE(web_contents()->GetKeyboardLockWidget());
  ASSERT_TRUE(web_contents()->GetRenderWidgetHostView()->IsKeyboardLocked());
}

IN_PROC_BROWSER_TEST_F(KeyboardLockBrowserTest,
                       CrossOriginIFrameReceivesInputWhenFocused) {
  NavigateToTestURL(https_cross_site_frame());
  EnterFullscreen(FROM_HERE, https_cross_site_frame());
  RequestKeyboardLock(FROM_HERE);

  GURL iframe_url =
      https_test_server()->GetURL(kCrossSiteChildDomain1, kInputFieldFramePath);
  ASSERT_TRUE(
      NavigateIframeToURL(web_contents(), kChildIframeName_1, iframe_url));
  ASSERT_TRUE(web_contents()->GetRenderWidgetHostView()->IsKeyboardLocked());

  RenderFrameHost* main_frame = web_contents()->GetMainFrame();
  RenderFrameHost* child = ChildFrameAt(main_frame, 1);
  ASSERT_TRUE(child);

  ASSERT_EQ(main_frame, web_contents()->GetFocusedFrame());

  std::string result;
  ASSERT_TRUE(ExecuteScript(child, kFocusInputFieldScript));
  ASSERT_TRUE(ExecuteScriptAndExtractString(
      child, "window.focus(); focusInputField();", &result));
  ASSERT_EQ("input-focus", result);
  ASSERT_EQ(child, web_contents()->GetFocusedFrame());
  ASSERT_TRUE(web_contents()->GetRenderWidgetHostView()->IsKeyboardLocked());

  SimulateKeyPress(web_contents(), "KeyB", "B");
  SimulateKeyPress(web_contents(), "KeyL", "BL");
  SimulateKeyPress(web_contents(), "KeyA", "BLA");
  SimulateKeyPress(web_contents(), "KeyR", "BLAR");
  SimulateKeyPress(web_contents(), "KeyG", "BLARG");
  SimulateKeyPress(web_contents(), "KeyH", "BLARGH");
  ASSERT_TRUE(web_contents()->GetRenderWidgetHostView()->IsKeyboardLocked());
}

IN_PROC_BROWSER_TEST_F(KeyboardLockBrowserTest,
                       LockRequestBeforeCrossOriginIFrameIsFullscreen) {
  // If the main frame trusts the child frame by granting it the allowfullscreen
  // permission, then we will allow keyboard lock to be activated when the child
  // frame activates fullscreen.
  NavigateToTestURL(https_cross_site_frame());
  RequestKeyboardLock(FROM_HERE);
  ASSERT_TRUE(web_contents()->GetKeyboardLockWidget());
  ASSERT_FALSE(web_contents()->GetRenderWidgetHostView()->IsKeyboardLocked());

  RenderFrameHost* main_frame = web_contents()->GetMainFrame();
  RenderFrameHost* child = ChildFrameAt(main_frame, 2);
  ASSERT_TRUE(child);

  // The third child is cross-domain and has the allowfullscreen attribute set.
  ASSERT_TRUE(
      NavigateIframeToURL(web_contents(), kChildIframeName_2,
                          https_test_server()->GetURL(kCrossSiteChildDomain2,
                                                      kFullscreenFramePath)));
  ASSERT_TRUE(ExecuteScript(child, "activateFullscreen()"));

  ASSERT_EQ(main_frame->GetView()->GetRenderWidgetHost(),
            web_contents()->GetKeyboardLockWidget());
  ASSERT_TRUE(web_contents()->GetRenderWidgetHostView()->IsKeyboardLocked());
}

IN_PROC_BROWSER_TEST_F(KeyboardLockBrowserTest,
                       LockRequestWhileCrossOriginIFrameIsFullscreen) {
  // If the main frame trusts the child frame by granting it the allowfullscreen
  // permission, then we will allow keyboard lock to be activated when the child
  // frame activates fullscreen.
  NavigateToTestURL(https_cross_site_frame());

  RenderFrameHost* main_frame = web_contents()->GetMainFrame();
  RenderFrameHost* child = ChildFrameAt(main_frame, 2);
  ASSERT_TRUE(child);

  // The third child is cross-domain and has the allowfullscreen attribute set.
  ASSERT_TRUE(
      NavigateIframeToURL(web_contents(), kChildIframeName_2,
                          https_test_server()->GetURL(kCrossSiteChildDomain2,
                                                      kFullscreenFramePath)));
  ASSERT_TRUE(ExecuteScript(child, "activateFullscreen()"));

  RequestKeyboardLock(FROM_HERE);

  ASSERT_EQ(main_frame->GetView()->GetRenderWidgetHost(),
            web_contents()->GetKeyboardLockWidget());
  ASSERT_TRUE(web_contents()->GetRenderWidgetHostView()->IsKeyboardLocked());
}

IN_PROC_BROWSER_TEST_F(KeyboardLockBrowserTest,
                       LockRequestFailsFromInnerWebContents) {
  NavigateToTestURL(https_cross_site_frame());

  // The first child is a same-origin iframe.
  RenderFrameHost* main_frame = web_contents()->GetMainFrame();
  RenderFrameHost* child = ChildFrameAt(main_frame, 0);
  ASSERT_TRUE(child);

  WebContents* inner_contents = CreateAndAttachInnerContents(child);
  inner_contents->SetDelegate(web_contents_delegate());

  ASSERT_TRUE(
      NavigateToURLFromRenderer(inner_contents, https_fullscreen_frame()));

  bool api_exists = false;
  ASSERT_TRUE(ExecuteScriptAndExtractBool(
      inner_contents, kKeyboardLockMethodExistanceCheck, &api_exists));
  ASSERT_TRUE(api_exists);

  bool result = false;
  ASSERT_TRUE(ExecuteScriptAndExtractBool(
      inner_contents, kKeyboardLockMethodCallWithAllKeys, &result));
  ASSERT_FALSE(result);

  // Verify neither inner nor outer WebContents have a pending lock request.
  WebContentsImpl* inner_contents_impl =
      static_cast<WebContentsImpl*>(inner_contents);
  ASSERT_FALSE(inner_contents_impl->GetKeyboardLockWidget());
  ASSERT_FALSE(
      inner_contents_impl->GetRenderWidgetHostView()->IsKeyboardLocked());
  ASSERT_FALSE(web_contents()->GetKeyboardLockWidget());
  ASSERT_FALSE(web_contents()->GetRenderWidgetHostView()->IsKeyboardLocked());
}

IN_PROC_BROWSER_TEST_F(KeyboardLockBrowserTest,
                       InnerContentsFullscreenBehavior) {
  // TODO(joedow): Added per code review feedback.  Need to define the behavior
  // for KeyboardLock when an attached InnerWebContents goes fullscreen.
  // Steps: 1. Request keyboard lock for all keys
  //        2. InnerWebContents request fullscreen
  //        3. Verify KeyboardLock behavior (should match iframe behavior)
}

IN_PROC_BROWSER_TEST_F(KeyboardLockBrowserTest, InnerContentsInputBehavior) {
  // TODO(joedow): Added per code review feedback.  Need to define the behavior
  // for KeyboardLock when an attached InnerWebContents goes fullscreen.
  // Steps: 1. Request keyboard lock for all keys
  //        2. Main frame goes fullscreen
  //        3. Inner web contents is focused
  //        4. Verify input behavior (should match iframe behavior)
}

}  // namespace content
