virtual_keyboard_browsertest.cc 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271
  1. // Copyright 2021 The Chromium Authors. All rights reserved.
  2. // Use of this source code is governed by a BSD-style license that can be
  3. // found in the LICENSE file.
  4. #include <fuchsia/input/virtualkeyboard/cpp/fidl.h>
  5. #include <fuchsia/ui/input3/cpp/fidl.h>
  6. #include <lib/fit/function.h>
  7. #include "base/callback.h"
  8. #include "base/fuchsia/fuchsia_logging.h"
  9. #include "base/fuchsia/koid.h"
  10. #include "base/fuchsia/scoped_service_binding.h"
  11. #include "base/fuchsia/test_component_context_for_process.h"
  12. #include "base/numerics/safe_conversions.h"
  13. #include "base/strings/stringprintf.h"
  14. #include "base/test/scoped_feature_list.h"
  15. #include "content/public/test/browser_test.h"
  16. #include "fuchsia_web/common/test/frame_test_util.h"
  17. #include "fuchsia_web/common/test/test_navigation_listener.h"
  18. #include "fuchsia_web/webengine/browser/context_impl.h"
  19. #include "fuchsia_web/webengine/browser/frame_impl.h"
  20. #include "fuchsia_web/webengine/browser/mock_virtual_keyboard.h"
  21. #include "fuchsia_web/webengine/features.h"
  22. #include "fuchsia_web/webengine/test/frame_for_test.h"
  23. #include "fuchsia_web/webengine/test/scenic_test_helper.h"
  24. #include "fuchsia_web/webengine/test/scoped_connection_checker.h"
  25. #include "fuchsia_web/webengine/test/test_data.h"
  26. #include "fuchsia_web/webengine/test/web_engine_browser_test.h"
  27. #include "testing/gtest/include/gtest/gtest.h"
  28. namespace virtualkeyboard = fuchsia::input::virtualkeyboard;
  29. namespace {
  30. const gfx::Point kNoTarget = {999, 999};
  31. constexpr char kInputFieldText[] = "input-text";
  32. constexpr char kInputFieldModeTel[] = "input-mode-tel";
  33. constexpr char kInputFieldModeNumeric[] = "input-mode-numeric";
  34. constexpr char kInputFieldModeUrl[] = "input-mode-url";
  35. constexpr char kInputFieldModeEmail[] = "input-mode-email";
  36. constexpr char kInputFieldModeDecimal[] = "input-mode-decimal";
  37. constexpr char kInputFieldModeSearch[] = "input-mode-search";
  38. constexpr char kInputFieldTypeTel[] = "input-type-tel";
  39. constexpr char kInputFieldTypeNumber[] = "input-type-number";
  40. constexpr char kInputFieldTypePassword[] = "input-type-password";
  41. class VirtualKeyboardTest : public WebEngineBrowserTest {
  42. public:
  43. VirtualKeyboardTest() {
  44. set_test_server_root(base::FilePath(kTestServerRoot));
  45. }
  46. ~VirtualKeyboardTest() override = default;
  47. void SetUp() override {
  48. scoped_feature_list_.InitWithFeatures(
  49. {features::kVirtualKeyboard, features::kKeyboardInput}, {});
  50. WebEngineBrowserTest::SetUp();
  51. }
  52. void SetUpOnMainThread() override {
  53. WebEngineBrowserTest::SetUpOnMainThread();
  54. ASSERT_TRUE(embedded_test_server()->Start());
  55. fuchsia::web::CreateFrameParams params;
  56. frame_for_test_ = FrameForTest::Create(context(), std::move(params));
  57. component_context_.emplace(
  58. base::TestComponentContextForProcess::InitialState::kCloneAll);
  59. controller_creator_.emplace(&*component_context_);
  60. controller_ = controller_creator_->CreateController();
  61. // Ensure that the fuchsia.ui.input3.Keyboard service is connected.
  62. component_context_->additional_services()
  63. ->RemovePublicService<fuchsia::ui::input3::Keyboard>();
  64. keyboard_input_checker_.emplace(component_context_->additional_services());
  65. fuchsia::web::NavigationControllerPtr controller;
  66. frame_for_test_.ptr()->GetNavigationController(controller.NewRequest());
  67. const GURL test_url(embedded_test_server()->GetURL("/input_fields.html"));
  68. EXPECT_TRUE(LoadUrlAndExpectResponse(
  69. controller.get(), fuchsia::web::LoadUrlParams(), test_url.spec()));
  70. frame_for_test_.navigation_listener().RunUntilUrlEquals(test_url);
  71. fuchsia::web::FramePtr* frame_ptr = &(frame_for_test_.ptr());
  72. web_contents_ =
  73. context_impl()->GetFrameImplForTest(frame_ptr)->web_contents();
  74. scenic_test_helper_.CreateScenicView(
  75. context_impl()->GetFrameImplForTest(frame_ptr), frame_for_test_.ptr());
  76. scenic_test_helper_.SetUpViewForInteraction(web_contents_);
  77. controller_->AwaitWatchAndRespondWith(false);
  78. ASSERT_EQ(
  79. base::GetKoid(controller_->view_ref().reference).value(),
  80. base::GetKoid(scenic_test_helper_.CloneViewRef().reference).value());
  81. }
  82. // The tests expect to have input processed immediately, even if the
  83. // content has not been displayed yet. That's fine for the test, but
  84. // we need to explicitly allow it.
  85. void SetUpCommandLine(base::CommandLine* command_line) override {
  86. command_line->AppendSwitch("allow-pre-commit-input");
  87. }
  88. gfx::Point GetCoordinatesOfInputField(base::StringPiece id) {
  89. // Distance to click from the top/left extents of an input field.
  90. constexpr int kInputFieldClickInset = 8;
  91. absl::optional<base::Value> result = ExecuteJavaScript(
  92. frame_for_test_.ptr().get(),
  93. base::StringPrintf("getPointInsideText('%.*s')",
  94. base::saturated_cast<int>(id.length()), id.data()));
  95. if (!result || !result->is_dict()) {
  96. ADD_FAILURE() << "!result";
  97. return {};
  98. }
  99. // Note that coordinates are floating point and must be retrieved as such
  100. // from the Value, but we can cast them to integers and disregard the
  101. // fractional value with no major consequences.
  102. return gfx::Point(
  103. *result->GetDict().FindDouble("x") + kInputFieldClickInset,
  104. *result->GetDict().FindDouble("y") + kInputFieldClickInset);
  105. }
  106. protected:
  107. FrameForTest frame_for_test_;
  108. ScenicTestHelper scenic_test_helper_;
  109. base::test::ScopedFeatureList scoped_feature_list_;
  110. absl::optional<EnsureConnectedChecker<fuchsia::ui::input3::Keyboard>>
  111. keyboard_input_checker_;
  112. // Fake virtual keyboard services for the InputMethod to use.
  113. absl::optional<base::TestComponentContextForProcess> component_context_;
  114. absl::optional<MockVirtualKeyboardControllerCreator> controller_creator_;
  115. std::unique_ptr<MockVirtualKeyboardController> controller_;
  116. content::WebContents* web_contents_ = nullptr;
  117. };
  118. // Verifies that RequestShow() is not called redundantly if the virtual
  119. // keyboard is reported as visible.
  120. IN_PROC_BROWSER_TEST_F(VirtualKeyboardTest, ShowAndHideWithVisibility) {
  121. testing::InSequence s;
  122. // Alphanumeric field click.
  123. base::RunLoop on_show_run_loop;
  124. EXPECT_CALL(*controller_, RequestShow())
  125. .WillOnce(testing::InvokeWithoutArgs(
  126. [&on_show_run_loop]() { on_show_run_loop.Quit(); }))
  127. .RetiresOnSaturation();
  128. // Numeric field click.
  129. base::RunLoop click_numeric_run_loop;
  130. EXPECT_CALL(*controller_, RequestHide()).RetiresOnSaturation();
  131. EXPECT_CALL(*controller_, SetTextType(virtualkeyboard::TextType::NUMERIC))
  132. .RetiresOnSaturation();
  133. EXPECT_CALL(*controller_, RequestShow())
  134. .WillOnce(testing::InvokeWithoutArgs(
  135. [&click_numeric_run_loop]() { click_numeric_run_loop.Quit(); }))
  136. .RetiresOnSaturation();
  137. // Input blur click.
  138. base::RunLoop on_hide_run_loop;
  139. EXPECT_CALL(*controller_, RequestHide())
  140. .WillOnce(testing::InvokeWithoutArgs(
  141. [&on_hide_run_loop]() { on_hide_run_loop.Quit(); }))
  142. .RetiresOnSaturation();
  143. // In some cases, Blink may signal an
  144. // InputMethodClient::OnTextInputTypeChanged event, which will cause
  145. // an extra call to VirtualKeyboardController:RequestHide. This is harmless
  146. // in practice due to RequestHide()'s idempotence, however we still need to
  147. // anticipate that behavior in the controller mocks.
  148. EXPECT_CALL(*controller_, RequestHide()).Times(testing::AtMost(1));
  149. // Give focus to an alphanumeric input field, which will result in
  150. // RequestShow() being called.
  151. content::SimulateTapAt(web_contents_,
  152. GetCoordinatesOfInputField(kInputFieldText));
  153. on_show_run_loop.Run();
  154. EXPECT_EQ(controller_->text_type(), virtualkeyboard::TextType::ALPHANUMERIC);
  155. // Indicate that the virtual keyboard is now visible.
  156. controller_->AwaitWatchAndRespondWith(true);
  157. base::RunLoop().RunUntilIdle();
  158. // Tap on another text field. RequestShow should not be called a second time
  159. // since the keyboard is already onscreen.
  160. content::SimulateTapAt(web_contents_,
  161. GetCoordinatesOfInputField(kInputFieldModeNumeric));
  162. click_numeric_run_loop.Run();
  163. // Trigger input blur by clicking outside any input element.
  164. content::SimulateTapAt(web_contents_, kNoTarget);
  165. on_hide_run_loop.Run();
  166. }
  167. // Gives focus to a sequence of HTML <input> nodes with different InputModes,
  168. // and verifies that the InputMode's FIDL equivalent is sent via SetTextType().
  169. IN_PROC_BROWSER_TEST_F(VirtualKeyboardTest, InputModeMappings) {
  170. // Note that the service will elide type updates if there is no change,
  171. // so the array is ordered to produce an update on each entry.
  172. const std::vector<std::pair<base::StringPiece, virtualkeyboard::TextType>>
  173. kInputTypeMappings = {
  174. {kInputFieldModeTel, virtualkeyboard::TextType::PHONE},
  175. {kInputFieldModeSearch, virtualkeyboard::TextType::ALPHANUMERIC},
  176. {kInputFieldModeNumeric, virtualkeyboard::TextType::NUMERIC},
  177. {kInputFieldModeUrl, virtualkeyboard::TextType::ALPHANUMERIC},
  178. {kInputFieldModeDecimal, virtualkeyboard::TextType::NUMERIC},
  179. {kInputFieldModeEmail, virtualkeyboard::TextType::ALPHANUMERIC},
  180. {kInputFieldTypeTel, virtualkeyboard::TextType::PHONE},
  181. {kInputFieldTypeNumber, virtualkeyboard::TextType::NUMERIC},
  182. {kInputFieldTypePassword, virtualkeyboard::TextType::ALPHANUMERIC},
  183. };
  184. // GMock expectations must be set upfront, hence the redundant for-each loop.
  185. testing::InSequence s;
  186. virtualkeyboard::TextType previous_text_type =
  187. virtualkeyboard::TextType::ALPHANUMERIC;
  188. std::vector<base::RunLoop> set_type_loops(std::size(kInputTypeMappings));
  189. for (size_t i = 0; i < std::size(kInputTypeMappings); ++i) {
  190. const auto& field_type_pair = kInputTypeMappings[i];
  191. EXPECT_NE(field_type_pair.second, previous_text_type);
  192. EXPECT_CALL(*controller_, SetTextType(field_type_pair.second))
  193. .WillOnce(testing::InvokeWithoutArgs(
  194. [run_loop = &set_type_loops[i]]() mutable { run_loop->Quit(); }))
  195. .RetiresOnSaturation();
  196. previous_text_type = field_type_pair.second;
  197. }
  198. controller_->AwaitWatchAndRespondWith(false);
  199. for (size_t i = 0; i < std::size(kInputTypeMappings); ++i) {
  200. content::SimulateTapAt(
  201. web_contents_, GetCoordinatesOfInputField(kInputTypeMappings[i].first));
  202. // Spin the runloop until we've received the type update.
  203. set_type_loops[i].Run();
  204. }
  205. }
  206. IN_PROC_BROWSER_TEST_F(VirtualKeyboardTest, Disconnection) {
  207. testing::InSequence s;
  208. base::RunLoop on_show_run_loop;
  209. EXPECT_CALL(*controller_, RequestShow())
  210. .WillOnce(testing::InvokeWithoutArgs(
  211. [&on_show_run_loop]() { on_show_run_loop.Quit(); }));
  212. // Tapping inside the text field should show the IME and signal RequestShow.
  213. content::SimulateTapAt(web_contents_,
  214. GetCoordinatesOfInputField(kInputFieldText));
  215. on_show_run_loop.Run();
  216. controller_->AwaitWatchAndRespondWith(true);
  217. base::RunLoop().RunUntilIdle();
  218. // Disconnect the FIDL service.
  219. controller_.reset();
  220. base::RunLoop().RunUntilIdle();
  221. // Focus on another text field, then defocus. Nothing should crash.
  222. content::SimulateTapAt(web_contents_,
  223. GetCoordinatesOfInputField(kInputFieldModeNumeric));
  224. content::SimulateTapAt(web_contents_, kNoTarget);
  225. }
  226. } // namespace