[MD Bookmarks] Allow left/right keys to close/open folders in sidebar.

This CL makes the left and right arrows close and open folders in the
sidebar respectively. A closed folder will transfer selection to the
parent when left is pressed and an open folder will transfer selection
to its first child.

BUG=692844
CQ_INCLUDE_TRYBOTS=master.tryserver.chromium.linux:closure_compilation

Review-Url: https://codereview.chromium.org/2857893002
Cr-Commit-Position: refs/heads/master@{#471199}
diff --git a/chrome/browser/resources/md_bookmarks/folder_node.html b/chrome/browser/resources/md_bookmarks/folder_node.html
index 9f87024..cf1629a 100644
--- a/chrome/browser/resources/md_bookmarks/folder_node.html
+++ b/chrome/browser/resources/md_bookmarks/folder_node.html
@@ -57,15 +57,18 @@
       #arrow {
         color: var(--secondary-text-color);
         margin: 0 8px;
+      }
+
+      #arrow iron-icon {
         transform: rotate(-90deg);
         transition: transform 150ms;
       }
 
-      :host-context([dir='rtl']) #arrow {
+      :host-context([dir='rtl']) #arrow iron-icon {
         transform: rotate(90deg);
       }
 
-      #arrow[is-open] {
+      #arrow iron-icon[is-open] {
         transform: initial;
       }
 
@@ -80,9 +83,9 @@
         hidden="[[isRootFolder_(depth)]]">
       <template is="dom-if" if="[[hasChildFolder_(item_.children)]]">
         <button is="paper-icon-button-light" id="arrow"
-            is-open$="[[!isClosed_]]" on-tap="toggleFolder_"
-            on-mousedown="preventDefault_" tabindex="-1">
-          <iron-icon icon="cr:arrow-drop-down"></iron-icon>
+            on-tap="toggleFolder_" on-mousedown="preventDefault_" tabindex="-1">
+          <iron-icon icon="cr:arrow-drop-down" is-open$="[[!isClosed_]]">
+          </iron-icon>
         </button>
       </template>
       <div id="folder-label" class="v-centered">
diff --git a/chrome/browser/resources/md_bookmarks/folder_node.js b/chrome/browser/resources/md_bookmarks/folder_node.js
index 4e25e715..12654b4 100644
--- a/chrome/browser/resources/md_bookmarks/folder_node.js
+++ b/chrome/browser/resources/md_bookmarks/folder_node.js
@@ -83,19 +83,26 @@
    * @param {!Event} e
    */
   onKeydown_: function(e) {
-    var direction = 0;
+    var yDirection = 0;
+    var xDirection = 0;
     var handled = true;
-    // TODO(calamity): Handle left/right arrow keys.
     if (e.key == 'ArrowUp') {
-      direction = -1;
+      yDirection = -1;
     } else if (e.key == 'ArrowDown') {
-      direction = 1;
+      yDirection = 1;
+    } else if (e.key == 'ArrowLeft') {
+      xDirection = -1;
+    } else if (e.key == 'ArrowRight') {
+      xDirection = 1;
     } else {
       handled = false;
     }
 
-    if (direction)
-      this.changeKeyboardSelection_(direction, this.root.activeElement);
+    if (this.getComputedStyleValue('direction') == 'rtl')
+      xDirection *= -1;
+
+    this.changeKeyboardSelection_(
+        xDirection, yDirection, this.root.activeElement);
 
     if (!handled)
       return;
@@ -106,17 +113,45 @@
 
   /**
    * @private
-   * @param {number} direction
+   * @param {number} xDirection
+   * @param {number} yDirection
    * @param {!HTMLElement} currentFocus
    */
-  changeKeyboardSelection_: function(direction, currentFocus) {
+  changeKeyboardSelection_: function(xDirection, yDirection, currentFocus) {
     var newFocusFolderNode = null;
     var isChildFolderNodeFocused =
         currentFocus.tagName == 'BOOKMARKS-FOLDER-NODE';
-    var reverse = direction == -1;
+
+    if (xDirection == 1) {
+      // The right arrow opens a folder if closed and goes to the first child
+      // otherwise.
+      if (this.hasChildFolder_()) {
+        if (this.isClosed_) {
+          this.dispatch(
+              bookmarks.actions.changeFolderOpen(this.item_.id, true));
+        } else {
+          yDirection = 1;
+        }
+      }
+    } else if (xDirection == -1) {
+      // The left arrow closes a folder if open and goes to the parent
+      // otherwise.
+      if (this.hasChildFolder_() && !this.isClosed_) {
+        this.dispatch(bookmarks.actions.changeFolderOpen(this.item_.id, false));
+      } else {
+        var parentFolderNode = this.getParentFolderNode_();
+        if (parentFolderNode.itemId != ROOT_NODE_ID) {
+          parentFolderNode.selectFolder_();
+          parentFolderNode.getFocusTarget().focus();
+        }
+      }
+    }
+
+    if (!yDirection)
+      return;
 
     // The current node's successor is its first child when open.
-    if (!isChildFolderNodeFocused && !reverse && !this.isClosed_) {
+    if (!isChildFolderNodeFocused && yDirection == 1 && !this.isClosed_) {
       var children = this.getChildFolderNodes_();
       if (children.length)
         newFocusFolderNode = children[0];
@@ -126,19 +161,20 @@
       // Get the next child folder node if a child is focused.
       if (!newFocusFolderNode) {
         newFocusFolderNode = this.getNextChild_(
-            reverse,
+            yDirection == -1,
             /** @type {!BookmarksFolderNodeElement} */ (currentFocus));
       }
 
       // The first child's predecessor is this node.
-      if (!newFocusFolderNode && reverse)
+      if (!newFocusFolderNode && yDirection == -1)
         newFocusFolderNode = this;
     }
 
     // If there is no newly focused node, allow the parent to handle the change.
     if (!newFocusFolderNode) {
       if (this.itemId != ROOT_NODE_ID)
-        this.getParentFolderNode_().changeKeyboardSelection_(direction, this);
+        this.getParentFolderNode_().changeKeyboardSelection_(
+            0, yDirection, this);
 
       return;
     }
diff --git a/chrome/test/data/webui/md_bookmarks/md_bookmarks_focus_test.js b/chrome/test/data/webui/md_bookmarks/md_bookmarks_focus_test.js
index e70c156..e1ca42b 100644
--- a/chrome/test/data/webui/md_bookmarks/md_bookmarks_focus_test.js
+++ b/chrome/test/data/webui/md_bookmarks/md_bookmarks_focus_test.js
@@ -97,6 +97,12 @@
       keydown('1', 'ArrowDown');
 
       assertDeepEquals(bookmarks.actions.selectFolder('2'), store.lastAction);
+      store.data.selectedFolder = '2';
+      store.notifyObservers();
+
+      assertEquals('', getFolderNode('1').$.container.getAttribute('tabindex'));
+      assertEquals(
+          '0', getFolderNode('2').$.container.getAttribute('tabindex'));
       assertFocused('1', '2');
 
       // Move down past closed folders.
@@ -124,6 +130,54 @@
       keydown('1', 'ArrowUp');
       assertDeepEquals(null, store.lastAction);
     });
+
+    test('keyboard left/right', function() {
+      store.data.closedFolders = new Set('2');
+      store.notifyObservers();
+
+      // Give keyboard focus to the first item.
+      getFolderNode('1').$.container.focus();
+
+      // Pressing right descends into first child.
+      keydown('1', 'ArrowRight');
+      assertDeepEquals(bookmarks.actions.selectFolder('2'), store.lastAction);
+
+      // Pressing right on a closed folder opens that folder
+      keydown('2', 'ArrowRight');
+      assertDeepEquals(
+          bookmarks.actions.changeFolderOpen('2', true), store.lastAction);
+
+      // Pressing right again descends into first child.
+      keydown('2', 'ArrowRight');
+      assertDeepEquals(bookmarks.actions.selectFolder('3'), store.lastAction);
+
+      // Pressing right on a folder with no children does nothing.
+      store.resetLastAction();
+      keydown('3', 'ArrowRight');
+      assertDeepEquals(null, store.lastAction);
+
+      // Pressing left on a folder with no children ascends to parent.
+      keydown('3', 'ArrowDown');
+      keydown('4', 'ArrowLeft');
+      assertDeepEquals(bookmarks.actions.selectFolder('2'), store.lastAction);
+
+      // Pressing left again closes the parent.
+      keydown('2', 'ArrowLeft');
+      assertDeepEquals(
+          bookmarks.actions.changeFolderOpen('2', false), store.lastAction);
+
+      // RTL flips left and right.
+      document.body.style.direction = 'rtl';
+      keydown('2', 'ArrowLeft');
+      assertDeepEquals(
+          bookmarks.actions.changeFolderOpen('2', true), store.lastAction);
+
+      keydown('2', 'ArrowRight');
+      assertDeepEquals(
+          bookmarks.actions.changeFolderOpen('2', false), store.lastAction);
+
+      document.body.style.direction = 'ltr';
+    });
   });
 
   mocha.run();