| Index: chrome/browser/renderer_host/site_per_process_text_input_browsertest.cc
|
| diff --git a/chrome/browser/renderer_host/site_per_process_text_input_browsertest.cc b/chrome/browser/renderer_host/site_per_process_text_input_browsertest.cc
|
| index a06446b7ac41606c3d191efc851e72b01f240e52..ca04f73bb03b57a0daf993f96a4325c47799b240 100644
|
| --- a/chrome/browser/renderer_host/site_per_process_text_input_browsertest.cc
|
| +++ b/chrome/browser/renderer_host/site_per_process_text_input_browsertest.cc
|
| @@ -24,6 +24,7 @@
|
| #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/clipboard/clipboard.h"
|
| #include "ui/base/ime/composition_underline.h"
|
| #include "ui/base/ime/text_edit_commands.h"
|
| #include "ui/base/ime/text_input_client.h"
|
| @@ -142,24 +143,6 @@ class TextInputManagerTypeObserver : public TextInputManagerObserverBase {
|
| DISALLOW_COPY_AND_ASSIGN(TextInputManagerTypeObserver);
|
| };
|
|
|
| -// This class observes TextInputManager for the first change in TextInputState.
|
| -class TextInputManagerChangeObserver : public TextInputManagerObserverBase {
|
| - public:
|
| - explicit TextInputManagerChangeObserver(content::WebContents* web_contents)
|
| - : TextInputManagerObserverBase(web_contents) {
|
| - tester()->SetUpdateTextInputStateCalledCallback(base::Bind(
|
| - &TextInputManagerChangeObserver::VerifyChange, base::Unretained(this)));
|
| - }
|
| -
|
| - private:
|
| - void VerifyChange() {
|
| - if (tester()->IsTextInputStateChanged())
|
| - OnSuccess();
|
| - }
|
| -
|
| - DISALLOW_COPY_AND_ASSIGN(TextInputManagerChangeObserver);
|
| -};
|
| -
|
| // This class observes |TextInputState.type| for a specific RWHV.
|
| class ViewTextInputTypeObserver : public TextInputManagerObserverBase {
|
| public:
|
| @@ -274,25 +257,55 @@ class ViewTextSelectionObserver : public TextInputManagerObserverBase {
|
| class TextSelectionObserver : public TextInputManagerObserverBase {
|
| public:
|
| explicit TextSelectionObserver(content::WebContents* web_contents)
|
| - : TextInputManagerObserverBase(web_contents) {
|
| + : TextInputManagerObserverBase(web_contents),
|
| + selection_changed_count_(0) {
|
| tester()->SetOnTextSelectionChangedCallback(base::Bind(
|
| &TextSelectionObserver::VerifyChange, base::Unretained(this)));
|
| }
|
|
|
| - void WaitForSelectedText(const std::string& text) {
|
| - selected_text_ = text;
|
| - Wait();
|
| + void WaitForSelectedText(const std::string& text,
|
| + bool user_initiated = true) {
|
| + expected_text_ = text;
|
| + expected_user_initiated_ = user_initiated;
|
| + if (last_selected_text_.has_value() &&
|
| + last_selected_text_ == expected_text_ &&
|
| + last_user_initiated_ == expected_user_initiated_) {
|
| + OnSuccess();
|
| + } else {
|
| + Wait();
|
| + }
|
| }
|
|
|
| + int selection_changed_count() { return selection_changed_count_; }
|
| +
|
| private:
|
| void VerifyChange() {
|
| - if (base::UTF16ToUTF8(tester()->GetUpdatedView()->GetSelectedText()) ==
|
| - selected_text_) {
|
| + selection_changed_count_++;
|
| +
|
| + last_selected_text_ =
|
| + base::UTF16ToUTF8(tester()->GetUpdatedView()->GetSelectedText());
|
| + EXPECT_TRUE(tester()->GetTextSelectionUserInitiatedForView(
|
| + tester()->GetUpdatedView(), &last_user_initiated_));
|
| +
|
| + // Check whether the expected values are already set.
|
| + if (!expected_text_.has_value())
|
| + return;
|
| +
|
| + if (last_selected_text_ == expected_text_ &&
|
| + last_user_initiated_ == expected_user_initiated_) {
|
| OnSuccess();
|
| }
|
| }
|
|
|
| - std::string selected_text_;
|
| + // These optional properties are also used to verify that the last and
|
| + // expected properties are set.
|
| + base::Optional<std::string> last_selected_text_;
|
| + base::Optional<std::string> expected_text_;
|
| +
|
| + bool last_user_initiated_;
|
| + bool expected_user_initiated_;
|
| +
|
| + int selection_changed_count_;
|
|
|
| DISALLOW_COPY_AND_ASSIGN(TextSelectionObserver);
|
| };
|
| @@ -980,6 +993,220 @@ IN_PROC_BROWSER_TEST_F(SitePerProcessTextInputManagerTest,
|
| }
|
| }
|
|
|
| +// The following tests verify that the TextInputManager notifies about a
|
| +// text selection change event, the corresponding |user_initiated| property
|
| +// of TextSelection is valid, and the selection clipboard is updated
|
| +// according to the source of the event.
|
| +// See: https://crbug.com/671986
|
| +
|
| +// Test text selection change event for non-user initiated cases
|
| +// (eg. JavaScript). Non-user initiated events should not update the selection
|
| +// clipboard.
|
| +IN_PROC_BROWSER_TEST_F(SitePerProcessTextInputManagerTest,
|
| + NonUserInitiatedTextSelection) {
|
| +#if defined(USE_AURA) && defined(OS_LINUX) && !defined(OS_CHROMEOS)
|
| + ui::Clipboard* clipboard = ui::Clipboard::GetForCurrentThread();
|
| + ASSERT_TRUE(clipboard);
|
| + clipboard->Clear(ui::CLIPBOARD_TYPE_SELECTION);
|
| +#endif
|
| +
|
| + CreateIframePage("a(b, c)");
|
| + std::vector<std::string> values{"node_a", "node_b", "node_c"};
|
| + std::vector<content::RenderFrameHost*> frames{GetFrame(IndexVector{}),
|
| + GetFrame(IndexVector{0}),
|
| + GetFrame(IndexVector{1})};
|
| +
|
| + for (size_t i = 0; i < frames.size(); ++i)
|
| + AddInputFieldToFrame(frames[i], "text", values[i], true);
|
| +
|
| + // Test text selection from JavaScript across frames (non-user initiated).
|
| + for (size_t i = 0; i < frames.size(); ++i) {
|
| + // Trigger text selection.
|
| + TextSelectionObserver trigger_observer(active_contents());
|
| + EXPECT_TRUE(
|
| + ExecuteScript(frames[i], "document.querySelector('input').select();"));
|
| + trigger_observer.WaitForSelectedText(values[i], false);
|
| +
|
| +#if defined(USE_AURA) && defined(OS_LINUX) && !defined(OS_CHROMEOS)
|
| + // Non-user initiated text selection should not update the selection
|
| + // clipboard. See: https://crbug.com/12392
|
| + base::string16 result_text;
|
| + clipboard->ReadText(ui::CLIPBOARD_TYPE_SELECTION, &result_text);
|
| + EXPECT_TRUE(result_text.empty());
|
| +#endif
|
| +
|
| + // Clear text selection.
|
| + TextSelectionObserver clear_observer(active_contents());
|
| + EXPECT_TRUE(ExecuteScript(frames[i], "document.getSelection().empty();"));
|
| + clear_observer.WaitForSelectedText("", false);
|
| + }
|
| +}
|
| +
|
| +// Test text selection change event for user initiated cases (eg. key press)
|
| +// User initiated events should update the selection clipboard where it is
|
| +// supported.
|
| +IN_PROC_BROWSER_TEST_F(SitePerProcessTextInputManagerTest,
|
| + UserInitiatedTextSelection) {
|
| +#if defined(USE_AURA) && defined(OS_LINUX) && !defined(OS_CHROMEOS)
|
| + ui::Clipboard* clipboard = ui::Clipboard::GetForCurrentThread();
|
| + ASSERT_TRUE(clipboard);
|
| + clipboard->Clear(ui::CLIPBOARD_TYPE_SELECTION);
|
| +#endif
|
| +
|
| + CreateIframePage("a(b, c)");
|
| + std::vector<std::string> values{"node_a", "node_b", "node_c"};
|
| + std::vector<content::RenderFrameHost*> frames{GetFrame(IndexVector{}),
|
| + GetFrame(IndexVector{0}),
|
| + GetFrame(IndexVector{1})};
|
| +
|
| + for (size_t i = 0; i < frames.size(); ++i)
|
| + AddInputFieldToFrame(frames[i], "text", values[i], true);
|
| +
|
| + // Test text selection by user input across frames (user initiated).
|
| + for (size_t i = 0; i < frames.size(); ++i) {
|
| + // Focus on input element.
|
| + std::string result;
|
| + std::string script =
|
| + "function getInputField() {"
|
| + " return document.querySelector('input');"
|
| + "}"
|
| + "function onInputFocus(e) {"
|
| + " domAutomationController.setAutomationId(0);"
|
| + " domAutomationController.send(getInputField().value);"
|
| + "}"
|
| + "getInputField().addEventListener('focus', onInputFocus);";
|
| + EXPECT_TRUE(ExecuteScript(frames[i], script));
|
| + EXPECT_TRUE(ExecuteScriptAndExtractString(
|
| + frames[i], "window.focus(); document.querySelector('input').focus();",
|
| + &result));
|
| + EXPECT_EQ(values[i], result);
|
| + EXPECT_EQ(frames[i], active_contents()->GetFocusedFrame());
|
| +
|
| + // Press ctrl+a to select text in the input field.
|
| + TextSelectionObserver trigger_observer(active_contents());
|
| +#if !defined(OS_MACOSX)
|
| + SimulateKeyPress(active_contents(), ui::DomKey::FromCharacter('A'),
|
| + ui::DomCode::US_A, ui::VKEY_A, true, false, false, false);
|
| +#else
|
| + // On macOS the select all shortcut (Cmd+A) is handled by the browser via
|
| + // keyboard accelerator. Thus we can't simulate key press on WebContents.
|
| + // As a workaround, call the SelectAll directly as the shortcut would do.
|
| + active_contents()->SelectAll();
|
| +#endif
|
| + trigger_observer.WaitForSelectedText(values[i], true);
|
| +
|
| +#if defined(USE_AURA) && defined(OS_LINUX) && !defined(OS_CHROMEOS)
|
| + // User initiated text selection should update the selection clipboard.
|
| + base::string16 result_text;
|
| + clipboard->ReadText(ui::CLIPBOARD_TYPE_SELECTION, &result_text);
|
| + EXPECT_EQ(base::ASCIIToUTF16(values[i]), result_text);
|
| +#endif
|
| +
|
| + // Press down key to clear text selection.
|
| + TextSelectionObserver clear_observer(active_contents());
|
| + SimulateKeyPress(active_contents(), ui::DomKey::ARROW_DOWN,
|
| + ui::DomCode::ARROW_DOWN, ui::VKEY_DOWN, false, false,
|
| + false, false);
|
| + clear_observer.WaitForSelectedText("", true);
|
| + }
|
| +}
|
| +
|
| +// This test changes focus between input fields and checks that no selection
|
| +// changed event was triggered when Blink tries to clear a potential text
|
| +// selection on the previous input field where there was no text selection.
|
| +IN_PROC_BROWSER_TEST_F(SitePerProcessTextInputManagerTest,
|
| + TextSelectionOnFocusChange) {
|
| + CreateIframePage("a(b, c)");
|
| + std::vector<std::string> values{"node_a", "node_b", "node_c"};
|
| + std::vector<content::RenderFrameHost*> frames{GetFrame(IndexVector{}),
|
| + GetFrame(IndexVector{0}),
|
| + GetFrame(IndexVector{1})};
|
| +
|
| + for (size_t i = 0; i < frames.size(); ++i) {
|
| + AddInputFieldToFrame(frames[i], "text", values[i], true);
|
| + AddInputFieldToFrame(frames[i], "number", std::to_string(i), true);
|
| + }
|
| +
|
| + for (size_t i = 0; i < frames.size(); ++i) {
|
| + std::string script =
|
| + "function onInputFocus(e) {"
|
| + " domAutomationController.setAutomationId(0);"
|
| + " domAutomationController.send(document.activeElement.value);"
|
| + "}"
|
| + "var text_input = document.querySelector('input[type=text]');"
|
| + "text_input.addEventListener('focus', onInputFocus);"
|
| + "var number_input = document.querySelector('input[type=number]');"
|
| + "number_input.addEventListener('focus', onInputFocus);";
|
| + EXPECT_TRUE(ExecuteScript(frames[i], script));
|
| + TextSelectionObserver selection_observer(active_contents());
|
| +
|
| + // Focus on text input element.
|
| + std::string text_result;
|
| + EXPECT_TRUE(ExecuteScriptAndExtractString(
|
| + frames[i],
|
| + "window.focus(); document.querySelector('input[type=text]').focus();",
|
| + &text_result));
|
| + EXPECT_EQ(values[i], text_result);
|
| + EXPECT_EQ(0, selection_observer.selection_changed_count());
|
| +
|
| + // Focus on number input element.
|
| + std::string number_result;
|
| + EXPECT_TRUE(ExecuteScriptAndExtractString(
|
| + frames[i],
|
| + "window.focus(); document.querySelector('input[type=number]').focus();",
|
| + &number_result));
|
| + EXPECT_EQ(std::to_string(i), number_result);
|
| + EXPECT_EQ(0, selection_observer.selection_changed_count());
|
| + }
|
| +}
|
| +
|
| +// This test replaces text in input filed in IME composition mode. The text is
|
| +// replaced by using text selection during the composition mode. Verify that
|
| +// the text replacement doesn't trigger any selection changed event.
|
| +IN_PROC_BROWSER_TEST_F(SitePerProcessTextInputManagerTest,
|
| + ImeSetCompositionTextReplacement) {
|
| + CreateIframePage("a(b, c)");
|
| + std::vector<std::string> values{"node_a", "node_b", "node_c"};
|
| + std::vector<content::RenderFrameHost*> frames{GetFrame(IndexVector{}),
|
| + GetFrame(IndexVector{0}),
|
| + GetFrame(IndexVector{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) {
|
| + // Focus on input element.
|
| + EXPECT_TRUE(ExecuteScript(frames[i],
|
| + "window.focus();"
|
| + "document.querySelector('input').focus();"));
|
| +
|
| + TextSelectionObserver selection_observer(active_contents());
|
| +
|
| + // Do text replacement in composition mode.
|
| + ViewCompositionRangeChangedObserver range_observer_set_composition(
|
| + active_contents(), frames[i]->GetView());
|
| + content::SendImeSetCompositionTextToWidget(
|
| + frames[i]->GetView()->GetRenderWidgetHost(),
|
| + base::UTF8ToUTF16(values[i]), std::vector<ui::CompositionUnderline>(),
|
| + gfx::Range(5, 1), 0, 0);
|
| + range_observer_set_composition.Wait();
|
| +
|
| + // Check composition text.
|
| + std::string result;
|
| + EXPECT_TRUE(ExecuteScriptAndExtractString(
|
| + frames[i],
|
| + "domAutomationController.setAutomationId(0);"
|
| + "var value = document.querySelector('input').value;"
|
| + "domAutomationController.send(value);",
|
| + &result));
|
| + EXPECT_EQ("node_" + values[i], result);
|
| +
|
| + // Text selection should not be triggered by IME composition text
|
| + // replacement.
|
| + EXPECT_EQ(0, selection_observer.selection_changed_count());
|
| + }
|
| +}
|
| +
|
| // 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).
|
|
|