Bug 933483 - Don't fire events (and especially request animation frame events) when we're in a modal dialog. r=smaug
authorBlake Kaplan <mrbkap@gmail.com>
Tue, 17 Dec 2013 16:12:33 -0800
changeset 160933 08dc602999421069b14dae53788694b504e1d293
parent 160932 cbe04dbc2bd662173585c1cb6fe1d2a86577fd7d
child 160934 c3dc4e624c8e367647c598d49eea0f577aeb8e0b
push id37746
push usermrbkap@mozilla.com
push dateWed, 18 Dec 2013 00:13:20 +0000
treeherdermozilla-inbound@08dc60299942 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewerssmaug
bugs933483
milestone29.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 933483 - Don't fire events (and especially request animation frame events) when we're in a modal dialog. r=smaug
content/base/src/nsDocument.cpp
dom/base/nsFocusManager.cpp
dom/base/nsGlobalWindow.cpp
dom/base/nsGlobalWindow.h
--- a/content/base/src/nsDocument.cpp
+++ b/content/base/src/nsDocument.cpp
@@ -8560,21 +8560,27 @@ static void
 FireOrClearDelayedEvents(nsTArray<nsCOMPtr<nsIDocument> >& aDocuments,
                          bool aFireEvents)
 {
   nsIFocusManager* fm = nsFocusManager::GetFocusManager();
   if (!fm)
     return;
 
   for (uint32_t i = 0; i < aDocuments.Length(); ++i) {
+    // NB: Don't bother trying to fire delayed events on documents that were
+    // closed before this event ran.
     if (!aDocuments[i]->EventHandlingSuppressed()) {
       fm->FireDelayedEvents(aDocuments[i]);
       nsCOMPtr<nsIPresShell> shell = aDocuments[i]->GetShell();
       if (shell) {
-        shell->FireOrClearDelayedEvents(aFireEvents);
+        // Only fire events for active documents.
+        bool fire = aFireEvents &&
+                    aDocuments[i]->GetInnerWindow() &&
+                    aDocuments[i]->GetInnerWindow()->IsCurrentInnerWindow();
+        shell->FireOrClearDelayedEvents(fire);
       }
     }
   }
 }
 
 void
 nsDocument::MaybePreLoadImage(nsIURI* uri, const nsAString &aCrossOriginAttr)
 {
--- a/dom/base/nsFocusManager.cpp
+++ b/dom/base/nsFocusManager.cpp
@@ -987,26 +987,33 @@ nsFocusManager::WindowHidden(nsIDOMWindo
 }
 
 NS_IMETHODIMP
 nsFocusManager::FireDelayedEvents(nsIDocument* aDocument)
 {
   NS_ENSURE_ARG(aDocument);
 
   // fire any delayed focus and blur events in the same order that they were added
-  for (uint32_t i = 0; i < mDelayedBlurFocusEvents.Length(); i++)
-  {
-    if (mDelayedBlurFocusEvents[i].mDocument == aDocument &&
-        !aDocument->EventHandlingSuppressed()) {
-      uint32_t type = mDelayedBlurFocusEvents[i].mType;
-      nsCOMPtr<EventTarget> target = mDelayedBlurFocusEvents[i].mTarget;
-      nsCOMPtr<nsIPresShell> presShell = mDelayedBlurFocusEvents[i].mPresShell;
-      mDelayedBlurFocusEvents.RemoveElementAt(i);
-      SendFocusOrBlurEvent(type, presShell, aDocument, target, 0, false);
-      --i;
+  for (uint32_t i = 0; i < mDelayedBlurFocusEvents.Length(); i++) {
+    if (mDelayedBlurFocusEvents[i].mDocument == aDocument) {
+      if (!aDocument->GetInnerWindow() ||
+          !aDocument->GetInnerWindow()->IsCurrentInnerWindow()) {
+        // If the document was navigated away from or is defunct, don't bother
+        // firing events on it. Note the symmetry between this condition and
+        // the similar one in nsDocument.cpp:FireOrClearDelayedEvents.
+        mDelayedBlurFocusEvents.RemoveElementAt(i);
+        --i;
+      } else if (!aDocument->EventHandlingSuppressed()) {
+        uint32_t type = mDelayedBlurFocusEvents[i].mType;
+        nsCOMPtr<EventTarget> target = mDelayedBlurFocusEvents[i].mTarget;
+        nsCOMPtr<nsIPresShell> presShell = mDelayedBlurFocusEvents[i].mPresShell;
+        mDelayedBlurFocusEvents.RemoveElementAt(i);
+        SendFocusOrBlurEvent(type, presShell, aDocument, target, 0, false);
+        --i;
+      }
     }
   }
 
   return NS_OK;
 }
 
 NS_IMETHODIMP
 nsFocusManager::FocusPlugin(nsIContent* aContent)
--- a/dom/base/nsGlobalWindow.cpp
+++ b/dom/base/nsGlobalWindow.cpp
@@ -1261,16 +1261,18 @@ nsGlobalWindow::~nsGlobalWindow()
     // alive, iterate through the inner windows and null out their
     // back pointer to this outer, and pull them out of the list of
     // inner windows.
 
     nsGlobalWindow *w;
     while ((w = (nsGlobalWindow *)PR_LIST_HEAD(this)) != this) {
       PR_REMOVE_AND_INIT_LINK(w);
     }
+
+    DropOuterWindowDocs();
   } else {
     Telemetry::Accumulate(Telemetry::INNERWINDOWS_WITH_MUTATION_LISTENERS,
                           mMutationBits ? 1 : 0);
 
     if (mListenerManager) {
       mListenerManager->Disconnect();
       mListenerManager = nullptr;
     }
@@ -1281,19 +1283,19 @@ nsGlobalWindow::~nsGlobalWindow()
     PR_REMOVE_LINK(this);
 
     // If our outer window's inner window is this window, null out the
     // outer window's reference to this window that's being deleted.
     nsGlobalWindow *outer = GetOuterWindowInternal();
     if (outer) {
       outer->MaybeClearInnerWindow(this);
     }
-  }
-
-  ClearDelayedEventsAndDropDocument();
+
+    MOZ_ASSERT_IF(mDoc, !mDoc->EventHandlingSuppressed());
+  }
 
   // Outer windows are always supposed to call CleanUp before letting themselves
   // be destroyed. And while CleanUp generally seems to be intended to clean up
   // outers, we've historically called it for both. Changing this would probably
   // involve auditing all of the references that inners and outers can have, and
   // separating the handling into CleanUp() and FreeInnerObjects.
   if (IsInnerWindow()) {
     CleanUp();
@@ -1355,22 +1357,21 @@ nsGlobalWindow::MaybeForgiveSpamCount()
     SetPopupSpamWindow(false);
     --gOpenPopupSpamCount;
     NS_ASSERTION(gOpenPopupSpamCount >= 0,
                  "Unbalanced decrement of gOpenPopupSpamCount");
   }
 }
 
 void
-nsGlobalWindow::ClearDelayedEventsAndDropDocument()
-{
-  if (mDoc && mDoc->EventHandlingSuppressed()) {
-    mDoc->UnsuppressEventHandlingAndFireEvents(false);
-  }
+nsGlobalWindow::DropOuterWindowDocs()
+{
+  MOZ_ASSERT(IsOuterWindow());
   mDoc = nullptr;
+  mSuspendedDoc = nullptr;
 }
 
 void
 nsGlobalWindow::CleanUp()
 {
   // Guarantee idempotence.
   if (mCleanedUp)
     return;
@@ -1540,16 +1541,20 @@ nsGlobalWindow::FreeInnerObjects()
     mScreen = nullptr;
   }
 
   if (mDoc) {
     // Remember the document's principal and URI.
     mDocumentPrincipal = mDoc->NodePrincipal();
     mDocumentURI = mDoc->GetDocumentURI();
     mDocBaseURI = mDoc->GetDocBaseURI();
+
+    if (mDoc->EventHandlingSuppressed()) {
+      mDoc->UnsuppressEventHandlingAndFireEvents(false);
+    }
   }
 
   // Remove our reference to the document and the document principal.
   mFocusedNode = nullptr;
 
   if (mApplicationCache) {
     static_cast<nsDOMOfflineResourceList*>(mApplicationCache.get())->Disconnect();
     mApplicationCache = nullptr;
@@ -2266,16 +2271,20 @@ nsGlobalWindow::SetNewDocument(nsIDocume
 
   nsresult rv = NS_OK;
 
   // Set mDoc even if this is an outer window to avoid
   // having to *always* reach into the inner window to find the
   // document.
   mDoc = aDocument;
 
+  // Take this opportunity to clear mSuspendedDoc. Our old inner window is now
+  // responsible for unsuspending it.
+  mSuspendedDoc = nullptr;
+
 #ifdef DEBUG
   mLastOpenedURI = aDocument->GetDocumentURI();
 #endif
 
   mContext->WillInitializeContext();
 
   nsGlobalWindow *currentInner = GetCurrentInnerWindowInternal();
 
@@ -2761,17 +2770,17 @@ nsGlobalWindow::DetachFromDocShell()
     NS_ASSERTION(mDoc, "Must have doc!");
 
     // Remember the document's principal and URI.
     mDocumentPrincipal = mDoc->NodePrincipal();
     mDocumentURI = mDoc->GetDocumentURI();
     mDocBaseURI = mDoc->GetDocBaseURI();
 
     // Release our document reference
-    ClearDelayedEventsAndDropDocument();
+    DropOuterWindowDocs();
     mFocusedNode = nullptr;
   }
 
   ClearControllers();
 
   mChromeEventHandler = nullptr; // force release now
 
   if (mContext) {
@@ -8165,16 +8174,18 @@ nsGlobalWindow::ReallyCloseWindow()
 
     CleanUp();
   }
 }
 
 void
 nsGlobalWindow::EnterModalState()
 {
+  FORWARD_TO_OUTER_VOID(EnterModalState, ());
+
   // GetScriptableTop, not GetTop, so that EnterModalState works properly with
   // <iframe mozbrowser>.
   nsGlobalWindow* topWin = GetScriptableTop();
 
   if (!topWin) {
     NS_ERROR("Uh, EnterModalState() called w/o a reachable top window?");
     return;
   }
@@ -8198,20 +8209,18 @@ nsGlobalWindow::EnterModalState()
       }
     }
   }
 
   if (topWin->mModalStateDepth == 0) {
     NS_ASSERTION(!mSuspendedDoc, "Shouldn't have mSuspendedDoc here!");
 
     mSuspendedDoc = topWin->GetExtantDoc();
-    if (mSuspendedDoc && mSuspendedDoc->EventHandlingSuppressed()) {
+    if (mSuspendedDoc) {
       mSuspendedDoc->SuppressEventHandling();
-    } else {
-      mSuspendedDoc = nullptr;
     }
   }
   topWin->mModalStateDepth++;
 }
 
 // static
 void
 nsGlobalWindow::RunPendingTimeoutsRecursive(nsGlobalWindow *aTopWindow,
@@ -8280,16 +8289,18 @@ public:
 
 private:
   nsRefPtr<nsGlobalWindow> mWindow;
 };
 
 void
 nsGlobalWindow::LeaveModalState()
 {
+  FORWARD_TO_OUTER_VOID(LeaveModalState, ());
+
   nsGlobalWindow* topWin = GetScriptableTop();
 
   if (!topWin) {
     NS_ERROR("Uh, LeaveModalState() called w/o a reachable top window?");
     return;
   }
 
   topWin->mModalStateDepth--;
--- a/dom/base/nsGlobalWindow.h
+++ b/dom/base/nsGlobalWindow.h
@@ -976,17 +976,17 @@ protected:
 
   static bool sIdleObserversAPIFuzzTimeDisabled;
 
   friend class HashchangeCallback;
   friend class mozilla::dom::BarProp;
 
   // Object Management
   virtual ~nsGlobalWindow();
-  void ClearDelayedEventsAndDropDocument();
+  void DropOuterWindowDocs();
   void CleanUp();
   void ClearControllers();
   nsresult FinalClose();
 
   inline void MaybeClearInnerWindow(nsGlobalWindow* aExpectedInner)
   {
     if(mInnerWindow == aExpectedInner) {
       mInnerWindow = nullptr;
@@ -1471,16 +1471,22 @@ protected:
 #endif // MOZ_B2G
 
   bool mCleanedUp;
 
   nsCOMPtr<nsIDOMOfflineResourceList> mApplicationCache;
 
   nsAutoPtr<nsJSThingHashtable<nsPtrHashKey<nsXBLPrototypeHandler>, JSObject*> > mCachedXBLPrototypeHandlers;
 
+  // mSuspendedDoc is only set on outer windows. It's useful when we get matched
+  // EnterModalState/LeaveModalState calls, in which case the outer window is
+  // responsible for unsuspending events on the document. If we don't (for
+  // example, if the outer window is closed before the LeaveModalState call),
+  // then the inner window whose mDoc is our mSuspendedDoc is responsible for
+  // unsuspending it.
   nsCOMPtr<nsIDocument> mSuspendedDoc;
 
   nsRefPtr<mozilla::dom::indexedDB::IDBFactory> mIndexedDB;
 
   // This counts the number of windows that have been opened in rapid succession
   // (i.e. within dom.successive_dialog_time_limit of each other). It is reset
   // to 0 once a dialog is opened after dom.successive_dialog_time_limit seconds
   // have elapsed without any other dialogs.