Backed out 2 changesets (bug 1477673, bug 1454865) for browser chrome failures on browser_PanelMultiView_keyboard. CLOSED TREE
authorCosmin Sabou <csabou@mozilla.com>
Tue, 09 Apr 2019 14:29:08 +0300
changeset 468549 a8e6fd651ef20b6940782870c2764a66949a542e
parent 468548 ae6b824bbe75f21fba58d990de702d25799ce200
child 468550 5a1c8172a7037369da43235f4c539c086e10747d
push id112733
push usercsabou@mozilla.com
push dateTue, 09 Apr 2019 16:30:22 +0000
treeherdermozilla-inbound@e14dba56bbfd [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
bugs1477673, 1454865
milestone68.0a1
backs outeca8a6e641c0248dc064050b2767d87aa9f1b4c6
69db665d8263b26b401fdfad83b235feba1592d7
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Backed out 2 changesets (bug 1477673, bug 1454865) for browser chrome failures on browser_PanelMultiView_keyboard. CLOSED TREE Backed out changeset eca8a6e641c0 (bug 1454865) Backed out changeset 69db665d8263 (bug 1477673)
browser/components/controlcenter/content/panel.inc.xul
browser/components/customizableui/PanelMultiView.jsm
browser/components/customizableui/content/panelUI.inc.xul
browser/components/customizableui/test/browser.ini
browser/components/customizableui/test/browser_PanelMultiView_focus.js
browser/components/customizableui/test/browser_PanelMultiView_keyboard.js
--- a/browser/components/controlcenter/content/panel.inc.xul
+++ b/browser/components/controlcenter/content/panel.inc.xul
@@ -8,17 +8,18 @@
        photon="true"
        role="alertdialog"
        aria-labelledby="identity-popup-mainView-panel-header-span"
        onpopupshown="gIdentityHandler.onPopupShown(event);"
        onpopuphidden="gIdentityHandler.onPopupHidden(event);"
        orient="vertical">
 
   <panelmultiview id="identity-popup-multiView"
-                  mainViewId="identity-popup-mainView">
+                  mainViewId="identity-popup-mainView"
+                  disablekeynav="true">
     <panelview id="identity-popup-mainView"
                descriptionheightworkaround="true">
       <vbox id="identity-popup-mainView-panel-header">
         <label>
           <html:span id="identity-popup-mainView-panel-header-span"/>
         </label>
       </vbox>
 
--- a/browser/components/customizableui/PanelMultiView.jsm
+++ b/browser/components/customizableui/PanelMultiView.jsm
@@ -639,20 +639,16 @@ var PanelMultiView = class extends Assoc
 
     // Do not re-enter the process if navigation is already in progress. Since
     // there is only one active view at any given time, we can do this check
     // safely, even considering that during the navigation process the actual
     // view to which prevPanelView refers will change.
     if (!prevPanelView.active) {
       return;
     }
-    // If prevPanelView._doingKeyboardActivation is true, it will be reset to
-    // false synchronously. Therefore, we must capture it before we use any
-    // "await" statements.
-    let doingKeyboardActivation = prevPanelView._doingKeyboardActivation;
     // Marking the view that is about to scrolled out of the visible area as
     // inactive will prevent re-entrancy and also disable keyboard navigation.
     // From this point onwards, "await" statements can be used safely.
     prevPanelView.active = false;
 
     // Provide visual feedback while navigation is in progress, starting before
     // the transition starts and ending when the previous view is invisible.
     if (anchor) {
@@ -691,17 +687,16 @@ var PanelMultiView = class extends Assoc
 
       await this._transitionViews(prevPanelView.node, viewNode, false, anchor);
     } finally {
       if (anchor) {
         anchor.removeAttribute("open");
       }
     }
 
-    nextPanelView.focusWhenActive = doingKeyboardActivation;
     this._activateView(nextPanelView);
   }
 
   /**
    * Navigates backwards by sliding out the most recent subview.
    */
   goBack() {
     this._goBack().catch(Cu.reportError);
@@ -814,17 +809,17 @@ var PanelMultiView = class extends Assoc
   /**
    * Activates the specified view and raises the ViewShown event, unless the
    * view was closed in the meantime.
    */
   _activateView(panelView) {
     if (panelView.isOpenIn(this)) {
       panelView.active = true;
       if (panelView.focusWhenActive) {
-        panelView.focusFirstNavigableElement(false, true);
+        panelView.focusFirstNavigableElement();
         panelView.focusWhenActive = false;
       }
       panelView.dispatchCustomEvent("ViewShown");
 
       // Re-enable panel autopositioning.
       this._panel.autoPosition = true;
     }
   }
@@ -1400,152 +1395,130 @@ var PanelView = class extends Associated
     // Now we can make all the necessary DOM changes at once.
     for (let { element, bounds } of items) {
       gMultiLineElementsMap.set(element, { bounds, textContent: element.textContent });
       element.style.height = bounds.height + "px";
     }
   }
 
   /**
-   * Determine whether an element can only be navigated to with tab/shift+tab,
-   * not the arrow keys.
-   */
-  _isNavigableWithTabOnly(element) {
-    let tag = element.localName;
-    return tag == "menulist" || tag == "textbox" || tag == "input"
-           || tag == "textarea";
-  }
-
-  /**
-   * Make a TreeWalker for keyboard navigation.
+   * Array of enabled elements that can be selected with the keyboard. This
+   * means all buttons, menulists, and text links including the back button.
    *
-   * @param {Boolean} arrowKey If `true`, elements only navigable with tab are
-   *        excluded.
+   * This list is cached until the view is closed, so elements that become
+   * enabled later may not be navigable.
    */
-  _makeNavigableTreeWalker(arrowKey) {
-    let filter = node => {
-      if (node.disabled) {
-        return NodeFilter.FILTER_REJECT;
-      }
-      let bounds = this._getBoundsWithoutFlushing(node);
-      if (bounds.width == 0 || bounds.height == 0) {
-        return NodeFilter.FILTER_REJECT;
+  get _navigableElements() {
+    if (this.__navigableElements) {
+      return this.__navigableElements;
+    }
+
+    let navigableElements = Array.from(this.node.querySelectorAll(
+      ":-moz-any(button,toolbarbutton,menulist,.text-link,.navigable):not([disabled])"));
+    return this.__navigableElements = navigableElements.filter(element => {
+      // Set the "tabindex" attribute to make sure the element is focusable.
+      if (!element.hasAttribute("tabindex")) {
+        element.setAttribute("tabindex", "0");
       }
-      if (node.tagName == "button" || node.tagName == "toolbarbutton" ||
-          node.classList.contains("text-link") ||
-          node.classList.contains("navigable") ||
-          (!arrowKey && this._isNavigableWithTabOnly(node))) {
-        // Set the tabindex attribute to make sure the node is focusable.
-        if (!node.hasAttribute("tabindex")) {
-          node.setAttribute("tabindex", "-1");
-        }
-        return NodeFilter.FILTER_ACCEPT;
+      if (element.hasAttribute("disabled")) {
+        return false;
       }
-      return NodeFilter.FILTER_SKIP;
-    };
-    return this.document.createTreeWalker(this.node, NodeFilter.SHOW_ELEMENT,
-      filter);
-  }
-
-  /**
-   * Get a TreeWalker which finds elements navigable with tab/shift+tab.
-   */
-  get _tabNavigableWalker() {
-    if (!this.__tabNavigableWalker) {
-      this.__tabNavigableWalker = this._makeNavigableTreeWalker(false);
-    }
-    return this.__tabNavigableWalker;
-  }
-
-  /**
-   * Get a TreeWalker which finds elements navigable with up/down arrow keys.
-   */
-  get _arrowNavigableWalker() {
-    if (!this.__arrowNavigableWalker) {
-      this.__arrowNavigableWalker = this._makeNavigableTreeWalker(true);
-    }
-    return this.__arrowNavigableWalker;
+      let bounds = this._getBoundsWithoutFlushing(element);
+      return bounds.width > 0 && bounds.height > 0;
+    });
   }
 
   /**
    * Element that is currently selected with the keyboard, or null if no element
    * is selected. Since the reference is held weakly, it can become null or
    * undefined at any time.
+   *
+   * The element is usually, but not necessarily, among the _navigableElements.
    */
   get selectedElement() {
     return this._selectedElement && this._selectedElement.get();
   }
   set selectedElement(value) {
     if (!value) {
       delete this._selectedElement;
     } else {
       this._selectedElement = Cu.getWeakReference(value);
     }
   }
 
   /**
    * Focuses and moves keyboard selection to the first navigable element.
    * This is a no-op if there are no navigable elements.
-   *
-   * @param {Boolean} homeKey   `true` if this is for the home key.
-   * @param {Boolean} skipBack   `true` if the Back button should be skipped.
    */
-  focusFirstNavigableElement(homeKey = false, skipBack = false) {
-    // The home key is conceptually similar to the up/down arrow keys.
-    let walker = homeKey ?
-      this._arrowNavigableWalker : this._tabNavigableWalker;
-    walker.currentNode = walker.root;
-    this.selectedElement = walker.firstChild();
-    if (skipBack && walker.currentNode
-        && walker.currentNode.classList.contains("subviewbutton-back")
-        && walker.nextNode()) {
-      this.selectedElement = walker.currentNode;
-    }
+  focusFirstNavigableElement() {
+    this.selectedElement = this._navigableElements[0];
     this.focusSelectedElement();
   }
 
   /**
    * Focuses and moves keyboard selection to the last navigable element.
    * This is a no-op if there are no navigable elements.
-   *
-   * @param {Boolean} endKey   `true` if this is for the end key.
    */
-  focusLastNavigableElement(endKey = false) {
-    // The end key is conceptually similar to the up/down arrow keys.
-    let walker = endKey ?
-      this._arrowNavigableWalker : this._tabNavigableWalker;
-    walker.currentNode = walker.root;
-    this.selectedElement = walker.lastChild();
+  focusLastNavigableElement() {
+    this.selectedElement = this._navigableElements[this._navigableElements.length - 1];
     this.focusSelectedElement();
   }
 
   /**
    * Based on going up or down, select the previous or next focusable element.
    *
    * @param {Boolean} isDown   whether we're going down (true) or up (false).
-   * @param {Boolean} arrowKey   `true` if this is for the up/down arrow keys.
    *
    * @return {DOMNode} the element we selected.
    */
-  moveSelection(isDown, arrowKey = false) {
-    let walker = arrowKey ?
-      this._arrowNavigableWalker : this._tabNavigableWalker;
-    let oldSel = this.selectedElement;
-    let newSel;
-    if (oldSel) {
-      walker.currentNode = oldSel;
-      newSel = isDown ? walker.nextNode() : walker.previousNode();
+  moveSelection(isDown) {
+    let buttons = this._navigableElements;
+    let lastSelected = this.selectedElement;
+    let newButton = null;
+    let maxIdx = buttons.length - 1;
+    if (lastSelected) {
+      let buttonIndex = buttons.indexOf(lastSelected);
+      if (buttonIndex != -1) {
+        // Buttons may get selected whilst the panel is shown, so add an extra
+        // check here.
+        do {
+          buttonIndex = buttonIndex + (isDown ? 1 : -1);
+        } while (buttons[buttonIndex] && buttons[buttonIndex].disabled);
+        if (isDown && buttonIndex > maxIdx)
+          buttonIndex = 0;
+        else if (!isDown && buttonIndex < 0)
+          buttonIndex = maxIdx;
+        newButton = buttons[buttonIndex];
+      } else {
+        // The previously selected item is no longer selectable. Find the next item:
+        let allButtons = lastSelected.closest("panelview").getElementsByTagName("toolbarbutton");
+        let maxAllButtonIdx = allButtons.length - 1;
+        let allButtonIndex = allButtons.indexOf(lastSelected);
+        while (allButtonIndex >= 0 && allButtonIndex <= maxAllButtonIdx) {
+          allButtonIndex++;
+          // Check if the next button is in the list of focusable buttons.
+          buttonIndex = buttons.indexOf(allButtons[allButtonIndex]);
+          if (buttonIndex != -1) {
+            // If it is, just use that button if we were going down, or the previous one
+            // otherwise. If this was the first button, newButton will end up undefined,
+            // which is fine because we'll fall back to using the last button at the
+            // bottom of this method.
+            newButton = buttons[isDown ? buttonIndex : buttonIndex - 1];
+            break;
+          }
+        }
+      }
     }
+
     // If we couldn't find something, select the first or last item:
-    if (!newSel) {
-      walker.currentNode = walker.root;
-      newSel = isDown ? walker.firstChild() : walker.lastChild();
+    if (!newButton) {
+      newButton = buttons[isDown ? 0 : maxIdx];
     }
-    this.selectedElement = newSel;
-    return newSel;
+    this.selectedElement = newButton;
+    return newButton;
   }
 
   /**
    * Allow for navigating subview buttons using the arrow keys and the Enter key.
    * The Up and Down keys can be used to navigate the list up and down and the
    * Enter, Right or Left - depending on the text direction - key can be used to
    * simulate a click on the currently selected button.
    * The Right or Left key - depending on the text direction - can be used to
@@ -1560,114 +1533,77 @@ var PanelView = class extends Associated
    *
    * @param {KeyEvent} event
    */
   keyNavigation(event) {
     if (!this.active) {
       return;
     }
 
+    let buttons = this._navigableElements;
+    if (!buttons.length) {
+      return;
+    }
+
     let stop = () => {
       event.stopPropagation();
       event.preventDefault();
     };
 
-    // If the focused element is only navigable with tab, it wants the arrow
-    // keys, etc. We shouldn't handle any keys except tab and shift+tab.
-    // We make a function for this for performance reasons: we only want to
-    // check this for keys we potentially care about, not *all* keys.
-    let tabOnly = () => {
-      // We use the real focus rather than this.selectedElement because focus
-      // might have been moved without keyboard navigation (e.g. mouse click)
-      // and this.selectedElement is only updated for keyboard navigation.
-      let focus = this.document.activeElement;
-      if (!focus) {
-        return false;
-      }
-      // Make sure the focus is actually inside the panel.
-      // (It might not be if the panel was opened with the mouse.)
-      // We use Node.compareDocumentPosition because Node.contains doesn't
-      // behave as expected for anonymous content; e.g. the input inside a
-      // textbox.
-      if (!(this.node.compareDocumentPosition(focus)
-            & Node.DOCUMENT_POSITION_CONTAINED_BY)) {
-        return false;
-      }
-      return this._isNavigableWithTabOnly(focus);
-    };
-
     let keyCode = event.code;
     switch (keyCode) {
       case "ArrowDown":
       case "ArrowUp":
-        if (tabOnly()) {
-          break;
-        }
-        // Fall-through...
       case "Tab": {
         stop();
         let isDown = (keyCode == "ArrowDown") ||
                      (keyCode == "Tab" && !event.shiftKey);
-        let button = this.moveSelection(isDown, keyCode != "Tab");
+        let button = this.moveSelection(isDown);
         button.focus();
         break;
       }
       case "Home":
-        if (tabOnly()) {
-          break;
-        }
         stop();
-        this.focusFirstNavigableElement(true);
+        this.focusFirstNavigableElement();
         break;
       case "End":
-        if (tabOnly()) {
-          break;
-        }
         stop();
-        this.focusLastNavigableElement(true);
+        this.focusLastNavigableElement();
         break;
       case "ArrowLeft":
       case "ArrowRight": {
-        if (tabOnly()) {
-          break;
-        }
         stop();
         if ((!this.window.RTL_UI && keyCode == "ArrowLeft") ||
             (this.window.RTL_UI && keyCode == "ArrowRight")) {
           this.node.panelMultiView.goBack();
           break;
         }
         // If the current button is _not_ one that points to a subview, pressing
         // the arrow key shouldn't do anything.
         let button = this.selectedElement;
         if (!button || !button.classList.contains("subviewbutton-nav")) {
           break;
         }
         // Fall-through...
       }
       case "Space":
       case "Enter": {
-        if (tabOnly()) {
-          break;
-        }
         let button = this.selectedElement;
         if (!button)
           break;
         stop();
 
-        this._doingKeyboardActivation = true;
         // Unfortunately, 'tabindex' doesn't execute the default action, so
         // we explicitly do this here.
         // We are sending a command event and then a click event.
         // This is done in order to mimic a "real" mouse click event.
         // The command event executes the action, then the click event closes the menu.
         button.doCommand();
         let clickEvent = new event.target.ownerGlobal.MouseEvent("click", {"bubbles": true});
         button.dispatchEvent(clickEvent);
-        this._doingKeyboardActivation = false;
         break;
       }
     }
   }
 
   /**
    * Focus the last selected element in the view, if any.
    */
@@ -1677,15 +1613,16 @@ var PanelView = class extends Associated
       selected.focus();
     }
   }
 
   /**
    * Clear all traces of keyboard navigation happening right now.
    */
   clearNavigation() {
+    delete this.__navigableElements;
     let selected = this.selectedElement;
     if (selected) {
       selected.blur();
       this.selectedElement = null;
     }
   }
 };
--- a/browser/components/customizableui/content/panelUI.inc.xul
+++ b/browser/components/customizableui/content/panelUI.inc.xul
@@ -4,17 +4,17 @@
 
 <panel id="widget-overflow"
        role="group"
        type="arrow"
        noautofocus="true"
        position="bottomcenter topright"
        photon="true"
        hidden="true">
-  <panelmultiview mainViewId="widget-overflow-mainView">
+  <panelmultiview mainViewId="widget-overflow-mainView" disablekeynav="true">
     <panelview id="widget-overflow-mainView"
                context="toolbar-context-menu">
       <vbox class="panel-subview-body">
         <vbox id="widget-overflow-list" class="widget-overflow-list"
               overflowfortoolbar="nav-bar"/>
         <toolbarseparator id="widget-overflow-fixed-separator" hidden="true"/>
         <vbox id="widget-overflow-fixed-list" class="widget-overflow-list" hidden="true" />
       </vbox>
--- a/browser/components/customizableui/test/browser.ini
+++ b/browser/components/customizableui/test/browser.ini
@@ -169,17 +169,16 @@ tags = fullscreen
 [browser_backfwd_enabled_post_customize.js]
 [browser_check_tooltips_in_navbar.js]
 [browser_editcontrols_update.js]
 subsuite = clipboard
 [browser_customization_context_menus.js]
 [browser_newtab_button_customizemode.js]
 [browser_open_from_popup.js]
 [browser_PanelMultiView_focus.js]
-[browser_PanelMultiView_keyboard.js]
 [browser_sidebar_toggle.js]
 skip-if = verify
 [browser_tabbar_big_widgets.js]
 [browser_remote_tabs_button.js]
 skip-if = (verify && debug && (os == 'linux' || os == 'mac'))
 [browser_widget_animation.js]
 [browser_lwt_telemetry.js]
 
--- a/browser/components/customizableui/test/browser_PanelMultiView_focus.js
+++ b/browser/components/customizableui/test/browser_PanelMultiView_focus.js
@@ -6,55 +6,41 @@
 /**
  * Test the focus behavior when opening PanelViews.
  */
 
 const {PanelMultiView} = ChromeUtils.import("resource:///modules/PanelMultiView.jsm");
 
 let gAnchor;
 let gPanel;
-let gPanelMultiView;
 let gMainView;
 let gMainButton;
-let gMainSubButton;
-let gSubView;
-let gSubButton;
 
 add_task(async function setup() {
   let navBar = document.getElementById("nav-bar");
   gAnchor = document.createXULElement("toolbarbutton");
   // Must be focusable in order for key presses to work.
   gAnchor.style["-moz-user-focus"] = "normal";
   navBar.appendChild(gAnchor);
+  gPanel = document.createXULElement("panel");
+  navBar.appendChild(gPanel);
+  let panelMultiView = document.createXULElement("panelmultiview");
+  panelMultiView.setAttribute("mainViewId", "testMainView");
+  gPanel.appendChild(panelMultiView);
+  gMainView = document.createXULElement("panelview");
+  gMainView.id = "testMainView";
+  panelMultiView.appendChild(gMainView);
+  gMainButton = document.createXULElement("button");
+  gMainView.appendChild(gMainButton);
+
   let onPress = event => PanelMultiView.openPopup(gPanel, gAnchor, {
     triggerEvent: event,
   });
   gAnchor.addEventListener("keypress", onPress);
   gAnchor.addEventListener("click", onPress);
-  gPanel = document.createXULElement("panel");
-  navBar.appendChild(gPanel);
-  gPanelMultiView = document.createXULElement("panelmultiview");
-  gPanelMultiView.setAttribute("mainViewId", "testMainView");
-  gPanel.appendChild(gPanelMultiView);
-
-  gMainView = document.createXULElement("panelview");
-  gMainView.id = "testMainView";
-  gPanelMultiView.appendChild(gMainView);
-  gMainButton = document.createXULElement("button");
-  gMainView.appendChild(gMainButton);
-  gMainSubButton = document.createXULElement("button");
-  gMainView.appendChild(gMainSubButton);
-  gMainSubButton.addEventListener("command", () =>
-      gPanelMultiView.showSubView("testSubView", gMainSubButton));
-
-  gSubView = document.createXULElement("panelview");
-  gSubView.id = "testSubView";
-  gPanelMultiView.appendChild(gSubView);
-  gSubButton = document.createXULElement("button");
-  gSubView.appendChild(gSubButton);
 
   registerCleanupFunction(() => {
     gAnchor.remove();
     gPanel.remove();
   });
 });
 
 // Activate the main view by pressing a key. Focus should be moved inside.
@@ -73,61 +59,8 @@ add_task(async function testMainViewByKe
 add_task(async function testMainViewByClick() {
   await gCUITestUtils.openPanelMultiView(gPanel, gMainView,
     () => gAnchor.click());
   Assert.notEqual(document.activeElement, gMainButton,
     "Focus not on button in main view");
   await gCUITestUtils.hidePanelMultiView(gPanel,
     () => PanelMultiView.hidePopup(gPanel));
 });
-
-// Activate the subview by pressing a key. Focus should be moved to the first
-// button after the Back button.
-add_task(async function testSubViewByKeypress() {
-  await gCUITestUtils.openPanelMultiView(gPanel, gMainView,
-    () => gAnchor.click());
-  while (document.activeElement != gMainSubButton) {
-    EventUtils.synthesizeKey("KEY_Tab", {shiftKey: true});
-  }
-  let shown = BrowserTestUtils.waitForEvent(gSubView, "ViewShown");
-  EventUtils.synthesizeKey(" ");
-  await shown;
-  Assert.equal(document.activeElement, gSubButton,
-    "Focus on first button after Back button in subview");
-  await gCUITestUtils.hidePanelMultiView(gPanel,
-    () => PanelMultiView.hidePopup(gPanel));
-});
-
-// Activate the subview by clicking the mouse. Focus should not be moved
-// inside.
-add_task(async function testSubViewByClick() {
-  await gCUITestUtils.openPanelMultiView(gPanel, gMainView,
-    () => gAnchor.click());
-  let shown = BrowserTestUtils.waitForEvent(gSubView, "ViewShown");
-  gMainSubButton.click();
-  await shown;
-  let backButton = gSubView.querySelector(".subviewbutton-back");
-  Assert.notEqual(document.activeElement, backButton,
-    "Focus not on Back button in subview");
-  Assert.notEqual(document.activeElement, gSubButton,
-    "Focus not on button after Back button in subview");
-  await gCUITestUtils.hidePanelMultiView(gPanel,
-    () => PanelMultiView.hidePopup(gPanel));
-});
-
-// Test that focus is restored when going back to a previous view.
-add_task(async function testBackRestoresFocus() {
-  await gCUITestUtils.openPanelMultiView(gPanel, gMainView,
-    () => gAnchor.click());
-  while (document.activeElement != gMainSubButton) {
-    EventUtils.synthesizeKey("KEY_Tab", {shiftKey: true});
-  }
-  let shown = BrowserTestUtils.waitForEvent(gSubView, "ViewShown");
-  EventUtils.synthesizeKey(" ");
-  await shown;
-  shown = BrowserTestUtils.waitForEvent(gMainView, "ViewShown");
-  EventUtils.synthesizeKey("KEY_ArrowLeft");
-  await shown;
-  Assert.equal(document.activeElement, gMainSubButton,
-    "Focus on sub button in main view");
-  await gCUITestUtils.hidePanelMultiView(gPanel,
-    () => PanelMultiView.hidePopup(gPanel));
-});
deleted file mode 100644
--- a/browser/components/customizableui/test/browser_PanelMultiView_keyboard.js
+++ /dev/null
@@ -1,239 +0,0 @@
-/* Any copyright is dedicated to the Public Domain.
- * http://creativecommons.org/publicdomain/zero/1.0/ */
-
-"use strict";
-
-/**
- * Test the keyboard behavior of PanelViews.
- */
-
-const {PanelMultiView} = ChromeUtils.import("resource:///modules/PanelMultiView.jsm");
-
-let gAnchor;
-let gPanel;
-let gPanelMultiView;
-let gMainView;
-let gMainButton1;
-let gMainMenulist;
-let gMainTextbox;
-let gMainButton2;
-let gMainButton3;
-let gMainTabOrder;
-let gMainArrowOrder;
-let gSubView;
-let gSubButton;
-let gSubTextarea;
-
-async function openPopup() {
-  let shown = BrowserTestUtils.waitForEvent(gMainView, "ViewShown");
-  PanelMultiView.openPopup(gPanel, gAnchor, "bottomcenter topright");
-  await shown;
-}
-
-async function hidePopup() {
-  let hidden = BrowserTestUtils.waitForEvent(gPanel, "popuphidden");
-  PanelMultiView.hidePopup(gPanel);
-  await hidden;
-}
-
-async function showSubView() {
-  let shown = BrowserTestUtils.waitForEvent(gSubView, "ViewShown");
-  gPanelMultiView.showSubView(gSubView);
-  await shown;
-}
-
-async function expectFocusAfterKey(aKey, aFocus) {
-  let res = aKey.match(/^(Shift\+)?(.+)$/);
-  let shift = Boolean(res[1]);
-  let key;
-  if (res[2].length == 1) {
-    key = res[2]; // Character.
-  } else {
-    key = "KEY_" + res[2]; // Tab, ArrowRight, etc.
-  }
-  info("Waiting for focus on " + aFocus.id);
-  let focused = BrowserTestUtils.waitForEvent(aFocus, "focus");
-  EventUtils.synthesizeKey(key, {shiftKey: shift});
-  await focused;
-  ok(true, aFocus.id + " focused after " + aKey + " pressed");
-}
-
-add_task(async function setup() {
-  let navBar = document.getElementById("nav-bar");
-  gAnchor = document.createXULElement("toolbarbutton");
-  navBar.appendChild(gAnchor);
-  gPanel = document.createXULElement("panel");
-  navBar.appendChild(gPanel);
-  gPanelMultiView = document.createXULElement("panelmultiview");
-  gPanelMultiView.setAttribute("mainViewId", "testMainView");
-  gPanel.appendChild(gPanelMultiView);
-
-  gMainView = document.createXULElement("panelview");
-  gMainView.id = "testMainView";
-  gPanelMultiView.appendChild(gMainView);
-  gMainButton1 = document.createXULElement("button");
-  gMainButton1.id = "gMainButton1";
-  gMainView.appendChild(gMainButton1);
-  gMainMenulist = document.createXULElement("menulist");
-  gMainMenulist.id = "gMainMenulist";
-  gMainView.appendChild(gMainMenulist);
-  let menuPopup = document.createXULElement("menupopup");
-  gMainMenulist.appendChild(menuPopup);
-  let item = document.createXULElement("menuitem");
-  item.setAttribute("value", "1");
-  item.setAttribute("selected", "true");
-  menuPopup.appendChild(item);
-  item = document.createXULElement("menuitem");
-  item.setAttribute("value", "2");
-  menuPopup.appendChild(item);
-  gMainTextbox = document.createXULElement("textbox");
-  gMainTextbox.id = "gMainTextbox";
-  gMainView.appendChild(gMainTextbox);
-  gMainTextbox.setAttribute("value", "value");
-  gMainButton2 = document.createXULElement("button");
-  gMainButton2.id = "gMainButton2";
-  gMainView.appendChild(gMainButton2);
-  gMainButton3 = document.createXULElement("button");
-  gMainButton3.id = "gMainButton3";
-  gMainView.appendChild(gMainButton3);
-  gMainTabOrder = [gMainButton1, gMainMenulist, gMainTextbox, gMainButton2,
-                   gMainButton3];
-  gMainArrowOrder = [gMainButton1, gMainButton2, gMainButton3];
-
-  gSubView = document.createXULElement("panelview");
-  gSubView.id = "testSubView";
-  gPanelMultiView.appendChild(gSubView);
-  gSubButton = document.createXULElement("button");
-  gSubView.appendChild(gSubButton);
-  gSubTextarea = document.createElementNS("http://www.w3.org/1999/xhtml",
-                                          "textarea");
-  gSubTextarea.id = "gSubTextarea";
-  gSubView.appendChild(gSubTextarea);
-  gSubTextarea.value = "value";
-
-  registerCleanupFunction(() => {
-    gAnchor.remove();
-    gPanel.remove();
-  });
-});
-
-// Test that the tab key focuses all expected controls.
-add_task(async function testTab() {
-  await openPopup();
-  for (let elem of gMainTabOrder) {
-    await expectFocusAfterKey("Tab", elem);
-  }
-  // Wrap around.
-  await expectFocusAfterKey("Tab", gMainTabOrder[0]);
-  await hidePopup();
-});
-
-// Test that the shift+tab key focuses all expected controls.
-add_task(async function testShiftTab() {
-  await openPopup();
-  for (let i = gMainTabOrder.length - 1; i >= 0; --i) {
-    await expectFocusAfterKey("Shift+Tab", gMainTabOrder[i]);
-  }
-  // Wrap around.
-  await expectFocusAfterKey("Shift+Tab",
-                            gMainTabOrder[gMainTabOrder.length - 1]);
-  await hidePopup();
-});
-
-// Test that the down arrow key skips menulists and textboxes.
-add_task(async function testDownArrow() {
-  await openPopup();
-  for (let elem of gMainArrowOrder) {
-    await expectFocusAfterKey("ArrowDown", elem);
-  }
-  // Wrap around.
-  await expectFocusAfterKey("ArrowDown", gMainArrowOrder[0]);
-  await hidePopup();
-});
-
-// Test that the up arrow key skips menulists and textboxes.
-add_task(async function testUpArrow() {
-  await openPopup();
-  for (let i = gMainArrowOrder.length - 1; i >= 0; --i) {
-    await expectFocusAfterKey("ArrowUp", gMainArrowOrder[i]);
-  }
-  // Wrap around.
-  await expectFocusAfterKey("ArrowUp",
-                            gMainArrowOrder[gMainArrowOrder.length - 1]);
-  await hidePopup();
-});
-
-// Test that the home/end keys move to the first/last controls.
-add_task(async function testHomeEnd() {
-  await openPopup();
-  await expectFocusAfterKey("Home", gMainArrowOrder[0]);
-  await expectFocusAfterKey("End",
-                            gMainArrowOrder[gMainArrowOrder.length - 1]);
-  await hidePopup();
-});
-
-// Test that the up/down arrow keys adjust the value of menulists.
-add_task(async function testArrowsMenulist() {
-  await openPopup();
-  gMainMenulist.focus();
-  is(document.activeElement, gMainMenulist, "menulist focused");
-  is(gMainMenulist.value, "1", "menulist initial value 1");
-  EventUtils.synthesizeKey("KEY_ArrowDown");
-  is(document.activeElement, gMainMenulist,
-     "menulist still focused after ArrowDown");
-  is(gMainMenulist.value, "2", "menulist value 2 after ArrowDown");
-  EventUtils.synthesizeKey("KEY_ArrowUp");
-  is(document.activeElement, gMainMenulist,
-     "menulist still focused after ArrowUp");
-  is(gMainMenulist.value, "1", "menulist value 1 after ArrowUp");
-  await hidePopup();
-});
-
-// Test that pressing space in a textbox inserts a space (instead of trying to
-// activate the control).
-add_task(async function testSpaceTextbox() {
-  await openPopup();
-  gMainTextbox.focus();
-  EventUtils.synthesizeKey("KEY_Home");
-  EventUtils.synthesizeKey(" ");
-  is(gMainTextbox.value, " value", "Space typed into textbox");
-  gMainTextbox.value = "value";
-  await hidePopup();
-});
-
-// Tests that the left arrow key normally moves back to the previous view.
-add_task(async function testLeftArrow() {
-  await openPopup();
-  await showSubView();
-  let shown = BrowserTestUtils.waitForEvent(gMainView, "ViewShown");
-  EventUtils.synthesizeKey("KEY_ArrowLeft");
-  await shown;
-  ok("Moved to previous view after ArrowLeft");
-  await hidePopup();
-});
-
-// Tests that the left arrow key moves the caret in a textarea in a subview
-// (instead of going back to the previous view).
-add_task(async function testLeftArrowTextarea() {
-  await openPopup();
-  await showSubView();
-  gSubTextarea.focus();
-  is(document.activeElement, gSubTextarea, "textarea focused");
-  EventUtils.synthesizeKey("KEY_End");
-  is(gSubTextarea.selectionStart, 5, "selectionStart 5 after End");
-  EventUtils.synthesizeKey("KEY_ArrowLeft");
-  is(gSubTextarea.selectionStart, 4, "selectionStart 4 after ArrowLeft");
-  is(document.activeElement, gSubTextarea, "textarea still focused");
-  await hidePopup();
-});
-
-// Test navigation to a button which is initially disabled and later enabled.
-add_task(async function testDynamicButton() {
-  gMainButton2.disabled = true;
-  await openPopup();
-  await expectFocusAfterKey("ArrowDown", gMainButton1);
-  await expectFocusAfterKey("ArrowDown", gMainButton3);
-  gMainButton2.disabled = false;
-  await expectFocusAfterKey("ArrowUp", gMainButton2);
-  await hidePopup();
-});