1 // Copyright 2016 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 <vector>
6
7 #include "base/bind.h"
8 #include "base/command_line.h"
9 #include "base/run_loop.h"
10 #include "base/strings/stringprintf.h"
11 #include "base/strings/utf_string_conversions.h"
12 #include "build/build_config.h"
13 #include "chrome/browser/ui/browser.h"
14 #include "chrome/browser/ui/browser_window.h"
15 #include "chrome/browser/ui/location_bar/location_bar.h"
16 #include "chrome/browser/ui/tabs/tab_strip_model.h"
17 #include "chrome/test/base/in_process_browser_test.h"
18 #include "chrome/test/base/interactive_test_utils.h"
19 #include "chrome/test/base/ui_test_utils.h"
20 #include "content/public/browser/browser_task_traits.h"
21 #include "content/public/browser/browser_thread.h"
22 #include "content/public/browser/content_browser_client.h"
23 #include "content/public/browser/render_frame_host.h"
24 #include "content/public/browser/render_process_host.h"
25 #include "content/public/browser/render_widget_host.h"
26 #include "content/public/browser/render_widget_host_view.h"
27 #include "content/public/browser/web_contents.h"
28 #include "content/public/common/content_client.h"
29 #include "content/public/test/browser_test.h"
30 #include "content/public/test/browser_test_utils.h"
31 #include "content/public/test/content_browser_test_utils.h"
32 #include "content/public/test/test_utils.h"
33 #include "content/public/test/text_input_test_utils.h"
34 #include "net/dns/mock_host_resolver.h"
35 #include "net/test/embedded_test_server/embedded_test_server.h"
36 #include "ui/base/ime/ime_text_span.h"
37 #include "ui/base/ime/text_edit_commands.h"
38 #include "ui/base/ime/text_input_client.h"
39 #include "ui/base/ime/text_input_mode.h"
40 #include "ui/base/ime/text_input_type.h"
41 #include "url/gurl.h"
42
43 #if defined(OS_LINUX) && !defined(OS_CHROMEOS)
44 #include "ui/base/ime/linux/text_edit_command_auralinux.h"
45 #include "ui/base/ime/linux/text_edit_key_bindings_delegate_auralinux.h"
46 #endif
47
48 ///////////////////////////////////////////////////////////////////////////////
49 // TextInputManager and IME Tests
50 //
51 // The following tests verify the correctness of TextInputState tracking on the
52 // browser side. They also make sure the IME logic works correctly. The baseline
53 // for comparison is the default functionality in the non-OOPIF case (i.e., the
54 // legacy implementation in RWHV's other than RWHVCF).
55 // These tests live outside content/ because they rely on being part of the
56 // interactive UI test framework (to avoid flakiness).
57
58 namespace {
59 using IndexVector = std::vector<size_t>;
60
61 // This class observes TextInputManager for the first change in TextInputState.
62 class TextInputManagerChangeObserver
63 : public content::TextInputManagerObserverBase {
64 public:
TextInputManagerChangeObserver(content::WebContents * web_contents)65 explicit TextInputManagerChangeObserver(content::WebContents* web_contents)
66 : TextInputManagerObserverBase(web_contents) {
67 tester()->SetUpdateTextInputStateCalledCallback(base::Bind(
68 &TextInputManagerChangeObserver::VerifyChange, base::Unretained(this)));
69 }
70
71 private:
VerifyChange()72 void VerifyChange() {
73 if (tester()->IsTextInputStateChanged())
74 OnSuccess();
75 }
76
77 DISALLOW_COPY_AND_ASSIGN(TextInputManagerChangeObserver);
78 };
79
80 // This class observes |TextInputState.type| for a specific RWHV.
81 class ViewTextInputTypeObserver : public content::TextInputManagerObserverBase {
82 public:
ViewTextInputTypeObserver(content::WebContents * web_contents,content::RenderWidgetHostView * rwhv,ui::TextInputType expected_type)83 explicit ViewTextInputTypeObserver(content::WebContents* web_contents,
84 content::RenderWidgetHostView* rwhv,
85 ui::TextInputType expected_type)
86 : TextInputManagerObserverBase(web_contents),
87 web_contents_(web_contents),
88 view_(rwhv),
89 expected_type_(expected_type) {
90 tester()->SetUpdateTextInputStateCalledCallback(base::Bind(
91 &ViewTextInputTypeObserver::VerifyType, base::Unretained(this)));
92 }
93
94 private:
VerifyType()95 void VerifyType() {
96 ui::TextInputType type;
97 if (!content::GetTextInputTypeForView(web_contents_, view_, &type))
98 return;
99 if (expected_type_ == type)
100 OnSuccess();
101 }
102
103 content::WebContents* web_contents_;
104 content::RenderWidgetHostView* view_;
105 const ui::TextInputType expected_type_;
106
107 DISALLOW_COPY_AND_ASSIGN(ViewTextInputTypeObserver);
108 };
109
110 // This class observes the |expected_view| for the first change in its
111 // selection bounds.
112 class ViewSelectionBoundsChangedObserver
113 : public content::TextInputManagerObserverBase {
114 public:
ViewSelectionBoundsChangedObserver(content::WebContents * web_contents,content::RenderWidgetHostView * expected_view)115 ViewSelectionBoundsChangedObserver(
116 content::WebContents* web_contents,
117 content::RenderWidgetHostView* expected_view)
118 : TextInputManagerObserverBase(web_contents),
119 expected_view_(expected_view) {
120 tester()->SetOnSelectionBoundsChangedCallback(
121 base::Bind(&ViewSelectionBoundsChangedObserver::VerifyChange,
122 base::Unretained(this)));
123 }
124
125 private:
VerifyChange()126 void VerifyChange() {
127 if (expected_view_ == tester()->GetUpdatedView())
128 OnSuccess();
129 }
130
131 const content::RenderWidgetHostView* const expected_view_;
132
133 DISALLOW_COPY_AND_ASSIGN(ViewSelectionBoundsChangedObserver);
134 };
135
136 // This class observes the |expected_view| for the first change in its
137 // composition range information.
138 class ViewCompositionRangeChangedObserver
139 : public content::TextInputManagerObserverBase {
140 public:
ViewCompositionRangeChangedObserver(content::WebContents * web_contents,content::RenderWidgetHostView * expected_view)141 ViewCompositionRangeChangedObserver(
142 content::WebContents* web_contents,
143 content::RenderWidgetHostView* expected_view)
144 : TextInputManagerObserverBase(web_contents),
145 expected_view_(expected_view) {
146 tester()->SetOnImeCompositionRangeChangedCallback(
147 base::Bind(&ViewCompositionRangeChangedObserver::VerifyChange,
148 base::Unretained(this)));
149 }
150
151 private:
VerifyChange()152 void VerifyChange() {
153 if (expected_view_ == tester()->GetUpdatedView())
154 OnSuccess();
155 }
156
157 const content::RenderWidgetHostView* const expected_view_;
158
159 DISALLOW_COPY_AND_ASSIGN(ViewCompositionRangeChangedObserver);
160 };
161
162 // This class observes the |expected_view| for a change in the text selection.
163 class ViewTextSelectionObserver : public content::TextInputManagerObserverBase {
164 public:
ViewTextSelectionObserver(content::WebContents * web_contents,content::RenderWidgetHostView * expected_view,size_t expected_length)165 ViewTextSelectionObserver(content::WebContents* web_contents,
166 content::RenderWidgetHostView* expected_view,
167 size_t expected_length)
168 : TextInputManagerObserverBase(web_contents),
169 expected_view_(expected_view),
170 expected_length_(expected_length) {
171 tester()->SetOnTextSelectionChangedCallback(base::Bind(
172 &ViewTextSelectionObserver::VerifyChange, base::Unretained(this)));
173 }
174
175 private:
VerifyChange()176 void VerifyChange() {
177 if (expected_view_ == tester()->GetUpdatedView()) {
178 size_t length;
179 EXPECT_TRUE(tester()->GetCurrentTextSelectionLength(&length));
180 if (length == expected_length_)
181 OnSuccess();
182 }
183 }
184
185 const content::RenderWidgetHostView* const expected_view_;
186 const size_t expected_length_;
187
188 DISALLOW_COPY_AND_ASSIGN(ViewTextSelectionObserver);
189 };
190
191 // This class observes all the text selection updates within a WebContents.
192 class TextSelectionObserver : public content::TextInputManagerObserverBase {
193 public:
TextSelectionObserver(content::WebContents * web_contents)194 explicit TextSelectionObserver(content::WebContents* web_contents)
195 : TextInputManagerObserverBase(web_contents) {
196 tester()->SetOnTextSelectionChangedCallback(base::Bind(
197 &TextSelectionObserver::VerifyChange, base::Unretained(this)));
198 }
199
WaitForSelectedText(const std::string & text)200 void WaitForSelectedText(const std::string& text) {
201 selected_text_ = text;
202 Wait();
203 }
204
205 private:
VerifyChange()206 void VerifyChange() {
207 if (base::UTF16ToUTF8(tester()->GetUpdatedView()->GetSelectedText()) ==
208 selected_text_) {
209 OnSuccess();
210 }
211 }
212
213 std::string selected_text_;
214
215 DISALLOW_COPY_AND_ASSIGN(TextSelectionObserver);
216 };
217
218 // This class monitors all the changes in TextInputState and keeps a record of
219 // the active views. There is no waiting and the recording process is
220 // continuous.
221 class RecordActiveViewsObserver {
222 public:
RecordActiveViewsObserver(content::WebContents * web_contents)223 explicit RecordActiveViewsObserver(content::WebContents* web_contents)
224 : tester_(new content::TextInputManagerTester(web_contents)) {
225 tester_->SetUpdateTextInputStateCalledCallback(base::Bind(
226 &RecordActiveViewsObserver::RecordActiveView, base::Unretained(this)));
227 }
228
active_views() const229 const std::vector<const content::RenderWidgetHostView*>* active_views()
230 const {
231 return &active_views_;
232 }
233
234 private:
RecordActiveView()235 void RecordActiveView() {
236 if (!tester_->IsTextInputStateChanged())
237 return;
238 active_views_.push_back(tester_->GetActiveView());
239 }
240
241 std::unique_ptr<content::TextInputManagerTester> tester_;
242 std::vector<const content::RenderWidgetHostView*> active_views_;
243
244 DISALLOW_COPY_AND_ASSIGN(RecordActiveViewsObserver);
245 };
246
247 } // namespace
248
249 // Main class for all TextInputState and IME related tests.
250 class SitePerProcessTextInputManagerTest : public InProcessBrowserTest {
251 public:
SitePerProcessTextInputManagerTest()252 SitePerProcessTextInputManagerTest() {}
~SitePerProcessTextInputManagerTest()253 ~SitePerProcessTextInputManagerTest() override {}
254
SetUpCommandLine(base::CommandLine * command_line)255 void SetUpCommandLine(base::CommandLine* command_line) override {
256 content::IsolateAllSitesForTesting(command_line);
257 }
258
SetUpOnMainThread()259 void SetUpOnMainThread() override {
260 host_resolver()->AddRule("*", "127.0.0.1");
261
262 // Add content/test/data 'cross_site_iframe_factory.html'.
263 embedded_test_server()->ServeFilesFromSourceDirectory("content/test/data");
264
265 ASSERT_TRUE(embedded_test_server()->Start());
266 }
267
268 protected:
active_contents()269 content::WebContents* active_contents() {
270 return browser()->tab_strip_model()->GetActiveWebContents();
271 }
272
273 // static
274 // Adds an <input> field to a given frame by executing javascript code.
275 // The input can be added as the first element or the last element of
276 // |document.body|. The text range defined by |selection_range| will be
277 // marked.
AddInputFieldToFrame(content::RenderFrameHost * rfh,const std::string & type,const std::string & value,bool append_as_first_child)278 static void AddInputFieldToFrame(content::RenderFrameHost* rfh,
279 const std::string& type,
280 const std::string& value,
281 bool append_as_first_child) {
282 std::string script = base::StringPrintf(
283 "var input = document.createElement('input');"
284 "input.setAttribute('type', '%s');"
285 "input.setAttribute('value', '%s');"
286 "document.body.%s;",
287 type.c_str(), value.c_str(),
288 append_as_first_child ? "insertBefore(input, document.body.firstChild)"
289 : "appendChild(input)");
290 EXPECT_TRUE(ExecuteScript(rfh, script));
291 }
292
293 // static
294 // Appends an <input> field with various attribues to a given frame by
295 // executing javascript code.
AppendInputFieldToFrame(content::RenderFrameHost * rfh,const std::string & type,const std::string & id,const std::string & value,const std::string & placeholder)296 static void AppendInputFieldToFrame(content::RenderFrameHost* rfh,
297 const std::string& type,
298 const std::string& id,
299 const std::string& value,
300 const std::string& placeholder) {
301 std::string script = base::StringPrintf(
302 "var input = document.createElement('input');"
303 "input.setAttribute('type', '%s');"
304 "input.setAttribute('id', '%s');"
305 "input.setAttribute('value', '%s');"
306 "input.setAttribute('placeholder', '%s');"
307 "document.body.appendChild(input);",
308 type.c_str(), id.c_str(), value.c_str(), placeholder.c_str());
309 EXPECT_TRUE(ExecuteScript(rfh, script));
310 }
311
312 // static
313 // Focus a form field by its Id.
FocusFormField(content::RenderFrameHost * rfh,const std::string & id)314 static void FocusFormField(content::RenderFrameHost* rfh,
315 const std::string& id) {
316 std::string script = base::StringPrintf(
317 "document.getElementById('%s').focus();", id.c_str());
318
319 EXPECT_TRUE(ExecuteScript(rfh, script));
320 }
321
322 // Uses 'cross_site_iframe_factory.html'. The main frame's domain is
323 // 'a.com'.
CreateIframePage(const std::string & structure)324 void CreateIframePage(const std::string& structure) {
325 std::string path = base::StringPrintf("/cross_site_iframe_factory.html?%s",
326 structure.c_str());
327 GURL main_url(embedded_test_server()->GetURL("a.com", path));
328 ui_test_utils::NavigateToURL(browser(), main_url);
329 }
330
331 // Iteratively uses ChildFrameAt(frame, i) to get the i-th child frame
332 // inside frame. For example, for 'a(b(c, d(e)))', [0] returns b, and
333 // [0, 1, 0] returns e;
GetFrame(const IndexVector & indices)334 content::RenderFrameHost* GetFrame(const IndexVector& indices) {
335 content::RenderFrameHost* current = active_contents()->GetMainFrame();
336 for (size_t index : indices)
337 current = ChildFrameAt(current, index);
338 return current;
339 }
340
341 private:
342 DISALLOW_COPY_AND_ASSIGN(SitePerProcessTextInputManagerTest);
343 };
344
345 // The following test loads a page with multiple nested <iframe> elements which
346 // are in or out of process with the main frame. Then an <input> field with
347 // unique value is added to every single frame on the frame tree. The test then
348 // creates a sequence of tab presses and verifies that after each key press, the
349 // TextInputState.value reflects that of the focused input, i.e., the
350 // TextInputManager is correctly tracking TextInputState across frames.
351 // Flaky on ChromeOS, Linux, Mac, and Windows; https://crbug.com/704994.
IN_PROC_BROWSER_TEST_F(SitePerProcessTextInputManagerTest,DISABLED_TrackStateWhenSwitchingFocusedFrames)352 IN_PROC_BROWSER_TEST_F(SitePerProcessTextInputManagerTest,
353 DISABLED_TrackStateWhenSwitchingFocusedFrames) {
354 CreateIframePage("a(a,b,c(a,b,d(e, f)),g)");
355 std::vector<std::string> values{
356 "main", "node_a", "node_b", "node_c", "node_c_a",
357 "node_c_b", "node_c_d", "node_c_d_e", "node_c_d_f", "node_g"};
358
359 // TODO(ekaramad): The use for explicitly constructing the IndexVector from
360 // initializer list should not be necessary. However, some chromeos bots throw
361 // errors if we do not do it like this.
362 std::vector<content::RenderFrameHost*> frames{
363 GetFrame(IndexVector{}), GetFrame(IndexVector{0}),
364 GetFrame(IndexVector{1}), GetFrame(IndexVector{2}),
365 GetFrame(IndexVector{2, 0}), GetFrame(IndexVector{2, 1}),
366 GetFrame(IndexVector{2, 2}), GetFrame(IndexVector{2, 2, 0}),
367 GetFrame(IndexVector{2, 2, 1}), GetFrame(IndexVector{3})};
368
369 for (size_t i = 0; i < frames.size(); ++i)
370 AddInputFieldToFrame(frames[i], "text", values[i], true);
371
372 for (size_t i = 0; i < frames.size(); ++i) {
373 content::TextInputManagerValueObserver observer(active_contents(),
374 values[i]);
375 SimulateKeyPress(active_contents(), ui::DomKey::TAB, ui::DomCode::TAB,
376 ui::VKEY_TAB, false, false, false, false);
377 observer.Wait();
378 }
379 }
380
381 // The following test loads a page with two OOPIFs. An <input> is added to both
382 // frames and tab key is pressed until the one in the second OOPIF is focused.
383 // Then, the renderer processes for both frames are crashed. The test verifies
384 // that the TextInputManager stops tracking the RWHVs as well as properly
385 // resets the TextInputState after the second (active) RWHV goes away.
IN_PROC_BROWSER_TEST_F(SitePerProcessTextInputManagerTest,StopTrackingCrashedChildFrame)386 IN_PROC_BROWSER_TEST_F(SitePerProcessTextInputManagerTest,
387 StopTrackingCrashedChildFrame) {
388 CreateIframePage("a(b, c)");
389 std::vector<std::string> values{"node_b", "node_c"};
390 std::vector<content::RenderFrameHost*> frames{GetFrame(IndexVector{0}),
391 GetFrame(IndexVector{1})};
392
393 for (size_t i = 0; i < frames.size(); ++i)
394 AddInputFieldToFrame(frames[i], "text", values[i], true);
395
396 // Tab into both inputs and make sure we correctly receive their
397 // TextInputState. For the second tab two IPCs arrive: one from the first
398 // frame to set the state to none, and another one from the second frame to
399 // set it to TEXT. To avoid the race between them, we shall also observe the
400 // first frame setting its state to NONE after the second tab.
401 ViewTextInputTypeObserver view_type_observer(
402 active_contents(), frames[0]->GetView(), ui::TEXT_INPUT_TYPE_NONE);
403
404 for (size_t i = 0; i < frames.size(); ++i) {
405 content::TextInputManagerValueObserver observer(active_contents(),
406 values[i]);
407 SimulateKeyPress(active_contents(), ui::DomKey::TAB, ui::DomCode::TAB,
408 ui::VKEY_TAB, false, false, false, false);
409 observer.Wait();
410 }
411
412 // Make sure that the first view has set its TextInputState.type to NONE.
413 view_type_observer.Wait();
414
415 // Verify that we are tracking the TextInputState from the first frame.
416 content::RenderWidgetHostView* first_view = frames[0]->GetView();
417 ui::TextInputType first_view_type;
418 EXPECT_TRUE(content::GetTextInputTypeForView(active_contents(), first_view,
419 &first_view_type));
420 EXPECT_EQ(ui::TEXT_INPUT_TYPE_NONE, first_view_type);
421
422 size_t registered_views_count =
423 content::GetRegisteredViewsCountFromTextInputManager(active_contents());
424
425 // We expect at least two views for the two child frames.
426 EXPECT_GT(registered_views_count, 2U);
427
428 // Now that the second frame's <input> is focused, we crash the first frame
429 // and observe that text input state is updated for the view.
430 std::unique_ptr<content::TestRenderWidgetHostViewDestructionObserver>
431 destruction_observer(
432 new content::TestRenderWidgetHostViewDestructionObserver(first_view));
433 {
434 content::ScopedAllowRendererCrashes allow_renderer_crashes(frames[0]);
435 frames[0]->GetProcess()->Shutdown(0);
436 destruction_observer->Wait();
437 }
438
439 // Verify that the TextInputManager is no longer tracking TextInputState for
440 // |first_view|.
441 EXPECT_EQ(
442 registered_views_count - 1U,
443 content::GetRegisteredViewsCountFromTextInputManager(active_contents()));
444
445 // Now crash the second <iframe> which has an active view.
446 destruction_observer.reset(
447 new content::TestRenderWidgetHostViewDestructionObserver(
448 frames[1]->GetView()));
449 {
450 content::ScopedAllowRendererCrashes allow_renderer_crashes(frames[1]);
451 frames[1]->GetProcess()->Shutdown(0);
452 destruction_observer->Wait();
453 }
454
455 EXPECT_EQ(
456 registered_views_count - 2U,
457 content::GetRegisteredViewsCountFromTextInputManager(active_contents()));
458 }
459
460 // The following test loads a page with two child frames: one in process and one
461 // out of process with main frame. The test inserts an <input> inside each frame
462 // and focuses the first frame and observes the TextInputManager setting the
463 // state to ui::TEXT_INPUT_TYPE_TEXT. Then, the frame is detached and the test
464 // observes that the state type is reset to ui::TEXT_INPUT_TYPE_NONE. The same
465 // sequence of actions is then performed on the out of process frame.
IN_PROC_BROWSER_TEST_F(SitePerProcessTextInputManagerTest,ResetStateAfterFrameDetached)466 IN_PROC_BROWSER_TEST_F(SitePerProcessTextInputManagerTest,
467 ResetStateAfterFrameDetached) {
468 CreateIframePage("a(a, b)");
469 std::vector<content::RenderFrameHost*> frames{GetFrame(IndexVector{0}),
470 GetFrame(IndexVector{1})};
471
472 for (size_t i = 0; i < frames.size(); ++i)
473 AddInputFieldToFrame(frames[i], "text", "", true);
474
475 // Press tab key to focus the <input> in the first frame.
476 content::TextInputManagerTypeObserver type_observer_text_a(
477 active_contents(), ui::TEXT_INPUT_TYPE_TEXT);
478 SimulateKeyPress(active_contents(), ui::DomKey::TAB, ui::DomCode::TAB,
479 ui::VKEY_TAB, false, false, false, false);
480 type_observer_text_a.Wait();
481
482 std::string remove_first_iframe_script =
483 "var frame = document.querySelector('iframe');"
484 "frame.parentNode.removeChild(frame);";
485 // Detach first frame and observe |TextInputState.type| resetting to
486 // ui::TEXT_INPUT_TYPE_NONE.
487 content::TextInputManagerTypeObserver type_observer_none_a(
488 active_contents(), ui::TEXT_INPUT_TYPE_NONE);
489 EXPECT_TRUE(ExecuteScript(active_contents(), remove_first_iframe_script));
490 type_observer_none_a.Wait();
491
492 // Press tab to focus the <input> in the second frame.
493 content::TextInputManagerTypeObserver type_observer_text_b(
494 active_contents(), ui::TEXT_INPUT_TYPE_TEXT);
495 SimulateKeyPress(active_contents(), ui::DomKey::TAB, ui::DomCode::TAB,
496 ui::VKEY_TAB, false, false, false, false);
497 type_observer_text_b.Wait();
498
499 // Detach first frame and observe |TextInputState.type| resetting to
500 // ui::TEXT_INPUT_TYPE_NONE.
501 content::TextInputManagerTypeObserver type_observer_none_b(
502 active_contents(), ui::TEXT_INPUT_TYPE_NONE);
503 EXPECT_TRUE(ExecuteScript(active_contents(), remove_first_iframe_script));
504 type_observer_none_b.Wait();
505 }
506
507 // This test creates a page with one OOPIF and adds an <input> to it. Then, the
508 // <input> is focused and the test verfies that the |TextInputState.type| is set
509 // to ui::TEXT_INPUT_TYPE_TEXT. Next, the child frame is navigated away and the
510 // test verifies that |TextInputState.type| resets to ui::TEXT_INPUT_TYPE_NONE.
IN_PROC_BROWSER_TEST_F(SitePerProcessTextInputManagerTest,ResetStateAfterChildNavigation)511 IN_PROC_BROWSER_TEST_F(SitePerProcessTextInputManagerTest,
512 ResetStateAfterChildNavigation) {
513 CreateIframePage("a(b)");
514 content::RenderFrameHost* main_frame = GetFrame(IndexVector{});
515 content::RenderFrameHost* child_frame = GetFrame(IndexVector{0});
516
517 AddInputFieldToFrame(child_frame, "text", "child", false);
518
519 // Focus <input> in child frame and verify the |TextInputState.value|.
520 content::TextInputManagerValueObserver child_set_state_observer(
521 active_contents(), "child");
522 SimulateKeyPress(active_contents(), ui::DomKey::TAB, ui::DomCode::TAB,
523 ui::VKEY_TAB, false, false, false, false);
524 child_set_state_observer.Wait();
525
526 // Navigate the child frame to about:blank and verify that TextInputManager
527 // correctly sets its |TextInputState.type| to ui::TEXT_INPUT_TYPE_NONE.
528 content::TextInputManagerTypeObserver child_reset_state_observer(
529 active_contents(), ui::TEXT_INPUT_TYPE_NONE);
530 EXPECT_TRUE(ExecuteScript(
531 main_frame, "document.querySelector('iframe').src = 'about:blank'"));
532 child_reset_state_observer.Wait();
533 }
534
535 // This test creates a blank page and adds an <input> to it. Then, the <input>
536 // is focused and the test verfies that the |TextInputState.type| is set to
537 // ui::TEXT_INPUT_TYPE_TEXT. Next, the browser is navigated away and the test
538 // verifies that |TextInputState.type| resets to ui::TEXT_INPUT_TYPE_NONE.
IN_PROC_BROWSER_TEST_F(SitePerProcessTextInputManagerTest,ResetStateAfterBrowserNavigation)539 IN_PROC_BROWSER_TEST_F(SitePerProcessTextInputManagerTest,
540 ResetStateAfterBrowserNavigation) {
541 CreateIframePage("a()");
542 content::RenderFrameHost* main_frame = GetFrame(IndexVector{});
543 AddInputFieldToFrame(main_frame, "text", "", false);
544
545 content::TextInputManagerTypeObserver set_state_observer(
546 active_contents(), ui::TEXT_INPUT_TYPE_TEXT);
547 SimulateKeyPress(active_contents(), ui::DomKey::TAB, ui::DomCode::TAB,
548 ui::VKEY_TAB, false, false, false, false);
549 set_state_observer.Wait();
550
551 content::TextInputManagerTypeObserver reset_state_observer(
552 active_contents(), ui::TEXT_INPUT_TYPE_NONE);
553 ui_test_utils::NavigateToURL(browser(), GURL("about:blank"));
554 reset_state_observer.Wait();
555 }
556
557 #if defined(USE_AURA)
558 // This test creates a blank page and adds an <input> to it. Then, the <input>
559 // is focused, UI is focused, then the input is refocused. The test verifies
560 // that selection bounds change with the refocus (see https://crbug.com/864563).
IN_PROC_BROWSER_TEST_F(SitePerProcessTextInputManagerTest,SelectionBoundsChangeAfterRefocusInput)561 IN_PROC_BROWSER_TEST_F(SitePerProcessTextInputManagerTest,
562 SelectionBoundsChangeAfterRefocusInput) {
563 CreateIframePage("a()");
564 content::RenderFrameHost* main_frame = GetFrame(IndexVector{});
565 content::RenderWidgetHostView* view = main_frame->GetView();
566 content::WebContents* web_contents = active_contents();
567 AddInputFieldToFrame(main_frame, "text", "", false);
568
569 auto focus_input_and_wait_for_selection_bounds_change =
570 [&main_frame, &web_contents, &view]() {
571 ViewSelectionBoundsChangedObserver bounds_observer(web_contents, view);
572 // SimulateKeyPress(web_contents, ui::DomKey::TAB, ui::DomCode::TAB,
573 // ui::VKEY_TAB, false, true, false, false);
574 EXPECT_TRUE(ExecuteScript(main_frame,
575 "document.querySelector('input').focus();"));
576 bounds_observer.Wait();
577 };
578
579 focus_input_and_wait_for_selection_bounds_change();
580
581 // Focus location bar.
582 BrowserWindow* window = browser()->window();
583 ASSERT_TRUE(window);
584 LocationBar* location_bar = window->GetLocationBar();
585 ASSERT_TRUE(location_bar);
586 location_bar->FocusLocation(true);
587
588 focus_input_and_wait_for_selection_bounds_change();
589 }
590 #endif
591
592 // This test verifies that if we have a focused <input> in the main frame and
593 // the tab is closed, TextInputManager handles unregistering itself and
594 // notifying the observers properly (see https://crbug.com/669375).
IN_PROC_BROWSER_TEST_F(SitePerProcessTextInputManagerTest,ClosingTabWillNotCrash)595 IN_PROC_BROWSER_TEST_F(SitePerProcessTextInputManagerTest,
596 ClosingTabWillNotCrash) {
597 CreateIframePage("a()");
598 content::RenderFrameHost* main_frame = GetFrame(IndexVector{});
599 AddInputFieldToFrame(main_frame, "text", "", false);
600
601 // Focus the input and wait for state update.
602 content::TextInputManagerTypeObserver observer(active_contents(),
603 ui::TEXT_INPUT_TYPE_TEXT);
604 SimulateKeyPress(active_contents(), ui::DomKey::TAB, ui::DomCode::TAB,
605 ui::VKEY_TAB, false, false, false, false);
606 observer.Wait();
607
608 // Now destroy the tab. We should exit without crashing.
609 browser()->tab_strip_model()->CloseWebContentsAt(
610 0, TabStripModel::CLOSE_USER_GESTURE);
611 }
612
613 // The following test verifies that when the active widget changes value, it is
614 // always from nullptr to non-null or vice versa.
IN_PROC_BROWSER_TEST_F(SitePerProcessTextInputManagerTest,ResetTextInputStateOnActiveWidgetChange)615 IN_PROC_BROWSER_TEST_F(SitePerProcessTextInputManagerTest,
616 ResetTextInputStateOnActiveWidgetChange) {
617 CreateIframePage("a(b,c(a,b),d)");
618 std::vector<content::RenderFrameHost*> frames{
619 GetFrame(IndexVector{}), GetFrame(IndexVector{0}),
620 GetFrame(IndexVector{1}), GetFrame(IndexVector{1, 0}),
621 GetFrame(IndexVector{1, 1}), GetFrame(IndexVector{2})};
622 std::vector<content::RenderWidgetHostView*> views;
623 for (auto* frame : frames)
624 views.push_back(frame->GetView());
625 std::vector<std::string> values{"a", "ab", "ac", "aca", "acb", "acd"};
626 for (size_t i = 0; i < frames.size(); ++i)
627 AddInputFieldToFrame(frames[i], "text", values[i], true);
628
629 content::WebContents* web_contents = active_contents();
630
631 auto send_tab_and_wait_for_value =
632 [&web_contents](const std::string& expected_value) {
633 content::TextInputManagerValueObserver observer(web_contents,
634 expected_value);
635 SimulateKeyPress(web_contents, ui::DomKey::TAB, ui::DomCode::TAB,
636 ui::VKEY_TAB, false, false, false, false);
637 observer.Wait();
638 };
639
640 // Record all active view changes.
641 RecordActiveViewsObserver recorder(web_contents);
642 for (auto value : values)
643 send_tab_and_wait_for_value(value);
644
645 // We have covered a total of 6 views, so there should at least be 11 entries
646 // recorded (at least one null between two views).
647 size_t record_count = recorder.active_views()->size();
648 EXPECT_GT(record_count, 10U);
649
650 // Verify we do not have subsequent nullptr or non-nullptrs.
651 for (size_t i = 0; i < record_count - 1U; ++i) {
652 const content::RenderWidgetHostView* current =
653 recorder.active_views()->at(i);
654 const content::RenderWidgetHostView* next =
655 recorder.active_views()->at(i + 1U);
656 EXPECT_TRUE((current != nullptr && next == nullptr) ||
657 (current == nullptr && next != nullptr));
658 }
659 }
660
661 // This test creates a page with multiple child frames and adds an <input> to
662 // each frame. Then, sequentially, each <input> is focused by sending a tab key.
663 // Then, after |TextInputState.type| for a view is changed to text, the test
664 // sends a set composition IPC to the active widget and waits until the widget
665 // updates its composition range.
IN_PROC_BROWSER_TEST_F(SitePerProcessTextInputManagerTest,TrackCompositionRangeForAllFrames)666 IN_PROC_BROWSER_TEST_F(SitePerProcessTextInputManagerTest,
667 TrackCompositionRangeForAllFrames) {
668 CreateIframePage("a(b,c(a,b),d)");
669 std::vector<content::RenderFrameHost*> frames{
670 GetFrame(IndexVector{}), GetFrame(IndexVector{0}),
671 GetFrame(IndexVector{1}), GetFrame(IndexVector{1, 0}),
672 GetFrame(IndexVector{1, 1}), GetFrame(IndexVector{2})};
673 std::vector<content::RenderWidgetHostView*> views;
674 for (auto* frame : frames)
675 views.push_back(frame->GetView());
676 for (size_t i = 0; i < frames.size(); ++i)
677 AddInputFieldToFrame(frames[i], "text", "text", true);
678
679 content::WebContents* web_contents = active_contents();
680
681 auto send_tab_set_composition_wait_for_bounds_change =
682 [&web_contents](content::RenderWidgetHostView* view) {
683 ViewTextInputTypeObserver type_observer(web_contents, view,
684 ui::TEXT_INPUT_TYPE_TEXT);
685 SimulateKeyPress(web_contents, ui::DomKey::TAB, ui::DomCode::TAB,
686 ui::VKEY_TAB, false, false, false, false);
687 type_observer.Wait();
688
689 ViewCompositionRangeChangedObserver range_observer(web_contents, view);
690 EXPECT_TRUE(
691 content::RequestCompositionInfoFromActiveWidget(web_contents));
692 range_observer.Wait();
693 };
694
695 for (auto* view : views)
696 send_tab_set_composition_wait_for_bounds_change(view);
697 }
698
699 // Failing on Mac - http://crbug.com/852452
700 #if defined(OS_MAC)
701 #define MAYBE_TrackTextSelectionForAllFrames \
702 DISABLED_TrackTextSelectionForAllFrames
703 #else
704 #define MAYBE_TrackTextSelectionForAllFrames TrackTextSelectionForAllFrames
705 #endif
706
707 // This test creates a page with multiple child frames and adds an <input> to
708 // each frame. Then, sequentially, each <input> is focused by sending a tab key.
709 // After focusing each input, a sequence of key presses (character 'E') are sent
710 // to the focused widget. The test then verifies that the selection length
711 // equals the length of the sequence of 'E's.
IN_PROC_BROWSER_TEST_F(SitePerProcessTextInputManagerTest,MAYBE_TrackTextSelectionForAllFrames)712 IN_PROC_BROWSER_TEST_F(SitePerProcessTextInputManagerTest,
713 MAYBE_TrackTextSelectionForAllFrames) {
714 CreateIframePage("a(b,c(a,b),d)");
715 std::vector<content::RenderFrameHost*> frames{
716 GetFrame(IndexVector{}), GetFrame(IndexVector{0}),
717 GetFrame(IndexVector{1}), GetFrame(IndexVector{1, 0}),
718 GetFrame(IndexVector{1, 1}), GetFrame(IndexVector{2})};
719 std::vector<std::string> values{"main", "b", "c", "ca", "cb", "d"};
720 std::vector<content::RenderWidgetHostView*> views;
721 for (auto* frame : frames)
722 views.push_back(frame->GetView());
723 for (size_t i = 0; i < frames.size(); ++i)
724 AddInputFieldToFrame(frames[i], "text", values[i], true);
725
726 content::WebContents* web_contents = active_contents();
727
728 auto send_tab_and_wait_for_value = [&web_contents](const std::string& value) {
729 content::TextInputManagerValueObserver observer(web_contents, value);
730 SimulateKeyPress(web_contents, ui::DomKey::TAB, ui::DomCode::TAB,
731 ui::VKEY_TAB, false, false, false, false);
732 observer.Wait();
733 };
734
735 auto send_keys_select_all_wait_for_selection_change =
736 [&web_contents](content::RenderWidgetHostView* view, size_t count) {
737 ViewTextSelectionObserver observer(web_contents, view, count);
738 for (size_t i = 0; i < count; ++i) {
739 SimulateKeyPress(web_contents, ui::DomKey::FromCharacter('E'),
740 ui::DomCode::US_E, ui::VKEY_E, false, false, false,
741 false);
742 }
743 observer.Wait();
744 };
745
746 size_t count = 2;
747 for (size_t i = 0; i < views.size(); ++i) {
748 // First focus the <input>.
749 send_tab_and_wait_for_value(values[i]);
750
751 // Send a sequence of |count| 'E' keys and wait until the view receives a
752 // selection change update for a text of the corresponding size, |count|.
753 send_keys_select_all_wait_for_selection_change(views[i], count++);
754 }
755 }
756
757 // This test verifies that committing text works as expected for all the frames
758 // on the page. Specifically, the test sends an IPC to the RenderWidget
759 // corresponding to a focused frame with a focused <input> to commit some text.
760 // Then, it verifies that the <input>'s value matches the committed text
761 // (https://crbug.com/688842).
762 // Flaky on Android and Linux http://crbug.com/852274
763 #if defined(OS_MAC)
764 #define MAYBE_ImeCommitTextForAllFrames DISABLED_ImeCommitTextForAllFrames
765 #else
766 #define MAYBE_ImeCommitTextForAllFrames ImeCommitTextForAllFrames
767 #endif
IN_PROC_BROWSER_TEST_F(SitePerProcessTextInputManagerTest,MAYBE_ImeCommitTextForAllFrames)768 IN_PROC_BROWSER_TEST_F(SitePerProcessTextInputManagerTest,
769 MAYBE_ImeCommitTextForAllFrames) {
770 CreateIframePage("a(b,c(a))");
771 std::vector<content::RenderFrameHost*> frames{
772 GetFrame(IndexVector{}), GetFrame(IndexVector{0}),
773 GetFrame(IndexVector{1}), GetFrame(IndexVector{1, 0})};
774 for (size_t i = 0; i < frames.size(); ++i)
775 AddInputFieldToFrame(frames[i], "text", "", true);
776
777 std::vector<std::string> sample_text{"main", "child_b", "child_c", "child_a"};
778 ASSERT_EQ(frames.size(), sample_text.size());
779
780 // An observer of all text selection updates within a WebContents.
781 TextSelectionObserver observer(active_contents());
782 for (size_t index = 0; index < frames.size(); ++index) {
783 // Focus the input and listen to 'input' event inside the frame. When the
784 // event fires, select all the text inside the input. This will trigger a
785 // selection update on the browser side.
786 ASSERT_TRUE(ExecuteScript(frames[index],
787 "window.focus();"
788 "var input = document.querySelector('input');"
789 "input.focus();"
790 "window.addEventListener('input', function(e) {"
791 " input.select();"
792 "});"))
793 << "Could not run script in frame with index:" << index;
794
795 // Commit some text for this frame.
796 content::SendImeCommitTextToWidget(
797 frames[index]->GetView()->GetRenderWidgetHost(),
798 base::UTF8ToUTF16(sample_text[index]), std::vector<ui::ImeTextSpan>(),
799 gfx::Range(), 0);
800
801 // Verify that the text we committed is now selected by listening to a
802 // selection update from a RenderWidgetHostView which has the expected
803 // selected text.
804 observer.WaitForSelectedText(sample_text[index]);
805 }
806 }
807
808 // TODO(ekaramad): Some of the following tests should be active on Android as
809 // well. Enable them when the corresponding feature is implemented for Android
810 // (https://crbug.com/602723).
811 #if !defined(OS_ANDROID)
812 // This test creates a page with multiple child frames and adds an <input> to
813 // each frame. Then, sequentially, each <input> is focused by sending a tab key.
814 // Then, after |TextInputState.type| for a view is changed to text, another key
815 // is pressed (a character) and then the test verifies that TextInputManager
816 // receives the corresponding update on the change in selection bounds on the
817 // browser side.
IN_PROC_BROWSER_TEST_F(SitePerProcessTextInputManagerTest,TrackSelectionBoundsForAllFrames)818 IN_PROC_BROWSER_TEST_F(SitePerProcessTextInputManagerTest,
819 TrackSelectionBoundsForAllFrames) {
820 CreateIframePage("a(b,c(a,b),d)");
821 std::vector<content::RenderFrameHost*> frames{
822 GetFrame(IndexVector{}), GetFrame(IndexVector{0}),
823 GetFrame(IndexVector{1}), GetFrame(IndexVector{1, 0}),
824 GetFrame(IndexVector{1, 1}), GetFrame(IndexVector{2})};
825 std::vector<content::RenderWidgetHostView*> views;
826 for (auto* frame : frames)
827 views.push_back(frame->GetView());
828 for (size_t i = 0; i < frames.size(); ++i)
829 AddInputFieldToFrame(frames[i], "text", "", true);
830
831 content::WebContents* web_contents = active_contents();
832
833 auto send_tab_insert_text_wait_for_bounds_change =
834 [&web_contents](content::RenderWidgetHostView* view) {
835 ViewTextInputTypeObserver type_observer(web_contents, view,
836 ui::TEXT_INPUT_TYPE_TEXT);
837 SimulateKeyPress(web_contents, ui::DomKey::TAB, ui::DomCode::TAB,
838 ui::VKEY_TAB, false, false, false, false);
839 type_observer.Wait();
840 ViewSelectionBoundsChangedObserver bounds_observer(web_contents, view);
841 SimulateKeyPress(web_contents, ui::DomKey::FromCharacter('E'),
842 ui::DomCode::US_E, ui::VKEY_E, false, false, false,
843 false);
844 bounds_observer.Wait();
845 };
846
847 for (auto* view : views)
848 send_tab_insert_text_wait_for_bounds_change(view);
849 }
850
851 // This test makes sure browser correctly tracks focused editable element inside
852 // each RenderFrameHost.
853 // Test is flaky on chromeOS; https://crbug.com/705203.
854 #if defined(OS_CHROMEOS)
855 #define MAYBE_TrackingFocusedElementForAllFrames \
856 DISABLED_TrackingFocusedElementForAllFrames
857 #else
858 #define MAYBE_TrackingFocusedElementForAllFrames \
859 TrackingFocusedElementForAllFrames
860 #endif
IN_PROC_BROWSER_TEST_F(SitePerProcessTextInputManagerTest,MAYBE_TrackingFocusedElementForAllFrames)861 IN_PROC_BROWSER_TEST_F(SitePerProcessTextInputManagerTest,
862 MAYBE_TrackingFocusedElementForAllFrames) {
863 CreateIframePage("a(a, b(a))");
864 std::vector<content::RenderFrameHost*> frames{
865 GetFrame(IndexVector{}), GetFrame(IndexVector{0}),
866 GetFrame(IndexVector{1}), GetFrame(IndexVector{1, 0})};
867 for (size_t i = 0; i < frames.size(); ++i)
868 AddInputFieldToFrame(frames[i], "text", "some text", true);
869
870 // Focus the <input> in |frame| and return if RenderFrameHost thinks there is
871 // a focused editable element in it.
872 auto focus_input_and_return_editable_element_state =
873 [](content::RenderFrameHost* frame) {
874 EXPECT_TRUE(
875 ExecuteScript(frame, "document.querySelector('input').focus();"));
876 return content::DoesFrameHaveFocusedEditableElement(frame);
877 };
878
879 // When focusing an <input> we should receive an update.
880 for (auto* frame : frames)
881 EXPECT_TRUE(focus_input_and_return_editable_element_state(frame));
882
883 // Blur the <input> in |frame| and return if RenderFrameHost thinks there is a
884 // focused editable element in it.
885 auto blur_input_and_return_editable_element_state =
886 [](content::RenderFrameHost* frame) {
887 EXPECT_TRUE(
888 ExecuteScript(frame, "document.querySelector('input').blur();"));
889 return content::DoesFrameHaveFocusedEditableElement(frame);
890 };
891
892 // Similarly, we should receive updates when losing focus.
893 for (auto* frame : frames)
894 EXPECT_FALSE(blur_input_and_return_editable_element_state(frame));
895 }
896
897 // This test tracks page level focused editable element tracking using
898 // WebContents. In a page with multiple frames, a frame is selected and
899 // focused. Then the <input> inside frame is both focused and blurred and and
900 // in both cases the test verifies that WebContents is aware whether or not a
901 // focused editable element exists on the page.
902 // Test is flaky on ChromeOS. crbug.com/705289
903 #if defined(OS_CHROMEOS)
904 #define MAYBE_TrackPageFocusEditableElement \
905 DISABLED_TrackPageFocusEditableElement
906 #else
907 #define MAYBE_TrackPageFocusEditableElement TrackPageFocusEditableElement
908 #endif
IN_PROC_BROWSER_TEST_F(SitePerProcessTextInputManagerTest,MAYBE_TrackPageFocusEditableElement)909 IN_PROC_BROWSER_TEST_F(SitePerProcessTextInputManagerTest,
910 MAYBE_TrackPageFocusEditableElement) {
911 CreateIframePage("a(a, b(a))");
912 std::vector<content::RenderFrameHost*> frames{
913 GetFrame(IndexVector{}), GetFrame(IndexVector{0}),
914 GetFrame(IndexVector{1}), GetFrame(IndexVector{1, 0})};
915 for (size_t i = 0; i < frames.size(); ++i)
916 AddInputFieldToFrame(frames[i], "text", "some text", true);
917
918 auto focus_frame = [](content::RenderFrameHost* frame) {
919 EXPECT_TRUE(ExecuteScript(frame, "window.focus();"));
920 };
921
922 auto set_input_focus = [](content::RenderFrameHost* frame, bool focus) {
923 EXPECT_TRUE(ExecuteScript(
924 frame, base::StringPrintf("document.querySelector('input').%s();",
925 (focus ? "focus" : "blur"))));
926 };
927
928 for (auto* frame : frames) {
929 focus_frame(frame);
930 // Focus the <input>.
931 set_input_focus(frame, true);
932 EXPECT_TRUE(active_contents()->IsFocusedElementEditable());
933 // No blur <input>.
934 set_input_focus(frame, false);
935 EXPECT_FALSE(active_contents()->IsFocusedElementEditable());
936 }
937 }
938
939 // TODO(ekaramad): Could this become a unit test instead?
940 // This test focuses <input> elements on the page and verifies that
941 // WebContents knows about the focused editable element. Then it asks the
942 // WebContents to clear focused element and verifies that there is no longer
943 // a focused editable element on the page.
944 // Test is flaky on ChromeOS; https://crbug.com/705203.
945 #if defined(OS_CHROMEOS)
946 #define MAYBE_ClearFocusedElementOnPage DISABLED_ClearFocusedElementOnPage
947 #else
948 #define MAYBE_ClearFocusedElementOnPage ClearFocusedElementOnPage
949 #endif
IN_PROC_BROWSER_TEST_F(SitePerProcessTextInputManagerTest,MAYBE_ClearFocusedElementOnPage)950 IN_PROC_BROWSER_TEST_F(SitePerProcessTextInputManagerTest,
951 MAYBE_ClearFocusedElementOnPage) {
952 CreateIframePage("a(a, b(a))");
953 std::vector<content::RenderFrameHost*> frames{
954 GetFrame(IndexVector{}), GetFrame(IndexVector{0}),
955 GetFrame(IndexVector{1}), GetFrame(IndexVector{1, 0})};
956 for (size_t i = 0; i < frames.size(); ++i)
957 AddInputFieldToFrame(frames[i], "text", "some text", true);
958
959 auto focus_frame_and_input = [](content::RenderFrameHost* frame) {
960 EXPECT_TRUE(ExecuteScript(frame,
961 "window.focus();"
962 "document.querySelector('input').focus();"));
963 };
964
965 for (auto* frame : frames) {
966 focus_frame_and_input(frame);
967 EXPECT_TRUE(active_contents()->IsFocusedElementEditable());
968 active_contents()->ClearFocusedElement();
969 EXPECT_FALSE(active_contents()->IsFocusedElementEditable());
970 }
971 }
972
973 // TODO(ekaramad): The following tests are specifically written for Aura and are
974 // based on InputMethodObserver. Write similar tests for Mac/Android/Mus
975 // (crbug.com/602723).
976 #if defined(USE_AURA)
977 // -----------------------------------------------------------------------------
978 // Input Method Observer Tests
979 //
980 // The following tests will make use of the InputMethodObserver to verify that
981 // OOPIF pages interact properly with the InputMethod through the tab's view.
982
983 // TODO(ekaramad): We only have coverage for some aura tests as the whole idea
984 // of ui::TextInputClient/ui::InputMethod/ui::InputMethodObserver seems to be
985 // only fit to aura (specifically, OS_CHROMEOS). Can we add more tests here for
986 // aura as well as other platforms (https://crbug.com/602723)?
987
988 // Observes current input method for state changes.
989 class InputMethodObserverBase {
990 public:
InputMethodObserverBase(content::WebContents * web_contents)991 explicit InputMethodObserverBase(content::WebContents* web_contents)
992 : success_(false),
993 test_observer_(content::TestInputMethodObserver::Create(web_contents)) {
994 }
995
Wait()996 void Wait() {
997 if (success_)
998 return;
999 message_loop_runner_ = new content::MessageLoopRunner();
1000 message_loop_runner_->Run();
1001 }
1002
success() const1003 bool success() const { return success_; }
1004
1005 protected:
test_observer()1006 content::TestInputMethodObserver* test_observer() {
1007 return test_observer_.get();
1008 }
1009
success_closure()1010 const base::Closure success_closure() {
1011 return base::Bind(&InputMethodObserverBase::OnSuccess,
1012 base::Unretained(this));
1013 }
1014
1015 private:
OnSuccess()1016 void OnSuccess() {
1017 success_ = true;
1018 if (message_loop_runner_)
1019 message_loop_runner_->Quit();
1020 }
1021
1022 bool success_;
1023 std::unique_ptr<content::TestInputMethodObserver> test_observer_;
1024 scoped_refptr<content::MessageLoopRunner> message_loop_runner_;
1025
1026 DISALLOW_COPY_AND_ASSIGN(InputMethodObserverBase);
1027 };
1028
1029 class InputMethodObserverForShowIme : public InputMethodObserverBase {
1030 public:
InputMethodObserverForShowIme(content::WebContents * web_contents)1031 explicit InputMethodObserverForShowIme(content::WebContents* web_contents)
1032 : InputMethodObserverBase(web_contents) {
1033 test_observer()->SetOnShowVirtualKeyboardIfEnabledCallback(
1034 success_closure());
1035 }
1036
1037 private:
1038 DISALLOW_COPY_AND_ASSIGN(InputMethodObserverForShowIme);
1039 };
1040
1041 // TODO(ekaramad): This test is actually a unit test and should be moved to
1042 // somewhere more relevant (https://crbug.com/602723).
1043 // This test verifies that the IME for Aura is shown if and only if the current
1044 // client's |TextInputState.type| is not ui::TEXT_INPUT_TYPE_NONE and the flag
1045 // |TextInputState.show_ime_if_needed| is true. This should happen even when
1046 // the TextInputState has not changed (according to the platform), e.g., in
1047 // aura when receiving two consecutive updates with same |TextInputState.type|.
1048
1049 // This test is disabled on Windows because we have removed TryShow/TryHide API
1050 // calls and replaced it with TSF input pane policy which is a policy applied by
1051 // text service framework on Windows based on whether TSF edit control has focus
1052 // or not. On Windows we have implemented TSF1 on Chromium that takes care of
1053 // IME compositions, handwriting panels, SIP visibility etc. Please see
1054 // (https://crbug.com/1007958) for more details.
1055 #if !defined(OS_WIN)
IN_PROC_BROWSER_TEST_F(SitePerProcessTextInputManagerTest,CorrectlyShowVirtualKeyboardIfEnabled)1056 IN_PROC_BROWSER_TEST_F(SitePerProcessTextInputManagerTest,
1057 CorrectlyShowVirtualKeyboardIfEnabled) {
1058 // We only need the <iframe> page to create RWHV.
1059 CreateIframePage("a()");
1060 content::RenderFrameHost* main_frame = GetFrame(IndexVector{});
1061 content::RenderWidgetHostView* view = main_frame->GetView();
1062 content::WebContents* web_contents = active_contents();
1063
1064 content::TextInputStateSender sender(view);
1065
1066 auto send_and_check_show_ime = [&sender, &web_contents]() {
1067 InputMethodObserverForShowIme observer(web_contents);
1068 sender.Send();
1069 return observer.success();
1070 };
1071
1072 // Sending an empty state should not trigger ime.
1073 EXPECT_FALSE(send_and_check_show_ime());
1074
1075 // Set |TextInputState.type| to text. Expect no IME.
1076 sender.SetType(ui::TEXT_INPUT_TYPE_TEXT);
1077 EXPECT_FALSE(send_and_check_show_ime());
1078
1079 // Set |TextInputState.show_ime_if_needed| to true. Expect IME.
1080 sender.SetShowVirtualKeyboardIfEnabled(true);
1081 EXPECT_TRUE(send_and_check_show_ime());
1082
1083 // Send the same message. Expect IME (no change).
1084 EXPECT_TRUE(send_and_check_show_ime());
1085
1086 // Reset |TextInputState.show_ime_if_needed|. Expect no IME.
1087 sender.SetShowVirtualKeyboardIfEnabled(false);
1088 EXPECT_FALSE(send_and_check_show_ime());
1089
1090 // Setting an irrelevant field. Expect no IME.
1091 sender.SetMode(ui::TEXT_INPUT_MODE_TEXT);
1092 EXPECT_FALSE(send_and_check_show_ime());
1093
1094 // Set |TextInputState.show_ime_if_needed|. Expect IME.
1095 sender.SetShowVirtualKeyboardIfEnabled(true);
1096 EXPECT_TRUE(send_and_check_show_ime());
1097
1098 // Set |TextInputState.type| to ui::TEXT_INPUT_TYPE_NONE. Expect no IME.
1099 sender.SetType(ui::TEXT_INPUT_TYPE_NONE);
1100 EXPECT_FALSE(send_and_check_show_ime());
1101 }
1102 #endif // OS_WIN
1103
1104 #endif // USE_AURA
1105
1106 // Ensure that a cross-process subframe can utilize keyboard edit commands.
1107 // See https://crbug.com/640706. This test is Linux-specific, as it relies on
1108 // overriding TextEditKeyBindingsDelegateAuraLinux, which only exists on Linux.
1109 #if defined(OS_LINUX) && !defined(OS_CHROMEOS)
IN_PROC_BROWSER_TEST_F(SitePerProcessTextInputManagerTest,SubframeKeyboardEditCommands)1110 IN_PROC_BROWSER_TEST_F(SitePerProcessTextInputManagerTest,
1111 SubframeKeyboardEditCommands) {
1112 GURL main_url(embedded_test_server()->GetURL(
1113 "a.com", "/frame_tree/page_with_one_frame.html"));
1114 ui_test_utils::NavigateToURL(browser(), main_url);
1115 content::WebContents* web_contents = active_contents();
1116
1117 GURL frame_url(
1118 embedded_test_server()->GetURL("b.com", "/page_with_input_field.html"));
1119 EXPECT_TRUE(NavigateIframeToURL(web_contents, "child0", frame_url));
1120
1121 // Focus the subframe and then its input field. The return value
1122 // "input-focus" will be sent once the input field's focus event fires.
1123 content::RenderFrameHost* child =
1124 ChildFrameAt(web_contents->GetMainFrame(), 0);
1125 std::string result;
1126 std::string script =
1127 "function onInput(e) {"
1128 " domAutomationController.send(getInputFieldText());"
1129 "}"
1130 "inputField = document.getElementById('text-field');"
1131 "inputField.addEventListener('input', onInput, false);";
1132 EXPECT_TRUE(ExecuteScript(child, script));
1133 EXPECT_TRUE(ExecuteScriptAndExtractString(
1134 child, "window.focus(); focusInputField();", &result));
1135 EXPECT_EQ("input-focus", result);
1136 EXPECT_EQ(child, web_contents->GetFocusedFrame());
1137
1138 // Generate a couple of keystrokes, which will be routed to the subframe.
1139 content::DOMMessageQueue msg_queue;
1140 std::string reply;
1141 SimulateKeyPress(web_contents, ui::DomKey::FromCharacter('1'),
1142 ui::DomCode::DIGIT1, ui::VKEY_1, false, false, false, false);
1143 EXPECT_TRUE(msg_queue.WaitForMessage(&reply));
1144 SimulateKeyPress(web_contents, ui::DomKey::FromCharacter('2'),
1145 ui::DomCode::DIGIT2, ui::VKEY_2, false, false, false, false);
1146 EXPECT_TRUE(msg_queue.WaitForMessage(&reply));
1147
1148 // Verify that the input field in the subframe received the keystrokes.
1149 EXPECT_TRUE(ExecuteScriptAndExtractString(
1150 child, "window.domAutomationController.send(getInputFieldText());",
1151 &result));
1152 EXPECT_EQ("12", result);
1153
1154 // Define and install a test delegate that translates any keystroke to a
1155 // command to delete all text from current cursor position to the beginning
1156 // of the line.
1157 class TextDeleteDelegate : public ui::TextEditKeyBindingsDelegateAuraLinux {
1158 public:
1159 TextDeleteDelegate() {}
1160 ~TextDeleteDelegate() override {}
1161
1162 bool MatchEvent(
1163 const ui::Event& event,
1164 std::vector<ui::TextEditCommandAuraLinux>* commands) override {
1165 if (commands) {
1166 commands->emplace_back(ui::TextEditCommand::DELETE_TO_BEGINNING_OF_LINE,
1167 "");
1168 }
1169 return true;
1170 }
1171
1172 private:
1173 DISALLOW_COPY_AND_ASSIGN(TextDeleteDelegate);
1174 };
1175
1176 TextDeleteDelegate delegate;
1177 ui::TextEditKeyBindingsDelegateAuraLinux* old_delegate =
1178 ui::GetTextEditKeyBindingsDelegate();
1179 ui::SetTextEditKeyBindingsDelegate(&delegate);
1180
1181 // Press ctrl-alt-shift-D. The test's delegate will pretend that this
1182 // corresponds to the command to delete everyting to the beginning of the
1183 // line. Note the use of SendKeyPressSync instead of SimulateKeyPress, as
1184 // the latter doesn't go through
1185 // RenderWidgetHostViewAura::ForwardKeyboardEvent, which contains the edit
1186 // commands logic that's tested here.
1187 ASSERT_TRUE(ui_test_utils::SendKeyPressSync(browser(), ui::VKEY_D, true, true,
1188 true, false));
1189 ui::SetTextEditKeyBindingsDelegate(old_delegate);
1190
1191 // Verify that the input field in the subframe is erased.
1192 EXPECT_TRUE(ExecuteScriptAndExtractString(
1193 child, "window.domAutomationController.send(getInputFieldText());",
1194 &result));
1195 EXPECT_EQ("", result);
1196 }
1197 #endif
1198
1199 // Ideally, the following code + test should be live in
1200 // 'site_per_process_mac_browsertest.mm'. However, the test
1201 // 'LookUpStringForRangeRoutesToFocusedWidget' relies on an override in
1202 // ContentBrowserClient to register its filters in time. In content shell, we
1203 // cannot have two instances of ShellContentBrowserClient (due to a DCHECK in
1204 // the ctor). Therefore, we put the test here to use ChromeContentBrowserClient
1205 // which does not have the same singleton constraint.
1206 #if defined(OS_MAC)
1207 class ShowDefinitionForWordObserver
1208 : content::RenderWidgetHostViewCocoaObserver {
1209 public:
ShowDefinitionForWordObserver(content::WebContents * web_contents)1210 explicit ShowDefinitionForWordObserver(content::WebContents* web_contents)
1211 : content::RenderWidgetHostViewCocoaObserver(web_contents) {}
1212
~ShowDefinitionForWordObserver()1213 ~ShowDefinitionForWordObserver() override {}
1214
WaitForWordLookUp()1215 const std::string& WaitForWordLookUp() {
1216 if (did_receive_string_)
1217 return word_;
1218
1219 run_loop_.reset(new base::RunLoop());
1220 run_loop_->Run();
1221 return word_;
1222 }
1223
1224 private:
OnShowDefinitionForAttributedString(const std::string & for_word)1225 void OnShowDefinitionForAttributedString(
1226 const std::string& for_word) override {
1227 did_receive_string_ = true;
1228 word_ = for_word;
1229 if (run_loop_)
1230 run_loop_->Quit();
1231 }
1232
1233 bool did_receive_string_ = false;
1234 std::string word_;
1235 std::unique_ptr<base::RunLoop> run_loop_;
1236
1237 DISALLOW_COPY_AND_ASSIGN(ShowDefinitionForWordObserver);
1238 };
1239
1240 // Flakey (https:://crbug.com/874417).
1241 // This test verifies that requests for dictionary lookup based on selection
1242 // range are routed to the focused RenderWidgetHost.
IN_PROC_BROWSER_TEST_F(SitePerProcessTextInputManagerTest,DISABLED_LookUpStringForRangeRoutesToFocusedWidget)1243 IN_PROC_BROWSER_TEST_F(SitePerProcessTextInputManagerTest,
1244 DISABLED_LookUpStringForRangeRoutesToFocusedWidget) {
1245 CreateIframePage("a(b)");
1246 std::vector<content::RenderFrameHost*> frames{GetFrame(IndexVector{}),
1247 GetFrame(IndexVector{0})};
1248 std::string expected_words[] = {"word1", "word2"};
1249
1250 // For each frame, add <input>, set its value to expected word, select it, ask
1251 // for dictionary and verify the word returned from renderer matches.
1252 for (size_t i = 0; i < frames.size(); ++i) {
1253 AddInputFieldToFrame(frames[i], "text", expected_words[i].c_str(), true);
1254 // Focusing the <input> automatically selects the text.
1255 ASSERT_TRUE(
1256 ExecuteScript(frames[i], "document.querySelector('input').focus();"));
1257 ShowDefinitionForWordObserver word_lookup_observer(active_contents());
1258 // Request for the dictionary lookup and intercept the word on its way back.
1259 // The request is always on the tab's view which is a
1260 // RenderWidgetHostViewMac.
1261 content::AskForLookUpDictionaryForRange(
1262 active_contents()->GetRenderWidgetHostView(),
1263 gfx::Range(0, expected_words[i].size()));
1264 EXPECT_EQ(expected_words[i], word_lookup_observer.WaitForWordLookUp());
1265 }
1266 }
1267
1268 // This test verifies that when a word lookup result comes from the renderer
1269 // after the target RenderWidgetHost has been deleted, the browser will not
1270 // crash. This test covers the case where the target RenderWidgetHost is that of
1271 // an OOPIF.
IN_PROC_BROWSER_TEST_F(SitePerProcessTextInputManagerTest,DoNotCrashBrowserInWordLookUpForDestroyedWidget_ChildFrame)1272 IN_PROC_BROWSER_TEST_F(
1273 SitePerProcessTextInputManagerTest,
1274 DoNotCrashBrowserInWordLookUpForDestroyedWidget_ChildFrame) {
1275 std::unique_ptr<content::WebContents> new_contents =
1276 content::WebContents::Create(content::WebContents::CreateParams(
1277 active_contents()->GetBrowserContext(), nullptr));
1278 content::WebContents* raw_new_contents = new_contents.get();
1279 browser()->tab_strip_model()->InsertWebContentsAt(1, std::move(new_contents),
1280 TabStripModel::ADD_ACTIVE);
1281 EXPECT_EQ(active_contents(), raw_new_contents);
1282
1283 // Simple page with 1 cross origin (out-of-process) <iframe>.
1284 CreateIframePage("a(b)");
1285
1286 content::RenderFrameHost* child_frame = GetFrame(IndexVector{0});
1287 // Now add an <input> field and select its text.
1288 AddInputFieldToFrame(child_frame, "text", "four", true);
1289 EXPECT_TRUE(ExecuteScript(child_frame,
1290 "window.focus();"
1291 "document.querySelector('input').focus();"
1292 "document.querySelector('input').select();"));
1293
1294 content::TextInputTestLocalFrame text_input_local_frame;
1295 text_input_local_frame.SetUp(child_frame);
1296
1297 // We need to wait for test scenario to complete before leaving this block.
1298 base::RunLoop test_complete_waiter;
1299
1300 // Destroy the RenderWidgetHost from the browser side right after the
1301 // dictionary message is received. The destruction is post tasked to UI
1302 // thread.
1303 int32_t child_process_id = child_frame->GetProcess()->GetID();
1304 int32_t child_frame_routing_id = child_frame->GetRoutingID();
1305
1306 text_input_local_frame.SetStringForRangeCallback(base::Bind(
1307 [](int32_t process_id, int32_t routing_id,
1308 const base::Closure& callback_on_io) {
1309 // This runs before TextInputClientMac gets to handle the mojo message.
1310 // Then, by the time TextInputClientMac calls back into UI to show the
1311 // dictionary, the target RWH is already destroyed which will be a
1312 // close enough repro for the crash in https://crbug.com/737032.
1313 ASSERT_TRUE(content::DestroyRenderWidgetHost(process_id, routing_id));
1314
1315 // Quit the run loop on IO to make sure the message handler of
1316 // TextInputClientMac has successfully run on UI thread.
1317 content::GetIOThreadTaskRunner({})->PostTask(FROM_HERE, callback_on_io);
1318 },
1319 child_process_id, child_frame_routing_id,
1320 test_complete_waiter.QuitClosure()));
1321
1322 content::RenderWidgetHostView* page_rwhv =
1323 content::WebContents::FromRenderFrameHost(child_frame)
1324 ->GetRenderWidgetHostView();
1325
1326 // The dictionary request to be made will be routed to the focused frame.
1327 ASSERT_EQ(child_frame, raw_new_contents->GetFocusedFrame());
1328
1329 // Request for the dictionary lookup and intercept the word on its way back.
1330 // The request is always on the tab's view which is a RenderWidgetHostViewMac.
1331 content::AskForLookUpDictionaryForRange(page_rwhv, gfx::Range(0, 4));
1332
1333 test_complete_waiter.Run();
1334 }
1335
1336 // This test verifies that when a word lookup result comes from the renderer
1337 // after the target RenderWidgetHost has been deleted, the browser will not
1338 // crash. This test covers the case where the target RenderWidgetHost is that of
1339 // the main frame (no OOPIFs on page).
IN_PROC_BROWSER_TEST_F(SitePerProcessTextInputManagerTest,DoNotCrashBrowserInWordLookUpForDestroyedWidget_MainFrame)1340 IN_PROC_BROWSER_TEST_F(
1341 SitePerProcessTextInputManagerTest,
1342 DoNotCrashBrowserInWordLookUpForDestroyedWidget_MainFrame) {
1343 std::unique_ptr<content::WebContents> new_contents =
1344 content::WebContents::Create(content::WebContents::CreateParams(
1345 active_contents()->GetBrowserContext(), nullptr));
1346 content::WebContents* raw_new_contents = new_contents.get();
1347 browser()->tab_strip_model()->InsertWebContentsAt(1, std::move(new_contents),
1348 TabStripModel::ADD_ACTIVE);
1349 EXPECT_EQ(active_contents(), raw_new_contents);
1350
1351 // Simple page with no <iframe>s.
1352 CreateIframePage("a()");
1353
1354 content::RenderFrameHost* main_frame = GetFrame(IndexVector{});
1355 // Now add an <input> field and select its text.
1356 AddInputFieldToFrame(main_frame, "text", "four", true);
1357 EXPECT_TRUE(ExecuteScript(main_frame,
1358 "document.querySelector('input').focus();"
1359 "document.querySelector('input').select();"));
1360
1361 content::TextInputTestLocalFrame text_input_local_frame;
1362 text_input_local_frame.SetUp(main_frame);
1363
1364 content::RenderWidgetHostView* page_rwhv = main_frame->GetView();
1365
1366 // We need to wait for test scenario to complete before leaving this block.
1367 base::RunLoop test_complete_waiter;
1368
1369 // Destroy the RenderWidgetHost from the browser side right after the
1370 // dictionary message is received. The destruction is post tasked to UI
1371 // thread.
1372 int32_t main_frame_process_id = main_frame->GetProcess()->GetID();
1373 int32_t main_frame_routing_id = main_frame->GetRoutingID();
1374 text_input_local_frame.SetStringForRangeCallback(base::Bind(
1375 [](int32_t process_id, int32_t routing_id,
1376 const base::Closure& callback_on_io) {
1377 // This runs before TextInputClientMac gets to handle the mojo message.
1378 // Then, by the time TextInputClientMac calls back into UI to show the
1379 // dictionary, the target RWH is already destroyed which will be a
1380 // close enough repro for the crash in https://crbug.com/737032.
1381 ASSERT_TRUE(content::DestroyRenderWidgetHost(process_id, routing_id));
1382
1383 // Quit the run loop on IO to make sure the message handler of
1384 // TextInputClientMac has successfully run on UI thread.
1385 content::GetIOThreadTaskRunner({})->PostTask(FROM_HERE, callback_on_io);
1386 },
1387 main_frame_process_id, main_frame_routing_id,
1388 test_complete_waiter.QuitClosure()));
1389
1390 // Request for the dictionary lookup and intercept the word on its way back.
1391 // The request is always on the tab's view which is a RenderWidgetHostViewMac.
1392 content::AskForLookUpDictionaryForRange(page_rwhv, gfx::Range(0, 4));
1393
1394 test_complete_waiter.Run();
1395 }
1396 #endif // defined(MAC_OSX)
1397
1398 #endif // !defined(OS_ANDROID)
1399