| Index: chrome/browser/site_per_process_interactive_browsertest.cc
|
| diff --git a/chrome/browser/site_per_process_interactive_browsertest.cc b/chrome/browser/site_per_process_interactive_browsertest.cc
|
| index 17fdbe539cda066e20a09ac4add980c8cb00a292..d25357a247771cd8d87fa00fb1a5f520e238c858 100644
|
| --- a/chrome/browser/site_per_process_interactive_browsertest.cc
|
| +++ b/chrome/browser/site_per_process_interactive_browsertest.cc
|
| @@ -2,18 +2,26 @@
|
| // Use of this source code is governed by a BSD-style license that can be
|
| // found in the LICENSE file.
|
|
|
| +#include <vector>
|
| +
|
| #include "base/command_line.h"
|
| #include "chrome/browser/ui/browser.h"
|
| #include "chrome/browser/ui/tabs/tab_strip_model.h"
|
| #include "chrome/test/base/in_process_browser_test.h"
|
| #include "chrome/test/base/ui_test_utils.h"
|
| #include "content/public/browser/render_frame_host.h"
|
| +#include "content/public/browser/render_process_host.h"
|
| #include "content/public/browser/web_contents.h"
|
| #include "content/public/test/browser_test_utils.h"
|
| #include "content/public/test/content_browser_test_utils.h"
|
| #include "content/public/test/test_utils.h"
|
| +#include "content/public/test/text_input_test_utils.h"
|
| #include "net/dns/mock_host_resolver.h"
|
| #include "net/test/embedded_test_server/embedded_test_server.h"
|
| +#include "ui/base/ime/text_input_client.h"
|
| +#include "ui/base/ime/text_input_mode.h"
|
| +#include "ui/base/ime/text_input_type.h"
|
| +
|
| #include "url/gurl.h"
|
|
|
| class SitePerProcessInteractiveBrowserTest : public InProcessBrowserTest {
|
| @@ -232,3 +240,460 @@ IN_PROC_BROWSER_TEST_F(SitePerProcessInteractiveBrowserTest,
|
| EXPECT_EQ(main_frame, web_contents->GetFocusedFrame());
|
| }
|
|
|
| +// TODO(ekaramad): After fixing crbug.com/578168 for all platforms, the
|
| +// following tests should be activated for other platforms, e.g., Mac and
|
| +// Android (crbug.com/602723).
|
| +#ifdef USE_AURA
|
| +///////////////////////////////////////////////////////////////////////////////
|
| +// TextInputManager and IME Tests
|
| +//
|
| +// The following tests verify the correctness of TextInputState tracking on the
|
| +// browser side. They also make sure the IME logic works correctly. The baseline
|
| +// for comparison is the default functionality in the non-OOPIF case (i.e., the
|
| +// legacy implementation in RWHV's other than RWHVCF.).
|
| +
|
| +// TextInputManager Observers
|
| +// Observing the |TextInputState.value|.
|
| +class TextInputManagerObserverBase {
|
| + public:
|
| + explicit TextInputManagerObserverBase(content::WebContents* web_contents)
|
| + : success_(false) {
|
| + test_observer_ =
|
| + content::TestTextInputManagerObserver::Create(web_contents);
|
| + }
|
| +
|
| + virtual ~TextInputManagerObserverBase() {}
|
| +
|
| + void Wait() {
|
| + if (success_)
|
| + return;
|
| + message_loop_runner_ = new content::MessageLoopRunner();
|
| + message_loop_runner_->Run();
|
| + }
|
| +
|
| + bool success() const { return success_; }
|
| +
|
| + protected:
|
| + base::Closure on_success() {
|
| + return base::Bind(&TextInputManagerObserverBase::OnSuccess,
|
| + base::Unretained(this));
|
| + }
|
| +
|
| + content::TestTextInputManagerObserver* observer() {
|
| + return test_observer_.get();
|
| + }
|
| +
|
| + private:
|
| + void OnSuccess() {
|
| + success_ = true;
|
| + if (message_loop_runner_ && message_loop_runner_->loop_running())
|
| + message_loop_runner_->Quit();
|
| + }
|
| +
|
| + std::unique_ptr<content::TestTextInputManagerObserver> test_observer_;
|
| + bool success_;
|
| + scoped_refptr<content::MessageLoopRunner> message_loop_runner_;
|
| +
|
| + DISALLOW_COPY_AND_ASSIGN(TextInputManagerObserverBase);
|
| +};
|
| +
|
| +// This class observers the TextInputManager for updated |TextInputState.value|.
|
| +class TextInputManagerValueObserver : public TextInputManagerObserverBase {
|
| + public:
|
| + explicit TextInputManagerValueObserver(content::WebContents* web_contents,
|
| + const std::string& expected_value)
|
| + : TextInputManagerObserverBase(web_contents),
|
| + expected_value_(expected_value) {
|
| + observer()->SetUpdateCallback(base::Bind(
|
| + &TextInputManagerValueObserver::VerifyValue, base::Unretained(this)));
|
| + }
|
| +
|
| + private:
|
| + void VerifyValue(content::TestTextInputManagerObserver* observer) {
|
| + std::string value;
|
| + if (observer->GetTextInputValue(value) && expected_value_ == value)
|
| + on_success().Run();
|
| + }
|
| +
|
| + std::string expected_value_;
|
| +};
|
| +
|
| +// This class observers the TextInputManager for updated |TextInputState.type|.
|
| +class TextInputManagerTypeObserver : public TextInputManagerObserverBase {
|
| + public:
|
| + explicit TextInputManagerTypeObserver(content::WebContents* web_contents,
|
| + ui::TextInputType expected_type)
|
| + : TextInputManagerObserverBase(web_contents),
|
| + expected_type_(expected_type) {
|
| + observer()->SetUpdateCallback(base::Bind(
|
| + &TextInputManagerTypeObserver::VerifyType, base::Unretained(this)));
|
| + }
|
| +
|
| + private:
|
| + void VerifyType(content::TestTextInputManagerObserver* observer) {
|
| + if (expected_type_ == observer->GetTextInputType())
|
| + on_success().Run();
|
| + }
|
| +
|
| + ui::TextInputType expected_type_;
|
| +};
|
| +
|
| +// An observer class which observes the TextInputManager until the first time
|
| +// the TextInputManager detects a change in TextInputState.
|
| +class TextInputManagerChangeObserver : public TextInputManagerObserverBase {
|
| + public:
|
| + explicit TextInputManagerChangeObserver(content::WebContents* web_contents)
|
| + : TextInputManagerObserverBase(web_contents) {
|
| + observer()->SetUpdateCallback(base::Bind(
|
| + &TextInputManagerChangeObserver::VerifyChange, base::Unretained(this)));
|
| + }
|
| +
|
| + private:
|
| + void VerifyChange(content::TestTextInputManagerObserver* observer) {
|
| + if (observer->IsTextInputStateChanged())
|
| + on_success().Run();
|
| + }
|
| +};
|
| +
|
| +// Main class for all TextInputState and IME related tests.
|
| +class TextInputInteractiveBrowserTest
|
| + : public SitePerProcessInteractiveBrowserTest {
|
| + protected:
|
| + content::WebContents* active_contents() {
|
| + return browser()->tab_strip_model()->GetActiveWebContents();
|
| + }
|
| +
|
| + // static
|
| + // Adds an <input> field to a given frame by executing javascript code.
|
| + // The input can be added as the first element or the last element of
|
| + // |document.body|.
|
| + static void AddInputFieldToFrame(content::RenderFrameHost* rfh,
|
| + const std::string& type,
|
| + const std::string& value,
|
| + bool append_as_first_child) {
|
| + std::string script = base::StringPrintf(
|
| + "var input = document.createElement('input');"
|
| + "input.setAttribute('type', '%s');"
|
| + "input.setAttribute('value', '%s');"
|
| + "if (%s && !!document.body.firstChild) {"
|
| + " document.body.insertBefore(input, document.body.firstChild);"
|
| + "} else {"
|
| + " document.body.appendChild(input);"
|
| + "}",
|
| + type.c_str(), value.c_str(), append_as_first_child ? "true" : "false");
|
| + EXPECT_TRUE(ExecuteScript(rfh, script));
|
| + }
|
| +
|
| + // Uses 'cross_site_iframe_factory.html'. The main frame's domain is
|
| + // 'a.com'.
|
| + void CreateIframePage(const std::string& structure) {
|
| + std::string path = base::StringPrintf("/cross_site_iframe_factory.html?%s",
|
| + structure.c_str());
|
| + GURL main_url(embedded_test_server()->GetURL("a.com", path));
|
| + ui_test_utils::NavigateToURL(browser(), main_url);
|
| + }
|
| +
|
| + // Recrusively uses ChildFrameAt(frame, i) to get the i-th child frame
|
| + // inside
|
| + // frame.
|
| + // For example, for 'a(b(c, d(e)))', [0] returns b, and [0, 1, 0] returns e;
|
| + content::RenderFrameHost* GetFrame(const std::vector<size_t>& indices) {
|
| + content::RenderFrameHost* current = active_contents()->GetMainFrame();
|
| + for (size_t index : indices)
|
| + current = ChildFrameAt(current, index);
|
| + return current;
|
| + }
|
| +};
|
| +
|
| +// The following test loads a page with multiple nested <iframe> elements which
|
| +// are in or out of process with the main frame. Then an <input> field is added
|
| +// to every single frame on the frame tree where the input's value is distinctly
|
| +// selected for each frame. The test then creates a sequence of tab presses and
|
| +// verifies that after eahc key press, the right TextInputState is observed.
|
| +IN_PROC_BROWSER_TEST_F(TextInputInteractiveBrowserTest,
|
| + TrackStateWhenSwitchingFocusedFrames) {
|
| + CreateIframePage("a(a,b,c(a,b,d(e, f)),g)");
|
| + std::vector<std::string> values{
|
| + "main", "node_a", "node_b", "node_c", "node_c_a",
|
| + "node_c_b", "node_c_d", "node_c_d_e", "node_c_d_f", "node_g"};
|
| +
|
| + // TODO(ekaramad): This should not be needed and uniform initialization should
|
| + // work. However, in some bots, this is failing.
|
| + using vector = std::vector<size_t>;
|
| + std::vector<content::RenderFrameHost*> frames{
|
| + GetFrame(vector{}), GetFrame(vector{0}),
|
| + GetFrame(vector{1}), GetFrame(vector{2}),
|
| + GetFrame(vector{2, 0}), GetFrame(vector{2, 1}),
|
| + GetFrame(vector{2, 2}), GetFrame(vector{2, 2, 0}),
|
| + GetFrame(vector{2, 2, 1}), GetFrame(vector{3})};
|
| +
|
| + for (size_t i = 0; i < frames.size(); ++i)
|
| + AddInputFieldToFrame(frames[i], "text", values[i], true);
|
| +
|
| + for (size_t i = 0; i < frames.size(); ++i) {
|
| + TextInputManagerValueObserver observer(active_contents(), values[i]);
|
| + SimulateKeyPress(active_contents(), ui::VKEY_TAB, false, false, false,
|
| + false);
|
| + observer.Wait();
|
| + }
|
| +}
|
| +
|
| +// The following test loads a page with two OOPIFs. An <input> is added to
|
| +// both frames and tab key is faked until the one in the second OOPIF is
|
| +// focused. Then, the renderer process for both frames are crashed. The test
|
| +// verifies that the TextInputManager stop tracking the RWHVs as well as
|
| +// properly resetting the TextInputState after the second (active) RWHV goes
|
| +// away.
|
| +IN_PROC_BROWSER_TEST_F(TextInputInteractiveBrowserTest,
|
| + StopTrackingCrashedChildFrame) {
|
| + CreateIframePage("a(b, c)");
|
| + std::vector<std::string> values{"node_b", "node_c"};
|
| + using vector = std::vector<size_t>;
|
| + std::vector<content::RenderFrameHost*> frames{GetFrame(vector{0}),
|
| + GetFrame(vector{1})};
|
| +
|
| + for (size_t i = 0; i < frames.size(); ++i)
|
| + AddInputFieldToFrame(frames[i], "text", values[i], true);
|
| +
|
| + for (size_t i = 0; i < frames.size(); ++i) {
|
| + TextInputManagerValueObserver observer(active_contents(), values[i]);
|
| + SimulateKeyPress(active_contents(), ui::VKEY_TAB, false, false, false,
|
| + false);
|
| + observer.Wait();
|
| + }
|
| +
|
| + // Verify that we are tracking the TextInputState from the first frame.
|
| + content::RenderWidgetHostView* first_view = frames[0]->GetView();
|
| + std::unordered_map<const content::RenderWidgetHostView*, ui::TextInputType>
|
| + type_map = content::TestTextInputManagerObserver::GetTextInputTypeMap(
|
| + active_contents());
|
| + EXPECT_EQ(1UL, type_map.count(first_view));
|
| + EXPECT_EQ(ui::TEXT_INPUT_TYPE_NONE, type_map[first_view]);
|
| +
|
| + // Now that the second frame's <input> is focused, we crash the first frame
|
| + // and observe that text input state is updated for the view.
|
| + std::unique_ptr<content::RenderWidgetHostViewDestructionObserver>
|
| + destruction_observer =
|
| + content::RenderWidgetHostViewDestructionObserver::Create(first_view);
|
| + frames[0]->GetProcess()->Shutdown(0, false);
|
| + destruction_observer->Wait();
|
| +
|
| + // Verifying that the TextInputManager has smaller cleaned the memory
|
| + // allocated to the view.
|
| + type_map = content::TestTextInputManagerObserver::GetTextInputTypeMap(
|
| + active_contents());
|
| + EXPECT_EQ(0UL, type_map.count(first_view));
|
| +
|
| + // Now crash the second <iframe> which has an active view.
|
| + content::RenderWidgetHostView* second_view = frames[1]->GetView();
|
| + TextInputManagerChangeObserver change_observer(active_contents());
|
| + frames[1]->GetProcess()->Shutdown(0, false);
|
| + change_observer.Wait();
|
| + EXPECT_EQ(ui::TEXT_INPUT_TYPE_NONE,
|
| + content::GetTextInputTypeFromWebContents(active_contents()));
|
| + EXPECT_FALSE(!!content::GetActiveViewFromWebContents(active_contents()));
|
| + type_map = content::TestTextInputManagerObserver::GetTextInputTypeMap(
|
| + active_contents());
|
| + EXPECT_EQ(0UL, type_map.count(second_view));
|
| +}
|
| +
|
| +// The following test loads a page with two child frames; one in process and one
|
| +// out of process with main frame. The test inserts an <input> inside each frame
|
| +// and focuses the first frame and observes the TextInputManager setting the
|
| +// state to ui::TEXT_INPUT_TYPE_TEXT. Then, the frame is detached and the test
|
| +// observes that the state type is reset to ui::TEXT_INPUT_TYPE_NONE. The same
|
| +// sequence of actions is then performed on the out of process frame.
|
| +IN_PROC_BROWSER_TEST_F(TextInputInteractiveBrowserTest,
|
| + ResetStateAfterFrameDetached) {
|
| + CreateIframePage("a(a, b)");
|
| + using vector = std::vector<size_t>;
|
| + std::vector<content::RenderFrameHost*> frames{GetFrame(vector{0}),
|
| + GetFrame(vector{1})};
|
| +
|
| + for (size_t i = 0; i < frames.size(); ++i)
|
| + AddInputFieldToFrame(frames[i], "text", "", true);
|
| +
|
| + // Press tab key to focus the <input> in the first frame.
|
| + TextInputManagerTypeObserver type_observer_text_a(active_contents(),
|
| + ui::TEXT_INPUT_TYPE_TEXT);
|
| + SimulateKeyPress(active_contents(), ui::VKEY_TAB, false, false, false, false);
|
| + type_observer_text_a.Wait();
|
| +
|
| + // Detach first frame and observe |TextInputState.type| resetting to
|
| + // ui::TEXT_INPUT_TYPE_NONE.
|
| + TextInputManagerTypeObserver type_observer_none_a(active_contents(),
|
| + ui::TEXT_INPUT_TYPE_NONE);
|
| + EXPECT_TRUE(ExecuteScript(
|
| + frames[0], "document.body.removeChild(document.body.firstChild);"));
|
| + type_observer_none_a.Wait();
|
| +
|
| + // Press tab to focus the <input> in the second frame.
|
| + TextInputManagerTypeObserver type_observer_text_b(active_contents(),
|
| + ui::TEXT_INPUT_TYPE_TEXT);
|
| + SimulateKeyPress(active_contents(), ui::VKEY_TAB, false, false, false, false);
|
| + type_observer_text_b.Wait();
|
| +
|
| + // Detach first frame and observe |TextInputState.type| resetting to
|
| + // ui::TEXT_INPUT_TYPE_NONE.
|
| + TextInputManagerTypeObserver type_observer_none_b(active_contents(),
|
| + ui::TEXT_INPUT_TYPE_NONE);
|
| + EXPECT_TRUE(ExecuteScript(
|
| + frames[1], "document.body.removeChild(document.body.firstChild);"));
|
| + type_observer_none_b.Wait();
|
| +}
|
| +
|
| +// This test creates a page with one OOPIF and adds an <input> to it. Then, the
|
| +// <input> is focused and the test verfies that the |TextInputState.type| is set
|
| +// to ui::TEXT_INPUT_TYPE_TEXT. Next, the child frame is navigated away and the
|
| +// test verifies that |TextInputState.type| resets to ui::TEXT_INPUT_TYPE_NONE.
|
| +IN_PROC_BROWSER_TEST_F(TextInputInteractiveBrowserTest,
|
| + ResetStateAfterChildNavigation) {
|
| + CreateIframePage("a(b)");
|
| + using vector = std::vector<size_t>;
|
| + content::RenderFrameHost* main_frame = GetFrame(vector{});
|
| + content::RenderFrameHost* child_frame = GetFrame(vector{0});
|
| +
|
| + AddInputFieldToFrame(child_frame, "text", "child", false);
|
| +
|
| + // Focus <input> in child frame and verify the |TextInputState.value|.
|
| + TextInputManagerValueObserver child_set_state_observer(active_contents(),
|
| + "child");
|
| + SimulateKeyPress(active_contents(), ui::VKEY_TAB, false, false, false, false);
|
| + child_set_state_observer.Wait();
|
| +
|
| + // Navigate the child frame to about:blank and verify that TextInputManager
|
| + // correctly sets its |TextInputState.type| to ui::TEXT_INPUT_TYPE_NONE.
|
| + TextInputManagerTypeObserver child_reset_state_observer(
|
| + active_contents(), ui::TEXT_INPUT_TYPE_NONE);
|
| + EXPECT_TRUE(ExecuteScript(
|
| + main_frame, "document.querySelector('iframe').src = 'about:blank'"));
|
| + child_reset_state_observer.Wait();
|
| +}
|
| +
|
| +// This test creates a blank page and adds an <input> to it. Then, the <input>
|
| +// is focused and the test verfies that the |TextInputState.type| is set to
|
| +// ui::TEXT_INPUT_TYPE_TEXT. Next, the browser is navigated away and the test
|
| +// verifies that |TextInputState.type| resets to ui::TEXT_INPUT_TYPE_NONE.
|
| +IN_PROC_BROWSER_TEST_F(TextInputInteractiveBrowserTest,
|
| + ResetStateAfterBrowserNavigation) {
|
| + CreateIframePage("a()");
|
| + content::RenderFrameHost* main_frame = GetFrame(std::vector<size_t>{});
|
| + AddInputFieldToFrame(main_frame, "text", "", false);
|
| +
|
| + TextInputManagerTypeObserver set_state_observer(active_contents(),
|
| + ui::TEXT_INPUT_TYPE_TEXT);
|
| + SimulateKeyPress(active_contents(), ui::VKEY_TAB, false, false, false, false);
|
| + set_state_observer.Wait();
|
| +
|
| + TextInputManagerTypeObserver reset_state_observer(active_contents(),
|
| + ui::TEXT_INPUT_TYPE_NONE);
|
| + ui_test_utils::NavigateToURL(browser(), GURL("about:blank"));
|
| + reset_state_observer.Wait();
|
| +}
|
| +#endif // USE_AURA
|
| +
|
| +// TODO(ekaramad): The following tests are specifically written for Aura and are
|
| +// based on InputMethodObserver. Write similar tests for Mac/Android/Mus
|
| +// (crbug.com/602723).
|
| +#ifdef USE_AURA
|
| +// Observes current input method for state changes.
|
| +class InputMethodObserverBase {
|
| + public:
|
| + explicit InputMethodObserverBase(content::WebContents* web_contents)
|
| + : success_(false),
|
| + test_observer_(content::TestInputMethodObserver::Create(web_contents)) {
|
| + }
|
| +
|
| + void Wait() {
|
| + if (success_)
|
| + return;
|
| + message_loop_runner_ = new content::MessageLoopRunner();
|
| + message_loop_runner_->Run();
|
| + }
|
| +
|
| + bool success() const { return success_; }
|
| +
|
| + protected:
|
| + content::TestInputMethodObserver* test_observer() {
|
| + return test_observer_.get();
|
| + }
|
| +
|
| + const base::Closure success_closure() {
|
| + return base::Bind(&InputMethodObserverBase::OnSuccess,
|
| + base::Unretained(this));
|
| + }
|
| +
|
| + private:
|
| + void OnSuccess() {
|
| + success_ = true;
|
| + if (message_loop_runner_ && message_loop_runner_->loop_running())
|
| + message_loop_runner_->Quit();
|
| + }
|
| +
|
| + bool success_;
|
| + std::unique_ptr<content::TestInputMethodObserver> test_observer_;
|
| + scoped_refptr<content::MessageLoopRunner> message_loop_runner_;
|
| +
|
| + DISALLOW_COPY_AND_ASSIGN(InputMethodObserverBase);
|
| +};
|
| +
|
| +class InputMethodObserverForShowIme : public InputMethodObserverBase {
|
| + public:
|
| + explicit InputMethodObserverForShowIme(content::WebContents* web_contents)
|
| + : InputMethodObserverBase(web_contents) {
|
| + test_observer()->SetOnShowImeIfNeededCallback(success_closure());
|
| + }
|
| +};
|
| +
|
| +// This test verifies that the IME for Aura is shown if and only if the current
|
| +// client's |TextInputState.type| is not ui::TEXT_INPUT_TYPE_NONE and the flag
|
| +// |TextInputState.show_ime_if_needed| is true. This should happen even.
|
| +// TODO(ekaramad): This test is actually a unit test and should be moved to some
|
| +// place more appropriate.
|
| +IN_PROC_BROWSER_TEST_F(TextInputInteractiveBrowserTest,
|
| + CorrectlyShowImeIfNeeded) {
|
| + // We only need the <iframe> page to create RWHV.
|
| + CreateIframePage("a()");
|
| + content::RenderFrameHost* main_frame = GetFrame(std::vector<size_t>{});
|
| + content::RenderWidgetHostView* view = main_frame->GetView();
|
| + content::WebContents* web_contents = active_contents();
|
| +
|
| + content::TextInputStateSender sender(view);
|
| +
|
| + auto send_and_check_show_ime = [&sender, &web_contents]() {
|
| + InputMethodObserverForShowIme observer(web_contents);
|
| + sender.Send();
|
| + return observer.success();
|
| + };
|
| +
|
| + // Sending an empty state should not trigger ime.
|
| + EXPECT_FALSE(send_and_check_show_ime());
|
| +
|
| + // Set |TextInputState.type| to text. Expect no IME.
|
| + sender.SetType(ui::TEXT_INPUT_TYPE_TEXT);
|
| + EXPECT_FALSE(send_and_check_show_ime());
|
| +
|
| + // Set |TextInputState.show_ime_if_needed| to true. Expect IME.
|
| + sender.SetShowImeIfNeeded(true);
|
| + EXPECT_TRUE(send_and_check_show_ime());
|
| +
|
| + // Send the same message. Expect IME (no change).
|
| + EXPECT_TRUE(send_and_check_show_ime());
|
| +
|
| + // Reset |TextInputState.show_ime_if_needed|. Expect no IME.
|
| + sender.SetShowImeIfNeeded(false);
|
| + EXPECT_FALSE(send_and_check_show_ime());
|
| +
|
| + // Setting an irrelevant field. Expect no IME.
|
| + sender.SetMode(ui::TEXT_INPUT_MODE_LATIN);
|
| + EXPECT_FALSE(send_and_check_show_ime());
|
| +
|
| + // Set |TextInputState.show_ime_if_needed|. Expect IME.
|
| + sender.SetShowImeIfNeeded(true);
|
| + EXPECT_TRUE(send_and_check_show_ime());
|
| +
|
| + // Set |TextInputState.type| to ui::TEXT_INPUT_TYPE_NONE. Expect no IME.
|
| + sender.SetType(ui::TEXT_INPUT_TYPE_NONE);
|
| + EXPECT_FALSE(send_and_check_show_ime());
|
| +}
|
| +#endif // USE_AURA
|
|
|