blob: 51171b5ffdc71e40a7cbb5017de1a4c1beffd127 [file] [log] [blame]
// Copyright 2020 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 "base/bind.h"
#include "base/files/file_path.h"
#include "base/macros.h"
#include "base/run_loop.h"
#include "base/strings/utf_string_conversions.h"
#include "base/win/windows_version.h"
#include "build/build_config.h"
#include "chrome/browser/ui/browser.h"
#include "chrome/browser/ui/browser_window.h"
#include "chrome/browser/ui/tabs/tab_strip_model.h"
#include "chrome/test/base/in_process_browser_test.h"
#include "chrome/test/base/interactive_test_utils.h"
#include "chrome/test/base/ui_test_utils.h"
#include "content/public/browser/render_frame_host.h"
#include "content/public/browser/web_contents.h"
#include "content/public/common/content_switches.h"
#include "content/public/test/browser_test.h"
#include "content/public/test/browser_test_utils.h"
#include "content/public/test/text_input_test_utils.h"
#include "ui/base/ime/text_input_type.h"
#include "ui/base/test/ui_controls.h"
#include "ui/events/keycodes/keyboard_codes.h"
namespace {
const int kTextAreaHeight = 36;
const int kTextAreaWidth = 162;
const int kTextAreaOffsetX = 100;
// TextInputManager Observers
// A base class for observing the TextInputManager owned by the given
// WebContents. Subclasses could observe the TextInputManager for different
// changes. The class wraps a public tester which accepts callbacks that
// are run after specific changes in TextInputManager. Different observers can
// be subclassed from this by providing their specific callback methods.
class TextInputManagerObserverBase {
public:
explicit TextInputManagerObserverBase(content::WebContents* web_contents)
: tester_(new content::TextInputManagerTester(web_contents)),
success_(false) {}
virtual ~TextInputManagerObserverBase() {}
// Wait for derived class's definition of success.
void Wait() {
if (success_)
return;
run_loop.Run();
}
bool success() const { return success_; }
protected:
content::TextInputManagerTester* tester() { return tester_.get(); }
void OnSuccess() {
success_ = true;
run_loop.Quit();
// By deleting |tester_| we make sure that the internal observer used in
// content/ is removed from the observer list of TextInputManager.
tester_.reset(nullptr);
}
private:
std::unique_ptr<content::TextInputManagerTester> tester_;
bool success_;
base::RunLoop run_loop;
DISALLOW_COPY_AND_ASSIGN(TextInputManagerObserverBase);
};
// This class observes TextInputManager for changes in
// |TextInputState.vk_policy|.
class TextInputManagerVkPolicyObserver : public TextInputManagerObserverBase {
public:
TextInputManagerVkPolicyObserver(
content::WebContents* web_contents,
ui::mojom::VirtualKeyboardPolicy expected_value)
: TextInputManagerObserverBase(web_contents),
expected_value_(expected_value) {
tester()->SetUpdateTextInputStateCalledCallback(
base::BindRepeating(&TextInputManagerVkPolicyObserver::VerifyVkPolicy,
base::Unretained(this)));
}
private:
void VerifyVkPolicy() {
ui::mojom::VirtualKeyboardPolicy value;
if (tester()->GetTextInputVkPolicy(&value) && expected_value_ == value)
OnSuccess();
}
ui::mojom::VirtualKeyboardPolicy expected_value_;
DISALLOW_COPY_AND_ASSIGN(TextInputManagerVkPolicyObserver);
};
// This class observes TextInputManager for changes in
// |TextInputState.last_vk_visibility_request|.
class TextInputManagerVkVisibilityRequestObserver
: public TextInputManagerObserverBase {
public:
TextInputManagerVkVisibilityRequestObserver(
content::WebContents* web_contents,
ui::mojom::VirtualKeyboardVisibilityRequest expected_value)
: TextInputManagerObserverBase(web_contents),
expected_value_(expected_value) {
tester()->SetUpdateTextInputStateCalledCallback(base::BindRepeating(
&TextInputManagerVkVisibilityRequestObserver::VerifyVkVisibilityRequest,
base::Unretained(this)));
}
private:
void VerifyVkVisibilityRequest() {
ui::mojom::VirtualKeyboardVisibilityRequest value;
if (tester()->GetTextInputVkVisibilityRequest(&value) &&
expected_value_ == value)
OnSuccess();
}
ui::mojom::VirtualKeyboardVisibilityRequest expected_value_;
DISALLOW_COPY_AND_ASSIGN(TextInputManagerVkVisibilityRequestObserver);
};
// This class observes TextInputManager for changes in
// |TextInputState.show_ime_if_needed|.
class TextInputManagerShowImeIfNeededObserver
: public TextInputManagerObserverBase {
public:
TextInputManagerShowImeIfNeededObserver(content::WebContents* web_contents,
bool expected_value)
: TextInputManagerObserverBase(web_contents),
expected_value_(expected_value) {
tester()->SetUpdateTextInputStateCalledCallback(base::BindRepeating(
&TextInputManagerShowImeIfNeededObserver::VerifyShowImeIfNeeded,
base::Unretained(this)));
}
private:
void VerifyShowImeIfNeeded() {
bool show_ime_if_needed;
if (tester()->GetTextInputShowImeIfNeeded(&show_ime_if_needed) &&
expected_value_ == show_ime_if_needed)
OnSuccess();
}
bool expected_value_;
DISALLOW_COPY_AND_ASSIGN(TextInputManagerShowImeIfNeededObserver);
};
class VirtualKeyboardPolicyTest : public InProcessBrowserTest {
public:
VirtualKeyboardPolicyTest() {}
// InProcessBrowserTest:
void SetUpOnMainThread() override {
ASSERT_TRUE(ui_test_utils::BringBrowserWindowToFront(browser()));
}
content::WebContents* GetActiveWebContents() const {
return browser()->tab_strip_model()->GetActiveWebContents();
}
ui::BaseWindow* GetWindow() const { return browser()->window(); }
void SetUpCommandLine(base::CommandLine* command_line) override {
command_line->AppendSwitchASCII(switches::kEnableBlinkFeatures,
"VirtualKeyboard,EditContext");
InProcessBrowserTest::SetUpCommandLine(command_line);
}
// Wait for the active web contents title to match |title|.
void WaitForTitle(const std::string& title) {
const base::string16 expected_title(base::ASCIIToUTF16(title));
content::TitleWatcher title_watcher(GetActiveWebContents(), expected_title);
ASSERT_EQ(expected_title, title_watcher.WaitAndGetTitle());
}
void NavigateAndWaitForLoad() {
ASSERT_TRUE(ui_test_utils::BringBrowserWindowToFront(browser()));
// Navigate to the test page and wait for onload to be called.
const GURL url = ui_test_utils::GetTestUrl(
base::FilePath(),
base::FilePath(FILE_PATH_LITERAL("virtual_keyboard_policy.html")));
ui_test_utils::NavigateToURL(browser(), url);
WaitForTitle("onload");
}
DISALLOW_COPY_AND_ASSIGN(VirtualKeyboardPolicyTest);
};
IN_PROC_BROWSER_TEST_F(VirtualKeyboardPolicyTest, Load) {
NavigateAndWaitForLoad();
}
// Tapping on an editable element should show VK.
IN_PROC_BROWSER_TEST_F(VirtualKeyboardPolicyTest, ShowVK) {
// ui_controls::SendTouchEvents which uses InjectTouchInput API only works
// on Windows 8 and up.
if (base::win::GetVersion() < base::win::Version::WIN8) {
return;
}
NavigateAndWaitForLoad();
// Tap on the third textarea to open VK.
gfx::Rect bounds = GetActiveWebContents()->GetContainerBounds();
TextInputManagerVkPolicyObserver type_observer_auto(
GetActiveWebContents(), ui::mojom::VirtualKeyboardPolicy::AUTO);
ASSERT_TRUE(ui_controls::SendTouchEvents(
ui_controls::PRESS, 1,
bounds.x() + kTextAreaWidth / 2 + kTextAreaOffsetX * 2,
bounds.y() + kTextAreaHeight / 2));
type_observer_auto.Wait();
}
// Tapping on an editable element with virtualkeyboardpolicy="auto" should
// raise the VK but JS focus shouldn't.
IN_PROC_BROWSER_TEST_F(VirtualKeyboardPolicyTest, DontShowVKOnJSFocus) {
// ui_controls::SendTouchEvents which uses InjectTouchInput API only works
// on Windows 8 and up.
if (base::win::GetVersion() < base::win::Version::WIN8) {
return;
}
NavigateAndWaitForLoad();
content::RenderFrameHost* current = GetActiveWebContents()->GetMainFrame();
TextInputManagerShowImeIfNeededObserver show_ime_observer_false(
GetActiveWebContents(), false);
// Run the JS that focuses the edit control.
std::string script =
"inputField = document.getElementById('txt4');"
"inputField.focus();";
EXPECT_TRUE(ExecuteScriptWithoutUserGesture(current, script));
show_ime_observer_false.Wait();
TextInputManagerShowImeIfNeededObserver show_ime_observer_true(
GetActiveWebContents(), true);
// Tap on the third textarea to open VK.
gfx::Rect bounds = GetActiveWebContents()->GetContainerBounds();
ASSERT_TRUE(ui_controls::SendTouchEvents(
ui_controls::PRESS, 1,
bounds.x() + kTextAreaWidth / 2 + kTextAreaOffsetX * 2,
bounds.y() + kTextAreaHeight / 2));
show_ime_observer_true.Wait();
}
// Tapping on an editable element with virtualkeyboardpolicy="manual" that
// calls hide() explicitly should hide VK.
IN_PROC_BROWSER_TEST_F(VirtualKeyboardPolicyTest, HideVK) {
// ui_controls::SendTouchEvents which uses InjectTouchInput API only works
// on Windows 8 and up.
if (base::win::GetVersion() < base::win::Version::WIN8) {
return;
}
NavigateAndWaitForLoad();
// Tap on the first textarea that would trigger show() call.
gfx::Rect bounds = GetActiveWebContents()->GetContainerBounds();
TextInputManagerVkVisibilityRequestObserver type_observer_hide(
GetActiveWebContents(),
ui::mojom::VirtualKeyboardVisibilityRequest::HIDE);
ASSERT_TRUE(ui_controls::SendTouchEvents(
ui_controls::PRESS, 1, bounds.x() + kTextAreaWidth / 2 + kTextAreaOffsetX,
bounds.y() + kTextAreaHeight / 2));
type_observer_hide.Wait();
}
// Tapping on an editable element with virtualkeyboardpolicy="manual" that
// calls show() explicitly should show the VK and if hide()is called, then it
// should hide VK.
IN_PROC_BROWSER_TEST_F(VirtualKeyboardPolicyTest, ShowAndThenHideVK) {
// ui_controls::SendTouchEvents which uses InjectTouchInput API only works
// on Windows 8 and up.
if (base::win::GetVersion() < base::win::Version::WIN8) {
return;
}
NavigateAndWaitForLoad();
// Tap on the first textarea that would trigger show() call and then on the
// second textarea that would trigger hide() call.
gfx::Rect bounds = GetActiveWebContents()->GetContainerBounds();
TextInputManagerVkVisibilityRequestObserver type_observer_show(
GetActiveWebContents(),
ui::mojom::VirtualKeyboardVisibilityRequest::SHOW);
ASSERT_TRUE(ui_controls::SendTouchEvents(ui_controls::PRESS, 1,
bounds.x() + kTextAreaWidth / 2,
bounds.y() + kTextAreaHeight / 2));
type_observer_show.Wait();
TextInputManagerVkVisibilityRequestObserver type_observer_hide(
GetActiveWebContents(),
ui::mojom::VirtualKeyboardVisibilityRequest::HIDE);
ASSERT_TRUE(ui_controls::SendTouchEvents(
ui_controls::PRESS, 1, bounds.x() + kTextAreaWidth / 2 + kTextAreaOffsetX,
bounds.y() + kTextAreaHeight / 2));
type_observer_hide.Wait();
}
// Tapping on an editable element with virtualkeyboardpolicy="manual" that
// calls show() explicitly should show the VK and if hide()is called, then it
// should hide VK on keydown.
IN_PROC_BROWSER_TEST_F(VirtualKeyboardPolicyTest, ShowAndThenHideVKOnKeyDown) {
// ui_controls::SendTouchEvents which uses InjectTouchInput API only works
// on Windows 8 and up.
if (base::win::GetVersion() < base::win::Version::WIN8) {
return;
}
NavigateAndWaitForLoad();
// Tap on the first textarea that would trigger show() call and then on the
// second textarea that would trigger hide() call.
gfx::Rect bounds = GetActiveWebContents()->GetContainerBounds();
TextInputManagerVkVisibilityRequestObserver type_observer_show(
GetActiveWebContents(),
ui::mojom::VirtualKeyboardVisibilityRequest::SHOW);
ASSERT_TRUE(ui_controls::SendTouchEvents(ui_controls::PRESS, 1,
bounds.x() + kTextAreaWidth / 2,
bounds.y() + kTextAreaHeight / 2));
type_observer_show.Wait();
TextInputManagerVkVisibilityRequestObserver type_observer_hide(
GetActiveWebContents(),
ui::mojom::VirtualKeyboardVisibilityRequest::HIDE);
ASSERT_TRUE(ui_controls::SendKeyPress(GetWindow()->GetNativeWindow(),
ui::VKEY_RETURN, false, false, false,
false));
type_observer_hide.Wait();
}
IN_PROC_BROWSER_TEST_F(VirtualKeyboardPolicyTest,
VKVisibilityRequestInDeletedDocument) {
// ui_controls::SendTouchEvents which uses InjectTouchInput API only works
// on Windows 8 and up.
if (base::win::GetVersion() < base::win::Version::WIN8) {
return;
}
NavigateAndWaitForLoad();
// Tap on the first textarea that would trigger show() call and then on the
// second textarea that would trigger hide() call.
gfx::Rect bounds = GetActiveWebContents()->GetContainerBounds();
TextInputManagerVkVisibilityRequestObserver type_observer_none(
GetActiveWebContents(),
ui::mojom::VirtualKeyboardVisibilityRequest::NONE);
ASSERT_TRUE(ui_controls::SendTouchEvents(
ui_controls::PRESS, 1,
bounds.x() + kTextAreaWidth / 2 + kTextAreaOffsetX * 8,
bounds.y() + kTextAreaHeight / 2));
type_observer_none.Wait();
}
// Tapping on an editcontext with inputpanelpolicy="manual" that
// calls show() explicitly should show the VK and if hide() is called, then it
// should hide VK.
IN_PROC_BROWSER_TEST_F(VirtualKeyboardPolicyTest,
ShowAndThenHideVKInEditContext) {
// ui_controls::SendTouchEvents which uses InjectTouchInput API only works
// on Windows 8 and up.
if (base::win::GetVersion() < base::win::Version::WIN8) {
return;
}
NavigateAndWaitForLoad();
// Tap on the textarea that would trigger show() call and then on the
// second textarea that would trigger hide() call.
gfx::Rect bounds = GetActiveWebContents()->GetContainerBounds();
TextInputManagerVkVisibilityRequestObserver type_observer_show(
GetActiveWebContents(),
ui::mojom::VirtualKeyboardVisibilityRequest::SHOW);
ASSERT_TRUE(ui_controls::SendTouchEvents(
ui_controls::PRESS, 1,
bounds.x() + kTextAreaWidth / 2 + kTextAreaOffsetX * 4,
bounds.y() + kTextAreaHeight / 2));
type_observer_show.Wait();
TextInputManagerVkVisibilityRequestObserver type_observer_hide(
GetActiveWebContents(),
ui::mojom::VirtualKeyboardVisibilityRequest::HIDE);
ASSERT_TRUE(ui_controls::SendKeyPress(GetWindow()->GetNativeWindow(),
ui::VKEY_RETURN, false, false, false,
false));
type_observer_hide.Wait();
}
} // namespace