Bug 1675329: Add an API for returning an exit code from the application. r=dthayer,necko-reviewers,dragana,nika

Callers can pass an exit code to nsIAppStartup::Quit and it will be returned from the process when
it exits.

Note that I have using uint16_t as the exit code because on Windows the exit code can be a uint and
elsewhere it is an int. A uint16_t will safely convert to either of those and no-one will ever need
more than 64k exit codes!

Differential Revision: https://phabricator.services.mozilla.com/D96857
This commit is contained in:
Dave Townsend 2020-11-20 19:13:34 +00:00
Родитель ec28416cf3
Коммит a3cb03324e
11 изменённых файлов: 83 добавлений и 24 удалений

Просмотреть файл

@ -103,7 +103,7 @@ NS_IMETHODIMP nsReadConfig::Observe(nsISupports* aSubject, const char* aTopic,
components::AppStartup::Service();
if (appStartup) {
bool userAllowedQuit = true;
appStartup->Quit(nsIAppStartup::eAttemptQuit, &userAllowedQuit);
appStartup->Quit(nsIAppStartup::eAttemptQuit, 0, &userAllowedQuit);
}
}
}

Просмотреть файл

@ -88,7 +88,7 @@ void SocketProcessParent::ActorDestroy(ActorDestroyReason aWhy) {
do_GetService("@mozilla.org/toolkit/app-startup;1");
if (appService) {
bool userAllowedQuit = true;
appService->Quit(nsIAppStartup::eForceQuit, &userAllowedQuit);
appService->Quit(nsIAppStartup::eForceQuit, 1, &userAllowedQuit);
}
}
}

Просмотреть файл

@ -275,7 +275,7 @@ nsAppStartup::Run(void) {
// regardless of whether the event loop has spun or not. Note that this call
// is a no-op if Quit has already been called previously.
bool userAllowedQuit = true;
Quit(eForceQuit, &userAllowedQuit);
Quit(eForceQuit, 0, &userAllowedQuit);
nsresult retval = NS_OK;
if (mozilla::AppShutdown::IsRestarting()) {
@ -286,7 +286,7 @@ nsAppStartup::Run(void) {
}
NS_IMETHODIMP
nsAppStartup::Quit(uint32_t aMode, bool* aUserAllowedQuit) {
nsAppStartup::Quit(uint32_t aMode, int aExitCode, bool* aUserAllowedQuit) {
uint32_t ferocity = (aMode & 0xF);
// If the shutdown was cancelled due to a hidden window or
@ -371,7 +371,7 @@ nsAppStartup::Quit(uint32_t aMode, bool* aUserAllowedQuit) {
auto shutdownMode = ((aMode & eRestart) != 0)
? mozilla::AppShutdownMode::Restart
: mozilla::AppShutdownMode::Normal;
mozilla::AppShutdown::Init(shutdownMode);
mozilla::AppShutdown::Init(shutdownMode, aExitCode);
if (mozilla::AppShutdown::IsRestarting()) {
// Mark the next startup as a restart.
@ -506,7 +506,12 @@ nsAppStartup::ExitLastWindowClosingSurvivalArea(void) {
if (mRunning) {
bool userAllowedQuit = false;
Quit(eConsiderQuit, &userAllowedQuit);
// A previous call to Quit may have told all windows to close and then
// bailed out waiting for that to happen. This is how we get back into Quit
// after each window closes so the exit process can continue when ready.
// Make sure to pass along the exit code that was initially passed to Quit.
Quit(eConsiderQuit, mozilla::AppShutdown::GetExitCode(), &userAllowedQuit);
}
return NS_OK;
@ -952,7 +957,7 @@ NS_IMETHODIMP
nsAppStartup::RestartInSafeMode(uint32_t aQuitMode) {
PR_SetEnv("MOZ_SAFE_MODE_RESTART=1");
bool userAllowedQuit = false;
this->Quit(aQuitMode | nsIAppStartup::eRestart, &userAllowedQuit);
this->Quit(aQuitMode | nsIAppStartup::eRestart, 0, &userAllowedQuit);
return NS_OK;
}

Просмотреть файл

@ -123,12 +123,17 @@ interface nsIAppStartup : nsISupports
* @param aMode
* This parameter modifies how the app is shutdown, and it is
* constructed from the constants defined above.
* @param aExitCode
* The exit code to return from the process. The precise code
* returned by the process may vary depending on the platform. Only
* values 0-255 should generallt be used. If not specified an exit
* code of 0 will be used.
*
* @return false if the shutdown was cancelled due to the presence
* of a hidden window or if the user disallowed a window
* to be closed.
*/
bool quit(in uint32_t aMode);
bool quit(in uint32_t aMode, [optional] in int32_t aExitCode);
/**
* True if the application is in the process of shutting down.

Просмотреть файл

@ -304,7 +304,7 @@ void ProcessPendingGetURLAppleEvents() {
nsCOMPtr<nsIAppStartup> appService = do_GetService("@mozilla.org/toolkit/app-startup;1");
if (appService) {
bool userAllowedQuit = true;
appService->Quit(nsIAppStartup::eForceQuit, &userAllowedQuit);
appService->Quit(nsIAppStartup::eForceQuit, 0, &userAllowedQuit);
if (!userAllowedQuit) {
return NSTerminateCancel;
}

Просмотреть файл

@ -2207,9 +2207,9 @@ nsresult LaunchChild(bool aBlankCommandLine) {
PRStatus failed = PR_WaitProcess(process, &exitCode);
if (failed || exitCode) return NS_ERROR_FAILURE;
# endif // XP_UNIX
# endif // WP_WIN
# endif // WP_MACOSX
#endif // MOZ_WIDGET_ANDROID
# endif // WP_WIN
# endif // WP_MACOSX
#endif // MOZ_WIDGET_ANDROID
return NS_ERROR_LAUNCHED_CHILD_PROCESS;
}
@ -4154,7 +4154,7 @@ int XREMain::XRE_mainStartup(bool* aExitFlag) {
}
}
# endif /* DEBUG */
#endif /* FUZZING */
#endif /* FUZZING */
#if defined(XP_WIN)
// Enable the HeapEnableTerminationOnCorruption exploit mitigation. We ignore
@ -4691,7 +4691,7 @@ nsresult XREMain::XRE_mainRun() {
# if defined(MOZ_GECKO_PROFILER)
mozilla::mscom::InitProfilerMarkers();
# endif // defined(MOZ_GECKO_PROFILER)
#endif // defined(XP_WIN)
#endif // defined(XP_WIN)
rv = mScopedXPCOM->SetWindowCreator(mNativeApp);
NS_ENSURE_SUCCESS(rv, NS_ERROR_FAILURE);
@ -5342,7 +5342,10 @@ int XREMain::XRE_main(int argc, char* argv[], const BootstrapConfig& aConfig) {
XRE_DeinitCommandLine();
return NS_FAILED(rv) ? 1 : 0;
if (NS_FAILED(rv)) {
return 1;
}
return mozilla::AppShutdown::GetExitCode();
}
void XRE_StopLateWriteChecks(void) { mozilla::StopLateWriteChecks(); }

Просмотреть файл

@ -1 +1,2 @@
[test_fission_autostart.py]
[test_exitcode.py]

Просмотреть файл

@ -0,0 +1,33 @@
from marionette_harness import MarionetteTestCase
class TestFissionAutostart(MarionetteTestCase):
def test_normal_exit(self):
self.marionette.set_context(self.marionette.CONTEXT_CHROME)
def call_quit():
self.marionette.execute_script(
"""
const { Services } = ChromeUtils.import("resource://gre/modules/Services.jsm");
Services.startup.quit(Ci.nsIAppStartup.eAttemptQuit);
""",
sandbox="system",
)
self.marionette.quit(in_app=True, callback=call_quit)
self.assertEqual(self.marionette.instance.runner.returncode, 0)
def test_exit_code(self):
self.marionette.set_context(self.marionette.CONTEXT_CHROME)
def call_quit():
self.marionette.execute_script(
"""
const { Services } = ChromeUtils.import("resource://gre/modules/Services.jsm");
Services.startup.quit(Ci.nsIAppStartup.eAttemptQuit, 5);
""",
sandbox="system",
)
self.marionette.quit(in_app=True, callback=call_quit)
self.assertEqual(self.marionette.instance.runner.returncode, 5)

Просмотреть файл

@ -917,7 +917,7 @@ static BOOL gMenuItemsExecuteCommands = YES;
nsCOMPtr<nsIAppStartup> appStartup = mozilla::components::AppStartup::Service();
if (appStartup) {
bool userAllowedQuit = true;
appStartup->Quit(nsIAppStartup::eAttemptQuit, &userAllowedQuit);
appStartup->Quit(nsIAppStartup::eAttemptQuit, 0, &userAllowedQuit);
}
}
return;

Просмотреть файл

@ -37,6 +37,7 @@ static ShutdownPhase sFastShutdownPhase = ShutdownPhase::NotInShutdown;
static ShutdownPhase sLateWriteChecksPhase = ShutdownPhase::NotInShutdown;
static AppShutdownMode sShutdownMode = AppShutdownMode::Normal;
static Atomic<bool, MemoryOrdering::Relaxed> sIsShuttingDown;
static int sExitCode = 0;
// These environment variable strings are all deliberately copied and leaked
// due to requirements of PR_SetEnv and similar.
@ -66,6 +67,8 @@ ShutdownPhase GetShutdownPhaseFromPrefValue(int32_t aPrefValue) {
bool AppShutdown::IsShuttingDown() { return sIsShuttingDown; }
int AppShutdown::GetExitCode() { return sExitCode; }
void AppShutdown::SaveEnvVarsForPotentialRestart() {
const char* s = PR_GetEnv("XUL_APP_FILE");
if (s) {
@ -124,11 +127,13 @@ wchar_t* CopyPathIntoNewWCString(nsIFile* aFile) {
}
#endif
void AppShutdown::Init(AppShutdownMode aMode) {
void AppShutdown::Init(AppShutdownMode aMode, int aExitCode) {
if (sShutdownMode == AppShutdownMode::Normal) {
sShutdownMode = aMode;
}
sExitCode = aExitCode;
// Late-write checks needs to find the profile directory, so it has to
// be initialized before services::Shutdown or (because of
// xpcshell tests replacing the service) modules being unloaded.
@ -184,7 +189,7 @@ void AppShutdown::MaybeFastShutdown(ShutdownPhase aPhase) {
profiler_shutdown(IsFastShutdown::Yes);
#endif
DoImmediateExit();
DoImmediateExit(sExitCode);
} else if (aPhase == sLateWriteChecksPhase) {
#ifdef XP_MACOSX
OnlyReportDirtyWrites();
@ -221,15 +226,15 @@ void AppShutdown::OnShutdownConfirmed() {
}
}
void AppShutdown::DoImmediateExit() {
void AppShutdown::DoImmediateExit(int aExitCode) {
#ifdef XP_WIN
HANDLE process = ::GetCurrentProcess();
if (::TerminateProcess(process, 0)) {
if (::TerminateProcess(process, aExitCode)) {
::WaitForSingleObject(process, INFINITE);
}
MOZ_CRASH("TerminateProcess failed.");
#else
_exit(0);
_exit(aExitCode);
#endif
}

Просмотреть файл

@ -20,6 +20,11 @@ class AppShutdown {
public:
static bool IsShuttingDown();
/**
* Returns the current exit code that the process will be terminated with.
*/
static int GetExitCode();
/**
* Save environment variables that we might need if the app initiates a
* restart later in its lifecycle.
@ -27,9 +32,9 @@ class AppShutdown {
static void SaveEnvVarsForPotentialRestart();
/**
* Init the shutdown with the requested shutdown mode.
* Init the shutdown with the requested shutdown mode and exit code.
*/
static void Init(AppShutdownMode aMode);
static void Init(AppShutdownMode aMode, int aExitCode);
/**
* Confirm that we are in fact going to be shutting down.
@ -54,8 +59,10 @@ class AppShutdown {
* destructors for xul.dll.
*
* This method terminates the current process without those issues.
*
* Optionally a custom exit code can be supplied.
*/
static void DoImmediateExit();
static void DoImmediateExit(int aExitCode = 0);
/**
* True if the application is currently attempting to shut down in order to