Bug 498648 - Start private browsing while editing a message, cancel, doesn't cancel private browsing; r=bz,jst,ehsan
authorNochum Sossonko <highmind63@gmail.com>
Tue, 20 Oct 2009 10:19:43 -0400
changeset 34032 68490d9daf2309a55bfcdb3fc47cf5a664197efe
parent 34031 d82fe5d73822cc510690b9cabe9f1265c4808ee4
child 34033 1ebdaad9b24a5e08e6ebe5c01641b9dc0dc3576b
child 35999 f3175b1461360373451987db30e9e060bbeec09c
push idunknown
push userunknown
push dateunknown
reviewersbz, jst, ehsan
bugs498648
milestone1.9.3a1pre
Bug 498648 - Start private browsing while editing a message, cancel, doesn't cancel private browsing; r=bz,jst,ehsan
browser/components/privatebrowsing/src/nsPrivateBrowsingService.js
content/html/document/src/nsHTMLDocument.cpp
docshell/base/nsDocShell.cpp
docshell/base/nsIContentViewer.idl
dom/base/nsGlobalWindow.cpp
dom/src/jsurl/nsJSProtocolHandler.cpp
layout/base/nsDocumentViewer.cpp
--- a/browser/components/privatebrowsing/src/nsPrivateBrowsingService.js
+++ b/browser/components/privatebrowsing/src/nsPrivateBrowsingService.js
@@ -119,16 +119,19 @@ PrivateBrowsingService.prototype = {
   _alreadyChangingMode: false,
 
   // Whether the private browsing mode has been started automatically (ie. always-on)
   _autoStarted: false,
 
   // List of view source window URIs for restoring later
   _viewSrcURLs: [],
 
+  // List of nsIXULWindows we are going to be closing during the transition
+  _windowsToClose: [],
+
   // XPCOM registration
   classDescription: "PrivateBrowsing Service",
   contractID: "@mozilla.org/privatebrowsing;1",
   classID: Components.ID("{c31f4883-839b-45f6-82ad-a6a9bc5ad599}"),
   _xpcom_categories: [
     { category: "command-line-handler", entry: "m-privatebrowsing" },
     { category: "app-startup", service: true }
   ],
@@ -203,22 +206,30 @@ PrivateBrowsingService.prototype = {
         // separation between private and non-private sessions
         if (browserWindow) {
           // set an empty session to transition from/to pb mode, see bug 476463
           ss.setBrowserState(blankState);
 
           // just in case the only remaining window after setBrowserState is different.
           // it probably shouldn't be with the current sessionstore impl, but we shouldn't
           // rely on behaviour the API doesn't guarantee
-          let browser = this._getBrowserWindow().gBrowser;
+          browserWindow = this._getBrowserWindow();
+          let browser = browserWindow.gBrowser;
 
           // this ensures a clean slate from which to transition into or out of
           // private browsing
           browser.addTab();
+          browser.getBrowserForTab(browser.tabContainer.firstChild).stop();
           browser.removeTab(browser.tabContainer.firstChild);
+          browserWindow.getInterface(Ci.nsIWebNavigation)
+                       .QueryInterface(Ci.nsIDocShellTreeItem)
+                       .treeOwner
+                       .QueryInterface(Ci.nsIInterfaceRequestor)
+                       .getInterface(Ci.nsIXULWindow)
+                       .docShell.contentViewer.resetCloseWindow();
         }
       }
     }
     else
       this._saveSession = false;
   },
 
   _onAfterPrivateBrowsingModeChange: function PBS__onAfterPrivateBrowsingModeChange() {
@@ -293,16 +304,30 @@ PrivateBrowsingService.prototype = {
   },
 
   _getBrowserWindow: function PBS__getBrowserWindow() {
     return Cc["@mozilla.org/appshell/window-mediator;1"].
            getService(Ci.nsIWindowMediator).
            getMostRecentWindow("navigator:browser");
   },
 
+  _ensureCanCloseWindows: function PBS__ensureCanCloseWindows() {
+    let windowMediator = Cc["@mozilla.org/appshell/window-mediator;1"].
+                         getService(Ci.nsIWindowMediator);
+    let windowsEnum = windowMediator.getXULWindowEnumerator("navigator:browser");
+
+    while (windowsEnum.hasMoreElements()) {
+      let win = windowsEnum.getNext().QueryInterface(Ci.nsIXULWindow);
+      if (win.docShell.contentViewer.permitUnload(true))
+        this._windowsToClose.push(win);
+      else
+        throw Cr.NS_ERROR_ABORT;
+    }
+  },
+
   _closePageInfoWindows: function PBS__closePageInfoWindows() {
     let pageInfoEnum = Cc["@mozilla.org/appshell/window-mediator;1"].
                        getService(Ci.nsIWindowMediator).
                        getEnumerator("Browser:page-info");
     while (pageInfoEnum.hasMoreElements()) {
       let win = pageInfoEnum.getNext();
       win.close();
     }
@@ -396,16 +421,18 @@ PrivateBrowsingService.prototype = {
           if (!this._canEnterPrivateBrowsingMode())
             return;
         }
         else {
           if (!this._canLeavePrivateBrowsingMode())
             return;
         }
 
+        this._ensureCanCloseWindows();
+
         this._autoStarted = this._prefs.getBoolPref("browser.privatebrowsing.autostart");
         this._inPrivateBrowsing = val != false;
 
         let data = val ? "enter" : "exit";
 
         let quitting = Cc["@mozilla.org/supports-PRBool;1"].
                        createInstance(Ci.nsISupportsPRBool);
         quitting.data = this._quitting;
@@ -417,19 +444,26 @@ PrivateBrowsingService.prototype = {
         this._onBeforePrivateBrowsingModeChange();
 
         this._obs.notifyObservers(quitting, "private-browsing", data);
 
         // load the appropriate session
         this._onAfterPrivateBrowsingModeChange();
       }
     } catch (ex) {
-      Cu.reportError("Exception thrown while processing the " +
-        "private browsing mode change request: " + ex.toString());
+      // We aborted the transition to/from private browsing, we must restore the
+      // beforeunload handling on all the windows for which we switched it off.
+      for (let i = 0; i < this._windowsToClose.length; i++)
+        this._windowsToClose[i].docShell.contentViewer.resetCloseWindow();
+      // We don't log an error when the transition is canceled from beforeunload
+      if (ex != Cr.NS_ERROR_ABORT)
+        Cu.reportError("Exception thrown while processing the " +
+          "private browsing mode change request: " + ex.toString());
     } finally {
+      this._windowsToClose = [];
       this._alreadyChangingMode = false;
     }
   },
 
   /**
    * Whether private browsing has been started automatically.
    */
   get autoStarted PBS_get_autoStarted() {
--- a/content/html/document/src/nsHTMLDocument.cpp
+++ b/content/html/document/src/nsHTMLDocument.cpp
@@ -1865,17 +1865,17 @@ nsHTMLDocument::OpenCommon(const nsACStr
 
   // Stop current loads targeted at the window this document is in.
   if (mScriptGlobalObject) {
     nsCOMPtr<nsIContentViewer> cv;
     shell->GetContentViewer(getter_AddRefs(cv));
 
     if (cv) {
       PRBool okToUnload;
-      rv = cv->PermitUnload(&okToUnload);
+      rv = cv->PermitUnload(PR_FALSE, &okToUnload);
 
       if (NS_SUCCEEDED(rv) && !okToUnload) {
         // We don't want to unload, so stop here, but don't throw an
         // exception.
         return NS_OK;
       }
     }
 
--- a/docshell/base/nsDocShell.cpp
+++ b/docshell/base/nsDocShell.cpp
@@ -6144,17 +6144,17 @@ nsDocShell::CreateAboutBlankContentViewe
   
   if (mContentViewer) {
     // We've got a content viewer already. Make sure the user
     // permits us to discard the current document and replace it
     // with about:blank. And also ensure we fire the unload events
     // in the current document.
 
     PRBool okToUnload;
-    rv = mContentViewer->PermitUnload(&okToUnload);
+    rv = mContentViewer->PermitUnload(PR_FALSE, &okToUnload);
 
     if (NS_SUCCEEDED(rv) && !okToUnload) {
       // The user chose not to unload the page, interrupt the load.
       return NS_ERROR_FAILURE;
     }
 
     mSavingOldViewer = CanSavePresentation(LOAD_NORMAL, nsnull, nsnull);
 
@@ -7934,17 +7934,17 @@ nsDocShell::InternalLoad(nsIURI * aURI,
     // Hold onto |this| until we return, to prevent a crash from happening. 
     // (bug#331040)
     nsCOMPtr<nsIDocShell> kungFuDeathGrip(this);
 
     // Check if the page doesn't want to be unloaded. The javascript:
     // protocol handler deals with this for javascript: URLs.
     if (!bIsJavascript && mContentViewer) {
         PRBool okToUnload;
-        rv = mContentViewer->PermitUnload(&okToUnload);
+        rv = mContentViewer->PermitUnload(PR_FALSE, &okToUnload);
 
         if (NS_SUCCEEDED(rv) && !okToUnload) {
             // The user chose not to unload the page, interrupt the
             // load.
             return NS_OK;
         }
     }
 
--- a/docshell/base/nsIContentViewer.idl
+++ b/docshell/base/nsIContentViewer.idl
@@ -8,28 +8,48 @@ interface nsIPrintSettings;
 %{ C++
 class nsIWidget;
 struct nsIntRect;
 %}
 
 [ptr] native nsIWidgetPtr(nsIWidget);
 [ref] native nsIntRectRef(nsIntRect);
 
-[scriptable, uuid(c9aba5da-7d8b-46a8-87cd-9ab7e16480b8)]
+[scriptable, uuid(08665a60-b398-11de-8a39-0800200c9a66)]
 interface nsIContentViewer : nsISupports
 {
 
   [noscript] void init(in nsIWidgetPtr aParentWidget,
                        [const] in nsIntRectRef aBounds);
 
   attribute nsISupports container;
 
   void loadStart(in nsISupports aDoc);
   void loadComplete(in unsigned long aStatus);
-  boolean permitUnload();
+
+  /**
+   * Checks if the document wants to prevent unloading by firing beforeunload on
+   * the document, and if it does, prompts the user. The result is returned.
+   *
+   * @param aCallerClosesWindow indicates that the current caller will close the
+   *        window. If the method returns true, all subsequent calls will be
+   *        ignored.
+   */
+  boolean permitUnload([optional] in boolean aCallerClosesWindow);
+
+  /**
+   * Works in tandem with permitUnload, if the caller decides not to close the
+   * window it indicated it will, it is the caller's responsibility to reset
+   * that with this method.
+   *
+   * @Note this method is only meant to be called on documents for which the
+   *  caller has indicated that it will close the window. If that is not the case
+   *  the behavior of this method is undefined.
+   */
+  void resetCloseWindow();
   void pageHide(in boolean isUnload);
 
   /**
    * All users of a content viewer are responsible for calling both
    * close() and destroy(), in that order. 
    *
    * close() should be called when the load of a new page for the next
    * content viewer begins, and destroy() should be called when the next
--- a/dom/base/nsGlobalWindow.cpp
+++ b/dom/base/nsGlobalWindow.cpp
@@ -5535,17 +5535,17 @@ nsGlobalWindow::Close()
   // Close() as soon as it is possible for the window to close.
   // This allows us to not close the window while printing is happening.
 
   nsCOMPtr<nsIContentViewer> cv;
   mDocShell->GetContentViewer(getter_AddRefs(cv));
   if (!mInClose && !mIsClosed && cv) {
     PRBool canClose;
 
-    rv = cv->PermitUnload(&canClose);
+    rv = cv->PermitUnload(PR_FALSE, &canClose);
     if (NS_SUCCEEDED(rv) && !canClose)
       return NS_OK;
 
     rv = cv->RequestWindowClose(&canClose);
     if (NS_SUCCEEDED(rv) && !canClose)
       return NS_OK;
   }
 
--- a/dom/src/jsurl/nsJSProtocolHandler.cpp
+++ b/dom/src/jsurl/nsJSProtocolHandler.cpp
@@ -779,17 +779,17 @@ nsJSChannel::EvaluateScript()
         NS_QueryNotificationCallbacks(mStreamChannel, docShell);
         if (docShell) {
             nsCOMPtr<nsIContentViewer> cv;
             docShell->GetContentViewer(getter_AddRefs(cv));
 
             if (cv) {
                 PRBool okToUnload;
 
-                if (NS_SUCCEEDED(cv->PermitUnload(&okToUnload)) &&
+                if (NS_SUCCEEDED(cv->PermitUnload(PR_FALSE, &okToUnload)) &&
                     !okToUnload) {
                     // The user didn't want to unload the current
                     // page, translate this into an undefined
                     // return from the javascript: URL...
                     mStatus = NS_ERROR_DOM_RETVAL_UNDEFINED;
                 }
             }
         }
--- a/layout/base/nsDocumentViewer.cpp
+++ b/layout/base/nsDocumentViewer.cpp
@@ -492,16 +492,17 @@ protected:
   /* character set member data */
   PRInt32 mHintCharsetSource;
   nsCString mHintCharset;
   nsCString mDefaultCharacterSet;
   nsCString mForceCharacterSet;
   nsCString mPrevDocCharacterSet;
   
   PRPackedBool mIsPageMode;
+  PRPackedBool mCallerIsClosingWindow;
 
 };
 
 //------------------------------------------------------------------
 // DocumentViewerImpl
 //------------------------------------------------------------------
 // Class IDs
 static NS_DEFINE_CID(kViewManagerCID,       NS_VIEW_MANAGER_CID);
@@ -523,16 +524,17 @@ NS_NewDocumentViewer(nsIDocumentViewer**
 }
 
 void DocumentViewerImpl::PrepareToStartLoad()
 {
   mEnableRendering  = PR_TRUE;
   mStopped          = PR_FALSE;
   mLoaded           = PR_FALSE;
   mDeferredWindowClose = PR_FALSE;
+  mCallerIsClosingWindow = PR_FALSE;
 
 #ifdef NS_PRINTING
   mPrintIsPending        = PR_FALSE;
   mPrintDocIsFullyLoaded = PR_FALSE;
   mClosingWhilePrinting  = PR_FALSE;
 
   // Make sure we have destroyed it and cleared the data member
   if (mPrintEngine) {
@@ -1078,21 +1080,21 @@ DocumentViewerImpl::LoadComplete(nsresul
     mCachedPrintWebProgressListner = nsnull;
   }
 #endif
 
   return rv;
 }
 
 NS_IMETHODIMP
-DocumentViewerImpl::PermitUnload(PRBool *aPermitUnload)
+DocumentViewerImpl::PermitUnload(PRBool aCallerClosesWindow, PRBool *aPermitUnload)
 {
   *aPermitUnload = PR_TRUE;
 
-  if (!mDocument || mInPermitUnload) {
+  if (!mDocument || mInPermitUnload || mCallerIsClosingWindow) {
     return NS_OK;
   }
 
   // First, get the script global object from the document...
   nsPIDOMWindow *window = mDocument->GetWindow();
 
   if (!window) {
     // This is odd, but not fatal
@@ -1188,25 +1190,57 @@ DocumentViewerImpl::PermitUnload(PRBool 
     for (PRInt32 i = 0; i < childCount && *aPermitUnload; ++i) {
       nsCOMPtr<nsIDocShellTreeItem> item;
       docShellNode->GetChildAt(i, getter_AddRefs(item));
 
       nsCOMPtr<nsIDocShell> docShell(do_QueryInterface(item));
 
       if (docShell) {
         nsCOMPtr<nsIContentViewer> cv;
-      docShell->GetContentViewer(getter_AddRefs(cv));
-
-      if (cv) {
-        cv->PermitUnload(aPermitUnload);
+        docShell->GetContentViewer(getter_AddRefs(cv));
+
+        if (cv) {
+          cv->PermitUnload(aCallerClosesWindow, aPermitUnload);
         }
       }
     }
   }
 
+  if (aCallerClosesWindow && *aPermitUnload)
+    mCallerIsClosingWindow = PR_TRUE;
+
+  return NS_OK;
+}
+
+NS_IMETHODIMP
+DocumentViewerImpl::ResetCloseWindow()
+{
+  mCallerIsClosingWindow = PR_FALSE;
+
+  nsCOMPtr<nsIDocShellTreeNode> docShellNode(do_QueryReferent(mContainer));
+  if (docShellNode) {
+    PRInt32 childCount;
+    docShellNode->GetChildCount(&childCount);
+
+    for (PRInt32 i = 0; i < childCount; ++i) {
+      nsCOMPtr<nsIDocShellTreeItem> item;
+      docShellNode->GetChildAt(i, getter_AddRefs(item));
+
+      nsCOMPtr<nsIDocShell> docShell(do_QueryInterface(item));
+
+      if (docShell) {
+        nsCOMPtr<nsIContentViewer> cv;
+        docShell->GetContentViewer(getter_AddRefs(cv));
+
+        if (cv) {
+          cv->ResetCloseWindow();
+        }
+      }
+    }
+  }
   return NS_OK;
 }
 
 NS_IMETHODIMP
 DocumentViewerImpl::PageHide(PRBool aIsUnload)
 {
   mEnableRendering = PR_FALSE;