Cleanup: use iron-a11y-keys-behavior for keyboard shortcuts

+ This does not cover on-keydown handlers within elements.
  A follow-up change will account for those.
+ Keyboard shortcuts are disabled within gr-overlay, input,
  and textarea elements.
+ Added tests for new behavior (plus some missing ones covering
  broken behavior).
+ Removed blur hacks used on elements to placate the kb
  shortcuts due to restrictions that have been removed.

Bug: Issue 4198
Change-Id: Ide8009a3bfc340a35a8ec8b9189a85b49c8a95aa
diff --git a/polygerrit-ui/app/elements/diff/gr-diff-view/gr-diff-view.js b/polygerrit-ui/app/elements/diff/gr-diff-view/gr-diff-view.js
index 44204fd..1d63b47 100644
--- a/polygerrit-ui/app/elements/diff/gr-diff-view/gr-diff-view.js
+++ b/polygerrit-ui/app/elements/diff/gr-diff-view/gr-diff-view.js
@@ -100,6 +100,22 @@
       '_getFiles(_changeNum, _patchRange.*)',
     ],
 
+    keyBindings: {
+      'esc': '_handleEscKey',
+      'shift+left': '_handleShiftLeftKey',
+      'shift+right': '_handleShiftRightKey',
+      'up k': '_handleUpKey',
+      'down j': '_handleDownKey',
+      'c': '_handleCKey',
+      '[': '_handleLeftBracketKey',
+      ']': '_handleRightBracketKey',
+      'n shift+n': '_handleNKey',
+      'p shift+p': '_handlePKey',
+      'a shift+a': '_handleAKey',
+      'u': '_handleUKey',
+      ',': '_handleCommaKey',
+    },
+
     attached: function() {
       this._getLoggedIn().then(function(loggedIn) {
         this._loggedIn = loggedIn;
@@ -185,103 +201,129 @@
           this._patchRange.patchNum, this._path, reviewed);
     },
 
-    _checkForModifiers: function(e) {
-      return e.altKey || e.ctrlKey || e.metaKey || e.shiftKey || false;
-    },
-
-    _handleKey: function(e) {
+    _handleEscKey: function(e) {
       if (this.shouldSuppressKeyboardShortcut(e)) { return; }
 
-      switch (e.keyCode) {
-        case 27: // escape
-          e.preventDefault();
-          this.$.diff.displayLine = false;
-          break;
-        case 37: // left
-          if (e.shiftKey) {
-            e.preventDefault();
-            this.$.cursor.moveLeft();
-          }
-          break;
-        case 39: // right
-          if (e.shiftKey) {
-            e.preventDefault();
-            this.$.cursor.moveRight();
-          }
-          break;
-        case 40: // down
-        case 74: // 'j'
-          e.preventDefault();
-          this.$.diff.displayLine = true;
-          this.$.cursor.moveDown();
-          break;
-        case 38: // up
-        case 75: // 'k'
-          e.preventDefault();
-          this.$.diff.displayLine = true;
-          this.$.cursor.moveUp();
-          break;
-        case 67: // 'c'
-          if (this._checkForModifiers(e)) { return; }
-          if (!this.$.diff.isRangeSelected()) {
-            e.preventDefault();
-            var line = this.$.cursor.getTargetLineElement();
-            if (line) {
-              this.$.diff.addDraftAtLine(line);
-            }
-          }
-          break;
-        case 219:  // '['
-          e.preventDefault();
-          this._navToFile(this._path, this._fileList, -1);
-          break;
-        case 221:  // ']'
-          e.preventDefault();
-          this._navToFile(this._path, this._fileList, 1);
-          break;
-        case 78:  // 'n'
-          e.preventDefault();
-          if (e.shiftKey) {
-            this.$.cursor.moveToNextCommentThread();
-          } else {
-            this.$.cursor.moveToNextChunk();
-          }
-          break;
-        case 80:  // 'p'
-          e.preventDefault();
-          if (e.shiftKey) {
-            this.$.cursor.moveToPreviousCommentThread();
-          } else {
-            this.$.cursor.moveToPreviousChunk();
-          }
-          break;
-        case 65:  // 'a'
-          if (e.shiftKey) { // Hide left diff.
-            e.preventDefault();
-            this.$.diff.toggleLeftDiff();
-            break;
-          }
+      e.preventDefault();
+      this.$.diff.displayLine = false;
+    },
 
-          if (!this._loggedIn) { break; }
+    _handleShiftLeftKey: function(e) {
+      if (this.shouldSuppressKeyboardShortcut(e)) { return; }
 
-          this.set('changeViewState.showReplyDialog', true);
-          /* falls through */ // required by JSHint
-        case 85:  // 'u'
-          if (this._changeNum && this._patchRange.patchNum) {
-            e.preventDefault();
-            page.show(this._getChangePath(
-                this._changeNum,
-                this._patchRange,
-                this._change && this._change.revisions));
-          }
-          break;
-        case 188:  // ','
-          e.preventDefault();
-          this._openPrefs();
-          break;
+      e.preventDefault();
+      this.$.cursor.moveLeft();
+    },
+
+    _handleShiftRightKey: function(e) {
+      if (this.shouldSuppressKeyboardShortcut(e)) { return; }
+
+      e.preventDefault();
+      this.$.cursor.moveRight();
+    },
+
+    _handleUpKey: function(e) {
+      if (this.shouldSuppressKeyboardShortcut(e)) { return; }
+
+      e.preventDefault();
+      this.$.diff.displayLine = true;
+      this.$.cursor.moveUp();
+    },
+
+    _handleDownKey: function(e) {
+      if (this.shouldSuppressKeyboardShortcut(e)) { return; }
+
+      e.preventDefault();
+      this.$.diff.displayLine = true;
+      this.$.cursor.moveDown();
+    },
+
+    _handleCKey: function(e) {
+      if (this.shouldSuppressKeyboardShortcut(e)) { return; }
+      if (this.$.diff.isRangeSelected()) { return; }
+
+      e.preventDefault();
+      var line = this.$.cursor.getTargetLineElement();
+      if (line) {
+        this.$.diff.addDraftAtLine(line);
       }
     },
 
+    _handleLeftBracketKey: function(e) {
+      if (this.shouldSuppressKeyboardShortcut(e)) { return; }
+
+      e.preventDefault();
+      this._navToFile(this._path, this._fileList, -1);
+    },
+
+    _handleRightBracketKey: function(e) {
+      if (this.shouldSuppressKeyboardShortcut(e)) { return; }
+
+      e.preventDefault();
+      this._navToFile(this._path, this._fileList, 1);
+    },
+
+    _handleNKey: function(e) {
+      if (this.shouldSuppressKeyboardShortcut(e)) { return; }
+
+      e.preventDefault();
+      if (e.detail.keyboardEvent.shiftKey) {
+        this.$.cursor.moveToNextCommentThread();
+      } else {
+        this.$.cursor.moveToNextChunk();
+      }
+    },
+
+    _handlePKey: function(e) {
+      if (this.shouldSuppressKeyboardShortcut(e)) { return; }
+
+      e.preventDefault();
+      if (e.detail.keyboardEvent.shiftKey) {
+        this.$.cursor.moveToPreviousCommentThread();
+      } else {
+        this.$.cursor.moveToPreviousChunk();
+      }
+    },
+
+    _handleAKey: function(e) {
+      if (this.shouldSuppressKeyboardShortcut(e)) { return; }
+
+      if (e.detail.keyboardEvent.shiftKey) { // Hide left diff.
+        e.preventDefault();
+        this.$.diff.toggleLeftDiff();
+        return;
+      }
+
+      if (!this._loggedIn) { return; }
+
+      this.set('changeViewState.showReplyDialog', true);
+      e.preventDefault();
+      this._navToChangeView();
+    },
+
+    _handleUKey: function(e) {
+      if (this.shouldSuppressKeyboardShortcut(e)) { return; }
+
+      e.preventDefault();
+      this._navToChangeView();
+    },
+
+    _handleCommaKey: function(e) {
+      if (this.shouldSuppressKeyboardShortcut(e)) { return; }
+
+      e.preventDefault();
+      this._openPrefs();
+    },
+
+    _navToChangeView: function() {
+      if (!this._changeNum || !this._patchRange.patchNum) { return; }
+
+      page.show(this._getChangePath(
+          this._changeNum,
+          this._patchRange,
+          this._change && this._change.revisions));
+    },
+
     _navToFile: function(path, fileList, direction) {
       var url = this._computeNavLinkURL(path, fileList, direction);
       if (!url) { return; }
@@ -556,10 +598,6 @@
       history.replaceState(null, null, '#' + this.$.cursor.getAddress());
     },
 
-    _handleDropdownChange: function(e) {
-      e.target.blur();
-    },
-
     _computeDownloadLink: function(changeNum, patchRange, path) {
       var url = this.changeBaseURL(changeNum, patchRange.patchNum);
       url += '/patch?zip&path=' + encodeURIComponent(path);