Bug 1361301: Don't toggle focus-within state past the nearest common flattened tree ancestor. r?smaug draft
authorEmilio Cobos Álvarez <emilio@crisal.io>
Wed, 03 May 2017 12:41:24 +0200
changeset 572345 da73da06fad51b1b69d36cc51045d63eb9de7f19
parent 572344 931a7a8a52a653afbd8ee00fc523773a699e3442
child 572346 b3bfc97f3ed5e31cf1088ebefec80ba29568e733
push id57038
push userbmo:emilio+bugs@crisal.io
push dateThu, 04 May 2017 00:20:51 +0000
reviewerssmaug
bugs1361301
milestone55.0a1
Bug 1361301: Don't toggle focus-within state past the nearest common flattened tree ancestor. r?smaug MozReview-Commit-ID: 3L5Nr6z4Q9X
dom/base/nsFocusManager.cpp
dom/base/nsFocusManager.h
--- a/dom/base/nsFocusManager.cpp
+++ b/dom/base/nsFocusManager.cpp
@@ -532,20 +532,18 @@ nsFocusManager::MoveFocus(mozIDOMWindowP
 
   nsCOMPtr<nsPIDOMWindowOuter> window;
   nsCOMPtr<nsIContent> startContent;
   if (aStartElement) {
     startContent = do_QueryInterface(aStartElement);
     NS_ENSURE_TRUE(startContent, NS_ERROR_INVALID_ARG);
 
     window = GetCurrentWindow(startContent);
-  }
-  else {
+  } else {
     window = aWindow ? nsPIDOMWindowOuter::From(aWindow) : mFocusedWindow.get();
-    NS_ENSURE_TRUE(window, NS_ERROR_FAILURE);
   }
 
   NS_ENSURE_TRUE(window, NS_ERROR_FAILURE);
 
   bool noParentTraversal = aFlags & FLAG_NOPARENTFRAME;
   nsCOMPtr<nsIContent> newFocus;
   nsresult rv = DetermineElementToMoveFocus(window, startContent, aType, noParentTraversal,
                                             getter_AddRefs(newFocus));
@@ -860,17 +858,17 @@ nsFocusManager::ContentRemoved(nsIDocume
             if (limiter == content) {
               editor->FinalizeSelection();
             }
           }
         }
       }
     }
 
-    NotifyFocusStateChange(content, shouldShowFocusRing, false);
+    NotifyFocusStateChange(content, nullptr, shouldShowFocusRing, false);
   }
 
   return NS_OK;
 }
 
 NS_IMETHODIMP
 nsFocusManager::WindowShown(mozIDOMWindowProxy* aWindow, bool aNeedsFocus)
 {
@@ -966,16 +964,17 @@ nsFocusManager::WindowHidden(mozIDOMWind
 
   nsCOMPtr<nsIContent> oldFocusedContent = mFocusedContent.forget();
 
   nsCOMPtr<nsIDocShell> focusedDocShell = mFocusedWindow->GetDocShell();
   nsCOMPtr<nsIPresShell> presShell = focusedDocShell->GetPresShell();
 
   if (oldFocusedContent && oldFocusedContent->IsInComposedDoc()) {
     NotifyFocusStateChange(oldFocusedContent,
+                           nullptr,
                            mFocusedWindow->ShouldShowFocusRing(),
                            false);
     window->UpdateCommands(NS_LITERAL_STRING("focus"), nullptr, 0);
 
     if (presShell) {
       SendFocusOrBlurEvent(eBlur, presShell,
                            oldFocusedContent->GetComposedDoc(),
                            oldFocusedContent, 1, false);
@@ -1085,36 +1084,54 @@ nsFocusManager::ParentActivated(mozIDOMW
 
   ActivateOrDeactivate(window, aActive);
   return NS_OK;
 }
 
 /* static */
 void
 nsFocusManager::NotifyFocusStateChange(nsIContent* aContent,
+                                       nsIContent* aContentToFocus,
                                        bool aWindowShouldShowFocusRing,
                                        bool aGettingFocus)
 {
+  MOZ_ASSERT_IF(aContentToFocus, !aGettingFocus);
   if (!aContent->IsElement()) {
     return;
   }
+
+  nsIContent* commonAncestor = nullptr;
+  if (aContentToFocus && aContentToFocus->IsElement()) {
+    commonAncestor =
+      nsContentUtils::GetCommonFlattenedTreeAncestor(aContent, aContentToFocus);
+  }
+
   EventStates eventState = NS_EVENT_STATE_FOCUS;
   if (aWindowShouldShowFocusRing) {
     eventState |= NS_EVENT_STATE_FOCUSRING;
   }
 
   if (aGettingFocus) {
     aContent->AsElement()->AddStates(eventState);
   } else {
     aContent->AsElement()->RemoveStates(eventState);
   }
 
-  for (Element* element = aContent->AsElement(); element;
-       element = element->GetParentElementCrossingShadowRoot()) {
+  for (nsIContent* content = aContent;
+       content && content != commonAncestor;
+       content = content->GetFlattenedTreeParent()) {
+    if (!content->IsElement()) {
+      continue;
+    }
+
+    Element* element = content->AsElement();
     if (aGettingFocus) {
+      if (element->State().HasState(NS_EVENT_STATE_FOCUS_WITHIN)) {
+        break;
+      }
       element->AddStates(NS_EVENT_STATE_FOCUS_WITHIN);
     } else {
       element->RemoveStates(NS_EVENT_STATE_FOCUS_WITHIN);
     }
   }
 }
 
 // static
@@ -1658,17 +1675,20 @@ nsFocusManager::Blur(nsPIDOMWindowOuter*
 
   LOGCONTENT("Element %s has been blurred", content.get());
 
   // Don't fire blur event on the root content which isn't editable.
   bool sendBlurEvent =
     content && content->IsInComposedDoc() && !IsNonFocusableRoot(content);
   if (content) {
     if (sendBlurEvent) {
-      NotifyFocusStateChange(content, shouldShowFocusRing, false);
+      NotifyFocusStateChange(content,
+                             aContentToFocus,
+                             shouldShowFocusRing,
+                             false);
     }
 
     // if an object/plug-in/remote browser is being blurred, move the system focus
     // to the parent window, otherwise events will still get fired at the plugin.
     // But don't do this if we are blurring due to the window being lowered,
     // otherwise, the parent window can get raised again.
     if (mActiveWindow) {
       nsIFrame* contentFrame = content->GetPrimaryFrame();
@@ -1909,17 +1929,20 @@ nsFocusManager::Focus(nsPIDOMWindowOuter
     if (aContent && aFocusChanged) {
       ScrollIntoView(presShell, aContent, aFlags);
     }
 
     bool sendFocusEvent =
       aContent && aContent->IsInComposedDoc() && !IsNonFocusableRoot(aContent);
     nsPresContext* presContext = presShell->GetPresContext();
     if (sendFocusEvent) {
-      NotifyFocusStateChange(aContent, aWindow->ShouldShowFocusRing(), true);
+      NotifyFocusStateChange(aContent,
+                             nullptr,
+                             aWindow->ShouldShowFocusRing(),
+                             true);
 
       // if this is an object/plug-in/remote browser, focus its widget.  Note that we might
       // no longer be in the same document, due to the events we fired above when
       // aIsNewDocument.
       if (presShell->GetDocument() == aContent->GetComposedDoc()) {
         if (aAdjustWidgets && objectFrameWidget && !sTestMode)
           objectFrameWidget->SetFocus(false);
 
--- a/dom/base/nsFocusManager.h
+++ b/dom/base/nsFocusManager.h
@@ -510,16 +510,17 @@ private:
   // we need to pass in whether the window should show a focus ring
   // before the SetFocusedNode call on it happened when losing focus
   // and after the SetFocusedNode call when gaining focus, which is
   // why that information needs to be an explicit argument instead of
   // just passing in the window and asking it whether it should show
   // focus rings: in the losing focus case that information could be
   // wrong..
   static void NotifyFocusStateChange(nsIContent* aContent,
+                                     nsIContent* aContentToFocus,
                                      bool aWindowShouldShowFocusRing,
                                      bool aGettingFocus);
 
   void SetFocusedWindowInternal(nsPIDOMWindowOuter* aWindow);
 
   // Notify the change of content window ID
   // belonging to the top level outer window.
   void NotifyCurrentTopLevelContentWindowChange(nsPIDOMWindowOuter* aWindow);