Bug 712130 - Defer autofocus until after frame construction. r=bz
☠☠ backed out by 96acd76fdf71 ☠ ☠
authordecltype <mozilla@decltype.org>
Mon, 08 Jan 2018 22:35:00 +0100
changeset 453364 3774e90777a793f70cfb492b5a7726c9de098d16
parent 453363 a3cffbfc83950a3453bce4d446f677c6e0c2fbba
child 453365 e7738f07edae521d7459d2ac759416005a4899cd
push id1648
push usermtabara@mozilla.com
push dateThu, 01 Mar 2018 12:45:47 +0000
treeherdermozilla-release@cbb9688c2eeb [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersbz
bugs712130
milestone59.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 712130 - Defer autofocus until after frame construction. r=bz The autofocus attribute on form elements forces layout in CheckIfFocusable. To avoid unpleasant FOUCs, defer autofocus processing until frames are constructed in PresShell::Initialize. Resolve the race between nsAutoFocusEvent running and page load by checking the readystate at time of event posting. MozReview-Commit-ID: 90jiJYJWmRg
dom/base/nsDocument.cpp
dom/base/nsDocument.h
dom/base/nsFocusManager.cpp
dom/base/nsIDocument.h
dom/html/nsGenericHTMLElement.cpp
layout/base/PresShell.cpp
--- a/dom/base/nsDocument.cpp
+++ b/dom/base/nsDocument.cpp
@@ -1573,16 +1573,17 @@ nsDocument::nsDocument(const char* aCont
   , mNeedsReleaseAfterStackRefCntRelease(false)
   , mMaybeServiceWorkerControlled(false)
 #ifdef DEBUG
   , mWillReparent(false)
 #endif
   , mDOMLoadingSet(false)
   , mDOMInteractiveSet(false)
   , mDOMCompleteSet(false)
+  , mAutoFocusFired(false)
 {
   SetContentTypeInternal(nsDependentCString(aContentType));
 
   MOZ_LOG(gDocumentLeakPRLog, LogLevel::Debug, ("DOCUMENT %p created", this));
 
   // Start out mLastStyleSheetSet as null, per spec
   SetDOMStringToNull(mLastStyleSheetSet);
 
@@ -9958,16 +9959,128 @@ nsDocument::GetTemplateContentsOwner()
     // |doc| is the template contents owner of template elements created
     // by |doc|.
     doc->mTemplateContentsOwner = doc;
   }
 
   return mTemplateContentsOwner;
 }
 
+
+/**
+ * nsAutoFocusEvent is used to dispatch a focus event for an
+ * nsGenericHTMLFormElement with the autofocus attribute enabled.
+ */
+class nsAutoFocusEvent : public Runnable
+{
+public:
+  explicit nsAutoFocusEvent(already_AddRefed<Element>&& aElement,
+                            already_AddRefed<nsPIDOMWindowOuter>&& aRootWindow)
+    : mozilla::Runnable("nsAutoFocusEvent")
+    , mElement(aElement)
+    , mRootWindow(aRootWindow)
+  {
+  }
+
+  NS_IMETHOD Run() override
+  {
+    nsFocusManager* fm = nsFocusManager::GetFocusManager();
+    if (!fm) {
+      return NS_ERROR_NULL_POINTER;
+    }
+
+    nsIDocument* document = mElement->OwnerDoc();
+
+    // Don't steal focus from the user.
+    if (mRootWindow->GetFocusedNode()) {
+      return NS_OK;
+    }
+
+    // If something is focused in the same document, ignore autofocus.
+    if (!fm->GetFocusedContent() ||
+        fm->GetFocusedContent()->OwnerDoc() != document) {
+      mozilla::ErrorResult rv;
+      mElement->Focus(rv);
+      return rv.StealNSResult();
+    }
+
+    return NS_OK;
+  }
+private:
+  nsCOMPtr<Element> mElement;
+  nsCOMPtr<nsPIDOMWindowOuter> mRootWindow;
+};
+
+void
+nsDocument::SetAutoFocusElement(Element* aAutoFocusElement)
+{
+  if (mAutoFocusFired) {
+    // Too late.
+    return;
+  }
+
+  if (mAutoFocusElement) {
+    // The spec disallows multiple autofocus elements, so we consider only the
+    // first one to preserve the old behavior.
+    return;
+  }
+
+  mAutoFocusElement = do_GetWeakReference(aAutoFocusElement);
+  TriggerAutoFocus();
+}
+
+static nsCOMPtr<nsPIDOMWindowOuter>
+GetRootWindow(nsIDocument* aDoc)
+{
+  nsIDocShell* docShell = aDoc->GetDocShell();
+  if (!docShell) {
+    return nullptr;
+  }
+  nsCOMPtr<nsIDocShellTreeItem> rootItem;
+  docShell->GetRootTreeItem(getter_AddRefs(rootItem));
+  return rootItem ? rootItem->GetWindow() : nullptr;
+}
+
+void
+nsDocument::TriggerAutoFocus()
+{
+  if (mAutoFocusFired) {
+    return;
+  }
+
+  if (!mPresShell || !mPresShell->DidInitialize()) {
+    // Delay autofocus until frames are constructed so that we don't thrash
+    // style and layout calculations.
+    return;
+  }
+
+  nsCOMPtr<Element> autoFocusElement = do_QueryReferent(mAutoFocusElement);
+  if (autoFocusElement) {
+    mAutoFocusFired = true;
+
+    nsCOMPtr<nsPIDOMWindowOuter> rootWindow = GetRootWindow(this);
+    if (!rootWindow) {
+      return;
+    }
+
+    // NOTE: This may be removed in the future since the spec technically
+    // allows autofocus after load.
+    nsCOMPtr<nsIDocument> rootDoc = rootWindow->GetExtantDoc();
+    if (rootDoc &&
+        rootDoc->GetReadyStateEnum() == nsIDocument::READYSTATE_COMPLETE) {
+      return;
+    }
+
+    nsCOMPtr<nsIRunnable> event =
+      new nsAutoFocusEvent(autoFocusElement.forget(), rootWindow.forget());
+    nsresult rv = NS_DispatchToCurrentThread(event.forget());
+    NS_ENSURE_SUCCESS_VOID(rv);
+  }
+}
+
 void
 nsDocument::SetScrollToRef(nsIURI *aDocumentURI)
 {
   if (!aDocumentURI) {
     return;
   }
 
   nsAutoCString ref;
@@ -11750,28 +11863,16 @@ public:
 private:
   PendingFullscreenRequestList() = delete;
 
   static LinkedList<FullscreenRequest> sList;
 };
 
 /* static */ LinkedList<FullscreenRequest> PendingFullscreenRequestList::sList;
 
-static nsCOMPtr<nsPIDOMWindowOuter>
-GetRootWindow(nsIDocument* aDoc)
-{
-  nsIDocShell* docShell = aDoc->GetDocShell();
-  if (!docShell) {
-    return nullptr;
-  }
-  nsCOMPtr<nsIDocShellTreeItem> rootItem;
-  docShell->GetRootTreeItem(getter_AddRefs(rootItem));
-  return rootItem ? rootItem->GetWindow() : nullptr;
-}
-
 static bool
 ShouldApplyFullscreenDirectly(nsIDocument* aDoc,
                               nsPIDOMWindowOuter* aRootWin)
 {
   if (XRE_GetProcessType() == GeckoProcessType_Content) {
     // If we are in the content process, we can apply the fullscreen
     // state directly only if we have been in DOM fullscreen, because
     // otherwise we always need to notify the chrome.
--- a/dom/base/nsDocument.h
+++ b/dom/base/nsDocument.h
@@ -808,16 +808,19 @@ public:
   virtual nsresult LoadChromeSheetSync(nsIURI* uri, bool isAgentSheet,
                                        RefPtr<mozilla::StyleSheet>* aSheet) override;
 
   virtual nsISupports* GetCurrentContentSink() override;
 
   // Only BlockOnload should call this!
   void AsyncBlockOnload();
 
+  virtual void SetAutoFocusElement(Element* aAutoFocusElement) override;
+  virtual void TriggerAutoFocus() override;
+
   virtual void SetScrollToRef(nsIURI *aDocumentURI) override;
   virtual void ScrollToRef() override;
   virtual void ResetScrolledToRefAlready() override;
   virtual void SetChangeScrollPosWhenScrollingToRef(bool aValue) override;
 
   virtual Element* LookupImageElement(const nsAString& aElementId) override;
   virtual void MozSetImageElement(const nsAString& aImageElementId,
                                   Element* aElement) override;
@@ -1313,16 +1316,18 @@ private:
 
   // Set if we've found a URL for the current picture
   nsString mPreloadPictureFoundSource;
 
   RefPtr<mozilla::dom::DOMImplementation> mDOMImplementation;
 
   RefPtr<nsContentList> mImageMaps;
 
+  nsWeakPtr mAutoFocusElement;
+
   nsCString mScrollToRef;
   uint8_t mScrolledToRefAlready : 1;
   uint8_t mChangeScrollPosWhenScrollingToRef : 1;
 
   // Tracking for plugins in the document.
   nsTHashtable< nsPtrHashKey<nsIObjectLoadingContent> > mPlugins;
 
   RefPtr<mozilla::dom::DocumentTimeline> mDocumentTimeline;
@@ -1363,16 +1368,17 @@ public:
   bool mWillReparent;
 #endif
 
 private:
   void RecordNavigationTiming(ReadyState aReadyState);
   bool mDOMLoadingSet : 1;
   bool mDOMInteractiveSet : 1;
   bool mDOMCompleteSet : 1;
+  bool mAutoFocusFired : 1;
 };
 
 class nsDocumentOnStack
 {
 public:
   explicit nsDocumentOnStack(nsDocument* aDoc) : mDoc(aDoc)
   {
     mDoc->IncreaseStackRefCnt();
--- a/dom/base/nsFocusManager.cpp
+++ b/dom/base/nsFocusManager.cpp
@@ -1594,17 +1594,17 @@ nsFocusManager::CheckIfFocusable(nsICont
   nsCOMPtr<nsIDocument> doc = aContent->GetComposedDoc();
   // can't focus elements that are not in documents
   if (!doc) {
     LOGCONTENT("Cannot focus %s because content not in document", aContent)
     return nullptr;
   }
 
   // Make sure that our frames are up to date while ensuring the presshell is
-  // also initialized in case we come from an autofocus event.
+  // also initialized in case we come from a script calling focus() early.
   mEventHandlingNeedsFlush = false;
   doc->FlushPendingNotifications(FlushType::EnsurePresShellInitAndFrames);
 
   nsIPresShell *shell = doc->GetShell();
   if (!shell)
     return nullptr;
 
   // the root content can always be focused,
--- a/dom/base/nsIDocument.h
+++ b/dom/base/nsIDocument.h
@@ -2601,16 +2601,19 @@ public:
    */
   mozilla::EventStates GetDocumentState() const
   {
     return mDocumentState;
   }
 
   virtual nsISupports* GetCurrentContentSink() = 0;
 
+  virtual void SetAutoFocusElement(Element* aAutoFocusElement) = 0;
+  virtual void TriggerAutoFocus() = 0;
+
   virtual void SetScrollToRef(nsIURI *aDocumentURI) = 0;
   virtual void ScrollToRef() = 0;
   virtual void ResetScrolledToRefAlready() = 0;
   virtual void SetChangeScrollPosWhenScrollingToRef(bool aValue) = 0;
 
   using mozilla::dom::DocumentOrShadowRoot::GetElementById;
   using mozilla::dom::DocumentOrShadowRoot::GetElementsByTagName;
   using mozilla::dom::DocumentOrShadowRoot::GetElementsByTagNameNS;
--- a/dom/html/nsGenericHTMLElement.cpp
+++ b/dom/html/nsGenericHTMLElement.cpp
@@ -107,74 +107,16 @@
 #include "mozilla/StyleSetHandleInlines.h"
 #include "ReferrerPolicy.h"
 #include "mozilla/dom/HTMLLabelElement.h"
 #include "mozilla/dom/HTMLInputElement.h"
 
 using namespace mozilla;
 using namespace mozilla::dom;
 
-/**
- * nsAutoFocusEvent is used to dispatch a focus event when a
- * nsGenericHTMLFormElement is binded to the tree with the autofocus attribute
- * enabled.
- */
-class nsAutoFocusEvent : public Runnable
-{
-public:
-  explicit nsAutoFocusEvent(nsGenericHTMLFormElement* aElement)
-    : mozilla::Runnable("nsAutoFocusEvent")
-    , mElement(aElement)
-  {
-  }
-
-  NS_IMETHOD Run() override {
-    nsFocusManager* fm = nsFocusManager::GetFocusManager();
-    if (!fm) {
-      return NS_ERROR_NULL_POINTER;
-    }
-
-    nsIDocument* document = mElement->OwnerDoc();
-
-    nsPIDOMWindowOuter* window = document->GetWindow();
-    if (!window) {
-      return NS_OK;
-    }
-
-    // Trying to found the top window (equivalent to window.top).
-    if (nsCOMPtr<nsPIDOMWindowOuter> top = window->GetTop()) {
-      window = top;
-    }
-
-    if (window->GetFocusedNode()) {
-      return NS_OK;
-    }
-
-    nsCOMPtr<nsIDocument> topDoc = window->GetExtantDoc();
-    if (topDoc && topDoc->GetReadyStateEnum() == nsIDocument::READYSTATE_COMPLETE) {
-      return NS_OK;
-    }
-
-    // If something is focused in the same document, ignore autofocus.
-    if (!fm->GetFocusedContent() ||
-        fm->GetFocusedContent()->OwnerDoc() != document) {
-      mozilla::ErrorResult rv;
-      mElement->Focus(rv);
-      return rv.StealNSResult();
-    }
-
-    return NS_OK;
-  }
-private:
-  // NOTE: nsGenericHTMLFormElement is saved as a nsGenericHTMLElement
-  // because AddRef/Release are ambiguous with nsGenericHTMLFormElement
-  // and Focus() is declared (and defined) in nsGenericHTMLElement class.
-  RefPtr<nsGenericHTMLElement> mElement;
-};
-
 NS_IMPL_ADDREF_INHERITED(nsGenericHTMLElement, nsGenericHTMLElementBase)
 NS_IMPL_RELEASE_INHERITED(nsGenericHTMLElement, nsGenericHTMLElementBase)
 
 NS_INTERFACE_MAP_BEGIN(nsGenericHTMLElement)
   NS_INTERFACE_MAP_ENTRY(nsIDOMHTMLElement)
   NS_INTERFACE_MAP_ENTRY(nsIDOMElement)
   NS_INTERFACE_MAP_ENTRY(nsIDOMNode)
 NS_INTERFACE_MAP_END_INHERITING(nsGenericHTMLElementBase)
@@ -1881,20 +1823,18 @@ nsGenericHTMLFormElement::BindToTree(nsI
                                                  aCompileEventHandlers);
   NS_ENSURE_SUCCESS(rv, rv);
 
   // An autofocus event has to be launched if the autofocus attribute is
   // specified and the element accept the autofocus attribute. In addition,
   // the document should not be already loaded and the "browser.autofocus"
   // preference should be 'true'.
   if (IsAutofocusable() && HasAttr(kNameSpaceID_None, nsGkAtoms::autofocus) &&
-      nsContentUtils::AutoFocusEnabled()) {
-    nsCOMPtr<nsIRunnable> event = new nsAutoFocusEvent(this);
-    rv = NS_DispatchToCurrentThread(event);
-    NS_ENSURE_SUCCESS(rv, rv);
+      nsContentUtils::AutoFocusEnabled() && aDocument) {
+    aDocument->SetAutoFocusElement(this);
   }
 
   // If @form is set, the element *has* to be in a document, otherwise it
   // wouldn't be possible to find an element with the corresponding id.
   // If @form isn't set, the element *has* to have a parent, otherwise it
   // wouldn't be possible to find a form ancestor.
   // We should not call UpdateFormOwner if none of these conditions are
   // fulfilled.
--- a/layout/base/PresShell.cpp
+++ b/layout/base/PresShell.cpp
@@ -1831,16 +1831,18 @@ PresShell::Initialize(nscoord aWidth, ns
     NS_ENSURE_STATE(!mHaveShutDown);
 
     // Run the XBL binding constructors for any new frames we've constructed.
     // (Do this in a script runner, since our caller might have a script
     // blocker on the stack.)
     nsContentUtils::AddScriptRunner(new XBLConstructorRunner(mDocument));
   }
 
+  mDocument->TriggerAutoFocus();
+
   NS_ASSERTION(rootFrame, "How did that happen?");
 
   // Note: when the frame was created above it had the NS_FRAME_IS_DIRTY bit
   // set, but XBL processing could have caused a reflow which clears it.
   if (MOZ_LIKELY(rootFrame->GetStateBits() & NS_FRAME_IS_DIRTY)) {
     // Unset the DIRTY bits so that FrameNeedsReflow() will work right.
     rootFrame->RemoveStateBits(NS_FRAME_IS_DIRTY |
                                NS_FRAME_HAS_DIRTY_CHILDREN);