Bug 383760, Add quit confirmation dialog, r=Mano, ui-r=beltzner
authorflamingice@sourmilk.net
Fri, 15 Jun 2007 11:01:25 -0700
changeset 2442 04a55431795e7869f164863f1c11e88fba74bae7
parent 2441 eae491fbdbbb6b98daed63c0bbf93e4016ed3267
child 2443 733641d9feafe27a33c6e66c80a941148326b250
child 2447 4f31988099041703fd35e2e2e213e39a5a0c4078
push idunknown
push userunknown
push dateunknown
reviewersMano, beltzner
bugs383760
milestone1.9a6pre
Bug 383760, Add quit confirmation dialog, r=Mano, ui-r=beltzner
browser/app/profile/firefox.js
browser/base/content/browser.js
browser/components/nsBrowserGlue.js
browser/locales/en-US/chrome/browser/quitDialog.properties
browser/locales/jar.mn
toolkit/content/globalOverlay.js
--- a/browser/app/profile/firefox.js
+++ b/browser/app/profile/firefox.js
@@ -182,16 +182,17 @@ pref("browser.startup.homepage",        
 
 pref("browser.cache.disk.capacity",         50000);
 pref("browser.enable_automatic_image_resizing", true);
 pref("browser.urlbar.autoFill", false);
 pref("browser.urlbar.matchOnlyTyped", false);
 pref("browser.chrome.site_icons", true);
 pref("browser.chrome.favicons", true);
 pref("browser.formfill.enable", true);
+pref("browser.warnOnQuit", true);
 
 pref("browser.download.useDownloadDir", true);
 pref("browser.download.folderList", 0);
 pref("browser.download.manager.showAlertOnComplete", true);
 pref("browser.download.manager.showAlertInterval", 2000);
 pref("browser.download.manager.retention", 2);
 pref("browser.download.manager.showWhenStarting", true);
 pref("browser.download.manager.useWindow", true);
--- a/browser/base/content/browser.js
+++ b/browser/base/content/browser.js
@@ -769,18 +769,16 @@ function xpinstallEditPermissions(aDocSh
 
   return true;
 }
 
 function BrowserStartup()
 {
   gBrowser = document.getElementById("content");
 
-  window.tryToClose = WindowIsClosing;
-
   var uriToLoad = null;
   // Check for window.arguments[0]. If present, use that for uriToLoad.
   if ("arguments" in window && window.arguments[0])
     uriToLoad = window.arguments[0];
 
   gIsLoadingBlank = uriToLoad == "about:blank";
 
   prepareForStartup();
@@ -1210,16 +1208,24 @@ function BrowserShutdown()
     Components.utils.reportError(ex);
   }
 
   if (gSanitizeListener)
     gSanitizeListener.shutdown();
 
   BrowserOffline.uninit();
 
+  // Store current window position/size into the window attributes 
+  // for persistence.
+  var win = document.documentElement;
+  win.setAttribute("x", window.screenX);
+  win.setAttribute("y", window.screenY);
+  win.setAttribute("height", window.outerHeight);
+  win.setAttribute("width", window.outerWidth);
+
   var windowManager = Components.classes['@mozilla.org/appshell/window-mediator;1'].getService();
   var windowManagerInterface = windowManager.QueryInterface(Components.interfaces.nsIWindowMediator);
   var enumerator = windowManagerInterface.getEnumerator(null);
   enumerator.getNext();
   if (!enumerator.hasMoreElements()) {
     document.persist("sidebar-box", "sidebarcommand");
     document.persist("sidebar-box", "width");
     document.persist("sidebar-box", "src");
@@ -1921,48 +1927,23 @@ function BrowserCloseTabOrWindow()
     // Replace the remaining tab with a blank one and focus the address bar
     gBrowser.removeCurrentTab();
     if (gURLBar)
       setTimeout(function() { gURLBar.focus(); }, 0);
     return;
   }
 #endif
 
-  BrowserCloseWindow();
+  closeWindow(true);
 }
 
 function BrowserTryToCloseWindow()
 {
-  //give tryToClose a chance to veto if it is defined
-  if (typeof(window.tryToClose) != "function" || window.tryToClose())
-    BrowserCloseWindow();
-}
-
-function BrowserCloseWindow()
-{
-  // This code replicates stuff in BrowserShutdown().  It is here because
-  // window.screenX and window.screenY have real values.  We need
-  // to fix this eventually but by replicating the code here, we
-  // provide a means of saving position (it just requires that the
-  // user close the window via File->Close (vs. close box).
-
-  // Get the current window position/size.
-  var x = window.screenX;
-  var y = window.screenY;
-  var h = window.outerHeight;
-  var w = window.outerWidth;
-
-  // Store these into the window attributes (for persistence).
-  var win = document.getElementById( "main-window" );
-  win.setAttribute( "x", x );
-  win.setAttribute( "y", y );
-  win.setAttribute( "height", h );
-  win.setAttribute( "width", w );
-
-  closeWindow(true);
+  if (WindowIsClosing())
+    window.close();     // WindowIsClosing does all the necessary checks
 }
 
 function loadURI(uri, referrer, postData, allowThirdPartyFixup)
 {
   try {
     if (postData === undefined)
       postData = null;
     var flags = nsIWebNavigation.LOAD_FLAGS_NONE;
@@ -5095,29 +5076,35 @@ var BrowserOffline = {
   }
 };
 
 function WindowIsClosing()
 {
   var browser = getBrowser();
   var cn = browser.tabContainer.childNodes;
   var numtabs = cn.length;
-  var reallyClose = browser.warnAboutClosingTabs(true);
+  var reallyClose = true;
 
   for (var i = 0; reallyClose && i < numtabs; ++i) {
     var ds = browser.getBrowserForTab(cn[i]).docShell;
 
     if (ds.contentViewer && !ds.contentViewer.permitUnload())
       reallyClose = false;
   }
 
-  if (reallyClose)
-    return closeWindow(false);
-
-  return reallyClose;
+  if (!reallyClose)
+    return false;
+
+  // closeWindow takes a second optional function argument to open up a
+  // window closing warning dialog if we're not quitting. (Quitting opens
+  // up another dialog so we don't need to.)
+  return closeWindow(false,
+    function () {
+      return browser.warnAboutClosingTabs(true);
+    });
 }
 
 var MailIntegration = {
   sendLinkForWindow: function (aWindow) {
     this.sendMessage(aWindow.location.href,
                      aWindow.document.title);
   },
 
--- a/browser/components/nsBrowserGlue.js
+++ b/browser/components/nsBrowserGlue.js
@@ -40,16 +40,18 @@
 // Constructor
 
 function BrowserGlue() {
   this._init();
   this._profileStarted = false;
 }
 
 BrowserGlue.prototype = {
+  _saveSession: false,
+
   QueryInterface: function(iid) 
   {
      xpcomCheckInterfaces(iid, kServiceIIds, Components.results.NS_ERROR_NO_INTERFACE);
      return this;
   }
 ,
   // nsIObserver implementation 
   observe: function(subject, topic, data) 
@@ -69,43 +71,57 @@ BrowserGlue.prototype = {
         break;
       case "browser:purge-session-history":
         // reset the console service's error buffer
         const cs = Components.classes["@mozilla.org/consoleservice;1"]
                              .getService(Components.interfaces.nsIConsoleService);
         cs.logStringMessage(null); // clear the console (in case it's open)
         cs.reset();
         break;
+      case "quit-application-requested":
+        this._onQuitRequest(subject);
+        break;
+      case "quit-application-granted":
+        if (this._saveSession) {
+          var prefService = Components.classes["@mozilla.org/preferences-service;1"]
+                                      .getService(Components.interfaces.nsIPrefBranch);
+          prefService.setBoolPref("browser.sessionstore.resume_session_once", true);
+        }
+        break;
     }
   }
 , 
   // initialization (called on application startup) 
   _init: function() 
   {
     // observer registration
     const osvr = Components.classes['@mozilla.org/observer-service;1']
                            .getService(Components.interfaces.nsIObserverService);
     osvr.addObserver(this, "profile-before-change", false);
     osvr.addObserver(this, "profile-change-teardown", false);
     osvr.addObserver(this, "xpcom-shutdown", false);
     osvr.addObserver(this, "final-ui-startup", false);
     osvr.addObserver(this, "browser:purge-session-history", false);
+    osvr.addObserver(this, "quit-application-requested", false);
+    osvr.addObserver(this, "quit-application-granted", false);
   },
 
   // cleanup (called on application shutdown)
   _dispose: function() 
   {
     // observer removal 
     const osvr = Components.classes['@mozilla.org/observer-service;1']
                            .getService(Components.interfaces.nsIObserverService);
     osvr.removeObserver(this, "profile-before-change");
     osvr.removeObserver(this, "profile-change-teardown");
     osvr.removeObserver(this, "xpcom-shutdown");
     osvr.removeObserver(this, "final-ui-startup");
     osvr.removeObserver(this, "browser:purge-session-history");
+    osvr.removeObserver(this, "quit-application-requested");
+    osvr.removeObserver(this, "quit-application-granted");
   },
 
   // profile startup handler (contains profile initialization routines)
   _onProfileStartup: function() 
   {
     // check to see if the EULA must be shown on startup
     try {
       var mustDisplayEULA = true;
@@ -164,16 +180,102 @@ BrowserGlue.prototype = {
       this.Sanitizer.onShutdown();
 
     } catch(ex) {
     } finally {
       appStartup.exitLastWindowClosingSurvivalArea();
     }
   },
 
+  _onQuitRequest: function(aCancelQuit)
+  {
+    var wm = Components.classes["@mozilla.org/appshell/window-mediator;1"]
+                       .getService(Components.interfaces.nsIWindowMediator);
+    var windowcount = 0;
+    var pagecount = 0;
+    var browserEnum = wm.getEnumerator("navigator:browser");
+    while (browserEnum.hasMoreElements()) {
+      windowcount++;
+
+      var browser = browserEnum.getNext();
+      var tabbrowser = browser.document.getElementById("content");
+      if (tabbrowser)
+        pagecount += tabbrowser.browsers.length;
+    }
+
+    this._saveSession = false;
+    if (pagecount < 2)
+      return;
+
+    var prefService = Components.classes["@mozilla.org/preferences-service;1"]
+                                .getService(Components.interfaces.nsIPrefBranch);
+    var showPrompt = true;
+    try {
+      if (prefService.getIntPref("browser.startup.page") == 3 ||
+          prefService.getBoolPref("browser.sessionstore.resume_session_once"))
+        showPrompt = false;
+      else
+        showPrompt = prefService.getBoolPref("browser.warnOnQuit");
+    } catch (ex) {}
+
+    var buttonChoice = 0;
+    if (showPrompt) {
+      var bundleService = Components.classes["@mozilla.org/intl/stringbundle;1"]
+                                    .getService(Components.interfaces.nsIStringBundleService);
+      var quitBundle = bundleService.createBundle("chrome://browser/locale/quitDialog.properties");
+      var brandBundle = bundleService.createBundle("chrome://branding/locale/brand.properties");
+
+      var appName = brandBundle.GetStringFromName("brandShortName");
+      var quitDialogTitle = quitBundle.formatStringFromName("quitDialogTitle",
+                                                            [appName], 1);
+      var quitTitle = quitBundle.GetStringFromName("quitTitle");
+      var cancelTitle = quitBundle.GetStringFromName("cancelTitle");
+      var saveTitle = quitBundle.GetStringFromName("saveTitle");
+      var neverAskText = quitBundle.GetStringFromName("neverAsk");
+
+      if (windowcount == 1)
+        var message = quitBundle.formatStringFromName("messageNoWindows",
+                                                      [appName], 1);
+      else
+        var message = quitBundle.formatStringFromName("message",
+                                                      [appName], 1);
+
+      var promptService = Components.classes["@mozilla.org/embedcomp/prompt-service;1"]
+                                    .getService(Components.interfaces.nsIPromptService);
+
+      var flags = promptService.BUTTON_TITLE_IS_STRING * promptService.BUTTON_POS_0 +
+                  promptService.BUTTON_TITLE_IS_STRING * promptService.BUTTON_POS_1 +
+                  promptService.BUTTON_TITLE_IS_STRING * promptService.BUTTON_POS_2 +
+                  promptService.BUTTON_POS_0_DEFAULT;
+      var neverAsk = {value:false};
+      buttonChoice = promptService.confirmEx(null, quitDialogTitle, message,
+                                   flags, quitTitle, cancelTitle, saveTitle,
+                                   neverAskText, neverAsk);
+
+      switch (buttonChoice) {
+      case 0:
+        if (neverAsk.value)
+          prefService.setBoolPref("browser.warnOnQuit", false);
+        break;
+      case 1:
+        aCancelQuit.QueryInterface(Components.interfaces.nsISupportsPRBool);
+        aCancelQuit.data = true;
+        break;
+      case 2:
+        // could also set browser.warnOnQuit to false here,
+        // but not setting it is a little safer.
+        if (neverAsk.value)
+          prefService.setIntPref("browser.startup.page", 3);
+        break;
+      }
+
+      this._saveSession = buttonChoice == 2;
+    }
+  },
+
   // returns the (cached) Sanitizer constructor
   get Sanitizer() 
   {
     if(typeof(Sanitizer) != "function") { // we should dynamically load the script
       Components.classes["@mozilla.org/moz/jssubscript-loader;1"]
                 .getService(Components.interfaces.mozIJSSubScriptLoader)
                 .loadSubScript("chrome://browser/content/sanitize.js", null);
     }
new file mode 100644
--- /dev/null
+++ b/browser/locales/en-US/chrome/browser/quitDialog.properties
@@ -0,0 +1,8 @@
+quitDialogTitle=Quit %S
+
+quitTitle=&Quit
+cancelTitle=&Cancel
+saveTitle=&Save and quit
+neverAsk=Do not ask next time
+message=Do you want %S to save your tabs and windows for the next time it starts?
+messageNoWindows=Do you want %S to save your tabs for the next time it starts?
--- a/browser/locales/jar.mn
+++ b/browser/locales/jar.mn
@@ -10,16 +10,17 @@
     locale/browser/metaData.dtd                    (%chrome/browser/metaData.dtd)
     locale/browser/metaData.properties             (%chrome/browser/metaData.properties)
     locale/browser/openLocation.dtd                (%chrome/browser/openLocation.dtd)
     locale/browser/openLocation.properties         (%chrome/browser/openLocation.properties)
 *   locale/browser/pageInfo.dtd                    (%chrome/browser/pageInfo.dtd)
     locale/browser/pageInfo.properties             (%chrome/browser/pageInfo.properties)
     locale/browser/pageReport.dtd                  (%chrome/browser/pageReport.dtd)
     locale/browser/pageReportFirstTime.dtd         (%chrome/browser/pageReportFirstTime.dtd)
+    locale/browser/quitDialog.properties           (%chrome/browser/quitDialog.properties)
 *   locale/browser/safeMode.dtd                    (%chrome/browser/safeMode.dtd)
     locale/browser/sanitize.dtd                    (%chrome/browser/sanitize.dtd)
     locale/browser/search.properties               (%chrome/browser/search.properties)
     locale/browser/searchbar.dtd                   (%chrome/browser/searchbar.dtd)
     locale/browser/engineManager.dtd               (%chrome/browser/engineManager.dtd)
     locale/browser/setDesktopBackground.dtd        (%chrome/browser/setDesktopBackground.dtd)
     locale/browser/shellservice.properties         (%chrome/browser/shellservice.properties)
     locale/browser/sessionstore.properties         (%chrome/browser/sessionstore.properties)
--- a/toolkit/content/globalOverlay.js
+++ b/toolkit/content/globalOverlay.js
@@ -1,9 +1,9 @@
-function closeWindow(aClose)
+function closeWindow(aClose, aPromptFunction)
 {
   var windowCount = 0;
    var wm = Components.classes["@mozilla.org/appshell/window-mediator;1"]
                       .getService(Components.interfaces.nsIWindowMediator);
   var e = wm.getEnumerator(null);
   
   while (e.hasMoreElements()) {
     var w = e.getNext();
@@ -12,16 +12,18 @@ function closeWindow(aClose)
   }
 
 # Closing the last window doesn't quit the application on OS X.
 #ifndef XP_MACOSX
   // If we're down to the last window and someone tries to shut down, check to make sure we can!
   if (windowCount == 1 && !canQuitApplication())
     return false;
 #endif
+  if (windowCount != 1 && typeof(aPromptFunction) == "function" && !aPromptFunction())
+    return false;
 
   if (aClose)    
     window.close();
   
   return true;
 }
 
 function canQuitApplication()