Back to index

lightning-sunbird  0.9+nobinonly
nsAppStartup.cpp
Go to the documentation of this file.
00001 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
00002 /* ***** BEGIN LICENSE BLOCK *****
00003  * Version: MPL 1.1/GPL 2.0/LGPL 2.1
00004  *
00005  * The contents of this file are subject to the Mozilla Public License Version
00006  * 1.1 (the "License"); you may not use this file except in compliance with
00007  * the License. You may obtain a copy of the License at
00008  * http://www.mozilla.org/MPL/
00009  *
00010  * Software distributed under the License is distributed on an "AS IS" basis,
00011  * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
00012  * for the specific language governing rights and limitations under the
00013  * License.
00014  *
00015  * The Original Code is Mozilla Communicator client code. This file was split
00016  * from xpfe/appshell/src/nsAppShellService.cpp
00017  *
00018  * The Initial Developer of the Original Code is
00019  * Netscape Communications Corporation.
00020  * Portions created by the Initial Developer are Copyright (C) 1998
00021  * the Initial Developer. All Rights Reserved.
00022  *
00023  * Contributor(s):
00024  *   Pierre Phaneuf <pp@ludusdesign.com>
00025  *   Robert O'Callahan <roc+moz@cs.cmu.edu>
00026  *   Benjamin Smedberg <bsmedberg@covad.net>
00027  *
00028  * Alternatively, the contents of this file may be used under the terms of
00029  * either of the GNU General Public License Version 2 or later (the "GPL"),
00030  * or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
00031  * in which case the provisions of the GPL or the LGPL are applicable instead
00032  * of those above. If you wish to allow use of your version of this file only
00033  * under the terms of either the GPL or the LGPL, and not to allow others to
00034  * use your version of this file under the terms of the MPL, indicate your
00035  * decision by deleting the provisions above and replace them with the notice
00036  * and other provisions required by the GPL or the LGPL. If you do not delete
00037  * the provisions above, a recipient may use your version of this file under
00038  * the terms of any one of the MPL, the GPL or the LGPL.
00039  *
00040  * ***** END LICENSE BLOCK ***** */
00041 
00042 #include "nsAppStartup.h"
00043 
00044 #include "nsIAppShellService.h"
00045 #include "nsICloseAllWindows.h"
00046 #include "nsIDOMWindowInternal.h"
00047 #include "nsIEventQueue.h"
00048 #include "nsIEventQueueService.h"
00049 #include "nsIInterfaceRequestor.h"
00050 #include "nsILocalFile.h"
00051 #include "nsIObserverService.h"
00052 #include "nsIPrefBranch.h"
00053 #include "nsIPrefService.h"
00054 #include "nsIProfileChangeStatus.h"
00055 #include "nsIPromptService.h"
00056 #include "nsIStringBundle.h"
00057 #include "nsISupportsPrimitives.h"
00058 #include "nsITimelineService.h"
00059 #include "nsIWebBrowserChrome.h"
00060 #include "nsIWindowMediator.h"
00061 #include "nsIWindowWatcher.h"
00062 #include "nsIXULWindow.h"
00063 #include "nsNativeCharsetUtils.h"
00064 #include "nsString.h"
00065 
00066 #include "prprf.h"
00067 #include "nsCRT.h"
00068 #include "nsEventQueueUtils.h"
00069 #include "nsIInterfaceRequestorUtils.h"
00070 #include "nsWidgetsCID.h"
00071 #include "nsAppShellCID.h"
00072 #include "nsXPFEComponentsCID.h"
00073 
00074 NS_DEFINE_CID(kAppShellCID, NS_APPSHELL_CID);
00075 
00076 //
00077 // nsAppStartup
00078 //
00079 
00080 nsAppStartup::nsAppStartup() :
00081   mConsiderQuitStopper(0),
00082   mRunning(PR_FALSE),
00083   mShuttingDown(PR_FALSE),
00084   mAttemptingQuit(PR_FALSE),
00085   mRestart(PR_FALSE)
00086 { }
00087 
00088 
00089 nsresult
00090 nsAppStartup::Init()
00091 {
00092   nsresult rv;
00093 
00094   // Create widget application shell
00095   mAppShell = do_CreateInstance(kAppShellCID, &rv);
00096   NS_ENSURE_SUCCESS(rv, rv);
00097 
00098   rv = mAppShell->Create(nsnull, nsnull);
00099   NS_ENSURE_SUCCESS(rv, rv);
00100 
00101   // listen to EventQueues' comings and goings. do this after the appshell
00102   // has been created, but after the event queue has been created. that
00103   // latter bit is unfortunate, but we deal with it.
00104   nsCOMPtr<nsIObserverService> os
00105     (do_GetService("@mozilla.org/observer-service;1", &rv));
00106   NS_ENSURE_SUCCESS(rv, rv);
00107 
00108   os->AddObserver(this, "nsIEventQueueActivated", PR_TRUE);
00109   os->AddObserver(this, "nsIEventQueueDestroyed", PR_TRUE);
00110   os->AddObserver(this, "profile-change-teardown", PR_TRUE);
00111   os->AddObserver(this, "xul-window-registered", PR_TRUE);
00112   os->AddObserver(this, "xul-window-destroyed", PR_TRUE);
00113 
00114   return NS_OK;
00115 }
00116 
00117 
00118 //
00119 // nsAppStartup->nsISupports
00120 //
00121 
00122 NS_IMPL_THREADSAFE_ISUPPORTS5(nsAppStartup,
00123                               nsIAppStartup,
00124                               nsIWindowCreator,
00125                               nsIWindowCreator2,
00126                               nsIObserver,
00127                               nsISupportsWeakReference)
00128 
00129 
00130 //
00131 // nsAppStartup->nsIAppStartup
00132 //
00133 
00134 NS_IMETHODIMP
00135 nsAppStartup::CreateHiddenWindow()
00136 {
00137   nsCOMPtr<nsIAppShellService> appShellService
00138     (do_GetService(NS_APPSHELLSERVICE_CONTRACTID));
00139   NS_ENSURE_TRUE(appShellService, NS_ERROR_FAILURE);
00140 
00141   return appShellService->CreateHiddenWindow(mAppShell);
00142 }
00143 
00144 
00145 NS_IMETHODIMP
00146 nsAppStartup::Run(void)
00147 {
00148   if (!mShuttingDown) {
00149     mRunning = PR_TRUE;
00150 
00151     nsresult rv = mAppShell->Run();
00152     if (NS_FAILED(rv))
00153       return rv;
00154   }
00155 
00156   return mRestart ? NS_SUCCESS_RESTART_APP : NS_OK;
00157 }
00158 
00159 
00160 NS_IMETHODIMP
00161 nsAppStartup::Quit(PRUint32 aMode)
00162 {
00163   PRUint32 ferocity = (aMode & 0xF);
00164 
00165   // Quit the application. We will asynchronously call the appshell's
00166   // Exit() method via HandleExitEvent() to allow one last pass
00167   // through any events in the queue. This guarantees a tidy cleanup.
00168   nsresult rv = NS_OK;
00169   PRBool postedExitEvent = PR_FALSE;
00170 
00171   if (mShuttingDown)
00172     return NS_OK;
00173 
00174   /* eForceQuit doesn't actually work; it can cause a subtle crash if
00175      there are windows open which have unload handlers which open
00176      new windows. Use eAttemptQuit for now. */
00177   if (ferocity == eForceQuit) {
00178     NS_WARNING("attempted to force quit");
00179     // it will be treated the same as eAttemptQuit, below
00180   }
00181 
00182   mShuttingDown = PR_TRUE;
00183   if (!mRestart) 
00184     mRestart = aMode & eRestart;
00185 
00186   nsCOMPtr<nsIWindowMediator> mediator
00187     (do_GetService(NS_WINDOWMEDIATOR_CONTRACTID));
00188 
00189   if (ferocity == eConsiderQuit && mConsiderQuitStopper == 0) {
00190     // attempt quit if the last window has been unregistered/closed
00191 
00192     PRBool windowsRemain = PR_TRUE;
00193 
00194     if (mediator) {
00195       nsCOMPtr<nsISimpleEnumerator> windowEnumerator;
00196       mediator->GetEnumerator(nsnull, getter_AddRefs(windowEnumerator));
00197       if (windowEnumerator)
00198         windowEnumerator->HasMoreElements(&windowsRemain);
00199     }
00200     if (!windowsRemain) {
00201       ferocity = eAttemptQuit;
00202     }
00203   }
00204 
00205   /* Currently ferocity can never have the value of eForceQuit here.
00206      That's temporary (in an unscheduled kind of way) and logically
00207      this code is part of the eForceQuit case, so I'm checking against
00208      that value anyway. Reviewers made me add this comment. */
00209   if (ferocity == eAttemptQuit || ferocity == eForceQuit) {
00210 
00211     AttemptingQuit(PR_TRUE);
00212 
00213     /* Enumerate through each open window and close it. It's important to do
00214        this before we forcequit because this can control whether we really quit
00215        at all. e.g. if one of these windows has an unload handler that
00216        opens a new window. Ugh. I know. */
00217     if (mediator) {
00218       nsCOMPtr<nsISimpleEnumerator> windowEnumerator;
00219 
00220       mediator->GetEnumerator(nsnull, getter_AddRefs(windowEnumerator));
00221 
00222       if (windowEnumerator) {
00223 
00224         while (1) {
00225           PRBool more;
00226           if (NS_FAILED(rv = windowEnumerator->HasMoreElements(&more)) || !more)
00227             break;
00228 
00229           nsCOMPtr<nsISupports> isupports;
00230           rv = windowEnumerator->GetNext(getter_AddRefs(isupports));
00231           if (NS_FAILED(rv))
00232             break;
00233 
00234           nsCOMPtr<nsIDOMWindowInternal> window = do_QueryInterface(isupports);
00235           NS_ASSERTION(window, "not an nsIDOMWindowInternal");
00236           if (!window)
00237             continue;
00238 
00239           window->Close();
00240         }
00241       }
00242 
00243       if (ferocity == eAttemptQuit) {
00244 
00245         ferocity = eForceQuit; // assume success
00246 
00247         /* Were we able to immediately close all windows? if not, eAttemptQuit
00248            failed. This could happen for a variety of reasons; in fact it's
00249            very likely. Perhaps we're being called from JS and the window->Close
00250            method hasn't had a chance to wrap itself up yet. So give up.
00251            We'll return (with eConsiderQuit) as the remaining windows are
00252            closed. */
00253         mediator->GetEnumerator(nsnull, getter_AddRefs(windowEnumerator));
00254         if (windowEnumerator) {
00255           PRBool more;
00256           while (windowEnumerator->HasMoreElements(&more), more) {
00257             /* we can't quit immediately. we'll try again as the last window
00258                finally closes. */
00259             ferocity = eAttemptQuit;
00260             nsCOMPtr<nsISupports> window;
00261             windowEnumerator->GetNext(getter_AddRefs(window));
00262             nsCOMPtr<nsIDOMWindowInternal> domWindow(do_QueryInterface(window));
00263             if (domWindow) {
00264               PRBool closed = PR_FALSE;
00265               domWindow->GetClosed(&closed);
00266               if (!closed) {
00267                 rv = NS_ERROR_FAILURE;
00268                 break;
00269               }
00270             }
00271           }
00272         }
00273       }
00274     }
00275   }
00276 
00277   if (ferocity == eForceQuit) {
00278     // do it!
00279 
00280     // No chance of the shutdown being cancelled from here on; tell people
00281     // we're shutting down for sure while all services are still available.
00282     nsCOMPtr<nsIObserverService> obsService
00283       (do_GetService("@mozilla.org/observer-service;1"));
00284     if (obsService) {
00285       NS_NAMED_LITERAL_STRING(shutdownStr, "shutdown");
00286       NS_NAMED_LITERAL_STRING(restartStr, "restart");
00287       obsService->NotifyObservers(nsnull, "quit-application",
00288         mRestart ? restartStr.get() : shutdownStr.get());
00289     }
00290 
00291     nsCOMPtr<nsIAppShellService> appShellService
00292       (do_GetService(NS_APPSHELLSERVICE_CONTRACTID));
00293     NS_ASSERTION(appShellService, "We're gonna leak something.");
00294     if (appShellService)
00295       appShellService->DestroyHiddenWindow();
00296 
00297     if (!mRunning) {
00298       postedExitEvent = PR_TRUE;
00299     }
00300     else {
00301       // no matter what, make sure we send the exit event.  If
00302       // worst comes to worst, we'll do a leaky shutdown but we WILL
00303       // shut down. Well, assuming that all *this* stuff works ;-).
00304       nsCOMPtr<nsIEventQueueService> svc = do_GetService(NS_EVENTQUEUESERVICE_CONTRACTID, &rv);
00305       if (NS_SUCCEEDED(rv)) {
00306 
00307         nsCOMPtr<nsIEventQueue> queue;
00308         rv = NS_GetMainEventQ(getter_AddRefs(queue));
00309         if (NS_SUCCEEDED(rv)) {
00310 
00311           PLEvent* event = new PLEvent;
00312           if (event) {
00313             NS_ADDREF_THIS();
00314             PL_InitEvent(event,
00315                          this,
00316                          HandleExitEvent,
00317                          DestroyExitEvent);
00318 
00319             rv = queue->PostEvent(event);
00320             if (NS_SUCCEEDED(rv)) {
00321               postedExitEvent = PR_TRUE;
00322             }
00323             else {
00324               PL_DestroyEvent(event);
00325             }
00326           }
00327           else {
00328             rv = NS_ERROR_OUT_OF_MEMORY;
00329           }
00330         }
00331       }
00332     }
00333   }
00334 
00335   // turn off the reentrancy check flag, but not if we have
00336   // more asynchronous work to do still.
00337   if (!postedExitEvent)
00338     mShuttingDown = PR_FALSE;
00339   return rv;
00340 }
00341 
00342 
00343 /* We know we're trying to quit the app, but may not be able to do so
00344    immediately. Enter a state where we're more ready to quit.
00345    (Does useful work only on the Mac.) */
00346 void
00347 nsAppStartup::AttemptingQuit(PRBool aAttempt)
00348 {
00349 #if defined(XP_MAC) || defined(XP_MACOSX)
00350   if (aAttempt) {
00351     // now even the Mac wants to quit when the last window is closed
00352     if (!mAttemptingQuit)
00353       ExitLastWindowClosingSurvivalArea();
00354     mAttemptingQuit = PR_TRUE;
00355   } else {
00356     // changed our mind. back to normal.
00357     if (mAttemptingQuit)
00358       EnterLastWindowClosingSurvivalArea();
00359     mAttemptingQuit = PR_FALSE;
00360   }
00361 #else
00362   mAttemptingQuit = aAttempt;
00363 #endif
00364 }
00365 
00366 NS_IMETHODIMP
00367 nsAppStartup::EnterLastWindowClosingSurvivalArea(void)
00368 {
00369   ++mConsiderQuitStopper;
00370   return NS_OK;
00371 }
00372 
00373 
00374 NS_IMETHODIMP
00375 nsAppStartup::ExitLastWindowClosingSurvivalArea(void)
00376 {
00377   NS_ASSERTION(mConsiderQuitStopper > 0, "consider quit stopper out of bounds");
00378   --mConsiderQuitStopper;
00379   return NS_OK;
00380 }
00381 
00382 
00383 void* PR_CALLBACK
00384 nsAppStartup::HandleExitEvent(PLEvent* aEvent)
00385 {
00386   nsAppStartup *service =
00387     NS_REINTERPRET_CAST(nsAppStartup*, aEvent->owner);
00388 
00389   // Tell the appshell to exit
00390   service->mAppShell->Exit();
00391 
00392   // We're done "shutting down".
00393   service->mShuttingDown = PR_FALSE;
00394   service->mRunning = PR_FALSE;
00395 
00396   return nsnull;
00397 }
00398 
00399 void PR_CALLBACK
00400 nsAppStartup::DestroyExitEvent(PLEvent* aEvent)
00401 {
00402   nsAppStartup *service =
00403     NS_REINTERPRET_CAST(nsAppStartup*, aEvent->owner);
00404   NS_RELEASE(service);
00405   delete aEvent;
00406 }
00407 
00408 
00409 //
00410 // nsAppStartup->nsIWindowCreator
00411 //
00412 
00413 NS_IMETHODIMP
00414 nsAppStartup::CreateChromeWindow(nsIWebBrowserChrome *aParent,
00415                                  PRUint32 aChromeFlags,
00416                                  nsIWebBrowserChrome **_retval)
00417 {
00418   PRBool cancel;
00419   return CreateChromeWindow2(aParent, aChromeFlags, 0, 0, &cancel, _retval);
00420 }
00421 
00422 
00423 //
00424 // nsAppStartup->nsIWindowCreator2
00425 //
00426 
00427 NS_IMETHODIMP
00428 nsAppStartup::CreateChromeWindow2(nsIWebBrowserChrome *aParent,
00429                                   PRUint32 aChromeFlags,
00430                                   PRUint32 aContextFlags,
00431                                   nsIURI *aURI,
00432                                   PRBool *aCancel,
00433                                   nsIWebBrowserChrome **_retval)
00434 {
00435   NS_ENSURE_ARG_POINTER(aCancel);
00436   NS_ENSURE_ARG_POINTER(_retval);
00437   *aCancel = PR_FALSE;
00438   *_retval = 0;
00439 
00440   nsCOMPtr<nsIXULWindow> newWindow;
00441 
00442   if (aParent) {
00443     nsCOMPtr<nsIXULWindow> xulParent(do_GetInterface(aParent));
00444     NS_ASSERTION(xulParent, "window created using non-XUL parent. that's unexpected, but may work.");
00445 
00446     if (xulParent)
00447       xulParent->CreateNewWindow(aChromeFlags, mAppShell, getter_AddRefs(newWindow));
00448     // And if it fails, don't try again without a parent. It could fail
00449     // intentionally (bug 115969).
00450   } else { // try using basic methods:
00451     /* You really shouldn't be making dependent windows without a parent.
00452       But unparented modal (and therefore dependent) windows happen
00453       in our codebase, so we allow it after some bellyaching: */
00454     if (aChromeFlags & nsIWebBrowserChrome::CHROME_DEPENDENT)
00455       NS_WARNING("dependent window created without a parent");
00456 
00457     nsCOMPtr<nsIAppShellService> appShell(do_GetService(NS_APPSHELLSERVICE_CONTRACTID));
00458     if (!appShell)
00459       return NS_ERROR_FAILURE;
00460     
00461     appShell->CreateTopLevelWindow(0, 0, aChromeFlags,
00462                                    nsIAppShellService::SIZE_TO_CONTENT,
00463                                    nsIAppShellService::SIZE_TO_CONTENT,
00464                                    mAppShell, getter_AddRefs(newWindow));
00465   }
00466 
00467   // if anybody gave us anything to work with, use it
00468   if (newWindow) {
00469     newWindow->SetContextFlags(aContextFlags);
00470     nsCOMPtr<nsIInterfaceRequestor> thing(do_QueryInterface(newWindow));
00471     if (thing)
00472       CallGetInterface(thing.get(), _retval);
00473   }
00474 
00475   return *_retval ? NS_OK : NS_ERROR_FAILURE;
00476 }
00477 
00478 
00479 //
00480 // nsAppStartup->nsIObserver
00481 //
00482 
00483 NS_IMETHODIMP
00484 nsAppStartup::Observe(nsISupports *aSubject,
00485                       const char *aTopic, const PRUnichar *aData)
00486 {
00487   NS_ASSERTION(mAppShell, "appshell service notified before appshell built");
00488   if (!strcmp(aTopic, "nsIEventQueueActivated")) {
00489     nsCOMPtr<nsIEventQueue> eq(do_QueryInterface(aSubject));
00490     if (eq) {
00491       PRBool isNative = PR_TRUE;
00492       // we only add native event queues to the appshell
00493       eq->IsQueueNative(&isNative);
00494       if (isNative)
00495         mAppShell->ListenToEventQueue(eq, PR_TRUE);
00496     }
00497   } else if (!strcmp(aTopic, "nsIEventQueueDestroyed")) {
00498     nsCOMPtr<nsIEventQueue> eq(do_QueryInterface(aSubject));
00499     if (eq) {
00500       PRBool isNative = PR_TRUE;
00501       // we only remove native event queues from the appshell
00502       eq->IsQueueNative(&isNative);
00503       if (isNative)
00504         mAppShell->ListenToEventQueue(eq, PR_FALSE);
00505     }
00506   } else if (!strcmp(aTopic, "profile-change-teardown")) {
00507     nsresult rv;
00508     EnterLastWindowClosingSurvivalArea();
00509     // NOTE: No early error exits because we need to execute the
00510     // balancing ExitLastWindowClosingSurvivalArea().
00511     nsCOMPtr<nsICloseAllWindows> closer =
00512             do_CreateInstance("@mozilla.org/appshell/closeallwindows;1", &rv);
00513     NS_ASSERTION(closer, "Failed to create nsICloseAllWindows impl.");
00514     PRBool proceedWithSwitch = PR_FALSE;
00515     if (closer)
00516       rv = closer->CloseAll(PR_TRUE, &proceedWithSwitch);
00517 
00518     if (NS_FAILED(rv) || !proceedWithSwitch) {
00519       nsCOMPtr<nsIProfileChangeStatus> changeStatus(do_QueryInterface(aSubject));
00520       if (changeStatus)
00521         changeStatus->VetoChange();
00522     }
00523     ExitLastWindowClosingSurvivalArea();
00524   } else if (!strcmp(aTopic, "xul-window-registered")) {
00525     AttemptingQuit(PR_FALSE);
00526   } else if (!strcmp(aTopic, "xul-window-destroyed")) {
00527     Quit(eConsiderQuit);
00528   } else {
00529     NS_ERROR("Unexpected observer topic.");
00530   }
00531 
00532   return NS_OK;
00533 }