Bug 621764 - tab-modal prompt in Gmail eventually triggers slow-script dialog for nsPrompter.js. r=mrbkap, sr=jst, a=blocker
authorJustin Dolske <dolske@mozilla.com>
Tue, 01 Feb 2011 19:23:00 -0800
changeset 61768 835b313007b49d24a8243dbddf070e65c9e59c01
parent 61767 3fd821a55f6d9cd9e9a8d03497aab09c5fc9e8bc
child 61769 76b361ae1c3a5c78d59dc6c911d8de2be71ef41f
push idunknown
push userunknown
push dateunknown
reviewersmrbkap, jst, blocker
bugs621764
milestone2.0b11pre
Bug 621764 - tab-modal prompt in Gmail eventually triggers slow-script dialog for nsPrompter.js. r=mrbkap, sr=jst, a=blocker
dom/base/nsDOMWindowUtils.cpp
dom/base/nsGlobalWindow.cpp
dom/base/nsGlobalWindow.h
dom/base/nsPIDOMWindow.h
dom/interfaces/base/nsIDOMWindowUtils.idl
dom/tests/mochitest/bugs/test_bug61098.html
embedding/components/windowwatcher/src/nsAutoWindowStateHelper.cpp
toolkit/components/prompts/src/nsPrompter.js
--- a/dom/base/nsDOMWindowUtils.cpp
+++ b/dom/base/nsDOMWindowUtils.cpp
@@ -1408,17 +1408,33 @@ nsDOMWindowUtils::EnterModalState()
 {
   mWindow->EnterModalState();
   return NS_OK;
 }
 
 NS_IMETHODIMP
 nsDOMWindowUtils::LeaveModalState()
 {
-  mWindow->LeaveModalState();
+  mWindow->LeaveModalState(nsnull);
+  return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDOMWindowUtils::EnterModalStateWithWindow(nsIDOMWindow **aWindow)
+{
+  *aWindow = mWindow->EnterModalState();
+  NS_IF_ADDREF(*aWindow);
+  return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDOMWindowUtils::LeaveModalStateWithWindow(nsIDOMWindow *aWindow)
+{
+  NS_ENSURE_ARG_POINTER(aWindow);
+  mWindow->LeaveModalState(aWindow);
   return NS_OK;
 }
 
 NS_IMETHODIMP
 nsDOMWindowUtils::IsInModalState(PRBool *retval)
 {
   *retval = mWindow->IsInModalState();
   return NS_OK;
--- a/dom/base/nsGlobalWindow.cpp
+++ b/dom/base/nsGlobalWindow.cpp
@@ -5058,17 +5058,17 @@ nsGlobalWindow::Print()
                                                          PR_TRUE, 
                                                          nsIPrintSettings::kInitSaveAll);
       } else {
         printSettingsService->GetNewPrintSettings(getter_AddRefs(printSettings));
       }
 
       EnterModalState();
       webBrowserPrint->Print(printSettings, nsnull);
-      LeaveModalState();
+      LeaveModalState(nsnull);
 
       PRBool savePrintSettings =
         nsContentUtils::GetBoolPref("print.save_print_settings", PR_FALSE);
       if (printSettingsAreGlobal && savePrintSettings) {
         printSettingsService->
           SavePrintSettingsToPrefs(printSettings,
                                    PR_TRUE,
                                    nsIPrintSettings::kInitSaveAll);
@@ -6263,25 +6263,25 @@ nsGlobalWindow::ReallyCloseWindow()
           treeOwnerAsWin->Destroy();
       }
     }
 
     CleanUp(PR_FALSE);
   }
 }
 
-void
+nsIDOMWindow *
 nsGlobalWindow::EnterModalState()
 {
   nsGlobalWindow* topWin = GetTop();
 
   if (!topWin) {
     NS_ERROR("Uh, EnterModalState() called w/o a reachable top window?");
 
-    return;
+    return nsnull;
   }
 
   // If there is an active ESM in this window, clear it. Otherwise, this can
   // cause a problem if a modal state is entered during a mouseup event.
   nsEventStateManager* activeESM =
     static_cast<nsEventStateManager*>(nsEventStateManager::GetActiveEventStateManager());
   if (activeESM && activeESM->GetPresContext()) {
     nsIPresShell* activeShell = activeESM->GetPresContext()->GetPresShell();
@@ -6306,19 +6306,30 @@ nsGlobalWindow::EnterModalState()
     if (mSuspendedDoc && mSuspendedDoc->EventHandlingSuppressed()) {
       mSuspendedDoc->SuppressEventHandling();
     } else {
       mSuspendedDoc = nsnull;
     }
   }
   topWin->mModalStateDepth++;
 
+  JSContext *cx = nsContentUtils::GetCurrentJSContext();
+
+  nsCOMPtr<nsIDOMWindow> callerWin;
+  nsIScriptContext *scx;
+  if (cx && (scx = GetScriptContextFromJSContext(cx))) {
+    scx->EnterModalState();
+    callerWin = do_QueryInterface(nsJSUtils::GetDynamicScriptGlobal(cx));
+  }
+
   if (mContext) {
     mContext->EnterModalState();
   }
+
+  return callerWin;
 }
 
 // static
 void
 nsGlobalWindow::RunPendingTimeoutsRecursive(nsGlobalWindow *aTopWindow,
                                             nsGlobalWindow *aWindow)
 {
   nsGlobalWindow *inner;
@@ -6382,17 +6393,17 @@ public:
     return NS_OK;
   }
 
 private:
   nsRefPtr<nsGlobalWindow> mWindow;
 };
 
 void
-nsGlobalWindow::LeaveModalState()
+nsGlobalWindow::LeaveModalState(nsIDOMWindow *aCallerWin)
 {
   nsGlobalWindow *topWin = GetTop();
 
   if (!topWin) {
     NS_ERROR("Uh, LeaveModalState() called w/o a reachable top window?");
     return;
   }
 
@@ -6406,16 +6417,24 @@ nsGlobalWindow::LeaveModalState()
     if (mSuspendedDoc) {
       nsCOMPtr<nsIDocument> currentDoc =
         do_QueryInterface(topWin->GetExtantDocument());
       mSuspendedDoc->UnsuppressEventHandlingAndFireEvents(currentDoc == mSuspendedDoc);
       mSuspendedDoc = nsnull;
     }
   }
 
+  JSContext *cx = nsContentUtils::GetCurrentJSContext();
+
+  if (aCallerWin) {
+    nsCOMPtr<nsIScriptGlobalObject> sgo(do_QueryInterface(aCallerWin));
+    nsIScriptContext *scx = sgo->GetContext();
+    scx->LeaveModalState();
+  }
+
   if (mContext) {
     mContext->LeaveModalState();
   }
 
   // Remember the time of the last dialog quit.
   nsGlobalWindow *inner = topWin->GetCurrentInnerWindowInternal();
   if (inner)
     inner->mLastDialogQuitTime = TimeStamp::Now();
@@ -6757,17 +6776,17 @@ nsGlobalWindow::ShowModalDialog(const ns
                              PR_FALSE,          // aDialog
                              PR_TRUE,           // aContentModal
                              PR_TRUE,           // aCalledNoScript
                              PR_TRUE,           // aDoJSFixups
                              nsnull, aArgs,     // args
                              GetPrincipal(),    // aCalleePrincipal
                              nsnull,            // aJSCallerContext
                              getter_AddRefs(dlgWin));
-  LeaveModalState();
+  LeaveModalState(nsnull);
 
   NS_ENSURE_SUCCESS(rv, rv);
   
   if (dlgWin) {
     nsCOMPtr<nsIPrincipal> subjectPrincipal;
     rv = nsContentUtils::GetSecurityManager()->
       GetSubjectPrincipal(getter_AddRefs(subjectPrincipal));
     if (NS_FAILED(rv)) {
--- a/dom/base/nsGlobalWindow.h
+++ b/dom/base/nsGlobalWindow.h
@@ -404,18 +404,18 @@ public:
   virtual NS_HIDDEN_(nsresult) SetNewDocument(nsIDocument *aDocument,
                                               nsISupports *aState,
                                               PRBool aForceReuseInnerWindow);
   void DispatchDOMWindowCreated();
   virtual NS_HIDDEN_(void) SetOpenerWindow(nsIDOMWindowInternal *aOpener,
                                            PRBool aOriginalOpener);
   virtual NS_HIDDEN_(void) EnsureSizeUpToDate();
 
-  virtual NS_HIDDEN_(void) EnterModalState();
-  virtual NS_HIDDEN_(void) LeaveModalState();
+  virtual NS_HIDDEN_(nsIDOMWindow *) EnterModalState();
+  virtual NS_HIDDEN_(void) LeaveModalState(nsIDOMWindow *aWindow);
 
   virtual NS_HIDDEN_(PRBool) CanClose();
   virtual NS_HIDDEN_(nsresult) ForceClose();
 
   virtual NS_HIDDEN_(void) SetHasOrientationEventListener();
   virtual NS_HIDDEN_(void) MaybeUpdateTouchState();
   virtual NS_HIDDEN_(void) UpdateTouchState();
 
--- a/dom/base/nsPIDOMWindow.h
+++ b/dom/base/nsPIDOMWindow.h
@@ -387,18 +387,18 @@ public:
                                PRBool aOriginalOpener) = 0;
 
   virtual void EnsureSizeUpToDate() = 0;
 
   /**
    * Callback for notifying a window about a modal dialog being
    * opened/closed with the window as a parent.
    */
-  virtual void EnterModalState() = 0;
-  virtual void LeaveModalState() = 0;
+  virtual nsIDOMWindow *EnterModalState() = 0;
+  virtual void LeaveModalState(nsIDOMWindow *) = 0;
 
   virtual PRBool CanClose() = 0;
   virtual nsresult ForceClose() = 0;
 
   PRBool IsModalContentWindow() const
   {
     return mIsModalContentWindow;
   }
--- a/dom/interfaces/base/nsIDOMWindowUtils.idl
+++ b/dom/interfaces/base/nsIDOMWindowUtils.idl
@@ -763,28 +763,28 @@ interface nsIDOMWindowUtils : nsISupport
    * is no current inner window, throws NS_ERROR_NOT_AVAILABLE.
    */
   readonly attribute unsigned long long currentInnerWindowID;
 
   /**
    * Put the window into a state where scripts are frozen and events
    * suppressed, for use when the window has launched a modal prompt.
    */
-  void enterModalState();
+  [noscript] void enterModalState();
 
   /**
    * Resume normal window state, where scripts can run and events are
    * delivered.
    */
-  void leaveModalState();
+  [noscript] void leaveModalState();
 
   /**
    * Is the window is in a modal state? [See enterModalState()]
    */
-  boolean isInModalState();
+  [noscript] boolean isInModalState();
 
   /**
    * Suspend/resume timeouts on this window and its descendant windows.
    */
   void suspendTimeouts();
   void resumeTimeouts();
 
   /**
@@ -819,22 +819,35 @@ interface nsIDOMWindowUtils : nsISupport
   double computeAnimationDistance(in nsIDOMElement element,
                                   in AString property,
                                   in AString value1,
                                   in AString value2);
 };
 
 typedef unsigned long long nsViewID;
 
-[scriptable, uuid(3a0334aa-b9cc-4b32-9b6c-599cd4e40d5b)]
+[scriptable, uuid(526d22ab-4997-8666-53f6-38c7f93e77a5)]
 interface nsIDOMWindowUtils_MOZILLA_2_0_BRANCH : nsISupports {
   /**
    * Get the type of the currently focused html input, if any.
    */
   readonly attribute string focusedInputType;
 
   /**
    * Given a view ID from the compositor process, retrieve the element
    * associated with a view. For scrollpanes for documents, the root
    * element of the document is returned.
    */
   nsIDOMElement findElementWithViewId(in nsViewID aId);
+
+  /**
+   * Same as enterModalState, but returns the window associated with the
+   * current JS context.
+   */
+  nsIDOMWindow enterModalStateWithWindow();
+
+  /**
+   * Same as leaveModalState, but takes a window associated with the active
+   * context when enterModalStateWithWindow was called. The currently context
+   * might be different at the moment (see bug 621764).
+   */
+  void leaveModalStateWithWindow(in nsIDOMWindow aWindow);
 };
--- a/dom/tests/mochitest/bugs/test_bug61098.html
+++ b/dom/tests/mochitest/bugs/test_bug61098.html
@@ -49,19 +49,19 @@ function registerMockPromptService()
       // immediately before showing a modal prompt, and leaves the modal state
       // when the prompt is dismissed by the user. This mock prompt doesn't
       // show anything to the user, so we only need to enter and immediately
       // leave the modal state -- this is done to trigger the necessary
       // accounting for triggering the "stop showing more prompts" code for
       // abusive pages.
       var winUtils = this.domWindow
                          .QueryInterface(Ci.nsIInterfaceRequestor)
-                         .getInterface(Ci.nsIDOMWindowUtils);
-      winUtils.enterModalState();
-      winUtils.leaveModalState();
+                         .getInterface(Ci.nsIDOMWindowUtils_MOZILLA_2_0_BRANCH);
+      var w = winUtils.enterModalStateWithWindow();
+      winUtils.leaveModalStateWithWindow(w);
     },
 
     alert: function(aDialogTitle, aText)
     {
       this._toggleModalState();
       promptState = {method: "alert",
                      parent: this.domWindow,
                      title: aDialogTitle,
--- a/embedding/components/windowwatcher/src/nsAutoWindowStateHelper.cpp
+++ b/embedding/components/windowwatcher/src/nsAutoWindowStateHelper.cpp
@@ -60,17 +60,17 @@ nsAutoWindowStateHelper::nsAutoWindowSta
   }
 }
 
 nsAutoWindowStateHelper::~nsAutoWindowStateHelper()
 {
   nsCOMPtr<nsPIDOMWindow> window(do_QueryInterface(mWindow));
 
   if (window) {
-    window->LeaveModalState();
+    window->LeaveModalState(nsnull);
   }
 
   if (mDefaultEnabled) {
     DispatchCustomEvent("DOMModalDialogClosed");
   }
 }
 
 PRBool
--- a/toolkit/components/prompts/src/nsPrompter.js
+++ b/toolkit/components/prompts/src/nsPrompter.js
@@ -415,32 +415,32 @@ function openModalWindow(domWin, uri, ar
 
     Services.ww.openWindow(domWin, uri, "_blank", "centerscreen,chrome,modal,titlebar", args);
 }
 
 function openTabPrompt(domWin, tabPrompt, args) {
     PromptUtils.fireDialogEvent(domWin, "DOMWillOpenModalDialog");
 
     let winUtils = domWin.QueryInterface(Ci.nsIInterfaceRequestor)
-                         .getInterface(Ci.nsIDOMWindowUtils);
-    winUtils.enterModalState();
+                         .getInterface(Ci.nsIDOMWindowUtils_MOZILLA_2_0_BRANCH);
+    let callerWin = winUtils.enterModalStateWithWindow();
 
     // We provide a callback so the prompt can close itself. We don't want to
     // wait for this event loop to return... Otherwise the presence of other
     // prompts on the call stack would in this dialog appearing unresponsive
     // until the other prompts had been closed.
     let callbackInvoked = false;
     function onPromptClose(forceCleanup) {
         if (!newPrompt && !forceCleanup)
             return;
         callbackInvoked = true;
         if (newPrompt)
             tabPrompt.removePrompt(newPrompt);
 
-        winUtils.leaveModalState();
+        winUtils.leaveModalStateWithWindow(callerWin);
 
         PromptUtils.fireDialogEvent(domWin, "DOMModalDialogClosed");
 
         // Restore focus to the previously focused element within tab.
         let fm = Cc["@mozilla.org/focus-manager;1"].
                  getService(Ci.nsIFocusManager);
         let e = fm.getFocusedElementForWindow(domWin.top, false, {});
         fm.setFocus(e, fm.FLAG_NOSCROLL);