1 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* ***** BEGIN LICENSE BLOCK *****
3 * Version: MPL 1.1/GPL 2.0/LGPL 2.1
5 * The contents of this file are subject to the Mozilla Public License Version
6 * 1.1 (the "License"); you may not use this file except in compliance with
7 * the License. You may obtain a copy of the License at
8 * http://www.mozilla.org/MPL/
10 * Software distributed under the License is distributed on an "AS IS" basis,
11 * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
12 * for the specific language governing rights and limitations under the
15 * The Original Code is Mozilla Communicator client code. This file was split
16 * from xpfe/appshell/src/nsAppShellService.cpp
18 * The Initial Developer of the Original Code is
19 * Netscape Communications Corporation.
20 * Portions created by the Initial Developer are Copyright (C) 1998
21 * the Initial Developer. All Rights Reserved.
24 * Pierre Phaneuf <pp@ludusdesign.com>
25 * Robert O'Callahan <roc+moz@cs.cmu.edu>
26 * Benjamin Smedberg <bsmedberg@covad.net>
28 * Alternatively, the contents of this file may be used under the terms of
29 * either of the GNU General Public License Version 2 or later (the "GPL"),
30 * or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
31 * in which case the provisions of the GPL or the LGPL are applicable instead
32 * of those above. If you wish to allow use of your version of this file only
33 * under the terms of either the GPL or the LGPL, and not to allow others to
34 * use your version of this file under the terms of the MPL, indicate your
35 * decision by deleting the provisions above and replace them with the notice
36 * and other provisions required by the GPL or the LGPL. If you do not delete
37 * the provisions above, a recipient may use your version of this file under
38 * the terms of any one of the MPL, the GPL or the LGPL.
40 * ***** END LICENSE BLOCK ***** */
42 #include "nsAppStartup.h"
44 #include "nsIAppShellService.h"
45 #include "nsIDOMWindowInternal.h"
46 #include "nsIInterfaceRequestor.h"
47 #include "nsILocalFile.h"
48 #include "nsIObserverService.h"
49 #include "nsIPrefBranch.h"
50 #include "nsIPrefService.h"
51 #include "nsIProfileChangeStatus.h"
52 #include "nsIPromptService.h"
53 #include "nsIStringBundle.h"
54 #include "nsISupportsPrimitives.h"
55 #include "nsITimelineService.h"
56 #include "nsIWebBrowserChrome.h"
57 #include "nsIWindowMediator.h"
58 #include "nsIWindowWatcher.h"
59 #include "nsIXULWindow.h"
60 #include "nsNativeCharsetUtils.h"
61 #include "nsThreadUtils.h"
62 #include "nsAutoPtr.h"
63 #include "nsStringGlue.h"
67 #include "nsIInterfaceRequestorUtils.h"
68 #include "nsWidgetsCID.h"
69 #include "nsAppShellCID.h"
70 #include "nsXPFEComponentsCID.h"
72 static NS_DEFINE_CID(kAppShellCID
, NS_APPSHELL_CID
);
74 class nsAppExitEvent
: public nsRunnable
{
76 nsRefPtr
<nsAppStartup
> mService
;
79 nsAppExitEvent(nsAppStartup
*service
) : mService(service
) {}
82 // Tell the appshell to exit
83 mService
->mAppShell
->Exit();
85 // We're done "shutting down".
86 mService
->mShuttingDown
= PR_FALSE
;
87 mService
->mRunning
= PR_FALSE
;
96 nsAppStartup::nsAppStartup() :
97 mConsiderQuitStopper(0),
99 mShuttingDown(PR_FALSE
),
100 mAttemptingQuit(PR_FALSE
),
110 // Create widget application shell
111 mAppShell
= do_GetService(kAppShellCID
, &rv
);
112 NS_ENSURE_SUCCESS(rv
, rv
);
114 nsCOMPtr
<nsIObserverService
> os
115 (do_GetService("@mozilla.org/observer-service;1", &rv
));
116 NS_ENSURE_SUCCESS(rv
, rv
);
118 os
->AddObserver(this, "quit-application-forced", PR_TRUE
);
119 os
->AddObserver(this, "profile-change-teardown", PR_TRUE
);
120 os
->AddObserver(this, "xul-window-registered", PR_TRUE
);
121 os
->AddObserver(this, "xul-window-destroyed", PR_TRUE
);
128 // nsAppStartup->nsISupports
131 NS_IMPL_THREADSAFE_ISUPPORTS5(nsAppStartup
,
136 nsISupportsWeakReference
)
140 // nsAppStartup->nsIAppStartup
144 nsAppStartup::CreateHiddenWindow()
146 nsCOMPtr
<nsIAppShellService
> appShellService
147 (do_GetService(NS_APPSHELLSERVICE_CONTRACTID
));
148 NS_ENSURE_TRUE(appShellService
, NS_ERROR_FAILURE
);
150 return appShellService
->CreateHiddenWindow(mAppShell
);
155 nsAppStartup::DestroyHiddenWindow()
157 nsCOMPtr
<nsIAppShellService
> appShellService
158 (do_GetService(NS_APPSHELLSERVICE_CONTRACTID
));
159 NS_ENSURE_TRUE(appShellService
, NS_ERROR_FAILURE
);
161 return appShellService
->DestroyHiddenWindow();
165 nsAppStartup::RealQuitStoppers()
168 // When attempting quit is set we must subtract the hidden window
169 return mConsiderQuitStopper
- (mAttemptingQuit
? 0 : 1);
171 return mConsiderQuitStopper
;
176 nsAppStartup::Run(void)
178 NS_ASSERTION(!mRunning
, "Reentrant appstartup->Run()");
180 // If we have no windows open and no explicit calls to
181 // enterLastWindowClosingSurvivalArea, or somebody has explicitly called
182 // quit, don't bother running the event loop which would probably leave us
183 // with a zombie process.
185 if (!mShuttingDown
&& mConsiderQuitStopper
!= 0) {
187 EnterLastWindowClosingSurvivalArea();
192 nsresult rv
= mAppShell
->Run();
197 return mRestart
? NS_SUCCESS_RESTART_APP
: NS_OK
;
202 nsAppStartup::Quit(PRUint32 aMode
)
204 PRUint32 ferocity
= (aMode
& 0xF);
206 // Quit the application. We will asynchronously call the appshell's
207 // Exit() method via nsAppExitEvent to allow one last pass
208 // through any events in the queue. This guarantees a tidy cleanup.
210 PRBool postedExitEvent
= PR_FALSE
;
215 // If we're considering quitting, we will only do so if:
216 if (ferocity
== eConsiderQuit
) {
217 if (mConsiderQuitStopper
== 0) {
218 // there are no windows...
219 ferocity
= eAttemptQuit
;
222 else if (mConsiderQuitStopper
== 1) {
223 // ... or there is only a hiddenWindow left, and it's useless:
224 nsCOMPtr
<nsIAppShellService
> appShell
225 (do_GetService(NS_APPSHELLSERVICE_CONTRACTID
));
227 // Failure shouldn't be fatal, but will abort quit attempt:
231 PRBool usefulHiddenWindow
;
232 appShell
->GetApplicationProvidedHiddenWindow(&usefulHiddenWindow
);
233 nsCOMPtr
<nsIXULWindow
> hiddenWindow
;
234 appShell
->GetHiddenWindow(getter_AddRefs(hiddenWindow
));
235 // If the one window is useful, we won't quit:
236 if (!hiddenWindow
|| usefulHiddenWindow
)
239 ferocity
= eAttemptQuit
;
244 mShuttingDown
= PR_TRUE
;
246 mRestart
= (aMode
& eRestart
) != 0;
248 nsCOMPtr
<nsIObserverService
> obsService
;
249 if (ferocity
== eAttemptQuit
|| ferocity
== eForceQuit
) {
251 obsService
= do_GetService("@mozilla.org/observer-service;1");
253 obsService
->NotifyObservers(nsnull
, "quit-application-granted", nsnull
);
255 AttemptingQuit(PR_TRUE
);
257 /* Enumerate through each open window and close it. It's important to do
258 this before we forcequit because this can control whether we really quit
259 at all. e.g. if one of these windows has an unload handler that
260 opens a new window. Ugh. I know. */
263 nsCOMPtr
<nsIWindowMediator
> mediator
264 (do_GetService(NS_WINDOWMEDIATOR_CONTRACTID
));
266 if (ferocity
== eAttemptQuit
) {
267 nsCOMPtr
<nsISimpleEnumerator
> windowEnumerator
;
269 ferocity
= eForceQuit
; // assume success
271 /* Were we able to immediately close all windows? if not, eAttemptQuit
272 failed. This could happen for a variety of reasons; in fact it's
273 very likely. Perhaps we're being called from JS and the window->Close
274 method hasn't had a chance to wrap itself up yet. So give up.
275 We'll return (with eConsiderQuit) as the remaining windows are
277 mediator
->GetEnumerator(nsnull
, getter_AddRefs(windowEnumerator
));
278 if (windowEnumerator
) {
280 while (windowEnumerator
->HasMoreElements(&more
), more
) {
281 /* we can't quit immediately. we'll try again as the last window
283 ferocity
= eAttemptQuit
;
284 nsCOMPtr
<nsISupports
> window
;
285 windowEnumerator
->GetNext(getter_AddRefs(window
));
286 nsCOMPtr
<nsIDOMWindowInternal
> domWindow(do_QueryInterface(window
));
288 PRBool closed
= PR_FALSE
;
289 domWindow
->GetClosed(&closed
);
291 rv
= NS_ERROR_FAILURE
;
301 if (ferocity
== eForceQuit
) {
304 // No chance of the shutdown being cancelled from here on; tell people
305 // we're shutting down for sure while all services are still available.
307 NS_NAMED_LITERAL_STRING(shutdownStr
, "shutdown");
308 NS_NAMED_LITERAL_STRING(restartStr
, "restart");
309 obsService
->NotifyObservers(nsnull
, "quit-application",
310 mRestart
? restartStr
.get() : shutdownStr
.get());
314 postedExitEvent
= PR_TRUE
;
317 // no matter what, make sure we send the exit event. If
318 // worst comes to worst, we'll do a leaky shutdown but we WILL
319 // shut down. Well, assuming that all *this* stuff works ;-).
320 nsCOMPtr
<nsIRunnable
> event
= new nsAppExitEvent(this);
321 rv
= NS_DispatchToCurrentThread(event
);
322 if (NS_SUCCEEDED(rv
)) {
323 postedExitEvent
= PR_TRUE
;
326 NS_WARNING("failed to dispatch nsAppExitEvent");
331 // turn off the reentrancy check flag, but not if we have
332 // more asynchronous work to do still.
333 if (!postedExitEvent
)
334 mShuttingDown
= PR_FALSE
;
339 /* We know we're trying to quit the app, but may not be able to do so
340 immediately. Enter a state where we're more ready to quit.
341 (Does useful work only on the Mac.) */
343 nsAppStartup::AttemptingQuit(PRBool aAttempt
)
347 // now even the Mac wants to quit when the last window is closed
348 if (!mAttemptingQuit
)
349 ExitLastWindowClosingSurvivalArea();
351 // changed our mind. back to normal.
353 EnterLastWindowClosingSurvivalArea();
357 mAttemptingQuit
= aAttempt
;
361 nsAppStartup::CloseAllWindows()
363 nsCOMPtr
<nsIWindowMediator
> mediator
364 (do_GetService(NS_WINDOWMEDIATOR_CONTRACTID
));
366 nsCOMPtr
<nsISimpleEnumerator
> windowEnumerator
;
368 mediator
->GetEnumerator(nsnull
, getter_AddRefs(windowEnumerator
));
370 if (!windowEnumerator
)
374 while (NS_SUCCEEDED(windowEnumerator
->HasMoreElements(&more
)) && more
) {
375 nsCOMPtr
<nsISupports
> isupports
;
376 if (NS_FAILED(windowEnumerator
->GetNext(getter_AddRefs(isupports
))))
379 nsCOMPtr
<nsIDOMWindowInternal
> window
= do_QueryInterface(isupports
);
380 NS_ASSERTION(window
, "not an nsIDOMWindowInternal");
383 PRInt32 quitStoppers
= RealQuitStoppers();
387 if (!mAttemptingQuit
) {
388 PRInt32 currentQuitStoppers
= RealQuitStoppers();
389 // If the current number of windows is smaller or same then the number
390 // recorded before window close, we must re-attempt quit.
391 // 'Or same' condition is here because the actual window deregisters
392 // later asynchronously.
393 if (currentQuitStoppers
<= quitStoppers
)
394 AttemptingQuit(PR_TRUE
);
402 nsAppStartup::EnterLastWindowClosingSurvivalArea(void)
404 ++mConsiderQuitStopper
;
410 nsAppStartup::ExitLastWindowClosingSurvivalArea(void)
412 NS_ASSERTION(mConsiderQuitStopper
> 0, "consider quit stopper out of bounds");
413 --mConsiderQuitStopper
;
416 if (!mShuttingDown
&& mRunning
&& (mConsiderQuitStopper
<= 1))
419 if (!mShuttingDown
&& mRunning
&& (mConsiderQuitStopper
== 0))
427 // nsAppStartup->nsIWindowCreator
431 nsAppStartup::CreateChromeWindow(nsIWebBrowserChrome
*aParent
,
432 PRUint32 aChromeFlags
,
433 nsIWebBrowserChrome
**_retval
)
436 return CreateChromeWindow2(aParent
, aChromeFlags
, 0, 0, &cancel
, _retval
);
441 // nsAppStartup->nsIWindowCreator2
445 nsAppStartup::CreateChromeWindow2(nsIWebBrowserChrome
*aParent
,
446 PRUint32 aChromeFlags
,
447 PRUint32 aContextFlags
,
450 nsIWebBrowserChrome
**_retval
)
452 NS_ENSURE_ARG_POINTER(aCancel
);
453 NS_ENSURE_ARG_POINTER(_retval
);
457 nsCOMPtr
<nsIXULWindow
> newWindow
;
460 nsCOMPtr
<nsIXULWindow
> xulParent(do_GetInterface(aParent
));
461 NS_ASSERTION(xulParent
, "window created using non-XUL parent. that's unexpected, but may work.");
464 xulParent
->CreateNewWindow(aChromeFlags
, mAppShell
, getter_AddRefs(newWindow
));
465 // And if it fails, don't try again without a parent. It could fail
466 // intentionally (bug 115969).
467 } else { // try using basic methods:
468 /* You really shouldn't be making dependent windows without a parent.
469 But unparented modal (and therefore dependent) windows happen
470 in our codebase, so we allow it after some bellyaching: */
471 if (aChromeFlags
& nsIWebBrowserChrome::CHROME_DEPENDENT
)
472 NS_WARNING("dependent window created without a parent");
474 nsCOMPtr
<nsIAppShellService
> appShell(do_GetService(NS_APPSHELLSERVICE_CONTRACTID
));
476 return NS_ERROR_FAILURE
;
478 appShell
->CreateTopLevelWindow(0, 0, aChromeFlags
,
479 nsIAppShellService::SIZE_TO_CONTENT
,
480 nsIAppShellService::SIZE_TO_CONTENT
,
481 mAppShell
, getter_AddRefs(newWindow
));
484 // if anybody gave us anything to work with, use it
486 newWindow
->SetContextFlags(aContextFlags
);
487 nsCOMPtr
<nsIInterfaceRequestor
> thing(do_QueryInterface(newWindow
));
489 CallGetInterface(thing
.get(), _retval
);
492 return *_retval
? NS_OK
: NS_ERROR_FAILURE
;
497 // nsAppStartup->nsIObserver
501 nsAppStartup::Observe(nsISupports
*aSubject
,
502 const char *aTopic
, const PRUnichar
*aData
)
504 NS_ASSERTION(mAppShell
, "appshell service notified before appshell built");
505 if (!strcmp(aTopic
, "quit-application-forced")) {
506 mShuttingDown
= PR_TRUE
;
508 else if (!strcmp(aTopic
, "profile-change-teardown")) {
509 if (!mShuttingDown
) {
510 EnterLastWindowClosingSurvivalArea();
512 ExitLastWindowClosingSurvivalArea();
514 } else if (!strcmp(aTopic
, "xul-window-registered")) {
515 EnterLastWindowClosingSurvivalArea();
516 AttemptingQuit(PR_FALSE
);
517 } else if (!strcmp(aTopic
, "xul-window-destroyed")) {
518 ExitLastWindowClosingSurvivalArea();
520 NS_ERROR("Unexpected observer topic.");