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