Bug 1512043 - Ensure traverse all nodes owned by the top level shadow host; r=smaug
authorEdgar Chen <echen@mozilla.com>
Fri, 14 Dec 2018 18:14:52 +0000
changeset 450603 166eac2f9e58007dc36694d30523001b0bb30a8b
parent 450602 dc998af2b5fdbc8a9177b3836a97de92e5f1da52
child 450604 d8a944c012a7a814ee5a5fea4c9075969451a48b
push id35207
push useraciure@mozilla.com
push dateSat, 15 Dec 2018 02:27:37 +0000
treeherdermozilla-central@d8a944c012a7 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewerssmaug
bugs1512043
milestone66.0a1
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
Bug 1512043 - Ensure traverse all nodes owned by the top level shadow host; r=smaug Differential Revision: https://phabricator.services.mozilla.com/D14143
dom/base/nsFocusManager.cpp
dom/base/test/file_bug1453693.html
--- a/dom/base/nsFocusManager.cpp
+++ b/dom/base/nsFocusManager.cpp
@@ -3122,45 +3122,42 @@ nsIContent* nsFocusManager::GetNextTabba
   return nullptr;
 }
 
 nsIContent* nsFocusManager::GetNextTabbableContentInAncestorScopes(
     nsIContent** aStartContent, nsIContent* aOriginalStartContent,
     bool aForward, int32_t* aCurrentTabIndex, bool aIgnoreTabIndex,
     bool aForDocumentNavigation) {
   nsIContent* startContent = *aStartContent;
-  while (1) {
-    nsIContent* owner = FindOwner(startContent);
-    MOZ_ASSERT(owner, "focus navigation scope owner not in document");
-
+  nsIContent* owner = FindOwner(startContent);
+  MOZ_ASSERT(owner, "focus navigation scope owner not in document");
+  MOZ_ASSERT(IsHostOrSlot(owner), "scope owner should be host or slot");
+
+  while (IsHostOrSlot(owner)) {
     int32_t tabIndex = 0;
     if (IsHostOrSlot(startContent)) {
       tabIndex = HostOrSlotTabIndexValue(startContent);
     } else {
       startContent->IsFocusable(&tabIndex);
     }
     nsIContent* contentToFocus = GetNextTabbableContentInScope(
         owner, startContent, aOriginalStartContent, aForward, tabIndex,
         aIgnoreTabIndex, aForDocumentNavigation, false /* aSkipOwner */);
     if (contentToFocus) {
       return contentToFocus;
     }
 
-    // If not found in shadow DOM, search from the shadow host in light DOM
-    if (!owner->IsInShadowTree()) {
-      MOZ_ASSERT(owner->GetShadowRoot());
-
-      *aStartContent = owner;
-      *aCurrentTabIndex = HostOrSlotTabIndexValue(owner);
-      break;
-    }
-
     startContent = owner;
+    owner = FindOwner(startContent);
   }
 
+  // If not found in shadow DOM, search from the top level shadow host in light DOM
+  *aStartContent = startContent;
+  *aCurrentTabIndex = HostOrSlotTabIndexValue(startContent);
+
   return nullptr;
 }
 
 static nsIContent* GetTopLevelHost(nsIContent* aContent) {
   nsIContent* topLevelhost = nullptr;
   while (aContent) {
     if (HTMLSlotElement* slot = aContent->GetAssignedSlot()) {
       aContent = slot;
@@ -3214,19 +3211,21 @@ nsresult nsFocusManager::GetNextTabbable
         aIgnoreTabIndex, aForDocumentNavigation);
     if (contentToFocus) {
       NS_ADDREF(*aResultContent = contentToFocus);
       return NS_OK;
     }
   }
 
   // If we reach here, it means no next tabbable content in shadow DOM.
-  // We need to continue searching in light DOM, starting at the shadow host
-  // in light DOM (updated aStartContent) and its tabindex
+  // We need to continue searching in light DOM, starting at the top level
+  // shadow host in light DOM (updated aStartContent) and its tabindex
   // (updated aCurrentTabIndex).
+  MOZ_ASSERT(FindOwner(aStartContent) == rootElement,
+             "aStartContent should be owned by the root element at this point");
 
   nsPresContext* presContext = aPresShell->GetPresContext();
 
   bool getNextFrame = true;
   nsCOMPtr<nsIContent> iterStartContent = aStartContent;
   while (1) {
     nsIFrame* startFrame = iterStartContent->GetPrimaryFrame();
     // if there is no frame, look for another content node that has a frame
--- a/dom/base/test/file_bug1453693.html
+++ b/dom/base/test/file_bug1453693.html
@@ -415,25 +415,113 @@
         synthesizeKey("KEY_Tab", {shiftKey: true});
         opener.is(document.activeElement, document.body.firstChild,
                   "body's first child should have focus.");
 
         host.remove();
         input.remove();
       }
 
+      function testTabbingThroughNestedSlot() {
+        opener.is(document.activeElement, document.body.firstChild, "body's first child should have focus.");
+
+        var host0 = document.createElement("div");
+        var sr0 = host0.attachShadow({mode: "open"});
+        sr0.innerHTML = "<slot></slot>";
+        document.body.appendChild(host0);
+
+        // focusable
+        var host00 = document.createElement("div");
+        var sr00 = host00.attachShadow({mode: "open"});
+        var div00 = document.createElement("div");
+        div00.tabIndex = 0;
+        div00.onfocus = focusLogger;
+        sr00.appendChild(div00);
+        host0.appendChild(host00);
+
+        // not focusable
+        var host01 = document.createElement("div");
+        var sr01 = host01.attachShadow({mode: "open"});
+        sr01.innerHTML = "<div></div>";
+        host0.appendChild(host01);
+
+        // focusable
+        var host02 = document.createElement("div");
+        var sr02 = host02.attachShadow({mode: "open"});
+        var div02 = document.createElement("div");
+        div02.tabIndex = 0;
+        div02.onfocus = focusLogger;
+        sr02.appendChild(div02);
+        host0.appendChild(host02);
+
+        var host1 = document.createElement("div");
+        var sr1 = host1.attachShadow({mode: "open"});
+        sr1.innerHTML = "<slot></slot>";
+        document.body.appendChild(host1);
+
+        var host10 = document.createElement("div");
+        var sr10 = host10.attachShadow({mode: "open"});
+        sr10.innerHTML = "<slot></slot>";
+        host1.appendChild(host10);
+
+        var input10 = document.createElement("input");
+        input10.onfocus = focusLogger;
+        host10.appendChild(input10);
+
+        var host11 = document.createElement("div");
+        var sr11 = host11.attachShadow({mode: "open"});
+        sr11.innerHTML = "<slot></slot>";
+        host1.appendChild(host11);
+
+        var input11 = document.createElement("input");
+        input11.onfocus = focusLogger;
+        host11.appendChild(input11);
+
+        document.body.offsetLeft;
+
+        synthesizeKey("KEY_Tab");
+        opener.is(lastFocusTarget, div00, "Should have focused div element in shadow DOM. (1)");
+
+        synthesizeKey("KEY_Tab");
+        opener.is(lastFocusTarget, div02, "Should have focused div element in shadow DOM. (2)");
+
+        synthesizeKey("KEY_Tab");
+        opener.is(lastFocusTarget, input10, "Should have focused input element in shadow DOM. (3)");
+
+        synthesizeKey("KEY_Tab");
+        opener.is(lastFocusTarget, input11, "Should have focused button element in shadow DOM. (4)");
+
+        // Backwards
+        synthesizeKey("KEY_Tab", {shiftKey: true});
+        opener.is(lastFocusTarget, input10, "Should have focused input element in shadow DOM. (5)");
+
+        synthesizeKey("KEY_Tab", {shiftKey: true});
+        opener.is(lastFocusTarget, div02, "Should have focused input element in shadow DOM. (6)");
+
+        synthesizeKey("KEY_Tab", {shiftKey: true});
+        opener.is(lastFocusTarget, div00, "Should have focused input element in shadow DOM. (7)");
+
+        synthesizeKey("KEY_Tab", {shiftKey: true});
+        opener.is(document.activeElement, document.body.firstChild,
+                  "body's first child should have focus.");
+
+        host0.remove();
+        host1.remove();
+      }
+
       function runTest() {
 
         testTabbingThroughShadowDOMWithTabIndexes();
         testTabbingThroughSimpleShadowDOM();
         testTabbingThroughNestedShadowDOM();
         testTabbingThroughDisplayContentsHost();
         testTabbingThroughLightDOMShadowDOMLightDOM();
         testFocusableHost();
         testShiftTabbingThroughFocusableHost();
+        testTabbingThroughNestedSlot();
 
         opener.didRunTests();
         window.close();
       }
 
       function init() {
         SimpleTest.waitForFocus(runTest);
       }