bug 1430692, Handle focus navigation on NAC in shadow DOM, r=mrbkap
authorOlli Pettay <Olli.Pettay@helsinki.fi>
Fri, 27 Apr 2018 12:37:43 +0300
changeset 416036 4b51c5cf8035e7e18506b2e3a1a8aee66eeed5e8
parent 416035 1893ddb56c3b2c4264cae95ae53139cd769330bf
child 416037 d8c8620f0be8c16a45d8a3b6c3f47a01b2869087
push id33915
push userncsoregi@mozilla.com
push dateFri, 27 Apr 2018 21:53:44 +0000
treeherdermozilla-central@8b2c1fc3d6c3 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmrbkap
bugs1430692
milestone61.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 1430692, Handle focus navigation on NAC in shadow DOM, r=mrbkap
dom/base/nsFocusManager.cpp
dom/base/test/file_bug1453693.html
--- a/dom/base/nsFocusManager.cpp
+++ b/dom/base/nsFocusManager.cpp
@@ -3256,34 +3256,113 @@ nsFocusManager::GetNextTabbableContentIn
     }
   }
 
   //
   // Iterate contents in scope
   //
   ScopedContentTraversal contentTraversal(aStartContent, aOwner);
   nsCOMPtr<nsIContent> iterContent;
+  nsIContent* firstNonChromeOnly = aStartContent->IsInNativeAnonymousSubtree() ?
+    aStartContent->FindFirstNonChromeOnlyAccessContent() : nullptr;
   while (1) {
     // Iterate tab index to find corresponding contents in scope
 
     while (1) {
       // Iterate remaining contents in scope to find next content to focus
 
       // Get next content
       aForward ? contentTraversal.Next() : contentTraversal.Prev();
       iterContent = contentTraversal.GetCurrent();
+
+      if (firstNonChromeOnly && firstNonChromeOnly == iterContent) {
+        // We just broke out from the native anonynous content, so move
+        // to the previous/next node of the native anonymous owner.
+        if (aForward) {
+          contentTraversal.Next();
+        } else {
+          contentTraversal.Prev();
+        }
+        iterContent = contentTraversal.GetCurrent();
+      }
       if (!iterContent) {
         // Reach the end
         break;
       }
 
-      // Get tab index of next content
+      // Get the tab index of the next element. For NAC we rely on frames.
+      //XXXsmaug we should probably use frames also for Shadow DOM and special
+      //         case only display:contents elements.
       int32_t tabIndex = 0;
-      iterContent->IsFocusable(&tabIndex);
+      if (iterContent->IsInNativeAnonymousSubtree() &&
+          iterContent->GetPrimaryFrame()) {
+        iterContent->GetPrimaryFrame()->IsFocusable(&tabIndex);
+      } else {
+        iterContent->IsFocusable(&tabIndex);
+      }
       if (tabIndex < 0 || !(aIgnoreTabIndex || tabIndex == aCurrentTabIndex)) {
+        // If the element has native anonymous content, we may need to
+        // focus some NAC element, even if the element itself isn't focusable.
+        // This happens for example with <input type="date">.
+        // So, try to find NAC and then traverse the frame tree to find elements
+        // to focus.
+        nsIFrame* possibleAnonOwnerFrame = iterContent->GetPrimaryFrame();
+        nsIAnonymousContentCreator* anonCreator =
+          do_QueryFrame(possibleAnonOwnerFrame);
+        if (anonCreator && !iterContent->IsInNativeAnonymousSubtree()) {
+          nsIFrame* frame = nullptr;
+          // Find the first or last frame in tree order so that
+          // we can scope frame traversing to NAC.
+          if (aForward) {
+            frame = possibleAnonOwnerFrame->PrincipalChildList().FirstChild();
+          } else {
+            frame = possibleAnonOwnerFrame->PrincipalChildList().LastChild();
+            nsIFrame* last = frame;
+            while (last) {
+              frame = last;
+              last = frame->PrincipalChildList().LastChild();
+            }
+          };
+
+          nsCOMPtr<nsIFrameEnumerator> frameTraversal;
+          nsresult rv = NS_NewFrameTraversal(getter_AddRefs(frameTraversal),
+                                             iterContent->OwnerDoc()->
+                                               GetShell()->GetPresContext(),
+                                             frame,
+                                             ePreOrder,
+                                             false, // aVisual
+                                             false, // aLockInScrollView
+                                             true, // aFollowOOFs
+                                             true,  // aSkipPopupChecks
+                                             false // aSkipShadow
+                                             );
+          if (NS_SUCCEEDED(rv)) {
+            nsIFrame* frame =
+              static_cast<nsIFrame*>(frameTraversal->CurrentItem());
+            while (frame) {
+              int32_t tabIndex;
+              frame->IsFocusable(&tabIndex, 0);
+              if (tabIndex >= 0 &&
+                  (aIgnoreTabIndex || aCurrentTabIndex == tabIndex)) {
+                return frame->GetContent();
+              }
+
+              if (aForward) {
+                frameTraversal->Next();
+              } else {
+                frameTraversal->Prev();
+              }
+              frame = static_cast<nsIFrame*>(frameTraversal->CurrentItem());
+              if (frame == possibleAnonOwnerFrame) {
+                break;
+              }
+            }
+          }
+        }
+
         continue;
       }
 
       if (!IsHostOrSlot(iterContent)) {
         // Found content to focus
         return iterContent;
       }
 
--- a/dom/base/test/file_bug1453693.html
+++ b/dom/base/test/file_bug1453693.html
@@ -26,16 +26,22 @@
         shadowAnchor.onfocus = focusLogger;
         shadowAnchor.textContent = "in shadow DOM";
         sr.appendChild(shadowAnchor);
         var shadowInput = document.createElement("input");
         shadowInput.onfocus = focusLogger;
         shadowInput.tabIndex = 1;
         sr.appendChild(shadowInput);
 
+        var shadowDate = document.createElement("input");
+        shadowDate.type = "date";
+        shadowDate.onfocus = focusLogger;
+        shadowDate.tabIndex = 1;
+        sr.appendChild(shadowDate);
+
         var input = document.createElement("input");
         input.onfocus = focusLogger;
         input.tabIndex = 1;
         document.body.appendChild(input);
 
         var input2 = document.createElement("input");
         input2.onfocus = focusLogger;
         document.body.appendChild(input2);
@@ -44,24 +50,36 @@
 
         synthesizeKey("KEY_Tab");
         opener.is(lastFocusTarget, input, "Should have focused input element. (3)");
         synthesizeKey("KEY_Tab");
         opener.is(lastFocusTarget, anchor, "Should have focused anchor element. (3)");
         synthesizeKey("KEY_Tab");
         opener.is(lastFocusTarget, shadowInput, "Should have focused input element in shadow DOM. (3)");
         synthesizeKey("KEY_Tab");
+        opener.is(lastFocusTarget, shadowDate, "Should have focused date element in shadow DOM. (3)");
+        synthesizeKey("KEY_Tab");
+        opener.is(lastFocusTarget, shadowDate, "Should have focused date element in shadow DOM. (3)");
+        synthesizeKey("KEY_Tab");
+        opener.is(lastFocusTarget, shadowDate, "Should have focused date element in shadow DOM. (3)");
+        synthesizeKey("KEY_Tab");
         opener.is(lastFocusTarget, shadowAnchor, "Should have focused anchor element in shadow DOM. (3)");
         synthesizeKey("KEY_Tab");
         opener.is(lastFocusTarget, input2, "Should have focused input[2] element. (3)");
 
         // Backwards
         synthesizeKey("KEY_Tab", {shiftKey: true});
         opener.is(lastFocusTarget, shadowAnchor, "Should have focused anchor element in shadow DOM. (4)");
         synthesizeKey("KEY_Tab", {shiftKey: true});
+        opener.is(lastFocusTarget, shadowDate, "Should have focused date element in shadow DOM. (4)");
+        synthesizeKey("KEY_Tab", {shiftKey: true});
+        opener.is(lastFocusTarget, shadowDate, "Should have focused date element in shadow DOM. (4)");
+        synthesizeKey("KEY_Tab", {shiftKey: true});
+        opener.is(lastFocusTarget, shadowDate, "Should have focused date element in shadow DOM. (4)");
+        synthesizeKey("KEY_Tab", {shiftKey: true});
         opener.is(lastFocusTarget, shadowInput, "Should have focused input element in shadow DOM. (4)");
         synthesizeKey("KEY_Tab", {shiftKey: true});
         opener.is(lastFocusTarget, anchor, "Should have focused anchor element. (4)");
         synthesizeKey("KEY_Tab", {shiftKey: true});
         opener.is(lastFocusTarget, input, "Should have focused input element. (4)");
 
         document.body.innerHTML = null;
       }