Upstream version 7.35.139.0
[platform/framework/web/crosswalk.git] / src / ui / views / controls / textfield / textfield.cc
index 98b364e..c9836d3 100644 (file)
 #include "base/win/win_util.h"
 #endif
 
+#if defined(OS_LINUX) && !defined(OS_CHROMEOS)
+#include "base/strings/utf_string_conversions.h"
+#include "ui/events/linux/text_edit_command_auralinux.h"
+#include "ui/events/linux/text_edit_key_bindings_delegate_auralinux.h"
+#endif
+
 namespace views {
 
 namespace {
@@ -47,6 +53,8 @@ namespace {
 // Default placeholder text color.
 const SkColor kDefaultPlaceholderTextColor = SK_ColorLTGRAY;
 
+const int kNoCommand = 0;
+
 void ConvertRectToScreen(const View* src, gfx::Rect* r) {
   DCHECK(src);
 
@@ -55,6 +63,164 @@ void ConvertRectToScreen(const View* src, gfx::Rect* r) {
   r->set_origin(new_origin);
 }
 
+int GetCommandForKeyEvent(const ui::KeyEvent& event, bool has_selection) {
+  if (event.type() != ui::ET_KEY_PRESSED || event.IsUnicodeKeyCode())
+    return kNoCommand;
+
+  const bool shift = event.IsShiftDown();
+  const bool control = event.IsControlDown();
+  const bool alt = event.IsAltDown() || event.IsAltGrDown();
+  switch (event.key_code()) {
+    case ui::VKEY_Z:
+      if (control && !shift && !alt)
+        return IDS_APP_UNDO;
+      return (control && shift && !alt) ? IDS_APP_REDO : kNoCommand;
+    case ui::VKEY_Y:
+      return (control && !alt) ? IDS_APP_REDO : kNoCommand;
+    case ui::VKEY_A:
+      return (control && !alt) ? IDS_APP_SELECT_ALL : kNoCommand;
+    case ui::VKEY_X:
+      return (control && !alt) ? IDS_APP_CUT : kNoCommand;
+    case ui::VKEY_C:
+      return (control && !alt) ? IDS_APP_COPY : kNoCommand;
+    case ui::VKEY_V:
+      return (control && !alt) ? IDS_APP_PASTE : kNoCommand;
+    case ui::VKEY_RIGHT:
+      // Ignore alt+right, which may be a browser navigation shortcut.
+      if (alt)
+        return kNoCommand;
+      if (!shift)
+        return control ? IDS_MOVE_WORD_RIGHT : IDS_MOVE_RIGHT;
+      return control ? IDS_MOVE_WORD_RIGHT_AND_MODIFY_SELECTION :
+                        IDS_MOVE_RIGHT_AND_MODIFY_SELECTION;
+    case ui::VKEY_LEFT:
+      // Ignore alt+left, which may be a browser navigation shortcut.
+      if (alt)
+        return kNoCommand;
+      if (!shift)
+        return control ? IDS_MOVE_WORD_LEFT : IDS_MOVE_LEFT;
+      return control ? IDS_MOVE_WORD_LEFT_AND_MODIFY_SELECTION :
+                        IDS_MOVE_LEFT_AND_MODIFY_SELECTION;
+    case ui::VKEY_HOME:
+      return shift ? IDS_MOVE_TO_BEGINNING_OF_LINE_AND_MODIFY_SELECTION :
+                      IDS_MOVE_TO_BEGINNING_OF_LINE;
+    case ui::VKEY_END:
+      return shift ? IDS_MOVE_TO_END_OF_LINE_AND_MODIFY_SELECTION :
+                      IDS_MOVE_TO_END_OF_LINE;
+    case ui::VKEY_BACK:
+      if (!control || has_selection)
+        return IDS_DELETE_BACKWARD;
+#if defined(OS_LINUX)
+      // Only erase by line break on Linux and ChromeOS.
+      if (shift)
+        return IDS_DELETE_TO_BEGINNING_OF_LINE;
+#endif
+      return IDS_DELETE_WORD_BACKWARD;
+    case ui::VKEY_DELETE:
+      if (!control || has_selection)
+        return (shift && has_selection) ? IDS_APP_CUT : IDS_DELETE_FORWARD;
+#if defined(OS_LINUX)
+      // Only erase by line break on Linux and ChromeOS.
+      if (shift)
+        return IDS_DELETE_TO_END_OF_LINE;
+#endif
+      return IDS_DELETE_WORD_FORWARD;
+    case ui::VKEY_INSERT:
+      if (control && !shift)
+        return IDS_APP_COPY;
+      return (shift && !control) ? IDS_APP_PASTE : kNoCommand;
+    default:
+      return kNoCommand;
+  }
+  return kNoCommand;
+}
+
+#if defined(OS_LINUX) && !defined(OS_CHROMEOS)
+int GetViewsCommand(const ui::TextEditCommandAuraLinux& command, bool rtl) {
+  const bool select = command.extend_selection();
+  switch (command.command_id()) {
+    case ui::TextEditCommandAuraLinux::COPY:
+      return IDS_APP_COPY;
+    case ui::TextEditCommandAuraLinux::CUT:
+      return IDS_APP_CUT;
+    case ui::TextEditCommandAuraLinux::DELETE_BACKWARD:
+      return IDS_DELETE_BACKWARD;
+    case ui::TextEditCommandAuraLinux::DELETE_FORWARD:
+      return IDS_DELETE_FORWARD;
+    case ui::TextEditCommandAuraLinux::DELETE_TO_BEGINING_OF_LINE:
+    case ui::TextEditCommandAuraLinux::DELETE_TO_BEGINING_OF_PARAGRAPH:
+      return IDS_DELETE_TO_BEGINNING_OF_LINE;
+    case ui::TextEditCommandAuraLinux::DELETE_TO_END_OF_LINE:
+    case ui::TextEditCommandAuraLinux::DELETE_TO_END_OF_PARAGRAPH:
+      return IDS_DELETE_TO_END_OF_LINE;
+    case ui::TextEditCommandAuraLinux::DELETE_WORD_BACKWARD:
+      return IDS_DELETE_WORD_BACKWARD;
+    case ui::TextEditCommandAuraLinux::DELETE_WORD_FORWARD:
+      return IDS_DELETE_WORD_FORWARD;
+    case ui::TextEditCommandAuraLinux::INSERT_TEXT:
+      return kNoCommand;
+    case ui::TextEditCommandAuraLinux::MOVE_BACKWARD:
+      if (rtl)
+        return select ? IDS_MOVE_RIGHT_AND_MODIFY_SELECTION : IDS_MOVE_RIGHT;
+      return select ? IDS_MOVE_LEFT_AND_MODIFY_SELECTION : IDS_MOVE_LEFT;
+    case ui::TextEditCommandAuraLinux::MOVE_DOWN:
+      return IDS_MOVE_DOWN;
+    case ui::TextEditCommandAuraLinux::MOVE_FORWARD:
+      if (rtl)
+        return select ? IDS_MOVE_LEFT_AND_MODIFY_SELECTION : IDS_MOVE_LEFT;
+      return select ? IDS_MOVE_RIGHT_AND_MODIFY_SELECTION : IDS_MOVE_RIGHT;
+    case ui::TextEditCommandAuraLinux::MOVE_LEFT:
+      return select ? IDS_MOVE_LEFT_AND_MODIFY_SELECTION : IDS_MOVE_LEFT;
+    case ui::TextEditCommandAuraLinux::MOVE_PAGE_DOWN:
+    case ui::TextEditCommandAuraLinux::MOVE_PAGE_UP:
+      return kNoCommand;
+    case ui::TextEditCommandAuraLinux::MOVE_RIGHT:
+      return select ? IDS_MOVE_RIGHT_AND_MODIFY_SELECTION : IDS_MOVE_RIGHT;
+    case ui::TextEditCommandAuraLinux::MOVE_TO_BEGINING_OF_DOCUMENT:
+    case ui::TextEditCommandAuraLinux::MOVE_TO_BEGINING_OF_LINE:
+    case ui::TextEditCommandAuraLinux::MOVE_TO_BEGINING_OF_PARAGRAPH:
+      return select ? IDS_MOVE_TO_BEGINNING_OF_LINE_AND_MODIFY_SELECTION :
+                      IDS_MOVE_TO_BEGINNING_OF_LINE;
+    case ui::TextEditCommandAuraLinux::MOVE_TO_END_OF_DOCUMENT:
+    case ui::TextEditCommandAuraLinux::MOVE_TO_END_OF_LINE:
+    case ui::TextEditCommandAuraLinux::MOVE_TO_END_OF_PARAGRAPH:
+      return select ? IDS_MOVE_TO_END_OF_LINE_AND_MODIFY_SELECTION :
+                      IDS_MOVE_TO_END_OF_LINE;
+    case ui::TextEditCommandAuraLinux::MOVE_UP:
+      return IDS_MOVE_UP;
+    case ui::TextEditCommandAuraLinux::MOVE_WORD_BACKWARD:
+      if (rtl) {
+        return select ? IDS_MOVE_WORD_RIGHT_AND_MODIFY_SELECTION :
+                        IDS_MOVE_WORD_RIGHT;
+      }
+      return select ? IDS_MOVE_WORD_LEFT_AND_MODIFY_SELECTION :
+                      IDS_MOVE_WORD_LEFT;
+    case ui::TextEditCommandAuraLinux::MOVE_WORD_FORWARD:
+      if (rtl) {
+        return select ? IDS_MOVE_WORD_LEFT_AND_MODIFY_SELECTION :
+                        IDS_MOVE_WORD_LEFT;
+      }
+      return select ? IDS_MOVE_WORD_RIGHT_AND_MODIFY_SELECTION :
+                      IDS_MOVE_WORD_RIGHT;
+    case ui::TextEditCommandAuraLinux::MOVE_WORD_LEFT:
+      return select ? IDS_MOVE_WORD_LEFT_AND_MODIFY_SELECTION :
+                      IDS_MOVE_WORD_LEFT;
+    case ui::TextEditCommandAuraLinux::MOVE_WORD_RIGHT:
+      return select ? IDS_MOVE_WORD_RIGHT_AND_MODIFY_SELECTION :
+                      IDS_MOVE_WORD_RIGHT;
+    case ui::TextEditCommandAuraLinux::PASTE:
+      return IDS_APP_PASTE;
+    case ui::TextEditCommandAuraLinux::SELECT_ALL:
+      return IDS_APP_SELECT_ALL;
+    case ui::TextEditCommandAuraLinux::SET_MARK:
+    case ui::TextEditCommandAuraLinux::UNSELECT:
+    case ui::TextEditCommandAuraLinux::INVALID_COMMAND:
+      return kNoCommand;
+  }
+  return kNoCommand;
+}
+#endif
+
 }  // namespace
 
 // static
@@ -82,6 +248,7 @@ Textfield::Textfield()
       use_default_background_color_(true),
       placeholder_text_color_(kDefaultPlaceholderTextColor),
       text_input_type_(ui::TEXT_INPUT_TYPE_TEXT),
+      performing_user_action_(false),
       skip_input_method_cancel_composition_(false),
       cursor_visible_(false),
       drop_cursor_visible_(false),
@@ -424,124 +591,29 @@ bool Textfield::OnKeyPressed(const ui::KeyEvent& event) {
   if (handled)
     return true;
 
-  // TODO(oshima): Refactor and consolidate with ExecuteCommand.
-  if (event.type() == ui::ET_KEY_PRESSED) {
-    ui::KeyboardCode key_code = event.key_code();
-    if (key_code == ui::VKEY_TAB || event.IsUnicodeKeyCode())
+#if defined(OS_LINUX) && !defined(OS_CHROMEOS)
+  ui::TextEditKeyBindingsDelegateAuraLinux* delegate =
+      ui::GetTextEditKeyBindingsDelegate();
+  std::vector<ui::TextEditCommandAuraLinux> commands;
+  if (delegate) {
+    if (!delegate->MatchEvent(event, &commands))
       return false;
-
-    gfx::RenderText* render_text = GetRenderText();
-    const bool editable = !read_only();
-    const bool readable = text_input_type_ != ui::TEXT_INPUT_TYPE_PASSWORD;
-    const bool shift = event.IsShiftDown();
-    const bool control = event.IsControlDown();
-    const bool alt = event.IsAltDown() || event.IsAltGrDown();
-    bool text_changed = false;
-    bool cursor_changed = false;
-
-    OnBeforeUserAction();
-    switch (key_code) {
-      case ui::VKEY_Z:
-        if (control && !shift && !alt && editable)
-          cursor_changed = text_changed = model_->Undo();
-        else if (control && shift && !alt && editable)
-          cursor_changed = text_changed = model_->Redo();
-        break;
-      case ui::VKEY_Y:
-        if (control && !alt && editable)
-          cursor_changed = text_changed = model_->Redo();
-        break;
-      case ui::VKEY_A:
-        if (control && !alt) {
-          model_->SelectAll(false);
-          UpdateSelectionClipboard();
-          cursor_changed = true;
-        }
-        break;
-      case ui::VKEY_X:
-        if (control && !alt && editable && readable)
-          cursor_changed = text_changed = Cut();
-        break;
-      case ui::VKEY_C:
-        if (control && !alt && readable)
-          Copy();
-        break;
-      case ui::VKEY_V:
-        if (control && !alt && editable)
-          cursor_changed = text_changed = Paste();
-        break;
-      case ui::VKEY_RIGHT:
-      case ui::VKEY_LEFT: {
-        // We should ignore the alt-left/right keys because alt key doesn't make
-        // any special effects for them and they can be shortcut keys such like
-        // forward/back of the browser history.
-        if (alt)
-          break;
-        const gfx::Range selection_range = render_text->selection();
-        model_->MoveCursor(
-            control ? gfx::WORD_BREAK : gfx::CHARACTER_BREAK,
-            (key_code == ui::VKEY_RIGHT) ? gfx::CURSOR_RIGHT : gfx::CURSOR_LEFT,
-            shift);
-        UpdateSelectionClipboard();
-        cursor_changed = render_text->selection() != selection_range;
-        break;
+    const bool rtl = GetTextDirection() == base::i18n::RIGHT_TO_LEFT;
+    for (size_t i = 0; i < commands.size(); ++i) {
+      int command = GetViewsCommand(commands[i], rtl);
+      if (IsCommandIdEnabled(command)) {
+        ExecuteCommand(command);
+        handled = true;
       }
-      case ui::VKEY_END:
-      case ui::VKEY_HOME:
-        if ((key_code == ui::VKEY_HOME) ==
-            (render_text->GetTextDirection() == base::i18n::RIGHT_TO_LEFT))
-          model_->MoveCursor(gfx::LINE_BREAK, gfx::CURSOR_RIGHT, shift);
-        else
-          model_->MoveCursor(gfx::LINE_BREAK, gfx::CURSOR_LEFT, shift);
-        UpdateSelectionClipboard();
-        cursor_changed = true;
-        break;
-      case ui::VKEY_BACK:
-      case ui::VKEY_DELETE:
-        if (!editable)
-          break;
-        if (!model_->HasSelection()) {
-          gfx::VisualCursorDirection direction = (key_code == ui::VKEY_DELETE) ?
-              gfx::CURSOR_RIGHT : gfx::CURSOR_LEFT;
-          if (shift && control) {
-            // If shift and control are pressed, erase up to the next line break
-            // on Linux and ChromeOS. Otherwise, do nothing.
-#if defined(OS_LINUX)
-            model_->MoveCursor(gfx::LINE_BREAK, direction, true);
-#else
-            break;
-#endif
-          } else if (control) {
-            // If only control is pressed, then erase the previous/next word.
-            model_->MoveCursor(gfx::WORD_BREAK, direction, true);
-          }
-        }
-        if (key_code == ui::VKEY_BACK)
-          model_->Backspace();
-        else if (shift && model_->HasSelection() && readable)
-          Cut();
-        else
-          model_->Delete();
-
-        // Consume backspace and delete keys even if the edit did nothing. This
-        // prevents potential unintended side-effects of further event handling.
-        text_changed = true;
-        break;
-      case ui::VKEY_INSERT:
-        if (control && !shift && readable)
-          Copy();
-        else if (shift && !control && editable)
-          cursor_changed = text_changed = Paste();
-        break;
-      default:
-        break;
     }
+    return handled;
+  }
+#endif
 
-    // We must have input method in order to support text input.
-    DCHECK(GetInputMethod());
-    UpdateAfterChange(text_changed, cursor_changed);
-    OnAfterUserAction();
-    return (text_changed || cursor_changed);
+  const int command = GetCommandForKeyEvent(event, HasSelection());
+  if (IsCommandIdEnabled(command)) {
+    ExecuteCommand(command);
+    return true;
   }
   return false;
 }
@@ -635,10 +707,23 @@ void Textfield::AboutToRequestFocusFromTabTraversal(bool reverse) {
   SelectAll(false);
 }
 
-bool Textfield::SkipDefaultKeyEventProcessing(const ui::KeyEvent& e) {
+bool Textfield::SkipDefaultKeyEventProcessing(const ui::KeyEvent& event) {
+#if defined(OS_LINUX) && !defined(OS_CHROMEOS)
+  // Skip any accelerator handling that conflicts with custom keybindings.
+  ui::TextEditKeyBindingsDelegateAuraLinux* delegate =
+      ui::GetTextEditKeyBindingsDelegate();
+  std::vector<ui::TextEditCommandAuraLinux> commands;
+  if (delegate && delegate->MatchEvent(event, &commands)) {
+    const bool rtl = GetTextDirection() == base::i18n::RIGHT_TO_LEFT;
+    for (size_t i = 0; i < commands.size(); ++i)
+      if (IsCommandIdEnabled(GetViewsCommand(commands[i], rtl)))
+        return true;
+  }
+#endif
+
   // Skip any accelerator handling of backspace; textfields handle this key.
   // Also skip processing Windows [Alt]+<num-pad digit> Unicode alt-codes.
-  return e.key_code() == ui::VKEY_BACK || e.IsUnicodeKeyCode();
+  return event.key_code() == ui::VKEY_BACK || event.IsUnicodeKeyCode();
 }
 
 bool Textfield::GetDropFormats(
@@ -960,6 +1045,8 @@ bool Textfield::IsCommandIdEnabled(int command_id) const {
   switch (command_id) {
     case IDS_APP_UNDO:
       return editable && model_->CanUndo();
+    case IDS_APP_REDO:
+      return editable && model_->CanRedo();
     case IDS_APP_CUT:
       return editable && readable && model_->HasSelection();
     case IDS_APP_COPY:
@@ -972,6 +1059,26 @@ bool Textfield::IsCommandIdEnabled(int command_id) const {
       return editable && model_->HasSelection();
     case IDS_APP_SELECT_ALL:
       return !text().empty();
+    case IDS_DELETE_FORWARD:
+    case IDS_DELETE_BACKWARD:
+    case IDS_DELETE_TO_BEGINNING_OF_LINE:
+    case IDS_DELETE_TO_END_OF_LINE:
+    case IDS_DELETE_WORD_BACKWARD:
+    case IDS_DELETE_WORD_FORWARD:
+      return editable;
+    case IDS_MOVE_LEFT:
+    case IDS_MOVE_LEFT_AND_MODIFY_SELECTION:
+    case IDS_MOVE_RIGHT:
+    case IDS_MOVE_RIGHT_AND_MODIFY_SELECTION:
+    case IDS_MOVE_WORD_LEFT:
+    case IDS_MOVE_WORD_LEFT_AND_MODIFY_SELECTION:
+    case IDS_MOVE_WORD_RIGHT:
+    case IDS_MOVE_WORD_RIGHT_AND_MODIFY_SELECTION:
+    case IDS_MOVE_TO_BEGINNING_OF_LINE:
+    case IDS_MOVE_TO_BEGINNING_OF_LINE_AND_MODIFY_SELECTION:
+    case IDS_MOVE_TO_END_OF_LINE:
+    case IDS_MOVE_TO_END_OF_LINE_AND_MODIFY_SELECTION:
+      return true;
     default:
       return false;
   }
@@ -988,31 +1095,102 @@ void Textfield::ExecuteCommand(int command_id, int event_flags) {
     return;
 
   bool text_changed = false;
+  bool cursor_changed = false;
+  bool rtl = GetTextDirection() == base::i18n::RIGHT_TO_LEFT;
+  gfx::VisualCursorDirection begin = rtl ? gfx::CURSOR_RIGHT : gfx::CURSOR_LEFT;
+  gfx::VisualCursorDirection end = rtl ? gfx::CURSOR_LEFT : gfx::CURSOR_RIGHT;
+  gfx::Range selection_range = GetSelectedRange();
+
   OnBeforeUserAction();
   switch (command_id) {
     case IDS_APP_UNDO:
-      text_changed = model_->Undo();
+      text_changed = cursor_changed = model_->Undo();
+      break;
+    case IDS_APP_REDO:
+      text_changed = cursor_changed = model_->Redo();
       break;
     case IDS_APP_CUT:
-      text_changed = Cut();
+      text_changed = cursor_changed = Cut();
       break;
     case IDS_APP_COPY:
       Copy();
       break;
     case IDS_APP_PASTE:
-      text_changed = Paste();
+      text_changed = cursor_changed = Paste();
       break;
     case IDS_APP_DELETE:
-      text_changed = model_->Delete();
+      text_changed = cursor_changed = model_->Delete();
       break;
     case IDS_APP_SELECT_ALL:
       SelectAll(false);
       break;
+    case IDS_DELETE_BACKWARD:
+      text_changed = cursor_changed = model_->Backspace();
+      break;
+    case IDS_DELETE_FORWARD:
+      text_changed = cursor_changed = model_->Delete();
+      break;
+    case IDS_DELETE_TO_END_OF_LINE:
+      model_->MoveCursor(gfx::LINE_BREAK, end, true);
+      text_changed = cursor_changed = model_->Delete();
+      break;
+    case IDS_DELETE_TO_BEGINNING_OF_LINE:
+      model_->MoveCursor(gfx::LINE_BREAK, begin, true);
+      text_changed = cursor_changed = model_->Backspace();
+      break;
+    case IDS_DELETE_WORD_BACKWARD:
+      model_->MoveCursor(gfx::WORD_BREAK, begin, true);
+      text_changed = cursor_changed = model_->Backspace();
+      break;
+    case IDS_DELETE_WORD_FORWARD:
+      model_->MoveCursor(gfx::WORD_BREAK, end, true);
+      text_changed = cursor_changed = model_->Delete();
+      break;
+    case IDS_MOVE_LEFT:
+      model_->MoveCursor(gfx::CHARACTER_BREAK, gfx::CURSOR_LEFT, false);
+      break;
+    case IDS_MOVE_LEFT_AND_MODIFY_SELECTION:
+      model_->MoveCursor(gfx::CHARACTER_BREAK, gfx::CURSOR_LEFT, true);
+      break;
+    case IDS_MOVE_RIGHT:
+      model_->MoveCursor(gfx::CHARACTER_BREAK, gfx::CURSOR_RIGHT, false);
+      break;
+    case IDS_MOVE_RIGHT_AND_MODIFY_SELECTION:
+      model_->MoveCursor(gfx::CHARACTER_BREAK, gfx::CURSOR_RIGHT, true);
+      break;
+    case IDS_MOVE_WORD_LEFT:
+      model_->MoveCursor(gfx::WORD_BREAK, gfx::CURSOR_LEFT, false);
+      break;
+    case IDS_MOVE_WORD_LEFT_AND_MODIFY_SELECTION:
+      model_->MoveCursor(gfx::WORD_BREAK, gfx::CURSOR_LEFT, true);
+      break;
+    case IDS_MOVE_WORD_RIGHT:
+      model_->MoveCursor(gfx::WORD_BREAK, gfx::CURSOR_RIGHT, false);
+      break;
+    case IDS_MOVE_WORD_RIGHT_AND_MODIFY_SELECTION:
+      model_->MoveCursor(gfx::WORD_BREAK, gfx::CURSOR_RIGHT, true);
+      break;
+    case IDS_MOVE_TO_BEGINNING_OF_LINE:
+      model_->MoveCursor(gfx::LINE_BREAK, begin, false);
+      break;
+    case IDS_MOVE_TO_BEGINNING_OF_LINE_AND_MODIFY_SELECTION:
+      model_->MoveCursor(gfx::LINE_BREAK, begin, true);
+      break;
+    case IDS_MOVE_TO_END_OF_LINE:
+      model_->MoveCursor(gfx::LINE_BREAK, end, false);
+      break;
+    case IDS_MOVE_TO_END_OF_LINE_AND_MODIFY_SELECTION:
+      model_->MoveCursor(gfx::LINE_BREAK, end, true);
+      break;
     default:
       NOTREACHED();
       break;
   }
-  UpdateAfterChange(text_changed, text_changed);
+
+  cursor_changed |= GetSelectedRange() != selection_range;
+  if (cursor_changed)
+    UpdateSelectionClipboard();
+  UpdateAfterChange(text_changed, cursor_changed);
   OnAfterUserAction();
 }
 
@@ -1371,6 +1549,8 @@ void Textfield::OnCaretBoundsChanged() {
 }
 
 void Textfield::OnBeforeUserAction() {
+  DCHECK(!performing_user_action_);
+  performing_user_action_ = true;
   if (controller_)
     controller_->OnBeforeUserAction(this);
 }
@@ -1378,13 +1558,15 @@ void Textfield::OnBeforeUserAction() {
 void Textfield::OnAfterUserAction() {
   if (controller_)
     controller_->OnAfterUserAction(this);
+  DCHECK(performing_user_action_);
+  performing_user_action_ = false;
 }
 
 bool Textfield::Cut() {
   if (!read_only() && text_input_type_ != ui::TEXT_INPUT_TYPE_PASSWORD &&
       model_->Cut()) {
     if (controller_)
-      controller_->OnAfterCutOrCopy();
+      controller_->OnAfterCutOrCopy(ui::CLIPBOARD_TYPE_COPY_PASTE);
     return true;
   }
   return false;
@@ -1393,7 +1575,7 @@ bool Textfield::Cut() {
 bool Textfield::Copy() {
   if (text_input_type_ != ui::TEXT_INPUT_TYPE_PASSWORD && model_->Copy()) {
     if (controller_)
-      controller_->OnAfterCutOrCopy();
+      controller_->OnAfterCutOrCopy(ui::CLIPBOARD_TYPE_COPY_PASTE);
     return true;
   }
   return false;
@@ -1471,10 +1653,12 @@ void Textfield::CreateTouchSelectionControllerAndNotifyIt() {
 
 void Textfield::UpdateSelectionClipboard() const {
 #if defined(OS_LINUX) && !defined(OS_CHROMEOS)
-  if (HasSelection()) {
+  if (performing_user_action_ && HasSelection()) {
     ui::ScopedClipboardWriter(
         ui::Clipboard::GetForCurrentThread(),
         ui::CLIPBOARD_TYPE_SELECTION).WriteText(GetSelectedText());
+    if (controller_)
+      controller_->OnAfterCutOrCopy(ui::CLIPBOARD_TYPE_SELECTION);
   }
 #endif
 }