bug 1481079, Shadow DOM hosts should be focusable, r=mrbkap
authorOlli Pettay <Olli.Pettay@helsinki.fi>
Sat, 11 Aug 2018 02:58:23 +0300
changeset 486139 141dc7ae832e94ecd0c3b38a6a8ff04159b9bdf8
parent 486138 b4b17e9deb17e35ac6420741532fb78447171741
child 486140 919250fdd7c2b335346f28ff841f5febdffb44e1
push id9719
push userffxbld-merge
push dateFri, 24 Aug 2018 17:49:46 +0000
treeherdermozilla-beta@719ec98fba77 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmrbkap
bugs1481079
milestone63.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 1481079, Shadow DOM hosts should be focusable, r=mrbkap
dom/base/nsFocusManager.cpp
dom/base/nsFocusManager.h
dom/base/test/file_bug1453693.html
--- a/dom/base/nsFocusManager.cpp
+++ b/dom/base/nsFocusManager.cpp
@@ -3202,20 +3202,31 @@ nsFocusManager::FindOwner(nsIContent* aC
 bool
 nsFocusManager::IsHostOrSlot(nsIContent* aContent)
 {
   return aContent->GetShadowRoot() || // shadow host
          aContent->IsHTMLElement(nsGkAtoms::slot); // slot
 }
 
 int32_t
-nsFocusManager::HostOrSlotTabIndexValue(nsIContent* aContent)
+nsFocusManager::HostOrSlotTabIndexValue(nsIContent* aContent,
+                                        bool* aIsFocusable)
 {
   MOZ_ASSERT(IsHostOrSlot(aContent));
 
+  if (aIsFocusable) {
+    *aIsFocusable = false;
+    nsIFrame* frame = aContent->GetPrimaryFrame();
+    if (frame) {
+      int32_t tabIndex;
+      frame->IsFocusable(&tabIndex, 0);
+      *aIsFocusable = tabIndex >= 0;
+    }
+  }
+
   const nsAttrValue* attrVal =
     aContent->AsElement()->GetParsedAttr(nsGkAtoms::tabindex);
   if (!attrVal) {
     return 0;
   }
 
   if (attrVal->Type() == nsAttrValue::eInteger) {
     return attrVal->GetIntegerValue();
@@ -3668,18 +3679,22 @@ nsFocusManager::GetNextTabbableContent(n
       // in the obsolete Shadow DOM specification.
       // http://w3c.github.io/webcomponents/spec/shadow/#sequential-focus-navigation
       // "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 && nsDocument::IsShadowDOMEnabled(currentContent) &&
           IsHostOrSlot(currentContent)) {
-        int32_t tabIndex = HostOrSlotTabIndexValue(currentContent);
-        if (tabIndex >= 0 &&
+        bool focusableHostSlot;
+        int32_t tabIndex = HostOrSlotTabIndexValue(currentContent,
+                                                   &focusableHostSlot);
+        // Host or slot itself isn't focusable, enter its scope.
+        if (!focusableHostSlot &&
+            tabIndex >= 0 &&
             (aIgnoreTabIndex || aCurrentTabIndex == tabIndex)) {
           nsIContent* contentToFocus =
             GetNextTabbableContentInScope(currentContent, currentContent,
                                           aOriginalStartContent, aForward,
                                           aForward ? 1 : 0, aIgnoreTabIndex,
                                           aForDocumentNavigation,
                                           true /* aSkipOwner */);
           if (contentToFocus) {
--- a/dom/base/nsFocusManager.h
+++ b/dom/base/nsFocusManager.h
@@ -448,18 +448,21 @@ protected:
    * Returns true if aContent is a shadow host or slot
    */
   bool IsHostOrSlot(nsIContent* aContent);
 
   /**
    * Host and Slot elements need to be handled as if they had tabindex 0 even
    * when they don't have the attribute. This is a helper method to get the right
    * value for focus navigation.
+   * If aIsFocusable is passed, it is set to true if the element itself is
+   * focusable.
    */
-  int32_t HostOrSlotTabIndexValue(nsIContent* aContent);
+  int32_t HostOrSlotTabIndexValue(nsIContent* aContent,
+                                  bool* aIsFocusable = nullptr);
 
   /**
    * Retrieve the next tabbable element in scope owned by aOwner, using
    * focusability and tabindex to determine the tab order.
    *
    * aOwner is the owner of scope to search in.
    *
    * aStartContent is the starting point for this call of this method.
--- a/dom/base/test/file_bug1453693.html
+++ b/dom/base/test/file_bug1453693.html
@@ -301,23 +301,74 @@
         synthesizeKey("KEY_Tab", {shiftKey: true});
         opener.is(document.activeElement, document.body.firstChild,
                   "body's first child should have focus.");
 
         host.remove();
         p.remove();
       }
 
+      function testFocusableHost() {
+        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 slotted = document.createElement("div");
+        slotted.tabIndex = 0;
+        slotted.onfocus = focusLogger;
+        host.appendChild(slotted);
+
+        var sr0 = host.attachShadow({mode: "open"});
+        sr0.appendChild(document.createElement("slot"));
+
+        var p = document.createElement("p");
+        p.innerHTML = " <a href='#p'>link 1</a> ";
+        var a = p.firstElementChild;
+        a.onfocus = focusLogger;
+        document.body.appendChild(p);
+
+        document.body.offsetLeft;
+
+        synthesizeKey("KEY_Tab");
+        opener.is(lastFocusTarget, host, "Should have focused host.");
+
+        synthesizeKey("KEY_Tab");
+        opener.is(lastFocusTarget, slotted, "Should have focused slotted.");
+
+        synthesizeKey("KEY_Tab");
+        opener.is(lastFocusTarget, a, "Should have focused a.");
+
+        // Backwards
+        synthesizeKey("KEY_Tab", {shiftKey: true});
+        opener.is(lastFocusTarget, slotted, "Should have focused slotted.");
+
+        synthesizeKey("KEY_Tab", {shiftKey: true});
+        opener.is(lastFocusTarget, host, "Should have focused host.");
+
+        synthesizeKey("KEY_Tab", {shiftKey: true});
+        opener.is(document.activeElement, document.body.firstChild,
+                  "body's first child should have focus.");
+
+        host.remove();
+        p.remove();
+      }
+
       function runTest() {
 
         testTabbingThroughShadowDOMWithTabIndexes();
         testTabbingThroughSimpleShadowDOM();
         testTabbingThroughNestedShadowDOM();
         testTabbingThroughDisplayContentsHost();
         testTabbingThroughLightDOMShadowDOMLightDOM();
+        testFocusableHost();
 
         opener.didRunTests();
         window.close();
       }
 
       function init() {
         SimpleTest.waitForFocus(runTest);
       }