hterm: Don't clear selection on copy

Clearing the selection on a non-keyboard copy was tricky, because
we don't want to do it right away, but the delay sucks if you're
trying to work quickly.

Unfortunately we *have* to clear the selection as part of the copy
process, so this patch restores it right after the copy completes.

BUG=none
TEST=text_harness.html, 64/64 tests passed.

Change-Id: I489093dcbebb170c92d64a6f4e11ef4f86fd97b1
Reviewed-on: https://gerrit.chromium.org/gerrit/31061
Reviewed-by: Marius Schilder <mschilder@google.com>
Commit-Ready: Robert Ginda <rginda@chromium.org>
Reviewed-by: Robert Ginda <rginda@chromium.org>
Tested-by: Robert Ginda <rginda@chromium.org>
diff --git a/hterm/js/hterm_keyboard_keymap.js b/hterm/js/hterm_keyboard_keymap.js
index b2b66ae..e918bfa 100644
--- a/hterm/js/hterm_keyboard_keymap.js
+++ b/hterm/js/hterm_keyboard_keymap.js
@@ -401,14 +401,16 @@
  * the key again.
  */
 hterm.Keyboard.KeyMap.prototype.onCtrlC_ = function(e, keyDef) {
-  var document = this.keyboard.terminal.getDocument();
-  if (e.shiftKey || document.getSelection().isCollapsed) {
-    // If the shift key is being held, or there is no document selection, send
-    // a ^C.
+  var selection = this.keyboard.terminal.getDocument().getSelection();
+  if (e.shiftKey || selection.isCollapsed) {
+    // If the shift key is being held or there is no document selection, then
+    // send a ^C.
     return '\x03';
   }
 
-  // Otherwise let the browser handle it as a copy command.
+  // Otherwise let the browser handle it as a copy command.  Clear the selection
+  // soon after a Ctrl-C copy, so that it frees up Ctrl-C to send ^C.
+  setTimeout(selection.collapseToEnd.bind(selection), 750);
   return hterm.Keyboard.KeyActions.PASS;
 };
 
diff --git a/hterm/js/hterm_scrollport.js b/hterm/js/hterm_scrollport.js
index 17b4a8c..5dad219 100644
--- a/hterm/js/hterm_scrollport.js
+++ b/hterm/js/hterm_scrollport.js
@@ -161,8 +161,8 @@
   }
 
   if (!anchorRow) {
-    console.log('Selection anchor is not rooted in a row node: ' +
-                selection.anchorNode.nodeName);
+    console.error('Selection anchor is not rooted in a row node: ' +
+                  selection.anchorNode.nodeName);
     return;
   }
 
@@ -172,8 +172,8 @@
   }
 
   if (!focusRow) {
-    console.log('Selection focus is not rooted in a row node: ' +
-                selection.focusNode.nodeName);
+    console.error('Selection focus is not rooted in a row node: ' +
+                  selection.focusNode.nodeName);
     return;
   }
 
diff --git a/hterm/js/hterm_terminal.js b/hterm/js/hterm_terminal.js
index a3e6ed8..0d0411d 100644
--- a/hterm/js/hterm_terminal.js
+++ b/hterm/js/hterm_terminal.js
@@ -2281,7 +2281,7 @@
  * Note: If there is a selected range in the terminal, it'll be cleared.
  */
 hterm.Terminal.prototype.copyStringToClipboard = function(str) {
-  this.showOverlay(hterm.msg('NOTIFY_COPY'), 500);
+  setTimeout(this.showOverlay.bind(this, hterm.msg('NOTIFY_COPY'), 500), 200);
 
   var copySource = this.document_.createElement('pre');
   copySource.textContent = str;
@@ -2291,11 +2291,20 @@
       'top: -99px');
 
   this.document_.body.appendChild(copySource);
+
   var selection = this.document_.getSelection();
+  var anchorNode = selection.anchorNode;
+  var anchorOffset = selection.anchorOffset;
+  var focusNode = selection.focusNode;
+  var focusOffset = selection.focusOffset;
+
   selection.selectAllChildren(copySource);
 
   hterm.copySelectionToClipboard(this.document_);
 
+  selection.collapse(anchorNode, anchorOffset);
+  selection.extend(focusNode, focusOffset);
+
   copySource.parentNode.removeChild(copySource);
 };
 
@@ -2362,6 +2371,19 @@
  * coordinates for the mouse event.
  */
 hterm.Terminal.prototype.onMouse_ = function(e) {
+  if (e.processedByTerminalHandler_) {
+    // We register our event handlers on the document, as well as the cursor
+    // and the scroll blocker.  Mouse events that occur on the cursor or
+    // scroll blocker will also appear on the document, but we don't want to
+    // process them twice.
+    //
+    // We can't just prevent bubbling because that has other side effects, so
+    // we decorate the event object with this property instead.
+    return;
+  }
+
+  e.processedByTerminalHandler_ = true;
+
   if (e.type == 'mousedown' && e.which == this.mousePasteButton) {
     this.paste();
     return;
@@ -2369,7 +2391,7 @@
 
   if (e.type == 'mouseup' && e.which == 1 && this.copyOnSelect &&
       !this.document_.getSelection().isCollapsed) {
-    this.copySelectionToClipboard();
+    hterm.copySelectionToClipboard(this.document_);
     return;
   }
 
@@ -2398,18 +2420,7 @@
     this.scrollBlockerNode_.style.top = '-99px';
   }
 
-  if (!e.processedByTerminalHandler_) {
-    // We register our event handlers on the document, as well as the cursor
-    // and the scroll blocker.  Mouse events that occur on the cursor or
-    // scroll blocker will also appear on the document, but we don't want to
-    // process them twice.
-    //
-    // We can't just prevent bubbling because that has other side effects, so
-    // we decorate the event object with this property instead.
-    e.processedByTerminalHandler_ = true;
-
-    this.onMouse(e);
-  }
+  this.onMouse(e);
 };
 
 /**
@@ -2446,7 +2457,7 @@
  */
 hterm.Terminal.prototype.onCopy_ = function(e) {
   e.preventDefault();
-  setTimeout(this.copySelectionToClipboard.bind(this), 200);
+  this.copySelectionToClipboard();
 };
 
 /**