Bug 1500273 - Ensure backward focus navigation works in Shadow DOM. r=smaug, a=RyanVM
authorEdgar Chen <echen@mozilla.com>
Tue, 11 Dec 2018 20:43:51 +0000
changeset 509089 a931bf550f3399a4fd3e91edec59676b75b5d6ff
parent 509088 ef02c82614389826dd6e9246a0b965afacf68168
child 509090 2fe74b55dfcc2a1837af0f5c45b2ce1efd3f97e2
push id1905
push userffxbld-merge
push dateMon, 21 Jan 2019 12:33:13 +0000
treeherdermozilla-release@c2fca1944d8c [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewerssmaug, RyanVM
bugs1500273
milestone65.0
Bug 1500273 - Ensure backward focus navigation works in Shadow DOM. r=smaug, a=RyanVM When entering into a Shadow DOM in backward navigation, the frame still points to the last frame in Shadow DOM which could be a non-focusable frame, so move the handling of backward navigation for Shadow DOM upward. Differential Revision: https://phabricator.services.mozilla.com/D14069
dom/base/nsFocusManager.cpp
dom/base/test/file_bug1453693.html
--- a/dom/base/nsFocusManager.cpp
+++ b/dom/base/nsFocusManager.cpp
@@ -3358,18 +3358,18 @@ nsresult nsFocusManager::GetNextTabbable
       // "if ELEMENT is focusable, a shadow host, or a slot element,
       //  append ELEMENT to NAVIGATION-ORDER."
       // and later in "For each element ELEMENT in NAVIGATION-ORDER: "
       // hosts and slots are handled before other elements.
       if (currentContent && IsHostOrSlot(currentContent)) {
         bool focusableHostSlot;
         int32_t tabIndex =
             HostOrSlotTabIndexValue(currentContent, &focusableHostSlot);
-        // Host or slot itself isn't focusable, enter its scope.
-        if (!focusableHostSlot && tabIndex >= 0 &&
+        // Host or slot itself isn't focusable or going backwards, enter its scope.
+        if ((!aForward || !focusableHostSlot) && tabIndex >= 0 &&
             (aIgnoreTabIndex || aCurrentTabIndex == tabIndex)) {
           nsIContent* contentToFocus = GetNextTabbableContentInScope(
               currentContent, currentContent, aOriginalStartContent, aForward,
               aForward ? 1 : 0, aIgnoreTabIndex, aForDocumentNavigation,
               true /* aSkipOwner */);
           if (contentToFocus) {
             NS_ADDREF(*aResultContent = contentToFocus);
             return NS_OK;
@@ -3455,29 +3455,16 @@ nsresult nsFocusManager::GetNextTabbable
             // popup focusable.
             // Also, when going backwards, check to ensure that the focus
             // wouldn't be redirected. Otherwise, for example, when an input in
             // a textbox is focused, the enclosing textbox would be found and
             // the same inner input would be returned again.
             else if (currentContent == aRootContent ||
                      (currentContent != startContent &&
                       (aForward || !GetRedirectedFocus(currentContent)))) {
-              // If currentContent is a shadow host in backward
-              // navigation, search in scope owned by currentContent
-              if (!aForward && currentContent->GetShadowRoot()) {
-                nsIContent* contentToFocus = GetNextTabbableContentInScope(
-                    currentContent, currentContent, aOriginalStartContent,
-                    aForward, aForward ? 1 : 0, aIgnoreTabIndex,
-                    aForDocumentNavigation, true /* aSkipOwner */);
-                if (contentToFocus) {
-                  NS_ADDREF(*aResultContent = contentToFocus);
-                  return NS_OK;
-                }
-              }
-
               NS_ADDREF(*aResultContent = currentContent);
               return NS_OK;
             }
           }
         }
       } else if (aOriginalStartContent &&
                  currentContent == aOriginalStartContent) {
         // not focusable, so return if we have wrapped around to the original
--- a/dom/base/test/file_bug1453693.html
+++ b/dom/base/test/file_bug1453693.html
@@ -4,16 +4,17 @@
     <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
     <script type="application/javascript" src="/tests/SimpleTest/EventUtils.js"></script>
     <script>
 
       var lastFocusTarget;
       function focusLogger(event) {
         lastFocusTarget = event.target;
         console.log(event.target + " under " + event.target.parentNode);
+        event.stopPropagation();
       }
 
       function testTabbingThroughShadowDOMWithTabIndexes() {
         var anchor = document.createElement("a");
         anchor.onfocus = focusLogger;
         anchor.href = "#";
         anchor.textContent = "in light DOM";
         document.body.appendChild(anchor);
@@ -355,24 +356,84 @@
         synthesizeKey("KEY_Tab", {shiftKey: true});
         opener.is(document.activeElement, document.body.firstChild,
                   "body's first child should have focus.");
 
         host.remove();
         p.remove();
       }
 
+      function testShiftTabbingThroughFocusableHost() {
+        opener.is(document.activeElement, document.body.firstChild,
+                  "body's first child should have focus.");
+
+        var host = document.createElement("div");
+        host.id = "host";
+        host.tabIndex = 0;
+        host.onfocus = focusLogger;
+        document.body.appendChild(host);
+
+        var sr = host.attachShadow({mode: "open"});
+        var shadowButton = document.createElement("button");
+        shadowButton.innerText = "X";
+        shadowButton.onfocus = focusLogger;
+        sr.appendChild(shadowButton);
+
+        var shadowInput = document.createElement("input");
+        shadowInput.onfocus = focusLogger;
+        sr.appendChild(shadowInput);
+        sr.appendChild(document.createElement("br"));
+
+        var input = document.createElement("input");
+        input.onfocus = focusLogger;
+        document.body.appendChild(input);
+
+        document.body.offsetLeft;
+
+        synthesizeKey("KEY_Tab");
+        opener.is(lastFocusTarget, host, "Should have focused host element. (1)");
+
+        synthesizeKey("KEY_Tab");
+        opener.is(lastFocusTarget, shadowButton, "Should have focused button element in shadow DOM. (2)");
+
+        synthesizeKey("KEY_Tab");
+        opener.is(lastFocusTarget, shadowInput, "Should have focused input element in shadow DOM. (3)");
+
+        synthesizeKey("KEY_Tab");
+        opener.is(lastFocusTarget, input, "Should have focused input element. (4)");
+
+        // Backwards
+        synthesizeKey("KEY_Tab", {shiftKey: true});
+        opener.is(lastFocusTarget, shadowInput, "Should have focused input element in shadow DOM. (5)");
+
+        synthesizeKey("KEY_Tab", {shiftKey: true});
+        opener.is(lastFocusTarget, shadowButton, "Should have focused button element in shadow DOM. (6)");
+
+        synthesizeKey("KEY_Tab", {shiftKey: true});
+        // focus is already on host
+        opener.is(sr.activeElement, null,
+                  "Focus should have left button element in shadow DOM. (7)");
+
+        synthesizeKey("KEY_Tab", {shiftKey: true});
+        opener.is(document.activeElement, document.body.firstChild,
+                  "body's first child should have focus.");
+
+        host.remove();
+        input.remove();
+      }
+
       function runTest() {
 
         testTabbingThroughShadowDOMWithTabIndexes();
         testTabbingThroughSimpleShadowDOM();
         testTabbingThroughNestedShadowDOM();
         testTabbingThroughDisplayContentsHost();
         testTabbingThroughLightDOMShadowDOMLightDOM();
         testFocusableHost();
+        testShiftTabbingThroughFocusableHost();
 
         opener.didRunTests();
         window.close();
       }
 
       function init() {
         SimpleTest.waitForFocus(runTest);
       }