Bug 1532291, bail out early when trying to find any focusable element in shadow tree, r=masayuki
authorOlli Pettay <Olli.Pettay@helsinki.fi>
Wed, 13 Mar 2019 15:41:02 +0200
changeset 521967 0cc6396199a8508d711e080bee5cd16029748f29
parent 521966 abe66ccddd970628f025780e1e5d0adc0b06e018
child 521968 56553ce763245cc4d1d1077b653fc556e891e6b8
push id10870
push usernbeleuzu@mozilla.com
push dateFri, 15 Mar 2019 20:00:07 +0000
treeherdermozilla-beta@c594aee5b7a4 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmasayuki
bugs1532291
milestone67.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 1532291, bail out early when trying to find any focusable element in shadow tree, r=masayuki If aIgnoreTabIndex is true, we're just trying to find any focusable element, doesn't matter which tabindex it has. So better to bail out early from the deeply nested GetNextTabbableContentInScope to avoid exponential number of calls. Differential Revision: https://phabricator.services.mozilla.com/D23312
dom/base/nsFocusManager.cpp
dom/base/test/file_bug1453693.html
--- a/dom/base/nsFocusManager.cpp
+++ b/dom/base/nsFocusManager.cpp
@@ -3117,16 +3117,22 @@ nsIContent* nsFocusManager::GetNextTabba
     };
 
     // If already at lowest priority tab (0), end search completely.
     // A bit counterintuitive but true, tabindex order goes 1, 2, ... 32767, 0
     if (aCurrentTabIndex == (aForward ? 0 : 1)) {
       break;
     }
 
+    // We've been just trying to find some focusable element, and haven't, so
+    // bail out.
+    if (aIgnoreTabIndex) {
+      break;
+    }
+
     // Continue looking for next highest priority tabindex
     aCurrentTabIndex = GetNextTabIndex(aOwner, aCurrentTabIndex, aForward);
     contentTraversal.Reset();
   }
 
   // Return scope owner at last for backward navigation if its tabindex
   // is non-negative
   if (!aSkipOwner && !aForward) {
--- a/dom/base/test/file_bug1453693.html
+++ b/dom/base/test/file_bug1453693.html
@@ -1,15 +1,26 @@
 <html>
   <head>
     <title>Test for Bug 1453693</title>
     <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
     <script type="application/javascript" src="/tests/SimpleTest/EventUtils.js"></script>
     <script>
 
+      class TestNode extends HTMLElement {
+      constructor() {
+        super();
+        const styles = "<style>:focus{background-color:yellow;}</style>";
+        this.attachShadow({ mode: 'open' });
+        this.shadowRoot.innerHTML =
+          `${styles}<div tabindex='-1'>test node</div> <slot></slot>`;
+      }}
+
+      window.customElements.define('test-node', TestNode);
+
       var lastFocusTarget;
       function focusLogger(event) {
         lastFocusTarget = event.target;
         console.log(event.target + " under " + event.target.parentNode);
         event.stopPropagation();
       }
 
       function testTabbingThroughShadowDOMWithTabIndexes() {
@@ -769,29 +780,76 @@
         synthesizeKey("KEY_Tab", {shiftKey: true});
         opener.is(document.activeElement, document.body.firstChild,
                   "body's first child should have focus.");
 
         host0.remove();
         input1.remove();
       }
 
+      function testDeeplyNestedShadowTree() {
+        opener.is(document.activeElement, document.body.firstChild, "body's first child should have focus.");
+        var host1 = document.createElement("test-node");
+        var lastHost = host1;
+        for (var i = 0; i < 20; ++i) {
+          lastHost.appendChild(document.createElement("test-node"));
+          lastHost = lastHost.firstChild;
+        }
+
+        var input = document.createElement("input");
+        document.body.appendChild(host1);
+        document.body.appendChild(input);
+        document.body.offsetLeft;
+
+        // Test shadow tree which doesn't have anything tab-focusable.
+        host1.shadowRoot.getElementsByTagName("div")[0].focus();
+        synthesizeKey("KEY_Tab");
+        is(document.activeElement, input, "Should have focused input element.");
+        synthesizeKey("KEY_Tab", {shiftKey: true});
+        opener.is(document.activeElement, document.body.firstChild, "body's first child should have focus.");
+
+        // Same test but with focusable elements in the tree...
+        var input2 = document.createElement("input");
+        var host2 = host1.firstChild;
+        var host3 = host2.firstChild;
+        host2.insertBefore(input2, host3);
+        var input3 = document.createElement("input");
+        lastHost.appendChild(input3);
+        document.body.offsetLeft;
+        host3.shadowRoot.getElementsByTagName("div")[0].focus();
+        synthesizeKey("KEY_Tab");
+        is(document.activeElement, input3, "Should have focused input3 element.");
+
+        // ...and backwards
+        host3.shadowRoot.getElementsByTagName("div")[0].focus();
+        synthesizeKey("KEY_Tab", {shiftKey: true});
+        is(document.activeElement, input2, "Should have focused input2 element.");
+
+        // Remove elements added to body element.
+        host1.remove();
+        input.remove();
+
+        // Tests expect body.firstChild to have focus.
+        document.body.firstChild.focus();
+      }
+
       function runTest() {
 
         testTabbingThroughShadowDOMWithTabIndexes();
         testTabbingThroughSimpleShadowDOM();
         testTabbingThroughNestedShadowDOM();
         testTabbingThroughDisplayContentsHost();
         testTabbingThroughLightDOMShadowDOMLightDOM();
         testFocusableHost();
         testShiftTabbingThroughFocusableHost();
         testTabbingThroughNestedSlot();
         testTabbingThroughSlotInLightDOM();
         testTabbingThroughFocusableSlotInLightDOM();
         testTabbingThroughScrollableShadowDOM();
+        testDeeplyNestedShadowTree();
 
         opener.didRunTests();
         window.close();
       }
 
       function init() {
         SimpleTest.waitForFocus(runTest);
       }