Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(2424)

Unified Diff: chrome/browser/renderer_host/site_per_process_text_input_browsertest.cc

Issue 2903833002: Reland: Update TextSelection for non-user initiated events
Patch Set: Add test for JS cursor movement Created 3 years, 5 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View side-by-side diff with in-line comments
Download patch
« no previous file with comments | « AUTHORS ('k') | content/browser/frame_host/render_frame_host_impl.h » ('j') | no next file with comments »
Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
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 736216abba0d6fb28e31ddc52297f86324279624..5ad23c74640c2fdc55f325b0614cf515d6ded320 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
@@ -26,6 +26,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"
@@ -144,22 +145,31 @@ class TextInputManagerTypeObserver : public TextInputManagerObserverBase {
DISALLOW_COPY_AND_ASSIGN(TextInputManagerTypeObserver);
};
-// This class observes TextInputManager for the first change in TextInputState.
-class TextInputManagerChangeObserver : public TextInputManagerObserverBase {
+// This class observes TextInputManager for changes in
+// |TextInputState.selection_start|.
+class TextInputManagerCursorPositionObserver
+ : public TextInputManagerObserverBase {
public:
- explicit TextInputManagerChangeObserver(content::WebContents* web_contents)
- : TextInputManagerObserverBase(web_contents) {
- tester()->SetUpdateTextInputStateCalledCallback(base::Bind(
- &TextInputManagerChangeObserver::VerifyChange, base::Unretained(this)));
+ TextInputManagerCursorPositionObserver(content::WebContents* web_contents,
+ int expected_position)
+ : TextInputManagerObserverBase(web_contents),
+ expected_position_(expected_position) {
+ tester()->SetUpdateTextInputStateCalledCallback(
+ base::Bind(&TextInputManagerCursorPositionObserver::VerifyPosition,
+ base::Unretained(this)));
}
private:
- void VerifyChange() {
- if (tester()->IsTextInputStateChanged())
+ void VerifyPosition() {
+ int position =
+ tester()->GetTextInputCursorPosition(&position) ? position : -1;
+ if (expected_position_ == position)
OnSuccess();
}
- DISALLOW_COPY_AND_ASSIGN(TextInputManagerChangeObserver);
+ const int expected_position_;
+
+ DISALLOW_COPY_AND_ASSIGN(TextInputManagerCursorPositionObserver);
};
// This class observes |TextInputState.type| for a specific RWHV.
@@ -276,25 +286,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);
};
@@ -982,6 +1022,280 @@ 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());
+ }
+}
+
+// This test changes the cursor position in an input field from JavaScript.
+// Verify that the cursor position |TextInputState.selection_start| always
+// updated but non-user initiated text selection change is only triggered if
+// an existing selection is changed.
+IN_PROC_BROWSER_TEST_F(SitePerProcessTextInputManagerTest, JSCursorMovement) {
+ 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();"));
+
+ // No text selection, set cursor position.
+ {
+ TextSelectionObserver selection_observer(active_contents());
+ TextInputManagerCursorPositionObserver position_observer(
+ active_contents(), 2);
+ EXPECT_TRUE(ExecuteScript(
+ frames[i],
+ "document.querySelector('input').setSelectionRange(2, 2);"));
+ position_observer.Wait();
+ // Non-user initiated empty text selection after empty text selection
+ // should not trigger selection change.
+ EXPECT_EQ(0, selection_observer.selection_changed_count());
+ }
+
+ // Set text selection.
+ {
+ TextSelectionObserver selection_observer(active_contents());
+ EXPECT_TRUE(ExecuteScript(frames[i],
+ "document.querySelector('input').select();"));
+ selection_observer.WaitForSelectedText(values[i], false);
+ EXPECT_EQ(1, selection_observer.selection_changed_count());
+ }
+
+ // Set cursor position and clear text selection.
+ {
+ TextSelectionObserver selection_observer(active_contents());
+ TextInputManagerCursorPositionObserver position_observer(
+ active_contents(), 3);
+ EXPECT_TRUE(ExecuteScript(
+ frames[i],
+ "document.querySelector('input').setSelectionRange(3, 3);"));
+ position_observer.Wait();
+ // Non-user initiated empty text selection clears an existing text
+ // selection. Notification is expected about the selection change.
+ selection_observer.WaitForSelectedText("", false);
+ EXPECT_EQ(1, 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).
« no previous file with comments | « AUTHORS ('k') | content/browser/frame_host/render_frame_host_impl.h » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698