1 // Copyright 2017 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
5 #include <memory>
6 #include <string>
7 #include <utility>
8
9 #include "base/bind.h"
10 #include "base/strings/utf_string_conversions.h"
11 #include "build/build_config.h"
12 #include "chrome/browser/vr/elements/content_element.h"
13 #include "chrome/browser/vr/elements/keyboard.h"
14 #include "chrome/browser/vr/model/model.h"
15 #include "chrome/browser/vr/platform_input_handler.h"
16 #include "chrome/browser/vr/test/mock_keyboard_delegate.h"
17 #include "chrome/browser/vr/test/mock_text_input_delegate.h"
18 #include "chrome/browser/vr/test/ui_test.h"
19 #include "chrome/browser/vr/text_edit_action.h"
20 #include "chrome/browser/vr/ui_input_manager.h"
21 #include "chrome/browser/vr/ui_scene.h"
22 #include "testing/gmock/include/gmock/gmock.h"
23
24 using ::testing::_;
25 using ::testing::InSequence;
26 using ::testing::StrictMock;
27
28 namespace vr {
29
30 class TestContentInputDelegate : public MockContentInputDelegate {
31 public:
TestContentInputDelegate()32 TestContentInputDelegate() { info_ = TextInputInfo(); }
33
SetTextInputInfo(const TextInputInfo & info)34 void SetTextInputInfo(const TextInputInfo& info) { info_ = info; }
35
OnWebInputIndicesChanged(int selection_start,int selection_end,int composition_start,int compositon_end,base::OnceCallback<void (const TextInputInfo &)> callback)36 void OnWebInputIndicesChanged(
37 int selection_start,
38 int selection_end,
39 int composition_start,
40 int compositon_end,
41 base::OnceCallback<void(const TextInputInfo&)> callback) override {
42 info_.selection_start = selection_start;
43 info_.selection_end = selection_end;
44 info_.composition_start = composition_start;
45 info_.composition_end = compositon_end;
46 std::move(callback).Run(info_);
47 }
48
49 private:
50 TextInputInfo info_;
51 };
52
53 class TestPlatformInputHandler : public PlatformInputHandler {
54 public:
TestPlatformInputHandler()55 TestPlatformInputHandler() {}
~TestPlatformInputHandler()56 ~TestPlatformInputHandler() override {}
57
ForwardEventToPlatformUi(std::unique_ptr<InputEvent>)58 void ForwardEventToPlatformUi(std::unique_ptr<InputEvent>) override {}
ForwardEventToContent(std::unique_ptr<InputEvent>,int)59 void ForwardEventToContent(std::unique_ptr<InputEvent>, int) override {}
60
ClearFocusedElement()61 void ClearFocusedElement() override { clear_focus_called_ = true; }
OnWebInputEdited(const TextEdits & edits)62 void OnWebInputEdited(const TextEdits& edits) override { edits_ = edits; }
SubmitWebInput()63 void SubmitWebInput() override {}
RequestWebInputText(TextStateUpdateCallback callback)64 void RequestWebInputText(TextStateUpdateCallback callback) override {
65 web_input_text_requests.emplace(std::move(callback));
66 }
67
report_text_state(const base::string16 & text)68 void report_text_state(const base::string16& text) {
69 while (!web_input_text_requests.empty()) {
70 auto callback = std::move(web_input_text_requests.front());
71 web_input_text_requests.pop();
72 std::move(callback).Run(text);
73 }
74 }
75
edits()76 TextEdits edits() { return edits_; }
text_state_requested()77 bool text_state_requested() { return !web_input_text_requests.empty(); }
clear_focus_called()78 bool clear_focus_called() { return clear_focus_called_; }
79
Reset()80 void Reset() {
81 edits_.clear();
82 clear_focus_called_ = false;
83 web_input_text_requests = {};
84 }
85
86 private:
87 TextEdits edits_;
88 bool clear_focus_called_ = false;
89 std::queue<TextStateUpdateCallback> web_input_text_requests;
90
91 DISALLOW_COPY_AND_ASSIGN(TestPlatformInputHandler);
92 };
93
94 class ContentElementSceneTest : public UiTest {
95 public:
SetUp()96 void SetUp() override {
97 UiTest::SetUp();
98
99 UiInitialState state;
100 state.in_web_vr = false;
101 auto content_input_delegate =
102 std::make_unique<testing::NiceMock<TestContentInputDelegate>>();
103 CreateSceneInternal(state, std::move(content_input_delegate));
104
105 // Make test text input.
106 text_input_delegate_ =
107 std::make_unique<StrictMock<MockTextInputDelegate>>();
108
109 input_forwarder_ = std::make_unique<TestPlatformInputHandler>();
110 ui_instance_->GetContentInputDelegateForTest()
111 ->SetPlatformInputHandlerForTest(input_forwarder_.get());
112
113 auto* content =
114 static_cast<ContentElement*>(scene_->GetUiElementByName(kContentQuad));
115 content->SetTextInputDelegate(text_input_delegate_.get());
116 AdvanceFrame();
117 }
118
119 protected:
120 std::unique_ptr<StrictMock<MockTextInputDelegate>> text_input_delegate_;
121 std::unique_ptr<TestPlatformInputHandler> input_forwarder_;
122 testing::InSequence in_sequence_;
123 };
124
TEST_F(ContentElementSceneTest,WebInputFocus)125 TEST_F(ContentElementSceneTest, WebInputFocus) {
126 auto* content =
127 static_cast<ContentElement*>(scene_->GetUiElementByName(kContentQuad));
128 // Set mock keyboard delegate.
129 auto* kb = static_cast<Keyboard*>(scene_->GetUiElementByName(kKeyboard));
130 auto kb_delegate = std::make_unique<StrictMock<MockKeyboardDelegate>>();
131 EXPECT_CALL(*kb_delegate, HideKeyboard());
132 kb->SetKeyboardDelegate(kb_delegate.get());
133
134 auto browser_ui = ui_->GetBrowserUiWeakPtr();
135 // Editing web input.
136 EXPECT_CALL(*text_input_delegate_, RequestFocus(_));
137 EXPECT_CALL(*kb_delegate, ShowKeyboard());
138 EXPECT_CALL(*kb_delegate, OnBeginFrame());
139 EXPECT_CALL(*kb_delegate, SetTransform(_));
140 browser_ui->ShowSoftInput(true);
141 EXPECT_TRUE(AdvanceFrame());
142
143 // Giving content focus should tell the delegate the focued field's content.
144 EXPECT_CALL(*text_input_delegate_, UpdateInput(_));
145 EXPECT_CALL(*kb_delegate, OnBeginFrame());
146 EXPECT_CALL(*kb_delegate, SetTransform(_));
147 content->OnFocusChanged(true);
148 EXPECT_FALSE(AdvanceFrame());
149
150 // Updates from the browser should update keyboard state.
151 TextInputInfo info(base::ASCIIToUTF16("asdfg"));
152 static_cast<TestContentInputDelegate*>(content_input_delegate_)
153 ->SetTextInputInfo(info);
154 info.selection_start = 1;
155 info.selection_end = 1;
156 info.composition_start = 0;
157 info.composition_end = 1;
158 EXPECT_CALL(*text_input_delegate_, UpdateInput(info));
159 EXPECT_CALL(*kb_delegate, OnBeginFrame());
160 EXPECT_CALL(*kb_delegate, SetTransform(_));
161 browser_ui->UpdateWebInputIndices(1, 1, 0, 1);
162 EXPECT_TRUE(AdvanceFrame());
163
164 // End editing.
165 EXPECT_CALL(*kb_delegate, HideKeyboard());
166 EXPECT_CALL(*kb_delegate, OnBeginFrame());
167 EXPECT_CALL(*kb_delegate, SetTransform(_));
168 browser_ui->ShowSoftInput(false);
169 EXPECT_TRUE(AdvanceFrame());
170
171 // Taking focus away from content should clear the delegate state.
172 EXPECT_FALSE(input_forwarder_->clear_focus_called());
173 content->OnFocusChanged(false);
174 EXPECT_TRUE(input_forwarder_->clear_focus_called());
175
176 // OnBeginFrame on the keyboard delegate should be called despite of
177 // visibility.
178 EXPECT_CALL(*kb_delegate, OnBeginFrame());
179 AdvanceFrame();
180 }
181
182 // Verify that we clear the model for the web input field when it loses focus to
183 // prevent updating the keyboard with the old state when it gains focus again.
TEST_F(ContentElementSceneTest,ClearWebInputInfoModel)184 TEST_F(ContentElementSceneTest, ClearWebInputInfoModel) {
185 auto* content =
186 static_cast<ContentElement*>(scene_->GetUiElementByName(kContentQuad));
187 // Initial state.
188 EditedText info = model_->web_input_text_field_info;
189 info.current = TextInputInfo(base::ASCIIToUTF16("asdfg"));
190 model_->set_web_input_text_field_info(info);
191 EXPECT_TRUE(AdvanceFrame());
192
193 // Initial state gets pushed when the content element is focused.
194 EXPECT_CALL(*text_input_delegate_, UpdateInput(info.current));
195 content->OnFocusChanged(true);
196 EXPECT_FALSE(AdvanceFrame());
197
198 // Unfocus the content element.
199 content->OnFocusChanged(false);
200 EXPECT_TRUE(AdvanceFrame());
201
202 // A cleared state gets pushed when the content element is focused. This is
203 // needed because the user may have clicked another text field in the content,
204 // so we shouldn't be pushing the stale state.
205 EXPECT_CALL(*text_input_delegate_, UpdateInput(TextInputInfo()));
206 content->OnFocusChanged(true);
207 EXPECT_FALSE(AdvanceFrame());
208 }
209
210 class ContentElementInputEditingTest : public UiTest {
211 public:
SetUp()212 void SetUp() override {
213 UiTest::SetUp();
214
215 CreateScene(kNotInWebVr);
216
217 text_input_delegate_ =
218 std::make_unique<StrictMock<MockTextInputDelegate>>();
219 input_forwarder_ = std::make_unique<TestPlatformInputHandler>();
220 content_delegate_ = ui_instance_->GetContentInputDelegateForTest();
221 content_delegate_->SetPlatformInputHandlerForTest(input_forwarder_.get());
222
223 content_ =
224 static_cast<ContentElement*>(scene_->GetUiElementByName(kContentQuad));
225 content_->SetTextInputDelegate(text_input_delegate_.get());
226 AdvanceFrame();
227 }
228
229 protected:
GetInputInfo(const std::string & text,int selection_start,int selection_end,int composition_start,int composition_end)230 TextInputInfo GetInputInfo(const std::string& text,
231 int selection_start,
232 int selection_end,
233 int composition_start,
234 int composition_end) {
235 return TextInputInfo(base::UTF8ToUTF16(text), selection_start,
236 selection_end, composition_start, composition_end);
237 }
238
GetInputInfo(const std::string & text,int selection_start,int selection_end)239 TextInputInfo GetInputInfo(const std::string& text,
240 int selection_start,
241 int selection_end) {
242 return GetInputInfo(text, selection_start, selection_end, -1, -1);
243 }
244
SetInput(const TextInputInfo & current,const TextInputInfo & previous)245 void SetInput(const TextInputInfo& current, const TextInputInfo& previous) {
246 EditedText info;
247 info.current = current;
248 info.previous = previous;
249 content_->OnInputEdited(info);
250 }
251
252 std::unique_ptr<StrictMock<MockTextInputDelegate>> text_input_delegate_;
253 std::unique_ptr<TestPlatformInputHandler> input_forwarder_;
254 ContentInputDelegate* content_delegate_;
255 ContentElement* content_;
256 };
257
TEST_F(ContentElementInputEditingTest,IndicesUpdated)258 TEST_F(ContentElementInputEditingTest, IndicesUpdated) {
259 // If the changed indices match with that from the last keyboard edit, the
260 // given callback is triggered right away.
261 SetInput(GetInputInfo("asdf", 4, 4), GetInputInfo("", 0, 0));
262 TextInputInfo model_actual;
263 TextInputInfo model_exp(base::UTF8ToUTF16("asdf"));
264 content_delegate_->OnWebInputIndicesChanged(
265 4, 4, -1, -1,
266 base::BindOnce([](TextInputInfo* model,
267 const TextInputInfo& info) { *model = info; },
268 base::Unretained(&model_actual)));
269 EXPECT_FALSE(input_forwarder_->text_state_requested());
270 EXPECT_EQ(model_actual, model_exp);
271
272 // If the changed indices do not match with that from the last keyboard edit,
273 // the latest text state is requested for and the given callback is called
274 // when the response arrives.
275 content_delegate_->OnWebInputIndicesChanged(
276 2, 2, -1, -1,
277 base::BindOnce([](TextInputInfo* model,
278 const TextInputInfo& info) { *model = info; },
279 base::Unretained(&model_actual)));
280 EXPECT_TRUE(input_forwarder_->text_state_requested());
281 input_forwarder_->report_text_state(base::UTF8ToUTF16("asdf"));
282 model_exp.selection_start = 2;
283 model_exp.selection_end = 2;
284 EXPECT_EQ(model_actual, model_exp);
285 }
286
287 } // namespace vr
288