diff options
author | Matt A. Tobin <mattatobin@localhost.localdomain> | 2018-02-02 04:16:08 -0500 |
---|---|---|
committer | Matt A. Tobin <mattatobin@localhost.localdomain> | 2018-02-02 04:16:08 -0500 |
commit | ad18d877ddd2a44d98fa12ccd3dbbcf4d0ac4299 (patch) | |
tree | 10027f336435511475e392454359edea8e25895d /toolkit/xre | |
parent | 15477ed9af4859dacb069040b5d4de600803d3bc (diff) | |
download | uxp-ad18d877ddd2a44d98fa12ccd3dbbcf4d0ac4299.tar.gz |
Add m-esr52 at 52.6.0
Diffstat (limited to 'toolkit/xre')
58 files changed, 16530 insertions, 0 deletions
diff --git a/toolkit/xre/CreateAppData.cpp b/toolkit/xre/CreateAppData.cpp new file mode 100644 index 0000000000..8c91ddc874 --- /dev/null +++ b/toolkit/xre/CreateAppData.cpp @@ -0,0 +1,165 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "nsXULAppAPI.h" +#include "nsINIParser.h" +#include "nsIFile.h" +#include "nsAutoPtr.h" +#include "mozilla/AppData.h" + +using namespace mozilla; + +nsresult +XRE_CreateAppData(nsIFile* aINIFile, nsXREAppData **aAppData) +{ + NS_ENSURE_ARG(aINIFile && aAppData); + + nsAutoPtr<ScopedAppData> data(new ScopedAppData()); + if (!data) + return NS_ERROR_OUT_OF_MEMORY; + + nsresult rv = XRE_ParseAppData(aINIFile, data); + if (NS_FAILED(rv)) + return rv; + + if (!data->directory) { + nsCOMPtr<nsIFile> appDir; + rv = aINIFile->GetParent(getter_AddRefs(appDir)); + if (NS_FAILED(rv)) + return rv; + + appDir.forget(&data->directory); + } + + *aAppData = data.forget(); + return NS_OK; +} + +struct ReadString { + const char *section; + const char *key; + const char **buffer; +}; + +static void +ReadStrings(nsINIParser &parser, const ReadString *reads) +{ + nsresult rv; + nsCString str; + + while (reads->section) { + rv = parser.GetString(reads->section, reads->key, str); + if (NS_SUCCEEDED(rv)) { + SetAllocatedString(*reads->buffer, str); + } + + ++reads; + } +} + +struct ReadFlag { + const char *section; + const char *key; + uint32_t flag; +}; + +static void +ReadFlags(nsINIParser &parser, const ReadFlag *reads, uint32_t *buffer) +{ + nsresult rv; + char buf[6]; // large enough to hold "false" + + while (reads->section) { + rv = parser.GetString(reads->section, reads->key, buf, sizeof(buf)); + if (NS_SUCCEEDED(rv) || rv == NS_ERROR_LOSS_OF_SIGNIFICANT_DATA) { + if (buf[0] == '1' || buf[0] == 't' || buf[0] == 'T') { + *buffer |= reads->flag; + } + if (buf[0] == '0' || buf[0] == 'f' || buf[0] == 'F') { + *buffer &= ~reads->flag; + } + } + + ++reads; + } +} + +nsresult +XRE_ParseAppData(nsIFile* aINIFile, nsXREAppData *aAppData) +{ + NS_ENSURE_ARG(aINIFile && aAppData); + + nsresult rv; + + nsINIParser parser; + rv = parser.Init(aINIFile); + if (NS_FAILED(rv)) + return rv; + + nsCString str; + + ReadString strings[] = { + { "App", "Vendor", &aAppData->vendor }, + { "App", "Name", &aAppData->name }, + { "App", "RemotingName", &aAppData->remotingName }, + { "App", "Version", &aAppData->version }, + { "App", "BuildID", &aAppData->buildID }, + { "App", "ID", &aAppData->ID }, + { "App", "Copyright", &aAppData->copyright }, + { "App", "Profile", &aAppData->profile }, + { nullptr } + }; + ReadStrings(parser, strings); + + ReadFlag flags[] = { + { "XRE", "EnableProfileMigrator", NS_XRE_ENABLE_PROFILE_MIGRATOR }, + { nullptr } + }; + ReadFlags(parser, flags, &aAppData->flags); + + if (aAppData->size > offsetof(nsXREAppData, xreDirectory)) { + ReadString strings2[] = { + { "Gecko", "MinVersion", &aAppData->minVersion }, + { "Gecko", "MaxVersion", &aAppData->maxVersion }, + { nullptr } + }; + ReadStrings(parser, strings2); + } + + if (aAppData->size > offsetof(nsXREAppData, crashReporterURL)) { + ReadString strings3[] = { + { "Crash Reporter", "ServerURL", &aAppData->crashReporterURL }, + { nullptr } + }; + ReadStrings(parser, strings3); + ReadFlag flags2[] = { + { "Crash Reporter", "Enabled", NS_XRE_ENABLE_CRASH_REPORTER }, + { nullptr } + }; + ReadFlags(parser, flags2, &aAppData->flags); + } + + if (aAppData->size > offsetof(nsXREAppData, UAName)) { + ReadString strings4[] = { + { "App", "UAName", &aAppData->UAName }, + { nullptr } + }; + ReadStrings(parser, strings4); + } + + return NS_OK; +} + +void +XRE_FreeAppData(nsXREAppData *aAppData) +{ + if (!aAppData) { + NS_ERROR("Invalid arg"); + return; + } + + ScopedAppData* sad = static_cast<ScopedAppData*>(aAppData); + delete sad; +} diff --git a/toolkit/xre/EventTracer.cpp b/toolkit/xre/EventTracer.cpp new file mode 100644 index 0000000000..cb0d88524d --- /dev/null +++ b/toolkit/xre/EventTracer.cpp @@ -0,0 +1,267 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +/* + * Event loop instrumentation. This code attempts to measure the + * latency of the UI-thread event loop by firing native events at it from + * a background thread, and measuring how long it takes for them + * to be serviced. The measurement interval (kMeasureInterval, below) + * is also used as the upper bound of acceptable response time. + * When an event takes longer than that interval to be serviced, + * a sample will be written to the log. + * + * Usage: + * + * Set MOZ_INSTRUMENT_EVENT_LOOP=1 in the environment to enable + * this instrumentation. Currently only the UI process is instrumented. + * + * Set MOZ_INSTRUMENT_EVENT_LOOP_OUTPUT in the environment to a + * file path to contain the log output, the default is to log to stdout. + * + * Set MOZ_INSTRUMENT_EVENT_LOOP_THRESHOLD in the environment to an + * integer number of milliseconds to change the threshold for reporting. + * The default is 20 milliseconds. Unresponsive periods shorter than this + * threshold will not be reported. + * + * Set MOZ_INSTRUMENT_EVENT_LOOP_INTERVAL in the environment to an + * integer number of milliseconds to change the maximum sampling frequency. + * This variable controls how often events will be sent to the main + * thread's event loop to sample responsiveness. The sampler will not + * send events twice within LOOP_INTERVAL milliseconds. + * The default is 10 milliseconds. + * + * All logged output lines start with MOZ_EVENT_TRACE. All timestamps + * output are milliseconds since the epoch (PRTime / 1000). + * + * On startup, a line of the form: + * MOZ_EVENT_TRACE start <timestamp> + * will be output. + * + * On shutdown, a line of the form: + * MOZ_EVENT_TRACE stop <timestamp> + * will be output. + * + * When an event servicing time exceeds the threshold, a line of the form: + * MOZ_EVENT_TRACE sample <timestamp> <duration> + * will be output, where <duration> is the number of milliseconds that + * it took for the event to be serviced. Duration may contain a fractional + * component. + */ + +#include "GeckoProfiler.h" + +#include "EventTracer.h" + +#include <stdio.h> + +#include "mozilla/Preferences.h" +#include "mozilla/TimeStamp.h" +#include "mozilla/WidgetTraceEvent.h" +#include "nsDebug.h" +#include <limits.h> +#include <prenv.h> +#include <prinrval.h> +#include <prthread.h> +#include <prtime.h> + +#ifdef MOZ_WIDGET_GONK +#include "nsThreadUtils.h" +#include "nsIObserverService.h" +#include "mozilla/Services.h" +#endif + +using mozilla::TimeDuration; +using mozilla::TimeStamp; +using mozilla::FireAndWaitForTracerEvent; + +namespace { + +PRThread* sTracerThread = nullptr; +bool sExit = false; + +struct TracerStartClosure { + bool mLogTracing; + int32_t mThresholdInterval; +}; + +#ifdef MOZ_WIDGET_GONK +class EventLoopLagDispatcher : public Runnable +{ + public: + explicit EventLoopLagDispatcher(int aLag) + : mLag(aLag) {} + + NS_IMETHOD Run() override + { + nsCOMPtr<nsIObserverService> obsService = + mozilla::services::GetObserverService(); + if (!obsService) { + return NS_ERROR_FAILURE; + } + + nsAutoString value; + value.AppendInt(mLag); + return obsService->NotifyObservers(nullptr, "event-loop-lag", value.get()); + } + + private: + int mLag; +}; +#endif + +/* + * The tracer thread fires events at the native event loop roughly + * every kMeasureInterval. It will sleep to attempt not to send them + * more quickly, but if the response time is longer than kMeasureInterval + * it will not send another event until the previous response is received. + * + * The output defaults to stdout, but can be redirected to a file by + * settting the environment variable MOZ_INSTRUMENT_EVENT_LOOP_OUTPUT + * to the name of a file to use. + */ +void TracerThread(void *arg) +{ + PR_SetCurrentThreadName("Event Tracer"); + + TracerStartClosure* threadArgs = static_cast<TracerStartClosure*>(arg); + + // These are the defaults. They can be overridden by environment vars. + // This should be set to the maximum latency we'd like to allow + // for responsiveness. + int32_t thresholdInterval = threadArgs->mThresholdInterval; + PRIntervalTime threshold = PR_MillisecondsToInterval(thresholdInterval); + // This is the sampling interval. + PRIntervalTime interval = PR_MillisecondsToInterval(thresholdInterval / 2); + + sExit = false; + FILE* log = nullptr; + char* envfile = PR_GetEnv("MOZ_INSTRUMENT_EVENT_LOOP_OUTPUT"); + if (envfile) { + log = fopen(envfile, "w"); + } + if (log == nullptr) + log = stdout; + + char* thresholdenv = PR_GetEnv("MOZ_INSTRUMENT_EVENT_LOOP_THRESHOLD"); + if (thresholdenv && *thresholdenv) { + int val = atoi(thresholdenv); + if (val != 0 && val != INT_MAX && val != INT_MIN) { + threshold = PR_MillisecondsToInterval(val); + } + } + + char* intervalenv = PR_GetEnv("MOZ_INSTRUMENT_EVENT_LOOP_INTERVAL"); + if (intervalenv && *intervalenv) { + int val = atoi(intervalenv); + if (val != 0 && val != INT_MAX && val != INT_MIN) { + interval = PR_MillisecondsToInterval(val); + } + } + + if (threadArgs->mLogTracing) { + long long now = PR_Now() / PR_USEC_PER_MSEC; + fprintf(log, "MOZ_EVENT_TRACE start %llu\n", now); + } + + while (!sExit) { + TimeStamp start(TimeStamp::Now()); + profiler_responsiveness(start); + PRIntervalTime next_sleep = interval; + + //TODO: only wait up to a maximum of interval; return + // early if that threshold is exceeded and dump a stack trace + // or do something else useful. + if (FireAndWaitForTracerEvent()) { + TimeDuration duration = TimeStamp::Now() - start; + // Only report samples that exceed our measurement threshold. + long long now = PR_Now() / PR_USEC_PER_MSEC; + if (threadArgs->mLogTracing && duration.ToMilliseconds() > threshold) { + fprintf(log, "MOZ_EVENT_TRACE sample %llu %lf\n", + now, + duration.ToMilliseconds()); +#ifdef MOZ_WIDGET_GONK + NS_DispatchToMainThread( + new EventLoopLagDispatcher(int(duration.ToSecondsSigDigits() * 1000))); +#endif + } + + if (next_sleep > duration.ToMilliseconds()) { + next_sleep -= int(duration.ToMilliseconds()); + } + else { + // Don't sleep at all if this event took longer than the measure + // interval to deliver. + next_sleep = 0; + } + } + + if (next_sleep != 0 && !sExit) { + PR_Sleep(next_sleep); + } + } + + if (threadArgs->mLogTracing) { + long long now = PR_Now() / PR_USEC_PER_MSEC; + fprintf(log, "MOZ_EVENT_TRACE stop %llu\n", now); + } + + if (log != stdout) + fclose(log); + + delete threadArgs; +} + +} // namespace + +namespace mozilla { + +bool InitEventTracing(bool aLog) +{ + if (sTracerThread) + return true; + + // Initialize the widget backend. + if (!InitWidgetTracing()) + return false; + + // The tracer thread owns the object and will delete it. + TracerStartClosure* args = new TracerStartClosure(); + args->mLogTracing = aLog; + + // Pass the default threshold interval. + int32_t thresholdInterval = 20; + Preferences::GetInt("devtools.eventlooplag.threshold", &thresholdInterval); + args->mThresholdInterval = thresholdInterval; + + // Create a thread that will fire events back at the + // main thread to measure responsiveness. + MOZ_ASSERT(!sTracerThread, "Event tracing already initialized!"); + sTracerThread = PR_CreateThread(PR_USER_THREAD, + TracerThread, + args, + PR_PRIORITY_NORMAL, + PR_GLOBAL_THREAD, + PR_JOINABLE_THREAD, + 0); + return sTracerThread != nullptr; +} + +void ShutdownEventTracing() +{ + if (!sTracerThread) + return; + + sExit = true; + // Ensure that the tracer thread doesn't hang. + SignalTracerThread(); + + if (sTracerThread) + PR_JoinThread(sTracerThread); + sTracerThread = nullptr; + + // Allow the widget backend to clean up. + CleanUpWidgetTracing(); +} + +} // namespace mozilla diff --git a/toolkit/xre/EventTracer.h b/toolkit/xre/EventTracer.h new file mode 100644 index 0000000000..886b624e0a --- /dev/null +++ b/toolkit/xre/EventTracer.h @@ -0,0 +1,23 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef XRE_EVENTTRACER_H_ +#define XRE_EVENTTRACER_H_ + +namespace mozilla { + +// Create a thread that will fire events back at the +// main thread to measure responsiveness. Return true +// if the thread was created successfully. +// aLog If the tracing results should be printed to +// the console. +bool InitEventTracing(bool aLog); + +// Signal the background thread to stop, and join it. +// Must be called from the same thread that called InitEventTracing. +void ShutdownEventTracing(); + +} // namespace mozilla + +#endif /* XRE_EVENTTRACER_H_ */ diff --git a/toolkit/xre/MacApplicationDelegate.h b/toolkit/xre/MacApplicationDelegate.h new file mode 100644 index 0000000000..74f9a93ed2 --- /dev/null +++ b/toolkit/xre/MacApplicationDelegate.h @@ -0,0 +1,16 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +// This file defines the interface between Cocoa-specific Obj-C++ and generic C++, +// so it itself cannot have any Obj-C bits in it. + +#ifndef MacApplicationDelegate_h_ +#define MacApplicationDelegate_h_ + +void EnsureUseCocoaDockAPI(void); +void SetupMacApplicationDelegate(void); +void ProcessPendingGetURLAppleEvents(void); + +#endif diff --git a/toolkit/xre/MacApplicationDelegate.mm b/toolkit/xre/MacApplicationDelegate.mm new file mode 100644 index 0000000000..2b295aa7d9 --- /dev/null +++ b/toolkit/xre/MacApplicationDelegate.mm @@ -0,0 +1,396 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +// NSApplication delegate for Mac OS X Cocoa API. + +// As of 10.4 Tiger, the system can send six kinds of Apple Events to an application; +// a well-behaved XUL app should have some kind of handling for all of them. +// +// See http://developer.apple.com/documentation/Cocoa/Conceptual/ScriptableCocoaApplications/SApps_handle_AEs/chapter_11_section_3.html for details. + +#import <Cocoa/Cocoa.h> +#import <Carbon/Carbon.h> + +#include "nsCOMPtr.h" +#include "nsINativeAppSupport.h" +#include "nsAppRunner.h" +#include "nsAppShell.h" +#include "nsComponentManagerUtils.h" +#include "nsIServiceManager.h" +#include "nsServiceManagerUtils.h" +#include "nsIAppStartup.h" +#include "nsIObserverService.h" +#include "nsISupportsPrimitives.h" +#include "nsObjCExceptions.h" +#include "nsIFile.h" +#include "nsDirectoryServiceDefs.h" +#include "nsICommandLineRunner.h" +#include "nsIMacDockSupport.h" +#include "nsIStandaloneNativeMenu.h" +#include "nsILocalFileMac.h" +#include "nsString.h" +#include "nsCommandLineServiceMac.h" + +class AutoAutoreleasePool { +public: + AutoAutoreleasePool() + { + mLocalPool = [[NSAutoreleasePool alloc] init]; + } + ~AutoAutoreleasePool() + { + [mLocalPool release]; + } +private: + NSAutoreleasePool *mLocalPool; +}; + +@interface MacApplicationDelegate : NSObject<NSApplicationDelegate> +{ +} + +@end + +static bool sProcessedGetURLEvent = false; + +// Methods that can be called from non-Objective-C code. + +// This is needed, on relaunch, to force the OS to use the "Cocoa Dock API" +// instead of the "Carbon Dock API". For more info see bmo bug 377166. +void +EnsureUseCocoaDockAPI() +{ + NS_OBJC_BEGIN_TRY_ABORT_BLOCK; + + [GeckoNSApplication sharedApplication]; + + NS_OBJC_END_TRY_ABORT_BLOCK; +} + +void +SetupMacApplicationDelegate() +{ + NS_OBJC_BEGIN_TRY_ABORT_BLOCK; + + // this is called during startup, outside an event loop, and therefore + // needs an autorelease pool to avoid cocoa object leakage (bug 559075) + AutoAutoreleasePool pool; + + // Ensure that ProcessPendingGetURLAppleEvents() doesn't regress bug 377166. + [GeckoNSApplication sharedApplication]; + + // This call makes it so that application:openFile: doesn't get bogus calls + // from Cocoa doing its own parsing of the argument string. And yes, we need + // to use a string with a boolean value in it. That's just how it works. + [[NSUserDefaults standardUserDefaults] setObject:@"NO" + forKey:@"NSTreatUnknownArgumentsAsOpen"]; + + // Create the delegate. This should be around for the lifetime of the app. + id<NSApplicationDelegate> delegate = [[MacApplicationDelegate alloc] init]; + [[GeckoNSApplication sharedApplication] setDelegate:delegate]; + + NS_OBJC_END_TRY_ABORT_BLOCK; +} + +// Indirectly make the OS process any pending GetURL Apple events. This is +// done via _DPSNextEvent() (an undocumented AppKit function called from +// [NSApplication nextEventMatchingMask:untilDate:inMode:dequeue:]). Apple +// events are only processed if 'dequeue' is 'YES' -- so we need to call +// [NSApplication sendEvent:] on any event that gets returned. 'event' will +// never itself be an Apple event, and it may be 'nil' even when Apple events +// are processed. +void +ProcessPendingGetURLAppleEvents() +{ + AutoAutoreleasePool pool; + bool keepSpinning = true; + while (keepSpinning) { + sProcessedGetURLEvent = false; + NSEvent *event = [NSApp nextEventMatchingMask:NSAnyEventMask + untilDate:nil + inMode:NSDefaultRunLoopMode + dequeue:YES]; + if (event) + [NSApp sendEvent:event]; + keepSpinning = sProcessedGetURLEvent; + } +} + +@implementation MacApplicationDelegate + +- (id)init +{ + NS_OBJC_BEGIN_TRY_ABORT_BLOCK_RETURN; + + if ((self = [super init])) { + NSAppleEventManager *aeMgr = [NSAppleEventManager sharedAppleEventManager]; + + [aeMgr setEventHandler:self + andSelector:@selector(handleAppleEvent:withReplyEvent:) + forEventClass:kInternetEventClass + andEventID:kAEGetURL]; + + [aeMgr setEventHandler:self + andSelector:@selector(handleAppleEvent:withReplyEvent:) + forEventClass:'WWW!' + andEventID:'OURL']; + + [aeMgr setEventHandler:self + andSelector:@selector(handleAppleEvent:withReplyEvent:) + forEventClass:kCoreEventClass + andEventID:kAEOpenDocuments]; + + if (![NSApp windowsMenu]) { + // If the application has a windows menu, it will keep it up to date and + // prepend the window list to the Dock menu automatically. + NSMenu* windowsMenu = [[NSMenu alloc] initWithTitle:@"Window"]; + [NSApp setWindowsMenu:windowsMenu]; + [windowsMenu release]; + } + } + return self; + + NS_OBJC_END_TRY_ABORT_BLOCK_RETURN(nil); +} + +- (void)dealloc +{ + NS_OBJC_BEGIN_TRY_ABORT_BLOCK; + + NSAppleEventManager *aeMgr = [NSAppleEventManager sharedAppleEventManager]; + [aeMgr removeEventHandlerForEventClass:kInternetEventClass andEventID:kAEGetURL]; + [aeMgr removeEventHandlerForEventClass:'WWW!' andEventID:'OURL']; + [aeMgr removeEventHandlerForEventClass:kCoreEventClass andEventID:kAEOpenDocuments]; + [super dealloc]; + + NS_OBJC_END_TRY_ABORT_BLOCK; +} + +// The method that NSApplication calls upon a request to reopen, such as when +// the Dock icon is clicked and no windows are open. A "visible" window may be +// miniaturized, so we can't skip nsCocoaNativeReOpen() if 'flag' is 'true'. +- (BOOL)applicationShouldHandleReopen:(NSApplication*)theApp hasVisibleWindows:(BOOL)flag +{ + nsCOMPtr<nsINativeAppSupport> nas = do_CreateInstance(NS_NATIVEAPPSUPPORT_CONTRACTID); + NS_ENSURE_TRUE(nas, NO); + + // Go to the common Carbon/Cocoa reopen method. + nsresult rv = nas->ReOpen(); + NS_ENSURE_SUCCESS(rv, NO); + + // NO says we don't want NSApplication to do anything else for us. + return NO; +} + +// The method that NSApplication calls when documents are requested to be opened. +// It will be called once for each selected document. +- (BOOL)application:(NSApplication*)theApplication openFile:(NSString*)filename +{ + NS_OBJC_BEGIN_TRY_ABORT_BLOCK_RETURN; + + NSURL *url = [NSURL fileURLWithPath:filename]; + if (!url) + return NO; + + NSString *urlString = [url absoluteString]; + if (!urlString) + return NO; + + // Add the URL to any command line we're currently setting up. + if (CommandLineServiceMac::AddURLToCurrentCommandLine([urlString UTF8String])) + return YES; + + nsCOMPtr<nsILocalFileMac> inFile; + nsresult rv = NS_NewLocalFileWithCFURL((CFURLRef)url, true, getter_AddRefs(inFile)); + if (NS_FAILED(rv)) + return NO; + + nsCOMPtr<nsICommandLineRunner> cmdLine(do_CreateInstance("@mozilla.org/toolkit/command-line;1")); + if (!cmdLine) { + NS_ERROR("Couldn't create command line!"); + return NO; + } + + nsCString filePath; + rv = inFile->GetNativePath(filePath); + if (NS_FAILED(rv)) + return NO; + + nsCOMPtr<nsIFile> workingDir; + rv = NS_GetSpecialDirectory(NS_OS_CURRENT_WORKING_DIR, getter_AddRefs(workingDir)); + if (NS_FAILED(rv)) + return NO; + + const char *argv[3] = {nullptr, "-file", filePath.get()}; + rv = cmdLine->Init(3, argv, workingDir, nsICommandLine::STATE_REMOTE_EXPLICIT); + if (NS_FAILED(rv)) + return NO; + + if (NS_SUCCEEDED(cmdLine->Run())) + return YES; + + return NO; + + NS_OBJC_END_TRY_ABORT_BLOCK_RETURN(NO); +} + +// The method that NSApplication calls when documents are requested to be printed +// from the Finder (under the "File" menu). +// It will be called once for each selected document. +- (BOOL)application:(NSApplication*)theApplication printFile:(NSString*)filename +{ + return NO; +} + +// Create the menu that shows up in the Dock. +- (NSMenu*)applicationDockMenu:(NSApplication*)sender +{ + NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NIL; + + // Create the NSMenu that will contain the dock menu items. + NSMenu *menu = [[[NSMenu alloc] initWithTitle:@""] autorelease]; + [menu setAutoenablesItems:NO]; + + // Add application-specific dock menu items. On error, do not insert the + // dock menu items. + nsresult rv; + nsCOMPtr<nsIMacDockSupport> dockSupport = do_GetService("@mozilla.org/widget/macdocksupport;1", &rv); + if (NS_FAILED(rv) || !dockSupport) + return menu; + + nsCOMPtr<nsIStandaloneNativeMenu> dockMenu; + rv = dockSupport->GetDockMenu(getter_AddRefs(dockMenu)); + if (NS_FAILED(rv) || !dockMenu) + return menu; + + // Determine if the dock menu items should be displayed. This also gives + // the menu the opportunity to update itself before display. + bool shouldShowItems; + rv = dockMenu->MenuWillOpen(&shouldShowItems); + if (NS_FAILED(rv) || !shouldShowItems) + return menu; + + // Obtain a copy of the native menu. + NSMenu * nativeDockMenu; + rv = dockMenu->GetNativeMenu(reinterpret_cast<void **>(&nativeDockMenu)); + if (NS_FAILED(rv) || !nativeDockMenu) + return menu; + + // Loop through the application-specific dock menu and insert its + // contents into the dock menu that we are building for Cocoa. + int numDockMenuItems = [nativeDockMenu numberOfItems]; + if (numDockMenuItems > 0) { + if ([menu numberOfItems] > 0) + [menu addItem:[NSMenuItem separatorItem]]; + + for (int i = 0; i < numDockMenuItems; i++) { + NSMenuItem * itemCopy = [[nativeDockMenu itemAtIndex:i] copy]; + [menu addItem:itemCopy]; + [itemCopy release]; + } + } + + return menu; + + NS_OBJC_END_TRY_ABORT_BLOCK_NIL; +} + +// If we don't handle applicationShouldTerminate:, a call to [NSApp terminate:] +// (from the browser or from the OS) can result in an unclean shutdown. +- (NSApplicationTerminateReply)applicationShouldTerminate:(NSApplication *)sender +{ + nsCOMPtr<nsIObserverService> obsServ = + do_GetService("@mozilla.org/observer-service;1"); + if (!obsServ) + return NSTerminateNow; + + nsCOMPtr<nsISupportsPRBool> cancelQuit = + do_CreateInstance(NS_SUPPORTS_PRBOOL_CONTRACTID); + if (!cancelQuit) + return NSTerminateNow; + + cancelQuit->SetData(false); + obsServ->NotifyObservers(cancelQuit, "quit-application-requested", nullptr); + + bool abortQuit; + cancelQuit->GetData(&abortQuit); + if (abortQuit) + return NSTerminateCancel; + + nsCOMPtr<nsIAppStartup> appService = + do_GetService("@mozilla.org/toolkit/app-startup;1"); + if (appService) + appService->Quit(nsIAppStartup::eForceQuit); + + return NSTerminateNow; +} + +- (void)handleAppleEvent:(NSAppleEventDescriptor*)event withReplyEvent:(NSAppleEventDescriptor*)replyEvent +{ + if (!event) + return; + + AutoAutoreleasePool pool; + + bool isGetURLEvent = + ([event eventClass] == kInternetEventClass && [event eventID] == kAEGetURL); + if (isGetURLEvent) + sProcessedGetURLEvent = true; + + if (isGetURLEvent || + ([event eventClass] == 'WWW!' && [event eventID] == 'OURL')) { + NSString* urlString = [[event paramDescriptorForKeyword:keyDirectObject] stringValue]; + + // don't open chrome URLs + NSString* schemeString = [[NSURL URLWithString:urlString] scheme]; + if (!schemeString || + [schemeString compare:@"chrome" + options:NSCaseInsensitiveSearch + range:NSMakeRange(0, [schemeString length])] == NSOrderedSame) { + return; + } + + // Add the URL to any command line we're currently setting up. + if (CommandLineServiceMac::AddURLToCurrentCommandLine([urlString UTF8String])) + return; + + nsCOMPtr<nsICommandLineRunner> cmdLine(do_CreateInstance("@mozilla.org/toolkit/command-line;1")); + if (!cmdLine) { + NS_ERROR("Couldn't create command line!"); + return; + } + nsCOMPtr<nsIFile> workingDir; + nsresult rv = NS_GetSpecialDirectory(NS_OS_CURRENT_WORKING_DIR, getter_AddRefs(workingDir)); + if (NS_FAILED(rv)) + return; + const char *argv[3] = {nullptr, "-url", [urlString UTF8String]}; + rv = cmdLine->Init(3, argv, workingDir, nsICommandLine::STATE_REMOTE_EXPLICIT); + if (NS_FAILED(rv)) + return; + rv = cmdLine->Run(); + } + else if ([event eventClass] == kCoreEventClass && [event eventID] == kAEOpenDocuments) { + NSAppleEventDescriptor* fileListDescriptor = [event paramDescriptorForKeyword:keyDirectObject]; + if (!fileListDescriptor) + return; + + // Descriptor list indexing is one-based... + NSInteger numberOfFiles = [fileListDescriptor numberOfItems]; + for (NSInteger i = 1; i <= numberOfFiles; i++) { + NSString* urlString = [[fileListDescriptor descriptorAtIndex:i] stringValue]; + if (!urlString) + continue; + + // We need a path, not a URL + NSURL* url = [NSURL URLWithString:urlString]; + if (!url) + continue; + + [self application:NSApp openFile:[url path]]; + } + } +} + +@end diff --git a/toolkit/xre/MacAutoreleasePool.h b/toolkit/xre/MacAutoreleasePool.h new file mode 100644 index 0000000000..7668957f19 --- /dev/null +++ b/toolkit/xre/MacAutoreleasePool.h @@ -0,0 +1,31 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef MacAutoreleasePool_h_ +#define MacAutoreleasePool_h_ + +// This needs to be #include-able from non-ObjC code in nsAppRunner.cpp +#ifdef __OBJC__ +@class NSAutoreleasePool; +#else +class NSAutoreleasePool; +#endif + +namespace mozilla { + +class MacAutoreleasePool { +public: + MacAutoreleasePool(); + ~MacAutoreleasePool(); + +private: + NSAutoreleasePool *mPool; + + MacAutoreleasePool(const MacAutoreleasePool&); + void operator=(const MacAutoreleasePool&); +}; + +} // namespace mozilla + +#endif // MacAutoreleasePool_h_ diff --git a/toolkit/xre/MacAutoreleasePool.mm b/toolkit/xre/MacAutoreleasePool.mm new file mode 100644 index 0000000000..ae8ad51ff1 --- /dev/null +++ b/toolkit/xre/MacAutoreleasePool.mm @@ -0,0 +1,20 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "MacAutoreleasePool.h" +#include "nsDebug.h" + +#import <Foundation/Foundation.h> + +using mozilla::MacAutoreleasePool; + +MacAutoreleasePool::MacAutoreleasePool() +{ + mPool = [[NSAutoreleasePool alloc] init]; + NS_ASSERTION(mPool != nullptr, "failed to create pool, objects will leak"); +} + +MacAutoreleasePool::~MacAutoreleasePool() { + [mPool release]; +} diff --git a/toolkit/xre/MacLaunchHelper.h b/toolkit/xre/MacLaunchHelper.h new file mode 100644 index 0000000000..08035c53b6 --- /dev/null +++ b/toolkit/xre/MacLaunchHelper.h @@ -0,0 +1,23 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef MacLaunchHelper_h_ +#define MacLaunchHelper_h_ + +#include <stdint.h> + +#include <unistd.h> + +extern "C" { + /** + * Passing an aPid parameter to LaunchChildMac will wait for the launched + * process to terminate. When the process terminates, aPid will be set to the + * pid of the terminated process to confirm that it executed successfully. + */ + void LaunchChildMac(int aArgc, char** aArgv, pid_t* aPid = 0); + bool LaunchElevatedUpdate(int aArgc, char** aArgv, pid_t* aPid = 0); +} + +#endif diff --git a/toolkit/xre/MacLaunchHelper.mm b/toolkit/xre/MacLaunchHelper.mm new file mode 100644 index 0000000000..0dadb8de88 --- /dev/null +++ b/toolkit/xre/MacLaunchHelper.mm @@ -0,0 +1,137 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "MacLaunchHelper.h" + +#include "MacAutoreleasePool.h" +#include "mozilla/UniquePtr.h" +#include "nsIAppStartup.h" +#include "nsMemory.h" + +#include <Cocoa/Cocoa.h> +#include <crt_externs.h> +#include <ServiceManagement/ServiceManagement.h> +#include <Security/Authorization.h> +#include <spawn.h> +#include <stdio.h> + +using namespace mozilla; + +void LaunchChildMac(int aArgc, char** aArgv, pid_t* aPid) +{ + MacAutoreleasePool pool; + + @try { + NSString* launchPath = [NSString stringWithUTF8String:aArgv[0]]; + NSMutableArray* arguments = [NSMutableArray arrayWithCapacity:aArgc - 1]; + for (int i = 1; i < aArgc; i++) { + [arguments addObject:[NSString stringWithUTF8String:aArgv[i]]]; + } + NSTask* child = [NSTask launchedTaskWithLaunchPath:launchPath + arguments:arguments]; + if (aPid) { + *aPid = [child processIdentifier]; + // We used to use waitpid to wait for the process to terminate. This is + // incompatible with NSTask and we wait for the process to exit here + // instead. + [child waitUntilExit]; + } + } @catch (NSException* e) { + NSLog(@"%@: %@", e.name, e.reason); + } +} + +BOOL InstallPrivilegedHelper() +{ + AuthorizationRef authRef = NULL; + OSStatus status = AuthorizationCreate(NULL, + kAuthorizationEmptyEnvironment, + kAuthorizationFlagDefaults | + kAuthorizationFlagInteractionAllowed, + &authRef); + if (status != errAuthorizationSuccess) { + // AuthorizationCreate really shouldn't fail. + NSLog(@"AuthorizationCreate failed! NSOSStatusErrorDomain / %d", + (int)status); + return NO; + } + + BOOL result = NO; + AuthorizationItem authItem = { kSMRightBlessPrivilegedHelper, 0, NULL, 0 }; + AuthorizationRights authRights = { 1, &authItem }; + AuthorizationFlags flags = kAuthorizationFlagDefaults | + kAuthorizationFlagInteractionAllowed | + kAuthorizationFlagPreAuthorize | + kAuthorizationFlagExtendRights; + + // Obtain the right to install our privileged helper tool. + status = AuthorizationCopyRights(authRef, + &authRights, + kAuthorizationEmptyEnvironment, + flags, + NULL); + if (status != errAuthorizationSuccess) { + NSLog(@"AuthorizationCopyRights failed! NSOSStatusErrorDomain / %d", + (int)status); + } else { + CFErrorRef cfError; + // This does all the work of verifying the helper tool against the + // application and vice-versa. Once verification has passed, the embedded + // launchd.plist is extracted and placed in /Library/LaunchDaemons and then + // loaded. The executable is placed in /Library/PrivilegedHelperTools. + result = (BOOL)SMJobBless(kSMDomainSystemLaunchd, + (CFStringRef)@"org.mozilla.updater", + authRef, + &cfError); + if (!result) { + NSLog(@"Unable to install helper!"); + CFRelease(cfError); + } + } + + return result; +} + +void AbortElevatedUpdate() +{ + mozilla::MacAutoreleasePool pool; + + id updateServer = nil; + int currTry = 0; + const int numRetries = 10; // Number of IPC connection retries before + // giving up. + while (currTry < numRetries) { + @try { + updateServer = (id)[NSConnection + rootProxyForConnectionWithRegisteredName: + @"org.mozilla.updater.server" + host:nil + usingNameServer:[NSSocketPortNameServer sharedInstance]]; + if (updateServer && + [updateServer respondsToSelector:@selector(abort)]) { + [updateServer performSelector:@selector(abort)]; + return; + } + NSLog(@"Server doesn't exist or doesn't provide correct selectors."); + sleep(1); // Wait 1 second. + currTry++; + } @catch (NSException* e) { + NSLog(@"Encountered exception, retrying: %@: %@", e.name, e.reason); + sleep(1); // Wait 1 second. + currTry++; + } + } + NSLog(@"Unable to clean up updater."); +} + +bool LaunchElevatedUpdate(int aArgc, char** aArgv, pid_t* aPid) +{ + LaunchChildMac(aArgc, aArgv, aPid); + bool didSucceed = InstallPrivilegedHelper(); + if (!didSucceed) { + AbortElevatedUpdate(); + } + return didSucceed; +} diff --git a/toolkit/xre/Makefile.in b/toolkit/xre/Makefile.in new file mode 100644 index 0000000000..225dc3bbb6 --- /dev/null +++ b/toolkit/xre/Makefile.in @@ -0,0 +1,21 @@ +# -*- makefile -*- +# vim:set ts=8 sw=8 sts=8 noet: + +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + +milestone_txt = $(topsrcdir)/config/milestone.txt + +include $(topsrcdir)/config/rules.mk + +MOZ_BUILDID := $(shell awk '{print $$3}' $(DEPTH)/buildid.h) +$(call errorIfEmpty,GRE_MILESTONE MOZ_BUILDID) + +# Note these dependencies are broken because the target is *not* the cpp file. +# BUT, actually fixing it would make libxul rebuilt on every single incremental +# build because of the automatic buildid change. This is why we can't actually +# include buildid.h there, because it would add the dependency. +$(srcdir)/nsAppRunner.cpp: $(DEPTH)/buildid.h $(milestone_txt) + +nsAppRunner.$(OBJ_SUFFIX): DEFINES += -DMOZ_BUILDID=$(MOZ_BUILDID) diff --git a/toolkit/xre/MozMeegoAppService.h b/toolkit/xre/MozMeegoAppService.h new file mode 100644 index 0000000000..063d03e02b --- /dev/null +++ b/toolkit/xre/MozMeegoAppService.h @@ -0,0 +1,28 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef MOZMEEGOAPPSERVICE_H +#define MOZMEEGOAPPSERVICE_H + +#include <MApplicationService> + +/** + * App service class, which prevents registration to d-bus + * and allows multiple instances of application. This is + * required for Mozillas remote service to work, because + * it is initialized after MApplication. + */ +class MozMeegoAppService: public MApplicationService +{ + Q_OBJECT +public: + MozMeegoAppService(): MApplicationService(QString()) {} +public Q_SLOTS: + virtual QString registeredName() { return QString(); } + virtual bool isRegistered() { return false; } + virtual bool registerService() { return true; } +}; +#endif // MOZMEEGOAPPSERVICE_H diff --git a/toolkit/xre/ProfileReset.cpp b/toolkit/xre/ProfileReset.cpp new file mode 100644 index 0000000000..aef2d7746d --- /dev/null +++ b/toolkit/xre/ProfileReset.cpp @@ -0,0 +1,175 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "nsIAppStartup.h" +#include "nsIFile.h" +#include "nsIStringBundle.h" +#include "nsIToolkitProfile.h" +#include "nsIWindowWatcher.h" + +#include "ProfileReset.h" + +#include "nsDirectoryServiceDefs.h" +#include "nsDirectoryServiceUtils.h" +#include "nsPIDOMWindow.h" +#include "nsPrintfCString.h" +#include "nsToolkitCompsCID.h" +#include "nsXPCOMCIDInternal.h" +#include "nsXREAppData.h" + +#include "mozilla/Services.h" +#include "prtime.h" + +extern const nsXREAppData* gAppData; + +static const char kProfileProperties[] = + "chrome://mozapps/locale/profile/profileSelection.properties"; + +/** + * Creates a new profile with a timestamp in the name to use for profile reset. + */ +nsresult +CreateResetProfile(nsIToolkitProfileService* aProfileSvc, nsIToolkitProfile* *aNewProfile) +{ + MOZ_ASSERT(aProfileSvc, "NULL profile service"); + + nsCOMPtr<nsIToolkitProfile> newProfile; + // Make the new profile "default-" + the time in seconds since epoch for uniqueness. + nsAutoCString newProfileName("default-"); + newProfileName.Append(nsPrintfCString("%lld", PR_Now() / 1000)); + nsresult rv = aProfileSvc->CreateProfile(nullptr, // choose a default dir for us + newProfileName, + getter_AddRefs(newProfile)); + if (NS_FAILED(rv)) return rv; + + rv = aProfileSvc->Flush(); + if (NS_FAILED(rv)) return rv; + + newProfile.swap(*aNewProfile); + + return NS_OK; +} + +/** + * Delete the profile directory being reset after a backup and delete the local profile directory. + */ +nsresult +ProfileResetCleanup(nsIToolkitProfile* aOldProfile) +{ + nsresult rv; + nsCOMPtr<nsIFile> profileDir; + rv = aOldProfile->GetRootDir(getter_AddRefs(profileDir)); + if (NS_FAILED(rv)) return rv; + + nsCOMPtr<nsIFile> profileLocalDir; + rv = aOldProfile->GetLocalDir(getter_AddRefs(profileLocalDir)); + if (NS_FAILED(rv)) return rv; + + // Get the friendly name for the backup directory. + nsCOMPtr<nsIStringBundleService> sbs = mozilla::services::GetStringBundleService(); + if (!sbs) return NS_ERROR_FAILURE; + + nsCOMPtr<nsIStringBundle> sb; + rv = sbs->CreateBundle(kProfileProperties, getter_AddRefs(sb)); + if (!sb) return NS_ERROR_FAILURE; + + NS_ConvertUTF8toUTF16 appName(gAppData->name); + const char16_t* params[] = {appName.get(), appName.get()}; + + nsXPIDLString resetBackupDirectoryName; + + static const char16_t* kResetBackupDirectory = u"resetBackupDirectory"; + rv = sb->FormatStringFromName(kResetBackupDirectory, params, 2, + getter_Copies(resetBackupDirectoryName)); + + // Get info to copy the old root profile dir to the desktop as a backup. + nsCOMPtr<nsIFile> backupDest, containerDest, profileDest; + rv = NS_GetSpecialDirectory(NS_OS_DESKTOP_DIR, getter_AddRefs(backupDest)); + if (NS_FAILED(rv)) { + // Fall back to the home directory if the desktop is not available. + rv = NS_GetSpecialDirectory(NS_OS_HOME_DIR, getter_AddRefs(backupDest)); + if (NS_FAILED(rv)) return rv; + } + + // Try to create a directory for all the backups + backupDest->Clone(getter_AddRefs(containerDest)); + containerDest->Append(resetBackupDirectoryName); + rv = containerDest->Create(nsIFile::DIRECTORY_TYPE, 0700); + // It's OK if it already exists, if and only if it is a directory + if (rv == NS_ERROR_FILE_ALREADY_EXISTS) { + bool containerIsDir; + rv = containerDest->IsDirectory(&containerIsDir); + if (NS_FAILED(rv) || !containerIsDir) { + return rv; + } + } else if (NS_FAILED(rv)) { + return rv; + } + + // Get the name of the profile + nsAutoString leafName; + rv = profileDir->GetLeafName(leafName); + if (NS_FAILED(rv)) return rv; + + // Try to create a unique directory for the profile: + containerDest->Clone(getter_AddRefs(profileDest)); + profileDest->Append(leafName); + rv = profileDest->CreateUnique(nsIFile::DIRECTORY_TYPE, 0700); + if (NS_FAILED(rv)) return rv; + + // Get the unique profile name + rv = profileDest->GetLeafName(leafName); + if (NS_FAILED(rv)) return rv; + + // Delete the empty directory that CreateUnique just created. + rv = profileDest->Remove(false); + if (NS_FAILED(rv)) return rv; + + // Show a progress window while the cleanup happens since the disk I/O can take time. + nsCOMPtr<nsIWindowWatcher> windowWatcher(do_GetService(NS_WINDOWWATCHER_CONTRACTID)); + if (!windowWatcher) return NS_ERROR_FAILURE; + + nsCOMPtr<nsIAppStartup> appStartup(do_GetService(NS_APPSTARTUP_CONTRACTID)); + if (!appStartup) return NS_ERROR_FAILURE; + + nsCOMPtr<mozIDOMWindowProxy> progressWindow; + rv = windowWatcher->OpenWindow(nullptr, + kResetProgressURL, + "_blank", + "centerscreen,chrome,titlebar", + nullptr, + getter_AddRefs(progressWindow)); + if (NS_FAILED(rv)) return rv; + + // Create a new thread to do the bulk of profile cleanup to stay responsive. + nsCOMPtr<nsIThreadManager> tm = do_GetService(NS_THREADMANAGER_CONTRACTID); + nsCOMPtr<nsIThread> cleanupThread; + rv = tm->NewThread(0, 0, getter_AddRefs(cleanupThread)); + if (NS_SUCCEEDED(rv)) { + nsCOMPtr<nsIRunnable> runnable = new ProfileResetCleanupAsyncTask(profileDir, profileLocalDir, + containerDest, leafName); + cleanupThread->Dispatch(runnable, nsIThread::DISPATCH_NORMAL); + // The result callback will shut down the worker thread. + + nsIThread *thread = NS_GetCurrentThread(); + // Wait for the cleanup thread to complete. + while(!gProfileResetCleanupCompleted) { + NS_ProcessNextEvent(thread); + } + } else { + gProfileResetCleanupCompleted = true; + NS_WARNING("Cleanup thread creation failed"); + return rv; + } + // Close the progress window now that the cleanup thread is done. + auto* piWindow = nsPIDOMWindowOuter::From(progressWindow); + piWindow->Close(); + + // Delete the old profile from profiles.ini. The folder was already deleted by the thread above. + rv = aOldProfile->Remove(false); + if (NS_FAILED(rv)) NS_WARNING("Could not remove the profile"); + + return rv; +} diff --git a/toolkit/xre/ProfileReset.h b/toolkit/xre/ProfileReset.h new file mode 100644 index 0000000000..7b5efbc4ea --- /dev/null +++ b/toolkit/xre/ProfileReset.h @@ -0,0 +1,81 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "nsIToolkitProfileService.h" +#include "nsIFile.h" +#include "nsThreadUtils.h" + +static bool gProfileResetCleanupCompleted = false; +static const char kResetProgressURL[] = "chrome://global/content/resetProfileProgress.xul"; + +nsresult CreateResetProfile(nsIToolkitProfileService* aProfileSvc, + nsIToolkitProfile* *aNewProfile); + +nsresult ProfileResetCleanup(nsIToolkitProfile* aOldProfile); + +class ProfileResetCleanupResultTask : public mozilla::Runnable +{ +public: + ProfileResetCleanupResultTask() + : mWorkerThread(do_GetCurrentThread()) + { + MOZ_ASSERT(!NS_IsMainThread()); + } + + NS_IMETHOD Run() override { + MOZ_ASSERT(NS_IsMainThread()); + mWorkerThread->Shutdown(); + return NS_OK; + } + +private: + nsCOMPtr<nsIThread> mWorkerThread; +}; + +class ProfileResetCleanupAsyncTask : public mozilla::Runnable +{ +public: + ProfileResetCleanupAsyncTask(nsIFile* aProfileDir, nsIFile* aProfileLocalDir, + nsIFile* aTargetDir, const nsAString &aLeafName) + : mProfileDir(aProfileDir) + , mProfileLocalDir(aProfileLocalDir) + , mTargetDir(aTargetDir) + , mLeafName(aLeafName) + { } + +/** + * Copy a root profile to a backup folder before deleting it. Then delete the local profile dir. + */ + NS_IMETHOD Run() override + { + // Copy to the destination then delete the profile. A move doesn't follow links. + nsresult rv = mProfileDir->CopyToFollowingLinks(mTargetDir, mLeafName); + if (NS_SUCCEEDED(rv)) + rv = mProfileDir->Remove(true); + if (NS_WARN_IF(NS_FAILED(rv))) { + NS_WARNING("Could not backup the root profile directory"); + } + + // If we have a separate local cache profile directory, just delete it. + // Don't return an error if this fails so that reset can proceed if it can't be deleted. + bool sameDir; + nsresult rvLocal = mProfileDir->Equals(mProfileLocalDir, &sameDir); + if (NS_SUCCEEDED(rvLocal) && !sameDir) { + rvLocal = mProfileLocalDir->Remove(true); + if (NS_FAILED(rvLocal)) NS_WARNING("Could not remove the old local profile directory (cache)"); + } + gProfileResetCleanupCompleted = true; + + nsCOMPtr<nsIRunnable> resultRunnable = new ProfileResetCleanupResultTask(); + NS_DispatchToMainThread(resultRunnable); + return NS_OK; + } + +private: + nsCOMPtr<nsIFile> mProfileDir; + nsCOMPtr<nsIFile> mProfileLocalDir; + nsCOMPtr<nsIFile> mTargetDir; + nsString mLeafName; +}; diff --git a/toolkit/xre/UIKitDirProvider.h b/toolkit/xre/UIKitDirProvider.h new file mode 100644 index 0000000000..c74596125c --- /dev/null +++ b/toolkit/xre/UIKitDirProvider.h @@ -0,0 +1,13 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef TOOLKIT_XRE_UIKITDIRPROVIDER_H_ +#define TOOLKIT_XRE_UIKITDIRPROVIDER_H_ + +#include "nsString.h" + +bool GetUIKitDirectory(bool aLocal, nsACString& aUserDir); + +#endif // TOOLKIT_XRE_UIKITDIRPROVIDER_H_ diff --git a/toolkit/xre/UIKitDirProvider.mm b/toolkit/xre/UIKitDirProvider.mm new file mode 100644 index 0000000000..b5bd03c6e6 --- /dev/null +++ b/toolkit/xre/UIKitDirProvider.mm @@ -0,0 +1,19 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include <Foundation/Foundation.h> + +#include "UIKitDirProvider.h" + +bool GetUIKitDirectory(bool aLocal, nsACString& aUserDir) +{ + NSSearchPathDirectory directory = aLocal ? NSCachesDirectory : NSApplicationSupportDirectory; + NSArray* paths = NSSearchPathForDirectoriesInDomains(directory, NSUserDomainMask, YES); + if ([paths count] == 0) { + return false; + } + aUserDir = [[paths objectAtIndex:0] UTF8String]; + return true; +} diff --git a/toolkit/xre/glxtest.cpp b/toolkit/xre/glxtest.cpp new file mode 100644 index 0000000000..519a5e68be --- /dev/null +++ b/toolkit/xre/glxtest.cpp @@ -0,0 +1,349 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * vim: sw=2 ts=8 et : + */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + + +////////////////////////////////////////////////////////////////////////////// +// +// Explanation: See bug 639842. Safely getting GL driver info on X11 is hard, because the only way to do +// that is to create a GL context and call glGetString(), but with bad drivers, +// just creating a GL context may crash. +// +// This file implements the idea to do that in a separate process. +// +// The only non-static function here is fire_glxtest_process(). It creates a pipe, publishes its 'read' end as the +// mozilla::widget::glxtest_pipe global variable, forks, and runs that GLX probe in the child process, +// which runs the glxtest() static function. This creates a X connection, a GLX context, calls glGetString, and writes that +// to the 'write' end of the pipe. + +#include <cstdio> +#include <cstdlib> +#include <unistd.h> +#include <dlfcn.h> +#include "nscore.h" +#include <fcntl.h> +#include "stdint.h" + +#if MOZ_WIDGET_GTK == 2 +#include <glib.h> +#endif + +#ifdef __SUNPRO_CC +#include <stdio.h> +#endif + +#include "X11/Xlib.h" +#include "X11/Xutil.h" + +#include "mozilla/Unused.h" + +// stuff from glx.h +typedef struct __GLXcontextRec *GLXContext; +typedef XID GLXPixmap; +typedef XID GLXDrawable; +/* GLX 1.3 and later */ +typedef struct __GLXFBConfigRec *GLXFBConfig; +typedef XID GLXFBConfigID; +typedef XID GLXContextID; +typedef XID GLXWindow; +typedef XID GLXPbuffer; +#define GLX_RGBA 4 +#define GLX_RED_SIZE 8 +#define GLX_GREEN_SIZE 9 +#define GLX_BLUE_SIZE 10 + +// stuff from gl.h +typedef uint8_t GLubyte; +typedef uint32_t GLenum; +#define GL_VENDOR 0x1F00 +#define GL_RENDERER 0x1F01 +#define GL_VERSION 0x1F02 + +namespace mozilla { +namespace widget { +// the read end of the pipe, which will be used by GfxInfo +extern int glxtest_pipe; +// the PID of the glxtest process, to pass to waitpid() +extern pid_t glxtest_pid; +} +} + +// the write end of the pipe, which we're going to write to +static int write_end_of_the_pipe = -1; + +#if MOZ_WIDGET_GTK == 2 +static int gtk_write_end_of_the_pipe = -1; +int gtk_read_end_of_the_pipe = -1; +#endif + +// C++ standard collides with C standard in that it doesn't allow casting void* to function pointer types. +// So the work-around is to convert first to size_t. +// http://www.trilithium.com/johan/2004/12/problem-with-dlsym/ +template<typename func_ptr_type> +static func_ptr_type cast(void *ptr) +{ + return reinterpret_cast<func_ptr_type>( + reinterpret_cast<size_t>(ptr) + ); +} + +static void fatal_error(const char *str) +{ + mozilla::Unused << write(write_end_of_the_pipe, str, strlen(str)); + mozilla::Unused << write(write_end_of_the_pipe, "\n", 1); + _exit(EXIT_FAILURE); +} + +static int +x_error_handler(Display *, XErrorEvent *ev) +{ + enum { bufsize = 1024 }; + char buf[bufsize]; + int length = snprintf(buf, bufsize, + "X error occurred in GLX probe, error_code=%d, request_code=%d, minor_code=%d\n", + ev->error_code, + ev->request_code, + ev->minor_code); + mozilla::Unused << write(write_end_of_the_pipe, buf, length); + _exit(EXIT_FAILURE); + return 0; +} + + +// glxtest is declared inside extern "C" so that the name is not mangled. +// The name is used in build/valgrind/x86_64-redhat-linux-gnu.sup to suppress +// memory leak errors because we run it inside a short lived fork and we don't +// care about leaking memory +extern "C" { + +void glxtest() +{ + // we want to redirect to /dev/null stdout, stderr, and while we're at it, + // any PR logging file descriptors. To that effect, we redirect all positive + // file descriptors up to what open() returns here. In particular, 1 is stdout and 2 is stderr. + int fd = open("/dev/null", O_WRONLY); + for (int i = 1; i < fd; i++) + dup2(fd, i); + close(fd); + +#if MOZ_WIDGET_GTK == 2 + // On Gtk+2 builds, try to get the Gtk+3 version if it's installed, and + // use that in nsSystemInfo for secondaryLibrary. Better safe than sorry, + // we want to load the Gtk+3 library in a subprocess, and since we already + // have such a subprocess for the GLX test, we piggy back on it. + void *gtk3 = dlopen("libgtk-3.so.0", RTLD_LOCAL | RTLD_LAZY); + if (gtk3) { + auto gtk_get_major_version = reinterpret_cast<guint (*)(void)>( + dlsym(gtk3, "gtk_get_major_version")); + auto gtk_get_minor_version = reinterpret_cast<guint (*)(void)>( + dlsym(gtk3, "gtk_get_minor_version")); + auto gtk_get_micro_version = reinterpret_cast<guint (*)(void)>( + dlsym(gtk3, "gtk_get_micro_version")); + + if (gtk_get_major_version && gtk_get_minor_version && + gtk_get_micro_version) { + // 64 bytes is going to be well enough for "GTK " followed by 3 integers + // separated with dots. + char gtkver[64]; + int len = snprintf(gtkver, sizeof(gtkver), "GTK %u.%u.%u", + gtk_get_major_version(), gtk_get_minor_version(), + gtk_get_micro_version()); + if (len > 0 && size_t(len) < sizeof(gtkver)) { + mozilla::Unused << write(gtk_write_end_of_the_pipe, gtkver, len); + } + } + } +#endif + + + if (getenv("MOZ_AVOID_OPENGL_ALTOGETHER")) + fatal_error("The MOZ_AVOID_OPENGL_ALTOGETHER environment variable is defined"); + + ///// Open libGL and load needed symbols ///// +#ifdef __OpenBSD__ + #define LIBGL_FILENAME "libGL.so" +#else + #define LIBGL_FILENAME "libGL.so.1" +#endif + void *libgl = dlopen(LIBGL_FILENAME, RTLD_LAZY); + if (!libgl) + fatal_error("Unable to load " LIBGL_FILENAME); + + typedef void* (* PFNGLXGETPROCADDRESS) (const char *); + PFNGLXGETPROCADDRESS glXGetProcAddress = cast<PFNGLXGETPROCADDRESS>(dlsym(libgl, "glXGetProcAddress")); + + if (!glXGetProcAddress) + fatal_error("Unable to find glXGetProcAddress in " LIBGL_FILENAME); + + typedef GLXFBConfig* (* PFNGLXQUERYEXTENSION) (Display *, int *, int *); + PFNGLXQUERYEXTENSION glXQueryExtension = cast<PFNGLXQUERYEXTENSION>(glXGetProcAddress("glXQueryExtension")); + + typedef GLXFBConfig* (* PFNGLXQUERYVERSION) (Display *, int *, int *); + PFNGLXQUERYVERSION glXQueryVersion = cast<PFNGLXQUERYVERSION>(dlsym(libgl, "glXQueryVersion")); + + typedef XVisualInfo* (* PFNGLXCHOOSEVISUAL) (Display *, int, int *); + PFNGLXCHOOSEVISUAL glXChooseVisual = cast<PFNGLXCHOOSEVISUAL>(glXGetProcAddress("glXChooseVisual")); + + typedef GLXContext (* PFNGLXCREATECONTEXT) (Display *, XVisualInfo *, GLXContext, Bool); + PFNGLXCREATECONTEXT glXCreateContext = cast<PFNGLXCREATECONTEXT>(glXGetProcAddress("glXCreateContext")); + + typedef Bool (* PFNGLXMAKECURRENT) (Display*, GLXDrawable, GLXContext); + PFNGLXMAKECURRENT glXMakeCurrent = cast<PFNGLXMAKECURRENT>(glXGetProcAddress("glXMakeCurrent")); + + typedef void (* PFNGLXDESTROYCONTEXT) (Display*, GLXContext); + PFNGLXDESTROYCONTEXT glXDestroyContext = cast<PFNGLXDESTROYCONTEXT>(glXGetProcAddress("glXDestroyContext")); + + typedef GLubyte* (* PFNGLGETSTRING) (GLenum); + PFNGLGETSTRING glGetString = cast<PFNGLGETSTRING>(glXGetProcAddress("glGetString")); + + if (!glXQueryExtension || + !glXQueryVersion || + !glXChooseVisual || + !glXCreateContext || + !glXMakeCurrent || + !glXDestroyContext || + !glGetString) + { + fatal_error("glXGetProcAddress couldn't find required functions"); + } + ///// Open a connection to the X server ///// + Display *dpy = XOpenDisplay(nullptr); + if (!dpy) + fatal_error("Unable to open a connection to the X server"); + + ///// Check that the GLX extension is present ///// + if (!glXQueryExtension(dpy, nullptr, nullptr)) + fatal_error("GLX extension missing"); + + XSetErrorHandler(x_error_handler); + + ///// Get a visual ///// + int attribs[] = { + GLX_RGBA, + GLX_RED_SIZE, 1, + GLX_GREEN_SIZE, 1, + GLX_BLUE_SIZE, 1, + None }; + XVisualInfo *vInfo = glXChooseVisual(dpy, DefaultScreen(dpy), attribs); + if (!vInfo) + fatal_error("No visuals found"); + + // using a X11 Window instead of a GLXPixmap does not crash + // fglrx in indirect rendering. bug 680644 + Window window; + XSetWindowAttributes swa; + swa.colormap = XCreateColormap(dpy, RootWindow(dpy, vInfo->screen), + vInfo->visual, AllocNone); + + swa.border_pixel = 0; + window = XCreateWindow(dpy, RootWindow(dpy, vInfo->screen), + 0, 0, 16, 16, + 0, vInfo->depth, InputOutput, vInfo->visual, + CWBorderPixel | CWColormap, &swa); + + ///// Get a GL context and make it current ////// + GLXContext context = glXCreateContext(dpy, vInfo, nullptr, True); + glXMakeCurrent(dpy, window, context); + + ///// Look for this symbol to determine texture_from_pixmap support ///// + void* glXBindTexImageEXT = glXGetProcAddress("glXBindTexImageEXT"); + + ///// Get GL vendor/renderer/versions strings ///// + enum { bufsize = 1024 }; + char buf[bufsize]; + const GLubyte *vendorString = glGetString(GL_VENDOR); + const GLubyte *rendererString = glGetString(GL_RENDERER); + const GLubyte *versionString = glGetString(GL_VERSION); + + if (!vendorString || !rendererString || !versionString) + fatal_error("glGetString returned null"); + + int length = snprintf(buf, bufsize, + "VENDOR\n%s\nRENDERER\n%s\nVERSION\n%s\nTFP\n%s\n", + vendorString, + rendererString, + versionString, + glXBindTexImageEXT ? "TRUE" : "FALSE"); + if (length >= bufsize) + fatal_error("GL strings length too large for buffer size"); + + ///// Clean up. Indeed, the parent process might fail to kill us (e.g. if it doesn't need to check GL info) + ///// so we might be staying alive for longer than expected, so it's important to consume as little memory as + ///// possible. Also we want to check that we're able to do that too without generating X errors. + glXMakeCurrent(dpy, None, nullptr); // must release the GL context before destroying it + glXDestroyContext(dpy, context); + XDestroyWindow(dpy, window); + XFreeColormap(dpy, swa.colormap); + +#ifdef NS_FREE_PERMANENT_DATA // conditionally defined in nscore.h, don't forget to #include it above + XCloseDisplay(dpy); +#else + // This XSync call wanted to be instead: + // XCloseDisplay(dpy); + // but this can cause 1-minute stalls on certain setups using Nouveau, see bug 973192 + XSync(dpy, False); +#endif + + dlclose(libgl); + + ///// Finally write data to the pipe + mozilla::Unused << write(write_end_of_the_pipe, buf, length); +} + +} + +/** \returns true in the child glxtest process, false in the parent process */ +bool fire_glxtest_process() +{ + int pfd[2]; + if (pipe(pfd) == -1) { + perror("pipe"); + return false; + } +#if MOZ_WIDGET_GTK == 2 + int gtkpfd[2]; + if (pipe(gtkpfd) == -1) { + perror("pipe"); + return false; + } +#endif + pid_t pid = fork(); + if (pid < 0) { + perror("fork"); + close(pfd[0]); + close(pfd[1]); +#if MOZ_WIDGET_GTK == 2 + close(gtkpfd[0]); + close(gtkpfd[1]); +#endif + return false; + } + // The child exits early to avoid running the full shutdown sequence and avoid conflicting with threads + // we have already spawned (like the profiler). + if (pid == 0) { + close(pfd[0]); + write_end_of_the_pipe = pfd[1]; +#if MOZ_WIDGET_GTK == 2 + close(gtkpfd[0]); + gtk_write_end_of_the_pipe = gtkpfd[1]; +#endif + glxtest(); + close(pfd[1]); +#if MOZ_WIDGET_GTK == 2 + close(gtkpfd[1]); +#endif + _exit(0); + } + + close(pfd[1]); + mozilla::widget::glxtest_pipe = pfd[0]; + mozilla::widget::glxtest_pid = pid; +#if MOZ_WIDGET_GTK == 2 + close(gtkpfd[1]); + gtk_read_end_of_the_pipe = gtkpfd[0]; +#endif + return false; +} diff --git a/toolkit/xre/moz.build b/toolkit/xre/moz.build new file mode 100644 index 0000000000..dd15dc0bd2 --- /dev/null +++ b/toolkit/xre/moz.build @@ -0,0 +1,185 @@ +# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*- +# vim: set filetype=python: +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + +if CONFIG['OS_ARCH'] == 'WINNT': + TEST_DIRS += ['test/win'] + +MOCHITEST_MANIFESTS += ['test/mochitest.ini'] +BROWSER_CHROME_MANIFESTS += ['test/browser.ini'] + +XPIDL_SOURCES += [ + 'nsINativeAppSupport.idl', +] + +if CONFIG['OS_ARCH'] == 'WINNT': + XPIDL_SOURCES += [ + 'nsIWinAppHelper.idl', + ] + +XPIDL_MODULE = 'xulapp' + +EXPORTS += ['nsAppRunner.h'] + +if CONFIG['MOZ_INSTRUMENT_EVENT_LOOP']: + EXPORTS += ['EventTracer.h'] + +if CONFIG['MOZ_WIDGET_TOOLKIT'] == 'windows': + UNIFIED_SOURCES += [ + 'nsNativeAppSupportWin.cpp', + ] +elif CONFIG['MOZ_WIDGET_TOOLKIT'] == 'cocoa': + UNIFIED_SOURCES += [ + 'MacApplicationDelegate.mm', + 'MacAutoreleasePool.mm', + 'MacLaunchHelper.mm', + 'nsCommandLineServiceMac.cpp', + 'nsNativeAppSupportCocoa.mm', + 'updaterfileutils_osx.mm', + ] +elif CONFIG['MOZ_WIDGET_TOOLKIT'] == 'uikit': + UNIFIED_SOURCES += [ + 'nsNativeAppSupportDefault.cpp', + 'UIKitDirProvider.mm', + ] +elif 'gtk' in CONFIG['MOZ_WIDGET_TOOLKIT']: + UNIFIED_SOURCES += [ + 'nsNativeAppSupportUnix.cpp', + ] +else: + UNIFIED_SOURCES += [ + 'nsNativeAppSupportDefault.cpp', + ] + +if CONFIG['MOZ_WIDGET_TOOLKIT'] == 'gtk3': + UNIFIED_SOURCES += [ + 'nsGDKErrorHandler.cpp', + ] + +if CONFIG['MOZ_X11']: + EXPORTS += ['nsX11ErrorHandler.h'] + UNIFIED_SOURCES += [ + 'nsX11ErrorHandler.cpp', + ] + +if CONFIG['MOZ_WIDGET_TOOLKIT'] == 'android': + UNIFIED_SOURCES += [ + 'nsAndroidStartup.cpp', + ] + +UNIFIED_SOURCES += [ + 'CreateAppData.cpp', + 'nsConsoleWriter.cpp', + 'nsNativeAppSupportBase.cpp', + 'nsSigHandlers.cpp', + 'nsXREDirProvider.cpp', +] + +# nsAppRunner.cpp and ProfileReset.cpp cannot be built in unified mode because +# they pull in OS X system headers. +# nsEmbedFunctions.cpp cannot be built in unified mode because it pulls in X11 headers. +SOURCES += [ + 'nsAppRunner.cpp', + 'nsEmbedFunctions.cpp', + 'ProfileReset.cpp', +] + +if CONFIG['MOZ_GL_DEFAULT_PROVIDER'] == 'GLX': + UNIFIED_SOURCES += [ + 'glxtest.cpp', + ] + +if CONFIG['MOZ_INSTRUMENT_EVENT_LOOP']: + UNIFIED_SOURCES += [ + 'EventTracer.cpp', + ] + +if CONFIG['MOZ_UPDATER']: + if CONFIG['MOZ_WIDGET_TOOLKIT'] != 'android': + UNIFIED_SOURCES += [ + 'nsUpdateDriver.cpp', + ] + +include('/ipc/chromium/chromium-config.mozbuild') + +FINAL_LIBRARY = 'xul' + +if CONFIG['MOZ_GL_DEFAULT_PROVIDER'] == 'GLX': + DEFINES['USE_GLX_TEST'] = True + +for var in ('MOZ_APP_NAME', 'MOZ_APP_BASENAME', 'MOZ_APP_VERSION', 'OS_TARGET', + 'MOZ_WIDGET_TOOLKIT'): + DEFINES[var] = '"%s"' % CONFIG[var] + +if CONFIG['MOZ_UPDATER'] and CONFIG['MOZ_WIDGET_TOOLKIT'] != 'android': + DEFINES['MOZ_UPDATER'] = True + +if CONFIG['MOZ_WIDGET_TOOLKIT'] == 'windows': + DEFINES['WIN32_LEAN_AND_MEAN'] = True + DEFINES['UNICODE'] = True + DEFINES['_UNICODE'] = True + +if CONFIG['MOZ_WIDGET_TOOLKIT'] == 'android': + DEFINES['ANDROID_PACKAGE_NAME'] = '"%s"' % CONFIG['ANDROID_PACKAGE_NAME'] + +if CONFIG['TARGET_XPCOM_ABI']: + DEFINES['TARGET_OS_ABI'] = '"%s_%s"' % (CONFIG['OS_TARGET'], + CONFIG['TARGET_XPCOM_ABI']) + +if CONFIG['OS_ARCH'] == 'Linux' and 'lib64' in CONFIG['libdir']: + DEFINES['HAVE_USR_LIB64_DIR'] = True + +DEFINES['GRE_MILESTONE'] = CONFIG['GRE_MILESTONE'] + +for var in ('APP_VERSION', 'APP_ID'): + DEFINES[var] = CONFIG['MOZ_%s' % var] + +if CONFIG['MOZ_BUILD_APP'] == 'browser': + DEFINES['MOZ_BUILD_APP_IS_BROWSER'] = True + +LOCAL_INCLUDES += [ + '../profile', + '/config', + '/dom/base', + '/dom/ipc', + '/testing/gtest/mozilla', + '/toolkit/crashreporter', + '/xpcom/build', +] + +if CONFIG['MOZ_SANDBOX'] and CONFIG['OS_ARCH'] == 'WINNT': + LOCAL_INCLUDES += [ + '/security/sandbox/chromium', + '/security/sandbox/chromium-shim', + ] + +if CONFIG['MOZ_WIDGET_TOOLKIT'] == 'cocoa': + LOCAL_INCLUDES += [ + '/widget', + '/widget/cocoa', + ] + +if CONFIG['MOZ_ENABLE_XREMOTE']: + LOCAL_INCLUDES += [ + '/widget/xremoteclient', + ] + +CXXFLAGS += CONFIG['TK_CFLAGS'] +CXXFLAGS += CONFIG['MOZ_DBUS_CFLAGS'] +CXXFLAGS += CONFIG['MOZ_DBUS_GLIB_CFLAGS'] + +if 'gtk' in CONFIG['MOZ_WIDGET_TOOLKIT']: + CXXFLAGS += CONFIG['MOZ_PANGO_CFLAGS'] + +DEFINES['TOPOBJDIR'] = TOPOBJDIR +FINAL_TARGET_PP_FILES += [ + 'platform.ini' +] + +if CONFIG['GNU_CXX']: + CXXFLAGS += ['-Wno-error=shadow'] + +if CONFIG['MOZ_IPDL_TESTS']: + DEFINES['MOZ_IPDL_TESTS'] = True diff --git a/toolkit/xre/nsAndroidStartup.cpp b/toolkit/xre/nsAndroidStartup.cpp new file mode 100644 index 0000000000..a88c58e5d9 --- /dev/null +++ b/toolkit/xre/nsAndroidStartup.cpp @@ -0,0 +1,55 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include <android/log.h> + +#include <jni.h> + +#include <stdlib.h> +#include <string.h> +#include <pthread.h> + +#include "mozilla/jni/Utils.h" +#include "nsTArray.h" +#include "nsString.h" +#include "nsIFile.h" +#include "nsAppRunner.h" +#include "APKOpen.h" +#include "nsExceptionHandler.h" + +#define LOG(args...) __android_log_print(ANDROID_LOG_INFO, MOZ_APP_NAME, args) + +extern "C" NS_EXPORT void +GeckoStart(JNIEnv* env, char* data, const nsXREAppData* appData) +{ + mozilla::jni::SetGeckoThreadEnv(env); + +#ifdef MOZ_CRASHREPORTER + const struct mapping_info *info = getLibraryMapping(); + while (info->name) { + CrashReporter::AddLibraryMapping(info->name, info->base, + info->len, info->offset); + info++; + } +#endif + + if (!data) { + LOG("Failed to get arguments for GeckoStart\n"); + return; + } + + nsTArray<char *> targs; + char *arg = strtok(data, " "); + while (arg) { + targs.AppendElement(arg); + arg = strtok(nullptr, " "); + } + targs.AppendElement(static_cast<char *>(nullptr)); + + int result = XRE_main(targs.Length() - 1, targs.Elements(), appData, 0); + + if (result) + LOG("XRE_main returned %d", result); +} diff --git a/toolkit/xre/nsAppRunner.cpp b/toolkit/xre/nsAppRunner.cpp new file mode 100644 index 0000000000..4979e16523 --- /dev/null +++ b/toolkit/xre/nsAppRunner.cpp @@ -0,0 +1,5068 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "mozilla/dom/ContentParent.h" +#include "mozilla/dom/ContentChild.h" +#include "mozilla/ipc/GeckoChildProcessHost.h" + +#include "mozilla/ArrayUtils.h" +#include "mozilla/Attributes.h" +#include "mozilla/ChaosMode.h" +#include "mozilla/IOInterposer.h" +#include "mozilla/Likely.h" +#include "mozilla/MemoryChecking.h" +#include "mozilla/Poison.h" +#include "mozilla/Preferences.h" +#include "mozilla/ScopeExit.h" +#include "mozilla/Services.h" +#include "mozilla/ServoBindings.h" +#include "mozilla/Telemetry.h" + +#include "nsAppRunner.h" +#include "mozilla/AppData.h" +#if defined(MOZ_UPDATER) && !defined(MOZ_WIDGET_ANDROID) +#include "nsUpdateDriver.h" +#endif +#include "ProfileReset.h" + +#ifdef MOZ_INSTRUMENT_EVENT_LOOP +#include "EventTracer.h" +#endif + +#ifdef XP_MACOSX +#include "nsVersionComparator.h" +#include "MacLaunchHelper.h" +#include "MacApplicationDelegate.h" +#include "MacAutoreleasePool.h" +// these are needed for sysctl +#include <sys/types.h> +#include <sys/sysctl.h> +#endif + +#include "prmem.h" +#include "prnetdb.h" +#include "prprf.h" +#include "prproces.h" +#include "prenv.h" +#include "prtime.h" + +#include "nsIAppShellService.h" +#include "nsIAppStartup.h" +#include "nsIAppStartupNotifier.h" +#include "nsIMutableArray.h" +#include "nsICategoryManager.h" +#include "nsIChromeRegistry.h" +#include "nsICommandLineRunner.h" +#include "nsIComponentManager.h" +#include "nsIComponentRegistrar.h" +#include "nsIConsoleService.h" +#include "nsIContentHandler.h" +#include "nsIDialogParamBlock.h" +#include "nsIDOMWindow.h" +#include "mozilla/ModuleUtils.h" +#include "nsIIOService2.h" +#include "nsIObserverService.h" +#include "nsINativeAppSupport.h" +#include "nsIPlatformInfo.h" +#include "nsIProcess.h" +#include "nsIProfileUnlocker.h" +#include "nsIPromptService.h" +#include "nsIServiceManager.h" +#include "nsIStringBundle.h" +#include "nsISupportsPrimitives.h" +#include "nsIToolkitChromeRegistry.h" +#include "nsIToolkitProfile.h" +#include "nsIToolkitProfileService.h" +#include "nsIURI.h" +#include "nsIURL.h" +#include "nsIWindowCreator.h" +#include "nsIWindowMediator.h" +#include "nsIWindowWatcher.h" +#include "nsIXULAppInfo.h" +#include "nsIXULRuntime.h" +#include "nsPIDOMWindow.h" +#include "nsIBaseWindow.h" +#include "nsIWidget.h" +#include "nsIDocShell.h" +#include "nsAppShellCID.h" +#include "mozilla/scache/StartupCache.h" +#include "gfxPrefs.h" + +#include "mozilla/Unused.h" + +#ifdef XP_WIN +#include "nsIWinAppHelper.h" +#include <windows.h> +#include <intrin.h> +#include <math.h> +#include "cairo/cairo-features.h" +#include "mozilla/WindowsVersion.h" +#include "mozilla/mscom/MainThreadRuntime.h" +#include "mozilla/widget/AudioSession.h" + +#ifndef PROCESS_DEP_ENABLE +#define PROCESS_DEP_ENABLE 0x1 +#endif +#endif + +#if (defined(XP_WIN) || defined(XP_MACOSX)) && defined(MOZ_CONTENT_SANDBOX) +#include "nsIUUIDGenerator.h" +#endif + +#ifdef ACCESSIBILITY +#include "nsAccessibilityService.h" +#if defined(XP_WIN) +#include "mozilla/a11y/Compatibility.h" +#endif +#endif + +#include "nsCRT.h" +#include "nsCOMPtr.h" +#include "nsDirectoryServiceDefs.h" +#include "nsDirectoryServiceUtils.h" +#include "nsEmbedCID.h" +#include "nsNetUtil.h" +#include "nsReadableUtils.h" +#include "nsXPCOM.h" +#include "nsXPCOMCIDInternal.h" +#include "nsXPIDLString.h" +#include "nsPrintfCString.h" +#include "nsVersionComparator.h" + +#include "nsAppDirectoryServiceDefs.h" +#include "nsXULAppAPI.h" +#include "nsXREDirProvider.h" +#include "nsToolkitCompsCID.h" + +#include "nsINIParser.h" +#include "mozilla/Omnijar.h" +#include "mozilla/StartupTimeline.h" +#include "mozilla/LateWriteChecks.h" + +#include <stdlib.h> +#include <locale.h> + +#ifdef XP_UNIX +#include <sys/stat.h> +#include <unistd.h> +#include <pwd.h> +#endif + +#ifdef XP_WIN +#include <process.h> +#include <shlobj.h> +#include "nsThreadUtils.h" +#include <comdef.h> +#include <wbemidl.h> +#include "WinUtils.h" +#endif + +#ifdef XP_MACOSX +#include "nsILocalFileMac.h" +#include "nsCommandLineServiceMac.h" +#endif + +// for X remote support +#ifdef MOZ_ENABLE_XREMOTE +#include "XRemoteClient.h" +#include "nsIRemoteService.h" +#include "nsProfileLock.h" +#include "SpecialSystemDirectory.h" +#include <sched.h> +// Time to wait for the remoting service to start +#define MOZ_XREMOTE_START_TIMEOUT_SEC 5 +#endif + +#if defined(DEBUG) && defined(XP_WIN32) +#include <malloc.h> +#endif + +#if defined (XP_MACOSX) +#include <Carbon/Carbon.h> +#endif + +#ifdef DEBUG +#include "mozilla/Logging.h" +#endif + +#ifdef MOZ_JPROF +#include "jprof.h" +#endif + +#ifdef MOZ_CRASHREPORTER +#include "nsExceptionHandler.h" +#include "nsICrashReporter.h" +#define NS_CRASHREPORTER_CONTRACTID "@mozilla.org/toolkit/crash-reporter;1" +#include "nsIPrefService.h" +#include "nsIMemoryInfoDumper.h" +#if defined(XP_LINUX) && !defined(ANDROID) +#include "mozilla/widget/LSBUtils.h" +#endif +#endif + +#include "base/command_line.h" +#include "GTestRunner.h" + +#ifdef MOZ_WIDGET_ANDROID +#include "GeneratedJNIWrappers.h" +#endif + +#if defined(MOZ_SANDBOX) +#if defined(XP_LINUX) && !defined(ANDROID) +#include "mozilla/SandboxInfo.h" +#elif defined(XP_WIN) +#include "SandboxBroker.h" +#endif +#endif + +extern uint32_t gRestartMode; +extern void InstallSignalHandlers(const char *ProgramName); + +#define FILE_COMPATIBILITY_INFO NS_LITERAL_CSTRING("compatibility.ini") +#define FILE_INVALIDATE_CACHES NS_LITERAL_CSTRING(".purgecaches") + +int gArgc; +char **gArgv; + +static const char gToolkitVersion[] = NS_STRINGIFY(GRE_MILESTONE); +static const char gToolkitBuildID[] = NS_STRINGIFY(MOZ_BUILDID); + +static nsIProfileLock* gProfileLock; + +int gRestartArgc; +char **gRestartArgv; + +bool gIsGtest = false; + +nsString gAbsoluteArgv0Path; + +#if defined(MOZ_WIDGET_GTK) +#include <glib.h> +#if defined(DEBUG) || defined(NS_BUILD_REFCNT_LOGGING) +#define CLEANUP_MEMORY 1 +#define PANGO_ENABLE_BACKEND +#include <pango/pangofc-fontmap.h> +#endif +#include <gtk/gtk.h> +#ifdef MOZ_X11 +#include <gdk/gdkx.h> +#endif /* MOZ_X11 */ +#include "nsGTKToolkit.h" +#include <fontconfig/fontconfig.h> +#endif +#include "BinaryPath.h" +#ifndef MOZ_BUILDID +// See comment in Makefile.in why we want to avoid including buildid.h. +// Still include it when MOZ_BUILDID is not set, which can happen with some +// build backends. +#include "buildid.h" +#endif + +#ifdef MOZ_LINKER +extern "C" MFBT_API bool IsSignalHandlingBroken(); +#endif + +#ifdef LIBFUZZER +#include "LibFuzzerRunner.h" + +namespace mozilla { +LibFuzzerRunner* libFuzzerRunner = 0; +} // namespace mozilla + +extern "C" MOZ_EXPORT void XRE_LibFuzzerSetMain(int argc, char** argv, LibFuzzerMain main) { + mozilla::libFuzzerRunner->setParams(argc, argv, main); +} +#endif + +namespace mozilla { +int (*RunGTest)() = 0; +} // namespace mozilla + +using namespace mozilla; +using mozilla::Unused; +using mozilla::scache::StartupCache; +using mozilla::dom::ContentParent; +using mozilla::dom::ContentChild; + +// Save literal putenv string to environment variable. +static void +SaveToEnv(const char *putenv) +{ + char *expr = strdup(putenv); + if (expr) + PR_SetEnv(expr); + // We intentionally leak |expr| here since it is required by PR_SetEnv. + MOZ_LSAN_INTENTIONALLY_LEAK_OBJECT(expr); +} + +// Tests that an environment variable exists and has a value +static bool +EnvHasValue(const char *name) +{ + const char *val = PR_GetEnv(name); + return (val && *val); +} + +// Save the given word to the specified environment variable. +static void +SaveWordToEnv(const char *name, const nsACString & word) +{ + char *expr = PR_smprintf("%s=%s", name, PromiseFlatCString(word).get()); + if (expr) + PR_SetEnv(expr); + // We intentionally leak |expr| here since it is required by PR_SetEnv. +} + +// Save the path of the given file to the specified environment variable. +static void +SaveFileToEnv(const char *name, nsIFile *file) +{ +#ifdef XP_WIN + nsAutoString path; + file->GetPath(path); + SetEnvironmentVariableW(NS_ConvertASCIItoUTF16(name).get(), path.get()); +#else + nsAutoCString path; + file->GetNativePath(path); + SaveWordToEnv(name, path); +#endif +} + +// Load the path of a file saved with SaveFileToEnv +static already_AddRefed<nsIFile> +GetFileFromEnv(const char *name) +{ + nsresult rv; + nsCOMPtr<nsIFile> file; + +#ifdef XP_WIN + WCHAR path[_MAX_PATH]; + if (!GetEnvironmentVariableW(NS_ConvertASCIItoUTF16(name).get(), + path, _MAX_PATH)) + return nullptr; + + rv = NS_NewLocalFile(nsDependentString(path), true, getter_AddRefs(file)); + if (NS_FAILED(rv)) + return nullptr; + + return file.forget(); +#else + const char *arg = PR_GetEnv(name); + if (!arg || !*arg) + return nullptr; + + rv = NS_NewNativeLocalFile(nsDependentCString(arg), true, + getter_AddRefs(file)); + if (NS_FAILED(rv)) + return nullptr; + + return file.forget(); +#endif +} + +// Save the path of the given word to the specified environment variable +// provided the environment variable does not have a value. +static void +SaveWordToEnvIfUnset(const char *name, const nsACString & word) +{ + if (!EnvHasValue(name)) + SaveWordToEnv(name, word); +} + +// Save the path of the given file to the specified environment variable +// provided the environment variable does not have a value. +static void +SaveFileToEnvIfUnset(const char *name, nsIFile *file) +{ + if (!EnvHasValue(name)) + SaveFileToEnv(name, file); +} + +static bool +strimatch(const char* lowerstr, const char* mixedstr) +{ + while(*lowerstr) { + if (!*mixedstr) return false; // mixedstr is shorter + if (tolower(*mixedstr) != *lowerstr) return false; // no match + + ++lowerstr; + ++mixedstr; + } + + if (*mixedstr) return false; // lowerstr is shorter + + return true; +} + +static bool gIsExpectedExit = false; + +void MozExpectedExit() { + gIsExpectedExit = true; +} + +/** + * Runs atexit() to catch unexpected exit from 3rd party libraries like the + * Intel graphics driver calling exit in an error condition. When they + * call exit() to report an error we won't shutdown correctly and wont catch + * the issue with our crash reporter. + */ +static void UnexpectedExit() { + if (!gIsExpectedExit) { + gIsExpectedExit = true; // Don't risk re-entrency issues when crashing. + MOZ_CRASH("Exit called by third party code."); + } +} + +/** + * Output a string to the user. This method is really only meant to be used to + * output last-ditch error messages designed for developers NOT END USERS. + * + * @param isError + * Pass true to indicate severe errors. + * @param fmt + * printf-style format string followed by arguments. + */ +static void Output(bool isError, const char *fmt, ... ) +{ + va_list ap; + va_start(ap, fmt); + +#if defined(XP_WIN) && !MOZ_WINCONSOLE + char *msg = PR_vsmprintf(fmt, ap); + if (msg) + { + UINT flags = MB_OK; + if (isError) + flags |= MB_ICONERROR; + else + flags |= MB_ICONINFORMATION; + + wchar_t wide_msg[1024]; + MultiByteToWideChar(CP_ACP, + 0, + msg, + -1, + wide_msg, + sizeof(wide_msg) / sizeof(wchar_t)); + + MessageBoxW(nullptr, wide_msg, L"XULRunner", flags); + PR_smprintf_free(msg); + } +#else + vfprintf(stderr, fmt, ap); +#endif + + va_end(ap); +} + +enum RemoteResult { + REMOTE_NOT_FOUND = 0, + REMOTE_FOUND = 1, + REMOTE_ARG_BAD = 2 +}; + +enum ArgResult { + ARG_NONE = 0, + ARG_FOUND = 1, + ARG_BAD = 2 // you wanted a param, but there isn't one +}; + +static void RemoveArg(char **argv) +{ + do { + *argv = *(argv + 1); + ++argv; + } while (*argv); + + --gArgc; +} + +/** + * Check for a commandline flag. If the flag takes a parameter, the + * parameter is returned in aParam. Flags may be in the form -arg or + * --arg (or /arg on win32). + * + * @param aArg the parameter to check. Must be lowercase. + * @param aCheckOSInt if true returns ARG_BAD if the osint argument is present + * when aArg is also present. + * @param aParam if non-null, the -arg <data> will be stored in this pointer. + * This is *not* allocated, but rather a pointer to the argv data. + * @param aRemArg if true, the argument is removed from the gArgv array. + */ +static ArgResult +CheckArg(const char* aArg, bool aCheckOSInt = false, const char **aParam = nullptr, bool aRemArg = true) +{ + MOZ_ASSERT(gArgv, "gArgv must be initialized before CheckArg()"); + + char **curarg = gArgv + 1; // skip argv[0] + ArgResult ar = ARG_NONE; + + while (*curarg) { + char *arg = curarg[0]; + + if (arg[0] == '-' +#if defined(XP_WIN) + || *arg == '/' +#endif + ) { + ++arg; + if (*arg == '-') + ++arg; + + if (strimatch(aArg, arg)) { + if (aRemArg) + RemoveArg(curarg); + else + ++curarg; + if (!aParam) { + ar = ARG_FOUND; + break; + } + + if (*curarg) { + if (**curarg == '-' +#if defined(XP_WIN) + || **curarg == '/' +#endif + ) + return ARG_BAD; + + *aParam = *curarg; + if (aRemArg) + RemoveArg(curarg); + ar = ARG_FOUND; + break; + } + return ARG_BAD; + } + } + + ++curarg; + } + + if (aCheckOSInt && ar == ARG_FOUND) { + ArgResult arOSInt = CheckArg("osint"); + if (arOSInt == ARG_FOUND) { + ar = ARG_BAD; + PR_fprintf(PR_STDERR, "Error: argument --osint is invalid\n"); + } + } + + return ar; +} + +#if defined(XP_WIN) +/** + * Check for a commandline flag from the windows shell and remove it from the + * argv used when restarting. Flags MUST be in the form -arg. + * + * @param aArg the parameter to check. Must be lowercase. + */ +static ArgResult +CheckArgShell(const char* aArg) +{ + char **curarg = gRestartArgv + 1; // skip argv[0] + + while (*curarg) { + char *arg = curarg[0]; + + if (arg[0] == '-') { + ++arg; + + if (strimatch(aArg, arg)) { + do { + *curarg = *(curarg + 1); + ++curarg; + } while (*curarg); + + --gRestartArgc; + + return ARG_FOUND; + } + } + + ++curarg; + } + + return ARG_NONE; +} + +/** + * Enabled Native App Support to process DDE messages when the app needs to + * restart and the app has been launched by the Windows shell to open an url. + * When aWait is false this will process the DDE events manually. This prevents + * Windows from displaying an error message due to the DDE message not being + * acknowledged. + */ +static void +ProcessDDE(nsINativeAppSupport* aNative, bool aWait) +{ + // When the app is launched by the windows shell the windows shell + // expects the app to be available for DDE messages and if it isn't + // windows displays an error dialog. To prevent the error the DDE server + // is enabled and pending events are processed when the app needs to + // restart after it was launched by the shell with the requestpending + // argument. The requestpending pending argument is removed to + // differentiate it from being launched when an app restart is not + // required. + ArgResult ar; + ar = CheckArgShell("requestpending"); + if (ar == ARG_FOUND) { + aNative->Enable(); // enable win32 DDE responses + if (aWait) { + nsIThread *thread = NS_GetCurrentThread(); + // This is just a guesstimate based on testing different values. + // If count is 8 or less windows will display an error dialog. + int32_t count = 20; + while(--count >= 0) { + NS_ProcessNextEvent(thread); + PR_Sleep(PR_MillisecondsToInterval(1)); + } + } + } +} +#endif + +/** + * Determines if there is support for showing the profile manager + * + * @return true in all environments +*/ +static bool +CanShowProfileManager() +{ + return true; +} + +bool gSafeMode = false; + +/** + * The nsXULAppInfo object implements nsIFactory so that it can be its own + * singleton. + */ +class nsXULAppInfo : public nsIXULAppInfo, + public nsIObserver, +#ifdef XP_WIN + public nsIWinAppHelper, +#endif +#ifdef MOZ_CRASHREPORTER + public nsICrashReporter, + public nsIFinishDumpingCallback, +#endif + public nsIXULRuntime + +{ +public: + constexpr nsXULAppInfo() {} + NS_DECL_ISUPPORTS_INHERITED + NS_DECL_NSIPLATFORMINFO + NS_DECL_NSIXULAPPINFO + NS_DECL_NSIXULRUNTIME + NS_DECL_NSIOBSERVER +#ifdef MOZ_CRASHREPORTER + NS_DECL_NSICRASHREPORTER + NS_DECL_NSIFINISHDUMPINGCALLBACK +#endif +#ifdef XP_WIN + NS_DECL_NSIWINAPPHELPER +#endif +}; + +NS_INTERFACE_MAP_BEGIN(nsXULAppInfo) + NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIXULRuntime) + NS_INTERFACE_MAP_ENTRY(nsIXULRuntime) + NS_INTERFACE_MAP_ENTRY(nsIObserver) +#ifdef XP_WIN + NS_INTERFACE_MAP_ENTRY(nsIWinAppHelper) +#endif +#ifdef MOZ_CRASHREPORTER + NS_INTERFACE_MAP_ENTRY(nsICrashReporter) + NS_INTERFACE_MAP_ENTRY(nsIFinishDumpingCallback) +#endif + NS_INTERFACE_MAP_ENTRY(nsIPlatformInfo) + NS_INTERFACE_MAP_ENTRY_CONDITIONAL(nsIXULAppInfo, gAppData || + XRE_IsContentProcess()) +NS_INTERFACE_MAP_END + +NS_IMETHODIMP_(MozExternalRefCountType) +nsXULAppInfo::AddRef() +{ + return 1; +} + +NS_IMETHODIMP_(MozExternalRefCountType) +nsXULAppInfo::Release() +{ + return 1; +} + +NS_IMETHODIMP +nsXULAppInfo::GetVendor(nsACString& aResult) +{ + if (XRE_IsContentProcess()) { + ContentChild* cc = ContentChild::GetSingleton(); + aResult = cc->GetAppInfo().vendor; + return NS_OK; + } + aResult.Assign(gAppData->vendor); + + return NS_OK; +} + +NS_IMETHODIMP +nsXULAppInfo::GetName(nsACString& aResult) +{ + if (XRE_IsContentProcess()) { + ContentChild* cc = ContentChild::GetSingleton(); + aResult = cc->GetAppInfo().name; + return NS_OK; + } + aResult.Assign(gAppData->name); + + return NS_OK; +} + +NS_IMETHODIMP +nsXULAppInfo::GetID(nsACString& aResult) +{ + if (XRE_IsContentProcess()) { + ContentChild* cc = ContentChild::GetSingleton(); + aResult = cc->GetAppInfo().ID; + return NS_OK; + } + aResult.Assign(gAppData->ID); + + return NS_OK; +} + +NS_IMETHODIMP +nsXULAppInfo::GetVersion(nsACString& aResult) +{ + if (XRE_IsContentProcess()) { + ContentChild* cc = ContentChild::GetSingleton(); + aResult = cc->GetAppInfo().version; + return NS_OK; + } + aResult.Assign(gAppData->version); + + return NS_OK; +} + +NS_IMETHODIMP +nsXULAppInfo::GetPlatformVersion(nsACString& aResult) +{ + aResult.Assign(gToolkitVersion); + + return NS_OK; +} + +NS_IMETHODIMP +nsXULAppInfo::GetAppBuildID(nsACString& aResult) +{ + if (XRE_IsContentProcess()) { + ContentChild* cc = ContentChild::GetSingleton(); + aResult = cc->GetAppInfo().buildID; + return NS_OK; + } + aResult.Assign(gAppData->buildID); + + return NS_OK; +} + +NS_IMETHODIMP +nsXULAppInfo::GetPlatformBuildID(nsACString& aResult) +{ + aResult.Assign(gToolkitBuildID); + + return NS_OK; +} + +NS_IMETHODIMP +nsXULAppInfo::GetUAName(nsACString& aResult) +{ + if (XRE_IsContentProcess()) { + ContentChild* cc = ContentChild::GetSingleton(); + aResult = cc->GetAppInfo().UAName; + return NS_OK; + } + aResult.Assign(gAppData->UAName); + + return NS_OK; +} + +NS_IMETHODIMP +nsXULAppInfo::GetLogConsoleErrors(bool *aResult) +{ + *aResult = gLogConsoleErrors; + return NS_OK; +} + +NS_IMETHODIMP +nsXULAppInfo::SetLogConsoleErrors(bool aValue) +{ + gLogConsoleErrors = aValue; + return NS_OK; +} + +NS_IMETHODIMP +nsXULAppInfo::GetInSafeMode(bool *aResult) +{ + *aResult = gSafeMode; + return NS_OK; +} + +NS_IMETHODIMP +nsXULAppInfo::GetOS(nsACString& aResult) +{ + aResult.AssignLiteral(OS_TARGET); + return NS_OK; +} + +NS_IMETHODIMP +nsXULAppInfo::GetXPCOMABI(nsACString& aResult) +{ +#ifdef TARGET_XPCOM_ABI + aResult.AssignLiteral(TARGET_XPCOM_ABI); + return NS_OK; +#else + return NS_ERROR_NOT_AVAILABLE; +#endif +} + +NS_IMETHODIMP +nsXULAppInfo::GetWidgetToolkit(nsACString& aResult) +{ + aResult.AssignLiteral(MOZ_WIDGET_TOOLKIT); + return NS_OK; +} + +// Ensure that the GeckoProcessType enum, defined in xpcom/build/nsXULAppAPI.h, +// is synchronized with the const unsigned longs defined in +// xpcom/system/nsIXULRuntime.idl. +#define SYNC_ENUMS(a,b) \ + static_assert(nsIXULRuntime::PROCESS_TYPE_ ## a == \ + static_cast<int>(GeckoProcessType_ ## b), \ + "GeckoProcessType in nsXULAppAPI.h not synchronized with nsIXULRuntime.idl"); + +SYNC_ENUMS(DEFAULT, Default) +SYNC_ENUMS(PLUGIN, Plugin) +SYNC_ENUMS(CONTENT, Content) +SYNC_ENUMS(IPDLUNITTEST, IPDLUnitTest) +SYNC_ENUMS(GMPLUGIN, GMPlugin) +SYNC_ENUMS(GPU, GPU) + +// .. and ensure that that is all of them: +static_assert(GeckoProcessType_GPU + 1 == GeckoProcessType_End, + "Did not find the final GeckoProcessType"); + +NS_IMETHODIMP +nsXULAppInfo::GetProcessType(uint32_t* aResult) +{ + NS_ENSURE_ARG_POINTER(aResult); + *aResult = XRE_GetProcessType(); + return NS_OK; +} + +NS_IMETHODIMP +nsXULAppInfo::GetProcessID(uint32_t* aResult) +{ +#ifdef XP_WIN + *aResult = GetCurrentProcessId(); +#else + *aResult = getpid(); +#endif + return NS_OK; +} + +NS_IMETHODIMP +nsXULAppInfo::GetUniqueProcessID(uint64_t* aResult) +{ + if (XRE_IsContentProcess()) { + ContentChild* cc = ContentChild::GetSingleton(); + *aResult = cc->GetID(); + } else { + *aResult = 0; + } + return NS_OK; +} + +static bool gBrowserTabsRemoteAutostart = false; +static uint64_t gBrowserTabsRemoteStatus = 0; +static bool gBrowserTabsRemoteAutostartInitialized = false; + +static bool gMultiprocessBlockPolicyInitialized = false; +static uint32_t gMultiprocessBlockPolicy = 0; + +NS_IMETHODIMP +nsXULAppInfo::Observe(nsISupports *aSubject, const char *aTopic, const char16_t *aData) { + if (!nsCRT::strcmp(aTopic, "getE10SBlocked")) { + nsCOMPtr<nsISupportsPRUint64> ret = do_QueryInterface(aSubject); + if (!ret) + return NS_ERROR_FAILURE; + + ret->SetData(gBrowserTabsRemoteStatus); + + return NS_OK; + } + return NS_ERROR_FAILURE; +} + +NS_IMETHODIMP +nsXULAppInfo::GetBrowserTabsRemoteAutostart(bool* aResult) +{ + *aResult = BrowserTabsRemoteAutostart(); + return NS_OK; +} + +NS_IMETHODIMP +nsXULAppInfo::GetMultiprocessBlockPolicy(uint32_t* aResult) +{ + *aResult = MultiprocessBlockPolicy(); + return NS_OK; +} + +NS_IMETHODIMP +nsXULAppInfo::GetAccessibilityEnabled(bool* aResult) +{ +#ifdef ACCESSIBILITY + *aResult = GetAccService() != nullptr; +#else + *aResult = false; +#endif + return NS_OK; +} + +NS_IMETHODIMP +nsXULAppInfo::GetIs64Bit(bool* aResult) +{ +#ifdef HAVE_64BIT_BUILD + *aResult = true; +#else + *aResult = false; +#endif + return NS_OK; +} + +NS_IMETHODIMP +nsXULAppInfo::EnsureContentProcess() +{ + if (!XRE_IsParentProcess()) + return NS_ERROR_NOT_AVAILABLE; + + RefPtr<ContentParent> unused = ContentParent::GetNewOrUsedBrowserProcess(); + return NS_OK; +} + +NS_IMETHODIMP +nsXULAppInfo::InvalidateCachesOnRestart() +{ + nsCOMPtr<nsIFile> file; + nsresult rv = NS_GetSpecialDirectory(NS_APP_PROFILE_DIR_STARTUP, + getter_AddRefs(file)); + if (NS_FAILED(rv)) + return rv; + if (!file) + return NS_ERROR_NOT_AVAILABLE; + + file->AppendNative(FILE_COMPATIBILITY_INFO); + + nsINIParser parser; + rv = parser.Init(file); + if (NS_FAILED(rv)) { + // This fails if compatibility.ini is not there, so we'll + // flush the caches on the next restart anyways. + return NS_OK; + } + + nsAutoCString buf; + rv = parser.GetString("Compatibility", "InvalidateCaches", buf); + + if (NS_FAILED(rv)) { + PRFileDesc *fd; + rv = file->OpenNSPRFileDesc(PR_RDWR | PR_APPEND, 0600, &fd); + if (NS_FAILED(rv)) { + NS_ERROR("could not create output stream"); + return NS_ERROR_NOT_AVAILABLE; + } + static const char kInvalidationHeader[] = NS_LINEBREAK "InvalidateCaches=1" NS_LINEBREAK; + PR_Write(fd, kInvalidationHeader, sizeof(kInvalidationHeader) - 1); + PR_Close(fd); + } + return NS_OK; +} + +NS_IMETHODIMP +nsXULAppInfo::GetReplacedLockTime(PRTime *aReplacedLockTime) +{ + if (!gProfileLock) + return NS_ERROR_NOT_AVAILABLE; + gProfileLock->GetReplacedLockTime(aReplacedLockTime); + return NS_OK; +} + +NS_IMETHODIMP +nsXULAppInfo::GetLastRunCrashID(nsAString &aLastRunCrashID) +{ +#ifdef MOZ_CRASHREPORTER + CrashReporter::GetLastRunCrashID(aLastRunCrashID); + return NS_OK; +#else + return NS_ERROR_NOT_IMPLEMENTED; +#endif +} + +NS_IMETHODIMP +nsXULAppInfo::GetIsReleaseOrBeta(bool* aResult) +{ +#ifdef RELEASE_OR_BETA + *aResult = true; +#else + *aResult = false; +#endif + return NS_OK; +} + +NS_IMETHODIMP +nsXULAppInfo::GetIsOfficialBranding(bool* aResult) +{ +#ifdef MOZ_OFFICIAL_BRANDING + *aResult = true; +#else + *aResult = false; +#endif + return NS_OK; +} + +NS_IMETHODIMP +nsXULAppInfo::GetDefaultUpdateChannel(nsACString& aResult) +{ + aResult.AssignLiteral(NS_STRINGIFY(MOZ_UPDATE_CHANNEL)); + return NS_OK; +} + +NS_IMETHODIMP +nsXULAppInfo::GetDistributionID(nsACString& aResult) +{ + aResult.AssignLiteral(MOZ_DISTRIBUTION_ID); + return NS_OK; +} + +NS_IMETHODIMP +nsXULAppInfo::GetIsOfficial(bool* aResult) +{ +#ifdef MOZILLA_OFFICIAL + *aResult = true; +#else + *aResult = false; +#endif + return NS_OK; +} + +NS_IMETHODIMP +nsXULAppInfo::GetWindowsDLLBlocklistStatus(bool* aResult) +{ +#if defined(XP_WIN) + *aResult = gAppData->flags & NS_XRE_DLL_BLOCKLIST_ENABLED; +#else + *aResult = false; +#endif + return NS_OK; +} + +#ifdef XP_WIN +// Matches the enum in WinNT.h for the Vista SDK but renamed so that we can +// safely build with the Vista SDK and without it. +typedef enum +{ + VistaTokenElevationTypeDefault = 1, + VistaTokenElevationTypeFull, + VistaTokenElevationTypeLimited +} VISTA_TOKEN_ELEVATION_TYPE; + +// avoid collision with TokeElevationType enum in WinNT.h +// of the Vista SDK +#define VistaTokenElevationType static_cast< TOKEN_INFORMATION_CLASS >( 18 ) + +NS_IMETHODIMP +nsXULAppInfo::GetUserCanElevate(bool *aUserCanElevate) +{ + HANDLE hToken; + + VISTA_TOKEN_ELEVATION_TYPE elevationType; + DWORD dwSize; + + if (!OpenProcessToken(GetCurrentProcess(), TOKEN_QUERY, &hToken) || + !GetTokenInformation(hToken, VistaTokenElevationType, &elevationType, + sizeof(elevationType), &dwSize)) { + *aUserCanElevate = false; + } + else { + // The possible values returned for elevationType and their meanings are: + // TokenElevationTypeDefault: The token does not have a linked token + // (e.g. UAC disabled or a standard user, so they can't be elevated) + // TokenElevationTypeFull: The token is linked to an elevated token + // (e.g. UAC is enabled and the user is already elevated so they can't + // be elevated again) + // TokenElevationTypeLimited: The token is linked to a limited token + // (e.g. UAC is enabled and the user is not elevated, so they can be + // elevated) + *aUserCanElevate = (elevationType == VistaTokenElevationTypeLimited); + } + + if (hToken) + CloseHandle(hToken); + + return NS_OK; +} +#endif + +#ifdef MOZ_CRASHREPORTER +NS_IMETHODIMP +nsXULAppInfo::GetEnabled(bool *aEnabled) +{ + *aEnabled = CrashReporter::GetEnabled(); + return NS_OK; +} + +NS_IMETHODIMP +nsXULAppInfo::SetEnabled(bool aEnabled) +{ + if (aEnabled) { + if (CrashReporter::GetEnabled()) { + // no point in erroring for double-enabling + return NS_OK; + } + + nsCOMPtr<nsIFile> greBinDir; + NS_GetSpecialDirectory(NS_GRE_BIN_DIR, getter_AddRefs(greBinDir)); + if (!greBinDir) { + return NS_ERROR_FAILURE; + } + + nsCOMPtr<nsIFile> xreBinDirectory = do_QueryInterface(greBinDir); + if (!xreBinDirectory) { + return NS_ERROR_FAILURE; + } + + return CrashReporter::SetExceptionHandler(xreBinDirectory, true); + } + else { + if (!CrashReporter::GetEnabled()) { + // no point in erroring for double-disabling + return NS_OK; + } + + return CrashReporter::UnsetExceptionHandler(); + } +} + +NS_IMETHODIMP +nsXULAppInfo::GetServerURL(nsIURL** aServerURL) +{ + if (!CrashReporter::GetEnabled()) + return NS_ERROR_NOT_INITIALIZED; + + nsAutoCString data; + if (!CrashReporter::GetServerURL(data)) { + return NS_ERROR_FAILURE; + } + nsCOMPtr<nsIURI> uri; + NS_NewURI(getter_AddRefs(uri), data); + if (!uri) + return NS_ERROR_FAILURE; + + nsCOMPtr<nsIURL> url; + url = do_QueryInterface(uri); + NS_ADDREF(*aServerURL = url); + + return NS_OK; +} + +NS_IMETHODIMP +nsXULAppInfo::SetServerURL(nsIURL* aServerURL) +{ + bool schemeOk; + // only allow https or http URLs + nsresult rv = aServerURL->SchemeIs("https", &schemeOk); + NS_ENSURE_SUCCESS(rv, rv); + if (!schemeOk) { + rv = aServerURL->SchemeIs("http", &schemeOk); + NS_ENSURE_SUCCESS(rv, rv); + + if (!schemeOk) + return NS_ERROR_INVALID_ARG; + } + nsAutoCString spec; + rv = aServerURL->GetSpec(spec); + NS_ENSURE_SUCCESS(rv, rv); + + return CrashReporter::SetServerURL(spec); +} + +NS_IMETHODIMP +nsXULAppInfo::GetMinidumpPath(nsIFile** aMinidumpPath) +{ + if (!CrashReporter::GetEnabled()) + return NS_ERROR_NOT_INITIALIZED; + + nsAutoString path; + if (!CrashReporter::GetMinidumpPath(path)) + return NS_ERROR_FAILURE; + + nsresult rv = NS_NewLocalFile(path, false, aMinidumpPath); + NS_ENSURE_SUCCESS(rv, rv); + return NS_OK; +} + +NS_IMETHODIMP +nsXULAppInfo::SetMinidumpPath(nsIFile* aMinidumpPath) +{ + nsAutoString path; + nsresult rv = aMinidumpPath->GetPath(path); + NS_ENSURE_SUCCESS(rv, rv); + return CrashReporter::SetMinidumpPath(path); +} + +NS_IMETHODIMP +nsXULAppInfo::AnnotateCrashReport(const nsACString& key, + const nsACString& data) +{ + return CrashReporter::AnnotateCrashReport(key, data); +} + +NS_IMETHODIMP +nsXULAppInfo::AppendAppNotesToCrashReport(const nsACString& data) +{ + return CrashReporter::AppendAppNotesToCrashReport(data); +} + +NS_IMETHODIMP +nsXULAppInfo::RegisterAppMemory(uint64_t pointer, + uint64_t len) +{ + return CrashReporter::RegisterAppMemory((void *)pointer, len); +} + +NS_IMETHODIMP +nsXULAppInfo::WriteMinidumpForException(void* aExceptionInfo) +{ +#ifdef XP_WIN32 + return CrashReporter::WriteMinidumpForException(static_cast<EXCEPTION_POINTERS*>(aExceptionInfo)); +#else + return NS_ERROR_NOT_IMPLEMENTED; +#endif +} + +NS_IMETHODIMP +nsXULAppInfo::AppendObjCExceptionInfoToAppNotes(void* aException) +{ +#ifdef XP_MACOSX + return CrashReporter::AppendObjCExceptionInfoToAppNotes(aException); +#else + return NS_ERROR_NOT_IMPLEMENTED; +#endif +} + +NS_IMETHODIMP +nsXULAppInfo::GetSubmitReports(bool* aEnabled) +{ + return CrashReporter::GetSubmitReports(aEnabled); +} + +NS_IMETHODIMP +nsXULAppInfo::SetSubmitReports(bool aEnabled) +{ + return CrashReporter::SetSubmitReports(aEnabled); +} + +NS_IMETHODIMP +nsXULAppInfo::UpdateCrashEventsDir() +{ + CrashReporter::UpdateCrashEventsDir(); + return NS_OK; +} + +NS_IMETHODIMP +nsXULAppInfo::SaveMemoryReport() +{ + if (!CrashReporter::GetEnabled()) { + return NS_ERROR_NOT_INITIALIZED; + } + nsCOMPtr<nsIFile> file; + nsresult rv = CrashReporter::GetDefaultMemoryReportFile(getter_AddRefs(file)); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + nsString path; + file->GetPath(path); + + nsCOMPtr<nsIMemoryInfoDumper> dumper = + do_GetService("@mozilla.org/memory-info-dumper;1"); + if (NS_WARN_IF(!dumper)) { + return NS_ERROR_UNEXPECTED; + } + + rv = dumper->DumpMemoryReportsToNamedFile(path, this, file, true /* anonymize */); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + return NS_OK; +} + +NS_IMETHODIMP +nsXULAppInfo::SetTelemetrySessionId(const nsACString& id) +{ + CrashReporter::SetTelemetrySessionId(id); + return NS_OK; +} + +// This method is from nsIFInishDumpingCallback. +NS_IMETHODIMP +nsXULAppInfo::Callback(nsISupports* aData) +{ + nsCOMPtr<nsIFile> file = do_QueryInterface(aData); + MOZ_ASSERT(file); + + CrashReporter::SetMemoryReportFile(file); + return NS_OK; +} +#endif + +static const nsXULAppInfo kAppInfo; +static nsresult AppInfoConstructor(nsISupports* aOuter, + REFNSIID aIID, void **aResult) +{ + NS_ENSURE_NO_AGGREGATION(aOuter); + + return const_cast<nsXULAppInfo*>(&kAppInfo)-> + QueryInterface(aIID, aResult); +} + +bool gLogConsoleErrors = false; + +#define NS_ENSURE_TRUE_LOG(x, ret) \ + PR_BEGIN_MACRO \ + if (MOZ_UNLIKELY(!(x))) { \ + NS_WARNING("NS_ENSURE_TRUE(" #x ") failed"); \ + gLogConsoleErrors = true; \ + return ret; \ + } \ + PR_END_MACRO + +#define NS_ENSURE_SUCCESS_LOG(res, ret) \ + NS_ENSURE_TRUE_LOG(NS_SUCCEEDED(res), ret) + +/** + * Because we're starting/stopping XPCOM several times in different scenarios, + * this class is a stack-based critter that makes sure that XPCOM is shut down + * during early returns. + */ + +class ScopedXPCOMStartup +{ +public: + ScopedXPCOMStartup() : + mServiceManager(nullptr) { } + ~ScopedXPCOMStartup(); + + nsresult Initialize(); + nsresult SetWindowCreator(nsINativeAppSupport* native); + + static nsresult CreateAppSupport(nsISupports* aOuter, REFNSIID aIID, void** aResult); + +private: + nsIServiceManager* mServiceManager; + static nsINativeAppSupport* gNativeAppSupport; +}; + +ScopedXPCOMStartup::~ScopedXPCOMStartup() +{ + NS_IF_RELEASE(gNativeAppSupport); + + if (mServiceManager) { +#ifdef XP_MACOSX + // On OS X, we need a pool to catch cocoa objects that are autoreleased + // during teardown. + mozilla::MacAutoreleasePool pool; +#endif + + nsCOMPtr<nsIAppStartup> appStartup (do_GetService(NS_APPSTARTUP_CONTRACTID)); + if (appStartup) + appStartup->DestroyHiddenWindow(); + + gDirServiceProvider->DoShutdown(); + PROFILER_MARKER("Shutdown early"); + + WriteConsoleLog(); + + NS_ShutdownXPCOM(mServiceManager); + mServiceManager = nullptr; + } +} + +// {95d89e3e-a169-41a3-8e56-719978e15b12} +#define APPINFO_CID \ + { 0x95d89e3e, 0xa169, 0x41a3, { 0x8e, 0x56, 0x71, 0x99, 0x78, 0xe1, 0x5b, 0x12 } } + +// {0C4A446C-EE82-41f2-8D04-D366D2C7A7D4} +static const nsCID kNativeAppSupportCID = + { 0xc4a446c, 0xee82, 0x41f2, { 0x8d, 0x4, 0xd3, 0x66, 0xd2, 0xc7, 0xa7, 0xd4 } }; + +// {5F5E59CE-27BC-47eb-9D1F-B09CA9049836} +static const nsCID kProfileServiceCID = + { 0x5f5e59ce, 0x27bc, 0x47eb, { 0x9d, 0x1f, 0xb0, 0x9c, 0xa9, 0x4, 0x98, 0x36 } }; + +static already_AddRefed<nsIFactory> +ProfileServiceFactoryConstructor(const mozilla::Module& module, const mozilla::Module::CIDEntry& entry) +{ + nsCOMPtr<nsIFactory> factory; + NS_NewToolkitProfileFactory(getter_AddRefs(factory)); + return factory.forget(); +} + +NS_DEFINE_NAMED_CID(APPINFO_CID); + +static const mozilla::Module::CIDEntry kXRECIDs[] = { + { &kAPPINFO_CID, false, nullptr, AppInfoConstructor }, + { &kProfileServiceCID, false, ProfileServiceFactoryConstructor, nullptr }, + { &kNativeAppSupportCID, false, nullptr, ScopedXPCOMStartup::CreateAppSupport }, + { nullptr } +}; + +static const mozilla::Module::ContractIDEntry kXREContracts[] = { + { XULAPPINFO_SERVICE_CONTRACTID, &kAPPINFO_CID }, + { XULRUNTIME_SERVICE_CONTRACTID, &kAPPINFO_CID }, +#ifdef MOZ_CRASHREPORTER + { NS_CRASHREPORTER_CONTRACTID, &kAPPINFO_CID }, +#endif + { NS_PROFILESERVICE_CONTRACTID, &kProfileServiceCID }, + { NS_NATIVEAPPSUPPORT_CONTRACTID, &kNativeAppSupportCID }, + { nullptr } +}; + +static const mozilla::Module kXREModule = { + mozilla::Module::kVersion, + kXRECIDs, + kXREContracts +}; + +NSMODULE_DEFN(Apprunner) = &kXREModule; + +nsresult +ScopedXPCOMStartup::Initialize() +{ + NS_ASSERTION(gDirServiceProvider, "Should not get here!"); + + nsresult rv; + + rv = NS_InitXPCOM2(&mServiceManager, gDirServiceProvider->GetAppDir(), + gDirServiceProvider); + if (NS_FAILED(rv)) { + NS_ERROR("Couldn't start xpcom!"); + mServiceManager = nullptr; + } + else { +#ifdef DEBUG + nsCOMPtr<nsIComponentRegistrar> reg = + do_QueryInterface(mServiceManager); + NS_ASSERTION(reg, "Service Manager doesn't QI to Registrar."); +#endif + } + + return rv; +} + +/** + * This is a little factory class that serves as a singleton-service-factory + * for the nativeappsupport object. + */ +class nsSingletonFactory final : public nsIFactory +{ +public: + NS_DECL_ISUPPORTS + NS_DECL_NSIFACTORY + + explicit nsSingletonFactory(nsISupports* aSingleton); + +private: + ~nsSingletonFactory() { } + nsCOMPtr<nsISupports> mSingleton; +}; + +nsSingletonFactory::nsSingletonFactory(nsISupports* aSingleton) + : mSingleton(aSingleton) +{ + NS_ASSERTION(mSingleton, "Singleton was null!"); +} + +NS_IMPL_ISUPPORTS(nsSingletonFactory, nsIFactory) + +NS_IMETHODIMP +nsSingletonFactory::CreateInstance(nsISupports* aOuter, + const nsIID& aIID, + void* *aResult) +{ + NS_ENSURE_NO_AGGREGATION(aOuter); + + return mSingleton->QueryInterface(aIID, aResult); +} + +NS_IMETHODIMP +nsSingletonFactory::LockFactory(bool) +{ + return NS_OK; +} + +/** + * Set our windowcreator on the WindowWatcher service. + */ +nsresult +ScopedXPCOMStartup::SetWindowCreator(nsINativeAppSupport* native) +{ + nsresult rv; + + NS_IF_ADDREF(gNativeAppSupport = native); + + // Inform the chrome registry about OS accessibility + nsCOMPtr<nsIToolkitChromeRegistry> cr = + mozilla::services::GetToolkitChromeRegistryService(); + + if (cr) + cr->CheckForOSAccessibility(); + + nsCOMPtr<nsIWindowCreator> creator (do_GetService(NS_APPSTARTUP_CONTRACTID)); + if (!creator) return NS_ERROR_UNEXPECTED; + + nsCOMPtr<nsIWindowWatcher> wwatch + (do_GetService(NS_WINDOWWATCHER_CONTRACTID, &rv)); + NS_ENSURE_SUCCESS(rv, rv); + + return wwatch->SetWindowCreator(creator); +} + +/* static */ nsresult +ScopedXPCOMStartup::CreateAppSupport(nsISupports* aOuter, REFNSIID aIID, void** aResult) +{ + if (aOuter) + return NS_ERROR_NO_AGGREGATION; + + if (!gNativeAppSupport) + return NS_ERROR_NOT_INITIALIZED; + + return gNativeAppSupport->QueryInterface(aIID, aResult); +} + +nsINativeAppSupport* ScopedXPCOMStartup::gNativeAppSupport; + +static void DumpArbitraryHelp() +{ + nsresult rv; + + ScopedLogging log; + + { + ScopedXPCOMStartup xpcom; + xpcom.Initialize(); + + nsCOMPtr<nsICommandLineRunner> cmdline + (do_CreateInstance("@mozilla.org/toolkit/command-line;1")); + if (!cmdline) + return; + + nsCString text; + rv = cmdline->GetHelpText(text); + if (NS_SUCCEEDED(rv)) + printf("%s", text.get()); + } +} + +// English text needs to go into a dtd file. +// But when this is called we have no components etc. These strings must either be +// here, or in a native resource file. +static void +DumpHelp() +{ + printf("Usage: %s [ options ... ] [URL]\n" + " where options include:\n\n", gArgv[0]); + +#ifdef MOZ_X11 + printf("X11 options\n" + " --display=DISPLAY X display to use\n" + " --sync Make X calls synchronous\n"); +#endif +#ifdef XP_UNIX + printf(" --g-fatal-warnings Make all warnings fatal\n" + "\n%s options\n", gAppData->name); +#endif + + printf(" -h or --help Print this message.\n" + " -v or --version Print %s version.\n" + " -P <profile> Start with <profile>.\n" + " --profile <path> Start with profile at <path>.\n" + " --migration Start with migration wizard.\n" + " --ProfileManager Start with ProfileManager.\n" + " --no-remote Do not accept or send remote commands; implies\n" + " --new-instance.\n" + " --new-instance Open new instance, not a new window in running instance.\n" + " --UILocale <locale> Start with <locale> resources as UI Locale.\n" + " --safe-mode Disables extensions and themes for this session.\n", gAppData->name); + +#if defined(XP_WIN) + printf(" --console Start %s with a debugging console.\n", gAppData->name); +#endif + + // this works, but only after the components have registered. so if you drop in a new command line handler, --help + // won't not until the second run. + // out of the bug, because we ship a component.reg file, it works correctly. + DumpArbitraryHelp(); +} + +#if defined(DEBUG) && defined(XP_WIN) +#ifdef DEBUG_warren +#define _CRTDBG_MAP_ALLOC +#endif +// Set a CRT ReportHook function to capture and format MSCRT +// warnings, errors and assertions. +// See http://msdn.microsoft.com/en-US/library/74kabxyx(v=VS.80).aspx +#include <stdio.h> +#include <crtdbg.h> +#include "mozilla/mozalloc_abort.h" +static int MSCRTReportHook( int aReportType, char *aMessage, int *oReturnValue) +{ + *oReturnValue = 0; // continue execution + + // Do not use fprintf or other functions which may allocate + // memory from the heap which may be corrupted. Instead, + // use fputs to output the leading portion of the message + // and use mozalloc_abort to emit the remainder of the + // message. + + switch(aReportType) { + case 0: + fputs("\nWARNING: CRT WARNING", stderr); + fputs(aMessage, stderr); + fputs("\n", stderr); + break; + case 1: + fputs("\n###!!! ABORT: CRT ERROR ", stderr); + mozalloc_abort(aMessage); + break; + case 2: + fputs("\n###!!! ABORT: CRT ASSERT ", stderr); + mozalloc_abort(aMessage); + break; + } + + // do not invoke the debugger + return 1; +} + +#endif + +static inline void +DumpVersion() +{ + if (gAppData->vendor) + printf("%s ", gAppData->vendor); + printf("%s %s", gAppData->name, gAppData->version); + if (gAppData->copyright) + printf(", %s", gAppData->copyright); + printf("\n"); +} + +#ifdef MOZ_ENABLE_XREMOTE +static RemoteResult +ParseRemoteCommandLine(nsCString& program, + const char** profile, + const char** username) +{ + ArgResult ar; + + ar = CheckArg("p", false, profile, false); + if (ar == ARG_BAD) { + // Leave it to the normal command line handling to handle this situation. + return REMOTE_NOT_FOUND; + } + + const char *temp = nullptr; + ar = CheckArg("a", true, &temp); + if (ar == ARG_BAD) { + PR_fprintf(PR_STDERR, "Error: argument -a requires an application name\n"); + return REMOTE_ARG_BAD; + } else if (ar == ARG_FOUND) { + program.Assign(temp); + } + + ar = CheckArg("u", true, username); + if (ar == ARG_BAD) { + PR_fprintf(PR_STDERR, "Error: argument -u requires a username\n"); + return REMOTE_ARG_BAD; + } + + return REMOTE_FOUND; +} + +static RemoteResult +StartRemoteClient(const char* aDesktopStartupID, + nsCString& program, + const char* profile, + const char* username) +{ + XRemoteClient client; + nsresult rv = client.Init(); + if (NS_FAILED(rv)) + return REMOTE_NOT_FOUND; + + nsXPIDLCString response; + bool success = false; + rv = client.SendCommandLine(program.get(), username, profile, + gArgc, gArgv, aDesktopStartupID, + getter_Copies(response), &success); + // did the command fail? + if (!success) + return REMOTE_NOT_FOUND; + + // The "command not parseable" error is returned when the + // nsICommandLineHandler throws a NS_ERROR_ABORT. + if (response.EqualsLiteral("500 command not parseable")) + return REMOTE_ARG_BAD; + + if (NS_FAILED(rv)) + return REMOTE_NOT_FOUND; + + return REMOTE_FOUND; +} +#endif // MOZ_ENABLE_XREMOTE + +void +XRE_InitOmnijar(nsIFile* greOmni, nsIFile* appOmni) +{ + mozilla::Omnijar::Init(greOmni, appOmni); +} + +nsresult +XRE_GetBinaryPath(const char* argv0, nsIFile* *aResult) +{ + return mozilla::BinaryPath::GetFile(argv0, aResult); +} + +#ifdef XP_WIN +#include "nsWindowsRestart.cpp" +#include <shellapi.h> + +typedef BOOL (WINAPI* SetProcessDEPPolicyFunc)(DWORD dwFlags); +#endif + +// If aBlankCommandLine is true, then the application will be launched with a +// blank command line instead of being launched with the same command line that +// it was initially started with. +static nsresult LaunchChild(nsINativeAppSupport* aNative, + bool aBlankCommandLine = false) +{ + aNative->Quit(); // release DDE mutex, if we're holding it + + // Restart this process by exec'ing it into the current process + // if supported by the platform. Otherwise, use NSPR. + +#ifdef MOZ_JPROF + // make sure JPROF doesn't think we're E10s + unsetenv("JPROF_SLAVE"); +#endif + + if (aBlankCommandLine) { + gRestartArgc = 1; + gRestartArgv[gRestartArgc] = nullptr; + } + + SaveToEnv("MOZ_LAUNCHED_CHILD=1"); + +#if defined(MOZ_WIDGET_ANDROID) + java::GeckoAppShell::ScheduleRestart(); +#else +#if defined(XP_MACOSX) + CommandLineServiceMac::SetupMacCommandLine(gRestartArgc, gRestartArgv, true); + LaunchChildMac(gRestartArgc, gRestartArgv); +#else + nsCOMPtr<nsIFile> lf; + nsresult rv = XRE_GetBinaryPath(gArgv[0], getter_AddRefs(lf)); + if (NS_FAILED(rv)) + return rv; + +#if defined(XP_WIN) + nsAutoString exePath; + rv = lf->GetPath(exePath); + if (NS_FAILED(rv)) + return rv; + + if (!WinLaunchChild(exePath.get(), gRestartArgc, gRestartArgv)) + return NS_ERROR_FAILURE; + +#else + nsAutoCString exePath; + rv = lf->GetNativePath(exePath); + if (NS_FAILED(rv)) + return rv; + +#if defined(XP_UNIX) + if (execv(exePath.get(), gRestartArgv) == -1) + return NS_ERROR_FAILURE; +#else + PRProcess* process = PR_CreateProcess(exePath.get(), gRestartArgv, + nullptr, nullptr); + if (!process) return NS_ERROR_FAILURE; + + int32_t exitCode; + 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 + + return NS_ERROR_LAUNCHED_CHILD_PROCESS; +} + +static const char kProfileProperties[] = + "chrome://mozapps/locale/profile/profileSelection.properties"; + +namespace { + +/** + * This class, instead of a raw nsresult, should be the return type of any + * function called by SelectProfile that initializes XPCOM. + */ +class ReturnAbortOnError +{ +public: + MOZ_IMPLICIT ReturnAbortOnError(nsresult aRv) + { + mRv = ConvertRv(aRv); + } + + operator nsresult() + { + return mRv; + } + +private: + inline nsresult + ConvertRv(nsresult aRv) + { + if (NS_SUCCEEDED(aRv) || aRv == NS_ERROR_LAUNCHED_CHILD_PROCESS) { + return aRv; + } + return NS_ERROR_ABORT; + } + + nsresult mRv; +}; + +} // namespace + +static ReturnAbortOnError +ProfileLockedDialog(nsIFile* aProfileDir, nsIFile* aProfileLocalDir, + nsIProfileUnlocker* aUnlocker, + nsINativeAppSupport* aNative, nsIProfileLock* *aResult) +{ + nsresult rv; + + ScopedXPCOMStartup xpcom; + rv = xpcom.Initialize(); + NS_ENSURE_SUCCESS(rv, rv); + + mozilla::Telemetry::WriteFailedProfileLock(aProfileDir); + + rv = xpcom.SetWindowCreator(aNative); + NS_ENSURE_SUCCESS(rv, NS_ERROR_FAILURE); + + { //extra scoping is needed so we release these components before xpcom shutdown + nsCOMPtr<nsIStringBundleService> sbs = + mozilla::services::GetStringBundleService(); + NS_ENSURE_TRUE(sbs, NS_ERROR_FAILURE); + + nsCOMPtr<nsIStringBundle> sb; + sbs->CreateBundle(kProfileProperties, getter_AddRefs(sb)); + NS_ENSURE_TRUE_LOG(sbs, NS_ERROR_FAILURE); + + NS_ConvertUTF8toUTF16 appName(gAppData->name); + const char16_t* params[] = {appName.get(), appName.get()}; + + nsXPIDLString killMessage; +#ifndef XP_MACOSX + sb->FormatStringFromName(aUnlocker ? u"restartMessageUnlocker" + : u"restartMessageNoUnlocker", + params, 2, getter_Copies(killMessage)); +#else + sb->FormatStringFromName(aUnlocker ? u"restartMessageUnlockerMac" + : u"restartMessageNoUnlockerMac", + params, 2, getter_Copies(killMessage)); +#endif + + nsXPIDLString killTitle; + sb->FormatStringFromName(u"restartTitle", + params, 1, getter_Copies(killTitle)); + + if (!killMessage || !killTitle) + return NS_ERROR_FAILURE; + + nsCOMPtr<nsIPromptService> ps + (do_GetService(NS_PROMPTSERVICE_CONTRACTID)); + NS_ENSURE_TRUE(ps, NS_ERROR_FAILURE); + + if (aUnlocker) { + int32_t button; +#ifdef MOZ_WIDGET_ANDROID + java::GeckoAppShell::KillAnyZombies(); + button = 0; +#else + const uint32_t flags = + (nsIPromptService::BUTTON_TITLE_IS_STRING * + nsIPromptService::BUTTON_POS_0) + + (nsIPromptService::BUTTON_TITLE_CANCEL * + nsIPromptService::BUTTON_POS_1); + + bool checkState = false; + rv = ps->ConfirmEx(nullptr, killTitle, killMessage, flags, + killTitle, nullptr, nullptr, nullptr, + &checkState, &button); + NS_ENSURE_SUCCESS_LOG(rv, rv); +#endif + + if (button == 0) { + rv = aUnlocker->Unlock(nsIProfileUnlocker::FORCE_QUIT); + if (NS_FAILED(rv)) { + return rv; + } + + SaveFileToEnv("XRE_PROFILE_PATH", aProfileDir); + SaveFileToEnv("XRE_PROFILE_LOCAL_PATH", aProfileLocalDir); + + return LaunchChild(aNative); + } + } else { +#ifdef MOZ_WIDGET_ANDROID + if (java::GeckoAppShell::UnlockProfile()) { + return NS_LockProfilePath(aProfileDir, aProfileLocalDir, + nullptr, aResult); + } +#else + rv = ps->Alert(nullptr, killTitle, killMessage); + NS_ENSURE_SUCCESS_LOG(rv, rv); +#endif + } + + return NS_ERROR_ABORT; + } +} + +static nsresult +ProfileMissingDialog(nsINativeAppSupport* aNative) +{ + nsresult rv; + + ScopedXPCOMStartup xpcom; + rv = xpcom.Initialize(); + NS_ENSURE_SUCCESS(rv, rv); + + rv = xpcom.SetWindowCreator(aNative); + NS_ENSURE_SUCCESS(rv, NS_ERROR_FAILURE); + + { //extra scoping is needed so we release these components before xpcom shutdown + nsCOMPtr<nsIStringBundleService> sbs = + mozilla::services::GetStringBundleService(); + NS_ENSURE_TRUE(sbs, NS_ERROR_FAILURE); + + nsCOMPtr<nsIStringBundle> sb; + sbs->CreateBundle(kProfileProperties, getter_AddRefs(sb)); + NS_ENSURE_TRUE_LOG(sbs, NS_ERROR_FAILURE); + + NS_ConvertUTF8toUTF16 appName(gAppData->name); + const char16_t* params[] = {appName.get(), appName.get()}; + + nsXPIDLString missingMessage; + + // profileMissing + sb->FormatStringFromName(u"profileMissing", params, 2, getter_Copies(missingMessage)); + + nsXPIDLString missingTitle; + sb->FormatStringFromName(u"profileMissingTitle", + params, 1, getter_Copies(missingTitle)); + + if (missingMessage && missingTitle) { + nsCOMPtr<nsIPromptService> ps + (do_GetService(NS_PROMPTSERVICE_CONTRACTID)); + NS_ENSURE_TRUE(ps, NS_ERROR_FAILURE); + + ps->Alert(nullptr, missingTitle, missingMessage); + } + + return NS_ERROR_ABORT; + } +} + +static nsresult +ProfileLockedDialog(nsIToolkitProfile* aProfile, nsIProfileUnlocker* aUnlocker, + nsINativeAppSupport* aNative, nsIProfileLock* *aResult) +{ + nsCOMPtr<nsIFile> profileDir; + nsresult rv = aProfile->GetRootDir(getter_AddRefs(profileDir)); + if (NS_FAILED(rv)) return rv; + + bool exists; + profileDir->Exists(&exists); + if (!exists) { + return ProfileMissingDialog(aNative); + } + + nsCOMPtr<nsIFile> profileLocalDir; + rv = aProfile->GetLocalDir(getter_AddRefs(profileLocalDir)); + if (NS_FAILED(rv)) return rv; + + return ProfileLockedDialog(profileDir, profileLocalDir, aUnlocker, aNative, + aResult); +} + +static const char kProfileManagerURL[] = + "chrome://mozapps/content/profile/profileSelection.xul"; + +static ReturnAbortOnError +ShowProfileManager(nsIToolkitProfileService* aProfileSvc, + nsINativeAppSupport* aNative) +{ + if (!CanShowProfileManager()) { + return NS_ERROR_NOT_IMPLEMENTED; + } + + nsresult rv; + + nsCOMPtr<nsIFile> profD, profLD; + char16_t* profileNamePtr; + nsAutoCString profileName; + + { + ScopedXPCOMStartup xpcom; + rv = xpcom.Initialize(); + NS_ENSURE_SUCCESS(rv, rv); + + // Initialize the graphics prefs, some of the paths need them before + // any other graphics is initialized (e.g., showing the profile chooser.) + gfxPrefs::GetSingleton(); + + rv = xpcom.SetWindowCreator(aNative); + NS_ENSURE_SUCCESS(rv, NS_ERROR_FAILURE); + +#ifdef XP_MACOSX + CommandLineServiceMac::SetupMacCommandLine(gRestartArgc, gRestartArgv, true); +#endif + +#ifdef XP_WIN + // we don't have to wait here because profile manager window will pump + // and DDE message will be handled + ProcessDDE(aNative, false); +#endif + + { //extra scoping is needed so we release these components before xpcom shutdown + nsCOMPtr<nsIWindowWatcher> windowWatcher + (do_GetService(NS_WINDOWWATCHER_CONTRACTID)); + nsCOMPtr<nsIDialogParamBlock> ioParamBlock + (do_CreateInstance(NS_DIALOGPARAMBLOCK_CONTRACTID)); + nsCOMPtr<nsIMutableArray> dlgArray (do_CreateInstance(NS_ARRAY_CONTRACTID)); + NS_ENSURE_TRUE(windowWatcher && ioParamBlock && dlgArray, NS_ERROR_FAILURE); + + ioParamBlock->SetObjects(dlgArray); + + nsCOMPtr<nsIAppStartup> appStartup + (do_GetService(NS_APPSTARTUP_CONTRACTID)); + NS_ENSURE_TRUE(appStartup, NS_ERROR_FAILURE); + + nsCOMPtr<mozIDOMWindowProxy> newWindow; + rv = windowWatcher->OpenWindow(nullptr, + kProfileManagerURL, + "_blank", + "centerscreen,chrome,modal,titlebar", + ioParamBlock, + getter_AddRefs(newWindow)); + + NS_ENSURE_SUCCESS_LOG(rv, rv); + + aProfileSvc->Flush(); + + int32_t dialogConfirmed; + rv = ioParamBlock->GetInt(0, &dialogConfirmed); + if (NS_FAILED(rv) || dialogConfirmed == 0) return NS_ERROR_ABORT; + + nsCOMPtr<nsIProfileLock> lock; + rv = dlgArray->QueryElementAt(0, NS_GET_IID(nsIProfileLock), + getter_AddRefs(lock)); + NS_ENSURE_SUCCESS_LOG(rv, rv); + + rv = lock->GetDirectory(getter_AddRefs(profD)); + NS_ENSURE_SUCCESS(rv, rv); + + rv = lock->GetLocalDirectory(getter_AddRefs(profLD)); + NS_ENSURE_SUCCESS(rv, rv); + + rv = ioParamBlock->GetString(0, &profileNamePtr); + NS_ENSURE_SUCCESS(rv, rv); + + CopyUTF16toUTF8(profileNamePtr, profileName); + free(profileNamePtr); + + lock->Unlock(); + } + } + + SaveFileToEnv("XRE_PROFILE_PATH", profD); + SaveFileToEnv("XRE_PROFILE_LOCAL_PATH", profLD); + SaveWordToEnv("XRE_PROFILE_NAME", profileName); + + bool offline = false; + aProfileSvc->GetStartOffline(&offline); + if (offline) { + SaveToEnv("XRE_START_OFFLINE=1"); + } + + return LaunchChild(aNative); +} + +/** + * Set the currently running profile as the default/selected one. + * + * @param aCurrentProfileRoot The root directory of the current profile. + * @return an error if aCurrentProfileRoot is not found or the profile could not + * be set as the default. + */ +static nsresult +SetCurrentProfileAsDefault(nsIToolkitProfileService* aProfileSvc, + nsIFile* aCurrentProfileRoot) +{ + NS_ENSURE_ARG_POINTER(aProfileSvc); + + nsCOMPtr<nsISimpleEnumerator> profiles; + nsresult rv = aProfileSvc->GetProfiles(getter_AddRefs(profiles)); + if (NS_FAILED(rv)) + return rv; + + bool foundMatchingProfile = false; + nsCOMPtr<nsISupports> supports; + rv = profiles->GetNext(getter_AddRefs(supports)); + while (NS_SUCCEEDED(rv)) { + nsCOMPtr<nsIToolkitProfile> profile = do_QueryInterface(supports); + nsCOMPtr<nsIFile> profileRoot; + profile->GetRootDir(getter_AddRefs(profileRoot)); + profileRoot->Equals(aCurrentProfileRoot, &foundMatchingProfile); + if (foundMatchingProfile) { + return aProfileSvc->SetSelectedProfile(profile); + } + rv = profiles->GetNext(getter_AddRefs(supports)); + } + return rv; +} + +static bool gDoMigration = false; +static bool gDoProfileReset = false; +static nsAutoCString gResetOldProfileName; + +// Pick a profile. We need to end up with a profile lock. +// +// 1) check for --profile <path> +// 2) check for -P <name> +// 3) check for --ProfileManager +// 4) use the default profile, if there is one +// 5) if there are *no* profiles, set up profile-migration +// 6) display the profile-manager UI +static nsresult +SelectProfile(nsIProfileLock* *aResult, nsIToolkitProfileService* aProfileSvc, nsINativeAppSupport* aNative, + bool* aStartOffline, nsACString* aProfileName) +{ + StartupTimeline::Record(StartupTimeline::SELECT_PROFILE); + + nsresult rv; + ArgResult ar; + const char* arg; + *aResult = nullptr; + *aStartOffline = false; + + ar = CheckArg("offline", true); + if (ar == ARG_BAD) { + PR_fprintf(PR_STDERR, "Error: argument --offline is invalid when argument --osint is specified\n"); + return NS_ERROR_FAILURE; + } + + if (ar || EnvHasValue("XRE_START_OFFLINE")) + *aStartOffline = true; + + if (EnvHasValue("MOZ_RESET_PROFILE_RESTART")) { + gDoProfileReset = true; + gDoMigration = true; + SaveToEnv("MOZ_RESET_PROFILE_RESTART="); + } + + // reset-profile and migration args need to be checked before any profiles are chosen below. + ar = CheckArg("reset-profile", true); + if (ar == ARG_BAD) { + PR_fprintf(PR_STDERR, "Error: argument --reset-profile is invalid when argument --osint is specified\n"); + return NS_ERROR_FAILURE; + } else if (ar == ARG_FOUND) { + gDoProfileReset = true; + } + + ar = CheckArg("migration", true); + if (ar == ARG_BAD) { + PR_fprintf(PR_STDERR, "Error: argument --migration is invalid when argument --osint is specified\n"); + return NS_ERROR_FAILURE; + } else if (ar == ARG_FOUND) { + gDoMigration = true; + } + + nsCOMPtr<nsIFile> lf = GetFileFromEnv("XRE_PROFILE_PATH"); + if (lf) { + nsCOMPtr<nsIFile> localDir = + GetFileFromEnv("XRE_PROFILE_LOCAL_PATH"); + if (!localDir) { + localDir = lf; + } + + arg = PR_GetEnv("XRE_PROFILE_NAME"); + if (arg && *arg && aProfileName) { + aProfileName->Assign(nsDependentCString(arg)); + if (gDoProfileReset) { + gResetOldProfileName.Assign(*aProfileName); + } + } + + // Clear out flags that we handled (or should have handled!) last startup. + const char *dummy; + CheckArg("p", false, &dummy); + CheckArg("profile", false, &dummy); + CheckArg("profilemanager"); + + if (gDoProfileReset) { + // If we're resetting a profile, create a new one and use it to startup. + nsCOMPtr<nsIToolkitProfile> newProfile; + rv = CreateResetProfile(aProfileSvc, getter_AddRefs(newProfile)); + if (NS_SUCCEEDED(rv)) { + rv = newProfile->GetRootDir(getter_AddRefs(lf)); + NS_ENSURE_SUCCESS(rv, rv); + SaveFileToEnv("XRE_PROFILE_PATH", lf); + + rv = newProfile->GetLocalDir(getter_AddRefs(localDir)); + NS_ENSURE_SUCCESS(rv, rv); + SaveFileToEnv("XRE_PROFILE_LOCAL_PATH", localDir); + + rv = newProfile->GetName(*aProfileName); + if (NS_FAILED(rv)) + aProfileName->Truncate(0); + SaveWordToEnv("XRE_PROFILE_NAME", *aProfileName); + } else { + NS_WARNING("Profile reset failed."); + gDoProfileReset = false; + } + } + + return NS_LockProfilePath(lf, localDir, nullptr, aResult); + } + + ar = CheckArg("profile", true, &arg); + if (ar == ARG_BAD) { + PR_fprintf(PR_STDERR, "Error: argument --profile requires a path\n"); + return NS_ERROR_FAILURE; + } + if (ar) { + if (gDoProfileReset) { + NS_WARNING("Profile reset is not supported in conjunction with --profile."); + gDoProfileReset = false; + } + + nsCOMPtr<nsIFile> lf; + rv = XRE_GetFileFromPath(arg, getter_AddRefs(lf)); + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMPtr<nsIProfileUnlocker> unlocker; + + // Check if the profile path exists and it's a directory. + bool exists; + lf->Exists(&exists); + if (!exists) { + rv = lf->Create(nsIFile::DIRECTORY_TYPE, 0700); + NS_ENSURE_SUCCESS(rv, rv); + } + + // If a profile path is specified directory on the command line, then + // assume that the temp directory is the same as the given directory. + rv = NS_LockProfilePath(lf, lf, getter_AddRefs(unlocker), aResult); + if (NS_SUCCEEDED(rv)) + return rv; + + return ProfileLockedDialog(lf, lf, unlocker, aNative, aResult); + } + + ar = CheckArg("createprofile", true, &arg); + if (ar == ARG_BAD) { + PR_fprintf(PR_STDERR, "Error: argument --createprofile requires a profile name\n"); + return NS_ERROR_FAILURE; + } + if (ar) { + nsCOMPtr<nsIToolkitProfile> profile; + + const char* delim = strchr(arg, ' '); + if (delim) { + nsCOMPtr<nsIFile> lf; + rv = NS_NewNativeLocalFile(nsDependentCString(delim + 1), + true, getter_AddRefs(lf)); + if (NS_FAILED(rv)) { + PR_fprintf(PR_STDERR, "Error: profile path not valid.\n"); + return rv; + } + + // As with --profile, assume that the given path will be used for the + // main profile directory. + rv = aProfileSvc->CreateProfile(lf, nsDependentCSubstring(arg, delim), + getter_AddRefs(profile)); + } else { + rv = aProfileSvc->CreateProfile(nullptr, nsDependentCString(arg), + getter_AddRefs(profile)); + } + // Some pathological arguments can make it this far + if (NS_FAILED(rv)) { + PR_fprintf(PR_STDERR, "Error creating profile.\n"); + return rv; + } + rv = NS_ERROR_ABORT; + aProfileSvc->Flush(); + + // XXXben need to ensure prefs.js exists here so the tinderboxes will + // not go orange. + nsCOMPtr<nsIFile> prefsJSFile; + profile->GetRootDir(getter_AddRefs(prefsJSFile)); + prefsJSFile->AppendNative(NS_LITERAL_CSTRING("prefs.js")); + nsAutoCString pathStr; + prefsJSFile->GetNativePath(pathStr); + PR_fprintf(PR_STDERR, "Success: created profile '%s' at '%s'\n", arg, pathStr.get()); + bool exists; + prefsJSFile->Exists(&exists); + if (!exists) { + // Ignore any errors; we're about to return NS_ERROR_ABORT anyway. + Unused << prefsJSFile->Create(nsIFile::NORMAL_FILE_TYPE, 0644); + } + // XXXdarin perhaps 0600 would be better? + + return rv; + } + + uint32_t count; + rv = aProfileSvc->GetProfileCount(&count); + NS_ENSURE_SUCCESS(rv, rv); + + ar = CheckArg("p", false, &arg); + if (ar == ARG_BAD) { + ar = CheckArg("osint"); + if (ar == ARG_FOUND) { + PR_fprintf(PR_STDERR, "Error: argument -p is invalid when argument --osint is specified\n"); + return NS_ERROR_FAILURE; + } + + if (CanShowProfileManager()) { + return ShowProfileManager(aProfileSvc, aNative); + } + } + if (ar) { + ar = CheckArg("osint"); + if (ar == ARG_FOUND) { + PR_fprintf(PR_STDERR, "Error: argument -p is invalid when argument --osint is specified\n"); + return NS_ERROR_FAILURE; + } + nsCOMPtr<nsIToolkitProfile> profile; + rv = aProfileSvc->GetProfileByName(nsDependentCString(arg), + getter_AddRefs(profile)); + if (NS_SUCCEEDED(rv)) { + if (gDoProfileReset) { + { + // Check that the source profile is not in use by temporarily acquiring its lock. + nsIProfileLock* tempProfileLock; + nsCOMPtr<nsIProfileUnlocker> unlocker; + rv = profile->Lock(getter_AddRefs(unlocker), &tempProfileLock); + if (NS_FAILED(rv)) + return ProfileLockedDialog(profile, unlocker, aNative, &tempProfileLock); + } + + nsCOMPtr<nsIToolkitProfile> newProfile; + rv = CreateResetProfile(aProfileSvc, getter_AddRefs(newProfile)); + if (NS_FAILED(rv)) { + NS_WARNING("Failed to create a profile to reset to."); + gDoProfileReset = false; + } else { + nsresult gotName = profile->GetName(gResetOldProfileName); + if (NS_SUCCEEDED(gotName)) { + profile = newProfile; + } else { + NS_WARNING("Failed to get the name of the profile we're resetting, so aborting reset."); + gResetOldProfileName.Truncate(0); + gDoProfileReset = false; + } + } + } + + nsCOMPtr<nsIProfileUnlocker> unlocker; + rv = profile->Lock(getter_AddRefs(unlocker), aResult); + if (NS_SUCCEEDED(rv)) { + if (aProfileName) + aProfileName->Assign(nsDependentCString(arg)); + return NS_OK; + } + + return ProfileLockedDialog(profile, unlocker, aNative, aResult); + } + + if (CanShowProfileManager()) { + return ShowProfileManager(aProfileSvc, aNative); + } + } + + ar = CheckArg("profilemanager", true); + if (ar == ARG_BAD) { + PR_fprintf(PR_STDERR, "Error: argument --profilemanager is invalid when argument --osint is specified\n"); + return NS_ERROR_FAILURE; + } else if (ar == ARG_FOUND && CanShowProfileManager()) { + return ShowProfileManager(aProfileSvc, aNative); + } + +#ifndef MOZ_DEV_EDITION + // If the only existing profile is the dev-edition-profile and this is not + // Developer Edition, then no valid profiles were found. + if (count == 1) { + nsCOMPtr<nsIToolkitProfile> deProfile; + // GetSelectedProfile will auto-select the only profile if there's just one + aProfileSvc->GetSelectedProfile(getter_AddRefs(deProfile)); + nsAutoCString profileName; + deProfile->GetName(profileName); + if (profileName.EqualsLiteral("dev-edition-default")) { + count = 0; + } + } +#endif + + if (!count) { + gDoMigration = true; + gDoProfileReset = false; + + // create a default profile + nsCOMPtr<nsIToolkitProfile> profile; + nsresult rv = aProfileSvc->CreateProfile(nullptr, // choose a default dir for us +#ifdef MOZ_DEV_EDITION + NS_LITERAL_CSTRING("dev-edition-default"), +#else + NS_LITERAL_CSTRING("default"), +#endif + getter_AddRefs(profile)); + if (NS_SUCCEEDED(rv)) { +#ifndef MOZ_DEV_EDITION + aProfileSvc->SetDefaultProfile(profile); +#endif + aProfileSvc->Flush(); + rv = profile->Lock(nullptr, aResult); + if (NS_SUCCEEDED(rv)) { + if (aProfileName) +#ifdef MOZ_DEV_EDITION + aProfileName->AssignLiteral("dev-edition-default"); +#else + aProfileName->AssignLiteral("default"); +#endif + return NS_OK; + } + } + } + + bool useDefault = true; + if (count > 1 && CanShowProfileManager()) { + aProfileSvc->GetStartWithLastProfile(&useDefault); + } + + if (useDefault) { + nsCOMPtr<nsIToolkitProfile> profile; + // GetSelectedProfile will auto-select the only profile if there's just one + aProfileSvc->GetSelectedProfile(getter_AddRefs(profile)); + if (profile) { + // If we're resetting a profile, create a new one and use it to startup. + if (gDoProfileReset) { + { + // Check that the source profile is not in use by temporarily acquiring its lock. + nsIProfileLock* tempProfileLock; + nsCOMPtr<nsIProfileUnlocker> unlocker; + rv = profile->Lock(getter_AddRefs(unlocker), &tempProfileLock); + if (NS_FAILED(rv)) + return ProfileLockedDialog(profile, unlocker, aNative, &tempProfileLock); + } + + nsCOMPtr<nsIToolkitProfile> newProfile; + rv = CreateResetProfile(aProfileSvc, getter_AddRefs(newProfile)); + if (NS_FAILED(rv)) { + NS_WARNING("Failed to create a profile to reset to."); + gDoProfileReset = false; + } else { + nsresult gotName = profile->GetName(gResetOldProfileName); + if (NS_SUCCEEDED(gotName)) { + profile = newProfile; + } else { + NS_WARNING("Failed to get the name of the profile we're resetting, so aborting reset."); + gResetOldProfileName.Truncate(0); + gDoProfileReset = false; + } + } + } + + // If you close Firefox and very quickly reopen it, the old Firefox may + // still be closing down. Rather than immediately showing the + // "Firefox is running but is not responding" message, we spend a few + // seconds retrying first. + + static const int kLockRetrySeconds = 5; + static const int kLockRetrySleepMS = 100; + + nsCOMPtr<nsIProfileUnlocker> unlocker; + const TimeStamp start = TimeStamp::Now(); + do { + rv = profile->Lock(getter_AddRefs(unlocker), aResult); + if (NS_SUCCEEDED(rv)) { + StartupTimeline::Record(StartupTimeline::AFTER_PROFILE_LOCKED); + // Try to grab the profile name. + if (aProfileName) { + rv = profile->GetName(*aProfileName); + if (NS_FAILED(rv)) + aProfileName->Truncate(0); + } + return NS_OK; + } + PR_Sleep(kLockRetrySleepMS); + } while (TimeStamp::Now() - start < TimeDuration::FromSeconds(kLockRetrySeconds)); + + return ProfileLockedDialog(profile, unlocker, aNative, aResult); + } + } + + if (!CanShowProfileManager()) { + return NS_ERROR_FAILURE; + } + + return ShowProfileManager(aProfileSvc, aNative); +} + +/** + * Checks the compatibility.ini file to see if we have updated our application + * or otherwise invalidated our caches. If the application has been updated, + * we return false; otherwise, we return true. We also write the status + * of the caches (valid/invalid) into the return param aCachesOK. The aCachesOK + * is always invalid if the application has been updated. + */ +static bool +CheckCompatibility(nsIFile* aProfileDir, const nsCString& aVersion, + const nsCString& aOSABI, nsIFile* aXULRunnerDir, + nsIFile* aAppDir, nsIFile* aFlagFile, + bool* aCachesOK) +{ + *aCachesOK = false; + nsCOMPtr<nsIFile> file; + aProfileDir->Clone(getter_AddRefs(file)); + if (!file) + return false; + file->AppendNative(FILE_COMPATIBILITY_INFO); + + nsINIParser parser; + nsresult rv = parser.Init(file); + if (NS_FAILED(rv)) + return false; + + nsAutoCString buf; + rv = parser.GetString("Compatibility", "LastVersion", buf); + if (NS_FAILED(rv) || !aVersion.Equals(buf)) + return false; + + rv = parser.GetString("Compatibility", "LastOSABI", buf); + if (NS_FAILED(rv) || !aOSABI.Equals(buf)) + return false; + + rv = parser.GetString("Compatibility", "LastPlatformDir", buf); + if (NS_FAILED(rv)) + return false; + + nsCOMPtr<nsIFile> lf; + rv = NS_NewNativeLocalFile(buf, false, + getter_AddRefs(lf)); + if (NS_FAILED(rv)) + return false; + + bool eq; + rv = lf->Equals(aXULRunnerDir, &eq); + if (NS_FAILED(rv) || !eq) + return false; + + if (aAppDir) { + rv = parser.GetString("Compatibility", "LastAppDir", buf); + if (NS_FAILED(rv)) + return false; + + rv = NS_NewNativeLocalFile(buf, false, + getter_AddRefs(lf)); + if (NS_FAILED(rv)) + return false; + + rv = lf->Equals(aAppDir, &eq); + if (NS_FAILED(rv) || !eq) + return false; + } + + // If we see this flag, caches are invalid. + rv = parser.GetString("Compatibility", "InvalidateCaches", buf); + *aCachesOK = (NS_FAILED(rv) || !buf.EqualsLiteral("1")); + + bool purgeCaches = false; + if (aFlagFile) { + aFlagFile->Exists(&purgeCaches); + } + + *aCachesOK = !purgeCaches && *aCachesOK; + return true; +} + +static void BuildVersion(nsCString &aBuf) +{ + aBuf.Assign(gAppData->version); + aBuf.Append('_'); + aBuf.Append(gAppData->buildID); + aBuf.Append('/'); + aBuf.Append(gToolkitBuildID); +} + +static void +WriteVersion(nsIFile* aProfileDir, const nsCString& aVersion, + const nsCString& aOSABI, nsIFile* aXULRunnerDir, + nsIFile* aAppDir, bool invalidateCache) +{ + nsCOMPtr<nsIFile> file; + aProfileDir->Clone(getter_AddRefs(file)); + if (!file) + return; + file->AppendNative(FILE_COMPATIBILITY_INFO); + + nsAutoCString platformDir; + aXULRunnerDir->GetNativePath(platformDir); + + nsAutoCString appDir; + if (aAppDir) + aAppDir->GetNativePath(appDir); + + PRFileDesc *fd; + nsresult rv = + file->OpenNSPRFileDesc(PR_WRONLY | PR_CREATE_FILE | PR_TRUNCATE, 0600, &fd); + if (NS_FAILED(rv)) { + NS_ERROR("could not create output stream"); + return; + } + + static const char kHeader[] = "[Compatibility]" NS_LINEBREAK + "LastVersion="; + + PR_Write(fd, kHeader, sizeof(kHeader) - 1); + PR_Write(fd, aVersion.get(), aVersion.Length()); + + static const char kOSABIHeader[] = NS_LINEBREAK "LastOSABI="; + PR_Write(fd, kOSABIHeader, sizeof(kOSABIHeader) - 1); + PR_Write(fd, aOSABI.get(), aOSABI.Length()); + + static const char kPlatformDirHeader[] = NS_LINEBREAK "LastPlatformDir="; + + PR_Write(fd, kPlatformDirHeader, sizeof(kPlatformDirHeader) - 1); + PR_Write(fd, platformDir.get(), platformDir.Length()); + + static const char kAppDirHeader[] = NS_LINEBREAK "LastAppDir="; + if (aAppDir) { + PR_Write(fd, kAppDirHeader, sizeof(kAppDirHeader) - 1); + PR_Write(fd, appDir.get(), appDir.Length()); + } + + static const char kInvalidationHeader[] = NS_LINEBREAK "InvalidateCaches=1"; + if (invalidateCache) + PR_Write(fd, kInvalidationHeader, sizeof(kInvalidationHeader) - 1); + + static const char kNL[] = NS_LINEBREAK; + PR_Write(fd, kNL, sizeof(kNL) - 1); + + PR_Close(fd); +} + +/** + * Returns true if the startup cache file was successfully removed. + * Returns false if file->Clone fails at any point (OOM) or if unable + * to remove the startup cache file. Note in particular the return value + * is unaffected by a failure to remove extensions.ini + */ +static bool +RemoveComponentRegistries(nsIFile* aProfileDir, nsIFile* aLocalProfileDir, + bool aRemoveEMFiles) +{ + nsCOMPtr<nsIFile> file; + aProfileDir->Clone(getter_AddRefs(file)); + if (!file) + return false; + + if (aRemoveEMFiles) { + file->SetNativeLeafName(NS_LITERAL_CSTRING("extensions.ini")); + file->Remove(false); + } + + aLocalProfileDir->Clone(getter_AddRefs(file)); + if (!file) + return false; + +#if defined(XP_UNIX) || defined(XP_BEOS) +#define PLATFORM_FASL_SUFFIX ".mfasl" +#elif defined(XP_WIN) +#define PLATFORM_FASL_SUFFIX ".mfl" +#endif + + file->AppendNative(NS_LITERAL_CSTRING("XUL" PLATFORM_FASL_SUFFIX)); + file->Remove(false); + + file->SetNativeLeafName(NS_LITERAL_CSTRING("XPC" PLATFORM_FASL_SUFFIX)); + file->Remove(false); + + file->SetNativeLeafName(NS_LITERAL_CSTRING("startupCache")); + nsresult rv = file->Remove(true); + return NS_SUCCEEDED(rv) || rv == NS_ERROR_FILE_TARGET_DOES_NOT_EXIST; +} + +// To support application initiated restart via nsIAppStartup.quit, we +// need to save various environment variables, and then restore them +// before re-launching the application. + +static struct SavedVar { + const char *name; + char *value; +} gSavedVars[] = { + {"XUL_APP_FILE", nullptr} +}; + +static void SaveStateForAppInitiatedRestart() +{ + for (size_t i = 0; i < ArrayLength(gSavedVars); ++i) { + const char *s = PR_GetEnv(gSavedVars[i].name); + if (s) + gSavedVars[i].value = PR_smprintf("%s=%s", gSavedVars[i].name, s); + } +} + +static void RestoreStateForAppInitiatedRestart() +{ + for (size_t i = 0; i < ArrayLength(gSavedVars); ++i) { + if (gSavedVars[i].value) + PR_SetEnv(gSavedVars[i].value); + } +} + +#ifdef MOZ_CRASHREPORTER +// When we first initialize the crash reporter we don't have a profile, +// so we set the minidump path to $TEMP. Once we have a profile, +// we set it to $PROFILE/minidumps, creating the directory +// if needed. +static void MakeOrSetMinidumpPath(nsIFile* profD) +{ + nsCOMPtr<nsIFile> dumpD; + profD->Clone(getter_AddRefs(dumpD)); + + if (dumpD) { + bool fileExists; + //XXX: do some more error checking here + dumpD->Append(NS_LITERAL_STRING("minidumps")); + dumpD->Exists(&fileExists); + if (!fileExists) { + nsresult rv = dumpD->Create(nsIFile::DIRECTORY_TYPE, 0700); + NS_ENSURE_SUCCESS_VOID(rv); + } + + nsAutoString pathStr; + if (NS_SUCCEEDED(dumpD->GetPath(pathStr))) + CrashReporter::SetMinidumpPath(pathStr); + } +} +#endif + +const nsXREAppData* gAppData = nullptr; + +#ifdef MOZ_WIDGET_GTK +static void MOZ_gdk_display_close(GdkDisplay *display) +{ +#if CLEANUP_MEMORY + // XXX wallpaper for bug 417163: don't close the Display if we're using the + // Qt theme because we crash (in Qt code) when using jemalloc. + bool skip_display_close = false; + GtkSettings* settings = + gtk_settings_get_for_screen(gdk_display_get_default_screen(display)); + gchar *theme_name; + g_object_get(settings, "gtk-theme-name", &theme_name, nullptr); + if (theme_name) { + skip_display_close = strcmp(theme_name, "Qt") == 0; + if (skip_display_close) + NS_WARNING("wallpaper bug 417163 for Qt theme"); + g_free(theme_name); + } + +#if (MOZ_WIDGET_GTK == 3) + // A workaround for https://bugzilla.gnome.org/show_bug.cgi?id=703257 + if (gtk_check_version(3,9,8) != NULL) + skip_display_close = true; +#endif + + // Get a (new) Pango context that holds a reference to the fontmap that + // GTK has been using. gdk_pango_context_get() must be called while GTK + // has a default display. + PangoContext *pangoContext = gdk_pango_context_get(); + + bool buggyCairoShutdown = cairo_version() < CAIRO_VERSION_ENCODE(1, 4, 0); + + if (!buggyCairoShutdown) { + // We should shut down GDK before we shut down libraries it depends on + // like Pango and cairo. But if cairo shutdown is buggy, we should + // shut down cairo first otherwise it may crash because of dangling + // references to Display objects (see bug 469831). + if (!skip_display_close) + gdk_display_close(display); + } + + // Clean up PangoCairo's default fontmap. + // This pango_fc_font_map_shutdown call (and the associated code to + // get the font map) really shouldn't be needed anymore, except that + // it's needed to avoid having cairo_debug_reset_static_data fatally + // assert if we've leaked other things that hold on to the fontmap, + // which is something that currently happens in mochitest-plugins. + // Even if it didn't happen in mochitest-plugins, we probably want to + // avoid the crash-on-leak problem since it makes it harder to use + // many of our leak tools to debug leaks. + + // This doesn't take a reference. + PangoFontMap *fontmap = pango_context_get_font_map(pangoContext); + // Do some shutdown of the fontmap, which releases the fonts, clearing a + // bunch of circular references from the fontmap through the fonts back to + // itself. The shutdown that this does is much less than what's done by + // the fontmap's finalize, though. + if (PANGO_IS_FC_FONT_MAP(fontmap)) + pango_fc_font_map_shutdown(PANGO_FC_FONT_MAP(fontmap)); + g_object_unref(pangoContext); + + // Tell PangoCairo to release its default fontmap. + pango_cairo_font_map_set_default(nullptr); + + // cairo_debug_reset_static_data() is prototyped through cairo.h included + // by gtk.h. +#ifdef cairo_debug_reset_static_data +#error "Looks like we're including Mozilla's cairo instead of system cairo" +#endif + cairo_debug_reset_static_data(); + // FIXME: Do we need to call this in non-GTK2 cases as well? + FcFini(); + + if (buggyCairoShutdown) { + if (!skip_display_close) + gdk_display_close(display); + } +#else // not CLEANUP_MEMORY + // Don't do anything to avoid running into driver bugs under XCloseDisplay(). + // See bug 973192. + (void) display; +#endif +} + +static const char* detectDisplay(void) +{ + bool tryX11 = false; + bool tryWayland = false; + bool tryBroadway = false; + + // Honor user backend selection + const char *backend = PR_GetEnv("GDK_BACKEND"); + if (!backend || strstr(backend, "*")) { + // Try all backends + tryX11 = true; + tryWayland = true; + tryBroadway = true; + } else if (backend) { + if (strstr(backend, "x11")) + tryX11 = true; + if (strstr(backend, "wayland")) + tryWayland = true; + if (strstr(backend, "broadway")) + tryBroadway = true; + } + + const char *display_name; + if (tryX11 && (display_name = PR_GetEnv("DISPLAY"))) { + return display_name; + } else if (tryWayland && (display_name = PR_GetEnv("WAYLAND_DISPLAY"))) { + return display_name; + } else if (tryBroadway && (display_name = PR_GetEnv("BROADWAY_DISPLAY"))) { + return display_name; + } + + PR_fprintf(PR_STDERR, "Error: GDK_BACKEND does not match available displays\n"); + return nullptr; +} +#endif // MOZ_WIDGET_GTK + +/** + * NSPR will search for the "nspr_use_zone_allocator" symbol throughout + * the process and use it to determine whether the application defines its own + * memory allocator or not. + * + * Since most applications (e.g. Firefox and Thunderbird) don't use any special + * allocators and therefore don't define this symbol, NSPR must search the + * entire process, which reduces startup performance. + * + * By defining the symbol here, we can avoid the wasted lookup and hopefully + * improve startup performance. + */ +NS_VISIBILITY_DEFAULT PRBool nspr_use_zone_allocator = PR_FALSE; + +#ifdef CAIRO_HAS_DWRITE_FONT + +#include <dwrite.h> + +#ifdef DEBUG_DWRITE_STARTUP + +#define LOGREGISTRY(msg) LogRegistryEvent(msg) + +// for use when monitoring process +static void LogRegistryEvent(const wchar_t *msg) +{ + HKEY dummyKey; + HRESULT hr; + wchar_t buf[512]; + + wsprintf(buf, L" log %s", msg); + hr = RegOpenKeyEx(HKEY_LOCAL_MACHINE, buf, 0, KEY_READ, &dummyKey); + if (SUCCEEDED(hr)) { + RegCloseKey(dummyKey); + } +} +#else + +#define LOGREGISTRY(msg) + +#endif + +static DWORD WINAPI InitDwriteBG(LPVOID lpdwThreadParam) +{ + SetThreadPriority(GetCurrentThread(), THREAD_MODE_BACKGROUND_BEGIN); + LOGREGISTRY(L"loading dwrite.dll"); + HMODULE dwdll = LoadLibraryW(L"dwrite.dll"); + if (dwdll) { + decltype(DWriteCreateFactory)* createDWriteFactory = (decltype(DWriteCreateFactory)*) + GetProcAddress(dwdll, "DWriteCreateFactory"); + if (createDWriteFactory) { + LOGREGISTRY(L"creating dwrite factory"); + IDWriteFactory *factory; + HRESULT hr = createDWriteFactory( + DWRITE_FACTORY_TYPE_SHARED, + __uuidof(IDWriteFactory), + reinterpret_cast<IUnknown**>(&factory)); + if (SUCCEEDED(hr)) { + LOGREGISTRY(L"dwrite factory done"); + factory->Release(); + LOGREGISTRY(L"freed factory"); + } else { + LOGREGISTRY(L"failed to create factory"); + } + } + } + SetThreadPriority(GetCurrentThread(), THREAD_MODE_BACKGROUND_END); + return 0; +} +#endif + +#ifdef USE_GLX_TEST +bool fire_glxtest_process(); +#endif + +#include "GeckoProfiler.h" + +// Encapsulates startup and shutdown state for XRE_main +class XREMain +{ +public: + XREMain() : + mStartOffline(false) + , mShuttingDown(false) +#ifdef MOZ_ENABLE_XREMOTE + , mDisableRemote(false) +#endif +#if defined(MOZ_WIDGET_GTK) + , mGdkDisplay(nullptr) +#endif + {}; + + ~XREMain() { + mScopedXPCOM = nullptr; + mAppData = nullptr; + } + + int XRE_main(int argc, char* argv[], const nsXREAppData* aAppData); + int XRE_mainInit(bool* aExitFlag); + int XRE_mainStartup(bool* aExitFlag); + nsresult XRE_mainRun(); + + nsCOMPtr<nsINativeAppSupport> mNativeApp; + nsCOMPtr<nsIToolkitProfileService> mProfileSvc; + nsCOMPtr<nsIFile> mProfD; + nsCOMPtr<nsIFile> mProfLD; + nsCOMPtr<nsIProfileLock> mProfileLock; +#ifdef MOZ_ENABLE_XREMOTE + nsCOMPtr<nsIRemoteService> mRemoteService; + nsProfileLock mRemoteLock; + nsCOMPtr<nsIFile> mRemoteLockDir; +#endif + + UniquePtr<ScopedXPCOMStartup> mScopedXPCOM; + nsAutoPtr<mozilla::ScopedAppData> mAppData; + + nsXREDirProvider mDirProvider; + nsAutoCString mProfileName; + nsAutoCString mDesktopStartupID; + + bool mStartOffline; + bool mShuttingDown; +#ifdef MOZ_ENABLE_XREMOTE + bool mDisableRemote; +#endif + +#if defined(MOZ_WIDGET_GTK) + GdkDisplay* mGdkDisplay; +#endif +}; + +/* + * XRE_mainInit - Initial setup and command line parameter processing. + * Main() will exit early if either return value != 0 or if aExitFlag is + * true. + */ +int +XREMain::XRE_mainInit(bool* aExitFlag) +{ + if (!aExitFlag) + return 1; + *aExitFlag = false; + + atexit(UnexpectedExit); + auto expectedShutdown = mozilla::MakeScopeExit([&] { + MozExpectedExit(); + }); + + StartupTimeline::Record(StartupTimeline::MAIN); + + if (PR_GetEnv("MOZ_CHAOSMODE")) { + ChaosFeature feature = ChaosFeature::Any; + long featureInt = strtol(PR_GetEnv("MOZ_CHAOSMODE"), nullptr, 16); + if (featureInt) { + // NOTE: MOZ_CHAOSMODE=0 or a non-hex value maps to Any feature. + feature = static_cast<ChaosFeature>(featureInt); + } + ChaosMode::SetChaosFeature(feature); + } + + if (ChaosMode::isActive(ChaosFeature::Any)) { + printf_stderr("*** You are running in chaos test mode. See ChaosMode.h. ***\n"); + } + + nsresult rv; + ArgResult ar; + +#ifdef DEBUG + if (PR_GetEnv("XRE_MAIN_BREAK")) + NS_BREAK(); +#endif + +#ifdef USE_GLX_TEST + // bug 639842 - it's very important to fire this process BEFORE we set up + // error handling. indeed, this process is expected to be crashy, and we + // don't want the user to see its crashes. That's the whole reason for + // doing this in a separate process. + // + // This call will cause a fork and the fork will terminate itself separately + // from the usual shutdown sequence + fire_glxtest_process(); +#endif + + SetupErrorHandling(gArgv[0]); + +#ifdef CAIRO_HAS_DWRITE_FONT + { + // Bug 602792 - when DWriteCreateFactory is called the dwrite client dll + // starts the FntCache service if it isn't already running (it's set + // to manual startup by default in Windows 7 RTM). Subsequent DirectWrite + // calls cause the IDWriteFactory object to communicate with the FntCache + // service with a timeout; if there's no response after the timeout, the + // DirectWrite client library will assume the service isn't around and do + // manual font file I/O on _all_ system fonts. To avoid this, load the + // dwrite library and create a factory as early as possible so that the + // FntCache service is ready by the time it's needed. + + if (IsVistaOrLater()) { + CreateThread(nullptr, 0, &InitDwriteBG, nullptr, 0, nullptr); + } + } +#endif + +#ifdef XP_UNIX + const char *home = PR_GetEnv("HOME"); + if (!home || !*home) { + struct passwd *pw = getpwuid(geteuid()); + if (!pw || !pw->pw_dir) { + Output(true, "Could not determine HOME directory"); + return 1; + } + SaveWordToEnv("HOME", nsDependentCString(pw->pw_dir)); + } +#endif + +#ifdef MOZ_ACCESSIBILITY_ATK + // Suppress atk-bridge init at startup, until mozilla accessibility is + // initialized. This works after gnome 2.24.2. + SaveToEnv("NO_AT_BRIDGE=1"); +#endif + + // Check for application.ini overrides + const char* override = nullptr; + ar = CheckArg("override", true, &override); + if (ar == ARG_BAD) { + Output(true, "Incorrect number of arguments passed to --override"); + return 1; + } + else if (ar == ARG_FOUND) { + nsCOMPtr<nsIFile> overrideLF; + rv = XRE_GetFileFromPath(override, getter_AddRefs(overrideLF)); + if (NS_FAILED(rv)) { + Output(true, "Error: unrecognized override.ini path.\n"); + return 1; + } + + rv = XRE_ParseAppData(overrideLF, mAppData.get()); + if (NS_FAILED(rv)) { + Output(true, "Couldn't read override.ini"); + return 1; + } + } + + // Check sanity and correctness of app data. + + if (!mAppData->name) { + Output(true, "Error: App:Name not specified in application.ini\n"); + return 1; + } + if (!mAppData->buildID) { + Output(true, "Error: App:BuildID not specified in application.ini\n"); + return 1; + } + + // XXX Originally ScopedLogging was here? Now it's in XRE_main above + // XRE_mainInit. + + if (!mAppData->xreDirectory) { + nsCOMPtr<nsIFile> lf; + rv = XRE_GetBinaryPath(gArgv[0], getter_AddRefs(lf)); + if (NS_FAILED(rv)) + return 2; + + nsCOMPtr<nsIFile> greDir; + rv = lf->GetParent(getter_AddRefs(greDir)); + if (NS_FAILED(rv)) + return 2; + +#ifdef XP_MACOSX + nsCOMPtr<nsIFile> parent; + greDir->GetParent(getter_AddRefs(parent)); + greDir = parent.forget(); + greDir->AppendNative(NS_LITERAL_CSTRING("Resources")); +#endif + + greDir.forget(&mAppData->xreDirectory); + } + + if (!mAppData->directory) { + NS_IF_ADDREF(mAppData->directory = mAppData->xreDirectory); + } + + if (mAppData->size > offsetof(nsXREAppData, minVersion)) { + if (!mAppData->minVersion) { + Output(true, "Error: Gecko:MinVersion not specified in application.ini\n"); + return 1; + } + + if (!mAppData->maxVersion) { + // If no maxVersion is specified, we assume the app is only compatible + // with the initial preview release. Do not increment this number ever! + SetAllocatedString(mAppData->maxVersion, "1.*"); + } + + if (mozilla::Version(mAppData->minVersion) > gToolkitVersion || + mozilla::Version(mAppData->maxVersion) < gToolkitVersion) { + Output(true, "Error: Platform version '%s' is not compatible with\n" + "minVersion >= %s\nmaxVersion <= %s\n", + gToolkitVersion, + mAppData->minVersion, mAppData->maxVersion); + return 1; + } + } + + rv = mDirProvider.Initialize(mAppData->directory, mAppData->xreDirectory); + if (NS_FAILED(rv)) + return 1; + +#ifdef MOZ_CRASHREPORTER + if (EnvHasValue("MOZ_CRASHREPORTER")) { + mAppData->flags |= NS_XRE_ENABLE_CRASH_REPORTER; + } + + nsCOMPtr<nsIFile> xreBinDirectory; + xreBinDirectory = mDirProvider.GetGREBinDir(); + + if ((mAppData->flags & NS_XRE_ENABLE_CRASH_REPORTER) && + NS_SUCCEEDED( + CrashReporter::SetExceptionHandler(xreBinDirectory))) { + nsCOMPtr<nsIFile> file; + rv = mDirProvider.GetUserAppDataDirectory(getter_AddRefs(file)); + if (NS_SUCCEEDED(rv)) { + CrashReporter::SetUserAppDataDirectory(file); + } + if (mAppData->crashReporterURL) + CrashReporter::SetServerURL(nsDependentCString(mAppData->crashReporterURL)); + + // We overwrite this once we finish starting up. + CrashReporter::AnnotateCrashReport(NS_LITERAL_CSTRING("StartupCrash"), + NS_LITERAL_CSTRING("1")); + + // pass some basic info from the app data + if (mAppData->vendor) + CrashReporter::AnnotateCrashReport(NS_LITERAL_CSTRING("Vendor"), + nsDependentCString(mAppData->vendor)); + if (mAppData->name) + CrashReporter::AnnotateCrashReport(NS_LITERAL_CSTRING("ProductName"), + nsDependentCString(mAppData->name)); + if (mAppData->ID) + CrashReporter::AnnotateCrashReport(NS_LITERAL_CSTRING("ProductID"), + nsDependentCString(mAppData->ID)); + if (mAppData->version) + CrashReporter::AnnotateCrashReport(NS_LITERAL_CSTRING("Version"), + nsDependentCString(mAppData->version)); + if (mAppData->buildID) + CrashReporter::AnnotateCrashReport(NS_LITERAL_CSTRING("BuildID"), + nsDependentCString(mAppData->buildID)); + + nsDependentCString releaseChannel(NS_STRINGIFY(MOZ_UPDATE_CHANNEL)); + CrashReporter::AnnotateCrashReport(NS_LITERAL_CSTRING("ReleaseChannel"), + releaseChannel); +#ifdef MOZ_LINKER + CrashReporter::AnnotateCrashReport(NS_LITERAL_CSTRING("CrashAddressLikelyWrong"), + IsSignalHandlingBroken() ? NS_LITERAL_CSTRING("1") + : NS_LITERAL_CSTRING("0")); +#endif + +#ifdef XP_WIN + nsAutoString appInitDLLs; + if (widget::WinUtils::GetAppInitDLLs(appInitDLLs)) { + CrashReporter::AnnotateCrashReport(NS_LITERAL_CSTRING("AppInitDLLs"), + NS_ConvertUTF16toUTF8(appInitDLLs)); + } +#endif + + CrashReporter::SetRestartArgs(gArgc, gArgv); + + // annotate other data (user id etc) + nsCOMPtr<nsIFile> userAppDataDir; + if (NS_SUCCEEDED(mDirProvider.GetUserAppDataDirectory( + getter_AddRefs(userAppDataDir)))) { + CrashReporter::SetupExtraData(userAppDataDir, + nsDependentCString(mAppData->buildID)); + + // see if we have a crashreporter-override.ini in the application directory + nsCOMPtr<nsIFile> overrideini; + bool exists; + if (NS_SUCCEEDED(mDirProvider.GetAppDir()->Clone(getter_AddRefs(overrideini))) && + NS_SUCCEEDED(overrideini->AppendNative(NS_LITERAL_CSTRING("crashreporter-override.ini"))) && + NS_SUCCEEDED(overrideini->Exists(&exists)) && + exists) { +#ifdef XP_WIN + nsAutoString overridePathW; + overrideini->GetPath(overridePathW); + NS_ConvertUTF16toUTF8 overridePath(overridePathW); +#else + nsAutoCString overridePath; + overrideini->GetNativePath(overridePath); +#endif + + SaveWordToEnv("MOZ_CRASHREPORTER_STRINGS_OVERRIDE", overridePath); + } + } + } +#endif + +#if defined(MOZ_SANDBOX) && defined(XP_WIN) + if (mAppData->sandboxBrokerServices) { + SandboxBroker::Initialize(mAppData->sandboxBrokerServices); + Telemetry::Accumulate(Telemetry::SANDBOX_BROKER_INITIALIZED, true); + } else { + Telemetry::Accumulate(Telemetry::SANDBOX_BROKER_INITIALIZED, false); +#if defined(MOZ_CONTENT_SANDBOX) + // If we're sandboxing content and we fail to initialize, then crashing here + // seems like the sensible option. + if (BrowserTabsRemoteAutostart()) { + MOZ_CRASH("Failed to initialize broker services, can't continue."); + } +#endif + // Otherwise just warn for the moment, as most things will work. + NS_WARNING("Failed to initialize broker services, sandboxed processes will " + "fail to start."); + } +#endif + +#ifdef XP_MACOSX + // Set up ability to respond to system (Apple) events. This must occur before + // ProcessUpdates to ensure that links clicked in external applications aren't + // lost when updates are pending. + SetupMacApplicationDelegate(); + + if (EnvHasValue("MOZ_LAUNCHED_CHILD")) { + // This is needed, on relaunch, to force the OS to use the "Cocoa Dock + // API". Otherwise the call to ReceiveNextEvent() below will make it + // use the "Carbon Dock API". For more info see bmo bug 377166. + EnsureUseCocoaDockAPI(); + + // When the app relaunches, the original process exits. This causes + // the dock tile to stop bouncing, lose the "running" triangle, and + // if the tile does not permanently reside in the Dock, even disappear. + // This can be confusing to the user, who is expecting the app to launch. + // Calling ReceiveNextEvent without requesting any event is enough to + // cause a dock tile for the child process to appear. + const EventTypeSpec kFakeEventList[] = { { INT_MAX, INT_MAX } }; + EventRef event; + ::ReceiveNextEvent(GetEventTypeCount(kFakeEventList), kFakeEventList, + kEventDurationNoWait, false, &event); + } + + if (CheckArg("foreground")) { + // The original process communicates that it was in the foreground by + // adding this argument. This new process, which is taking over for + // the old one, should make itself the active application. + ProcessSerialNumber psn; + if (::GetCurrentProcess(&psn) == noErr) + ::SetFrontProcess(&psn); + } +#endif + + SaveToEnv("MOZ_LAUNCHED_CHILD="); + + gRestartArgc = gArgc; + gRestartArgv = (char**) malloc(sizeof(char*) * (gArgc + 1 + (override ? 2 : 0))); + if (!gRestartArgv) { + return 1; + } + + int i; + for (i = 0; i < gArgc; ++i) { + gRestartArgv[i] = gArgv[i]; + } + + // Add the -override argument back (it is removed automatically be CheckArg) if there is one + if (override) { + gRestartArgv[gRestartArgc++] = const_cast<char*>("-override"); + gRestartArgv[gRestartArgc++] = const_cast<char*>(override); + } + + gRestartArgv[gRestartArgc] = nullptr; + + + if (EnvHasValue("MOZ_SAFE_MODE_RESTART")) { + gSafeMode = true; + // unset the env variable + SaveToEnv("MOZ_SAFE_MODE_RESTART="); + } + + ar = CheckArg("safe-mode", true); + if (ar == ARG_BAD) { + PR_fprintf(PR_STDERR, "Error: argument --safe-mode is invalid when argument --osint is specified\n"); + return 1; + } else if (ar == ARG_FOUND) { + gSafeMode = true; + } + +#ifdef XP_WIN + // If the shift key is pressed and the ctrl and / or alt keys are not pressed + // during startup start in safe mode. GetKeyState returns a short and the high + // order bit will be 1 if the key is pressed. By masking the returned short + // with 0x8000 the result will be 0 if the key is not pressed and non-zero + // otherwise. + if ((GetKeyState(VK_SHIFT) & 0x8000) && + !(GetKeyState(VK_CONTROL) & 0x8000) && + !(GetKeyState(VK_MENU) & 0x8000) && + !EnvHasValue("MOZ_DISABLE_SAFE_MODE_KEY")) { + gSafeMode = true; + } +#endif + +#ifdef XP_MACOSX + if ((GetCurrentEventKeyModifiers() & optionKey) && + !EnvHasValue("MOZ_DISABLE_SAFE_MODE_KEY")) + gSafeMode = true; +#endif + +#ifdef XP_WIN + { + // Add CPU microcode version to the crash report as "CPUMicrocodeVersion". + // It feels like this code may belong in nsSystemInfo instead. + int cpuUpdateRevision = -1; + HKEY key; + static const WCHAR keyName[] = + L"HARDWARE\\DESCRIPTION\\System\\CentralProcessor\\0"; + + if (RegOpenKeyEx(HKEY_LOCAL_MACHINE, keyName , 0, KEY_QUERY_VALUE, &key) == ERROR_SUCCESS) { + + DWORD updateRevision[2]; + DWORD len = sizeof(updateRevision); + DWORD vtype; + + // Windows 7 uses "Update Signature", 8 uses "Update Revision". + // For AMD CPUs, "CurrentPatchLevel" is sometimes used. + // Take the first one we find. + LPCWSTR choices[] = {L"Update Signature", L"Update Revision", L"CurrentPatchLevel"}; + for (size_t oneChoice=0; oneChoice<ArrayLength(choices); oneChoice++) { + if (RegQueryValueExW(key, choices[oneChoice], + 0, &vtype, + reinterpret_cast<LPBYTE>(updateRevision), + &len) == ERROR_SUCCESS) { + if (vtype == REG_BINARY && len == sizeof(updateRevision)) { + // The first word is unused + cpuUpdateRevision = static_cast<int>(updateRevision[1]); + break; + } else if (vtype == REG_DWORD && len == sizeof(updateRevision[0])) { + cpuUpdateRevision = static_cast<int>(updateRevision[0]); + break; + } + } + } + } + +#ifdef MOZ_CRASHREPORTER + if (cpuUpdateRevision > 0) { + CrashReporter::AnnotateCrashReport(NS_LITERAL_CSTRING("CPUMicrocodeVersion"), + nsPrintfCString("0x%x", + cpuUpdateRevision)); + } +#endif + } +#endif + +#ifdef MOZ_CRASHREPORTER + CrashReporter::AnnotateCrashReport(NS_LITERAL_CSTRING("SafeMode"), + gSafeMode ? NS_LITERAL_CSTRING("1") : + NS_LITERAL_CSTRING("0")); +#endif + + // Handle --no-remote and --new-instance command line arguments. Setup + // the environment to better accommodate other components and various + // restart scenarios. + ar = CheckArg("no-remote", true); + if (ar == ARG_BAD) { + PR_fprintf(PR_STDERR, "Error: argument --no-remote is invalid when argument --osint is specified\n"); + return 1; + } else if (ar == ARG_FOUND) { + SaveToEnv("MOZ_NO_REMOTE=1"); + } + + ar = CheckArg("new-instance", true); + if (ar == ARG_BAD) { + PR_fprintf(PR_STDERR, "Error: argument --new-instance is invalid when argument --osint is specified\n"); + return 1; + } else if (ar == ARG_FOUND) { + SaveToEnv("MOZ_NEW_INSTANCE=1"); + } + + // Handle --help and --version command line arguments. + // They should return quickly, so we deal with them here. + if (CheckArg("h") || CheckArg("help") || CheckArg("?")) { + DumpHelp(); + *aExitFlag = true; + return 0; + } + + if (CheckArg("v") || CheckArg("version")) { + DumpVersion(); + *aExitFlag = true; + return 0; + } + + rv = XRE_InitCommandLine(gArgc, gArgv); + NS_ENSURE_SUCCESS(rv, 1); + + // Check for --register, which registers chrome and then exits immediately. + ar = CheckArg("register", true); + if (ar == ARG_BAD) { + PR_fprintf(PR_STDERR, "Error: argument --register is invalid when argument --osint is specified\n"); + return 1; + } else if (ar == ARG_FOUND) { + ScopedXPCOMStartup xpcom; + rv = xpcom.Initialize(); + NS_ENSURE_SUCCESS(rv, 1); + { + nsCOMPtr<nsIChromeRegistry> chromeReg = + mozilla::services::GetChromeRegistryService(); + NS_ENSURE_TRUE(chromeReg, 1); + + chromeReg->CheckForNewChrome(); + } + *aExitFlag = true; + return 0; + } + + return 0; +} + +#ifdef MOZ_CRASHREPORTER +#ifdef XP_WIN +/** + * Uses WMI to read some manufacturer information that may be useful for + * diagnosing hardware-specific crashes. This function is best-effort; failures + * shouldn't burden the caller. COM must be initialized before calling. + */ +static void AnnotateSystemManufacturer() +{ + RefPtr<IWbemLocator> locator; + + HRESULT hr = CoCreateInstance(CLSID_WbemLocator, nullptr, CLSCTX_INPROC_SERVER, + IID_IWbemLocator, getter_AddRefs(locator)); + + if (FAILED(hr)) { + return; + } + + RefPtr<IWbemServices> services; + + hr = locator->ConnectServer(_bstr_t(L"ROOT\\CIMV2"), nullptr, nullptr, nullptr, + 0, nullptr, nullptr, getter_AddRefs(services)); + + if (FAILED(hr)) { + return; + } + + hr = CoSetProxyBlanket(services, RPC_C_AUTHN_WINNT, RPC_C_AUTHZ_NONE, nullptr, + RPC_C_AUTHN_LEVEL_CALL, RPC_C_IMP_LEVEL_IMPERSONATE, + nullptr, EOAC_NONE); + + if (FAILED(hr)) { + return; + } + + RefPtr<IEnumWbemClassObject> enumerator; + + hr = services->ExecQuery(_bstr_t(L"WQL"), _bstr_t(L"SELECT * FROM Win32_BIOS"), + WBEM_FLAG_FORWARD_ONLY | WBEM_FLAG_RETURN_IMMEDIATELY, + nullptr, getter_AddRefs(enumerator)); + + if (FAILED(hr) || !enumerator) { + return; + } + + RefPtr<IWbemClassObject> classObject; + ULONG results; + + hr = enumerator->Next(WBEM_INFINITE, 1, getter_AddRefs(classObject), &results); + + if (FAILED(hr) || results == 0) { + return; + } + + VARIANT value; + VariantInit(&value); + + hr = classObject->Get(L"Manufacturer", 0, &value, 0, 0); + + if (SUCCEEDED(hr) && V_VT(&value) == VT_BSTR) { + CrashReporter::AnnotateCrashReport(NS_LITERAL_CSTRING("BIOS_Manufacturer"), + NS_ConvertUTF16toUTF8(V_BSTR(&value))); + } + + VariantClear(&value); +} + +static void PR_CALLBACK AnnotateSystemManufacturer_ThreadStart(void*) +{ + HRESULT hr = CoInitialize(nullptr); + + if (FAILED(hr)) { + return; + } + + AnnotateSystemManufacturer(); + + CoUninitialize(); +} +#endif // XP_WIN + +#if defined(XP_LINUX) && !defined(ANDROID) + +static void +AnnotateLSBRelease(void*) +{ + nsCString dist, desc, release, codename; + if (widget::lsb::GetLSBRelease(dist, desc, release, codename)) { + CrashReporter::AppendAppNotesToCrashReport(desc); + } +} + +#endif // defined(XP_LINUX) && !defined(ANDROID) + +#endif + +namespace mozilla { + ShutdownChecksMode gShutdownChecks = SCM_NOTHING; +} // namespace mozilla + +static void SetShutdownChecks() { + // Set default first. On debug builds we crash. On nightly and local + // builds we record. Nightlies will then send the info via telemetry, + // but it is usefull to have the data in about:telemetry in local builds + // too. + +#ifdef DEBUG + gShutdownChecks = SCM_CRASH; +#else + const char* releaseChannel = NS_STRINGIFY(MOZ_UPDATE_CHANNEL); + if (strcmp(releaseChannel, "nightly") == 0 || + strcmp(releaseChannel, "default") == 0) { + gShutdownChecks = SCM_RECORD; + } else { + gShutdownChecks = SCM_NOTHING; + } +#endif + + // We let an environment variable override the default so that addons + // authors can use it for debugging shutdown with released firefox versions. + const char* mozShutdownChecksEnv = PR_GetEnv("MOZ_SHUTDOWN_CHECKS"); + if (mozShutdownChecksEnv) { + if (strcmp(mozShutdownChecksEnv, "crash") == 0) { + gShutdownChecks = SCM_CRASH; + } else if (strcmp(mozShutdownChecksEnv, "record") == 0) { + gShutdownChecks = SCM_RECORD; + } else if (strcmp(mozShutdownChecksEnv, "nothing") == 0) { + gShutdownChecks = SCM_NOTHING; + } + } + +} + +/* + * XRE_mainStartup - Initializes the profile and various other services. + * Main() will exit early if either return value != 0 or if aExitFlag is + * true. + */ +int +XREMain::XRE_mainStartup(bool* aExitFlag) +{ + nsresult rv; + + if (!aExitFlag) + return 1; + *aExitFlag = false; + + SetShutdownChecks(); + + // Enable Telemetry IO Reporting on DEBUG, nightly and local builds +#ifdef DEBUG + mozilla::Telemetry::InitIOReporting(gAppData->xreDirectory); +#else + { + const char* releaseChannel = NS_STRINGIFY(MOZ_UPDATE_CHANNEL); + if (strcmp(releaseChannel, "nightly") == 0 || + strcmp(releaseChannel, "default") == 0) { + mozilla::Telemetry::InitIOReporting(gAppData->xreDirectory); + } + } +#endif /* DEBUG */ + +#if defined(MOZ_WIDGET_GTK) || defined(MOZ_ENABLE_XREMOTE) + // Stash DESKTOP_STARTUP_ID in malloc'ed memory because gtk_init will clear it. +#define HAVE_DESKTOP_STARTUP_ID + const char* desktopStartupIDEnv = PR_GetEnv("DESKTOP_STARTUP_ID"); + if (desktopStartupIDEnv) { + mDesktopStartupID.Assign(desktopStartupIDEnv); + } +#endif + +#if defined(MOZ_WIDGET_GTK) + // setup for private colormap. Ideally we'd like to do this + // in nsAppShell::Create, but we need to get in before gtk + // has been initialized to make sure everything is running + // consistently. +#if (MOZ_WIDGET_GTK == 2) + if (CheckArg("install")) + gdk_rgb_set_install(TRUE); +#endif + + // Set program name to the one defined in application.ini. + { + nsAutoCString program(gAppData->name); + ToLowerCase(program); + g_set_prgname(program.get()); + } + + // Initialize GTK here for splash. + +#if (MOZ_WIDGET_GTK == 3) && defined(MOZ_X11) + // Disable XInput2 support due to focus bugginess. See bugs 1182700, 1170342. + const char* useXI2 = PR_GetEnv("MOZ_USE_XINPUT2"); + if (!useXI2 || (*useXI2 == '0')) + gdk_disable_multidevice(); +#endif + + // Open the display ourselves instead of using gtk_init, so that we can + // close it without fear that one day gtk might clean up the display it + // opens. + if (!gtk_parse_args(&gArgc, &gArgv)) + return 1; +#endif /* MOZ_WIDGET_GTK */ + +#ifdef LIBFUZZER + if (PR_GetEnv("LIBFUZZER")) { + *aExitFlag = true; + return mozilla::libFuzzerRunner->Run(); + } +#endif + + if (PR_GetEnv("MOZ_RUN_GTEST")) { + int result; +#ifdef XP_WIN + UseParentConsole(); +#endif + // RunGTest will only be set if we're in xul-unit + if (mozilla::RunGTest) { + gIsGtest = true; + result = mozilla::RunGTest(); + gIsGtest = false; + } else { + result = 1; + printf("TEST-UNEXPECTED-FAIL | gtest | Not compiled with enable-tests\n"); + } + *aExitFlag = true; + return result; + } + +#if defined(MOZ_WIDGET_GTK) + // display_name is owned by gdk. + const char *display_name = gdk_get_display_arg_name(); + bool saveDisplayArg = false; + if (display_name) { + saveDisplayArg = true; + } else { + display_name = detectDisplay(); + if (!display_name) { + return 1; + } + } +#endif /* MOZ_WIDGET_GTK */ +#ifdef MOZ_X11 + // Init X11 in thread-safe mode. Must be called prior to the first call to XOpenDisplay + // (called inside gdk_display_open). This is a requirement for off main tread compositing. + XInitThreads(); +#endif +#if defined(MOZ_WIDGET_GTK) + mGdkDisplay = gdk_display_open(display_name); + if (!mGdkDisplay) { + PR_fprintf(PR_STDERR, "Error: cannot open display: %s\n", display_name); + return 1; + } + gdk_display_manager_set_default_display (gdk_display_manager_get(), + mGdkDisplay); + if (GDK_IS_X11_DISPLAY(mGdkDisplay)) { + if (saveDisplayArg) { + SaveWordToEnv("DISPLAY", nsDependentCString(display_name)); + } + } else { + mDisableRemote = true; + } +#endif +#ifdef MOZ_ENABLE_XREMOTE + // handle --remote now that xpcom is fired up + bool newInstance; + { + char *e = PR_GetEnv("MOZ_NO_REMOTE"); + mDisableRemote = (mDisableRemote || (e && *e)); + if (mDisableRemote) { + newInstance = true; + } else { + e = PR_GetEnv("MOZ_NEW_INSTANCE"); + newInstance = (e && *e); + } + } + + if (!newInstance) { + nsAutoCString program(gAppData->remotingName); + ToLowerCase(program); + + const char* username = getenv("LOGNAME"); + const char* profile = nullptr; + + RemoteResult rr = ParseRemoteCommandLine(program, &profile, &username); + if (rr == REMOTE_ARG_BAD) { + return 1; + } + + if (!username) { + struct passwd *pw = getpwuid(geteuid()); + if (pw && pw->pw_name) { + // Beware that another call to getpwent/getpwname/getpwuid will overwrite + // pw, but we don't have such another call between here and when username + // is used last. + username = pw->pw_name; + } + } + + nsCOMPtr<nsIFile> mutexDir; + rv = GetSpecialSystemDirectory(OS_TemporaryDirectory, getter_AddRefs(mutexDir)); + if (NS_SUCCEEDED(rv)) { + nsAutoCString mutexPath = program + NS_LITERAL_CSTRING("_"); + // In the unlikely even that LOGNAME is not set and getpwuid failed, just + // don't put the username in the mutex directory. It will conflict with + // other users mutex, but the worst that can happen is that they wait for + // MOZ_XREMOTE_START_TIMEOUT_SEC during startup in that case. + if (username) { + mutexPath.Append(username); + } + if (profile) { + mutexPath.Append(NS_LITERAL_CSTRING("_") + nsDependentCString(profile)); + } + mutexDir->AppendNative(mutexPath); + + rv = mutexDir->Create(nsIFile::DIRECTORY_TYPE, 0700); + if (NS_SUCCEEDED(rv) || rv == NS_ERROR_FILE_ALREADY_EXISTS) { + mRemoteLockDir = mutexDir; + } + } + + if (mRemoteLockDir) { + const TimeStamp epoch = mozilla::TimeStamp::Now(); + do { + rv = mRemoteLock.Lock(mRemoteLockDir, nullptr); + if (NS_SUCCEEDED(rv)) + break; + sched_yield(); + } while ((TimeStamp::Now() - epoch) + < TimeDuration::FromSeconds(MOZ_XREMOTE_START_TIMEOUT_SEC)); + if (NS_FAILED(rv)) { + NS_WARNING("Cannot lock XRemote start mutex"); + } + } + + // Try to remote the entire command line. If this fails, start up normally. + const char* desktopStartupIDPtr = + mDesktopStartupID.IsEmpty() ? nullptr : mDesktopStartupID.get(); + + rr = StartRemoteClient(desktopStartupIDPtr, program, profile, username); + if (rr == REMOTE_FOUND) { + *aExitFlag = true; + return 0; + } else if (rr == REMOTE_ARG_BAD) { + return 1; + } + } +#endif +#if defined(MOZ_WIDGET_GTK) + g_set_application_name(mAppData->name); + gtk_window_set_auto_startup_notification(false); + +#if (MOZ_WIDGET_GTK == 2) + gtk_widget_set_default_colormap(gdk_rgb_get_colormap()); +#endif /* (MOZ_WIDGET_GTK == 2) */ +#endif /* defined(MOZ_WIDGET_GTK) */ +#ifdef MOZ_X11 + // Do this after initializing GDK, or GDK will install its own handler. + XRE_InstallX11ErrorHandler(); +#endif + + // Call the code to install our handler +#ifdef MOZ_JPROF + setupProfilingStuff(); +#endif + + rv = NS_CreateNativeAppSupport(getter_AddRefs(mNativeApp)); + if (NS_FAILED(rv)) + return 1; + + bool canRun = false; + rv = mNativeApp->Start(&canRun); + if (NS_FAILED(rv) || !canRun) { + return 1; + } + +#if defined(HAVE_DESKTOP_STARTUP_ID) && defined(MOZ_WIDGET_GTK) + // DESKTOP_STARTUP_ID is cleared now, + // we recover it in case we need a restart. + if (!mDesktopStartupID.IsEmpty()) { + nsAutoCString desktopStartupEnv; + desktopStartupEnv.AssignLiteral("DESKTOP_STARTUP_ID="); + desktopStartupEnv.Append(mDesktopStartupID); + // Leak it with extreme prejudice! + PR_SetEnv(ToNewCString(desktopStartupEnv)); + } +#endif + +#if defined(MOZ_UPDATER) && !defined(MOZ_WIDGET_ANDROID) + // Check for and process any available updates + nsCOMPtr<nsIFile> updRoot; + bool persistent; + rv = mDirProvider.GetFile(XRE_UPDATE_ROOT_DIR, &persistent, + getter_AddRefs(updRoot)); + // XRE_UPDATE_ROOT_DIR may fail. Fallback to appDir if failed + if (NS_FAILED(rv)) + updRoot = mDirProvider.GetAppDir(); + + // If the MOZ_TEST_PROCESS_UPDATES environment variable already exists, then + // we are being called from the callback application. + if (EnvHasValue("MOZ_TEST_PROCESS_UPDATES")) { + // If the caller has asked us to log our arguments, do so. This is used + // to make sure that the maintenance service successfully launches the + // callback application. + const char *logFile = nullptr; + if (ARG_FOUND == CheckArg("dump-args", false, &logFile)) { + FILE* logFP = fopen(logFile, "wb"); + if (logFP) { + for (int i = 1; i < gRestartArgc; ++i) { + fprintf(logFP, "%s\n", gRestartArgv[i]); + } + fclose(logFP); + } + } + *aExitFlag = true; + return 0; + } + + // Support for processing an update and exiting. The MOZ_TEST_PROCESS_UPDATES + // environment variable will be part of the updater's environment and the + // application that is relaunched by the updater. When the application is + // relaunched by the updater it will be removed below and the application + // will exit. + if (CheckArg("test-process-updates")) { + SaveToEnv("MOZ_TEST_PROCESS_UPDATES=1"); + } + nsCOMPtr<nsIFile> exeFile, exeDir; + rv = mDirProvider.GetFile(XRE_EXECUTABLE_FILE, &persistent, + getter_AddRefs(exeFile)); + NS_ENSURE_SUCCESS(rv, 1); + rv = exeFile->GetParent(getter_AddRefs(exeDir)); + NS_ENSURE_SUCCESS(rv, 1); + ProcessUpdates(mDirProvider.GetGREDir(), + exeDir, + updRoot, + gRestartArgc, + gRestartArgv, + mAppData->version); + if (EnvHasValue("MOZ_TEST_PROCESS_UPDATES")) { + SaveToEnv("MOZ_TEST_PROCESS_UPDATES="); + *aExitFlag = true; + return 0; + } +#endif + + rv = NS_NewToolkitProfileService(getter_AddRefs(mProfileSvc)); + if (rv == NS_ERROR_FILE_ACCESS_DENIED) { + PR_fprintf(PR_STDERR, "Error: Access was denied while trying to open files in " \ + "your profile directory.\n"); + } + if (NS_FAILED(rv)) { + // We failed to choose or create profile - notify user and quit + ProfileMissingDialog(mNativeApp); + return 1; + } + + rv = SelectProfile(getter_AddRefs(mProfileLock), mProfileSvc, mNativeApp, &mStartOffline, + &mProfileName); + if (rv == NS_ERROR_LAUNCHED_CHILD_PROCESS || + rv == NS_ERROR_ABORT) { + *aExitFlag = true; + return 0; + } + + if (NS_FAILED(rv)) { + // We failed to choose or create profile - notify user and quit + ProfileMissingDialog(mNativeApp); + return 1; + } + gProfileLock = mProfileLock; + + rv = mProfileLock->GetDirectory(getter_AddRefs(mProfD)); + NS_ENSURE_SUCCESS(rv, 1); + + rv = mProfileLock->GetLocalDirectory(getter_AddRefs(mProfLD)); + NS_ENSURE_SUCCESS(rv, 1); + + rv = mDirProvider.SetProfile(mProfD, mProfLD); + NS_ENSURE_SUCCESS(rv, 1); + + //////////////////////// NOW WE HAVE A PROFILE //////////////////////// + + mozilla::Telemetry::SetProfileDir(mProfD); + +#ifdef MOZ_CRASHREPORTER + if (mAppData->flags & NS_XRE_ENABLE_CRASH_REPORTER) + MakeOrSetMinidumpPath(mProfD); + + CrashReporter::SetProfileDirectory(mProfD); +#endif + + nsAutoCString version; + BuildVersion(version); + +#ifdef TARGET_OS_ABI + NS_NAMED_LITERAL_CSTRING(osABI, TARGET_OS_ABI); +#else + // No TARGET_XPCOM_ABI, but at least the OS is known + NS_NAMED_LITERAL_CSTRING(osABI, OS_TARGET "_UNKNOWN"); +#endif + + // Check for version compatibility with the last version of the app this + // profile was started with. The format of the version stamp is defined + // by the BuildVersion function. + // Also check to see if something has happened to invalidate our + // fastload caches, like an extension upgrade or installation. + + // If we see .purgecaches, that means someone did a make. + // Re-register components to catch potential changes. + nsCOMPtr<nsIFile> flagFile; + + rv = NS_ERROR_FILE_NOT_FOUND; + nsCOMPtr<nsIFile> fFlagFile; + if (mAppData->directory) { + rv = mAppData->directory->Clone(getter_AddRefs(fFlagFile)); + } + flagFile = do_QueryInterface(fFlagFile); + if (flagFile) { + flagFile->AppendNative(FILE_INVALIDATE_CACHES); + } + + bool cachesOK; + bool versionOK = CheckCompatibility(mProfD, version, osABI, + mDirProvider.GetGREDir(), + mAppData->directory, flagFile, + &cachesOK); + if (CheckArg("purgecaches")) { + cachesOK = false; + } + if (PR_GetEnv("MOZ_PURGE_CACHES")) { + cachesOK = false; + } + + // Every time a profile is loaded by a build with a different version, + // it updates the compatibility.ini file saying what version last wrote + // the fastload caches. On subsequent launches if the version matches, + // there is no need for re-registration. If the user loads the same + // profile in different builds the component registry must be + // re-generated to prevent mysterious component loading failures. + // + bool startupCacheValid = true; + if (gSafeMode) { + startupCacheValid = RemoveComponentRegistries(mProfD, mProfLD, false); + WriteVersion(mProfD, NS_LITERAL_CSTRING("Safe Mode"), osABI, + mDirProvider.GetGREDir(), mAppData->directory, !startupCacheValid); + } + else if (versionOK) { + if (!cachesOK) { + // Remove caches, forcing component re-registration. + // The new list of additional components directories is derived from + // information in "extensions.ini". + startupCacheValid = RemoveComponentRegistries(mProfD, mProfLD, false); + + // Rewrite compatibility.ini to remove the flag + WriteVersion(mProfD, version, osABI, + mDirProvider.GetGREDir(), mAppData->directory, !startupCacheValid); + } + // Nothing need be done for the normal startup case. + } + else { + // Remove caches, forcing component re-registration + // with the default set of components (this disables any potentially + // troublesome incompatible XPCOM components). + startupCacheValid = RemoveComponentRegistries(mProfD, mProfLD, true); + + // Write out version + WriteVersion(mProfD, version, osABI, + mDirProvider.GetGREDir(), mAppData->directory, !startupCacheValid); + } + + if (!startupCacheValid) + StartupCache::IgnoreDiskCache(); + + if (flagFile) { + flagFile->Remove(true); + } + + return 0; +} + +#if defined(MOZ_CRASHREPORTER) +#if defined(MOZ_CONTENT_SANDBOX) && !defined(MOZ_WIDGET_GONK) +void AddSandboxAnnotations() +{ + // Include the sandbox content level, regardless of platform + int level = Preferences::GetInt("security.sandbox.content.level"); + + nsAutoCString levelString; + levelString.AppendInt(level); + + CrashReporter::AnnotateCrashReport( + NS_LITERAL_CSTRING("ContentSandboxLevel"), levelString); + + // Include whether or not this instance is capable of content sandboxing + bool sandboxCapable = false; + +#if defined(XP_WIN) + // All supported Windows versions support some level of content sandboxing + sandboxCapable = true; +#elif defined(XP_MACOSX) + // All supported OS X versions are capable + sandboxCapable = true; +#elif defined(XP_LINUX) + sandboxCapable = SandboxInfo::Get().CanSandboxContent(); +#endif + + CrashReporter::AnnotateCrashReport( + NS_LITERAL_CSTRING("ContentSandboxCapable"), + sandboxCapable ? NS_LITERAL_CSTRING("1") : NS_LITERAL_CSTRING("0")); +} +#endif /* MOZ_CONTENT_SANDBOX && !MOZ_WIDGET_GONK */ +#endif /* MOZ_CRASHREPORTER */ + +/* + * XRE_mainRun - Command line startup, profile migration, and + * the calling of appStartup->Run(). + */ +nsresult +XREMain::XRE_mainRun() +{ + nsresult rv = NS_OK; + NS_ASSERTION(mScopedXPCOM, "Scoped xpcom not initialized."); + +#ifdef NS_FUNCTION_TIMER + // initialize some common services, so we don't pay the cost for these at odd times later on; + // SetWindowCreator -> ChromeRegistry -> IOService -> SocketTransportService -> (nspr wspm init), Prefs + { + nsCOMPtr<nsISupports> comp; + + comp = do_GetService("@mozilla.org/preferences-service;1"); + + comp = do_GetService("@mozilla.org/network/socket-transport-service;1"); + + comp = do_GetService("@mozilla.org/network/dns-service;1"); + + comp = do_GetService("@mozilla.org/network/io-service;1"); + + comp = do_GetService("@mozilla.org/chrome/chrome-registry;1"); + + comp = do_GetService("@mozilla.org/focus-event-suppressor-service;1"); + } +#endif + + rv = mScopedXPCOM->SetWindowCreator(mNativeApp); + NS_ENSURE_SUCCESS(rv, NS_ERROR_FAILURE); + +#ifdef MOZ_CRASHREPORTER + // tell the crash reporter to also send the release channel + nsCOMPtr<nsIPrefService> prefs = do_GetService("@mozilla.org/preferences-service;1", &rv); + if (NS_SUCCEEDED(rv)) { + nsCOMPtr<nsIPrefBranch> defaultPrefBranch; + rv = prefs->GetDefaultBranch(nullptr, getter_AddRefs(defaultPrefBranch)); + + if (NS_SUCCEEDED(rv)) { + nsXPIDLCString sval; + rv = defaultPrefBranch->GetCharPref("app.update.channel", getter_Copies(sval)); + if (NS_SUCCEEDED(rv)) { + CrashReporter::AnnotateCrashReport(NS_LITERAL_CSTRING("ReleaseChannel"), + sval); + } + } + } + // Needs to be set after xpcom initialization. + CrashReporter::AnnotateCrashReport(NS_LITERAL_CSTRING("FramePoisonBase"), + nsPrintfCString("%.16llx", uint64_t(gMozillaPoisonBase))); + CrashReporter::AnnotateCrashReport(NS_LITERAL_CSTRING("FramePoisonSize"), + nsPrintfCString("%lu", uint32_t(gMozillaPoisonSize))); + +#ifdef XP_WIN + PR_CreateThread(PR_USER_THREAD, AnnotateSystemManufacturer_ThreadStart, 0, + PR_PRIORITY_LOW, PR_GLOBAL_THREAD, PR_UNJOINABLE_THREAD, 0); +#endif + +#if defined(XP_LINUX) && !defined(ANDROID) + PR_CreateThread(PR_USER_THREAD, AnnotateLSBRelease, 0, PR_PRIORITY_LOW, + PR_GLOBAL_THREAD, PR_UNJOINABLE_THREAD, 0); +#endif + +#endif + + if (mStartOffline) { + nsCOMPtr<nsIIOService2> io (do_GetService("@mozilla.org/network/io-service;1")); + NS_ENSURE_TRUE(io, NS_ERROR_FAILURE); + io->SetManageOfflineStatus(false); + io->SetOffline(true); + } + + { + nsCOMPtr<nsIObserver> startupNotifier + (do_CreateInstance(NS_APPSTARTUPNOTIFIER_CONTRACTID, &rv)); + NS_ENSURE_SUCCESS(rv, NS_ERROR_FAILURE); + + startupNotifier->Observe(nullptr, APPSTARTUP_TOPIC, nullptr); + } + + nsCOMPtr<nsIAppStartup> appStartup + (do_GetService(NS_APPSTARTUP_CONTRACTID)); + NS_ENSURE_TRUE(appStartup, NS_ERROR_FAILURE); + + if (gDoMigration) { + nsCOMPtr<nsIFile> file; + mDirProvider.GetAppDir()->Clone(getter_AddRefs(file)); + file->AppendNative(NS_LITERAL_CSTRING("override.ini")); + nsINIParser parser; + nsresult rv = parser.Init(file); + // if override.ini doesn't exist, also check for distribution.ini + if (NS_FAILED(rv)) { + bool persistent; + mDirProvider.GetFile(XRE_APP_DISTRIBUTION_DIR, &persistent, + getter_AddRefs(file)); + file->AppendNative(NS_LITERAL_CSTRING("distribution.ini")); + rv = parser.Init(file); + } + if (NS_SUCCEEDED(rv)) { + nsAutoCString buf; + rv = parser.GetString("XRE", "EnableProfileMigrator", buf); + if (NS_SUCCEEDED(rv)) { + if (buf[0] == '0' || buf[0] == 'f' || buf[0] == 'F') { + gDoMigration = false; + } + } + } + } + + { + nsCOMPtr<nsIToolkitProfile> profileBeingReset; + bool profileWasSelected = false; + if (gDoProfileReset) { + if (gResetOldProfileName.IsEmpty()) { + NS_WARNING("Not resetting profile as the profile has no name."); + gDoProfileReset = false; + } else { + rv = mProfileSvc->GetProfileByName(gResetOldProfileName, + getter_AddRefs(profileBeingReset)); + if (NS_FAILED(rv)) { + gDoProfileReset = false; + return NS_ERROR_FAILURE; + } + + nsCOMPtr<nsIToolkitProfile> defaultProfile; + // This can fail if there is no default profile. + // That shouldn't stop reset from proceeding. + nsresult gotSelected = mProfileSvc->GetSelectedProfile(getter_AddRefs(defaultProfile)); + if (NS_SUCCEEDED(gotSelected)) { + profileWasSelected = defaultProfile == profileBeingReset; + } + } + } + + // Profile Migration + if (mAppData->flags & NS_XRE_ENABLE_PROFILE_MIGRATOR && gDoMigration) { + gDoMigration = false; + nsCOMPtr<nsIProfileMigrator> pm(do_CreateInstance(NS_PROFILEMIGRATOR_CONTRACTID)); + if (pm) { + nsAutoCString aKey; + if (gDoProfileReset) { + // Automatically migrate from the current application if we just + // reset the profile. + aKey = MOZ_APP_NAME; + } + pm->Migrate(&mDirProvider, aKey, gResetOldProfileName); + } + } + + if (gDoProfileReset) { + nsresult backupCreated = ProfileResetCleanup(profileBeingReset); + if (NS_FAILED(backupCreated)) NS_WARNING("Could not cleanup the profile that was reset"); + + // Set the new profile as the default after we're done cleaning up the old profile, + // iff that profile was already the default + if (profileWasSelected) { + // this is actually "broken" - see bug 1122124 + rv = SetCurrentProfileAsDefault(mProfileSvc, mProfD); + if (NS_FAILED(rv)) NS_WARNING("Could not set current profile as the default"); + } + // Need to write out the fact that the profile has been removed and potentially + // that the selected/default profile changed. + mProfileSvc->Flush(); + } + } + + mDirProvider.DoStartup(); + + OverrideDefaultLocaleIfNeeded(); + +#ifdef MOZ_CRASHREPORTER + nsCString userAgentLocale; + // Try a localized string first. This pref is always a localized string in + // Fennec, and might be elsewhere, too. + if (NS_SUCCEEDED(Preferences::GetLocalizedCString("general.useragent.locale", &userAgentLocale))) { + CrashReporter::AnnotateCrashReport(NS_LITERAL_CSTRING("useragent_locale"), userAgentLocale); + } else if (NS_SUCCEEDED(Preferences::GetCString("general.useragent.locale", &userAgentLocale))) { + CrashReporter::AnnotateCrashReport(NS_LITERAL_CSTRING("useragent_locale"), userAgentLocale); + } +#endif + + appStartup->GetShuttingDown(&mShuttingDown); + + nsCOMPtr<nsICommandLineRunner> cmdLine; + + nsCOMPtr<nsIFile> workingDir; + rv = NS_GetSpecialDirectory(NS_OS_CURRENT_WORKING_DIR, getter_AddRefs(workingDir)); + NS_ENSURE_SUCCESS(rv, NS_ERROR_FAILURE); + + if (!mShuttingDown) { + cmdLine = do_CreateInstance("@mozilla.org/toolkit/command-line;1"); + NS_ENSURE_TRUE(cmdLine, NS_ERROR_FAILURE); + + rv = cmdLine->Init(gArgc, gArgv, workingDir, + nsICommandLine::STATE_INITIAL_LAUNCH); + NS_ENSURE_SUCCESS(rv, NS_ERROR_FAILURE); + + /* Special-case services that need early access to the command + line. */ + nsCOMPtr<nsIObserverService> obsService = + mozilla::services::GetObserverService(); + if (obsService) { + obsService->NotifyObservers(cmdLine, "command-line-startup", nullptr); + } + } + +#ifdef XP_WIN + // Hack to sync up the various environment storages. XUL_APP_FILE is special + // in that it comes from a different CRT (firefox.exe's static-linked copy). + // Ugly details in http://bugzil.la/1175039#c27 + char appFile[MAX_PATH]; + if (GetEnvironmentVariableA("XUL_APP_FILE", appFile, sizeof(appFile))) { + char* saved = PR_smprintf("XUL_APP_FILE=%s", appFile); + PR_SetEnv(saved); + PR_smprintf_free(saved); + } +#endif + + SaveStateForAppInitiatedRestart(); + + // clear out any environment variables which may have been set + // during the relaunch process now that we know we won't be relaunching. + SaveToEnv("XRE_PROFILE_PATH="); + SaveToEnv("XRE_PROFILE_LOCAL_PATH="); + SaveToEnv("XRE_PROFILE_NAME="); + SaveToEnv("XRE_START_OFFLINE="); + SaveToEnv("NO_EM_RESTART="); + SaveToEnv("XUL_APP_FILE="); + SaveToEnv("XRE_BINARY_PATH="); + + if (!mShuttingDown) { + rv = appStartup->CreateHiddenWindow(); + NS_ENSURE_SUCCESS(rv, NS_ERROR_FAILURE); + +#ifdef MOZ_STYLO + // We initialize Servo here so that the hidden DOM window is available, + // since initializing Servo calls style struct constructors, and the + // HackilyFindDeviceContext stuff we have right now depends on the hidden + // DOM window. When we fix that, this should move back to + // nsLayoutStatics.cpp + Servo_Initialize(); +#endif + +#if defined(HAVE_DESKTOP_STARTUP_ID) && defined(MOZ_WIDGET_GTK) + nsGTKToolkit* toolkit = nsGTKToolkit::GetToolkit(); + if (toolkit && !mDesktopStartupID.IsEmpty()) { + toolkit->SetDesktopStartupID(mDesktopStartupID); + } + // Clear the environment variable so it won't be inherited by + // child processes and confuse things. + g_unsetenv ("DESKTOP_STARTUP_ID"); +#endif + +#ifdef XP_MACOSX + // we re-initialize the command-line service and do appleevents munging + // after we are sure that we're not restarting + cmdLine = do_CreateInstance("@mozilla.org/toolkit/command-line;1"); + NS_ENSURE_TRUE(cmdLine, NS_ERROR_FAILURE); + + CommandLineServiceMac::SetupMacCommandLine(gArgc, gArgv, false); + + rv = cmdLine->Init(gArgc, gArgv, + workingDir, nsICommandLine::STATE_INITIAL_LAUNCH); + NS_ENSURE_SUCCESS(rv, NS_ERROR_FAILURE); +#endif + + nsCOMPtr<nsIObserverService> obsService = + mozilla::services::GetObserverService(); + if (obsService) + obsService->NotifyObservers(nullptr, "final-ui-startup", nullptr); + + (void)appStartup->DoneStartingUp(); + +#ifdef MOZ_CRASHREPORTER + CrashReporter::AnnotateCrashReport(NS_LITERAL_CSTRING("StartupCrash"), + NS_LITERAL_CSTRING("0")); +#endif + + appStartup->GetShuttingDown(&mShuttingDown); + } + + if (!mShuttingDown) { + rv = cmdLine->Run(); + NS_ENSURE_SUCCESS_LOG(rv, NS_ERROR_FAILURE); + + appStartup->GetShuttingDown(&mShuttingDown); + } + + if (!mShuttingDown) { +#ifdef MOZ_ENABLE_XREMOTE + // if we have X remote support, start listening for requests on the + // proxy window. + if (!mDisableRemote) + mRemoteService = do_GetService("@mozilla.org/toolkit/remote-service;1"); + if (mRemoteService) + mRemoteService->Startup(mAppData->remotingName, mProfileName.get()); + if (mRemoteLockDir) { + mRemoteLock.Unlock(); + mRemoteLockDir->Remove(false); + } +#endif /* MOZ_ENABLE_XREMOTE */ + + mNativeApp->Enable(); + } + +#ifdef MOZ_INSTRUMENT_EVENT_LOOP + if (PR_GetEnv("MOZ_INSTRUMENT_EVENT_LOOP")) { + bool logToConsole = true; + mozilla::InitEventTracing(logToConsole); + } +#endif /* MOZ_INSTRUMENT_EVENT_LOOP */ + +#if defined(MOZ_SANDBOX) && defined(XP_LINUX) && !defined(MOZ_WIDGET_GONK) + // If we're on Linux, we now have information about the OS capabilities + // available to us. + SandboxInfo sandboxInfo = SandboxInfo::Get(); + Telemetry::Accumulate(Telemetry::SANDBOX_HAS_SECCOMP_BPF, + sandboxInfo.Test(SandboxInfo::kHasSeccompBPF)); + Telemetry::Accumulate(Telemetry::SANDBOX_HAS_SECCOMP_TSYNC, + sandboxInfo.Test(SandboxInfo::kHasSeccompTSync)); + Telemetry::Accumulate(Telemetry::SANDBOX_HAS_USER_NAMESPACES_PRIVILEGED, + sandboxInfo.Test(SandboxInfo::kHasPrivilegedUserNamespaces)); + Telemetry::Accumulate(Telemetry::SANDBOX_HAS_USER_NAMESPACES, + sandboxInfo.Test(SandboxInfo::kHasUserNamespaces)); + Telemetry::Accumulate(Telemetry::SANDBOX_CONTENT_ENABLED, + sandboxInfo.Test(SandboxInfo::kEnabledForContent)); + Telemetry::Accumulate(Telemetry::SANDBOX_MEDIA_ENABLED, + sandboxInfo.Test(SandboxInfo::kEnabledForMedia)); +#if defined(MOZ_CRASHREPORTER) + nsAutoCString flagsString; + flagsString.AppendInt(sandboxInfo.AsInteger()); + + CrashReporter::AnnotateCrashReport( + NS_LITERAL_CSTRING("ContentSandboxCapabilities"), flagsString); +#endif /* MOZ_CRASHREPORTER */ +#endif /* MOZ_SANDBOX && XP_LINUX && !MOZ_WIDGET_GONK */ + +#if defined(MOZ_CRASHREPORTER) +#if defined(MOZ_CONTENT_SANDBOX) && !defined(MOZ_WIDGET_GONK) + AddSandboxAnnotations(); +#endif /* MOZ_CONTENT_SANDBOX && !MOZ_WIDGET_GONK */ +#endif /* MOZ_CRASHREPORTER */ + + { + rv = appStartup->Run(); + if (NS_FAILED(rv)) { + NS_ERROR("failed to run appstartup"); + gLogConsoleErrors = true; + } + } + +#ifdef MOZ_STYLO + // This, along with the call to Servo_Initialize, should eventually move back + // to nsLayoutStatics.cpp. + Servo_Shutdown(); +#endif + + return rv; +} + +#if MOZ_WIDGET_GTK == 2 +void XRE_GlibInit() +{ + static bool ran_once = false; + + // glib < 2.24 doesn't want g_thread_init to be invoked twice, so ensure + // we only do it once. No need for thread safety here, since this is invoked + // well before any thread is spawned. + if (!ran_once) { + // glib version < 2.36 doesn't initialize g_slice in a static initializer. + // Ensure this happens through g_thread_init (glib version < 2.32) or + // g_type_init (2.32 <= gLib version < 2.36)." + g_thread_init(nullptr); + g_type_init(); + ran_once = true; + } +} +#endif + +// Separate stub function to let us specifically suppress it in Valgrind +void +XRE_CreateStatsObject() +{ + // Initialize global variables used by histogram collection + // machinery that is used by by Telemetry. Note: is never de-initialised. + Telemetry::CreateStatisticsRecorder(); +} + +/* + * XRE_main - A class based main entry point used by most platforms. + * Note that on OSX, aAppData->xreDirectory will point to + * .app/Contents/Resources. + */ +int +XREMain::XRE_main(int argc, char* argv[], const nsXREAppData* aAppData) +{ + ScopedLogging log; + + // NB: this must happen after the creation of |ScopedLogging log| since + // ScopedLogging::ScopedLogging calls NS_LogInit, and + // XRE_CreateStatsObject calls Telemetry::CreateStatisticsRecorder, + // and NS_LogInit must be called before Telemetry::CreateStatisticsRecorder. + // NS_LogInit must be called before Telemetry::CreateStatisticsRecorder + // so as to avoid many log messages of the form + // WARNING: XPCOM objects created/destroyed from static ctor/dtor: [..] + // See bug 1279614. + XRE_CreateStatsObject(); + +#if defined(MOZ_SANDBOX) && defined(XP_LINUX) && !defined(ANDROID) + SandboxInfo::ThreadingCheck(); +#endif + + char aLocal; + GeckoProfilerInitRAII profilerGuard(&aLocal); + + PROFILER_LABEL("Startup", "XRE_Main", + js::ProfileEntry::Category::OTHER); + + nsresult rv = NS_OK; + + gArgc = argc; + gArgv = argv; + + NS_ENSURE_TRUE(aAppData, 2); + + mAppData = new ScopedAppData(aAppData); + if (!mAppData) + return 1; + if (!mAppData->remotingName) { + SetAllocatedString(mAppData->remotingName, mAppData->name); + } + // used throughout this file + gAppData = mAppData; + + nsCOMPtr<nsIFile> binFile; + rv = XRE_GetBinaryPath(argv[0], getter_AddRefs(binFile)); + NS_ENSURE_SUCCESS(rv, 1); + + rv = binFile->GetPath(gAbsoluteArgv0Path); + NS_ENSURE_SUCCESS(rv, 1); + + mozilla::IOInterposerInit ioInterposerGuard; + +#if defined(XP_WIN) + // Some COM settings are global to the process and must be set before any non- + // trivial COM is run in the application. Since these settings may affect + // stability, we should instantiate COM ASAP so that we can ensure that these + // global settings are configured before anything can interfere. + mozilla::mscom::MainThreadRuntime msCOMRuntime; +#endif + +#if MOZ_WIDGET_GTK == 2 + XRE_GlibInit(); +#endif + + // init + bool exit = false; + int result = XRE_mainInit(&exit); + if (result != 0 || exit) + return result; + + // startup + result = XRE_mainStartup(&exit); + if (result != 0 || exit) + return result; + + bool appInitiatedRestart = false; + + // Start the real application + mScopedXPCOM = MakeUnique<ScopedXPCOMStartup>(); + if (!mScopedXPCOM) + return 1; + + rv = mScopedXPCOM->Initialize(); + NS_ENSURE_SUCCESS(rv, 1); + + // run! + rv = XRE_mainRun(); + +#ifdef MOZ_INSTRUMENT_EVENT_LOOP + mozilla::ShutdownEventTracing(); +#endif + + gAbsoluteArgv0Path.Truncate(); + + // Check for an application initiated restart. This is one that + // corresponds to nsIAppStartup.quit(eRestart) + if (rv == NS_SUCCESS_RESTART_APP + || rv == NS_SUCCESS_RESTART_APP_NOT_SAME_PROFILE) { + appInitiatedRestart = true; + + // We have an application restart don't do any shutdown checks here + // In particular we don't want to poison IO for checking late-writes. + gShutdownChecks = SCM_NOTHING; + } + + if (!mShuttingDown) { +#ifdef MOZ_ENABLE_XREMOTE + // shut down the x remote proxy window + if (mRemoteService) { + mRemoteService->Shutdown(); + } +#endif /* MOZ_ENABLE_XREMOTE */ + } + + mScopedXPCOM = nullptr; + +#if defined(XP_WIN) + mozilla::widget::StopAudioSession(); +#endif + + // unlock the profile after ScopedXPCOMStartup object (xpcom) + // has gone out of scope. see bug #386739 for more details + mProfileLock->Unlock(); + gProfileLock = nullptr; + + // Restart the app after XPCOM has been shut down cleanly. + if (appInitiatedRestart) { + RestoreStateForAppInitiatedRestart(); + + if (rv != NS_SUCCESS_RESTART_APP_NOT_SAME_PROFILE) { + // Ensure that these environment variables are set: + SaveFileToEnvIfUnset("XRE_PROFILE_PATH", mProfD); + SaveFileToEnvIfUnset("XRE_PROFILE_LOCAL_PATH", mProfLD); + SaveWordToEnvIfUnset("XRE_PROFILE_NAME", mProfileName); + } + +#ifdef MOZ_WIDGET_GTK + MOZ_gdk_display_close(mGdkDisplay); +#endif + + { + rv = LaunchChild(mNativeApp, true); + } + +#ifdef MOZ_CRASHREPORTER + if (mAppData->flags & NS_XRE_ENABLE_CRASH_REPORTER) + CrashReporter::UnsetExceptionHandler(); +#endif + return rv == NS_ERROR_LAUNCHED_CHILD_PROCESS ? 0 : 1; + } + +#ifdef MOZ_WIDGET_GTK + // gdk_display_close also calls gdk_display_manager_set_default_display + // appropriately when necessary. + MOZ_gdk_display_close(mGdkDisplay); +#endif + +#ifdef MOZ_CRASHREPORTER + if (mAppData->flags & NS_XRE_ENABLE_CRASH_REPORTER) + CrashReporter::UnsetExceptionHandler(); +#endif + + XRE_DeinitCommandLine(); + + return NS_FAILED(rv) ? 1 : 0; +} + +void +XRE_StopLateWriteChecks(void) { + mozilla::StopLateWriteChecks(); +} + +int +XRE_main(int argc, char* argv[], const nsXREAppData* aAppData, uint32_t aFlags) +{ + XREMain main; + + int result = main.XRE_main(argc, argv, aAppData); + mozilla::RecordShutdownEndTimeStamp(); + return result; +} + +nsresult +XRE_InitCommandLine(int aArgc, char* aArgv[]) +{ + nsresult rv = NS_OK; + +#if defined(OS_WIN) + CommandLine::Init(aArgc, aArgv); +#else + + // these leak on error, but that's OK: we'll just exit() + char** canonArgs = new char*[aArgc]; + + // get the canonical version of the binary's path + nsCOMPtr<nsIFile> binFile; + rv = XRE_GetBinaryPath(aArgv[0], getter_AddRefs(binFile)); + if (NS_FAILED(rv)) + return NS_ERROR_FAILURE; + + nsAutoCString canonBinPath; + rv = binFile->GetNativePath(canonBinPath); + if (NS_FAILED(rv)) + return NS_ERROR_FAILURE; + + canonArgs[0] = strdup(canonBinPath.get()); + + for (int i = 1; i < aArgc; ++i) { + if (aArgv[i]) { + canonArgs[i] = strdup(aArgv[i]); + } + } + + NS_ASSERTION(!CommandLine::IsInitialized(), "Bad news!"); + CommandLine::Init(aArgc, canonArgs); + + for (int i = 0; i < aArgc; ++i) + free(canonArgs[i]); + delete[] canonArgs; +#endif + + const char *path = nullptr; + ArgResult ar = CheckArg("greomni", false, &path); + if (ar == ARG_BAD) { + PR_fprintf(PR_STDERR, "Error: argument --greomni requires a path argument\n"); + return NS_ERROR_FAILURE; + } + + if (!path) + return rv; + + nsCOMPtr<nsIFile> greOmni; + rv = XRE_GetFileFromPath(path, getter_AddRefs(greOmni)); + if (NS_FAILED(rv)) { + PR_fprintf(PR_STDERR, "Error: argument --greomni requires a valid path\n"); + return rv; + } + + ar = CheckArg("appomni", false, &path); + if (ar == ARG_BAD) { + PR_fprintf(PR_STDERR, "Error: argument --appomni requires a path argument\n"); + return NS_ERROR_FAILURE; + } + + nsCOMPtr<nsIFile> appOmni; + if (path) { + rv = XRE_GetFileFromPath(path, getter_AddRefs(appOmni)); + if (NS_FAILED(rv)) { + PR_fprintf(PR_STDERR, "Error: argument --appomni requires a valid path\n"); + return rv; + } + } + + mozilla::Omnijar::Init(greOmni, appOmni); + return rv; +} + +nsresult +XRE_DeinitCommandLine() +{ + nsresult rv = NS_OK; + + CommandLine::Terminate(); + + return rv; +} + +GeckoProcessType +XRE_GetProcessType() +{ + return mozilla::startup::sChildProcessType; +} + +bool +XRE_IsGPUProcess() +{ + return XRE_GetProcessType() == GeckoProcessType_GPU; +} + +bool +XRE_IsParentProcess() +{ + return XRE_GetProcessType() == GeckoProcessType_Default; +} + +bool +XRE_IsContentProcess() +{ + return XRE_GetProcessType() == GeckoProcessType_Content; +} + +// If you add anything to this enum, please update about:support to reflect it +enum { + kE10sEnabledByUser = 0, + kE10sEnabledByDefault = 1, + kE10sDisabledByUser = 2, + // kE10sDisabledInSafeMode = 3, was removed in bug 1172491. + kE10sDisabledForAccessibility = 4, + // kE10sDisabledForMacGfx = 5, was removed in bug 1068674. + // kE10sDisabledForBidi = 6, removed in bug 1309599 + kE10sDisabledForAddons = 7, + kE10sForceDisabled = 8, + // kE10sDisabledForXPAcceleration = 9, removed in bug 1296353 + kE10sDisabledForOperatingSystem = 10, +}; + +const char* kAccessibilityLastRunDatePref = "accessibility.lastLoadDate"; +const char* kAccessibilityLoadedLastSessionPref = "accessibility.loadedInLastSession"; + +#if defined(XP_WIN) +static inline uint32_t +PRTimeToSeconds(PRTime t_usec) +{ + PRTime usec_per_sec = PR_USEC_PER_SEC; + return uint32_t(t_usec /= usec_per_sec); +} +#endif + +const char* kForceEnableE10sPref = "browser.tabs.remote.force-enable"; +const char* kForceDisableE10sPref = "browser.tabs.remote.force-disable"; + +uint32_t +MultiprocessBlockPolicy() { + if (gMultiprocessBlockPolicyInitialized) { + return gMultiprocessBlockPolicy; + } + gMultiprocessBlockPolicyInitialized = true; + + /** + * Avoids enabling e10s if there are add-ons installed. + */ + bool addonsCanDisable = Preferences::GetBool("extensions.e10sBlocksEnabling", false); + bool disabledByAddons = Preferences::GetBool("extensions.e10sBlockedByAddons", false); + +#ifdef MOZ_CRASHREPORTER + CrashReporter::AnnotateCrashReport(NS_LITERAL_CSTRING("AddonsShouldHaveBlockedE10s"), + disabledByAddons ? NS_LITERAL_CSTRING("1") + : NS_LITERAL_CSTRING("0")); +#endif + + if (addonsCanDisable && disabledByAddons) { + gMultiprocessBlockPolicy = kE10sDisabledForAddons; + return gMultiprocessBlockPolicy; + } + +#if defined(XP_WIN) + bool disabledForA11y = false; + /** + * Avoids enabling e10s if accessibility has recently loaded. Performs the + * following checks: + * 1) Checks a pref indicating if a11y loaded in the last session. This pref + * is set in nsBrowserGlue.js. If a11y was loaded in the last session we + * do not enable e10s in this session. + * 2) Accessibility stores a last run date (PR_IntervalNow) when it is + * initialized (see nsBaseWidget.cpp). We check if this pref exists and + * compare it to now. If a11y hasn't run in an extended period of time or + * if the date pref does not exist we load e10s. + */ + disabledForA11y = Preferences::GetBool(kAccessibilityLoadedLastSessionPref, false); + if (!disabledForA11y && + Preferences::HasUserValue(kAccessibilityLastRunDatePref)) { + #define ONE_WEEK_IN_SECONDS (60*60*24*7) + uint32_t a11yRunDate = Preferences::GetInt(kAccessibilityLastRunDatePref, 0); + MOZ_ASSERT(0 != a11yRunDate); + // If a11y hasn't run for a period of time, clear the pref and load e10s + uint32_t now = PRTimeToSeconds(PR_Now()); + uint32_t difference = now - a11yRunDate; + if (difference > ONE_WEEK_IN_SECONDS || !a11yRunDate) { + Preferences::ClearUser(kAccessibilityLastRunDatePref); + } else { + disabledForA11y = true; + } + } + + if (disabledForA11y) { + gMultiprocessBlockPolicy = kE10sDisabledForAccessibility; + return gMultiprocessBlockPolicy; + } +#endif + + /** + * Avoids enabling e10s for Windows XP users on the release channel. + */ +#if defined(XP_WIN) + if (!IsVistaOrLater()) { + nsAdoptingCString channelName = Preferences::GetDefaultCString("app.update.channel"); + if (channelName.EqualsLiteral("release") || channelName.EqualsLiteral("esr")) { + gMultiprocessBlockPolicy = kE10sDisabledForOperatingSystem; + return gMultiprocessBlockPolicy; + } + } +#endif // XP_WIN + + /* + * None of the blocking policies matched, so e10s is allowed to run. + * Cache the information and return 0, indicating success. + */ + gMultiprocessBlockPolicy = 0; + return 0; +} + +bool +mozilla::BrowserTabsRemoteAutostart() +{ + if (gBrowserTabsRemoteAutostartInitialized) { + return gBrowserTabsRemoteAutostart; + } + gBrowserTabsRemoteAutostartInitialized = true; + + // If we're in the content process, we are running E10S. + if (XRE_IsContentProcess()) { + gBrowserTabsRemoteAutostart = true; + return gBrowserTabsRemoteAutostart; + } + + bool optInPref = Preferences::GetBool("browser.tabs.remote.autostart", false); + bool trialPref = Preferences::GetBool("browser.tabs.remote.autostart.2", false); + bool prefEnabled = optInPref || trialPref; + int status; + if (optInPref) { + status = kE10sEnabledByUser; + } else if (trialPref) { + status = kE10sEnabledByDefault; + } else { + status = kE10sDisabledByUser; + } + + if (prefEnabled) { + uint32_t blockPolicy = MultiprocessBlockPolicy(); + if (blockPolicy != 0) { + status = blockPolicy; + } else { + gBrowserTabsRemoteAutostart = true; + } + } + + // Uber override pref for manual testing purposes + if (Preferences::GetBool(kForceEnableE10sPref, false)) { + gBrowserTabsRemoteAutostart = true; + prefEnabled = true; + status = kE10sEnabledByUser; + } + + // Uber override pref for emergency blocking + if (gBrowserTabsRemoteAutostart && + (Preferences::GetBool(kForceDisableE10sPref, false) || + EnvHasValue("MOZ_FORCE_DISABLE_E10S"))) { + gBrowserTabsRemoteAutostart = false; + status = kE10sForceDisabled; + } + + gBrowserTabsRemoteStatus = status; + + mozilla::Telemetry::Accumulate(mozilla::Telemetry::E10S_STATUS, status); + if (prefEnabled) { + mozilla::Telemetry::Accumulate(mozilla::Telemetry::E10S_BLOCKED_FROM_RUNNING, + !gBrowserTabsRemoteAutostart); + } + return gBrowserTabsRemoteAutostart; +} + +void +SetupErrorHandling(const char* progname) +{ +#ifdef XP_WIN + /* On Windows XPSP3 and Windows Vista if DEP is configured off-by-default + we still want DEP protection: enable it explicitly and programmatically. + + This function is not available on WinXPSP2 so we dynamically load it. + */ + + HMODULE kernel32 = GetModuleHandleW(L"kernel32.dll"); + SetProcessDEPPolicyFunc _SetProcessDEPPolicy = + (SetProcessDEPPolicyFunc) GetProcAddress(kernel32, "SetProcessDEPPolicy"); + if (_SetProcessDEPPolicy) + _SetProcessDEPPolicy(PROCESS_DEP_ENABLE); +#endif + +#ifdef XP_WIN32 + // Suppress the "DLL Foo could not be found" dialog, such that if dependent + // libraries (such as GDI+) are not preset, we gracefully fail to load those + // XPCOM components, instead of being ungraceful. + UINT realMode = SetErrorMode(0); + realMode |= SEM_FAILCRITICALERRORS; + // If XRE_NO_WINDOWS_CRASH_DIALOG is set, suppress displaying the "This + // application has crashed" dialog box. This is mainly useful for + // automated testing environments, e.g. tinderbox, where there's no need + // for a dozen of the dialog boxes to litter the console + if (getenv("XRE_NO_WINDOWS_CRASH_DIALOG")) + realMode |= SEM_NOGPFAULTERRORBOX | SEM_NOOPENFILEERRORBOX; + + SetErrorMode(realMode); + +#endif + +#if defined (DEBUG) && defined(XP_WIN) + // Send MSCRT Warnings, Errors and Assertions to stderr. + // See http://msdn.microsoft.com/en-us/library/1y71x448(v=VS.80).aspx + // and http://msdn.microsoft.com/en-us/library/a68f826y(v=VS.80).aspx. + + _CrtSetReportMode(_CRT_WARN, _CRTDBG_MODE_FILE); + _CrtSetReportFile(_CRT_WARN, _CRTDBG_FILE_STDERR); + _CrtSetReportMode(_CRT_ERROR, _CRTDBG_MODE_FILE); + _CrtSetReportFile(_CRT_ERROR, _CRTDBG_FILE_STDERR); + _CrtSetReportMode(_CRT_ASSERT, _CRTDBG_MODE_FILE); + _CrtSetReportFile(_CRT_ASSERT, _CRTDBG_FILE_STDERR); + + _CrtSetReportHook(MSCRTReportHook); +#endif + + InstallSignalHandlers(progname); + + // Unbuffer stdout, needed for tinderbox tests. + setbuf(stdout, 0); +} + +void +OverrideDefaultLocaleIfNeeded() { + // Read pref to decide whether to override default locale with US English. + if (mozilla::Preferences::GetBool("javascript.use_us_english_locale", false)) { + // Set the application-wide C-locale. Needed to resist fingerprinting + // of Date.toLocaleFormat(). We use the locale to "C.UTF-8" if possible, + // to avoid interfering with non-ASCII keyboard input on some Linux desktops. + // Otherwise fall back to the "C" locale, which is available on all platforms. + setlocale(LC_ALL, "C.UTF-8") || setlocale(LC_ALL, "C"); + } +} + +void +XRE_EnableSameExecutableForContentProc() { + if (!PR_GetEnv("MOZ_SEPARATE_CHILD_PROCESS")) { + mozilla::ipc::GeckoChildProcessHost::EnableSameExecutableForContentProc(); + } +} diff --git a/toolkit/xre/nsAppRunner.h b/toolkit/xre/nsAppRunner.h new file mode 100644 index 0000000000..b8d9553190 --- /dev/null +++ b/toolkit/xre/nsAppRunner.h @@ -0,0 +1,138 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef nsAppRunner_h__ +#define nsAppRunner_h__ + +#ifdef XP_WIN +#include <windows.h> +#else +#include <limits.h> +#endif + +#ifndef MAXPATHLEN +#ifdef PATH_MAX +#define MAXPATHLEN PATH_MAX +#elif defined(_MAX_PATH) +#define MAXPATHLEN _MAX_PATH +#elif defined(CCHMAXPATH) +#define MAXPATHLEN CCHMAXPATH +#else +#define MAXPATHLEN 1024 +#endif +#endif + +#include "nsXULAppAPI.h" + +// This directory service key is a lot like NS_APP_LOCALSTORE_50_FILE, +// but it is always the "main" localstore file, even when we're in safe mode +// and we load localstore from somewhere else. +#define NS_LOCALSTORE_UNSAFE_FILE "LStoreS" + +class nsINativeAppSupport; +class nsXREDirProvider; +class nsIToolkitProfileService; +class nsIFile; +class nsIProfileLock; +class nsIProfileUnlocker; +class nsIFactory; +class nsString; + +extern nsXREDirProvider* gDirServiceProvider; + +// NOTE: gAppData will be null in embedded contexts. The "size" parameter +// will be the size of the original structure passed to XRE_main, but the +// structure will have all of the members available. +extern const nsXREAppData* gAppData; +extern bool gSafeMode; + +extern int gArgc; +extern char **gArgv; +extern int gRestartArgc; +extern char **gRestartArgv; +extern bool gLogConsoleErrors; +extern nsString gAbsoluteArgv0Path; + +extern bool gIsGtest; + +/** + * Create the nativeappsupport implementation. + * + * @note XPCOMInit has not happened yet. + */ +nsresult NS_CreateNativeAppSupport(nsINativeAppSupport* *aResult); + +nsresult +NS_NewToolkitProfileService(nsIToolkitProfileService* *aResult); + +nsresult +NS_NewToolkitProfileFactory(nsIFactory* *aResult); + +/** + * Try to acquire exclusive access to the specified profile directory. + * + * @param aPath + * The profile directory to lock. + * @param aTempPath + * The corresponding profile temporary directory. + * @param aUnlocker + * A callback interface used to attempt to unlock a profile that + * appears to be locked. + * @param aResult + * The resulting profile lock object (or null if the profile could + * not be locked). + * + * @return NS_ERROR_FILE_ACCESS_DENIED to indicate that the profile + * directory cannot be unlocked. + */ +nsresult +NS_LockProfilePath(nsIFile* aPath, nsIFile* aTempPath, + nsIProfileUnlocker* *aUnlocker, nsIProfileLock* *aResult); + +void +WriteConsoleLog(); + +void +OverrideDefaultLocaleIfNeeded(); + +/** + * Allow exit() calls to complete. This should be done from a proper Gecko + * shutdown path. Otherwise we aim to catch improper shutdowns. + */ +void +MozExpectedExit(); + +#ifdef XP_WIN +void +UseParentConsole(); + +BOOL +WinLaunchChild(const wchar_t *exePath, int argc, + char **argv, HANDLE userToken = nullptr, + HANDLE *hProcess = nullptr); +#endif + +#define NS_NATIVEAPPSUPPORT_CONTRACTID "@mozilla.org/toolkit/native-app-support;1" + +namespace mozilla { +namespace startup { +extern GeckoProcessType sChildProcessType; +} // namespace startup +} // namespace mozilla + +/** + * Set up platform specific error handling such as suppressing DLL load dialog + * and the JIT debugger on Windows, and install unix signal handlers. + */ +void SetupErrorHandling(const char* progname); + +/** + * A numeric value indicating whether multiprocess might be blocked. + * Possible values can be found at nsAppRunner.cpp. A value of 0 + * represents not blocking. + */ +uint32_t MultiprocessBlockPolicy(); + +#endif // nsAppRunner_h__ diff --git a/toolkit/xre/nsCommandLineServiceMac.cpp b/toolkit/xre/nsCommandLineServiceMac.cpp new file mode 100644 index 0000000000..f5964bfe32 --- /dev/null +++ b/toolkit/xre/nsCommandLineServiceMac.cpp @@ -0,0 +1,102 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "nsCommandLineServiceMac.h" +#include "MacApplicationDelegate.h" + +#include <CoreFoundation/CoreFoundation.h> +#include <Carbon/Carbon.h> + +namespace CommandLineServiceMac { + +static const int kArgsGrowSize = 20; + +static char** sArgs = nullptr; +static int sArgsAllocated = 0; +static int sArgsUsed = 0; + +static bool sBuildingCommandLine = false; + +void AddToCommandLine(const char* inArgText) +{ + if (sArgsUsed >= sArgsAllocated - 1) { + // realloc does not free the given pointer if allocation fails + char **temp = static_cast<char**>(realloc(sArgs, (sArgsAllocated + kArgsGrowSize) * sizeof(char*))); + if (!temp) + return; + sArgs = temp; + sArgsAllocated += kArgsGrowSize; + } + + char *temp2 = strdup(inArgText); + if (!temp2) + return; + + sArgs[sArgsUsed++] = temp2; + sArgs[sArgsUsed] = nullptr; + + return; +} + +void SetupMacCommandLine(int& argc, char**& argv, bool forRestart) +{ + sArgs = static_cast<char **>(malloc(kArgsGrowSize * sizeof(char*))); + if (!sArgs) + return; + sArgsAllocated = kArgsGrowSize; + sArgs[0] = nullptr; + sArgsUsed = 0; + + sBuildingCommandLine = true; + + // Copy args, stripping anything we don't want. + for (int arg = 0; arg < argc; arg++) { + char* flag = argv[arg]; + // Don't pass on the psn (Process Serial Number) flag from the OS, or + // the "-foreground" flag since it will be set below if necessary. + if (strncmp(flag, "-psn_", 5) != 0 && + strncmp(flag, "-foreground", 11) != 0) + AddToCommandLine(flag); + } + + // Force processing of any pending Apple GetURL Events while we're building + // the command line. The handlers will append to the command line rather than + // act directly so there is no chance we'll process them during a XUL window + // load and accidentally open unnecessary windows and home pages. + ProcessPendingGetURLAppleEvents(); + + // If the process will be relaunched, the child should be in the foreground + // if the parent is in the foreground. This will be communicated in a + // command-line argument to the child. + if (forRestart) { + Boolean isForeground = false; + ProcessSerialNumber psnSelf, psnFront; + if (::GetCurrentProcess(&psnSelf) == noErr && + ::GetFrontProcess(&psnFront) == noErr && + ::SameProcess(&psnSelf, &psnFront, &isForeground) == noErr && + isForeground) { + AddToCommandLine("-foreground"); + } + } + + sBuildingCommandLine = false; + + argc = sArgsUsed; + argv = sArgs; +} + +bool AddURLToCurrentCommandLine(const char* aURL) +{ + if (!sBuildingCommandLine) { + return false; + } + + AddToCommandLine("-url"); + AddToCommandLine(aURL); + + return true; +} + +} // namespace CommandLineServiceMac diff --git a/toolkit/xre/nsCommandLineServiceMac.h b/toolkit/xre/nsCommandLineServiceMac.h new file mode 100644 index 0000000000..f21596856e --- /dev/null +++ b/toolkit/xre/nsCommandLineServiceMac.h @@ -0,0 +1,20 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef nsCommandLineServiceMac_h_ +#define nsCommandLineServiceMac_h_ + +#include "nscore.h" + +namespace CommandLineServiceMac { + void SetupMacCommandLine(int& argc, char**& argv, bool forRestart); + + // Add a URL to the command line currently being set up via + // SetupMacCommandLine. Returns false if no command line is + // being set up or the addition fails for any other reason. + bool AddURLToCurrentCommandLine(const char* aURL); +} // namespace CommandLineServiceMac + +#endif // nsCommandLineServiceMac_h_ diff --git a/toolkit/xre/nsConsoleWriter.cpp b/toolkit/xre/nsConsoleWriter.cpp new file mode 100644 index 0000000000..952e88003f --- /dev/null +++ b/toolkit/xre/nsConsoleWriter.cpp @@ -0,0 +1,95 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "nsAppRunner.h" + +#include "prio.h" +#include "prprf.h" +#include "prenv.h" + +#include "nsCRT.h" +#include "nsNativeCharsetUtils.h" +#include "nsString.h" +#include "nsXREDirProvider.h" +#include "nsXULAppAPI.h" + +#include "nsIConsoleService.h" +#include "nsIConsoleMessage.h" + +void +WriteConsoleLog() +{ + nsresult rv; + + nsCOMPtr<nsIFile> lfile; + + char* logFileEnv = PR_GetEnv("XRE_CONSOLE_LOG"); + if (logFileEnv && *logFileEnv) { + rv = XRE_GetFileFromPath(logFileEnv, getter_AddRefs(lfile)); + if (NS_FAILED(rv)) + return; + } + else { + if (!gLogConsoleErrors) + return; + + rv = gDirServiceProvider->GetUserAppDataDirectory(getter_AddRefs(lfile)); + if (NS_FAILED(rv)) + return; + + lfile->AppendNative(NS_LITERAL_CSTRING("console.log")); + } + + PRFileDesc *file; + rv = lfile->OpenNSPRFileDesc(PR_WRONLY | PR_APPEND | PR_CREATE_FILE, + 0660, &file); + if (NS_FAILED(rv)) + return; + + nsCOMPtr<nsIConsoleService> csrv + (do_GetService(NS_CONSOLESERVICE_CONTRACTID)); + if (!csrv) { + PR_Close(file); + return; + } + + nsIConsoleMessage** messages; + uint32_t mcount; + + rv = csrv->GetMessageArray(&mcount, &messages); + if (NS_FAILED(rv)) { + PR_Close(file); + return; + } + + if (mcount) { + PRExplodedTime etime; + PR_ExplodeTime(PR_Now(), PR_LocalTimeParameters, &etime); + char datetime[512]; + PR_FormatTimeUSEnglish(datetime, sizeof(datetime), + "%Y-%m-%d %H:%M:%S", &etime); + + PR_fprintf(file, NS_LINEBREAK + "*** Console log: %s ***" NS_LINEBREAK, + datetime); + } + + // From this point on, we have to release all the messages, and free + // the memory allocated for the messages array. XPCOM arrays suck. + + nsXPIDLString msg; + nsAutoCString nativemsg; + + for (uint32_t i = 0; i < mcount; ++i) { + rv = messages[i]->GetMessageMoz(getter_Copies(msg)); + if (NS_SUCCEEDED(rv)) { + NS_CopyUnicodeToNative(msg, nativemsg); + PR_fprintf(file, "%s" NS_LINEBREAK, nativemsg.get()); + } + NS_IF_RELEASE(messages[i]); + } + + PR_Close(file); + free(messages); +} diff --git a/toolkit/xre/nsEmbedFunctions.cpp b/toolkit/xre/nsEmbedFunctions.cpp new file mode 100644 index 0000000000..0e85532b9d --- /dev/null +++ b/toolkit/xre/nsEmbedFunctions.cpp @@ -0,0 +1,966 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "mozilla/DebugOnly.h" + +#include "base/basictypes.h" + +#include "nsXULAppAPI.h" + +#include <stdlib.h> +#if defined(MOZ_WIDGET_GTK) +#include <glib.h> +#endif + +#include "prenv.h" + +#include "nsIAppShell.h" +#include "nsIAppStartupNotifier.h" +#include "nsIDirectoryService.h" +#include "nsIFile.h" +#include "nsIToolkitChromeRegistry.h" +#include "nsIToolkitProfile.h" + +#ifdef XP_WIN +#include <process.h> +#include "mozilla/ipc/WindowsMessageLoop.h" +#endif + +#include "nsAppDirectoryServiceDefs.h" +#include "nsAppRunner.h" +#include "nsAutoRef.h" +#include "nsDirectoryServiceDefs.h" +#include "nsExceptionHandler.h" +#include "nsString.h" +#include "nsThreadUtils.h" +#include "nsJSUtils.h" +#include "nsWidgetsCID.h" +#include "nsXREDirProvider.h" + +#include "mozilla/Omnijar.h" +#if defined(XP_MACOSX) +#include "nsVersionComparator.h" +#include "chrome/common/mach_ipc_mac.h" +#endif +#include "nsX11ErrorHandler.h" +#include "nsGDKErrorHandler.h" +#include "base/at_exit.h" +#include "base/command_line.h" +#include "base/message_loop.h" +#include "base/process_util.h" +#include "chrome/common/child_process.h" + +#include "mozilla/ipc/BrowserProcessSubThread.h" +#include "mozilla/ipc/GeckoChildProcessHost.h" +#include "mozilla/ipc/IOThreadChild.h" +#include "mozilla/ipc/ProcessChild.h" +#include "ScopedXREEmbed.h" + +#include "mozilla/plugins/PluginProcessChild.h" +#include "mozilla/dom/ContentProcess.h" +#include "mozilla/dom/ContentParent.h" +#include "mozilla/dom/ContentChild.h" + +#include "mozilla/ipc/TestShellParent.h" +#include "mozilla/ipc/XPCShellEnvironment.h" +#include "mozilla/WindowsDllBlocklist.h" + +#include "GMPProcessChild.h" +#include "GMPLoader.h" +#include "mozilla/gfx/GPUProcessImpl.h" + +#include "GeckoProfiler.h" + +#include "mozilla/Telemetry.h" + +#if defined(MOZ_SANDBOX) && defined(XP_WIN) +#include "mozilla/sandboxTarget.h" +#include "mozilla/sandboxing/loggingCallbacks.h" +#endif + +#if defined(MOZ_CONTENT_SANDBOX) && !defined(MOZ_WIDGET_GONK) +#include "mozilla/Preferences.h" +#endif + +#ifdef MOZ_IPDL_TESTS +#include "mozilla/_ipdltest/IPDLUnitTests.h" +#include "mozilla/_ipdltest/IPDLUnitTestProcessChild.h" + +using mozilla::_ipdltest::IPDLUnitTestProcessChild; +#endif // ifdef MOZ_IPDL_TESTS + +#ifdef MOZ_JPROF +#include "jprof.h" +#endif + +using namespace mozilla; + +using mozilla::ipc::BrowserProcessSubThread; +using mozilla::ipc::GeckoChildProcessHost; +using mozilla::ipc::IOThreadChild; +using mozilla::ipc::ProcessChild; +using mozilla::ipc::ScopedXREEmbed; + +using mozilla::plugins::PluginProcessChild; +using mozilla::dom::ContentProcess; +using mozilla::dom::ContentParent; +using mozilla::dom::ContentChild; + +using mozilla::gmp::GMPLoader; +using mozilla::gmp::CreateGMPLoader; +using mozilla::gmp::GMPProcessChild; + +using mozilla::ipc::TestShellParent; +using mozilla::ipc::TestShellCommandParent; +using mozilla::ipc::XPCShellEnvironment; + +using mozilla::startup::sChildProcessType; + +static NS_DEFINE_CID(kAppShellCID, NS_APPSHELL_CID); + +#ifdef XP_WIN +static const wchar_t kShellLibraryName[] = L"shell32.dll"; +#endif + +nsresult +XRE_LockProfileDirectory(nsIFile* aDirectory, + nsISupports* *aLockObject) +{ + nsCOMPtr<nsIProfileLock> lock; + + nsresult rv = NS_LockProfilePath(aDirectory, nullptr, nullptr, + getter_AddRefs(lock)); + if (NS_SUCCEEDED(rv)) + NS_ADDREF(*aLockObject = lock); + + return rv; +} + +static int32_t sInitCounter; + +nsresult +XRE_InitEmbedding2(nsIFile *aLibXULDirectory, + nsIFile *aAppDirectory, + nsIDirectoryServiceProvider *aAppDirProvider) +{ + // Initialize some globals to make nsXREDirProvider happy + static char* kNullCommandLine[] = { nullptr }; + gArgv = kNullCommandLine; + gArgc = 0; + + NS_ENSURE_ARG(aLibXULDirectory); + + if (++sInitCounter > 1) // XXXbsmedberg is this really the right solution? + return NS_OK; + + if (!aAppDirectory) + aAppDirectory = aLibXULDirectory; + + nsresult rv; + + new nsXREDirProvider; // This sets gDirServiceProvider + if (!gDirServiceProvider) + return NS_ERROR_OUT_OF_MEMORY; + + rv = gDirServiceProvider->Initialize(aAppDirectory, aLibXULDirectory, + aAppDirProvider); + if (NS_FAILED(rv)) + return rv; + + rv = NS_InitXPCOM2(nullptr, aAppDirectory, gDirServiceProvider); + if (NS_FAILED(rv)) + return rv; + + // We do not need to autoregister components here. The CheckCompatibility() + // bits in nsAppRunner.cpp check for an invalidation flag in + // compatibility.ini. + // If the app wants to autoregister every time (for instance, if it's debug), + // it can do so after we return from this function. + + nsCOMPtr<nsIObserver> startupNotifier + (do_CreateInstance(NS_APPSTARTUPNOTIFIER_CONTRACTID)); + if (!startupNotifier) + return NS_ERROR_FAILURE; + + startupNotifier->Observe(nullptr, APPSTARTUP_TOPIC, nullptr); + + return NS_OK; +} + +void +XRE_NotifyProfile() +{ + NS_ASSERTION(gDirServiceProvider, "XRE_InitEmbedding was not called!"); + gDirServiceProvider->DoStartup(); +} + +void +XRE_TermEmbedding() +{ + if (--sInitCounter != 0) + return; + + NS_ASSERTION(gDirServiceProvider, + "XRE_TermEmbedding without XRE_InitEmbedding"); + + gDirServiceProvider->DoShutdown(); + NS_ShutdownXPCOM(nullptr); + delete gDirServiceProvider; +} + +const char* +XRE_ChildProcessTypeToString(GeckoProcessType aProcessType) +{ + return (aProcessType < GeckoProcessType_End) ? + kGeckoProcessTypeString[aProcessType] : "invalid"; +} + +namespace mozilla { +namespace startup { +GeckoProcessType sChildProcessType = GeckoProcessType_Default; +} // namespace startup +} // namespace mozilla + +void +XRE_SetProcessType(const char* aProcessTypeString) +{ + static bool called = false; + if (called) { + MOZ_CRASH(); + } + called = true; + + sChildProcessType = GeckoProcessType_Invalid; + for (int i = 0; + i < (int) ArrayLength(kGeckoProcessTypeString); + ++i) { + if (!strcmp(kGeckoProcessTypeString[i], aProcessTypeString)) { + sChildProcessType = static_cast<GeckoProcessType>(i); + return; + } + } +} + +#if defined(MOZ_CRASHREPORTER) +// FIXME/bug 539522: this out-of-place function is stuck here because +// IPDL wants access to this crashreporter interface, and +// crashreporter is built in such a way to make that awkward +bool +XRE_TakeMinidumpForChild(uint32_t aChildPid, nsIFile** aDump, + uint32_t* aSequence) +{ + return CrashReporter::TakeMinidumpForChild(aChildPid, aDump, aSequence); +} + +bool +XRE_SetRemoteExceptionHandler(const char* aPipe/*= 0*/) +{ +#if defined(XP_WIN) || defined(XP_MACOSX) + return CrashReporter::SetRemoteExceptionHandler(nsDependentCString(aPipe)); +#elif defined(OS_LINUX) + return CrashReporter::SetRemoteExceptionHandler(); +#else +# error "OOP crash reporter unsupported on this platform" +#endif +} +#endif // if defined(MOZ_CRASHREPORTER) + +#if defined(XP_WIN) +void +SetTaskbarGroupId(const nsString& aId) +{ + typedef HRESULT (WINAPI * SetCurrentProcessExplicitAppUserModelIDPtr)(PCWSTR AppID); + + SetCurrentProcessExplicitAppUserModelIDPtr funcAppUserModelID = nullptr; + + HMODULE hDLL = ::LoadLibraryW(kShellLibraryName); + + funcAppUserModelID = (SetCurrentProcessExplicitAppUserModelIDPtr) + GetProcAddress(hDLL, "SetCurrentProcessExplicitAppUserModelID"); + + if (!funcAppUserModelID) { + ::FreeLibrary(hDLL); + return; + } + + if (FAILED(funcAppUserModelID(aId.get()))) { + NS_WARNING("SetCurrentProcessExplicitAppUserModelID failed for child process."); + } + + if (hDLL) + ::FreeLibrary(hDLL); +} +#endif + +#if defined(MOZ_CRASHREPORTER) +#if defined(MOZ_CONTENT_SANDBOX) && !defined(MOZ_WIDGET_GONK) +void +AddContentSandboxLevelAnnotation() +{ + if (XRE_GetProcessType() == GeckoProcessType_Content) { + int level = Preferences::GetInt("security.sandbox.content.level"); + nsAutoCString levelString; + levelString.AppendInt(level); + CrashReporter::AnnotateCrashReport( + NS_LITERAL_CSTRING("ContentSandboxLevel"), levelString); + } +} +#endif /* MOZ_CONTENT_SANDBOX && !MOZ_WIDGET_GONK */ +#endif /* MOZ_CRASHREPORTER */ + +nsresult +XRE_InitChildProcess(int aArgc, + char* aArgv[], + const XREChildData* aChildData) +{ + NS_ENSURE_ARG_MIN(aArgc, 2); + NS_ENSURE_ARG_POINTER(aArgv); + NS_ENSURE_ARG_POINTER(aArgv[0]); + MOZ_ASSERT(aChildData); + +#ifdef MOZ_JPROF + // Call the code to install our handler + setupProfilingStuff(); +#endif + +#if !defined(MOZ_WIDGET_ANDROID) && !defined(MOZ_WIDGET_GONK) + // On non-Fennec Gecko, the GMPLoader code resides in plugin-container, + // and we must forward it through to the GMP code here. + GMPProcessChild::SetGMPLoader(aChildData->gmpLoader.get()); +#else + // On Fennec, the GMPLoader's code resides inside XUL (because for the time + // being GMPLoader relies upon NSPR, which we can't use in plugin-container + // on Android), so we create it here inside XUL and pass it to the GMP code. + UniquePtr<GMPLoader> loader = CreateGMPLoader(nullptr); + GMPProcessChild::SetGMPLoader(loader.get()); +#endif + +#if defined(XP_WIN) + // From the --attach-console support in nsNativeAppSupportWin.cpp, but + // here we are a content child process, so we always attempt to attach + // to the parent's (ie, the browser's) console. + // Try to attach console to the parent process. + // It will succeed when the parent process is a command line, + // so that stdio will be displayed in it. + if (AttachConsole(ATTACH_PARENT_PROCESS)) { + // Change std handles to refer to new console handles. + // Before doing so, ensure that stdout/stderr haven't been + // redirected to a valid file + if (_fileno(stdout) == -1 || + _get_osfhandle(fileno(stdout)) == -1) + freopen("CONOUT$", "w", stdout); + // Merge stderr into CONOUT$ since there isn't any `CONERR$`. + // http://msdn.microsoft.com/en-us/library/windows/desktop/ms683231%28v=vs.85%29.aspx + if (_fileno(stderr) == -1 || + _get_osfhandle(fileno(stderr)) == -1) + freopen("CONOUT$", "w", stderr); + if (_fileno(stdin) == -1 || _get_osfhandle(fileno(stdin)) == -1) + freopen("CONIN$", "r", stdin); + } + +#if defined(MOZ_SANDBOX) + if (aChildData->sandboxTargetServices) { + SandboxTarget::Instance()->SetTargetServices(aChildData->sandboxTargetServices); + } +#endif +#endif + + // NB: This must be called before profiler_init + ScopedLogging logger; + + // This is needed by Telemetry to initialize histogram collection. + // NB: This must be called after NS_LogInit(). + // NS_LogInit must be called before Telemetry::CreateStatisticsRecorder + // so as to avoid many log messages of the form + // WARNING: XPCOM objects created/destroyed from static ctor/dtor: [..] + // See bug 1279614. + Telemetry::CreateStatisticsRecorder(); + + mozilla::LogModule::Init(); + + char aLocal; + GeckoProfilerInitRAII profiler(&aLocal); + + PROFILER_LABEL("Startup", "XRE_InitChildProcess", + js::ProfileEntry::Category::OTHER); + + // Complete 'task_t' exchange for Mac OS X. This structure has the same size + // regardless of architecture so we don't have any cross-arch issues here. +#ifdef XP_MACOSX + if (aArgc < 1) + return NS_ERROR_FAILURE; + const char* const mach_port_name = aArgv[--aArgc]; + + const int kTimeoutMs = 1000; + + MachSendMessage child_message(0); + if (!child_message.AddDescriptor(MachMsgPortDescriptor(mach_task_self()))) { + NS_WARNING("child AddDescriptor(mach_task_self()) failed."); + return NS_ERROR_FAILURE; + } + + ReceivePort child_recv_port; + mach_port_t raw_child_recv_port = child_recv_port.GetPort(); + if (!child_message.AddDescriptor(MachMsgPortDescriptor(raw_child_recv_port))) { + NS_WARNING("Adding descriptor to message failed"); + return NS_ERROR_FAILURE; + } + + ReceivePort* ports_out_receiver = new ReceivePort(); + if (!child_message.AddDescriptor(MachMsgPortDescriptor(ports_out_receiver->GetPort()))) { + NS_WARNING("Adding descriptor to message failed"); + return NS_ERROR_FAILURE; + } + + ReceivePort* ports_in_receiver = new ReceivePort(); + if (!child_message.AddDescriptor(MachMsgPortDescriptor(ports_in_receiver->GetPort()))) { + NS_WARNING("Adding descriptor to message failed"); + return NS_ERROR_FAILURE; + } + + MachPortSender child_sender(mach_port_name); + kern_return_t err = child_sender.SendMessage(child_message, kTimeoutMs); + if (err != KERN_SUCCESS) { + NS_WARNING("child SendMessage() failed"); + return NS_ERROR_FAILURE; + } + + MachReceiveMessage parent_message; + err = child_recv_port.WaitForMessage(&parent_message, kTimeoutMs); + if (err != KERN_SUCCESS) { + NS_WARNING("child WaitForMessage() failed"); + return NS_ERROR_FAILURE; + } + + if (parent_message.GetTranslatedPort(0) == MACH_PORT_NULL) { + NS_WARNING("child GetTranslatedPort(0) failed"); + return NS_ERROR_FAILURE; + } + + err = task_set_bootstrap_port(mach_task_self(), + parent_message.GetTranslatedPort(0)); + + if (parent_message.GetTranslatedPort(1) == MACH_PORT_NULL) { + NS_WARNING("child GetTranslatedPort(1) failed"); + return NS_ERROR_FAILURE; + } + MachPortSender* ports_out_sender = new MachPortSender(parent_message.GetTranslatedPort(1)); + + if (parent_message.GetTranslatedPort(2) == MACH_PORT_NULL) { + NS_WARNING("child GetTranslatedPort(2) failed"); + return NS_ERROR_FAILURE; + } + MachPortSender* ports_in_sender = new MachPortSender(parent_message.GetTranslatedPort(2)); + + if (err != KERN_SUCCESS) { + NS_WARNING("child task_set_bootstrap_port() failed"); + return NS_ERROR_FAILURE; + } + +#endif + + SetupErrorHandling(aArgv[0]); + +#if defined(MOZ_CRASHREPORTER) + if (aArgc < 1) + return NS_ERROR_FAILURE; + const char* const crashReporterArg = aArgv[--aArgc]; + +# if defined(XP_WIN) || defined(XP_MACOSX) + // on windows and mac, |crashReporterArg| is the named pipe on which the + // server is listening for requests, or "-" if crash reporting is + // disabled. + if (0 != strcmp("-", crashReporterArg) && + !XRE_SetRemoteExceptionHandler(crashReporterArg)) { + // Bug 684322 will add better visibility into this condition + NS_WARNING("Could not setup crash reporting\n"); + } +# elif defined(OS_LINUX) + // on POSIX, |crashReporterArg| is "true" if crash reporting is + // enabled, false otherwise + if (0 != strcmp("false", crashReporterArg) && + !XRE_SetRemoteExceptionHandler(nullptr)) { + // Bug 684322 will add better visibility into this condition + NS_WARNING("Could not setup crash reporting\n"); + } +# else +# error "OOP crash reporting unsupported on this platform" +# endif +#endif // if defined(MOZ_CRASHREPORTER) + + gArgv = aArgv; + gArgc = aArgc; + +#ifdef MOZ_X11 + XInitThreads(); +#endif +#if MOZ_WIDGET_GTK == 2 + XRE_GlibInit(); +#endif +#ifdef MOZ_WIDGET_GTK + // Setting the name here avoids the need to pass this through to gtk_init(). + g_set_prgname(aArgv[0]); +#endif + +#ifdef OS_POSIX + if (PR_GetEnv("MOZ_DEBUG_CHILD_PROCESS") || + PR_GetEnv("MOZ_DEBUG_CHILD_PAUSE")) { + printf_stderr("\n\nCHILDCHILDCHILDCHILD\n debug me @ %d\n\n", + base::GetCurrentProcId()); + sleep(30); + } +#elif defined(OS_WIN) + if (PR_GetEnv("MOZ_DEBUG_CHILD_PROCESS")) { + NS_DebugBreak(NS_DEBUG_BREAK, + "Invoking NS_DebugBreak() to debug child process", + nullptr, __FILE__, __LINE__); + } else if (PR_GetEnv("MOZ_DEBUG_CHILD_PAUSE")) { + printf_stderr("\n\nCHILDCHILDCHILDCHILD\n debug me @ %d\n\n", + base::GetCurrentProcId()); + ::Sleep(10000); + } +#endif + + // child processes launched by GeckoChildProcessHost get this magic + // argument appended to their command lines + const char* const parentPIDString = aArgv[aArgc-1]; + MOZ_ASSERT(parentPIDString, "NULL parent PID"); + --aArgc; + + char* end = 0; + base::ProcessId parentPID = strtol(parentPIDString, &end, 10); + MOZ_ASSERT(!*end, "invalid parent PID"); + +#ifdef XP_MACOSX + mozilla::ipc::SharedMemoryBasic::SetupMachMemory(parentPID, ports_in_receiver, ports_in_sender, + ports_out_sender, ports_out_receiver, true); +#endif + +#if defined(XP_WIN) + // On Win7+, register the application user model id passed in by + // parent. This insures windows created by the container properly + // group with the parent app on the Win7 taskbar. + const char* const appModelUserId = aArgv[--aArgc]; + if (appModelUserId) { + // '-' implies no support + if (*appModelUserId != '-') { + nsString appId; + appId.AssignWithConversion(nsDependentCString(appModelUserId)); + // The version string is encased in quotes + appId.Trim(NS_LITERAL_CSTRING("\"").get()); + // Set the id + SetTaskbarGroupId(appId); + } + } +#endif + + base::AtExitManager exitManager; + + nsresult rv = XRE_InitCommandLine(aArgc, aArgv); + if (NS_FAILED(rv)) { + return NS_ERROR_FAILURE; + } + + MessageLoop::Type uiLoopType; + switch (XRE_GetProcessType()) { + case GeckoProcessType_Content: + case GeckoProcessType_GPU: + // Content processes need the XPCOM/chromium frankenventloop + uiLoopType = MessageLoop::TYPE_MOZILLA_CHILD; + break; + case GeckoProcessType_GMPlugin: + uiLoopType = MessageLoop::TYPE_DEFAULT; + break; + default: + uiLoopType = MessageLoop::TYPE_UI; + break; + } + + { + // This is a lexical scope for the MessageLoop below. We want it + // to go out of scope before NS_LogTerm() so that we don't get + // spurious warnings about XPCOM objects being destroyed from a + // static context. + + // Associate this thread with a UI MessageLoop + MessageLoop uiMessageLoop(uiLoopType); + { + nsAutoPtr<ProcessChild> process; + +#ifdef XP_WIN + mozilla::ipc::windows::InitUIThread(); +#endif + + switch (XRE_GetProcessType()) { + case GeckoProcessType_Default: + NS_RUNTIMEABORT("This makes no sense"); + break; + + case GeckoProcessType_Plugin: + process = new PluginProcessChild(parentPID); + break; + + case GeckoProcessType_Content: { + process = new ContentProcess(parentPID); + // If passed in grab the application path for xpcom init + bool foundAppdir = false; + +#if defined(XP_MACOSX) && defined(MOZ_CONTENT_SANDBOX) + // If passed in grab the profile path for sandboxing + bool foundProfile = false; +#endif + + for (int idx = aArgc; idx > 0; idx--) { + if (aArgv[idx] && !strcmp(aArgv[idx], "-appdir")) { + MOZ_ASSERT(!foundAppdir); + if (foundAppdir) { + continue; + } + nsCString appDir; + appDir.Assign(nsDependentCString(aArgv[idx+1])); + static_cast<ContentProcess*>(process.get())->SetAppDir(appDir); + foundAppdir = true; + } + + if (aArgv[idx] && !strcmp(aArgv[idx], "-safeMode")) { + gSafeMode = true; + } + +#if defined(XP_MACOSX) && defined(MOZ_CONTENT_SANDBOX) + if (aArgv[idx] && !strcmp(aArgv[idx], "-profile")) { + MOZ_ASSERT(!foundProfile); + if (foundProfile) { + continue; + } + nsCString profile; + profile.Assign(nsDependentCString(aArgv[idx+1])); + static_cast<ContentProcess*>(process.get())->SetProfile(profile); + foundProfile = true; + } +#endif /* XP_MACOSX && MOZ_CONTENT_SANDBOX */ + } + } + break; + + case GeckoProcessType_IPDLUnitTest: +#ifdef MOZ_IPDL_TESTS + process = new IPDLUnitTestProcessChild(parentPID); +#else + NS_RUNTIMEABORT("rebuild with --enable-ipdl-tests"); +#endif + break; + + case GeckoProcessType_GMPlugin: + process = new gmp::GMPProcessChild(parentPID); + break; + + case GeckoProcessType_GPU: + process = new gfx::GPUProcessImpl(parentPID); + break; + + default: + NS_RUNTIMEABORT("Unknown main thread class"); + } + + if (!process->Init()) { + return NS_ERROR_FAILURE; + } + +#ifdef MOZ_CRASHREPORTER +#if defined(XP_WIN) || defined(XP_MACOSX) + CrashReporter::InitChildProcessTmpDir(); +#endif +#endif + +#if defined(XP_WIN) + // Set child processes up such that they will get killed after the + // chrome process is killed in cases where the user shuts the system + // down or logs off. + ::SetProcessShutdownParameters(0x280 - 1, SHUTDOWN_NORETRY); +#endif + +#if defined(MOZ_SANDBOX) && defined(XP_WIN) + // We need to do this after the process has been initialised, as + // InitLoggingIfRequired may need access to prefs. + mozilla::sandboxing::InitLoggingIfRequired(aChildData->ProvideLogFunction); +#endif + + OverrideDefaultLocaleIfNeeded(); + +#if defined(MOZ_CRASHREPORTER) +#if defined(MOZ_CONTENT_SANDBOX) && !defined(MOZ_WIDGET_GONK) + AddContentSandboxLevelAnnotation(); +#endif +#endif + + // Run the UI event loop on the main thread. + uiMessageLoop.MessageLoop::Run(); + + // Allow ProcessChild to clean up after itself before going out of + // scope and being deleted + process->CleanUp(); + mozilla::Omnijar::CleanUp(); + +#if defined(XP_MACOSX) + // Everybody should be done using shared memory by now. + mozilla::ipc::SharedMemoryBasic::Shutdown(); +#endif + } + } + + Telemetry::DestroyStatisticsRecorder(); + return XRE_DeinitCommandLine(); +} + +MessageLoop* +XRE_GetIOMessageLoop() +{ + if (sChildProcessType == GeckoProcessType_Default) { + return BrowserProcessSubThread::GetMessageLoop(BrowserProcessSubThread::IO); + } + return IOThreadChild::message_loop(); +} + +namespace { + +class MainFunctionRunnable : public Runnable +{ +public: + NS_DECL_NSIRUNNABLE + + MainFunctionRunnable(MainFunction aFunction, + void* aData) + : mFunction(aFunction), + mData(aData) + { + NS_ASSERTION(aFunction, "Don't give me a null pointer!"); + } + +private: + MainFunction mFunction; + void* mData; +}; + +} /* anonymous namespace */ + +NS_IMETHODIMP +MainFunctionRunnable::Run() +{ + mFunction(mData); + return NS_OK; +} + +nsresult +XRE_InitParentProcess(int aArgc, + char* aArgv[], + MainFunction aMainFunction, + void* aMainFunctionData) +{ + NS_ENSURE_ARG_MIN(aArgc, 1); + NS_ENSURE_ARG_POINTER(aArgv); + NS_ENSURE_ARG_POINTER(aArgv[0]); + + ScopedXREEmbed embed; + + gArgc = aArgc; + gArgv = aArgv; + nsresult rv = XRE_InitCommandLine(gArgc, gArgv); + if (NS_FAILED(rv)) + return NS_ERROR_FAILURE; + + { + embed.Start(); + + nsCOMPtr<nsIAppShell> appShell(do_GetService(kAppShellCID)); + NS_ENSURE_TRUE(appShell, NS_ERROR_FAILURE); + + if (aMainFunction) { + nsCOMPtr<nsIRunnable> runnable = + new MainFunctionRunnable(aMainFunction, aMainFunctionData); + NS_ENSURE_TRUE(runnable, NS_ERROR_OUT_OF_MEMORY); + + nsresult rv = NS_DispatchToCurrentThread(runnable); + NS_ENSURE_SUCCESS(rv, rv); + } + + // Do event loop + if (NS_FAILED(appShell->Run())) { + NS_WARNING("Failed to run appshell"); + return NS_ERROR_FAILURE; + } + } + + return XRE_DeinitCommandLine(); +} + +#ifdef MOZ_IPDL_TESTS +//----------------------------------------------------------------------------- +// IPDL unit test + +int +XRE_RunIPDLTest(int aArgc, char** aArgv) +{ + if (aArgc < 2) { + fprintf(stderr, "TEST-UNEXPECTED-FAIL | <---> | insufficient #args, need at least 2\n"); + return 1; + } + + void* data = reinterpret_cast<void*>(aArgv[aArgc-1]); + + nsresult rv = + XRE_InitParentProcess( + --aArgc, aArgv, mozilla::_ipdltest::IPDLUnitTestMain, data); + NS_ENSURE_SUCCESS(rv, 1); + + return 0; +} +#endif // ifdef MOZ_IPDL_TESTS + +nsresult +XRE_RunAppShell() +{ + nsCOMPtr<nsIAppShell> appShell(do_GetService(kAppShellCID)); + NS_ENSURE_TRUE(appShell, NS_ERROR_FAILURE); +#if defined(XP_MACOSX) + { + // In content processes that want XPCOM (and hence want + // AppShell), we usually run our hybrid event loop through + // MessagePump::Run(), by way of nsBaseAppShell::Run(). The + // Cocoa nsAppShell impl, however, implements its own Run() + // that's unaware of MessagePump. That's all rather suboptimal, + // but oddly enough not a problem... usually. + // + // The problem with this setup comes during startup. + // XPCOM-in-subprocesses depends on IPC, e.g. to init the pref + // service, so we have to init IPC first. But, IPC also + // indirectly kinda-depends on XPCOM, because MessagePump + // schedules work from off-main threads (e.g. IO thread) by + // using NS_DispatchToMainThread(). If the IO thread receives a + // Message from the parent before nsThreadManager is + // initialized, then DispatchToMainThread() will fail, although + // MessagePump will remember the task. This race condition + // isn't a problem when appShell->Run() ends up in + // MessagePump::Run(), because MessagePump will immediate see it + // has work to do. It *is* a problem when we end up in [NSApp + // run], because it's not aware that MessagePump has work that + // needs to be processed; that was supposed to be signaled by + // nsIRunnable(s). + // + // So instead of hacking Cocoa nsAppShell or rewriting the + // event-loop system, we compromise here by processing any tasks + // that might have been enqueued on MessagePump, *before* + // MessagePump::ScheduleWork was able to successfully + // DispatchToMainThread(). + MessageLoop* loop = MessageLoop::current(); + bool couldNest = loop->NestableTasksAllowed(); + + loop->SetNestableTasksAllowed(true); + RefPtr<Runnable> task = new MessageLoop::QuitTask(); + loop->PostTask(task.forget()); + loop->Run(); + + loop->SetNestableTasksAllowed(couldNest); + } +#endif // XP_MACOSX + return appShell->Run(); +} + +void +XRE_ShutdownChildProcess() +{ + MOZ_ASSERT(NS_IsMainThread(), "Wrong thread!"); + + mozilla::DebugOnly<MessageLoop*> ioLoop = XRE_GetIOMessageLoop(); + MOZ_ASSERT(!!ioLoop, "Bad shutdown order"); + + // Quit() sets off the following chain of events + // (1) UI loop starts quitting + // (2) UI loop returns from Run() in XRE_InitChildProcess() + // (3) ProcessChild goes out of scope and terminates the IO thread + // (4) ProcessChild joins the IO thread + // (5) exit() + MessageLoop::current()->Quit(); +#if defined(XP_MACOSX) + nsCOMPtr<nsIAppShell> appShell(do_GetService(kAppShellCID)); + if (appShell) { + // On Mac, we might be only above nsAppShell::Run(), not + // MessagePump::Run(). See XRE_RunAppShell(). To account for + // that case, we fire off an Exit() here. If we were indeed + // above MessagePump::Run(), this Exit() is just superfluous. + appShell->Exit(); + } +#endif // XP_MACOSX +} + +namespace { +ContentParent* gContentParent; //long-lived, manually refcounted +TestShellParent* GetOrCreateTestShellParent() +{ + if (!gContentParent) { + RefPtr<ContentParent> parent = ContentParent::GetNewOrUsedBrowserProcess(); + parent.forget(&gContentParent); + } else if (!gContentParent->IsAlive()) { + return nullptr; + } + TestShellParent* tsp = gContentParent->GetTestShellSingleton(); + if (!tsp) { + tsp = gContentParent->CreateTestShell(); + } + return tsp; +} + +} // namespace + +bool +XRE_SendTestShellCommand(JSContext* aCx, + JSString* aCommand, + void* aCallback) +{ + JS::RootedString cmd(aCx, aCommand); + TestShellParent* tsp = GetOrCreateTestShellParent(); + NS_ENSURE_TRUE(tsp, false); + + nsAutoJSString command; + NS_ENSURE_TRUE(command.init(aCx, cmd), false); + + if (!aCallback) { + return tsp->SendExecuteCommand(command); + } + + TestShellCommandParent* callback = static_cast<TestShellCommandParent*>( + tsp->SendPTestShellCommandConstructor(command)); + NS_ENSURE_TRUE(callback, false); + + JS::Value callbackVal = *reinterpret_cast<JS::Value*>(aCallback); + NS_ENSURE_TRUE(callback->SetCallback(aCx, callbackVal), false); + + return true; +} + +bool +XRE_ShutdownTestShell() +{ + if (!gContentParent) { + return true; + } + bool ret = true; + if (gContentParent->IsAlive()) { + ret = gContentParent->DestroyTestShell( + gContentParent->GetTestShellSingleton()); + } + NS_RELEASE(gContentParent); + return ret; +} + +#ifdef MOZ_X11 +void +XRE_InstallX11ErrorHandler() +{ +#if (MOZ_WIDGET_GTK == 3) + InstallGdkErrorHandler(); +#else + InstallX11ErrorHandler(); +#endif +} +#endif diff --git a/toolkit/xre/nsGDKErrorHandler.cpp b/toolkit/xre/nsGDKErrorHandler.cpp new file mode 100644 index 0000000000..a01a7c53b2 --- /dev/null +++ b/toolkit/xre/nsGDKErrorHandler.cpp @@ -0,0 +1,100 @@ +/* -*- Mode: C++; tab-width: 40; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "nsGDKErrorHandler.h" + +#include <gtk/gtk.h> +#include <gdk/gdkx.h> +#include <errno.h> +#include <stdlib.h> +#include <string.h> + +#include "nsDebug.h" +#include "nsString.h" +#include "nsX11ErrorHandler.h" + +#include "prenv.h" + + +/* See https://bugzilla.gnome.org/show_bug.cgi?id=629608#c8 + * + * GDK implements X11 error traps to ignore X11 errors. + * Unfortunatelly We don't know which X11 events can be ignored + * so we have to utilize the Gdk error handler to avoid + * false alarms in Gtk3. + */ +static void +GdkErrorHandler(const gchar *log_domain, GLogLevelFlags log_level, + const gchar *message, gpointer user_data) +{ + if (strstr(message, "X Window System error")) { + XErrorEvent event; + nsDependentCString buffer(message); + char *endptr; + + /* Parse Gdk X Window error message which has this format: + * (Details: serial XXXX error_code XXXX request_code XXXX (XXXX) minor_code XXXX) + */ + NS_NAMED_LITERAL_CSTRING(serialString, "(Details: serial "); + int32_t start = buffer.Find(serialString); + if (start == kNotFound) + NS_RUNTIMEABORT(message); + + start += serialString.Length(); + errno = 0; + event.serial = strtol(buffer.BeginReading() + start, &endptr, 10); + if (errno) + NS_RUNTIMEABORT(message); + + NS_NAMED_LITERAL_CSTRING(errorCodeString, " error_code "); + if (!StringBeginsWith(Substring(endptr, buffer.EndReading()), errorCodeString)) + NS_RUNTIMEABORT(message); + + errno = 0; + event.error_code = strtol(endptr + errorCodeString.Length(), &endptr, 10); + if (errno) + NS_RUNTIMEABORT(message); + + NS_NAMED_LITERAL_CSTRING(requestCodeString, " request_code "); + if (!StringBeginsWith(Substring(endptr, buffer.EndReading()), requestCodeString)) + NS_RUNTIMEABORT(message); + + errno = 0; + event.request_code = strtol(endptr + requestCodeString.Length(), &endptr, 10); + if (errno) + NS_RUNTIMEABORT(message); + + NS_NAMED_LITERAL_CSTRING(minorCodeString, " minor_code "); + start = buffer.Find(minorCodeString, endptr - buffer.BeginReading()); + if (!start) + NS_RUNTIMEABORT(message); + + errno = 0; + event.minor_code = strtol(buffer.BeginReading() + start + minorCodeString.Length(), nullptr, 10); + if (errno) + NS_RUNTIMEABORT(message); + + event.display = GDK_DISPLAY_XDISPLAY(gdk_display_get_default()); + // Gdk does not provide resource ID + event.resourceid = 0; + + X11Error(event.display, &event); + } else { + g_log_default_handler(log_domain, log_level, message, user_data); + NS_RUNTIMEABORT(message); + } +} + +void +InstallGdkErrorHandler() +{ + g_log_set_handler("Gdk", + (GLogLevelFlags)(G_LOG_LEVEL_ERROR | G_LOG_FLAG_FATAL | G_LOG_FLAG_RECURSION), + GdkErrorHandler, + nullptr); + if (PR_GetEnv("MOZ_X_SYNC")) { + XSynchronize(GDK_DISPLAY_XDISPLAY(gdk_display_get_default()), True); + } +} diff --git a/toolkit/xre/nsGDKErrorHandler.h b/toolkit/xre/nsGDKErrorHandler.h new file mode 100644 index 0000000000..dc74596164 --- /dev/null +++ b/toolkit/xre/nsGDKErrorHandler.h @@ -0,0 +1,8 @@ +/* -*- Mode: C++; tab-width: 40; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#if (MOZ_WIDGET_GTK == 3) +void InstallGdkErrorHandler(); +#endif diff --git a/toolkit/xre/nsINativeAppSupport.idl b/toolkit/xre/nsINativeAppSupport.idl new file mode 100644 index 0000000000..c9a316a986 --- /dev/null +++ b/toolkit/xre/nsINativeAppSupport.idl @@ -0,0 +1,104 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "nsISupports.idl" + +/* nsINativeAppSupport + * + * This "pseudo" (in the XPCOM sense) interface provides for + * platform-specific general application support: + * o It manages the details of the simple DDE communication + * supported on the Win32 platform (it is the addition of this + * item that prompted the creation of this interface. + * + * Due to the nature of the beast, this interface is not a full-blown + * XPCOM component. The primary reason is that objects that implement + * this interface generally must be operational *before* XPCOM (or any + * of the rest of Mozilla) are initialized. As a result, this + * interface is instantiated by somewhat unconventional means. + * + * To create the implementor of this interface, you call the function + * NS_CreateNativeAppSupport. This is done in the startup code + * in nsAppRunner.cpp + * + * The interface provides these functions: + * start - You call this to inform the native app support that the + * application is starting. In addition, it serves as a + * query as to whether the application should continue to + * run. + * + * If the returned boolean result is PR_FALSE, then the + * application should exit without further processing. In + * such cases, the returned nsresult indicates whether the + * reason to exit is due to an error or not. + * + * Win32 Note: In the case of starting a second instance + * of this executable, this function will return + * PR_FALSE and nsresult==NS_OK. This means that + * the command line arguments have been + * successfully passed to the instance of the + * application acting as a DDE server. + * + * stop - You call this to inform the native app support that the + * application *wishes* to terminate. If the returned boolean + * value is PR_FALSE, then the application should continue + * (as if there were still additional top-level windows open). + * + * Win32 Note: If this is the instance of the application + * acting as the DDE server, and there are current + * DDE conversations active with other instances + * acting as DDE clients, then this function will + * return PR_FALSE. + * + * quit - Like Stop, but this method *forces* termination (or more + * precisely, indicates that the application is about to be + * terminated regardless of what a call to Stop might have + * returned. + * + * This method is intended to be called when the user selects + * the "Quit" option (close all windows and exit). + * + * Win32 Note: Stop is problematic in the case of "Quit" (close + * all windows and exit the application) because + * either we don't Quit or (potentially) we lose + * requests coming from other instances of the + * application. The strategy is to give preference + * to the user's explicit Quit request. In the + * unlikely event that a request is pending from + * another instance of the application, then such + * requests are essentially ignored. This is + * roughly equivalent to handling that request by + * opening a new window, followed by immediately + * closing it. Since this is the same as if the + * request came in immediately before the Quit + * call (versus immediately after it), no harm. + * + * There is an exposure here: Upon return from this + * function, any DDE connect request (for Mozilla) + * will fail and other instances of the application + * will start up as a DDE server. In that case, + * those instances may do things that conflict with + * the subsequent shutting down of the instance that + * is quitting. For this reason, the call to Quit + * should be deferred as long as possible. + * + * onLastWindowClosing - Called when the last window is closed. Used as a + * "soft" shutdown, passwords are flushed. + */ + +interface nsIXULWindow; +interface nsICmdLineService; + +[scriptable, uuid(5fdf8480-1f98-11d4-8077-00600811a9c3)] +interface nsINativeAppSupport : nsISupports { + // Startup/shutdown. + boolean start(); + void enable(); + boolean stop(); + void quit(); + + void onLastWindowClosing(); + void ReOpen(); +}; diff --git a/toolkit/xre/nsIWinAppHelper.idl b/toolkit/xre/nsIWinAppHelper.idl new file mode 100644 index 0000000000..c8a602977c --- /dev/null +++ b/toolkit/xre/nsIWinAppHelper.idl @@ -0,0 +1,19 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "nsISupports.idl" + +/** + * A scriptable interface used on Windows only to do some work from + * a special process that gets created with elevated privileges. + * + * @status UNSTABLE - This interface is not frozen and will probably change in + * future releases. + */ + +[scriptable, uuid(dc263ca8-b257-47eb-b5b7-339d9e0b90f7)] +interface nsIWinAppHelper : nsISupports +{ + readonly attribute boolean userCanElevate; +}; diff --git a/toolkit/xre/nsNativeAppSupportBase.cpp b/toolkit/xre/nsNativeAppSupportBase.cpp new file mode 100644 index 0000000000..877bd3d8ac --- /dev/null +++ b/toolkit/xre/nsNativeAppSupportBase.cpp @@ -0,0 +1,55 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "nsNativeAppSupportBase.h" + +nsNativeAppSupportBase::nsNativeAppSupportBase() +{ +} + +nsNativeAppSupportBase::~nsNativeAppSupportBase() +{ +} + +NS_IMPL_ISUPPORTS(nsNativeAppSupportBase, nsINativeAppSupport) + +// Start answer defaults to OK. +NS_IMETHODIMP +nsNativeAppSupportBase::Start( bool *result ) +{ + *result = true; + return NS_OK; +} + +NS_IMETHODIMP +nsNativeAppSupportBase::Enable() +{ + return NS_OK; +} + +// Stop answer defaults to OK. +NS_IMETHODIMP +nsNativeAppSupportBase::Stop( bool *result ) +{ + *result = true; + return NS_OK; +} + +NS_IMETHODIMP +nsNativeAppSupportBase::Quit() +{ + return NS_OK; +} + +NS_IMETHODIMP +nsNativeAppSupportBase::ReOpen() +{ + return NS_OK; +} + +NS_IMETHODIMP +nsNativeAppSupportBase::OnLastWindowClosing() { + return NS_OK; +} diff --git a/toolkit/xre/nsNativeAppSupportBase.h b/toolkit/xre/nsNativeAppSupportBase.h new file mode 100644 index 0000000000..a3996b7282 --- /dev/null +++ b/toolkit/xre/nsNativeAppSupportBase.h @@ -0,0 +1,28 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef nsNativeAppSupportBase_h__ +#define nsNativeAppSupportBase_h__ + +#include "nsAppRunner.h" +#include "nsINativeAppSupport.h" + +// nsNativeAppSupportBase +// +// This is a default implementation of the nsINativeAppSupport interface +// declared in mozilla/xpfe/appshell/public/nsINativeAppSupport.h. + +class nsNativeAppSupportBase : public nsINativeAppSupport { +public: + nsNativeAppSupportBase(); + + NS_DECL_ISUPPORTS + NS_DECL_NSINATIVEAPPSUPPORT + +protected: + virtual ~nsNativeAppSupportBase(); +}; + +#endif diff --git a/toolkit/xre/nsNativeAppSupportCocoa.mm b/toolkit/xre/nsNativeAppSupportCocoa.mm new file mode 100644 index 0000000000..6da6790c15 --- /dev/null +++ b/toolkit/xre/nsNativeAppSupportCocoa.mm @@ -0,0 +1,198 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "nsString.h" + +#import <CoreServices/CoreServices.h> +#import <Cocoa/Cocoa.h> + +#include "nsCOMPtr.h" +#include "nsCocoaFeatures.h" +#include "nsNativeAppSupportBase.h" + +#include "nsIAppShellService.h" +#include "nsIAppStartup.h" +#include "nsIBaseWindow.h" +#include "nsICommandLineRunner.h" +#include "mozIDOMWindow.h" +#include "nsIDocShellTreeItem.h" +#include "nsIDocShellTreeOwner.h" +#include "nsIInterfaceRequestorUtils.h" +#include "nsIObserver.h" +#include "nsIServiceManager.h" +#include "nsIWebNavigation.h" +#include "nsIWidget.h" +#include "nsIWindowMediator.h" + +// This must be included last: +#include "nsObjCExceptions.h" + +nsresult +GetNativeWindowPointerFromDOMWindow(mozIDOMWindowProxy *a_window, NSWindow **a_nativeWindow) +{ + *a_nativeWindow = nil; + if (!a_window) + return NS_ERROR_INVALID_ARG; + + nsCOMPtr<nsIWebNavigation> mruWebNav(do_GetInterface(a_window)); + if (mruWebNav) { + nsCOMPtr<nsIDocShellTreeItem> mruTreeItem(do_QueryInterface(mruWebNav)); + nsCOMPtr<nsIDocShellTreeOwner> mruTreeOwner = nullptr; + mruTreeItem->GetTreeOwner(getter_AddRefs(mruTreeOwner)); + if(mruTreeOwner) { + nsCOMPtr<nsIBaseWindow> mruBaseWindow(do_QueryInterface(mruTreeOwner)); + if (mruBaseWindow) { + nsCOMPtr<nsIWidget> mruWidget = nullptr; + mruBaseWindow->GetMainWidget(getter_AddRefs(mruWidget)); + if (mruWidget) { + *a_nativeWindow = (NSWindow*)mruWidget->GetNativeData(NS_NATIVE_WINDOW); + } + } + } + } + + return NS_OK; +} + +class nsNativeAppSupportCocoa : public nsNativeAppSupportBase +{ +public: + nsNativeAppSupportCocoa() : + mCanShowUI(false) { } + + NS_IMETHOD Start(bool* aRetVal); + NS_IMETHOD ReOpen(); + NS_IMETHOD Enable(); + +private: + bool mCanShowUI; +}; + +NS_IMETHODIMP +nsNativeAppSupportCocoa::Enable() +{ + mCanShowUI = true; + return NS_OK; +} + +NS_IMETHODIMP nsNativeAppSupportCocoa::Start(bool *_retval) +{ + int major, minor, bugfix; + nsCocoaFeatures::GetSystemVersion(major, minor, bugfix); + + NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT; + + // Check that the OS version is supported, if not return false, + // which will make the browser quit. In principle we could display an + // alert here. But the alert's message and buttons would require custom + // localization. So (for now at least) we just log an English message + // to the console before quitting. + if (major < 10 || minor < 6) { + NSLog(@"Minimum OS version requirement not met!"); + return NS_OK; + } + + *_retval = true; + return NS_OK; + + NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT; +} + +NS_IMETHODIMP +nsNativeAppSupportCocoa::ReOpen() +{ + NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT; + + if (!mCanShowUI) + return NS_ERROR_FAILURE; + + bool haveNonMiniaturized = false; + bool haveOpenWindows = false; + bool done = false; + + nsCOMPtr<nsIWindowMediator> + wm(do_GetService(NS_WINDOWMEDIATOR_CONTRACTID)); + if (!wm) { + return NS_ERROR_FAILURE; + } + else { + nsCOMPtr<nsISimpleEnumerator> windowList; + wm->GetXULWindowEnumerator(nullptr, getter_AddRefs(windowList)); + bool more; + windowList->HasMoreElements(&more); + while (more) { + nsCOMPtr<nsISupports> nextWindow = nullptr; + windowList->GetNext(getter_AddRefs(nextWindow)); + nsCOMPtr<nsIBaseWindow> baseWindow(do_QueryInterface(nextWindow)); + if (!baseWindow) { + windowList->HasMoreElements(&more); + continue; + } + else { + haveOpenWindows = true; + } + + nsCOMPtr<nsIWidget> widget = nullptr; + baseWindow->GetMainWidget(getter_AddRefs(widget)); + if (!widget) { + windowList->HasMoreElements(&more); + continue; + } + NSWindow *cocoaWindow = (NSWindow*)widget->GetNativeData(NS_NATIVE_WINDOW); + if (![cocoaWindow isMiniaturized]) { + haveNonMiniaturized = true; + break; //have un-minimized windows, nothing to do + } + windowList->HasMoreElements(&more); + } // end while + + if (!haveNonMiniaturized) { + // Deminiaturize the most recenty used window + nsCOMPtr<mozIDOMWindowProxy> mru; + wm->GetMostRecentWindow(nullptr, getter_AddRefs(mru)); + + if (mru) { + NSWindow *cocoaMru = nil; + GetNativeWindowPointerFromDOMWindow(mru, &cocoaMru); + if (cocoaMru) { + [cocoaMru deminiaturize:nil]; + done = true; + } + } + } // end if have non miniaturized + + if (!haveOpenWindows && !done) { + char* argv[] = { nullptr }; + + // use an empty command line to make the right kind(s) of window open + nsCOMPtr<nsICommandLineRunner> cmdLine + (do_CreateInstance("@mozilla.org/toolkit/command-line;1")); + NS_ENSURE_TRUE(cmdLine, NS_ERROR_FAILURE); + + nsresult rv; + rv = cmdLine->Init(0, argv, nullptr, + nsICommandLine::STATE_REMOTE_EXPLICIT); + NS_ENSURE_SUCCESS(rv, rv); + + return cmdLine->Run(); + } + + } // got window mediator + return NS_OK; + + NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT; +} + +#pragma mark - + +// Create and return an instance of class nsNativeAppSupportCocoa. +nsresult NS_CreateNativeAppSupport(nsINativeAppSupport**aResult) +{ + *aResult = new nsNativeAppSupportCocoa; + if (!*aResult) return NS_ERROR_OUT_OF_MEMORY; + + NS_ADDREF(*aResult); + return NS_OK; +} diff --git a/toolkit/xre/nsNativeAppSupportDefault.cpp b/toolkit/xre/nsNativeAppSupportDefault.cpp new file mode 100644 index 0000000000..1b6c3f4870 --- /dev/null +++ b/toolkit/xre/nsNativeAppSupportDefault.cpp @@ -0,0 +1,17 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "nsNativeAppSupportBase.h" + +nsresult +NS_CreateNativeAppSupport( nsINativeAppSupport **aResult ) +{ + nsNativeAppSupportBase* native = new nsNativeAppSupportBase(); + if (!native) return NS_ERROR_OUT_OF_MEMORY; + + *aResult = native; + NS_ADDREF( *aResult ); + + return NS_OK; +} diff --git a/toolkit/xre/nsNativeAppSupportUnix.cpp b/toolkit/xre/nsNativeAppSupportUnix.cpp new file mode 100644 index 0000000000..a04a79953c --- /dev/null +++ b/toolkit/xre/nsNativeAppSupportUnix.cpp @@ -0,0 +1,708 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "nsNativeAppSupportBase.h" +#include "nsCOMPtr.h" +#include "nsXPCOM.h" +#include "nsISupportsPrimitives.h" +#include "nsIObserverService.h" +#include "nsIAppStartup.h" +#include "nsServiceManagerUtils.h" +#include "prlink.h" +#include "nsXREDirProvider.h" +#include "nsReadableUtils.h" + +#include "nsIFile.h" +#include "nsDirectoryServiceDefs.h" +#include "nsICommandLineRunner.h" +#include "nsIWindowMediator.h" +#include "nsPIDOMWindow.h" +#include "nsIDocShell.h" +#include "nsIBaseWindow.h" +#include "nsIWidget.h" +#include "nsIWritablePropertyBag2.h" +#include "nsIPrefService.h" +#include "mozilla/Services.h" + +#include <stdlib.h> +#include <glib.h> +#include <glib-object.h> +#include <gtk/gtk.h> + +#ifdef MOZ_X11 +#include <gdk/gdkx.h> +#include <X11/ICE/ICElib.h> +#include <X11/SM/SMlib.h> +#include <fcntl.h> +#include "nsThreadUtils.h" + +#include <pwd.h> +#endif + +#ifdef MOZ_ENABLE_DBUS +#include <dbus/dbus.h> +#endif + +#define MIN_GTK_MAJOR_VERSION 2 +#define MIN_GTK_MINOR_VERSION 10 +#define UNSUPPORTED_GTK_MSG "We're sorry, this application requires a version of the GTK+ library that is not installed on your computer.\n\n\ +You have GTK+ %d.%d.\nThis application requires GTK+ %d.%d or newer.\n\n\ +Please upgrade your GTK+ library if you wish to use this application." + +#if MOZ_X11 +#undef IceSetIOErrorHandler +#undef IceAddConnectionWatch +#undef IceConnectionNumber +#undef IceProcessMessages +#undef IceGetConnectionContext +#undef SmcInteractDone +#undef SmcSaveYourselfDone +#undef SmcInteractRequest +#undef SmcCloseConnection +#undef SmcOpenConnection +#undef SmcSetProperties + +typedef IceIOErrorHandler (*IceSetIOErrorHandlerFn) (IceIOErrorHandler); +typedef int (*IceAddConnectionWatchFn) (IceWatchProc, IcePointer); +typedef int (*IceConnectionNumberFn) (IceConn); +typedef IceProcessMessagesStatus (*IceProcessMessagesFn) (IceConn, IceReplyWaitInfo*, Bool*); +typedef IcePointer (*IceGetConnectionContextFn) (IceConn); + +typedef void (*SmcInteractDoneFn) (SmcConn, Bool); +typedef void (*SmcSaveYourselfDoneFn) (SmcConn, Bool); +typedef int (*SmcInteractRequestFn) (SmcConn, int, SmcInteractProc, SmPointer); +typedef SmcCloseStatus (*SmcCloseConnectionFn) (SmcConn, int, char**); +typedef SmcConn (*SmcOpenConnectionFn) (char*, SmPointer, int, int, + unsigned long, SmcCallbacks*, + const char*, char**, int, char*); +typedef void (*SmcSetPropertiesFn) (SmcConn, int, SmProp**); + +static IceSetIOErrorHandlerFn IceSetIOErrorHandlerPtr; +static IceAddConnectionWatchFn IceAddConnectionWatchPtr; +static IceConnectionNumberFn IceConnectionNumberPtr; +static IceProcessMessagesFn IceProcessMessagesPtr; +static IceGetConnectionContextFn IceGetConnectionContextPtr; +static SmcInteractDoneFn SmcInteractDonePtr; +static SmcSaveYourselfDoneFn SmcSaveYourselfDonePtr; +static SmcInteractRequestFn SmcInteractRequestPtr; +static SmcCloseConnectionFn SmcCloseConnectionPtr; +static SmcOpenConnectionFn SmcOpenConnectionPtr; +static SmcSetPropertiesFn SmcSetPropertiesPtr; + +#define IceSetIOErrorHandler IceSetIOErrorHandlerPtr +#define IceAddConnectionWatch IceAddConnectionWatchPtr +#define IceConnectionNumber IceConnectionNumberPtr +#define IceProcessMessages IceProcessMessagesPtr +#define IceGetConnectionContext IceGetConnectionContextPtr +#define SmcInteractDone SmcInteractDonePtr +#define SmcSaveYourselfDone SmcSaveYourselfDonePtr +#define SmcInteractRequest SmcInteractRequestPtr +#define SmcCloseConnection SmcCloseConnectionPtr +#define SmcOpenConnection SmcOpenConnectionPtr +#define SmcSetProperties SmcSetPropertiesPtr + +enum ClientState { + STATE_DISCONNECTED, + STATE_REGISTERING, + STATE_IDLE, + STATE_INTERACTING, + STATE_SHUTDOWN_CANCELLED +}; + +static const char *gClientStateTable[] = { + "DISCONNECTED", + "REGISTERING", + "IDLE", + "INTERACTING", + "SHUTDOWN_CANCELLED" +}; + +static LazyLogModule sMozSMLog("MozSM"); +#endif /* MOZ_X11 */ + +class nsNativeAppSupportUnix : public nsNativeAppSupportBase +{ +public: +#if MOZ_X11 + nsNativeAppSupportUnix(): mSessionConnection(nullptr), + mClientState(STATE_DISCONNECTED) {}; + ~nsNativeAppSupportUnix() + { + // this goes out of scope after "web-workers-shutdown" async shutdown phase + // so it's safe to disconnect here (i.e. the application won't lose data) + DisconnectFromSM(); + }; + + void DisconnectFromSM(); +#endif + NS_IMETHOD Start(bool* aRetVal); + NS_IMETHOD Stop(bool *aResult); + NS_IMETHOD Enable(); + +private: +#if MOZ_X11 + static void SaveYourselfCB(SmcConn smc_conn, SmPointer client_data, + int save_style, Bool shutdown, int interact_style, + Bool fast); + static void DieCB(SmcConn smc_conn, SmPointer client_data); + static void InteractCB(SmcConn smc_conn, SmPointer client_data); + static void SaveCompleteCB(SmcConn smc_conn, SmPointer client_data) {}; + static void ShutdownCancelledCB(SmcConn smc_conn, SmPointer client_data); + void DoInteract(); + void SetClientState(ClientState aState) + { + mClientState = aState; + MOZ_LOG(sMozSMLog, LogLevel::Debug, ("New state = %s\n", gClientStateTable[aState])); + } + + SmcConn mSessionConnection; + ClientState mClientState; +#endif +}; + +#if MOZ_X11 +static gboolean +process_ice_messages(IceConn connection) +{ + IceProcessMessagesStatus status; + + status = IceProcessMessages(connection, nullptr, nullptr); + + switch (status) { + case IceProcessMessagesSuccess: + return TRUE; + + case IceProcessMessagesIOError: { + nsNativeAppSupportUnix *native = + static_cast<nsNativeAppSupportUnix *>(IceGetConnectionContext(connection)); + native->DisconnectFromSM(); + } + return FALSE; + + case IceProcessMessagesConnectionClosed: + return FALSE; + + default: + g_assert_not_reached (); + } +} + +static gboolean +ice_iochannel_watch(GIOChannel *channel, GIOCondition condition, + gpointer client_data) +{ + return process_ice_messages(static_cast<IceConn>(client_data)); +} + +static void +ice_connection_watch(IceConn connection, IcePointer client_data, + Bool opening, IcePointer *watch_data) +{ + guint watch_id; + + if (opening) { + GIOChannel *channel; + int fd = IceConnectionNumber(connection); + + fcntl(fd, F_SETFD, fcntl(fd, F_GETFD, 0) | FD_CLOEXEC); + channel = g_io_channel_unix_new(fd); + watch_id = g_io_add_watch(channel, + static_cast<GIOCondition>(G_IO_IN | G_IO_ERR), + ice_iochannel_watch, connection); + g_io_channel_unref(channel); + + *watch_data = GUINT_TO_POINTER(watch_id); + } else { + watch_id = GPOINTER_TO_UINT(*watch_data); + g_source_remove(watch_id); + } +} + +static void +ice_io_error_handler(IceConn connection) +{ + // override the default handler which would exit the application; + // do nothing and let ICELib handle the failure of the connection gracefully. +} + +static void +ice_init(void) +{ + static bool initted = false; + + if (!initted) { + IceSetIOErrorHandler(ice_io_error_handler); + IceAddConnectionWatch(ice_connection_watch, nullptr); + initted = true; + } +} + +void +nsNativeAppSupportUnix::InteractCB(SmcConn smc_conn, SmPointer client_data) +{ + nsNativeAppSupportUnix *self = + static_cast<nsNativeAppSupportUnix *>(client_data); + + self->SetClientState(STATE_INTERACTING); + + // We do this asynchronously, as we spin the event loop recursively if + // a dialog is displayed. If we do this synchronously, we don't finish + // processing the current ICE event whilst the dialog is displayed, which + // means we won't process any more. libsm hates us if we do the InteractDone + // with a pending ShutdownCancelled, and we would certainly like to handle Die + // whilst a dialog is displayed + NS_DispatchToCurrentThread(NewRunnableMethod(self, &nsNativeAppSupportUnix::DoInteract)); +} + +void +nsNativeAppSupportUnix::DoInteract() +{ + nsCOMPtr<nsIObserverService> obsServ = + mozilla::services::GetObserverService(); + if (!obsServ) { + SmcInteractDone(mSessionConnection, False); + SmcSaveYourselfDone(mSessionConnection, True); + SetClientState(STATE_IDLE); + return; + } + + nsCOMPtr<nsISupportsPRBool> cancelQuit = + do_CreateInstance(NS_SUPPORTS_PRBOOL_CONTRACTID); + + bool abortQuit = false; + if (cancelQuit) { + cancelQuit->SetData(false); + obsServ->NotifyObservers(cancelQuit, "quit-application-requested", nullptr); + + cancelQuit->GetData(&abortQuit); + } + + if (!abortQuit && mClientState == STATE_DISCONNECTED) { + // The session manager disappeared, whilst we were interacting, so + // quit now + nsCOMPtr<nsIAppStartup> appService = + do_GetService("@mozilla.org/toolkit/app-startup;1"); + + if (appService) { + appService->Quit(nsIAppStartup::eForceQuit); + } + } else { + if (mClientState != STATE_SHUTDOWN_CANCELLED) { + // Only do this if the shutdown wasn't cancelled + SmcInteractDone(mSessionConnection, !!abortQuit); + SmcSaveYourselfDone(mSessionConnection, !abortQuit); + } + + SetClientState(STATE_IDLE); + } +} + +void +nsNativeAppSupportUnix::SaveYourselfCB(SmcConn smc_conn, SmPointer client_data, + int save_style, Bool shutdown, + int interact_style, Bool fast) +{ + nsNativeAppSupportUnix *self = + static_cast<nsNativeAppSupportUnix *>(client_data); + + // Expect a SaveYourselfCB if we're registering a new client. + // All properties are already set in Start() so just reply with + // SmcSaveYourselfDone if the callback matches the expected signature. + // + // Ancient versions (?) of xsm do not follow such an early SaveYourself with + // SaveComplete. This is a problem if the application freezes interaction + // while waiting for a response to SmcSaveYourselfDone. So never freeze + // interaction when in STATE_REGISTERING. + // + // That aside, we could treat each combination of flags appropriately and not + // special-case this. + if (self->mClientState == STATE_REGISTERING) { + self->SetClientState(STATE_IDLE); + + if (save_style == SmSaveLocal && interact_style == SmInteractStyleNone && + !shutdown && !fast) { + SmcSaveYourselfDone(self->mSessionConnection, True); + return; + } + } + + if (self->mClientState == STATE_SHUTDOWN_CANCELLED) { + // The last shutdown request was cancelled whilst we were interacting, + // and we haven't finished interacting yet. Switch the state back again + self->SetClientState(STATE_INTERACTING); + } + + nsCOMPtr<nsIObserverService> obsServ = + mozilla::services::GetObserverService(); + if (!obsServ) { + SmcSaveYourselfDone(smc_conn, True); + return; + } + + bool status = false; + if (save_style != SmSaveGlobal) { + nsCOMPtr<nsISupportsPRBool> didSaveSession = + do_CreateInstance(NS_SUPPORTS_PRBOOL_CONTRACTID); + + if (!didSaveSession) { + SmcSaveYourselfDone(smc_conn, True); + return; + } + + // Notify observers to save the session state + didSaveSession->SetData(false); + obsServ->NotifyObservers(didSaveSession, "session-save", nullptr); + + didSaveSession->GetData(&status); + } + + // If the interact style permits us to, we are shutting down and we didn't + // manage to (or weren't asked to) save the local state, then notify the user + // in advance that we are doing to quit (assuming that we aren't already + // doing so) + if (!status && shutdown && interact_style != SmInteractStyleNone) { + if (self->mClientState != STATE_INTERACTING) { + SmcInteractRequest(smc_conn, SmDialogNormal, + nsNativeAppSupportUnix::InteractCB, client_data); + } + } else { + SmcSaveYourselfDone(smc_conn, True); + } +} + +void +nsNativeAppSupportUnix::DieCB(SmcConn smc_conn, SmPointer client_data) +{ + nsCOMPtr<nsIAppStartup> appService = + do_GetService("@mozilla.org/toolkit/app-startup;1"); + + if (appService) { + appService->Quit(nsIAppStartup::eForceQuit); + } + // Quit causes the shutdown to begin but the shutdown process is asynchronous + // so we can't DisconnectFromSM() yet +} + +void +nsNativeAppSupportUnix::ShutdownCancelledCB(SmcConn smc_conn, + SmPointer client_data) +{ + nsNativeAppSupportUnix *self = + static_cast<nsNativeAppSupportUnix *>(client_data); + + // Interacting is the only time when we wouldn't already have called + // SmcSaveYourselfDone. Do that now, then set the state to make sure we + // don't send it again after finishing interacting + if (self->mClientState == STATE_INTERACTING) { + SmcSaveYourselfDone(smc_conn, False); + self->SetClientState(STATE_SHUTDOWN_CANCELLED); + } +} + +void +nsNativeAppSupportUnix::DisconnectFromSM() +{ + // the SM is free to exit any time after we disconnect, so callers must be + // sure to have reached a sufficiently advanced phase of shutdown that there + // is no risk of data loss: + // e.g. all async writes are complete by the end of "profile-before-change" + if (mSessionConnection) { + SetClientState(STATE_DISCONNECTED); + SmcCloseConnection(mSessionConnection, 0, nullptr); + mSessionConnection = nullptr; + gdk_x11_set_sm_client_id(nullptr); // follow gnome-client behaviour + } +} + +static void +SetSMValue(SmPropValue& val, const nsCString& data) +{ + val.value = static_cast<SmPointer>(const_cast<char*>(data.get())); + val.length = data.Length(); +} + +static void +SetSMProperty(SmProp& prop, const char* name, const char* type, int numVals, + SmPropValue vals[]) +{ + prop.name = const_cast<char*>(name); + prop.type = const_cast<char*>(type); + prop.num_vals = numVals; + prop.vals = vals; +} +#endif /* MOZ_X11 */ + +static void RemoveArg(char **argv) +{ + do { + *argv = *(argv + 1); + ++argv; + } while (*argv); + + --gArgc; +} + +NS_IMETHODIMP +nsNativeAppSupportUnix::Start(bool *aRetVal) +{ + NS_ASSERTION(gAppData, "gAppData must not be null."); + +// The dbus library is used by both nsWifiScannerDBus and BluetoothDBusService, +// from diffrent threads. This could lead to race conditions if the dbus is not +// initialized before making any other library calls. +#ifdef MOZ_ENABLE_DBUS + dbus_threads_init_default(); +#endif + +#if (MOZ_WIDGET_GTK == 2) + if (gtk_major_version < MIN_GTK_MAJOR_VERSION || + (gtk_major_version == MIN_GTK_MAJOR_VERSION && gtk_minor_version < MIN_GTK_MINOR_VERSION)) { + GtkWidget* versionErrDialog = gtk_message_dialog_new(nullptr, + GtkDialogFlags(GTK_DIALOG_MODAL | + GTK_DIALOG_DESTROY_WITH_PARENT), + GTK_MESSAGE_ERROR, + GTK_BUTTONS_OK, + UNSUPPORTED_GTK_MSG, + gtk_major_version, + gtk_minor_version, + MIN_GTK_MAJOR_VERSION, + MIN_GTK_MINOR_VERSION); + gtk_dialog_run(GTK_DIALOG(versionErrDialog)); + gtk_widget_destroy(versionErrDialog); + MozExpectedExit(); + exit(0); + } +#endif + + *aRetVal = true; + +#ifdef MOZ_X11 + gboolean sm_disable = FALSE; + if (!getenv("SESSION_MANAGER")) { + sm_disable = TRUE; + } + + nsAutoCString prev_client_id; + + char **curarg = gArgv + 1; + while (*curarg) { + char *arg = *curarg; + if (arg[0] == '-' && arg[1] == '-') { + arg += 2; + if (!strcmp(arg, "sm-disable")) { + RemoveArg(curarg); + sm_disable = TRUE; + continue; + } else if (!strcmp(arg, "sm-client-id")) { + RemoveArg(curarg); + if (*curarg[0] != '-') { + prev_client_id = *curarg; + RemoveArg(curarg); + } + continue; + } + } + + ++curarg; + } + + if (prev_client_id.IsEmpty()) { + prev_client_id = getenv("DESKTOP_AUTOSTART_ID"); + } + + // We don't want child processes to use the same ID + unsetenv("DESKTOP_AUTOSTART_ID"); + + char *client_id = nullptr; + if (!sm_disable) { + PRLibrary *iceLib = PR_LoadLibrary("libICE.so.6"); + if (!iceLib) { + return NS_OK; + } + + PRLibrary *smLib = PR_LoadLibrary("libSM.so.6"); + if (!smLib) { + PR_UnloadLibrary(iceLib); + return NS_OK; + } + + IceSetIOErrorHandler = (IceSetIOErrorHandlerFn)PR_FindFunctionSymbol(iceLib, "IceSetIOErrorHandler"); + IceAddConnectionWatch = (IceAddConnectionWatchFn)PR_FindFunctionSymbol(iceLib, "IceAddConnectionWatch"); + IceConnectionNumber = (IceConnectionNumberFn)PR_FindFunctionSymbol(iceLib, "IceConnectionNumber"); + IceProcessMessages = (IceProcessMessagesFn)PR_FindFunctionSymbol(iceLib, "IceProcessMessages"); + IceGetConnectionContext = (IceGetConnectionContextFn)PR_FindFunctionSymbol(iceLib, "IceGetConnectionContext"); + if (!IceSetIOErrorHandler || !IceAddConnectionWatch || + !IceConnectionNumber || !IceProcessMessages || !IceGetConnectionContext) { + PR_UnloadLibrary(iceLib); + PR_UnloadLibrary(smLib); + return NS_OK; + } + + SmcInteractDone = (SmcInteractDoneFn)PR_FindFunctionSymbol(smLib, "SmcInteractDone"); + SmcSaveYourselfDone = (SmcSaveYourselfDoneFn)PR_FindFunctionSymbol(smLib, "SmcSaveYourselfDone"); + SmcInteractRequest = (SmcInteractRequestFn)PR_FindFunctionSymbol(smLib, "SmcInteractRequest"); + SmcCloseConnection = (SmcCloseConnectionFn)PR_FindFunctionSymbol(smLib, "SmcCloseConnection"); + SmcOpenConnection = (SmcOpenConnectionFn)PR_FindFunctionSymbol(smLib, "SmcOpenConnection"); + SmcSetProperties = (SmcSetPropertiesFn)PR_FindFunctionSymbol(smLib, "SmcSetProperties"); + if (!SmcInteractDone || !SmcSaveYourselfDone || !SmcInteractRequest || + !SmcCloseConnection || !SmcOpenConnection || !SmcSetProperties) { + PR_UnloadLibrary(iceLib); + PR_UnloadLibrary(smLib); + return NS_OK; + } + + ice_init(); + + // all callbacks are mandatory in libSM 1.0, so listen even if we don't care. + unsigned long mask = SmcSaveYourselfProcMask | SmcDieProcMask | + SmcSaveCompleteProcMask | SmcShutdownCancelledProcMask; + + SmcCallbacks callbacks; + callbacks.save_yourself.callback = nsNativeAppSupportUnix::SaveYourselfCB; + callbacks.save_yourself.client_data = static_cast<SmPointer>(this); + + callbacks.die.callback = nsNativeAppSupportUnix::DieCB; + callbacks.die.client_data = static_cast<SmPointer>(this); + + callbacks.save_complete.callback = nsNativeAppSupportUnix::SaveCompleteCB; + callbacks.save_complete.client_data = nullptr; + + callbacks.shutdown_cancelled.callback = + nsNativeAppSupportUnix::ShutdownCancelledCB; + callbacks.shutdown_cancelled.client_data = static_cast<SmPointer>(this); + + char errbuf[256]; + mSessionConnection = SmcOpenConnection(nullptr, this, SmProtoMajor, + SmProtoMinor, mask, &callbacks, + prev_client_id.get(), &client_id, + sizeof(errbuf), errbuf); + } + + if (!mSessionConnection) { + return NS_OK; + } + + LogModule::Init(); // need to make sure initialized before SetClientState + if (prev_client_id.IsEmpty() || + (client_id && !prev_client_id.Equals(client_id))) { + SetClientState(STATE_REGISTERING); + } else { + SetClientState(STATE_IDLE); + } + + gdk_x11_set_sm_client_id(client_id); + + // Set SM Properties + // SmCloneCommand, SmProgram, SmRestartCommand, SmUserID are required + // properties so must be set, and must have a sensible fallback value. + + // Determine executable path to use for XSMP session restore + + // Is there a request to suppress default binary launcher? + nsAutoCString path(getenv("MOZ_APP_LAUNCHER")); + + if (path.IsEmpty()) { + NS_ASSERTION(gDirServiceProvider, "gDirServiceProvider is NULL! This shouldn't happen!"); + nsCOMPtr<nsIFile> executablePath; + nsresult rv; + + bool dummy; + rv = gDirServiceProvider->GetFile(XRE_EXECUTABLE_FILE, &dummy, getter_AddRefs(executablePath)); + + if (NS_SUCCEEDED(rv)) { + // Strip off the -bin suffix to get the shell script we should run; this is what Breakpad does + nsAutoCString leafName; + rv = executablePath->GetNativeLeafName(leafName); + if (NS_SUCCEEDED(rv) && StringEndsWith(leafName, NS_LITERAL_CSTRING("-bin"))) { + leafName.SetLength(leafName.Length() - strlen("-bin")); + executablePath->SetNativeLeafName(leafName); + } + + executablePath->GetNativePath(path); + } + } + + if (path.IsEmpty()) { + // can't determine executable path. Best fallback is name from + // application.ini but it might not resolve to the same executable at + // launch time. + path = gAppData->name; // will always be set + ToLowerCase(path); + MOZ_LOG(sMozSMLog, LogLevel::Warning, + ("Could not determine executable path. Falling back to %s.", path.get())); + } + + SmProp propRestart, propClone, propProgram, propUser, *props[4]; + SmPropValue valsRestart[3], valsClone[1], valsProgram[1], valsUser[1]; + int n = 0; + + NS_NAMED_LITERAL_CSTRING(kClientIDParam, "--sm-client-id"); + + SetSMValue(valsRestart[0], path); + SetSMValue(valsRestart[1], kClientIDParam); + SetSMValue(valsRestart[2], nsDependentCString(client_id)); + SetSMProperty(propRestart, SmRestartCommand, SmLISTofARRAY8, 3, valsRestart); + props[n++] = &propRestart; + + SetSMValue(valsClone[0], path); + SetSMProperty(propClone, SmCloneCommand, SmLISTofARRAY8, 1, valsClone); + props[n++] = &propClone; + + nsAutoCString appName(gAppData->name); // will always be set + ToLowerCase(appName); + + SetSMValue(valsProgram[0], appName); + SetSMProperty(propProgram, SmProgram, SmARRAY8, 1, valsProgram); + props[n++] = &propProgram; + + nsAutoCString userName; // username that started the program + struct passwd* pw = getpwuid(getuid()); + if (pw && pw->pw_name) { + userName = pw->pw_name; + } else { + userName = NS_LITERAL_CSTRING("nobody"); + MOZ_LOG(sMozSMLog, LogLevel::Warning, + ("Could not determine user-name. Falling back to %s.", userName.get())); + } + + SetSMValue(valsUser[0], userName); + SetSMProperty(propUser, SmUserID, SmARRAY8, 1, valsUser); + props[n++] = &propUser; + + SmcSetProperties(mSessionConnection, n, props); + + g_free(client_id); +#endif /* MOZ_X11 */ + + return NS_OK; +} + +NS_IMETHODIMP +nsNativeAppSupportUnix::Stop(bool *aResult) +{ + NS_ENSURE_ARG(aResult); + *aResult = true; + return NS_OK; +} + +NS_IMETHODIMP +nsNativeAppSupportUnix::Enable() +{ + return NS_OK; +} + +nsresult +NS_CreateNativeAppSupport(nsINativeAppSupport **aResult) +{ + nsNativeAppSupportBase* native = new nsNativeAppSupportUnix(); + if (!native) + return NS_ERROR_OUT_OF_MEMORY; + + *aResult = native; + NS_ADDREF(*aResult); + + return NS_OK; +} diff --git a/toolkit/xre/nsNativeAppSupportWin.cpp b/toolkit/xre/nsNativeAppSupportWin.cpp new file mode 100644 index 0000000000..0605ccb5af --- /dev/null +++ b/toolkit/xre/nsNativeAppSupportWin.cpp @@ -0,0 +1,1541 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "nsNativeAppSupportBase.h" +#include "nsNativeAppSupportWin.h" +#include "nsAppRunner.h" +#include "nsXULAppAPI.h" +#include "nsString.h" +#include "nsIBrowserDOMWindow.h" +#include "nsICommandLineRunner.h" +#include "nsCOMPtr.h" +#include "nsXPIDLString.h" +#include "nsIComponentManager.h" +#include "nsIServiceManager.h" +#include "nsIDOMChromeWindow.h" +#include "nsXPCOM.h" +#include "nsISupportsPrimitives.h" +#include "nsIWindowWatcher.h" +#include "nsPIDOMWindow.h" +#include "nsGlobalWindow.h" +#include "nsIDocShell.h" +#include "nsIDocShellTreeItem.h" +#include "nsIBaseWindow.h" +#include "nsIWidget.h" +#include "nsIAppShellService.h" +#include "nsIXULWindow.h" +#include "nsIInterfaceRequestor.h" +#include "nsIInterfaceRequestorUtils.h" +#include "nsIPromptService.h" +#include "nsNetCID.h" +#include "nsNetUtil.h" +#include "mozilla/Services.h" +#include "nsIFile.h" +#include "nsIObserver.h" +#include "nsIObserverService.h" +#include "nsIDOMLocation.h" +#include "nsIWebNavigation.h" +#include "nsIWindowMediator.h" +#include "nsNativeCharsetUtils.h" +#include "nsIAppStartup.h" + +#include <windows.h> +#include <shellapi.h> +#include <ddeml.h> +#include <stdlib.h> +#include <stdio.h> +#include <io.h> +#include <direct.h> +#include <fcntl.h> + +using namespace mozilla; + +static HWND hwndForDOMWindow( mozIDOMWindowProxy * ); + +static +nsresult +GetMostRecentWindow(const char16_t* aType, mozIDOMWindowProxy** aWindow) { + nsresult rv; + nsCOMPtr<nsIWindowMediator> med( do_GetService( NS_WINDOWMEDIATOR_CONTRACTID, &rv ) ); + if ( NS_FAILED( rv ) ) + return rv; + + if ( med ) + return med->GetMostRecentWindow( aType, aWindow ); + + return NS_ERROR_FAILURE; +} + +static +void +activateWindow( mozIDOMWindowProxy *win ) { + // Try to get native window handle. + HWND hwnd = hwndForDOMWindow( win ); + if ( hwnd ) { + // Restore the window if it is minimized. + if ( ::IsIconic( hwnd ) ) { + ::ShowWindow( hwnd, SW_RESTORE ); + } + // Use the OS call, if possible. + ::SetForegroundWindow( hwnd ); + } else { + // Use internal method. + nsCOMPtr<nsPIDOMWindowOuter> piWin = nsPIDOMWindowOuter::From(win); + piWin->Focus(); + } +} + + +#ifdef DEBUG_law +#undef MOZ_DEBUG_DDE +#define MOZ_DEBUG_DDE 1 +#endif + +// Simple Win32 mutex wrapper. +struct Win32Mutex { + Win32Mutex( const char16_t *name ) + : mName( name ), + mHandle( 0 ), + mState( -1 ) { + mHandle = CreateMutexW( 0, FALSE, mName.get() ); +#if MOZ_DEBUG_DDE + printf( "CreateMutex error = 0x%08X\n", (int)GetLastError() ); +#endif + } + ~Win32Mutex() { + if ( mHandle ) { + // Make sure we release it if we own it. + Unlock(); + + BOOL rc = CloseHandle( mHandle ); +#if MOZ_DEBUG_DDE + if ( !rc ) { + printf( "CloseHandle error = 0x%08X\n", (int)GetLastError() ); + } +#endif + } + } + BOOL Lock( DWORD timeout ) { + if ( mHandle ) { +#if MOZ_DEBUG_DDE + printf( "Waiting (%d msec) for DDE mutex...\n", (int)timeout ); +#endif + mState = WaitForSingleObject( mHandle, timeout ); +#if MOZ_DEBUG_DDE + printf( "...wait complete, result = 0x%08X, GetLastError=0x%08X\n", (int)mState, (int)::GetLastError() ); +#endif + return mState == WAIT_OBJECT_0 || mState == WAIT_ABANDONED; + } else { + return FALSE; + } + } + void Unlock() { + if ( mHandle && mState == WAIT_OBJECT_0 ) { +#if MOZ_DEBUG_DDE + printf( "Releasing DDE mutex\n" ); +#endif + ReleaseMutex( mHandle ); + mState = -1; + } + } +private: + nsString mName; + HANDLE mHandle; + DWORD mState; +}; + +/* DDE Notes + * + * This section describes the Win32 DDE service implementation for + * Mozilla. DDE is used on Win32 platforms to communicate between + * separate instances of mozilla.exe (or other Mozilla-based + * executables), or, between the Win32 desktop shell and Mozilla. + * + * The first instance of Mozilla will become the "server" and + * subsequent executables (and the shell) will use DDE to send + * requests to that process. The requests are DDE "execute" requests + * that pass the command line arguments. + * + * Mozilla registers the DDE application "Mozilla" and currently + * supports only the "WWW_OpenURL" topic. This should be reasonably + * compatible with applications that interfaced with Netscape + * Communicator (and its predecessors?). Note that even that topic + * may not be supported in a compatible fashion as the command-line + * options for Mozilla are different than for Communiator. + * + * It is imperative that at most one instance of Mozilla execute in + * "server mode" at any one time. The "native app support" in Mozilla + * on Win32 ensures that only the server process performs XPCOM + * initialization (that is not required for subsequent client processes + * to communicate with the server process). + * + * To guarantee that only one server starts up, a Win32 "mutex" is used + * to ensure only one process executes the server-detection code. That + * code consists of initializing DDE and doing a DdeConnect to Mozilla's + * application/topic. If that connection succeeds, then a server process + * must be running already. + * + * Otherwise, no server has started. In that case, the current process + * calls DdeNameService to register that application/topic. Only at that + * point does the mutex get released. + * + * There are a couple of subtleties that one should be aware of: + * + * 1. It is imperative that DdeInitialize be called only after the mutex + * lock has been obtained. The reason is that at shutdown, DDE + * notifications go out to all initialized DDE processes. Thus, if + * the mutex is owned by a terminating intance of Mozilla, then + * calling DdeInitialize and then WaitForSingleObject will cause the + * DdeUninitialize from the terminating process to "hang" until the + * process waiting for the mutex times out (and can then service the + * notification that the DDE server is terminating). So, don't mess + * with the sequence of things in the startup/shutdown logic. + * + * 2. All mutex requests are made with a reasonably long timeout value and + * are designed to "fail safe" (i.e., a timeout is treated as failure). + * + * 3. An attempt has been made to minimize the degree to which the main + * Mozilla application logic needs to be aware of the DDE mechanisms + * implemented herein. As a result, this module surfaces a very + * large-grained interface, consisting of simple start/stop methods. + * As a consequence, details of certain scenarios can be "lost." + * Particularly, incoming DDE requests can arrive after this module + * initiates the DDE server, but before Mozilla is initialized to the + * point where those requests can be serviced (e.g., open a browser + * window to a particular URL). Since the client process sends the + * request early on, it may not be prepared to respond to that error. + * Thus, such situations may fail silently. The design goal is that + * they fail harmlessly. Refinements on this point will be made as + * details emerge (and time permits). + */ + +/* Update 2001 March + * + * A significant DDE bug in Windows is causing Mozilla to get wedged at + * startup. This is detailed in Bugzill bug 53952 + * (http://bugzilla.mozilla.org/show_bug.cgi?id=53952). + * + * To resolve this, we are using a new strategy: + * o Use a "message window" to detect that Mozilla is already running and + * to pass requests from a second instance back to the first; + * o Run only as a "DDE server" (not as DDE client); this avoids the + * problematic call to DDEConnect(). + * + * We still use the mutex semaphore to protect the code that detects + * whether Mozilla is already running. + */ + +/* Update 2007 January + * + * A change in behavior was implemented in July 2004 which made the + * application on launch to add and on quit to remove the ddexec registry key. + * See bug 246078. + * Windows Vista has changed the methods used to set an application as default + * and the new methods are incompatible with removing the ddeexec registry key. + * See bug 353089. + * + * OS DDE Sequence: + * 1. OS checks if the dde name is registered. + * 2. If it is registered the OS sends a DDE request with the WWW_OpenURL topic + * and the params as specified in the default value of the ddeexec registry + * key for the verb (e.g. open). + * 3. If it isn't registered the OS launches the executable defined in the + * verb's (e.g. open) command registry key. + * 4. If the ifexec registry key is not present the OS sends a DDE request with + * the WWW_OpenURL topic and the params as specified in the default value of + * the ddeexec registry key for the verb (e.g. open). + * 5. If the ifexec registry key is present the OS sends a DDE request with the + * WWW_OpenURL topic and the params as specified in the ifexec registry key + * for the verb (e.g. open). + * + * Application DDE Sequence: + * 1. If the application is running a DDE request is received with the + * WWW_OpenURL topic and the params as specified in the default value of the + * ddeexec registry key (e.g. "%1",,0,0,,,, where '%1' is the url to open) + * for the verb (e.g. open). + * 2. If the application is not running it is launched with the --requestPending + * and the --url argument. + * 2.1 If the application does not need to restart and the --requestPending + * argument is present the accompanying url will not be used. Instead the + * application will wait for the DDE message to open the url. + * 2.2 If the application needs to restart the --requestPending argument is + * removed from the arguments used to restart the application and the url + * will be handled normally. + * + * Note: Due to a bug in IE the ifexec key should not be used (see bug 355650). + */ + +class nsNativeAppSupportWin : public nsNativeAppSupportBase, + public nsIObserver +{ +public: + NS_DECL_NSIOBSERVER + NS_DECL_ISUPPORTS_INHERITED + + // Overrides of base implementation. + NS_IMETHOD Start( bool *aResult ); + NS_IMETHOD Stop( bool *aResult ); + NS_IMETHOD Quit(); + NS_IMETHOD Enable(); + // The "old" Start method (renamed). + NS_IMETHOD StartDDE(); + // Utility function to handle a Win32-specific command line + // option: "--console", which dynamically creates a Windows + // console. + void CheckConsole(); + +private: + ~nsNativeAppSupportWin() {} + static void HandleCommandLine(const char* aCmdLineString, nsIFile* aWorkingDir, uint32_t aState); + static HDDEDATA CALLBACK HandleDDENotification( UINT uType, + UINT uFmt, + HCONV hconv, + HSZ hsz1, + HSZ hsz2, + HDDEDATA hdata, + ULONG_PTR dwData1, + ULONG_PTR dwData2 ); + static void ParseDDEArg( HSZ args, int index, nsString& string); + static void ParseDDEArg( const WCHAR* args, int index, nsString& aString); + static HDDEDATA CreateDDEData( DWORD value ); + static HDDEDATA CreateDDEData( LPBYTE value, DWORD len ); + static bool InitTopicStrings(); + static int FindTopic( HSZ topic ); + static void ActivateLastWindow(); + static nsresult OpenWindow( const char *urlstr, const char *args ); + static nsresult OpenBrowserWindow(); + static void SetupSysTrayIcon(); + static void RemoveSysTrayIcon(); + + static int mConversations; + enum { + topicOpenURL, + topicActivate, + topicCancelProgress, + topicVersion, + topicRegisterViewer, + topicUnRegisterViewer, + topicGetWindowInfo, + // Note: Insert new values above this line!!!!! + topicCount // Count of the number of real topics + }; + static HSZ mApplication, mTopics[ topicCount ]; + static DWORD mInstance; + static bool mCanHandleRequests; + static char16_t mMutexName[]; + friend struct MessageWindow; +}; // nsNativeAppSupportWin + +NS_INTERFACE_MAP_BEGIN(nsNativeAppSupportWin) + NS_INTERFACE_MAP_ENTRY(nsIObserver) +NS_INTERFACE_MAP_END_INHERITING(nsNativeAppSupportBase) + +NS_IMPL_ADDREF_INHERITED(nsNativeAppSupportWin, nsNativeAppSupportBase) +NS_IMPL_RELEASE_INHERITED(nsNativeAppSupportWin, nsNativeAppSupportBase) + +void +UseParentConsole() +{ + // Try to attach console to the parent process. + // It will succeed when the parent process is a command line, + // so that stdio will be displayed in it. + if (AttachConsole(ATTACH_PARENT_PROCESS)) { + // Change std handles to refer to new console handles. + // Before doing so, ensure that stdout/stderr haven't been + // redirected to a valid file. + // The return value for _fileno(<a std handle>) for GUI apps was changed over. + // Until VC7, it was -1. Starting from VC8, it was changed to -2. + // http://msdn.microsoft.com/en-us/library/zs6wbdhx%28v=vs.80%29.aspx + // Starting from VC11, the return value was cahnged to 0 for stdin, + // 1 for stdout, 2 for stdout. Accroding to Microsoft, this is a bug + // which will be fixed in VC14. + // https://connect.microsoft.com/VisualStudio/feedback/details/785119/ + // Although the document does not make it explicit, it looks like + // the return value from _get_osfhandle(_fileno(<a std handle>)) also + // changed to -2 and VC11 and 12 do not have a bug about _get_osfhandle(). + // We support VC10 or later, so it's sufficient to compare the return + // value with -2. + if (_fileno(stdout) == -2 || + _get_osfhandle(fileno(stdout)) == -2) + freopen("CONOUT$", "w", stdout); + // Merge stderr into CONOUT$ since there isn't any `CONERR$`. + // http://msdn.microsoft.com/en-us/library/windows/desktop/ms683231%28v=vs.85%29.aspx + if (_fileno(stderr) == -2 || + _get_osfhandle(fileno(stderr)) == -2) + freopen("CONOUT$", "w", stderr); + if (_fileno(stdin) == -2 || _get_osfhandle(fileno(stdin)) == -2) + freopen("CONIN$", "r", stdin); + } +} + +void +nsNativeAppSupportWin::CheckConsole() { + for ( int i = 1; i < gArgc; i++ ) { + if ( strcmp( "-console", gArgv[i] ) == 0 || + strcmp( "--console", gArgv[i] ) == 0 || + strcmp( "/console", gArgv[i] ) == 0 ) { + // Users wants to make sure we have a console. + // Try to allocate one. + BOOL rc = ::AllocConsole(); + if ( rc ) { + // Console allocated. Fix it up so that output works in + // all cases. See http://support.microsoft.com/support/kb/articles/q105/3/05.asp. + + // stdout + int hCrt = ::_open_osfhandle( (intptr_t)GetStdHandle( STD_OUTPUT_HANDLE ), + _O_TEXT ); + if ( hCrt != -1 ) { + FILE *hf = ::_fdopen( hCrt, "w" ); + if ( hf ) { + *stdout = *hf; +#ifdef DEBUG + ::fprintf( stdout, "stdout directed to dynamic console\n" ); +#endif + } + } + + // stderr + hCrt = ::_open_osfhandle( (intptr_t)::GetStdHandle( STD_ERROR_HANDLE ), + _O_TEXT ); + if ( hCrt != -1 ) { + FILE *hf = ::_fdopen( hCrt, "w" ); + if ( hf ) { + *stderr = *hf; +#ifdef DEBUG + ::fprintf( stderr, "stderr directed to dynamic console\n" ); +#endif + } + } + + // stdin? + /* Don't bother for now. + hCrt = ::_open_osfhandle( (long)::GetStdHandle( STD_INPUT_HANDLE ), + _O_TEXT ); + if ( hCrt != -1 ) { + FILE *hf = ::_fdopen( hCrt, "r" ); + if ( hf ) { + *stdin = *hf; + } + } + */ + } else { + // Failed. Probably because there already is one. + // There's little we can do, in any case. + } + // Remove the console argument from the command line. + do { + gArgv[i] = gArgv[i + 1]; + ++i; + } while (gArgv[i]); + + --gArgc; + + } else if ( strcmp( "-attach-console", gArgv[i] ) == 0 + || + strcmp( "/attach-console", gArgv[i] ) == 0 ) { + UseParentConsole(); + } + } + + return; +} + + +// Create and return an instance of class nsNativeAppSupportWin. +nsresult +NS_CreateNativeAppSupport( nsINativeAppSupport **aResult ) { + nsNativeAppSupportWin *pNative = new nsNativeAppSupportWin; + if (!pNative) return NS_ERROR_OUT_OF_MEMORY; + + // Check for dynamic console creation request. + pNative->CheckConsole(); + + *aResult = pNative; + NS_ADDREF( *aResult ); + + return NS_OK; +} + +// Constants +#define MOZ_DDE_APPLICATION "Mozilla" +#define MOZ_MUTEX_NAMESPACE L"Local\\" +#define MOZ_STARTUP_MUTEX_NAME L"StartupMutex" +#define MOZ_DDE_START_TIMEOUT 30000 +#define MOZ_DDE_STOP_TIMEOUT 15000 +#define MOZ_DDE_EXEC_TIMEOUT 15000 + +// The array entries must match the enum ordering! +const char * const topicNames[] = { "WWW_OpenURL", + "WWW_Activate", + "WWW_CancelProgress", + "WWW_Version", + "WWW_RegisterViewer", + "WWW_UnRegisterViewer", + "WWW_GetWindowInfo" }; + +// Static member definitions. +int nsNativeAppSupportWin::mConversations = 0; +HSZ nsNativeAppSupportWin::mApplication = 0; +HSZ nsNativeAppSupportWin::mTopics[nsNativeAppSupportWin::topicCount] = { 0 }; +DWORD nsNativeAppSupportWin::mInstance = 0; +bool nsNativeAppSupportWin::mCanHandleRequests = false; + +char16_t nsNativeAppSupportWin::mMutexName[ 128 ] = { 0 }; + + +// Message window encapsulation. +struct MessageWindow { + // ctor/dtor are simplistic + MessageWindow() { + // Try to find window. + mHandle = ::FindWindowW( className(), 0 ); + } + + // Act like an HWND. + operator HWND() { + return mHandle; + } + + // Class name: appName + "MessageWindow" + static const wchar_t *className() { + static wchar_t classNameBuffer[128]; + static wchar_t *mClassName = 0; + if ( !mClassName ) { + ::_snwprintf(classNameBuffer, + 128, // size of classNameBuffer in PRUnichars + L"%s%s", + static_cast<const wchar_t*>(NS_ConvertUTF8toUTF16(gAppData->remotingName).get()), + L"MessageWindow" ); + mClassName = classNameBuffer; + } + return mClassName; + } + + // Create: Register class and create window. + NS_IMETHOD Create() { + WNDCLASSW classStruct = { 0, // style + &MessageWindow::WindowProc, // lpfnWndProc + 0, // cbClsExtra + 0, // cbWndExtra + 0, // hInstance + 0, // hIcon + 0, // hCursor + 0, // hbrBackground + 0, // lpszMenuName + className() }; // lpszClassName + + // Register the window class. + NS_ENSURE_TRUE( ::RegisterClassW( &classStruct ), NS_ERROR_FAILURE ); + + // Create the window. + NS_ENSURE_TRUE( ( mHandle = ::CreateWindowW(className(), + 0, // title + WS_CAPTION, // style + 0,0,0,0, // x, y, cx, cy + 0, // parent + 0, // menu + 0, // instance + 0 ) ), // create struct + NS_ERROR_FAILURE ); + +#if MOZ_DEBUG_DDE + printf( "Message window = 0x%08X\n", (int)mHandle ); +#endif + + return NS_OK; + } + + // Destory: Get rid of window and reset mHandle. + NS_IMETHOD Destroy() { + nsresult retval = NS_OK; + + if ( mHandle ) { + // DestroyWindow can only destroy windows created from + // the same thread. + BOOL desRes = DestroyWindow( mHandle ); + if ( FALSE != desRes ) { + mHandle = nullptr; + } + else { + retval = NS_ERROR_FAILURE; + } + } + + return retval; + } + + // SendRequest: Pass the command line via WM_COPYDATA to message window. + NS_IMETHOD SendRequest() { + WCHAR *cmd = ::GetCommandLineW(); + WCHAR cwd[MAX_PATH]; + _wgetcwd(cwd, MAX_PATH); + + // Construct a narrow UTF8 buffer <commandline>\0<workingdir>\0 + NS_ConvertUTF16toUTF8 utf8buffer(cmd); + utf8buffer.Append('\0'); + AppendUTF16toUTF8(cwd, utf8buffer); + utf8buffer.Append('\0'); + + // We used to set dwData to zero, when we didn't send the working dir. + // Now we're using it as a version number. + COPYDATASTRUCT cds = { + 1, + utf8buffer.Length(), + (void*) utf8buffer.get() + }; + // Bring the already running Mozilla process to the foreground. + // nsWindow will restore the window (if minimized) and raise it. + ::SetForegroundWindow( mHandle ); + ::SendMessage( mHandle, WM_COPYDATA, 0, (LPARAM)&cds ); + return NS_OK; + } + + // Window proc. + static LRESULT CALLBACK WindowProc( HWND msgWindow, UINT msg, WPARAM wp, LPARAM lp ) { + if ( msg == WM_COPYDATA ) { + if (!nsNativeAppSupportWin::mCanHandleRequests) + return FALSE; + + // This is an incoming request. + COPYDATASTRUCT *cds = (COPYDATASTRUCT*)lp; +#if MOZ_DEBUG_DDE + printf( "Incoming request: %s\n", (const char*)cds->lpData ); +#endif + nsCOMPtr<nsIFile> workingDir; + + if (1 >= cds->dwData) { + char* wdpath = (char*) cds->lpData; + // skip the command line, and get the working dir of the + // other process, which is after the first null char + while (*wdpath) + ++wdpath; + + ++wdpath; + +#ifdef MOZ_DEBUG_DDE + printf( "Working dir: %s\n", wdpath); +#endif + + NS_NewLocalFile(NS_ConvertUTF8toUTF16(wdpath), + false, + getter_AddRefs(workingDir)); + } + (void)nsNativeAppSupportWin::HandleCommandLine((char*)cds->lpData, workingDir, nsICommandLine::STATE_REMOTE_AUTO); + + // Get current window and return its window handle. + nsCOMPtr<mozIDOMWindowProxy> win; + GetMostRecentWindow( 0, getter_AddRefs( win ) ); + return win ? (LRESULT)hwndForDOMWindow( win ) : 0; + } + return DefWindowProc( msgWindow, msg, wp, lp ); + } + +private: + HWND mHandle; +}; // struct MessageWindow + +/* Start: Tries to find the "message window" to determine if it + * exists. If so, then Mozilla is already running. In that + * case, we use the handle to the "message" window and send + * a request corresponding to this process's command line + * options. + * + * If not, then this is the first instance of Mozilla. In + * that case, we create and set up the message window. + * + * The checking for existence of the message window must + * be protected by use of a mutex semaphore. + */ +NS_IMETHODIMP +nsNativeAppSupportWin::Start( bool *aResult ) { + NS_ENSURE_ARG( aResult ); + NS_ENSURE_TRUE( mInstance == 0, NS_ERROR_NOT_INITIALIZED ); + NS_ENSURE_STATE( gAppData ); + + if (getenv("MOZ_NO_REMOTE")) + { + *aResult = true; + return NS_OK; + } + + nsresult rv = NS_ERROR_FAILURE; + *aResult = false; + + // Grab mutex first. + + // Build mutex name from app name. + ::_snwprintf(reinterpret_cast<wchar_t*>(mMutexName), + sizeof mMutexName / sizeof(char16_t), L"%s%s%s", + MOZ_MUTEX_NAMESPACE, + static_cast<const wchar_t*>(NS_ConvertUTF8toUTF16(gAppData->name).get()), + MOZ_STARTUP_MUTEX_NAME ); + Win32Mutex startupLock = Win32Mutex( mMutexName ); + + NS_ENSURE_TRUE( startupLock.Lock( MOZ_DDE_START_TIMEOUT ), NS_ERROR_FAILURE ); + + // Search for existing message window. + MessageWindow msgWindow; + if ( (HWND)msgWindow ) { + // We are a client process. Pass request to message window. + rv = msgWindow.SendRequest(); + } else { + // We will be server. + rv = msgWindow.Create(); + if ( NS_SUCCEEDED( rv ) ) { + // Start up DDE server. + this->StartDDE(); + // Tell caller to spin message loop. + *aResult = true; + } + } + + startupLock.Unlock(); + + return rv; +} + +bool +nsNativeAppSupportWin::InitTopicStrings() { + for ( int i = 0; i < topicCount; i++ ) { + if ( !( mTopics[ i ] = DdeCreateStringHandleA( mInstance, const_cast<char *>(topicNames[ i ]), CP_WINANSI ) ) ) { + return false; + } + } + return true; +} + +int +nsNativeAppSupportWin::FindTopic( HSZ topic ) { + for ( int i = 0; i < topicCount; i++ ) { + if ( DdeCmpStringHandles( topic, mTopics[i] ) == 0 ) { + return i; + } + } + return -1; +} + + +// Start DDE server. +// +// This used to be the Start() method when we were using DDE as the +// primary IPC mechanism between secondary Mozilla processes and the +// initial "server" process. +// +// Now, it simply initializes the DDE server. The caller must check +// that this process is to be the server, and, must acquire the DDE +// startup mutex semaphore prior to calling this routine. See ::Start(), +// above. +NS_IMETHODIMP +nsNativeAppSupportWin::StartDDE() { + NS_ENSURE_TRUE( mInstance == 0, NS_ERROR_NOT_INITIALIZED ); + + // Initialize DDE. + NS_ENSURE_TRUE( DMLERR_NO_ERROR == DdeInitialize( &mInstance, + nsNativeAppSupportWin::HandleDDENotification, + APPCLASS_STANDARD, + 0 ), + NS_ERROR_FAILURE ); + + // Allocate DDE strings. + NS_ENSURE_TRUE( ( mApplication = DdeCreateStringHandleA( mInstance, (char*) gAppData->name, CP_WINANSI ) ) && InitTopicStrings(), + NS_ERROR_FAILURE ); + + // Next step is to register a DDE service. + NS_ENSURE_TRUE( DdeNameService( mInstance, mApplication, 0, DNS_REGISTER ), NS_ERROR_FAILURE ); + +#if MOZ_DEBUG_DDE + printf( "DDE server started\n" ); +#endif + + return NS_OK; +} + +// If no DDE conversations are pending, terminate DDE. +NS_IMETHODIMP +nsNativeAppSupportWin::Stop( bool *aResult ) { + NS_ENSURE_ARG( aResult ); + NS_ENSURE_TRUE( mInstance, NS_ERROR_NOT_INITIALIZED ); + + nsresult rv = NS_OK; + *aResult = true; + + Win32Mutex ddeLock( mMutexName ); + + if ( ddeLock.Lock( MOZ_DDE_STOP_TIMEOUT ) ) { + if ( mConversations == 0 ) { + this->Quit(); + } else { + *aResult = false; + } + + ddeLock.Unlock(); + } + else { + // No DDE application name specified, but that's OK. Just + // forge ahead. + *aResult = true; + } + + return rv; +} + +NS_IMETHODIMP +nsNativeAppSupportWin::Observe(nsISupports* aSubject, const char* aTopic, + const char16_t* aData) +{ + if (strcmp(aTopic, "quit-application") == 0) { + Quit(); + } else { + NS_ERROR("Unexpected observer topic."); + } + + return NS_OK; +} + +// Terminate DDE regardless. +NS_IMETHODIMP +nsNativeAppSupportWin::Quit() { + // If another process wants to look for the message window, they need + // to wait to hold the lock, in which case they will not find the + // window as we will destroy ours under our lock. + // When the mutex goes off the stack, it is unlocked via destructor. + Win32Mutex mutexLock(mMutexName); + NS_ENSURE_TRUE(mutexLock.Lock(MOZ_DDE_START_TIMEOUT), NS_ERROR_FAILURE); + + // If we've got a message window to receive IPC or new window requests, + // get rid of it as we are shutting down. + // Note: Destroy calls DestroyWindow, which will only work on a window + // created by the same thread. + MessageWindow mw; + mw.Destroy(); + + if ( mInstance ) { + // Unregister application name. + DdeNameService( mInstance, mApplication, 0, DNS_UNREGISTER ); + // Clean up strings. + if ( mApplication ) { + DdeFreeStringHandle( mInstance, mApplication ); + mApplication = 0; + } + for ( int i = 0; i < topicCount; i++ ) { + if ( mTopics[i] ) { + DdeFreeStringHandle( mInstance, mTopics[i] ); + mTopics[i] = 0; + } + } + DdeUninitialize( mInstance ); + mInstance = 0; +#if MOZ_DEBUG_DDE + printf( "DDE server stopped\n" ); +#endif + } + + return NS_OK; +} + +NS_IMETHODIMP +nsNativeAppSupportWin::Enable() +{ + mCanHandleRequests = true; + + nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService(); + if (obs) { + obs->AddObserver(this, "quit-application", false); + } else { + NS_ERROR("No observer service?"); + } + + return NS_OK; +} + +#if MOZ_DEBUG_DDE +// Macro to generate case statement for a given XTYP value. +#define XTYP_CASE(t) \ + case t: result = #t; break + +static nsCString uTypeDesc( UINT uType ) { + nsCString result; + switch ( uType ) { + XTYP_CASE(XTYP_ADVSTART); + XTYP_CASE(XTYP_CONNECT); + XTYP_CASE(XTYP_ADVREQ); + XTYP_CASE(XTYP_REQUEST); + XTYP_CASE(XTYP_WILDCONNECT); + XTYP_CASE(XTYP_ADVDATA); + XTYP_CASE(XTYP_EXECUTE); + XTYP_CASE(XTYP_POKE); + XTYP_CASE(XTYP_ADVSTOP); + XTYP_CASE(XTYP_CONNECT_CONFIRM); + XTYP_CASE(XTYP_DISCONNECT); + XTYP_CASE(XTYP_ERROR); + XTYP_CASE(XTYP_MONITOR); + XTYP_CASE(XTYP_REGISTER); + XTYP_CASE(XTYP_XACT_COMPLETE); + XTYP_CASE(XTYP_UNREGISTER); + default: result = "XTYP_?????"; + } + return result; +} + +static nsCString hszValue( DWORD instance, HSZ hsz ) { + // Extract string from HSZ. + nsCString result("["); + DWORD len = DdeQueryString( instance, hsz, nullptr, nullptr, CP_WINANSI ); + if ( len ) { + char buffer[ 256 ]; + DdeQueryString( instance, hsz, buffer, sizeof buffer, CP_WINANSI ); + result += buffer; + } + result += "]"; + return result; +} +#else +// These are purely a safety measure to avoid the infamous "won't +// build non-debug" type Tinderbox flames. +static nsCString uTypeDesc( UINT ) { + return nsCString( "?" ); +} +static nsCString hszValue( DWORD, HSZ ) { + return nsCString( "?" ); +} +#endif + + +// Utility function to escape double-quotes within a string. +static void escapeQuotes( nsAString &aString ) { + int32_t offset = -1; + while( 1 ) { + // Find next '"'. + offset = aString.FindChar( '"', ++offset ); + if ( offset == kNotFound ) { + // No more quotes, exit. + break; + } else { + // Insert back-slash ahead of the '"'. + aString.Insert( char16_t('\\'), offset ); + // Increment offset because we just inserted a slash + offset++; + } + } + return; +} + +HDDEDATA CALLBACK +nsNativeAppSupportWin::HandleDDENotification( UINT uType, // transaction type + UINT uFmt, // clipboard data format + HCONV hconv, // handle to the conversation + HSZ hsz1, // handle to a string + HSZ hsz2, // handle to a string + HDDEDATA hdata, // handle to a global memory object + ULONG_PTR dwData1, // transaction-specific data + ULONG_PTR dwData2 ) { // transaction-specific data + + if (!mCanHandleRequests) + return 0; + + +#if MOZ_DEBUG_DDE + printf( "DDE: uType =%s\n", uTypeDesc( uType ).get() ); + printf( " uFmt =%u\n", (unsigned)uFmt ); + printf( " hconv =%08x\n", (int)hconv ); + printf( " hsz1 =%08x:%s\n", (int)hsz1, hszValue( mInstance, hsz1 ).get() ); + printf( " hsz2 =%08x:%s\n", (int)hsz2, hszValue( mInstance, hsz2 ).get() ); + printf( " hdata =%08x\n", (int)hdata ); + printf( " dwData1=%08x\n", (int)dwData1 ); + printf( " dwData2=%08x\n", (int)dwData2 ); +#endif + + HDDEDATA result = 0; + if ( uType & XCLASS_BOOL ) { + switch ( uType ) { + case XTYP_CONNECT: + // Make sure its for our service/topic. + if ( FindTopic( hsz1 ) != -1 ) { + // We support this connection. + result = (HDDEDATA)1; + } + break; + case XTYP_CONNECT_CONFIRM: + // We don't care about the conversation handle, at this point. + result = (HDDEDATA)1; + break; + } + } else if ( uType & XCLASS_DATA ) { + if ( uType == XTYP_REQUEST ) { + switch ( FindTopic( hsz1 ) ) { + case topicOpenURL: { + // Open a given URL... + + // Get the URL from the first argument in the command. + nsAutoString url; + ParseDDEArg(hsz2, 0, url); + + // Read the 3rd argument in the command to determine if a + // new window is to be used. + nsAutoString windowID; + ParseDDEArg(hsz2, 2, windowID); + // "" means to open the URL in a new window. + if ( windowID.IsEmpty() ) { + url.Insert(NS_LITERAL_STRING("mozilla -new-window "), 0); + } + else { + url.Insert(NS_LITERAL_STRING("mozilla -url "), 0); + } + +#if MOZ_DEBUG_DDE + printf( "Handling dde XTYP_REQUEST request: [%s]...\n", NS_ConvertUTF16toUTF8(url).get() ); +#endif + // Now handle it. + HandleCommandLine(NS_ConvertUTF16toUTF8(url).get(), nullptr, nsICommandLine::STATE_REMOTE_EXPLICIT); + + // Return pseudo window ID. + result = CreateDDEData( 1 ); + break; + } + case topicGetWindowInfo: { + // This topic has to get the current URL, get the current + // page title and then format the output into the DDE + // return string. The return value is "URL","Page Title", + // "Window ID" however the window ID is not used for this + // command, therefore it is returned as a null string + + // This isn't really a loop. We just use "break" + // statements to bypass the remaining steps when + // something goes wrong. + do { + // Get most recently used Nav window. + nsCOMPtr<mozIDOMWindowProxy> navWin; + GetMostRecentWindow( NS_LITERAL_STRING( "navigator:browser" ).get(), + getter_AddRefs( navWin ) ); + nsCOMPtr<nsPIDOMWindowOuter> piNavWin = do_QueryInterface(navWin); + if ( !piNavWin ) { + // There is not a window open + break; + } + + // Get content window. + nsCOMPtr<nsPIDOMWindowOuter> internalContent = nsGlobalWindow::Cast(piNavWin)->GetContent(); + if ( !internalContent ) { + break; + } + // Get location. + nsCOMPtr<nsIDOMLocation> location = internalContent->GetLocation(); + if ( !location ) { + break; + } + // Get href for URL. + nsAutoString url; + if ( NS_FAILED( location->GetHref( url ) ) ) { + break; + } + // Escape any double-quotes. + escapeQuotes( url ); + + // Now for the title... + + // Get the base window from the doc shell... + nsCOMPtr<nsIBaseWindow> baseWindow = + do_QueryInterface( internalContent->GetDocShell() ); + if ( !baseWindow ) { + break; + } + // And from the base window we can get the title. + nsXPIDLString title; + if(!baseWindow) { + break; + } + baseWindow->GetTitle(getter_Copies(title)); + // Escape any double-quotes in the title. + escapeQuotes( title ); + + // Use a string buffer for the output data, first + // save a quote. + nsAutoCString outpt( NS_LITERAL_CSTRING("\"") ); + // Now copy the URL converting the Unicode string + // to a single-byte ASCII string + nsAutoCString tmpNativeStr; + NS_CopyUnicodeToNative( url, tmpNativeStr ); + outpt.Append( tmpNativeStr ); + // Add the "," used to separate the URL and the page + // title + outpt.Append( NS_LITERAL_CSTRING("\",\"") ); + // Now copy the current page title to the return string + NS_CopyUnicodeToNative( title, tmpNativeStr ); + outpt.Append( tmpNativeStr ); + // Fill out the return string with the remainin ","" + outpt.Append( NS_LITERAL_CSTRING( "\",\"\"" )); + + // Create a DDE handle to a char string for the data + // being returned, this copies and creates a "shared" + // copy of the DDE response until the calling APP + // reads it and says it can be freed. + result = CreateDDEData( (LPBYTE)(const char*)outpt.get(), + outpt.Length() + 1 ); +#if MOZ_DEBUG_DDE + printf( "WWW_GetWindowInfo->%s\n", outpt.get() ); +#endif + } while ( false ); + break; + } + case topicActivate: { + // Activate a Nav window... + nsAutoString windowID; + ParseDDEArg(hsz2, 0, windowID); + // 4294967295 is decimal for 0xFFFFFFFF which is also a + // correct value to do that Activate last window stuff + if ( windowID.EqualsLiteral( "-1" ) || + windowID.EqualsLiteral( "4294967295" ) ) { + // We only support activating the most recent window (or a new one). + ActivateLastWindow(); + // Return pseudo window ID. + result = CreateDDEData( 1 ); + } + break; + } + case topicVersion: { + // Return version. We're restarting at 1.0! + DWORD version = 1 << 16; // "1.0" + result = CreateDDEData( version ); + break; + } + case topicRegisterViewer: { + // Register new viewer (not implemented). + result = CreateDDEData( false ); + break; + } + case topicUnRegisterViewer: { + // Unregister new viewer (not implemented). + result = CreateDDEData( false ); + break; + } + default: + break; + } + } else if ( uType & XTYP_POKE ) { + switch ( FindTopic( hsz1 ) ) { + case topicCancelProgress: { + // "Handle" progress cancel (actually, pretty much ignored). + result = (HDDEDATA)DDE_FACK; + break; + } + default: + break; + } + } + } else if ( uType & XCLASS_FLAGS ) { + if ( uType == XTYP_EXECUTE ) { + // Prove that we received the request. + DWORD bytes; + LPBYTE request = DdeAccessData( hdata, &bytes ); +#if MOZ_DEBUG_DDE + printf( "Handling dde request: [%s]...\n", (char*)request ); +#endif + + nsAutoString url; + ParseDDEArg((const WCHAR*) request, 0, url); + + // Read the 3rd argument in the command to determine if a + // new window is to be used. + nsAutoString windowID; + ParseDDEArg((const WCHAR*) request, 2, windowID); + + // "" means to open the URL in a new window. + if ( windowID.IsEmpty() ) { + url.Insert(NS_LITERAL_STRING("mozilla -new-window "), 0); + } + else { + url.Insert(NS_LITERAL_STRING("mozilla -url "), 0); + } +#if MOZ_DEBUG_DDE + printf( "Handling dde XTYP_REQUEST request: [%s]...\n", NS_ConvertUTF16toUTF8(url).get() ); +#endif + // Now handle it. + HandleCommandLine(NS_ConvertUTF16toUTF8(url).get(), nullptr, nsICommandLine::STATE_REMOTE_EXPLICIT); + + // Release the data. + DdeUnaccessData( hdata ); + result = (HDDEDATA)DDE_FACK; + } else { + result = (HDDEDATA)DDE_FNOTPROCESSED; + } + } else if ( uType & XCLASS_NOTIFICATION ) { + } +#if MOZ_DEBUG_DDE + printf( "DDE result=%d (0x%08X)\n", (int)result, (int)result ); +#endif + return result; +} + +// Utility function to advance to end of quoted string. +// p+offset must point to the comma preceding the arg on entry. +// On return, p+result points to the closing '"' (or end of the string +// if the closing '"' is missing) if the arg is quoted. If the arg +// is not quoted, then p+result will point to the first character +// of the arg. +static int32_t advanceToEndOfQuotedArg( const WCHAR *p, int32_t offset, int32_t len ) { + // Check whether the current arg is quoted. + if ( p[++offset] == '"' ) { + // Advance past the closing quote. + while ( offset < len && p[++offset] != '"' ) { + // If the current character is a backslash, then the + // next character can't be a *real* '"', so skip it. + if ( p[offset] == '\\' ) { + offset++; + } + } + } + return offset; +} + +void nsNativeAppSupportWin::ParseDDEArg( const WCHAR* args, int index, nsString& aString) { + if ( args ) { + nsDependentString temp(args); + + // offset points to the comma preceding the desired arg. + int32_t offset = -1; + // Skip commas till we get to the arg we want. + while( index-- ) { + // If this arg is quoted, then go to closing quote. + offset = advanceToEndOfQuotedArg( args, offset, temp.Length()); + // Find next comma. + offset = temp.FindChar( ',', offset ); + if ( offset == kNotFound ) { + // No more commas, give up. + aString = args; + return; + } + } + // The desired argument starts just past the preceding comma, + // which offset points to, and extends until the following + // comma (or the end of the string). + // + // Since the argument might be enclosed in quotes, we need to + // deal with that before searching for the terminating comma. + // We advance offset so it ends up pointing to the start of + // the argument we want. + int32_t end = advanceToEndOfQuotedArg( args, offset++, temp.Length() ); + // Find next comma (or end of string). + end = temp.FindChar( ',', end ); + if ( end == kNotFound ) { + // Arg is the rest of the string. + end = temp.Length(); + } + // Extract result. + aString.Assign( args + offset, end - offset ); + } + return; +} + +// Utility to parse out argument from a DDE item string. +void nsNativeAppSupportWin::ParseDDEArg( HSZ args, int index, nsString& aString) { + DWORD argLen = DdeQueryStringW( mInstance, args, nullptr, 0, CP_WINUNICODE ); + // there wasn't any string, so return empty string + if ( !argLen ) return; + nsAutoString temp; + // Ensure result's buffer is sufficiently big. + temp.SetLength( argLen ); + // Now get the string contents. + DdeQueryString( mInstance, args, reinterpret_cast<wchar_t*>(temp.BeginWriting()), temp.Length(), CP_WINUNICODE ); + // Parse out the given arg. + ParseDDEArg(temp.get(), index, aString); + return; +} + +HDDEDATA nsNativeAppSupportWin::CreateDDEData( DWORD value ) { + return CreateDDEData( (LPBYTE)&value, sizeof value ); +} + +HDDEDATA nsNativeAppSupportWin::CreateDDEData( LPBYTE value, DWORD len ) { + HDDEDATA result = DdeCreateDataHandle( mInstance, + value, + len, + 0, + mApplication, + CF_TEXT, + 0 ); + return result; +} + +void nsNativeAppSupportWin::ActivateLastWindow() { + nsCOMPtr<mozIDOMWindowProxy> navWin; + GetMostRecentWindow( u"navigator:browser", getter_AddRefs( navWin ) ); + if ( navWin ) { + // Activate that window. + activateWindow( navWin ); + } else { + // Need to create a Navigator window, then. + OpenBrowserWindow(); + } +} + +void +nsNativeAppSupportWin::HandleCommandLine(const char* aCmdLineString, + nsIFile* aWorkingDir, + uint32_t aState) +{ + nsresult rv; + + int justCounting = 1; + char **argv = 0; + // Flags, etc. + int init = 1; + int between, quoted, bSlashCount; + int argc; + const char *p; + nsAutoCString arg; + + nsCOMPtr<nsICommandLineRunner> cmdLine + (do_CreateInstance("@mozilla.org/toolkit/command-line;1")); + if (!cmdLine) { + NS_ERROR("Couldn't create command line!"); + return; + } + + // Parse command line args according to MS spec + // (see "Parsing C++ Command-Line Arguments" at + // http://msdn.microsoft.com/library/devprods/vs6/visualc/vclang/_pluslang_parsing_c.2b2b_.command.2d.line_arguments.htm). + // We loop if we've not finished the second pass through. + while ( 1 ) { + // Initialize if required. + if ( init ) { + p = aCmdLineString; + between = 1; + argc = quoted = bSlashCount = 0; + + init = 0; + } + if ( between ) { + // We are traversing whitespace between args. + // Check for start of next arg. + if ( *p != 0 && !isspace( *p ) ) { + // Start of another arg. + between = 0; + arg = ""; + switch ( *p ) { + case '\\': + // Count the backslash. + bSlashCount = 1; + break; + case '"': + // Remember we're inside quotes. + quoted = 1; + break; + default: + // Add character to arg. + arg += *p; + break; + } + } else { + // Another space between args, ignore it. + } + } else { + // We are processing the contents of an argument. + // Check for whitespace or end. + if ( *p == 0 || ( !quoted && isspace( *p ) ) ) { + // Process pending backslashes (interpret them + // literally since they're not followed by a "). + while( bSlashCount ) { + arg += '\\'; + bSlashCount--; + } + // End current arg. + if ( !justCounting ) { + argv[argc] = new char[ arg.Length() + 1 ]; + strcpy( argv[argc], arg.get() ); + } + argc++; + // We're now between args. + between = 1; + } else { + // Still inside argument, process the character. + switch ( *p ) { + case '"': + // First, digest preceding backslashes (if any). + while ( bSlashCount > 1 ) { + // Put one backsplash in arg for each pair. + arg += '\\'; + bSlashCount -= 2; + } + if ( bSlashCount ) { + // Quote is literal. + arg += '"'; + bSlashCount = 0; + } else { + // Quote starts or ends a quoted section. + if ( quoted ) { + // Check for special case of consecutive double + // quotes inside a quoted section. + if ( *(p+1) == '"' ) { + // This implies a literal double-quote. Fake that + // out by causing next double-quote to look as + // if it was preceded by a backslash. + bSlashCount = 1; + } else { + quoted = 0; + } + } else { + quoted = 1; + } + } + break; + case '\\': + // Add to count. + bSlashCount++; + break; + default: + // Accept any preceding backslashes literally. + while ( bSlashCount ) { + arg += '\\'; + bSlashCount--; + } + // Just add next char to the current arg. + arg += *p; + break; + } + } + } + // Check for end of input. + if ( *p ) { + // Go to next character. + p++; + } else { + // If on first pass, go on to second. + if ( justCounting ) { + // Allocate argv array. + argv = new char*[ argc ]; + + // Start second pass + justCounting = 0; + init = 1; + } else { + // Quit. + break; + } + } + } + + rv = cmdLine->Init(argc, argv, aWorkingDir, aState); + + // Cleanup. + while ( argc ) { + delete [] argv[ --argc ]; + } + delete [] argv; + + if (NS_FAILED(rv)) { + NS_ERROR("Error initializing command line."); + return; + } + + cmdLine->Run(); +} + +nsresult +nsNativeAppSupportWin::OpenWindow( const char*urlstr, const char *args ) { + + nsresult rv = NS_ERROR_FAILURE; + + nsCOMPtr<nsIWindowWatcher> wwatch(do_GetService(NS_WINDOWWATCHER_CONTRACTID)); + nsCOMPtr<nsISupportsCString> sarg(do_CreateInstance(NS_SUPPORTS_CSTRING_CONTRACTID)); + if (sarg) + sarg->SetData(nsDependentCString(args)); + + if (wwatch && sarg) { + nsCOMPtr<mozIDOMWindowProxy> newWindow; + rv = wwatch->OpenWindow(0, urlstr, "_blank", "chrome,dialog=no,all", + sarg, getter_AddRefs(newWindow)); +#if MOZ_DEBUG_DDE + } else { + printf("Get WindowWatcher (or create string) failed\n"); +#endif + } + return rv; +} + +HWND hwndForDOMWindow(mozIDOMWindowProxy *window ) { + if ( !window ) { + return 0; + } + nsCOMPtr<nsPIDOMWindowOuter > pidomwindow = nsPIDOMWindowOuter::From(window); + + nsCOMPtr<nsIBaseWindow> ppBaseWindow = + do_QueryInterface( pidomwindow->GetDocShell() ); + if ( !ppBaseWindow ) { + return 0; + } + + nsCOMPtr<nsIWidget> ppWidget; + ppBaseWindow->GetMainWidget( getter_AddRefs( ppWidget ) ); + + return (HWND)( ppWidget->GetNativeData( NS_NATIVE_WIDGET ) ); +} + +nsresult +nsNativeAppSupportWin::OpenBrowserWindow() +{ + nsresult rv = NS_OK; + + // Open the argument URL in the most recently used Navigator window. + // If there is no Nav window, open a new one. + + // If at all possible, hand the request off to the most recent + // browser window. + + nsCOMPtr<mozIDOMWindowProxy> navWin; + GetMostRecentWindow( NS_LITERAL_STRING( "navigator:browser" ).get(), getter_AddRefs( navWin ) ); + + // This isn't really a loop. We just use "break" statements to fall + // out to the OpenWindow call when things go awry. + do { + // If caller requires a new window, then don't use an existing one. + if ( !navWin ) { + // Have to open a new one. + break; + } + + nsCOMPtr<nsIBrowserDOMWindow> bwin; + { // scope a bunch of temporary cruft used to generate bwin + nsCOMPtr<nsIWebNavigation> navNav( do_GetInterface( navWin ) ); + nsCOMPtr<nsIDocShellTreeItem> navItem( do_QueryInterface( navNav ) ); + if ( navItem ) { + nsCOMPtr<nsIDocShellTreeItem> rootItem; + navItem->GetRootTreeItem( getter_AddRefs( rootItem ) ); + nsCOMPtr<nsPIDOMWindowOuter> rootWin = + rootItem ? rootItem->GetWindow() : nullptr; + nsCOMPtr<nsIDOMChromeWindow> chromeWin(do_QueryInterface(rootWin)); + if ( chromeWin ) + chromeWin->GetBrowserDOMWindow( getter_AddRefs ( bwin ) ); + } + } + if ( bwin ) { + nsCOMPtr<nsIURI> uri; + NS_NewURI( getter_AddRefs( uri ), NS_LITERAL_CSTRING("about:blank"), 0, 0 ); + if ( uri ) { + nsCOMPtr<mozIDOMWindowProxy> container; + rv = bwin->OpenURI( uri, 0, + nsIBrowserDOMWindow::OPEN_DEFAULTWINDOW, + nsIBrowserDOMWindow::OPEN_EXTERNAL, + getter_AddRefs( container ) ); + if ( NS_SUCCEEDED( rv ) ) + return NS_OK; + } + } + + NS_ERROR("failed to hand off external URL to extant window"); + } while ( false ); + + // open a new window if caller requested it or if anything above failed + + char* argv[] = { 0 }; + nsCOMPtr<nsICommandLineRunner> cmdLine + (do_CreateInstance("@mozilla.org/toolkit/command-line;1")); + NS_ENSURE_TRUE(cmdLine, NS_ERROR_FAILURE); + + rv = cmdLine->Init(0, argv, nullptr, nsICommandLine::STATE_REMOTE_EXPLICIT); + NS_ENSURE_SUCCESS(rv, rv); + + return cmdLine->Run(); +} + diff --git a/toolkit/xre/nsNativeAppSupportWin.h b/toolkit/xre/nsNativeAppSupportWin.h new file mode 100644 index 0000000000..3e6fb67e5e --- /dev/null +++ b/toolkit/xre/nsNativeAppSupportWin.h @@ -0,0 +1,39 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +/* This file has *public* stuff needed for the Win32 implementation of + * the nsINativeAppSupport interface. It has to be broken out into a + * separate file in order to ensure that the generated .h file can be + * used in a Win32 .rc file. See /mozilla/xpfe/bootstrap/splash.rc. + * + * This file, and the generated .h, are only needed on Win32 platforms. + */ + +// Constants identifying Win32 "native" resources. + +#ifdef MOZ_PHOENIX + +// Splash screen dialog ID. +#define IDD_SPLASH 100 + +// Splash screen bitmap ID. +#define IDB_SPLASH 101 + +// DDE application name +#define ID_DDE_APPLICATION_NAME 102 + +#define IDI_APPICON 1 +#define IDI_DOCUMENT 2 +#define IDI_NEWWINDOW 3 +#define IDI_NEWTAB 4 +#define IDI_PBMODE 5 +#ifndef IDI_APPLICATION +#define IDI_APPLICATION 32512 +#endif + +#endif + +// String that goes in the WinXP Start Menu. +#define IDS_STARTMENU_APPNAME 103 diff --git a/toolkit/xre/nsSigHandlers.cpp b/toolkit/xre/nsSigHandlers.cpp new file mode 100644 index 0000000000..098d9ae7e0 --- /dev/null +++ b/toolkit/xre/nsSigHandlers.cpp @@ -0,0 +1,404 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +/* + * This module is supposed to abstract signal handling away from the other + * platforms that do not support it. + */ + +#include "nsSigHandlers.h" + +#ifdef XP_UNIX + +#include <signal.h> +#include <stdio.h> +#include <string.h> +#include "prthread.h" +#include "plstr.h" +#include "prenv.h" +#include "nsDebug.h" +#include "nsXULAppAPI.h" + +#if defined(LINUX) +#include <sys/time.h> +#include <sys/resource.h> +#include <unistd.h> +#include <stdlib.h> // atoi +#include <sys/prctl.h> +#ifndef ANDROID // no Android impl +# include <ucontext.h> +#endif +#endif + +#if defined(SOLARIS) +#include <sys/resource.h> +#include <ucontext.h> +#endif + +static const char* gProgname = "huh?"; + +// Note: some tests manipulate this value. +unsigned int _gdb_sleep_duration = 300; + +#if defined(LINUX) && defined(DEBUG) && \ + (defined(__i386) || defined(__x86_64) || defined(PPC)) +#define CRAWL_STACK_ON_SIGSEGV +#endif + +#ifndef PR_SET_PTRACER +#define PR_SET_PTRACER 0x59616d61 +#endif +#ifndef PR_SET_PTRACER_ANY +#define PR_SET_PTRACER_ANY ((unsigned long)-1) +#endif + +#if defined(CRAWL_STACK_ON_SIGSEGV) + +#include <unistd.h> +#include "nsISupportsUtils.h" +#include "mozilla/StackWalk.h" + +// NB: keep me up to date with the same variable in +// ipc/chromium/chrome/common/ipc_channel_posix.cc +static const int kClientChannelFd = 3; + +extern "C" { + +static void PrintStackFrame(uint32_t aFrameNumber, void *aPC, void *aSP, + void *aClosure) +{ + char buf[1024]; + MozCodeAddressDetails details; + + MozDescribeCodeAddress(aPC, &details); + MozFormatCodeAddressDetails(buf, sizeof(buf), aFrameNumber, aPC, &details); + fprintf(stdout, "%s\n", buf); + fflush(stdout); +} + +} + +void +ah_crap_handler(int signum) +{ + printf("\nProgram %s (pid = %d) received signal %d.\n", + gProgname, + getpid(), + signum); + + printf("Stack:\n"); + MozStackWalk(PrintStackFrame, /* skipFrames */ 2, /* maxFrames */ 0, + nullptr, 0, nullptr); + + printf("Sleeping for %d seconds.\n",_gdb_sleep_duration); + printf("Type 'gdb %s %d' to attach your debugger to this thread.\n", + gProgname, + getpid()); + + // Allow us to be ptraced by gdb on Linux with Yama restrictions enabled. + prctl(PR_SET_PTRACER, PR_SET_PTRACER_ANY); + + sleep(_gdb_sleep_duration); + + printf("Done sleeping...\n"); + + _exit(signum); +} + +void +child_ah_crap_handler(int signum) +{ + if (!getenv("MOZ_DONT_UNBLOCK_PARENT_ON_CHILD_CRASH")) + close(kClientChannelFd); + ah_crap_handler(signum); +} + +#endif // CRAWL_STACK_ON_SIGSEGV + +#ifdef MOZ_WIDGET_GTK +// Need this include for version test below. +#include <glib.h> +#endif + +#if defined(MOZ_WIDGET_GTK) && (GLIB_MAJOR_VERSION > 2 || (GLIB_MAJOR_VERSION == 2 && GLIB_MINOR_VERSION >= 6)) + +static GLogFunc orig_log_func = nullptr; + +extern "C" { +static void +my_glib_log_func(const gchar *log_domain, GLogLevelFlags log_level, + const gchar *message, gpointer user_data); +} + +/* static */ void +my_glib_log_func(const gchar *log_domain, GLogLevelFlags log_level, + const gchar *message, gpointer user_data) +{ + if (log_level & (G_LOG_LEVEL_ERROR | G_LOG_FLAG_FATAL | G_LOG_FLAG_RECURSION)) { + NS_DebugBreak(NS_DEBUG_ASSERTION, message, "glib assertion", __FILE__, __LINE__); + } else if (log_level & (G_LOG_LEVEL_CRITICAL | G_LOG_LEVEL_WARNING)) { + NS_DebugBreak(NS_DEBUG_WARNING, message, "glib warning", __FILE__, __LINE__); + } + + orig_log_func(log_domain, log_level, message, nullptr); +} + +#endif + +#ifdef SA_SIGINFO +static void fpehandler(int signum, siginfo_t *si, void *context) +{ + /* Integer divide by zero or integer overflow. */ + /* Note: FPE_INTOVF is ignored on Intel, PowerPC and SPARC systems. */ + if (si->si_code == FPE_INTDIV || si->si_code == FPE_INTOVF) { + NS_DebugBreak(NS_DEBUG_ABORT, "Divide by zero", nullptr, __FILE__, __LINE__); + } + +#ifdef XP_MACOSX + ucontext_t *uc = (ucontext_t *)context; + +#if defined(__i386__) || defined(__amd64__) + _STRUCT_FP_CONTROL *ctrl = &uc->uc_mcontext->__fs.__fpu_fcw; + ctrl->__invalid = ctrl->__denorm = ctrl->__zdiv = ctrl->__ovrfl = ctrl->__undfl = ctrl->__precis = 1; + + _STRUCT_FP_STATUS *status = &uc->uc_mcontext->__fs.__fpu_fsw; + status->__invalid = status->__denorm = status->__zdiv = status->__ovrfl = status->__undfl = + status->__precis = status->__stkflt = status->__errsumm = 0; + + uint32_t *mxcsr = &uc->uc_mcontext->__fs.__fpu_mxcsr; + *mxcsr |= SSE_EXCEPTION_MASK; /* disable all SSE exceptions */ + *mxcsr &= ~SSE_STATUS_FLAGS; /* clear all pending SSE exceptions */ +#endif +#endif +#if defined(LINUX) && !defined(ANDROID) + ucontext_t *uc = (ucontext_t *)context; + +#if defined(__i386__) + /* + * It seems that we have no access to mxcsr on Linux. libc + * seems to be translating cw/sw to mxcsr. + */ + unsigned long int *cw = &uc->uc_mcontext.fpregs->cw; + *cw |= FPU_EXCEPTION_MASK; + + unsigned long int *sw = &uc->uc_mcontext.fpregs->sw; + *sw &= ~FPU_STATUS_FLAGS; +#endif +#if defined(__amd64__) + uint16_t *cw = &uc->uc_mcontext.fpregs->cwd; + *cw |= FPU_EXCEPTION_MASK; + + uint16_t *sw = &uc->uc_mcontext.fpregs->swd; + *sw &= ~FPU_STATUS_FLAGS; + + uint32_t *mxcsr = &uc->uc_mcontext.fpregs->mxcsr; + *mxcsr |= SSE_EXCEPTION_MASK; /* disable all SSE exceptions */ + *mxcsr &= ~SSE_STATUS_FLAGS; /* clear all pending SSE exceptions */ +#endif +#endif +#ifdef SOLARIS + ucontext_t *uc = (ucontext_t *)context; + +#if defined(__i386) + uint32_t *cw = &uc->uc_mcontext.fpregs.fp_reg_set.fpchip_state.state[0]; + *cw |= FPU_EXCEPTION_MASK; + + uint32_t *sw = &uc->uc_mcontext.fpregs.fp_reg_set.fpchip_state.state[1]; + *sw &= ~FPU_STATUS_FLAGS; + + /* address of the instruction that caused the exception */ + uint32_t *ip = &uc->uc_mcontext.fpregs.fp_reg_set.fpchip_state.state[3]; + uc->uc_mcontext.gregs[REG_PC] = *ip; +#endif +#if defined(__amd64__) + uint16_t *cw = &uc->uc_mcontext.fpregs.fp_reg_set.fpchip_state.cw; + *cw |= FPU_EXCEPTION_MASK; + + uint16_t *sw = &uc->uc_mcontext.fpregs.fp_reg_set.fpchip_state.sw; + *sw &= ~FPU_STATUS_FLAGS; + + uint32_t *mxcsr = &uc->uc_mcontext.fpregs.fp_reg_set.fpchip_state.mxcsr; + *mxcsr |= SSE_EXCEPTION_MASK; /* disable all SSE exceptions */ + *mxcsr &= ~SSE_STATUS_FLAGS; /* clear all pending SSE exceptions */ +#endif +#endif +} +#endif + +void InstallSignalHandlers(const char *aProgname) +{ + const char* tmp = PL_strdup(aProgname); + if (tmp) { + gProgname = tmp; + } + + const char *gdbSleep = PR_GetEnv("MOZ_GDB_SLEEP"); + if (gdbSleep && *gdbSleep) + { + unsigned int s; + if (1 == sscanf(gdbSleep, "%u", &s)) { + _gdb_sleep_duration = s; + } + } + +#if defined(CRAWL_STACK_ON_SIGSEGV) + if (!getenv("XRE_NO_WINDOWS_CRASH_DIALOG")) { + void (*crap_handler)(int) = + GeckoProcessType_Default != XRE_GetProcessType() ? + child_ah_crap_handler : + ah_crap_handler; + signal(SIGSEGV, crap_handler); + signal(SIGILL, crap_handler); + signal(SIGABRT, crap_handler); + } +#endif // CRAWL_STACK_ON_SIGSEGV + +#ifdef SA_SIGINFO + /* Install a handler for floating point exceptions and disable them if they occur. */ + struct sigaction sa, osa; + sa.sa_flags = SA_ONSTACK | SA_RESTART | SA_SIGINFO; + sa.sa_sigaction = fpehandler; + sigemptyset(&sa.sa_mask); + sigaction(SIGFPE, &sa, &osa); +#endif + + if (!XRE_IsParentProcess()) { + /* + * If the user is debugging a Gecko parent process in gdb and hits ^C to + * suspend, a SIGINT signal will be sent to the child. We ignore this signal + * so the child isn't killed. + */ + signal(SIGINT, SIG_IGN); + } + +#if defined(DEBUG) && defined(LINUX) + const char *memLimit = PR_GetEnv("MOZ_MEM_LIMIT"); + if (memLimit && *memLimit) + { + long m = atoi(memLimit); + m *= (1024*1024); + struct rlimit r; + r.rlim_cur = m; + r.rlim_max = m; + setrlimit(RLIMIT_AS, &r); + } +#endif + +#if defined(SOLARIS) +#define NOFILES 512 + + // Boost Solaris file descriptors + { + struct rlimit rl; + + if (getrlimit(RLIMIT_NOFILE, &rl) == 0) + + if (rl.rlim_cur < NOFILES) { + rl.rlim_cur = NOFILES; + + if (setrlimit(RLIMIT_NOFILE, &rl) < 0) { + perror("setrlimit(RLIMIT_NOFILE)"); + fprintf(stderr, "Cannot exceed hard limit for open files"); + } +#if defined(DEBUG) + if (getrlimit(RLIMIT_NOFILE, &rl) == 0) + printf("File descriptors set to %d\n", rl.rlim_cur); +#endif //DEBUG + } + } +#endif //SOLARIS + +#if defined(MOZ_WIDGET_GTK) && (GLIB_MAJOR_VERSION > 2 || (GLIB_MAJOR_VERSION == 2 && GLIB_MINOR_VERSION >= 6)) + const char *assertString = PR_GetEnv("XPCOM_DEBUG_BREAK"); + if (assertString && + (!strcmp(assertString, "suspend") || + !strcmp(assertString, "stack") || + !strcmp(assertString, "abort") || + !strcmp(assertString, "trap") || + !strcmp(assertString, "break"))) { + // Override the default glib logging function so we get stacks for it too. + orig_log_func = g_log_set_default_handler(my_glib_log_func, nullptr); + } +#endif +} + +#elif XP_WIN + +#include <windows.h> + +#ifdef _M_IX86 +/* + * WinNT.h prior to SDK7 does not expose the structure of the ExtendedRegisters for ia86. + * We known that MxCsr is at offset 0x18 and is a DWORD. + */ +#define MXCSR(ctx) (*(DWORD *)(((BYTE *)(ctx)->ExtendedRegisters) + 0x18)) +#endif + +#ifdef _M_X64 +#define MXCSR(ctx) (ctx)->MxCsr +#endif + +#if defined(_M_IX86) || defined(_M_X64) + +#ifdef _M_X64 +#define X87CW(ctx) (ctx)->FltSave.ControlWord +#define X87SW(ctx) (ctx)->FltSave.StatusWord +#else +#define X87CW(ctx) (ctx)->FloatSave.ControlWord +#define X87SW(ctx) (ctx)->FloatSave.StatusWord +#endif + +static LPTOP_LEVEL_EXCEPTION_FILTER gFPEPreviousFilter; + +LONG __stdcall FpeHandler(PEXCEPTION_POINTERS pe) +{ + PEXCEPTION_RECORD e = (PEXCEPTION_RECORD)pe->ExceptionRecord; + CONTEXT *c = (CONTEXT*)pe->ContextRecord; + + switch (e->ExceptionCode) { + case STATUS_FLOAT_DENORMAL_OPERAND: + case STATUS_FLOAT_DIVIDE_BY_ZERO: + case STATUS_FLOAT_INEXACT_RESULT: + case STATUS_FLOAT_INVALID_OPERATION: + case STATUS_FLOAT_OVERFLOW: + case STATUS_FLOAT_STACK_CHECK: + case STATUS_FLOAT_UNDERFLOW: + case STATUS_FLOAT_MULTIPLE_FAULTS: + case STATUS_FLOAT_MULTIPLE_TRAPS: + X87CW(c) |= FPU_EXCEPTION_MASK; /* disable all FPU exceptions */ + X87SW(c) &= ~FPU_STATUS_FLAGS; /* clear all pending FPU exceptions */ +#ifdef _M_IX86 + if (c->ContextFlags & CONTEXT_EXTENDED_REGISTERS) { +#endif + MXCSR(c) |= SSE_EXCEPTION_MASK; /* disable all SSE exceptions */ + MXCSR(c) &= ~SSE_STATUS_FLAGS; /* clear all pending SSE exceptions */ +#ifdef _M_IX86 + } +#endif + return EXCEPTION_CONTINUE_EXECUTION; + } + LONG action = EXCEPTION_CONTINUE_SEARCH; + if (gFPEPreviousFilter) + action = gFPEPreviousFilter(pe); + + return action; +} + +void InstallSignalHandlers(const char *aProgname) +{ + gFPEPreviousFilter = SetUnhandledExceptionFilter(FpeHandler); +} + +#else + +void InstallSignalHandlers(const char *aProgname) +{ +} + +#endif + +#else +#error No signal handling implementation for this platform. +#endif diff --git a/toolkit/xre/nsSigHandlers.h b/toolkit/xre/nsSigHandlers.h new file mode 100644 index 0000000000..a4f073cbb7 --- /dev/null +++ b/toolkit/xre/nsSigHandlers.h @@ -0,0 +1,39 @@ +/* -*- Mode: C++; tab-width: 40; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#if defined(_M_IX86) || defined(_M_X64) || defined(__i386__) || defined(__i386) || defined(__amd64__) + +/* + * x87 FPU Control Word: + * + * 0 -> IM Invalid Operation + * 1 -> DM Denormalized Operand + * 2 -> ZM Zero Divide + * 3 -> OM Overflow + * 4 -> UM Underflow + * 5 -> PM Precision + */ +#define FPU_EXCEPTION_MASK 0x3f + +/* + * x86 FPU Status Word: + * + * 0..5 -> Exception flags (see x86 FPU Control Word) + * 6 -> SF Stack Fault + * 7 -> ES Error Summary Status + */ +#define FPU_STATUS_FLAGS 0xff + +/* + * MXCSR Control and Status Register: + * + * 0..5 -> Exception flags (see x86 FPU Control Word) + * 6 -> DAZ Denormals Are Zero + * 7..12 -> Exception mask (see x86 FPU Control Word) + */ +#define SSE_STATUS_FLAGS FPU_EXCEPTION_MASK +#define SSE_EXCEPTION_MASK (FPU_EXCEPTION_MASK << 7) + +#endif diff --git a/toolkit/xre/nsUpdateDriver.cpp b/toolkit/xre/nsUpdateDriver.cpp new file mode 100644 index 0000000000..54a7110000 --- /dev/null +++ b/toolkit/xre/nsUpdateDriver.cpp @@ -0,0 +1,1316 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim:set ts=2 sw=2 sts=2 et cindent: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include <stdlib.h> +#include <stdio.h> +#include "nsUpdateDriver.h" +#include "nsXULAppAPI.h" +#include "nsAppRunner.h" +#include "nsIWritablePropertyBag.h" +#include "nsIFile.h" +#include "nsVariant.h" +#include "nsCOMPtr.h" +#include "nsString.h" +#include "prproces.h" +#include "mozilla/Logging.h" +#include "prenv.h" +#include "nsVersionComparator.h" +#include "nsXREDirProvider.h" +#include "SpecialSystemDirectory.h" +#include "nsDirectoryServiceDefs.h" +#include "nsThreadUtils.h" +#include "nsIXULAppInfo.h" +#include "mozilla/Preferences.h" +#include "nsPrintfCString.h" +#include "mozilla/DebugOnly.h" + +#ifdef XP_MACOSX +#include "nsILocalFileMac.h" +#include "nsCommandLineServiceMac.h" +#include "MacLaunchHelper.h" +#include "updaterfileutils_osx.h" +#endif + +#if defined(XP_WIN) +# include <direct.h> +# include <process.h> +# include <windows.h> +# include <shlwapi.h> +# include "nsWindowsHelpers.h" +# define getcwd(path, size) _getcwd(path, size) +# define getpid() GetCurrentProcessId() +#elif defined(XP_UNIX) +# include <unistd.h> +# include <sys/wait.h> +#endif + +using namespace mozilla; + +static PRLogModuleInfo * +GetUpdateLog() +{ + static PRLogModuleInfo *sUpdateLog; + if (!sUpdateLog) + sUpdateLog = PR_NewLogModule("updatedriver"); + return sUpdateLog; +} +#define LOG(args) MOZ_LOG(GetUpdateLog(), mozilla::LogLevel::Debug, args) + +#ifdef XP_WIN +#define UPDATER_BIN "updater.exe" +#elif XP_MACOSX +#define UPDATER_BIN "org.mozilla.updater" +#else +#define UPDATER_BIN "updater" +#endif +#define UPDATER_INI "updater.ini" +#ifdef XP_MACOSX +#define UPDATER_APP "updater.app" +#endif +#if defined(XP_UNIX) && !defined(XP_MACOSX) +#define UPDATER_PNG "updater.png" +#endif + +#if defined(MOZ_WIDGET_GONK) +#include <linux/ioprio.h> + +static const int kB2GServiceArgc = 2; +static const char *kB2GServiceArgv[] = { "/system/bin/start", "b2g" }; + +static const char kAppUpdaterPrio[] = "app.update.updater.prio"; +static const char kAppUpdaterOomScoreAdj[] = "app.update.updater.oom_score_adj"; +static const char kAppUpdaterIOPrioClass[] = "app.update.updater.ioprio.class"; +static const char kAppUpdaterIOPrioLevel[] = "app.update.updater.ioprio.level"; + +static const int kAppUpdaterPrioDefault = 19; // -20..19 where 19 = lowest priority +static const int kAppUpdaterOomScoreAdjDefault = -1000; // -1000 = Never kill +static const int kAppUpdaterIOPrioClassDefault = IOPRIO_CLASS_IDLE; +static const int kAppUpdaterIOPrioLevelDefault = 0; // Doesn't matter for CLASS IDLE +#endif + +static nsresult +GetCurrentWorkingDir(char *buf, size_t size) +{ + // Cannot use NS_GetSpecialDirectory because XPCOM is not yet initialized. + // This code is duplicated from xpcom/io/SpecialSystemDirectory.cpp: + +#if defined(XP_WIN) + wchar_t wpath[MAX_PATH]; + if (!_wgetcwd(wpath, size)) + return NS_ERROR_FAILURE; + NS_ConvertUTF16toUTF8 path(wpath); + strncpy(buf, path.get(), size); +#else + if(!getcwd(buf, size)) + return NS_ERROR_FAILURE; +#endif + return NS_OK; +} + +/** + * Get the path to the installation directory. For Mac OS X this will be the + * bundle directory. + * + * @param appDir the application directory file object + * @param installDirPath the path to the installation directory + */ +static nsresult +GetInstallDirPath(nsIFile *appDir, nsACString& installDirPath) +{ + nsresult rv; +#ifdef XP_MACOSX + nsCOMPtr<nsIFile> parentDir1, parentDir2; + rv = appDir->GetParent(getter_AddRefs(parentDir1)); + if (NS_FAILED(rv)) { + return rv; + } + rv = parentDir1->GetParent(getter_AddRefs(parentDir2)); + if (NS_FAILED(rv)) { + return rv; + } + rv = parentDir2->GetNativePath(installDirPath); +#elif XP_WIN + nsAutoString installDirPathW; + rv = appDir->GetPath(installDirPathW); + if (NS_FAILED(rv)) { + return rv; + } + installDirPath = NS_ConvertUTF16toUTF8(installDirPathW); +#else + rv = appDir->GetNativePath(installDirPath); +#endif + if (NS_FAILED(rv)) { + return rv; + } + return NS_OK; +} + +#if defined(XP_MACOSX) +// This is a copy of OS X's XRE_GetBinaryPath from nsAppRunner.cpp with the +// gBinaryPath check removed so that the updater can reload the stub executable +// instead of xulrunner-bin. See bug 349737. +static nsresult +GetXULRunnerStubPath(const char* argv0, nsIFile* *aResult) +{ + // Works even if we're not bundled. + CFBundleRef appBundle = ::CFBundleGetMainBundle(); + if (!appBundle) + return NS_ERROR_FAILURE; + + CFURLRef bundleURL = ::CFBundleCopyExecutableURL(appBundle); + if (!bundleURL) + return NS_ERROR_FAILURE; + + nsCOMPtr<nsILocalFileMac> lfm; + nsresult rv = NS_NewLocalFileWithCFURL(bundleURL, true, getter_AddRefs(lfm)); + + ::CFRelease(bundleURL); + + if (NS_FAILED(rv)) + return rv; + + lfm.forget(aResult); + return NS_OK; +} +#endif /* XP_MACOSX */ + +static bool +GetFile(nsIFile *dir, const nsCSubstring &name, nsCOMPtr<nsIFile> &result) +{ + nsresult rv; + + nsCOMPtr<nsIFile> file; + rv = dir->Clone(getter_AddRefs(file)); + if (NS_FAILED(rv)) + return false; + + rv = file->AppendNative(name); + if (NS_FAILED(rv)) + return false; + + result = do_QueryInterface(file, &rv); + return NS_SUCCEEDED(rv); +} + +static bool +GetStatusFile(nsIFile *dir, nsCOMPtr<nsIFile> &result) +{ + return GetFile(dir, NS_LITERAL_CSTRING("update.status"), result); +} + +/** + * Get the contents of the update.status file. + * + * @param statusFile the status file object. + * @param buf the buffer holding the file contents + * + * @return true if successful, false otherwise. + */ +template <size_t Size> +static bool +GetStatusFileContents(nsIFile *statusFile, char (&buf)[Size]) +{ + static_assert(Size > 16, "Buffer needs to be large enough to hold the known status codes"); + + PRFileDesc *fd = nullptr; + nsresult rv = statusFile->OpenNSPRFileDesc(PR_RDONLY, 0660, &fd); + if (NS_FAILED(rv)) + return false; + + const int32_t n = PR_Read(fd, buf, Size); + PR_Close(fd); + + return (n >= 0); +} + +typedef enum { + eNoUpdateAction, + ePendingUpdate, + ePendingService, + ePendingElevate, + eAppliedUpdate, + eAppliedService, +} UpdateStatus; + +/** + * Returns a value indicating what needs to be done in order to handle an update. + * + * @param dir the directory in which we should look for an update.status file. + * @param statusFile the update.status file found in the directory. + * + * @return the update action to be performed. + */ +static UpdateStatus +GetUpdateStatus(nsIFile* dir, nsCOMPtr<nsIFile> &statusFile) +{ + if (GetStatusFile(dir, statusFile)) { + char buf[32]; + if (GetStatusFileContents(statusFile, buf)) { + const char kPending[] = "pending"; + const char kPendingService[] = "pending-service"; + const char kPendingElevate[] = "pending-elevate"; + const char kApplied[] = "applied"; + const char kAppliedService[] = "applied-service"; + if (!strncmp(buf, kPendingElevate, sizeof(kPendingElevate) - 1)) { + return ePendingElevate; + } + if (!strncmp(buf, kPendingService, sizeof(kPendingService) - 1)) { + return ePendingService; + } + if (!strncmp(buf, kPending, sizeof(kPending) - 1)) { + return ePendingUpdate; + } + if (!strncmp(buf, kAppliedService, sizeof(kAppliedService) - 1)) { + return eAppliedService; + } + if (!strncmp(buf, kApplied, sizeof(kApplied) - 1)) { + return eAppliedUpdate; + } + } + } + return eNoUpdateAction; +} + +static bool +GetVersionFile(nsIFile *dir, nsCOMPtr<nsIFile> &result) +{ + return GetFile(dir, NS_LITERAL_CSTRING("update.version"), result); +} + +// Compares the current application version with the update's application +// version. +static bool +IsOlderVersion(nsIFile *versionFile, const char *appVersion) +{ + PRFileDesc *fd = nullptr; + nsresult rv = versionFile->OpenNSPRFileDesc(PR_RDONLY, 0660, &fd); + if (NS_FAILED(rv)) + return true; + + char buf[32]; + const int32_t n = PR_Read(fd, buf, sizeof(buf)); + PR_Close(fd); + + if (n < 0) + return false; + + // Trim off the trailing newline + if (buf[n - 1] == '\n') + buf[n - 1] = '\0'; + + // If the update xml doesn't provide the application version the file will + // contain the string "null" and it is assumed that the update is not older. + const char kNull[] = "null"; + if (strncmp(buf, kNull, sizeof(kNull) - 1) == 0) + return false; + + if (mozilla::Version(appVersion) > buf) + return true; + + return false; +} + +static bool +CopyFileIntoUpdateDir(nsIFile *parentDir, const nsACString& leaf, nsIFile *updateDir) +{ + nsCOMPtr<nsIFile> file; + + // Make sure there is not an existing file in the target location. + nsresult rv = updateDir->Clone(getter_AddRefs(file)); + if (NS_FAILED(rv)) + return false; + rv = file->AppendNative(leaf); + if (NS_FAILED(rv)) + return false; + file->Remove(true); + + // Now, copy into the target location. + rv = parentDir->Clone(getter_AddRefs(file)); + if (NS_FAILED(rv)) + return false; + rv = file->AppendNative(leaf); + if (NS_FAILED(rv)) + return false; + rv = file->CopyToNative(updateDir, EmptyCString()); + if (NS_FAILED(rv)) + return false; + + return true; +} + +static bool +CopyUpdaterIntoUpdateDir(nsIFile *greDir, nsIFile *appDir, nsIFile *updateDir, + nsCOMPtr<nsIFile> &updater) +{ + // Copy the updater application from the GRE and the updater ini from the app +#if defined(XP_MACOSX) + if (!CopyFileIntoUpdateDir(appDir, NS_LITERAL_CSTRING(UPDATER_APP), updateDir)) + return false; + CopyFileIntoUpdateDir(greDir, NS_LITERAL_CSTRING(UPDATER_INI), updateDir); +#else + if (!CopyFileIntoUpdateDir(greDir, NS_LITERAL_CSTRING(UPDATER_BIN), updateDir)) + return false; + CopyFileIntoUpdateDir(appDir, NS_LITERAL_CSTRING(UPDATER_INI), updateDir); +#endif +#if defined(XP_UNIX) && !defined(XP_MACOSX) && !defined(ANDROID) + nsCOMPtr<nsIFile> iconDir; + appDir->Clone(getter_AddRefs(iconDir)); + iconDir->AppendNative(NS_LITERAL_CSTRING("icons")); + if (!CopyFileIntoUpdateDir(iconDir, NS_LITERAL_CSTRING(UPDATER_PNG), updateDir)) + return false; +#endif + // Finally, return the location of the updater binary. + nsresult rv = updateDir->Clone(getter_AddRefs(updater)); + if (NS_FAILED(rv)) + return false; +#if defined(XP_MACOSX) + rv = updater->AppendNative(NS_LITERAL_CSTRING(UPDATER_APP)); + nsresult tmp = updater->AppendNative(NS_LITERAL_CSTRING("Contents")); + if (NS_FAILED(tmp)) { + rv = tmp; + } + tmp = updater->AppendNative(NS_LITERAL_CSTRING("MacOS")); + if (NS_FAILED(tmp) || NS_FAILED(rv)) + return false; +#endif + rv = updater->AppendNative(NS_LITERAL_CSTRING(UPDATER_BIN)); + return NS_SUCCEEDED(rv); +} + +/** + * Appends the specified path to the library path. + * This is used so that updater can find libmozsqlite3.so and other shared libs. + * + * @param pathToAppend A new library path to prepend to LD_LIBRARY_PATH + */ +#if defined(MOZ_VERIFY_MAR_SIGNATURE) && !defined(XP_WIN) && \ + !defined(XP_MACOSX) && !defined(MOZ_WIDGET_GONK) +#include "prprf.h" +#define PATH_SEPARATOR ":" +#define LD_LIBRARY_PATH_ENVVAR_NAME "LD_LIBRARY_PATH" +static void +AppendToLibPath(const char *pathToAppend) +{ + char *pathValue = getenv(LD_LIBRARY_PATH_ENVVAR_NAME); + if (nullptr == pathValue || '\0' == *pathValue) { + char *s = PR_smprintf("%s=%s", LD_LIBRARY_PATH_ENVVAR_NAME, pathToAppend); + PR_SetEnv(s); + } else if (!strstr(pathValue, pathToAppend)) { + char *s = PR_smprintf("%s=%s" PATH_SEPARATOR "%s", + LD_LIBRARY_PATH_ENVVAR_NAME, pathToAppend, pathValue); + PR_SetEnv(s); + } + + // The memory used by PR_SetEnv is not copied to the environment on all + // platform, it can be used by reference directly. So we purposely do not + // call PR_smprintf_free on s. Subsequent calls to PR_SetEnv will free + // the old memory first. +} +#endif + +/** + * Switch an existing application directory to an updated version that has been + * staged. + * + * @param greDir the GRE dir + * @param updateDir the update dir where the mar file is located + * @param appDir the app dir + * @param appArgc the number of args to the application + * @param appArgv the args to the application, used for restarting if needed + */ +static void +SwitchToUpdatedApp(nsIFile *greDir, nsIFile *updateDir, + nsIFile *appDir, int appArgc, char **appArgv) +{ + nsresult rv; + + // Steps: + // - copy updater into updates/0/MozUpdater/bgupdate/ dir on all platforms + // except Windows + // - run updater with the correct arguments +#ifndef XP_WIN + nsCOMPtr<nsIFile> mozUpdaterDir; + rv = updateDir->Clone(getter_AddRefs(mozUpdaterDir)); + if (NS_FAILED(rv)) { + LOG(("failed cloning update dir\n")); + return; + } + + // Create a new directory named MozUpdater in the updates/0 directory to copy + // the updater files to that will be used to replace the installation with the + // staged application that has been updated. Note that we don't check for + // directory creation errors since the call to CopyUpdaterIntoUpdateDir will + // fail if the creation of the directory fails. A unique directory is created + // in MozUpdater in case a previous attempt locked the directory or files. + mozUpdaterDir->Append(NS_LITERAL_STRING("MozUpdater")); + mozUpdaterDir->Append(NS_LITERAL_STRING("bgupdate")); + rv = mozUpdaterDir->CreateUnique(nsIFile::DIRECTORY_TYPE, 0755); + if (NS_FAILED(rv)) { + LOG(("failed creating unique dir\n")); + return; + } + + nsCOMPtr<nsIFile> updater; + if (!CopyUpdaterIntoUpdateDir(greDir, appDir, mozUpdaterDir, updater)) { + LOG(("failed copying updater\n")); + return; + } +#endif + + // We need to use the value returned from XRE_GetBinaryPath when attempting + // to restart the running application. + nsCOMPtr<nsIFile> appFile; + +#if defined(XP_MACOSX) + // On OS X we need to pass the location of the xulrunner-stub executable + // rather than xulrunner-bin. See bug 349737. + GetXULRunnerStubPath(appArgv[0], getter_AddRefs(appFile)); +#else + XRE_GetBinaryPath(appArgv[0], getter_AddRefs(appFile)); +#endif + + if (!appFile) + return; + +#ifdef XP_WIN + nsAutoString appFilePathW; + rv = appFile->GetPath(appFilePathW); + if (NS_FAILED(rv)) { + return; + } + NS_ConvertUTF16toUTF8 appFilePath(appFilePathW); + + nsCOMPtr<nsIFile> updater; + rv = greDir->Clone(getter_AddRefs(updater)); + if (NS_FAILED(rv)) { + return; + } + + rv = updater->AppendNative(NS_LITERAL_CSTRING(UPDATER_BIN)); + if (NS_FAILED(rv)) { + return; + } + + nsAutoString updaterPathW; + rv = updater->GetPath(updaterPathW); + if (NS_FAILED(rv)) { + return; + } + NS_ConvertUTF16toUTF8 updaterPath(updaterPathW); +#else + + nsAutoCString appFilePath; +#if defined(MOZ_WIDGET_GONK) + appFilePath.Assign(kB2GServiceArgv[0]); + appArgc = kB2GServiceArgc; + appArgv = const_cast<char**>(kB2GServiceArgv); +#else + rv = appFile->GetNativePath(appFilePath); + if (NS_FAILED(rv)) + return; +#endif + + nsAutoCString updaterPath; + rv = updater->GetNativePath(updaterPath); + if (NS_FAILED(rv)) + return; +#endif + + nsAutoCString installDirPath; + rv = GetInstallDirPath(appDir, installDirPath); + if (NS_FAILED(rv)) { + return; + } + + // Get the directory where the update will be staged. + nsAutoCString applyToDir; + nsCOMPtr<nsIFile> updatedDir; +#ifdef XP_MACOSX + if (!GetFile(updateDir, NS_LITERAL_CSTRING("Updated.app"), updatedDir)) { +#else + if (!GetFile(appDir, NS_LITERAL_CSTRING("updated"), updatedDir)) { +#endif + return; + } +#ifdef XP_WIN + nsAutoString applyToDirW; + rv = updatedDir->GetPath(applyToDirW); + if (NS_FAILED(rv)) { + return; + } + applyToDir = NS_ConvertUTF16toUTF8(applyToDirW); +#else + rv = updatedDir->GetNativePath(applyToDir); +#endif + if (NS_FAILED(rv)) { + return; + } + + // Make sure that the updated directory exists + bool updatedDirExists = false; + updatedDir->Exists(&updatedDirExists); + if (!updatedDirExists) { + return; + } + +#if defined(XP_WIN) + nsAutoString updateDirPathW; + rv = updateDir->GetPath(updateDirPathW); + NS_ConvertUTF16toUTF8 updateDirPath(updateDirPathW); +#else + nsAutoCString updateDirPath; + rv = updateDir->GetNativePath(updateDirPath); +#endif + if (NS_FAILED(rv)) + return; + + // Get the current working directory. + char workingDirPath[MAXPATHLEN]; + rv = GetCurrentWorkingDir(workingDirPath, sizeof(workingDirPath)); + if (NS_FAILED(rv)) + return; + + // Construct the PID argument for this process. We start the updater using + // execv on all Unix platforms except Mac, so on those platforms we pass 0 + // instead of a good PID to signal the updater not to try and wait for us. +#if defined(XP_UNIX) & !defined(XP_MACOSX) + nsAutoCString pid("0"); +#else + nsAutoCString pid; + pid.AppendInt((int32_t) getpid()); +#endif + + // Append a special token to the PID in order to let the updater know that it + // just needs to replace the update directory. + pid.AppendLiteral("/replace"); + + int immersiveArgc = 0; + int argc = appArgc + 6 + immersiveArgc; + char **argv = new char*[argc + 1]; + if (!argv) + return; + argv[0] = (char*) updaterPath.get(); + argv[1] = (char*) updateDirPath.get(); + argv[2] = (char*) installDirPath.get(); + argv[3] = (char*) applyToDir.get(); + argv[4] = (char*) pid.get(); + if (appArgc) { + argv[5] = workingDirPath; + argv[6] = (char*) appFilePath.get(); + for (int i = 1; i < appArgc; ++i) + argv[6 + i] = appArgv[i]; +#ifdef XP_WIN + if (immersiveArgc) { + argv[argc - 1] = "-ServerName:DefaultBrowserServer"; + } +#endif + argv[argc] = nullptr; + } else { + argc = 5; + argv[5] = nullptr; + } + + if (gSafeMode) { + PR_SetEnv("MOZ_SAFE_MODE_RESTART=1"); + } +#if defined(MOZ_VERIFY_MAR_SIGNATURE) && !defined(XP_WIN) && \ + !defined(XP_MACOSX) && !defined(MOZ_WIDGET_GONK) + AppendToLibPath(installDirPath.get()); +#endif + + LOG(("spawning updater process for replacing [%s]\n", updaterPath.get())); + +#if defined(XP_UNIX) & !defined(XP_MACOSX) +# if defined(MOZ_WIDGET_GONK) + // In Gonk, we preload libmozglue, which the updater process doesn't need. + // Since the updater will move and delete libmozglue.so, this can actually + // stop the /system mount from correctly being remounted as read-only. + unsetenv("LD_PRELOAD"); +# endif + exit(execv(updaterPath.get(), argv)); +#elif defined(XP_WIN) + // Switch the application using updater.exe + if (!WinLaunchChild(updaterPathW.get(), argc, argv)) { + return; + } + _exit(0); +#elif defined(XP_MACOSX) + CommandLineServiceMac::SetupMacCommandLine(argc, argv, true); + LaunchChildMac(argc, argv); + exit(0); +#else + PR_CreateProcessDetached(updaterPath.get(), argv, nullptr, nullptr); + exit(0); +#endif +} + +#if defined(MOZ_WIDGET_GONK) +static nsresult +GetOSApplyToDir(nsACString& applyToDir) +{ + nsCOMPtr<nsIProperties> ds = + do_GetService(NS_DIRECTORY_SERVICE_CONTRACTID); + NS_ASSERTION(ds, "Can't get directory service"); + + nsCOMPtr<nsIFile> osApplyToDir; + nsresult rv = ds->Get(XRE_OS_UPDATE_APPLY_TO_DIR, NS_GET_IID(nsIFile), + getter_AddRefs(osApplyToDir)); + if (NS_FAILED(rv)) { + LOG(("Can't get the OS applyTo dir")); + return rv; + } + + return osApplyToDir->GetNativePath(applyToDir); +} + +static void +SetOSApplyToDir(nsIUpdate* update, const nsACString& osApplyToDir) +{ + nsresult rv; + nsCOMPtr<nsIWritablePropertyBag> updateProperties = + do_QueryInterface(update, &rv); + + if (NS_FAILED(rv)) { + return; + } + + RefPtr<nsVariant> variant = new nsVariant(); + rv = variant->SetAsACString(osApplyToDir); + if (NS_FAILED(rv)) { + return; + } + + updateProperties->SetProperty(NS_LITERAL_STRING("osApplyToDir"), variant); +} +#endif + +/** + * Apply an update. This applies to both normal and staged updates. + * + * @param greDir the GRE dir + * @param updateDir the update root dir + * @param statusFile the update.status file + * @param appDir the app dir + * @param appArgc the number of args to the application + * @param appArgv the args to the application, used for restarting if needed + * @param restart if true, apply the update in the foreground and restart the + * application when done. otherwise, stage the update and don't + * restart the application. + * @param outpid out parameter holding the handle to the updater application for + * staging updates. + */ +static void +ApplyUpdate(nsIFile *greDir, nsIFile *updateDir, nsIFile *statusFile, + nsIFile *appDir, int appArgc, char **appArgv, + bool restart, bool isOSUpdate, nsIFile *osApplyToDir, + ProcessType *outpid) +{ + nsresult rv; + + // Steps: + // - mark update as 'applying' + // - copy updater into update dir on all platforms except Windows + // - run updater w/ appDir as the current working dir +#ifndef XP_WIN + nsCOMPtr<nsIFile> updater; + if (!CopyUpdaterIntoUpdateDir(greDir, appDir, updateDir, updater)) { + LOG(("failed copying updater\n")); + return; + } +#endif + + // We need to use the value returned from XRE_GetBinaryPath when attempting + // to restart the running application. + nsCOMPtr<nsIFile> appFile; + +#if defined(XP_MACOSX) + // On OS X we need to pass the location of the xulrunner-stub executable + // rather than xulrunner-bin. See bug 349737. + GetXULRunnerStubPath(appArgv[0], getter_AddRefs(appFile)); +#else + XRE_GetBinaryPath(appArgv[0], getter_AddRefs(appFile)); +#endif + + if (!appFile) + return; + +#ifdef XP_WIN + nsAutoString appFilePathW; + rv = appFile->GetPath(appFilePathW); + if (NS_FAILED(rv)) { + return; + } + NS_ConvertUTF16toUTF8 appFilePath(appFilePathW); + + nsCOMPtr<nsIFile> updater; + rv = greDir->Clone(getter_AddRefs(updater)); + if (NS_FAILED(rv)) { + return; + } + + rv = updater->AppendNative(NS_LITERAL_CSTRING(UPDATER_BIN)); + if (NS_FAILED(rv)) { + return; + } + + nsAutoString updaterPathW; + rv = updater->GetPath(updaterPathW); + if (NS_FAILED(rv)) { + return; + } + NS_ConvertUTF16toUTF8 updaterPath(updaterPathW); +#else + nsAutoCString appFilePath; + rv = appFile->GetNativePath(appFilePath); + if (NS_FAILED(rv)) + return; + + nsAutoCString updaterPath; + rv = updater->GetNativePath(updaterPath); + if (NS_FAILED(rv)) + return; + +#endif + + nsAutoCString installDirPath; + rv = GetInstallDirPath(appDir, installDirPath); + if (NS_FAILED(rv)) + return; + + // Get the directory where the update was staged for replace and GONK OS + // Updates or where it will be applied. +#ifndef MOZ_WIDGET_GONK + // OS Updates are only supported on GONK so force it to false on everything + // but GONK to simplify the following logic. + isOSUpdate = false; +#endif + nsAutoCString applyToDir; + nsCOMPtr<nsIFile> updatedDir; + if (restart && !isOSUpdate) { + applyToDir.Assign(installDirPath); + } else { +#ifdef XP_MACOSX + if (!GetFile(updateDir, NS_LITERAL_CSTRING("Updated.app"), updatedDir)) { +#else + if (!GetFile(appDir, NS_LITERAL_CSTRING("updated"), updatedDir)) { +#endif + return; + } +#ifdef XP_WIN + nsAutoString applyToDirW; + rv = updatedDir->GetPath(applyToDirW); + if (NS_FAILED(rv)) { + return; + } + applyToDir = NS_ConvertUTF16toUTF8(applyToDirW); +#elif MOZ_WIDGET_GONK + if (isOSUpdate) { + if (!osApplyToDir) { + return; + } + rv = osApplyToDir->GetNativePath(applyToDir); + } else { + rv = updatedDir->GetNativePath(applyToDir); + } +#else + rv = updatedDir->GetNativePath(applyToDir); +#endif + } + if (NS_FAILED(rv)) + return; + +#if defined(XP_WIN) + nsAutoString updateDirPathW; + rv = updateDir->GetPath(updateDirPathW); + NS_ConvertUTF16toUTF8 updateDirPath(updateDirPathW); +#else + nsAutoCString updateDirPath; + rv = updateDir->GetNativePath(updateDirPath); +#endif + if (NS_FAILED(rv)) { + return; + } + + // Get the current working directory. + char workingDirPath[MAXPATHLEN]; + rv = GetCurrentWorkingDir(workingDirPath, sizeof(workingDirPath)); + if (NS_FAILED(rv)) + return; + + // We used to write out "Applying" to the update.status file here. + // Instead we do this from within the updater application now. + // This is so that we don't overwrite the status of pending-service + // in the Windows case. This change was made for all platforms so + // that it stays consistent across all OS. + + // On platforms where we are not calling execv, we may need to make the + // updater executable wait for the calling process to exit. Otherwise, the + // updater may have trouble modifying our executable image (because it might + // still be in use). This is accomplished by passing our PID to the updater so + // that it can wait for us to exit. This is not perfect as there is a race + // condition that could bite us. It's possible that the calling process could + // exit before the updater waits on the specified PID, and in the meantime a + // new process with the same PID could be created. This situation is unlikely, + // however, given the way most operating systems recycle PIDs. We'll take our + // chances ;-) + // Construct the PID argument for this process. If we are using execv, then + // we pass "0" which is then ignored by the updater. + nsAutoCString pid; + if (!restart) { + // Signal the updater application that it should stage the update. + pid.AssignASCII("-1"); + } else { +#if defined(XP_UNIX) & !defined(XP_MACOSX) + pid.AssignASCII("0"); +#else + pid.AppendInt((int32_t) getpid()); +#endif + } + + int immersiveArgc = 0; + int argc = appArgc + 6 + immersiveArgc; + char **argv = new char*[argc + 1 ]; + if (!argv) + return; + argv[0] = (char*) updaterPath.get(); + argv[1] = (char*) updateDirPath.get(); + argv[2] = (char*) installDirPath.get(); + argv[3] = (char*) applyToDir.get(); + argv[4] = (char*) pid.get(); + if (restart && appArgc) { + argv[5] = workingDirPath; + argv[6] = (char*) appFilePath.get(); + for (int i = 1; i < appArgc; ++i) + argv[6 + i] = appArgv[i]; +#ifdef XP_WIN + if (immersiveArgc) { + argv[argc - 1] = "-ServerName:DefaultBrowserServer"; + } +#endif + argv[argc] = nullptr; + } else { + argc = 5; + argv[5] = nullptr; + } + + if (gSafeMode) { + PR_SetEnv("MOZ_SAFE_MODE_RESTART=1"); + } +#if defined(MOZ_VERIFY_MAR_SIGNATURE) && !defined(XP_WIN) && \ + !defined(XP_MACOSX) && !defined(MOZ_WIDGET_GONK) + AppendToLibPath(installDirPath.get()); +#endif + + if (isOSUpdate) { + PR_SetEnv("MOZ_OS_UPDATE=1"); + } +#if defined(MOZ_WIDGET_GONK) + // We want the updater to be CPU friendly and not subject to being killed by + // the low memory killer, so we pass in some preferences to allow it to + // adjust its priority. + + int32_t prioVal = Preferences::GetInt(kAppUpdaterPrio, + kAppUpdaterPrioDefault); + int32_t oomScoreAdj = Preferences::GetInt(kAppUpdaterOomScoreAdj, + kAppUpdaterOomScoreAdjDefault); + int32_t ioprioClass = Preferences::GetInt(kAppUpdaterIOPrioClass, + kAppUpdaterIOPrioClassDefault); + int32_t ioprioLevel = Preferences::GetInt(kAppUpdaterIOPrioLevel, + kAppUpdaterIOPrioLevelDefault); + nsPrintfCString prioEnv("MOZ_UPDATER_PRIO=%d/%d/%d/%d", + prioVal, oomScoreAdj, ioprioClass, ioprioLevel); + // Note: we allocate a new string on heap and pass that to PR_SetEnv, since + // the string can be used after this function returns. This means that we + // will intentionally leak this buffer. + PR_SetEnv(ToNewCString(prioEnv)); +#endif + + LOG(("spawning updater process [%s]\n", updaterPath.get())); + +#if defined(XP_UNIX) && !defined(XP_MACOSX) + // We use execv to spawn the updater process on all UNIX systems except Mac OSX + // since it is known to cause problems on the Mac. Windows has execv, but it + // is a faked implementation that doesn't really replace the current process. + // Instead it spawns a new process, so we gain nothing from using execv on + // Windows. + if (restart) { + exit(execv(updaterPath.get(), argv)); + } + *outpid = fork(); + if (*outpid == -1) { + return; + } else if (*outpid == 0) { + exit(execv(updaterPath.get(), argv)); + } +#elif defined(XP_WIN) + // Launch the update using updater.exe + if (!WinLaunchChild(updaterPathW.get(), argc, argv, nullptr, outpid)) { + return; + } + + if (restart) { + // We are going to process an update so we should exit now + _exit(0); + } +#elif defined(XP_MACOSX) + CommandLineServiceMac::SetupMacCommandLine(argc, argv, restart); + // We need to detect whether elevation is required for this update. This can + // occur when an admin user installs the application, but another admin + // user attempts to update (see bug 394984). + if (restart && !IsRecursivelyWritable(installDirPath.get())) { + if (!LaunchElevatedUpdate(argc, argv, outpid)) { + LOG(("Failed to launch elevated update!")); + exit(1); + } + exit(0); + } else { + if (restart) { + LaunchChildMac(argc, argv); + exit(0); + } + LaunchChildMac(argc, argv, outpid); + } +#else + *outpid = PR_CreateProcess(updaterPath.get(), argv, nullptr, nullptr); + if (restart) { + exit(0); + } +#endif +} + +/** + * Wait briefly to see if a process terminates, then return true if it has. + */ +static bool +ProcessHasTerminated(ProcessType pt) +{ +#if defined(XP_WIN) + if (WaitForSingleObject(pt, 1000)) { + return false; + } + CloseHandle(pt); + return true; +#elif defined(XP_MACOSX) + // We're waiting for the process to terminate in LaunchChildMac. + return true; +#elif defined(XP_UNIX) + int exitStatus; + pid_t exited = waitpid(pt, &exitStatus, WNOHANG); + if (exited == 0) { + // Process is still running. + sleep(1); + return false; + } + if (exited == -1) { + LOG(("Error while checking if the updater process is finished")); + // This shouldn't happen, but if it does, the updater process is lost to us, + // so the best we can do is pretend that it's exited. + return true; + } + // If we get here, the process has exited; make sure it exited normally. + if (WIFEXITED(exitStatus) && (WEXITSTATUS(exitStatus) != 0)) { + LOG(("Error while running the updater process, check update.log")); + } + return true; +#else + // No way to have a non-blocking implementation on these platforms, + // because we're using NSPR and it only provides a blocking wait. + int32_t exitCode; + PR_WaitProcess(pt, &exitCode); + if (exitCode != 0) { + LOG(("Error while running the updater process, check update.log")); + } + return true; +#endif +} + +nsresult +ProcessUpdates(nsIFile *greDir, nsIFile *appDir, nsIFile *updRootDir, + int argc, char **argv, const char *appVersion, + bool restart, bool isOSUpdate, nsIFile *osApplyToDir, + ProcessType *pid) +{ + nsresult rv; + + nsCOMPtr<nsIFile> updatesDir; + rv = updRootDir->Clone(getter_AddRefs(updatesDir)); + if (NS_FAILED(rv)) + return rv; + + rv = updatesDir->AppendNative(NS_LITERAL_CSTRING("updates")); + if (NS_FAILED(rv)) + return rv; + + rv = updatesDir->AppendNative(NS_LITERAL_CSTRING("0")); + if (NS_FAILED(rv)) + return rv; + + // Return early since there isn't a valid update when the update application + // version file doesn't exist or if the update's application version is less + // than the current application version. The cleanup of the update will happen + // during post update processing in nsUpdateService.js. + nsCOMPtr<nsIFile> versionFile; + if (!GetVersionFile(updatesDir, versionFile) || + IsOlderVersion(versionFile, appVersion)) { + return NS_OK; + } + + nsCOMPtr<nsIFile> statusFile; + UpdateStatus status = GetUpdateStatus(updatesDir, statusFile); + switch (status) { + case ePendingElevate: { + if (NS_IsMainThread()) { + // Only do this if we're called from the main thread. + nsCOMPtr<nsIUpdatePrompt> up = + do_GetService("@mozilla.org/updates/update-prompt;1"); + if (up) { + up->ShowUpdateElevationRequired(); + } + break; + } + // Intentional fallthrough to ePendingUpdate and ePendingService. + MOZ_FALLTHROUGH; + } + case ePendingUpdate: + case ePendingService: { + ApplyUpdate(greDir, updatesDir, statusFile, appDir, argc, argv, restart, + isOSUpdate, osApplyToDir, pid); + break; + } + case eAppliedUpdate: + case eAppliedService: + // An update was staged and needs to be switched so the updated application + // is used. + SwitchToUpdatedApp(greDir, updatesDir, appDir, argc, argv); + break; + case eNoUpdateAction: + // We don't need to do any special processing here, we'll just continue to + // startup the application. + break; + } + + return NS_OK; +} + + + +NS_IMPL_ISUPPORTS(nsUpdateProcessor, nsIUpdateProcessor) + +nsUpdateProcessor::nsUpdateProcessor() + : mUpdaterPID(0) +{ +} + +nsUpdateProcessor::~nsUpdateProcessor() +{ +} + +NS_IMETHODIMP +nsUpdateProcessor::ProcessUpdate(nsIUpdate* aUpdate) +{ + nsCOMPtr<nsIFile> greDir, appDir, updRoot; + nsAutoCString appVersion; + int argc; + char **argv; + + nsAutoCString binPath; + nsXREDirProvider* dirProvider = nsXREDirProvider::GetSingleton(); + if (dirProvider) { // Normal code path + // Check for and process any available updates + bool persistent; + nsresult rv = NS_ERROR_FAILURE; // Take the NS_FAILED path when non-GONK +#ifdef MOZ_WIDGET_GONK + // Check in the sdcard for updates first, since that's our preferred + // download location. + rv = dirProvider->GetFile(XRE_UPDATE_ARCHIVE_DIR, &persistent, + getter_AddRefs(updRoot)); +#endif + if (NS_FAILED(rv)) { + rv = dirProvider->GetFile(XRE_UPDATE_ROOT_DIR, &persistent, + getter_AddRefs(updRoot)); + } + // XRE_UPDATE_ROOT_DIR may fail. Fallback to appDir if failed + if (NS_FAILED(rv)) + updRoot = dirProvider->GetAppDir(); + + greDir = dirProvider->GetGREDir(); + nsCOMPtr<nsIFile> exeFile; + rv = dirProvider->GetFile(XRE_EXECUTABLE_FILE, &persistent, + getter_AddRefs(exeFile)); + if (NS_SUCCEEDED(rv)) + rv = exeFile->GetParent(getter_AddRefs(appDir)); + + if (NS_FAILED(rv)) + appDir = dirProvider->GetAppDir(); + + appVersion = gAppData->version; + argc = gRestartArgc; + argv = gRestartArgv; + } else { + // In the xpcshell environment, the usual XRE_main is not run, so things + // like dirProvider and gAppData do not exist. This code path accesses + // XPCOM (which is not available in the previous code path) in order to get + // the same information. + nsCOMPtr<nsIProperties> ds = + do_GetService(NS_DIRECTORY_SERVICE_CONTRACTID); + if (!ds) { + NS_ABORT(); // There's nothing which we can do if this fails! + } + + nsresult rv = ds->Get(NS_GRE_DIR, NS_GET_IID(nsIFile), + getter_AddRefs(greDir)); + NS_ASSERTION(NS_SUCCEEDED(rv), "Can't get the GRE dir"); + + nsCOMPtr<nsIFile> exeFile; + rv = ds->Get(XRE_EXECUTABLE_FILE, NS_GET_IID(nsIFile), + getter_AddRefs(exeFile)); + if (NS_SUCCEEDED(rv)) + rv = exeFile->GetParent(getter_AddRefs(appDir)); + + NS_ASSERTION(NS_SUCCEEDED(rv), "Can't get the XREExeF parent dir"); + + rv = ds->Get(XRE_UPDATE_ROOT_DIR, NS_GET_IID(nsIFile), + getter_AddRefs(updRoot)); + NS_ASSERTION(NS_SUCCEEDED(rv), "Can't get the UpdRootD dir"); + + nsCOMPtr<nsIXULAppInfo> appInfo = + do_GetService("@mozilla.org/xre/app-info;1"); + if (appInfo) { + rv = appInfo->GetVersion(appVersion); + NS_ENSURE_SUCCESS(rv, rv); + } else { + appVersion = MOZ_APP_VERSION; + } + + // We need argv[0] to point to the current executable's name. The rest of + // the entries in this array will be ignored if argc<2. Therefore, for + // xpcshell, we only fill out that item, and leave the rest empty. + argc = 1; + nsCOMPtr<nsIFile> binary; + rv = ds->Get(XRE_EXECUTABLE_FILE, NS_GET_IID(nsIFile), + getter_AddRefs(binary)); + NS_ASSERTION(NS_SUCCEEDED(rv), "Can't get the binary path"); + binary->GetNativePath(binPath); + } + + // Copy the parameters to the StagedUpdateInfo structure shared with the + // watcher thread. + mInfo.mGREDir = greDir; + mInfo.mAppDir = appDir; + mInfo.mUpdateRoot = updRoot; + mInfo.mArgc = argc; + mInfo.mArgv = new char*[argc]; + if (dirProvider) { + for (int i = 0; i < argc; ++i) { + const size_t length = strlen(argv[i]); + mInfo.mArgv[i] = new char[length + 1]; + strcpy(mInfo.mArgv[i], argv[i]); + } + } else { + MOZ_ASSERT(argc == 1); // see above + const size_t length = binPath.Length(); + mInfo.mArgv[0] = new char[length + 1]; + strcpy(mInfo.mArgv[0], binPath.get()); + } + mInfo.mAppVersion = appVersion; + +#if defined(MOZ_WIDGET_GONK) + NS_ENSURE_ARG_POINTER(aUpdate); + + bool isOSUpdate; + if (NS_SUCCEEDED(aUpdate->GetIsOSUpdate(&isOSUpdate)) && + isOSUpdate) { + nsAutoCString osApplyToDir; + + // This needs to be done on the main thread, so we pass it along in + // BackgroundThreadInfo + nsresult rv = GetOSApplyToDir(osApplyToDir); + if (NS_FAILED(rv)) { + LOG(("Can't get the OS apply to dir")); + return rv; + } + + SetOSApplyToDir(aUpdate, osApplyToDir); + + mInfo.mIsOSUpdate = true; + rv = NS_NewNativeLocalFile(osApplyToDir, false, + getter_AddRefs(mInfo.mOSApplyToDir)); + if (NS_FAILED(rv)) { + LOG(("Can't create nsIFile for OS apply to dir")); + return rv; + } + } +#endif + + MOZ_ASSERT(NS_IsMainThread(), "not main thread"); + nsCOMPtr<nsIRunnable> r = NewRunnableMethod(this, &nsUpdateProcessor::StartStagedUpdate); + return NS_NewThread(getter_AddRefs(mProcessWatcher), r); +} + + + +void +nsUpdateProcessor::StartStagedUpdate() +{ + MOZ_ASSERT(!NS_IsMainThread(), "main thread"); + + nsresult rv = ProcessUpdates(mInfo.mGREDir, + mInfo.mAppDir, + mInfo.mUpdateRoot, + mInfo.mArgc, + mInfo.mArgv, + mInfo.mAppVersion.get(), + false, + mInfo.mIsOSUpdate, + mInfo.mOSApplyToDir, + &mUpdaterPID); + NS_ENSURE_SUCCESS_VOID(rv); + + if (mUpdaterPID) { + // Track the state of the updater process while it is staging an update. + rv = NS_DispatchToCurrentThread(NewRunnableMethod(this, &nsUpdateProcessor::WaitForProcess)); + NS_ENSURE_SUCCESS_VOID(rv); + } else { + // Failed to launch the updater process for some reason. + // We need to shutdown the current thread as there isn't anything more for + // us to do... + rv = NS_DispatchToMainThread(NewRunnableMethod(this, &nsUpdateProcessor::ShutdownWatcherThread)); + NS_ENSURE_SUCCESS_VOID(rv); + } +} + +void +nsUpdateProcessor::ShutdownWatcherThread() +{ + MOZ_ASSERT(NS_IsMainThread(), "not main thread"); + mProcessWatcher->Shutdown(); + mProcessWatcher = nullptr; +} + +void +nsUpdateProcessor::WaitForProcess() +{ + MOZ_ASSERT(!NS_IsMainThread(), "main thread"); + if (ProcessHasTerminated(mUpdaterPID)) { + NS_DispatchToMainThread(NewRunnableMethod(this, &nsUpdateProcessor::UpdateDone)); + } else { + NS_DispatchToCurrentThread(NewRunnableMethod(this, &nsUpdateProcessor::WaitForProcess)); + } +} + +void +nsUpdateProcessor::UpdateDone() +{ + MOZ_ASSERT(NS_IsMainThread(), "not main thread"); + + nsCOMPtr<nsIUpdateManager> um = + do_GetService("@mozilla.org/updates/update-manager;1"); + if (um) { + um->RefreshUpdateStatus(); + } + + ShutdownWatcherThread(); +} diff --git a/toolkit/xre/nsUpdateDriver.h b/toolkit/xre/nsUpdateDriver.h new file mode 100644 index 0000000000..ed87751980 --- /dev/null +++ b/toolkit/xre/nsUpdateDriver.h @@ -0,0 +1,107 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim:set ts=2 sw=2 sts=2 et cindent: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef nsUpdateDriver_h__ +#define nsUpdateDriver_h__ + +#include "nscore.h" +#include "nsIUpdateService.h" +#include "nsIThread.h" +#include "nsCOMPtr.h" +#include "nsString.h" +#include "mozilla/Attributes.h" + +class nsIFile; + +#if defined(XP_WIN) +#include <windows.h> + typedef HANDLE ProcessType; +#elif defined(XP_UNIX) + typedef pid_t ProcessType; +#else +#include "prproces.h" + typedef PRProcess* ProcessType; +#endif + +/** + * This function processes any available updates. As part of that process, it + * may exit the current process and relaunch it at a later time. + * + * Two directories are passed to this function: greDir (where the actual + * binary resides) and appDir (which contains application.ini for XULRunner + * apps). If this is not a XULRunner app then appDir is identical to greDir. + * + * The argc and argv passed to this function should be what is needed to + * relaunch the current process. + * + * The appVersion param passed to this function is the current application's + * version and is used to determine if an update's version is older than the + * current application version. + * + * If you want the update to be processed without restarting, set the restart + * parameter to false. + * + * This function does not modify appDir. + */ +nsresult ProcessUpdates(nsIFile *greDir, nsIFile *appDir, + nsIFile *updRootDir, + int argc, char **argv, + const char *appVersion, + bool restart = true, + bool isOSUpdate = false, + nsIFile *osApplyToDir = nullptr, + ProcessType *pid = nullptr); + +// The implementation of the update processor handles the task of loading the +// updater application for staging an update. +// XXX ehsan this is living in this file in order to make use of the existing +// stuff here, we might want to move it elsewhere in the future. +class nsUpdateProcessor final : public nsIUpdateProcessor +{ +public: + nsUpdateProcessor(); + + NS_DECL_THREADSAFE_ISUPPORTS + NS_DECL_NSIUPDATEPROCESSOR + +private: + ~nsUpdateProcessor(); + + struct StagedUpdateInfo { + StagedUpdateInfo() + : mArgc(0), + mArgv(nullptr), + mIsOSUpdate(false) + {} + ~StagedUpdateInfo() { + for (int i = 0; i < mArgc; ++i) { + delete[] mArgv[i]; + } + delete[] mArgv; + } + + nsCOMPtr<nsIFile> mGREDir; + nsCOMPtr<nsIFile> mAppDir; + nsCOMPtr<nsIFile> mUpdateRoot; + nsCOMPtr<nsIFile> mOSApplyToDir; + int mArgc; + char **mArgv; + nsCString mAppVersion; + bool mIsOSUpdate; + }; + +private: + void StartStagedUpdate(); + void WaitForProcess(); + void UpdateDone(); + void ShutdownWatcherThread(); + +private: + ProcessType mUpdaterPID; + nsCOMPtr<nsIThread> mProcessWatcher; + StagedUpdateInfo mInfo; +}; +#endif // nsUpdateDriver_h__ diff --git a/toolkit/xre/nsWindowsRestart.cpp b/toolkit/xre/nsWindowsRestart.cpp new file mode 100644 index 0000000000..f8a6ec48b0 --- /dev/null +++ b/toolkit/xre/nsWindowsRestart.cpp @@ -0,0 +1,300 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +// This file is not build directly. Instead, it is included in multiple +// shared objects. + +#ifdef nsWindowsRestart_cpp +#error "nsWindowsRestart.cpp is not a header file, and must only be included once." +#else +#define nsWindowsRestart_cpp +#endif + +#include "nsUTF8Utils.h" + +#include <shellapi.h> + +// Needed for CreateEnvironmentBlock +#include <userenv.h> +#pragma comment(lib, "userenv.lib") + +/** + * Get the length that the string will take and takes into account the + * additional length if the string needs to be quoted and if characters need to + * be escaped. + */ +static int ArgStrLen(const wchar_t *s) +{ + int backslashes = 0; + int i = wcslen(s); + BOOL hasDoubleQuote = wcschr(s, L'"') != nullptr; + // Only add doublequotes if the string contains a space or a tab + BOOL addDoubleQuotes = wcspbrk(s, L" \t") != nullptr; + + if (addDoubleQuotes) { + i += 2; // initial and final duoblequote + } + + if (hasDoubleQuote) { + while (*s) { + if (*s == '\\') { + ++backslashes; + } else { + if (*s == '"') { + // Escape the doublequote and all backslashes preceding the doublequote + i += backslashes + 1; + } + + backslashes = 0; + } + + ++s; + } + } + + return i; +} + +/** + * Copy string "s" to string "d", quoting the argument as appropriate and + * escaping doublequotes along with any backslashes that immediately precede + * doublequotes. + * The CRT parses this to retrieve the original argc/argv that we meant, + * see STDARGV.C in the MSVC CRT sources. + * + * @return the end of the string + */ +static wchar_t* ArgToString(wchar_t *d, const wchar_t *s) +{ + int backslashes = 0; + BOOL hasDoubleQuote = wcschr(s, L'"') != nullptr; + // Only add doublequotes if the string contains a space or a tab + BOOL addDoubleQuotes = wcspbrk(s, L" \t") != nullptr; + + if (addDoubleQuotes) { + *d = '"'; // initial doublequote + ++d; + } + + if (hasDoubleQuote) { + int i; + while (*s) { + if (*s == '\\') { + ++backslashes; + } else { + if (*s == '"') { + // Escape the doublequote and all backslashes preceding the doublequote + for (i = 0; i <= backslashes; ++i) { + *d = '\\'; + ++d; + } + } + + backslashes = 0; + } + + *d = *s; + ++d; ++s; + } + } else { + wcscpy(d, s); + d += wcslen(s); + } + + if (addDoubleQuotes) { + *d = '"'; // final doublequote + ++d; + } + + return d; +} + +/** + * Creates a command line from a list of arguments. The returned + * string is allocated with "malloc" and should be "free"d. + * + * argv is UTF8 + */ +wchar_t* +MakeCommandLine(int argc, wchar_t **argv) +{ + int i; + int len = 0; + + // The + 1 of the last argument handles the allocation for null termination + for (i = 0; i < argc; ++i) + len += ArgStrLen(argv[i]) + 1; + + // Protect against callers that pass 0 arguments + if (len == 0) + len = 1; + + wchar_t *s = (wchar_t*) malloc(len * sizeof(wchar_t)); + if (!s) + return nullptr; + + wchar_t *c = s; + for (i = 0; i < argc; ++i) { + c = ArgToString(c, argv[i]); + if (i + 1 != argc) { + *c = ' '; + ++c; + } + } + + *c = '\0'; + + return s; +} + +/** + * Convert UTF8 to UTF16 without using the normal XPCOM goop, which we + * can't link to updater.exe. + */ +static char16_t* +AllocConvertUTF8toUTF16(const char *arg) +{ + // UTF16 can't be longer in units than UTF8 + int len = strlen(arg); + char16_t *s = new char16_t[(len + 1) * sizeof(char16_t)]; + if (!s) + return nullptr; + + ConvertUTF8toUTF16 convert(s); + convert.write(arg, len); + convert.write_terminator(); + return s; +} + +static void +FreeAllocStrings(int argc, wchar_t **argv) +{ + while (argc) { + --argc; + delete [] argv[argc]; + } + + delete [] argv; +} + + + +/** + * Launch a child process with the specified arguments. + * @note argv[0] is ignored + * @note The form of this function that takes char **argv expects UTF-8 + */ + +BOOL +WinLaunchChild(const wchar_t *exePath, + int argc, wchar_t **argv, + HANDLE userToken = nullptr, + HANDLE *hProcess = nullptr); + +BOOL +WinLaunchChild(const wchar_t *exePath, + int argc, char **argv, + HANDLE userToken, + HANDLE *hProcess) +{ + wchar_t** argvConverted = new wchar_t*[argc]; + if (!argvConverted) + return FALSE; + + for (int i = 0; i < argc; ++i) { + argvConverted[i] = reinterpret_cast<wchar_t*>(AllocConvertUTF8toUTF16(argv[i])); + if (!argvConverted[i]) { + FreeAllocStrings(i, argvConverted); + return FALSE; + } + } + + BOOL ok = WinLaunchChild(exePath, argc, argvConverted, userToken, hProcess); + FreeAllocStrings(argc, argvConverted); + return ok; +} + +BOOL +WinLaunchChild(const wchar_t *exePath, + int argc, + wchar_t **argv, + HANDLE userToken, + HANDLE *hProcess) +{ + wchar_t *cl; + BOOL ok; + + cl = MakeCommandLine(argc, argv); + if (!cl) { + return FALSE; + } + + STARTUPINFOW si = {0}; + si.cb = sizeof(STARTUPINFOW); + si.lpDesktop = L"winsta0\\Default"; + PROCESS_INFORMATION pi = {0}; + + if (userToken == nullptr) { + ok = CreateProcessW(exePath, + cl, + nullptr, // no special security attributes + nullptr, // no special thread attributes + FALSE, // don't inherit filehandles + 0, // creation flags + nullptr, // inherit my environment + nullptr, // use my current directory + &si, + &pi); + } else { + // Create an environment block for the process we're about to start using + // the user's token. + LPVOID environmentBlock = nullptr; + if (!CreateEnvironmentBlock(&environmentBlock, userToken, TRUE)) { + environmentBlock = nullptr; + } + + ok = CreateProcessAsUserW(userToken, + exePath, + cl, + nullptr, // no special security attributes + nullptr, // no special thread attributes + FALSE, // don't inherit filehandles + 0, // creation flags + environmentBlock, + nullptr, // use my current directory + &si, + &pi); + + if (environmentBlock) { + DestroyEnvironmentBlock(environmentBlock); + } + } + + if (ok) { + if (hProcess) { + *hProcess = pi.hProcess; // the caller now owns the HANDLE + } else { + CloseHandle(pi.hProcess); + } + CloseHandle(pi.hThread); + } else { + LPVOID lpMsgBuf = nullptr; + FormatMessage(FORMAT_MESSAGE_ALLOCATE_BUFFER | + FORMAT_MESSAGE_FROM_SYSTEM | + FORMAT_MESSAGE_IGNORE_INSERTS, + nullptr, + GetLastError(), + MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), + (LPTSTR) &lpMsgBuf, + 0, + nullptr); + wprintf(L"Error restarting: %s\n", lpMsgBuf ? static_cast<const wchar_t*>(lpMsgBuf) : L"(null)"); + if (lpMsgBuf) + LocalFree(lpMsgBuf); + } + + free(cl); + + return ok; +} diff --git a/toolkit/xre/nsWindowsWMain.cpp b/toolkit/xre/nsWindowsWMain.cpp new file mode 100644 index 0000000000..788d25a900 --- /dev/null +++ b/toolkit/xre/nsWindowsWMain.cpp @@ -0,0 +1,122 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +// This file is a .cpp file meant to be included in nsBrowserApp.cpp and other +// similar bootstrap code. It converts wide-character windows wmain into UTF-8 +// narrow-character strings. + +#ifndef XP_WIN +#error This file only makes sense on Windows. +#endif + +#include "nsUTF8Utils.h" +#include <intrin.h> +#include <math.h> + +#ifndef XRE_DONT_PROTECT_DLL_LOAD +#include "nsSetDllDirectory.h" +#endif + +#if defined(__GNUC__) +#define XRE_DONT_SUPPORT_XPSP2 +#endif + +#ifdef __MINGW32__ + +/* MingW currently does not implement a wide version of the + startup routines. Workaround is to implement something like + it ourselves. See bug 411826 */ + +#include <shellapi.h> + +int wmain(int argc, WCHAR **argv); + +int main(int argc, char **argv) +{ + LPWSTR commandLine = GetCommandLineW(); + int argcw = 0; + LPWSTR *argvw = CommandLineToArgvW(commandLine, &argcw); + if (!argvw) + return 127; + + int result = wmain(argcw, argvw); + LocalFree(argvw); + return result; +} +#endif /* __MINGW32__ */ + +#define main NS_internal_main + +#ifndef XRE_WANT_ENVIRON +int main(int argc, char **argv); +#else +int main(int argc, char **argv, char **envp); +#endif + +static char* +AllocConvertUTF16toUTF8(char16ptr_t arg) +{ + // be generous... UTF16 units can expand up to 3 UTF8 units + int len = wcslen(arg); + char *s = new char[len * 3 + 1]; + if (!s) + return nullptr; + + ConvertUTF16toUTF8 convert(s); + convert.write(arg, len); + convert.write_terminator(); + return s; +} + +static void +FreeAllocStrings(int argc, char **argv) +{ + while (argc) { + --argc; + delete [] argv[argc]; + } + + delete [] argv; +} + +int wmain(int argc, WCHAR **argv) +{ +#ifndef XRE_DONT_PROTECT_DLL_LOAD + mozilla::SanitizeEnvironmentVariables(); + SetDllDirectoryW(L""); +#endif + + char **argvConverted = new char*[argc + 1]; + if (!argvConverted) + return 127; + + for (int i = 0; i < argc; ++i) { + argvConverted[i] = AllocConvertUTF16toUTF8(argv[i]); + if (!argvConverted[i]) { + return 127; + } + } + argvConverted[argc] = nullptr; + + // need to save argvConverted copy for later deletion. + char **deleteUs = new char*[argc+1]; + if (!deleteUs) { + FreeAllocStrings(argc, argvConverted); + return 127; + } + for (int i = 0; i < argc; i++) + deleteUs[i] = argvConverted[i]; +#ifndef XRE_WANT_ENVIRON + int result = main(argc, argvConverted); +#else + // Force creation of the multibyte _environ variable. + getenv("PATH"); + int result = main(argc, argvConverted, _environ); +#endif + + delete[] argvConverted; + FreeAllocStrings(argc, deleteUs); + + return result; +} diff --git a/toolkit/xre/nsX11ErrorHandler.cpp b/toolkit/xre/nsX11ErrorHandler.cpp new file mode 100644 index 0000000000..0db24e58be --- /dev/null +++ b/toolkit/xre/nsX11ErrorHandler.cpp @@ -0,0 +1,162 @@ +/* -*- Mode: C++; tab-width: 40; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "nsX11ErrorHandler.h" + +#include "prenv.h" +#include "nsXULAppAPI.h" +#include "nsExceptionHandler.h" +#include "nsDebug.h" + +#include "mozilla/X11Util.h" +#include <X11/Xlib.h> + +#define BUFSIZE 2048 // What Xlib uses with XGetErrorDatabaseText + +extern "C" { +int +X11Error(Display *display, XErrorEvent *event) { + // Get an indication of how long ago the request that caused the error was + // made. + unsigned long age = NextRequest(display) - event->serial; + + // Get a string to represent the request that caused the error. + nsAutoCString message; + if (event->request_code < 128) { + // Core protocol request + message.AppendInt(event->request_code); + } else { + // Extension request + + // man XSetErrorHandler says "the error handler should not call any + // functions (directly or indirectly) on the display that will generate + // protocol requests or that will look for input events" so we use another + // temporary Display to request extension information. This assumes on + // the DISPLAY environment variable has been set and matches what was used + // to open |display|. + Display *tmpDisplay = XOpenDisplay(nullptr); + if (tmpDisplay) { + int nExts; + char** extNames = XListExtensions(tmpDisplay, &nExts); + int first_error; + if (extNames) { + for (int i = 0; i < nExts; ++i) { + int major_opcode, first_event; + if (XQueryExtension(tmpDisplay, extNames[i], + &major_opcode, &first_event, &first_error) + && major_opcode == event->request_code) { + message.Append(extNames[i]); + message.Append('.'); + message.AppendInt(event->minor_code); + break; + } + } + + XFreeExtensionList(extNames); + } + XCloseDisplay(tmpDisplay); + +#if (MOZ_WIDGET_GTK == 2) + // GDK2 calls XCloseDevice the devices that it opened on startup, but + // the XI protocol no longer ensures that the devices will still exist. + // If they have been removed, then a BadDevice error results. Ignore + // this error. + if (message.EqualsLiteral("XInputExtension.4") && + event->error_code == first_error + 0) { + return 0; + } +#endif + } + } + + char buffer[BUFSIZE]; + if (message.IsEmpty()) { + buffer[0] = '\0'; + } else { + XGetErrorDatabaseText(display, "XRequest", message.get(), "", + buffer, sizeof(buffer)); + } + + nsAutoCString notes; + if (buffer[0]) { + notes.Append(buffer); + } else { + notes.AppendLiteral("Request "); + notes.AppendInt(event->request_code); + notes.Append('.'); + notes.AppendInt(event->minor_code); + } + + notes.AppendLiteral(": "); + + // Get a string to describe the error. + XGetErrorText(display, event->error_code, buffer, sizeof(buffer)); + notes.Append(buffer); + + // For requests where Xlib gets the reply synchronously, |age| will be 1 + // and the stack will include the function making the request. For + // asynchronous requests, the current stack will often be unrelated to the + // point of making the request, even if |age| is 1, but sometimes this may + // help us count back to the point of the request. With XSynchronize on, + // the stack will include the function making the request, even though + // |age| will be 2 for asynchronous requests because XSynchronize is + // implemented by an empty request from an XSync, which has not yet been + // processed. + if (age > 1) { + // XSynchronize returns the previous "after function". If a second + // XSynchronize call returns the same function after an enable call then + // synchronization must have already been enabled. + if (XSynchronize(display, True) == XSynchronize(display, False)) { + notes.AppendLiteral("; sync"); + } else { + notes.AppendLiteral("; "); + notes.AppendInt(uint32_t(age)); + notes.AppendLiteral(" requests ago"); + } + } + +#ifdef MOZ_CRASHREPORTER + switch (XRE_GetProcessType()) { + case GeckoProcessType_Default: + case GeckoProcessType_Plugin: + case GeckoProcessType_Content: + CrashReporter::AppendAppNotesToCrashReport(notes); + break; + default: + ; // crash report notes not supported. + } +#endif + +#ifdef DEBUG + // The resource id is unlikely to be useful in a crash report without + // context of other ids, but add it to the debug console output. + notes.AppendLiteral("; id=0x"); + notes.AppendInt(uint32_t(event->resourceid), 16); +#ifdef MOZ_X11 + // Actually, for requests where Xlib gets the reply synchronously, + // MOZ_X_SYNC=1 will not be necessary, but we don't have a table to tell us + // which requests get a synchronous reply. + if (!PR_GetEnv("MOZ_X_SYNC")) { + notes.AppendLiteral("\nRe-running with MOZ_X_SYNC=1 in the environment may give a more helpful backtrace."); + } +#endif +#endif + + NS_RUNTIMEABORT(notes.get()); + return 0; // not reached +} +} + +void +InstallX11ErrorHandler() +{ + XSetErrorHandler(X11Error); + + Display *display = mozilla::DefaultXDisplay(); + NS_ASSERTION(display, "No X display"); + if (PR_GetEnv("MOZ_X_SYNC")) { + XSynchronize(display, True); + } +} diff --git a/toolkit/xre/nsX11ErrorHandler.h b/toolkit/xre/nsX11ErrorHandler.h new file mode 100644 index 0000000000..abd15fb42e --- /dev/null +++ b/toolkit/xre/nsX11ErrorHandler.h @@ -0,0 +1,17 @@ +/* -*- Mode: C++; tab-width: 40; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifdef MOZ_X11 +#include <X11/Xlib.h> + +/** + * InstallX11ErrorHandler is not suitable for processes running with GTK3 as + * GDK3 will replace the handler. This is still used for the plugin process, + * which runs with GTK2. + **/ +void InstallX11ErrorHandler(); + +extern "C" int X11Error(Display *display, XErrorEvent *event); +#endif diff --git a/toolkit/xre/nsXREDirProvider.cpp b/toolkit/xre/nsXREDirProvider.cpp new file mode 100644 index 0000000000..2fbd324b70 --- /dev/null +++ b/toolkit/xre/nsXREDirProvider.cpp @@ -0,0 +1,1905 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "nsAppRunner.h" +#include "nsToolkitCompsCID.h" +#include "nsXREDirProvider.h" + +#include "jsapi.h" +#include "xpcpublic.h" + +#include "nsIAddonInterposition.h" +#include "nsIAppStartup.h" +#include "nsIDirectoryEnumerator.h" +#include "nsIFile.h" +#include "nsIObserver.h" +#include "nsIObserverService.h" +#include "nsISimpleEnumerator.h" +#include "nsIToolkitChromeRegistry.h" +#include "nsIToolkitProfileService.h" +#include "nsIXULRuntime.h" + +#include "nsAppDirectoryServiceDefs.h" +#include "nsDirectoryServiceDefs.h" +#include "nsDirectoryServiceUtils.h" +#include "nsXULAppAPI.h" +#include "nsCategoryManagerUtils.h" + +#include "nsINIParser.h" +#include "nsDependentString.h" +#include "nsCOMArray.h" +#include "nsArrayEnumerator.h" +#include "nsEnumeratorUtils.h" +#include "nsReadableUtils.h" + +#include "SpecialSystemDirectory.h" + +#include "mozilla/dom/ScriptSettings.h" + +#include "mozilla/Services.h" +#include "mozilla/Omnijar.h" +#include "mozilla/Preferences.h" +#include "mozilla/Telemetry.h" + +#include <stdlib.h> + +#ifdef XP_WIN +#include <windows.h> +#include <shlobj.h> +#include "mozilla/WindowsVersion.h" +#endif +#ifdef XP_MACOSX +#include "nsILocalFileMac.h" +// for chflags() +#include <sys/stat.h> +#include <unistd.h> +#endif +#ifdef XP_UNIX +#include <ctype.h> +#endif +#ifdef XP_IOS +#include "UIKitDirProvider.h" +#endif + +#if (defined(XP_WIN) || defined(XP_MACOSX)) && defined(MOZ_CONTENT_SANDBOX) +#include "nsIUUIDGenerator.h" +#include "mozilla/Unused.h" +#endif + +#if defined(XP_MACOSX) +#define APP_REGISTRY_NAME "Application Registry" +#elif defined(XP_WIN) +#define APP_REGISTRY_NAME "registry.dat" +#else +#define APP_REGISTRY_NAME "appreg" +#endif + +#define PREF_OVERRIDE_DIRNAME "preferences" + +#if (defined(XP_WIN) || defined(XP_MACOSX)) && defined(MOZ_CONTENT_SANDBOX) +static already_AddRefed<nsIFile> GetContentProcessSandboxTempDir(); +static nsresult DeleteDirIfExists(nsIFile *dir); +static bool IsContentSandboxDisabled(); +static const char* GetContentProcessTempBaseDirKey(); +static already_AddRefed<nsIFile> CreateContentProcessSandboxTempDir(); +#endif + +static already_AddRefed<nsIFile> +CloneAndAppend(nsIFile* aFile, const char* name) +{ + nsCOMPtr<nsIFile> file; + aFile->Clone(getter_AddRefs(file)); + file->AppendNative(nsDependentCString(name)); + return file.forget(); +} + +nsXREDirProvider* gDirServiceProvider = nullptr; + +nsXREDirProvider::nsXREDirProvider() : + mProfileNotified(false) +{ + gDirServiceProvider = this; +} + +nsXREDirProvider::~nsXREDirProvider() +{ + gDirServiceProvider = nullptr; +} + +nsXREDirProvider* +nsXREDirProvider::GetSingleton() +{ + return gDirServiceProvider; +} + +nsresult +nsXREDirProvider::Initialize(nsIFile *aXULAppDir, + nsIFile *aGREDir, + nsIDirectoryServiceProvider* aAppProvider) +{ + NS_ENSURE_ARG(aXULAppDir); + NS_ENSURE_ARG(aGREDir); + + mAppProvider = aAppProvider; + mXULAppDir = aXULAppDir; + mGREDir = aGREDir; + mGREDir->Clone(getter_AddRefs(mGREBinDir)); +#ifdef XP_MACOSX + mGREBinDir->SetNativeLeafName(NS_LITERAL_CSTRING("MacOS")); +#endif + + if (!mProfileDir) { + nsCOMPtr<nsIDirectoryServiceProvider> app(do_QueryInterface(mAppProvider)); + if (app) { + bool per = false; + app->GetFile(NS_APP_USER_PROFILE_50_DIR, &per, getter_AddRefs(mProfileDir)); + NS_ASSERTION(per, "NS_APP_USER_PROFILE_50_DIR must be persistent!"); + NS_ASSERTION(mProfileDir, "NS_APP_USER_PROFILE_50_DIR not defined! This shouldn't happen!"); + } + } + +#ifdef MOZ_B2G + LoadAppBundleDirs(); +#endif + + return NS_OK; +} + +nsresult +nsXREDirProvider::SetProfile(nsIFile* aDir, nsIFile* aLocalDir) +{ + NS_ASSERTION(aDir && aLocalDir, "We don't support no-profile apps yet!"); + + nsresult rv; + + rv = EnsureDirectoryExists(aDir); + if (NS_FAILED(rv)) + return rv; + + rv = EnsureDirectoryExists(aLocalDir); + if (NS_FAILED(rv)) + return rv; + +#ifdef XP_MACOSX + bool same; + if (NS_SUCCEEDED(aDir->Equals(aLocalDir, &same)) && !same) { + // Ensure that the cache directory is not indexed by Spotlight + // (bug 718910). At least on OS X, the cache directory (under + // ~/Library/Caches/) is always the "local" user profile + // directory. This is confusing, since *both* user profile + // directories are "local" (they both exist under the user's + // home directory). But this usage dates back at least as far + // as the patch for bug 291033, where "local" seems to mean + // "suitable for temporary storage". Don't hide the cache + // directory if by some chance it and the "non-local" profile + // directory are the same -- there are bad side effects from + // hiding a profile directory under /Library/Application Support/ + // (see bug 801883). + nsAutoCString cacheDir; + if (NS_SUCCEEDED(aLocalDir->GetNativePath(cacheDir))) { + if (chflags(cacheDir.get(), UF_HIDDEN)) { + NS_WARNING("Failed to set Cache directory to HIDDEN."); + } + } + } +#endif + + mProfileDir = aDir; + mProfileLocalDir = aLocalDir; + return NS_OK; +} + +NS_IMPL_QUERY_INTERFACE(nsXREDirProvider, + nsIDirectoryServiceProvider, + nsIDirectoryServiceProvider2, + nsIProfileStartup) + +NS_IMETHODIMP_(MozExternalRefCountType) +nsXREDirProvider::AddRef() +{ + return 1; +} + +NS_IMETHODIMP_(MozExternalRefCountType) +nsXREDirProvider::Release() +{ + return 0; +} + +nsresult +nsXREDirProvider::GetUserProfilesRootDir(nsIFile** aResult, + const nsACString* aProfileName, + const nsACString* aAppName, + const nsACString* aVendorName) +{ + nsCOMPtr<nsIFile> file; + nsresult rv = GetUserDataDirectory(getter_AddRefs(file), + false, + aProfileName, aAppName, aVendorName); + + if (NS_SUCCEEDED(rv)) { +#if !defined(XP_UNIX) || defined(XP_MACOSX) + rv = file->AppendNative(NS_LITERAL_CSTRING("Profiles")); +#endif + // We must create the profile directory here if it does not exist. + nsresult tmp = EnsureDirectoryExists(file); + if (NS_FAILED(tmp)) { + rv = tmp; + } + } + file.swap(*aResult); + return rv; +} + +nsresult +nsXREDirProvider::GetUserProfilesLocalDir(nsIFile** aResult, + const nsACString* aProfileName, + const nsACString* aAppName, + const nsACString* aVendorName) +{ + nsCOMPtr<nsIFile> file; + nsresult rv = GetUserDataDirectory(getter_AddRefs(file), + true, + aProfileName, aAppName, aVendorName); + + if (NS_SUCCEEDED(rv)) { +#if !defined(XP_UNIX) || defined(XP_MACOSX) + rv = file->AppendNative(NS_LITERAL_CSTRING("Profiles")); +#endif + // We must create the profile directory here if it does not exist. + nsresult tmp = EnsureDirectoryExists(file); + if (NS_FAILED(tmp)) { + rv = tmp; + } + } + file.swap(*aResult); + return NS_OK; +} + +#if defined(XP_UNIX) || defined(XP_MACOSX) +/** + * Get the directory that is the parent of the system-wide directories + * for extensions and native-messaing manifests. + * + * On OSX this is /Library/Application Support/Mozilla + * On Linux this is /usr/{lib,lib64}/mozilla + * (for 32- and 64-bit systems respsectively) + */ +static nsresult +GetSystemParentDirectory(nsIFile** aFile) +{ + nsresult rv; + nsCOMPtr<nsIFile> localDir; +#if defined(XP_MACOSX) + rv = GetOSXFolderType(kOnSystemDisk, kApplicationSupportFolderType, getter_AddRefs(localDir)); + if (NS_SUCCEEDED(rv)) { + rv = localDir->AppendNative(NS_LITERAL_CSTRING("Mozilla")); + } +#else + NS_NAMED_LITERAL_CSTRING(dirname, +#ifdef HAVE_USR_LIB64_DIR + "/usr/lib64/mozilla" +#elif defined(__OpenBSD__) || defined(__FreeBSD__) + "/usr/local/lib/mozilla" +#else + "/usr/lib/mozilla" +#endif + ); + rv = NS_NewNativeLocalFile(dirname, false, getter_AddRefs(localDir)); +#endif + + if (NS_SUCCEEDED(rv)) { + localDir.forget(aFile); + } + return rv; +} +#endif + +NS_IMETHODIMP +nsXREDirProvider::GetFile(const char* aProperty, bool* aPersistent, + nsIFile** aFile) +{ + nsresult rv; + + bool gettingProfile = false; + + if (!strcmp(aProperty, NS_APP_USER_PROFILE_LOCAL_50_DIR)) { + // If XRE_NotifyProfile hasn't been called, don't fall through to + // mAppProvider on the profile keys. + if (!mProfileNotified) + return NS_ERROR_FAILURE; + + if (mProfileLocalDir) + return mProfileLocalDir->Clone(aFile); + + if (mAppProvider) + return mAppProvider->GetFile(aProperty, aPersistent, aFile); + + // This falls through to the case below + gettingProfile = true; + } + if (!strcmp(aProperty, NS_APP_USER_PROFILE_50_DIR) || gettingProfile) { + if (!mProfileNotified) + return NS_ERROR_FAILURE; + + if (mProfileDir) + return mProfileDir->Clone(aFile); + + if (mAppProvider) + return mAppProvider->GetFile(aProperty, aPersistent, aFile); + + // If we don't succeed here, bail early so that we aren't reentrant + // through the "GetProfileDir" call below. + return NS_ERROR_FAILURE; + } + + if (mAppProvider) { + rv = mAppProvider->GetFile(aProperty, aPersistent, aFile); + if (NS_SUCCEEDED(rv) && *aFile) + return rv; + } + + *aPersistent = true; + + if (!strcmp(aProperty, NS_GRE_DIR)) { + return mGREDir->Clone(aFile); + } + else if (!strcmp(aProperty, NS_GRE_BIN_DIR)) { + return mGREBinDir->Clone(aFile); + } + else if (!strcmp(aProperty, NS_OS_CURRENT_PROCESS_DIR) || + !strcmp(aProperty, NS_APP_INSTALL_CLEANUP_DIR)) { + return GetAppDir()->Clone(aFile); + } + + rv = NS_ERROR_FAILURE; + nsCOMPtr<nsIFile> file; + + if (!strcmp(aProperty, NS_APP_PREF_DEFAULTS_50_DIR)) + { + // return the GRE default prefs directory here, and the app default prefs + // directory (if applicable) in NS_APP_PREFS_DEFAULTS_DIR_LIST. + rv = mGREDir->Clone(getter_AddRefs(file)); + if (NS_SUCCEEDED(rv)) { + rv = file->AppendNative(NS_LITERAL_CSTRING("defaults")); + if (NS_SUCCEEDED(rv)) + rv = file->AppendNative(NS_LITERAL_CSTRING("pref")); + } + } + else if (!strcmp(aProperty, NS_APP_APPLICATION_REGISTRY_DIR) || + !strcmp(aProperty, XRE_USER_APP_DATA_DIR)) { + rv = GetUserAppDataDirectory(getter_AddRefs(file)); + } +#if defined(XP_UNIX) || defined(XP_MACOSX) + else if (!strcmp(aProperty, XRE_SYS_NATIVE_MESSAGING_MANIFESTS)) { + nsCOMPtr<nsIFile> localDir; + + rv = ::GetSystemParentDirectory(getter_AddRefs(localDir)); + if (NS_SUCCEEDED(rv)) { + NS_NAMED_LITERAL_CSTRING(dirname, +#if defined(XP_MACOSX) + "NativeMessagingHosts" +#else + "native-messaging-hosts" +#endif + ); + rv = localDir->AppendNative(dirname); + if (NS_SUCCEEDED(rv)) { + localDir.swap(file); + } + } + } + else if (!strcmp(aProperty, XRE_USER_NATIVE_MESSAGING_MANIFESTS)) { + nsCOMPtr<nsIFile> localDir; + rv = GetUserDataDirectoryHome(getter_AddRefs(localDir), false); + if (NS_SUCCEEDED(rv)) { +#if defined(XP_MACOSX) + rv = localDir->AppendNative(NS_LITERAL_CSTRING("Mozilla")); + if (NS_SUCCEEDED(rv)) { + rv = localDir->AppendNative(NS_LITERAL_CSTRING("NativeMessagingHosts")); + } +#else + rv = localDir->AppendNative(NS_LITERAL_CSTRING(".mozilla")); + if (NS_SUCCEEDED(rv)) { + rv = localDir->AppendNative(NS_LITERAL_CSTRING("native-messaging-hosts")); + } +#endif + } + if (NS_SUCCEEDED(rv)) { + localDir.swap(file); + } + } +#endif + else if (!strcmp(aProperty, XRE_UPDATE_ROOT_DIR)) { + rv = GetUpdateRootDir(getter_AddRefs(file)); + } + else if (!strcmp(aProperty, NS_APP_APPLICATION_REGISTRY_FILE)) { + rv = GetUserAppDataDirectory(getter_AddRefs(file)); + if (NS_SUCCEEDED(rv)) + rv = file->AppendNative(NS_LITERAL_CSTRING(APP_REGISTRY_NAME)); + } + else if (!strcmp(aProperty, NS_APP_USER_PROFILES_ROOT_DIR)) { + rv = GetUserProfilesRootDir(getter_AddRefs(file), nullptr, nullptr, nullptr); + } + else if (!strcmp(aProperty, NS_APP_USER_PROFILES_LOCAL_ROOT_DIR)) { + rv = GetUserProfilesLocalDir(getter_AddRefs(file), nullptr, nullptr, nullptr); + } + else if (!strcmp(aProperty, XRE_EXECUTABLE_FILE) && gArgv[0]) { + nsCOMPtr<nsIFile> lf; + rv = XRE_GetBinaryPath(gArgv[0], getter_AddRefs(lf)); + if (NS_SUCCEEDED(rv)) + file = lf; + } + + else if (!strcmp(aProperty, NS_APP_PROFILE_DIR_STARTUP) && mProfileDir) { + return mProfileDir->Clone(aFile); + } + else if (!strcmp(aProperty, NS_APP_PROFILE_LOCAL_DIR_STARTUP)) { + if (mProfileLocalDir) + return mProfileLocalDir->Clone(aFile); + + if (mProfileDir) + return mProfileDir->Clone(aFile); + + if (mAppProvider) + return mAppProvider->GetFile(NS_APP_PROFILE_DIR_STARTUP, aPersistent, + aFile); + } +#if defined(XP_UNIX) || defined(XP_MACOSX) + else if (!strcmp(aProperty, XRE_SYS_LOCAL_EXTENSION_PARENT_DIR)) { +#ifdef ENABLE_SYSTEM_EXTENSION_DIRS + return GetSystemExtensionsDirectory(aFile); +#else + return NS_ERROR_FAILURE; +#endif + } +#endif +#if defined(XP_UNIX) && !defined(XP_MACOSX) + else if (!strcmp(aProperty, XRE_SYS_SHARE_EXTENSION_PARENT_DIR)) { +#ifdef ENABLE_SYSTEM_EXTENSION_DIRS +#if defined(__OpenBSD__) || defined(__FreeBSD__) + static const char *const sysLExtDir = "/usr/local/share/mozilla/extensions"; +#else + static const char *const sysLExtDir = "/usr/share/mozilla/extensions"; +#endif + return NS_NewNativeLocalFile(nsDependentCString(sysLExtDir), + false, aFile); +#else + return NS_ERROR_FAILURE; +#endif + } +#endif + else if (!strcmp(aProperty, XRE_USER_SYS_EXTENSION_DIR)) { +#ifdef ENABLE_SYSTEM_EXTENSION_DIRS + return GetSysUserExtensionsDirectory(aFile); +#else + return NS_ERROR_FAILURE; +#endif + } + else if (!strcmp(aProperty, XRE_APP_DISTRIBUTION_DIR)) { + bool persistent = false; + rv = GetFile(NS_GRE_DIR, &persistent, getter_AddRefs(file)); + if (NS_SUCCEEDED(rv)) + rv = file->AppendNative(NS_LITERAL_CSTRING("distribution")); + } + else if (!strcmp(aProperty, XRE_APP_FEATURES_DIR)) { + rv = GetAppDir()->Clone(getter_AddRefs(file)); + if (NS_SUCCEEDED(rv)) + rv = file->AppendNative(NS_LITERAL_CSTRING("features")); + } + else if (!strcmp(aProperty, XRE_ADDON_APP_DIR)) { + nsCOMPtr<nsIDirectoryServiceProvider> dirsvc(do_GetService("@mozilla.org/file/directory_service;1", &rv)); + if (NS_FAILED(rv)) + return rv; + bool unused; + rv = dirsvc->GetFile("XCurProcD", &unused, getter_AddRefs(file)); + } +#if (defined(XP_WIN) || defined(XP_MACOSX)) && defined(MOZ_CONTENT_SANDBOX) + else if (!strcmp(aProperty, NS_APP_CONTENT_PROCESS_TEMP_DIR)) { + if (!mContentTempDir && NS_FAILED((rv = LoadContentProcessTempDir()))) { + return rv; + } + rv = mContentTempDir->Clone(getter_AddRefs(file)); + } +#endif // defined(XP_WIN) && defined(MOZ_CONTENT_SANDBOX) + else if (NS_SUCCEEDED(GetProfileStartupDir(getter_AddRefs(file)))) { + // We need to allow component, xpt, and chrome registration to + // occur prior to the profile-after-change notification. + if (!strcmp(aProperty, NS_APP_USER_CHROME_DIR)) { + rv = file->AppendNative(NS_LITERAL_CSTRING("chrome")); + } + } + + if (NS_SUCCEEDED(rv) && file) { + file.forget(aFile); + return NS_OK; + } + + bool ensureFilePermissions = false; + + if (NS_SUCCEEDED(GetProfileDir(getter_AddRefs(file)))) { + if (!strcmp(aProperty, NS_APP_PREFS_50_DIR)) { + rv = NS_OK; + } + else if (!strcmp(aProperty, NS_APP_PREFS_50_FILE)) { + rv = file->AppendNative(NS_LITERAL_CSTRING("prefs.js")); + } + else if (!strcmp(aProperty, NS_LOCALSTORE_UNSAFE_FILE)) { + rv = file->AppendNative(NS_LITERAL_CSTRING("localstore.rdf")); + } + else if (!strcmp(aProperty, NS_APP_LOCALSTORE_50_FILE)) { + if (gSafeMode) { + rv = file->AppendNative(NS_LITERAL_CSTRING("localstore-safe.rdf")); + file->Remove(false); + } + else { + rv = file->AppendNative(NS_LITERAL_CSTRING("localstore.rdf")); + ensureFilePermissions = true; + } + } + else if (!strcmp(aProperty, NS_APP_USER_MIMETYPES_50_FILE)) { + rv = file->AppendNative(NS_LITERAL_CSTRING("mimeTypes.rdf")); + ensureFilePermissions = true; + } + else if (!strcmp(aProperty, NS_APP_DOWNLOADS_50_FILE)) { + rv = file->AppendNative(NS_LITERAL_CSTRING("downloads.rdf")); + } + else if (!strcmp(aProperty, NS_APP_PREFS_OVERRIDE_DIR)) { + rv = mProfileDir->Clone(getter_AddRefs(file)); + nsresult tmp = file->AppendNative(NS_LITERAL_CSTRING(PREF_OVERRIDE_DIRNAME)); + if (NS_FAILED(tmp)) { + rv = tmp; + } + tmp = EnsureDirectoryExists(file); + if (NS_FAILED(tmp)) { + rv = tmp; + } + } + } + if (NS_FAILED(rv) || !file) + return NS_ERROR_FAILURE; + + if (ensureFilePermissions) { + bool fileToEnsureExists; + bool isWritable; + if (NS_SUCCEEDED(file->Exists(&fileToEnsureExists)) && fileToEnsureExists + && NS_SUCCEEDED(file->IsWritable(&isWritable)) && !isWritable) { + uint32_t permissions; + if (NS_SUCCEEDED(file->GetPermissions(&permissions))) { + rv = file->SetPermissions(permissions | 0600); + NS_ASSERTION(NS_SUCCEEDED(rv), "failed to ensure file permissions"); + } + } + } + + file.forget(aFile); + return NS_OK; +} + +static void +LoadDirIntoArray(nsIFile* dir, + const char *const *aAppendList, + nsCOMArray<nsIFile>& aDirectories) +{ + if (!dir) + return; + + nsCOMPtr<nsIFile> subdir; + dir->Clone(getter_AddRefs(subdir)); + if (!subdir) + return; + + for (const char *const *a = aAppendList; *a; ++a) { + subdir->AppendNative(nsDependentCString(*a)); + } + + bool exists; + if (NS_SUCCEEDED(subdir->Exists(&exists)) && exists) { + aDirectories.AppendObject(subdir); + } +} + +static void +LoadDirsIntoArray(nsCOMArray<nsIFile>& aSourceDirs, + const char *const* aAppendList, + nsCOMArray<nsIFile>& aDirectories) +{ + nsCOMPtr<nsIFile> appended; + bool exists; + + for (int32_t i = 0; i < aSourceDirs.Count(); ++i) { + aSourceDirs[i]->Clone(getter_AddRefs(appended)); + if (!appended) + continue; + + nsAutoCString leaf; + appended->GetNativeLeafName(leaf); + if (!Substring(leaf, leaf.Length() - 4).EqualsLiteral(".xpi")) { + LoadDirIntoArray(appended, + aAppendList, + aDirectories); + } + else if (NS_SUCCEEDED(appended->Exists(&exists)) && exists) + aDirectories.AppendObject(appended); + } +} + +NS_IMETHODIMP +nsXREDirProvider::GetFiles(const char* aProperty, nsISimpleEnumerator** aResult) +{ + nsresult rv; + + nsCOMPtr<nsISimpleEnumerator> appEnum; + nsCOMPtr<nsIDirectoryServiceProvider2> + appP2(do_QueryInterface(mAppProvider)); + if (appP2) { + rv = appP2->GetFiles(aProperty, getter_AddRefs(appEnum)); + if (NS_FAILED(rv)) { + appEnum = nullptr; + } + else if (rv != NS_SUCCESS_AGGREGATE_RESULT) { + appEnum.forget(aResult); + return NS_OK; + } + } + + nsCOMPtr<nsISimpleEnumerator> xreEnum; + rv = GetFilesInternal(aProperty, getter_AddRefs(xreEnum)); + if (NS_FAILED(rv)) { + if (appEnum) { + appEnum.forget(aResult); + return NS_SUCCESS_AGGREGATE_RESULT; + } + + return rv; + } + + rv = NS_NewUnionEnumerator(aResult, appEnum, xreEnum); + if (NS_FAILED(rv)) + return rv; + + return NS_SUCCESS_AGGREGATE_RESULT; +} + +static void +RegisterExtensionInterpositions(nsINIParser &parser) +{ + if (!mozilla::Preferences::GetBool("extensions.interposition.enabled", false)) + return; + + nsCOMPtr<nsIAddonInterposition> interposition = + do_GetService("@mozilla.org/addons/multiprocess-shims;1"); + + nsresult rv; + int32_t i = 0; + do { + nsAutoCString buf("Extension"); + buf.AppendInt(i++); + + nsAutoCString addonId; + rv = parser.GetString("MultiprocessIncompatibleExtensions", buf.get(), addonId); + if (NS_FAILED(rv)) + return; + + if (!xpc::SetAddonInterposition(addonId, interposition)) + continue; + + if (!xpc::AllowCPOWsInAddon(addonId, true)) + continue; + } + while (true); +} + +static void +LoadExtensionDirectories(nsINIParser &parser, + const char *aSection, + nsCOMArray<nsIFile> &aDirectories, + NSLocationType aType) +{ + nsresult rv; + int32_t i = 0; + do { + nsAutoCString buf("Extension"); + buf.AppendInt(i++); + + nsAutoCString path; + rv = parser.GetString(aSection, buf.get(), path); + if (NS_FAILED(rv)) + return; + + nsCOMPtr<nsIFile> dir = do_CreateInstance("@mozilla.org/file/local;1", &rv); + if (NS_FAILED(rv)) + continue; + + rv = dir->SetPersistentDescriptor(path); + if (NS_FAILED(rv)) + continue; + + aDirectories.AppendObject(dir); + if (Substring(path, path.Length() - 4).EqualsLiteral(".xpi")) { + XRE_AddJarManifestLocation(aType, dir); + } + else { + nsCOMPtr<nsIFile> manifest = + CloneAndAppend(dir, "chrome.manifest"); + XRE_AddManifestLocation(aType, manifest); + } + } + while (true); +} + +#if (defined(XP_WIN) || defined(XP_MACOSX)) && defined(MOZ_CONTENT_SANDBOX) + +static const char* +GetContentProcessTempBaseDirKey() +{ +#if defined(XP_WIN) + return NS_WIN_LOW_INTEGRITY_TEMP_BASE; +#else + return NS_OS_TEMP_DIR; +#endif +} + +// +// Sets mContentTempDir so that it refers to the appropriate temp dir. +// If the sandbox is enabled, NS_APP_CONTENT_PROCESS_TEMP_DIR, otherwise +// NS_OS_TEMP_DIR is used. +// +nsresult +nsXREDirProvider::LoadContentProcessTempDir() +{ + mContentTempDir = GetContentProcessSandboxTempDir(); + if (mContentTempDir) { + return NS_OK; + } else { + return NS_GetSpecialDirectory(NS_OS_TEMP_DIR, + getter_AddRefs(mContentTempDir)); + } +} + +static bool +IsContentSandboxDisabled() +{ + if (!BrowserTabsRemoteAutostart()) { + return false; + } +#if defined(XP_WIN) + const bool isSandboxDisabled = !mozilla::IsVistaOrLater() || + (Preferences::GetInt("security.sandbox.content.level") < 1); +#elif defined(XP_MACOSX) + const bool isSandboxDisabled = + Preferences::GetInt("security.sandbox.content.level") < 1; +#endif + return isSandboxDisabled; +} + +// +// If a content process sandbox temp dir is to be used, returns an nsIFile +// for the directory. Returns null if the content sandbox is disabled or +// an error occurs. +// +static already_AddRefed<nsIFile> +GetContentProcessSandboxTempDir() +{ + if (IsContentSandboxDisabled()) { + return nullptr; + } + + nsCOMPtr<nsIFile> localFile; + + nsresult rv = NS_GetSpecialDirectory(GetContentProcessTempBaseDirKey(), + getter_AddRefs(localFile)); + if (NS_WARN_IF(NS_FAILED(rv))) { + return nullptr; + } + + nsAutoString tempDirSuffix; + rv = Preferences::GetString("security.sandbox.content.tempDirSuffix", + &tempDirSuffix); + if (NS_WARN_IF(NS_FAILED(rv)) || tempDirSuffix.IsEmpty()) { + return nullptr; + } + + rv = localFile->Append(NS_LITERAL_STRING("Temp-") + tempDirSuffix); + if (NS_WARN_IF(NS_FAILED(rv))) { + return nullptr; + } + + return localFile.forget(); +} + +// +// Create a temporary directory for use from sandboxed content processes. +// Only called in the parent. The path is derived from a UUID stored in a +// pref which is available to content processes. Returns null if the +// content sandbox is disabled or if an error occurs. +// +static already_AddRefed<nsIFile> +CreateContentProcessSandboxTempDir() +{ + if (IsContentSandboxDisabled()) { + return nullptr; + } + + // Get (and create if blank) temp directory suffix pref. + nsresult rv; + nsAdoptingString tempDirSuffix = + Preferences::GetString("security.sandbox.content.tempDirSuffix"); + if (tempDirSuffix.IsEmpty()) { + nsCOMPtr<nsIUUIDGenerator> uuidgen = + do_GetService("@mozilla.org/uuid-generator;1", &rv); + if (NS_WARN_IF(NS_FAILED(rv))) { + return nullptr; + } + + nsID uuid; + rv = uuidgen->GenerateUUIDInPlace(&uuid); + if (NS_WARN_IF(NS_FAILED(rv))) { + return nullptr; + } + + char uuidChars[NSID_LENGTH]; + uuid.ToProvidedString(uuidChars); + tempDirSuffix.AssignASCII(uuidChars); + + // Save the pref + rv = Preferences::SetCString("security.sandbox.content.tempDirSuffix", + uuidChars); + if (NS_WARN_IF(NS_FAILED(rv))) { + // If we fail to save the pref we don't want to create the temp dir, + // because we won't be able to clean it up later. + return nullptr; + } + + nsCOMPtr<nsIPrefService> prefsvc = Preferences::GetService(); + if (!prefsvc || NS_FAILED((rv = prefsvc->SavePrefFile(nullptr)))) { + // Again, if we fail to save the pref file we might not be able to clean + // up the temp directory, so don't create one. + NS_WARNING("Failed to save pref file, cannot create temp dir."); + return nullptr; + } + } + + nsCOMPtr<nsIFile> sandboxTempDir = GetContentProcessSandboxTempDir(); + if (!sandboxTempDir) { + NS_WARNING("Failed to determine sandbox temp dir path."); + return nullptr; + } + + // Remove the directory. It may exist due to a previous crash. + if (NS_FAILED(DeleteDirIfExists(sandboxTempDir))) { + NS_WARNING("Failed to reset sandbox temp dir."); + return nullptr; + } + + // Create the directory + rv = sandboxTempDir->Create(nsIFile::DIRECTORY_TYPE, 0700); + if (NS_FAILED(rv)) { + NS_WARNING("Failed to create sandbox temp dir."); + return nullptr; + } + + return sandboxTempDir.forget(); +} + +static nsresult +DeleteDirIfExists(nsIFile* dir) +{ + if (dir) { + // Don't return an error if the directory doesn't exist. + // Windows Remove() returns NS_ERROR_FILE_NOT_FOUND while + // OS X returns NS_ERROR_FILE_TARGET_DOES_NOT_EXIST. + nsresult rv = dir->Remove(/* aRecursive */ true); + if (NS_FAILED(rv) && rv != NS_ERROR_FILE_NOT_FOUND && + rv != NS_ERROR_FILE_TARGET_DOES_NOT_EXIST) { + return rv; + } + } + return NS_OK; +} + +#endif // (defined(XP_WIN) || defined(XP_MACOSX)) && + // defined(MOZ_CONTENT_SANDBOX) + +void +nsXREDirProvider::LoadExtensionBundleDirectories() +{ + if (!mozilla::Preferences::GetBool("extensions.defaultProviders.enabled", true)) + return; + + if (mProfileDir) { + if (!gSafeMode) { + nsCOMPtr<nsIFile> extensionsINI; + mProfileDir->Clone(getter_AddRefs(extensionsINI)); + if (!extensionsINI) + return; + + extensionsINI->AppendNative(NS_LITERAL_CSTRING("extensions.ini")); + + nsCOMPtr<nsIFile> extensionsINILF = + do_QueryInterface(extensionsINI); + if (!extensionsINILF) + return; + + nsINIParser parser; + nsresult rv = parser.Init(extensionsINILF); + if (NS_FAILED(rv)) + return; + + RegisterExtensionInterpositions(parser); + LoadExtensionDirectories(parser, "ExtensionDirs", mExtensionDirectories, + NS_EXTENSION_LOCATION); + LoadExtensionDirectories(parser, "ThemeDirs", mThemeDirectories, + NS_SKIN_LOCATION); +/* non-Firefox applications that use overrides in their default theme should + * define AC_DEFINE(MOZ_SEPARATE_MANIFEST_FOR_THEME_OVERRIDES) in their + * configure.in */ +#if defined(MOZ_BUILD_APP_IS_BROWSER) || defined(MOZ_SEPARATE_MANIFEST_FOR_THEME_OVERRIDES) + } else { + // In safe mode, still load the default theme directory: + nsCOMPtr<nsIFile> themeManifest; + mXULAppDir->Clone(getter_AddRefs(themeManifest)); + themeManifest->AppendNative(NS_LITERAL_CSTRING("extensions")); + themeManifest->AppendNative(NS_LITERAL_CSTRING("{972ce4c6-7e08-4474-a285-3208198ce6fd}.xpi")); + bool exists = false; + if (NS_SUCCEEDED(themeManifest->Exists(&exists)) && exists) { + XRE_AddJarManifestLocation(NS_SKIN_LOCATION, themeManifest); + } else { + themeManifest->SetNativeLeafName(NS_LITERAL_CSTRING("{972ce4c6-7e08-4474-a285-3208198ce6fd}")); + themeManifest->AppendNative(NS_LITERAL_CSTRING("chrome.manifest")); + XRE_AddManifestLocation(NS_SKIN_LOCATION, themeManifest); + } +#endif + } + } +} + +#ifdef MOZ_B2G +void +nsXREDirProvider::LoadAppBundleDirs() +{ + nsCOMPtr<nsIFile> dir; + bool persistent = false; + nsresult rv = GetFile(XRE_APP_DISTRIBUTION_DIR, &persistent, getter_AddRefs(dir)); + if (NS_FAILED(rv)) + return; + + dir->AppendNative(NS_LITERAL_CSTRING("bundles")); + + nsCOMPtr<nsISimpleEnumerator> e; + rv = dir->GetDirectoryEntries(getter_AddRefs(e)); + if (NS_FAILED(rv)) + return; + + nsCOMPtr<nsIDirectoryEnumerator> files = do_QueryInterface(e); + if (!files) + return; + + nsCOMPtr<nsIFile> subdir; + while (NS_SUCCEEDED(files->GetNextFile(getter_AddRefs(subdir))) && subdir) { + mAppBundleDirectories.AppendObject(subdir); + + nsCOMPtr<nsIFile> manifest = + CloneAndAppend(subdir, "chrome.manifest"); + XRE_AddManifestLocation(NS_APP_LOCATION, manifest); + } +} +#endif + +static const char *const kAppendPrefDir[] = { "defaults", "preferences", nullptr }; + +#ifdef DEBUG_bsmedberg +static void +DumpFileArray(const char *key, + nsCOMArray<nsIFile> dirs) +{ + fprintf(stderr, "nsXREDirProvider::GetFilesInternal(%s)\n", key); + + nsAutoCString path; + for (int32_t i = 0; i < dirs.Count(); ++i) { + dirs[i]->GetNativePath(path); + fprintf(stderr, " %s\n", path.get()); + } +} +#endif // DEBUG_bsmedberg + +nsresult +nsXREDirProvider::GetFilesInternal(const char* aProperty, + nsISimpleEnumerator** aResult) +{ + nsresult rv = NS_OK; + *aResult = nullptr; + + if (!strcmp(aProperty, XRE_EXTENSIONS_DIR_LIST)) { + nsCOMArray<nsIFile> directories; + + static const char *const kAppendNothing[] = { nullptr }; + + LoadDirsIntoArray(mAppBundleDirectories, + kAppendNothing, directories); + LoadDirsIntoArray(mExtensionDirectories, + kAppendNothing, directories); + + rv = NS_NewArrayEnumerator(aResult, directories); + } + else if (!strcmp(aProperty, NS_APP_PREFS_DEFAULTS_DIR_LIST)) { + nsCOMArray<nsIFile> directories; + + LoadDirIntoArray(mXULAppDir, kAppendPrefDir, directories); + LoadDirsIntoArray(mAppBundleDirectories, + kAppendPrefDir, directories); + + rv = NS_NewArrayEnumerator(aResult, directories); + } + else if (!strcmp(aProperty, NS_EXT_PREFS_DEFAULTS_DIR_LIST)) { + nsCOMArray<nsIFile> directories; + + LoadDirsIntoArray(mExtensionDirectories, + kAppendPrefDir, directories); + + if (mProfileDir) { + nsCOMPtr<nsIFile> overrideFile; + mProfileDir->Clone(getter_AddRefs(overrideFile)); + overrideFile->AppendNative(NS_LITERAL_CSTRING(PREF_OVERRIDE_DIRNAME)); + + bool exists; + if (NS_SUCCEEDED(overrideFile->Exists(&exists)) && exists) + directories.AppendObject(overrideFile); + } + + rv = NS_NewArrayEnumerator(aResult, directories); + } + else if (!strcmp(aProperty, NS_APP_CHROME_DIR_LIST)) { + // NS_APP_CHROME_DIR_LIST is only used to get default (native) icons + // for OS window decoration. + + static const char *const kAppendChromeDir[] = { "chrome", nullptr }; + nsCOMArray<nsIFile> directories; + LoadDirIntoArray(mXULAppDir, + kAppendChromeDir, + directories); + LoadDirsIntoArray(mAppBundleDirectories, + kAppendChromeDir, + directories); + LoadDirsIntoArray(mExtensionDirectories, + kAppendChromeDir, + directories); + + rv = NS_NewArrayEnumerator(aResult, directories); + } + else if (!strcmp(aProperty, NS_APP_PLUGINS_DIR_LIST)) { + nsCOMArray<nsIFile> directories; + + if (mozilla::Preferences::GetBool("plugins.load_appdir_plugins", false)) { + nsCOMPtr<nsIFile> appdir; + rv = XRE_GetBinaryPath(gArgv[0], getter_AddRefs(appdir)); + if (NS_SUCCEEDED(rv)) { + appdir->SetNativeLeafName(NS_LITERAL_CSTRING("plugins")); + directories.AppendObject(appdir); + } + } + + static const char *const kAppendPlugins[] = { "plugins", nullptr }; + + // The root dirserviceprovider does quite a bit for us: we're mainly + // interested in xulapp and extension-provided plugins. + LoadDirsIntoArray(mAppBundleDirectories, + kAppendPlugins, + directories); + LoadDirsIntoArray(mExtensionDirectories, + kAppendPlugins, + directories); + + if (mProfileDir) { + nsCOMArray<nsIFile> profileDir; + profileDir.AppendObject(mProfileDir); + LoadDirsIntoArray(profileDir, + kAppendPlugins, + directories); + } + + rv = NS_NewArrayEnumerator(aResult, directories); + NS_ENSURE_SUCCESS(rv, rv); + + rv = NS_SUCCESS_AGGREGATE_RESULT; + } + else + rv = NS_ERROR_FAILURE; + + return rv; +} + +NS_IMETHODIMP +nsXREDirProvider::GetDirectory(nsIFile* *aResult) +{ + NS_ENSURE_TRUE(mProfileDir, NS_ERROR_NOT_INITIALIZED); + + return mProfileDir->Clone(aResult); +} + +NS_IMETHODIMP +nsXREDirProvider::DoStartup() +{ + if (!mProfileNotified) { + nsCOMPtr<nsIObserverService> obsSvc = + mozilla::services::GetObserverService(); + if (!obsSvc) return NS_ERROR_FAILURE; + + mProfileNotified = true; + + /* + Setup prefs before profile-do-change to be able to use them to track + crashes and because we want to begin crash tracking before other code run + from this notification since they may cause crashes. + */ + nsresult rv = mozilla::Preferences::ResetAndReadUserPrefs(); + if (NS_FAILED(rv)) NS_WARNING("Failed to setup pref service."); + + bool safeModeNecessary = false; + nsCOMPtr<nsIAppStartup> appStartup (do_GetService(NS_APPSTARTUP_CONTRACTID)); + if (appStartup) { + rv = appStartup->TrackStartupCrashBegin(&safeModeNecessary); + if (NS_FAILED(rv) && rv != NS_ERROR_NOT_AVAILABLE) + NS_WARNING("Error while beginning startup crash tracking"); + + if (!gSafeMode && safeModeNecessary) { + appStartup->RestartInSafeMode(nsIAppStartup::eForceQuit); + return NS_OK; + } + } + + static const char16_t kStartup[] = {'s','t','a','r','t','u','p','\0'}; + obsSvc->NotifyObservers(nullptr, "profile-do-change", kStartup); + + // Init the Extension Manager + nsCOMPtr<nsIObserver> em = do_GetService("@mozilla.org/addons/integration;1"); + if (em) { + em->Observe(nullptr, "addons-startup", nullptr); + } else { + NS_WARNING("Failed to create Addons Manager."); + } + + LoadExtensionBundleDirectories(); + + obsSvc->NotifyObservers(nullptr, "load-extension-defaults", nullptr); + obsSvc->NotifyObservers(nullptr, "profile-after-change", kStartup); + + // Any component that has registered for the profile-after-change category + // should also be created at this time. + (void)NS_CreateServicesFromCategory("profile-after-change", nullptr, + "profile-after-change"); + + if (gSafeMode && safeModeNecessary) { + static const char16_t kCrashed[] = {'c','r','a','s','h','e','d','\0'}; + obsSvc->NotifyObservers(nullptr, "safemode-forced", kCrashed); + } + + // 1 = Regular mode, 2 = Safe mode, 3 = Safe mode forced + int mode = 1; + if (gSafeMode) { + if (safeModeNecessary) + mode = 3; + else + mode = 2; + } + mozilla::Telemetry::Accumulate(mozilla::Telemetry::SAFE_MODE_USAGE, mode); + + // Telemetry about number of profiles. + nsCOMPtr<nsIToolkitProfileService> profileService = + do_GetService("@mozilla.org/toolkit/profile-service;1"); + if (profileService) { + nsCOMPtr<nsISimpleEnumerator> profiles; + rv = profileService->GetProfiles(getter_AddRefs(profiles)); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + uint32_t count = 0; + nsCOMPtr<nsISupports> profile; + while (NS_SUCCEEDED(profiles->GetNext(getter_AddRefs(profile)))) { + ++count; + } + + mozilla::Telemetry::Accumulate(mozilla::Telemetry::NUMBER_OF_PROFILES, + count); + } + + obsSvc->NotifyObservers(nullptr, "profile-initial-state", nullptr); + +#if (defined(XP_WIN) || defined(XP_MACOSX)) && defined(MOZ_CONTENT_SANDBOX) + // The parent is responsible for creating the sandbox temp dir + if (XRE_IsParentProcess()) { + mContentProcessSandboxTempDir = CreateContentProcessSandboxTempDir(); + mContentTempDir = mContentProcessSandboxTempDir; + } +#endif + } + return NS_OK; +} + +void +nsXREDirProvider::DoShutdown() +{ + PROFILER_LABEL_FUNC(js::ProfileEntry::Category::OTHER); + + if (mProfileNotified) { +#if (defined(XP_WIN) || defined(XP_MACOSX)) && defined(MOZ_CONTENT_SANDBOX) + if (XRE_IsParentProcess()) { + Unused << DeleteDirIfExists(mContentProcessSandboxTempDir); + } +#endif + + nsCOMPtr<nsIObserverService> obsSvc = + mozilla::services::GetObserverService(); + NS_ASSERTION(obsSvc, "No observer service?"); + if (obsSvc) { + static const char16_t kShutdownPersist[] = u"shutdown-persist"; + obsSvc->NotifyObservers(nullptr, "profile-change-net-teardown", kShutdownPersist); + obsSvc->NotifyObservers(nullptr, "profile-change-teardown", kShutdownPersist); + + // Phase 2c: Now that things are torn down, force JS GC so that things which depend on + // resources which are about to go away in "profile-before-change" are destroyed first. + + if (JSContext* cx = dom::danger::GetJSContext()) { + JS_GC(cx); + } + + // Phase 3: Notify observers of a profile change + obsSvc->NotifyObservers(nullptr, "profile-before-change", kShutdownPersist); + obsSvc->NotifyObservers(nullptr, "profile-before-change-qm", kShutdownPersist); + obsSvc->NotifyObservers(nullptr, "profile-before-change-telemetry", kShutdownPersist); + } + mProfileNotified = false; + } +} + +#ifdef XP_WIN +static nsresult +GetShellFolderPath(int folder, nsAString& _retval) +{ + wchar_t* buf; + uint32_t bufLength = _retval.GetMutableData(&buf, MAXPATHLEN + 3); + NS_ENSURE_TRUE(bufLength >= (MAXPATHLEN + 3), NS_ERROR_OUT_OF_MEMORY); + + nsresult rv = NS_OK; + + LPITEMIDLIST pItemIDList = nullptr; + + if (SUCCEEDED(SHGetSpecialFolderLocation(nullptr, folder, &pItemIDList)) && + SHGetPathFromIDListW(pItemIDList, buf)) { + // We're going to use wcslen (wcsnlen not available in msvc7.1) so make + // sure to null terminate. + buf[bufLength - 1] = L'\0'; + _retval.SetLength(wcslen(buf)); + } else { + _retval.SetLength(0); + rv = NS_ERROR_NOT_AVAILABLE; + } + + CoTaskMemFree(pItemIDList); + + return rv; +} + +/** + * Provides a fallback for getting the path to APPDATA or LOCALAPPDATA by + * querying the registry when the call to SHGetSpecialFolderLocation or + * SHGetPathFromIDListW is unable to provide these paths (Bug 513958). + */ +static nsresult +GetRegWindowsAppDataFolder(bool aLocal, nsAString& _retval) +{ + HKEY key; + NS_NAMED_LITERAL_STRING(keyName, + "Software\\Microsoft\\Windows\\CurrentVersion\\Explorer\\Shell Folders"); + DWORD res = ::RegOpenKeyExW(HKEY_CURRENT_USER, keyName.get(), 0, KEY_READ, + &key); + if (res != ERROR_SUCCESS) { + _retval.SetLength(0); + return NS_ERROR_NOT_AVAILABLE; + } + + DWORD type, size; + res = RegQueryValueExW(key, (aLocal ? L"Local AppData" : L"AppData"), + nullptr, &type, nullptr, &size); + // The call to RegQueryValueExW must succeed, the type must be REG_SZ, the + // buffer size must not equal 0, and the buffer size be a multiple of 2. + if (res != ERROR_SUCCESS || type != REG_SZ || size == 0 || size % 2 != 0) { + ::RegCloseKey(key); + _retval.SetLength(0); + return NS_ERROR_NOT_AVAILABLE; + } + + // |size| may or may not include room for the terminating null character + DWORD resultLen = size / 2; + + if (!_retval.SetLength(resultLen, mozilla::fallible)) { + ::RegCloseKey(key); + _retval.SetLength(0); + return NS_ERROR_NOT_AVAILABLE; + } + + nsAString::iterator begin; + _retval.BeginWriting(begin); + + res = RegQueryValueExW(key, (aLocal ? L"Local AppData" : L"AppData"), + nullptr, nullptr, (LPBYTE) begin.get(), &size); + ::RegCloseKey(key); + if (res != ERROR_SUCCESS) { + _retval.SetLength(0); + return NS_ERROR_NOT_AVAILABLE; + } + + if (!_retval.CharAt(resultLen - 1)) { + // It was already null terminated. + _retval.Truncate(resultLen - 1); + } + + return NS_OK; +} + +static bool +GetCachedHash(HKEY rootKey, const nsAString ®Path, const nsAString &path, + nsAString &cachedHash) +{ + HKEY baseKey; + if (RegOpenKeyExW(rootKey, reinterpret_cast<const wchar_t*>(regPath.BeginReading()), 0, KEY_READ, &baseKey) != + ERROR_SUCCESS) { + return false; + } + + wchar_t cachedHashRaw[512]; + DWORD bufferSize = sizeof(cachedHashRaw); + LONG result = RegQueryValueExW(baseKey, reinterpret_cast<const wchar_t*>(path.BeginReading()), 0, nullptr, + (LPBYTE)cachedHashRaw, &bufferSize); + RegCloseKey(baseKey); + if (result == ERROR_SUCCESS) { + cachedHash.Assign(cachedHashRaw); + } + return ERROR_SUCCESS == result; +} + +#endif + +nsresult +nsXREDirProvider::GetUpdateRootDir(nsIFile* *aResult) +{ + nsCOMPtr<nsIFile> updRoot; +#if defined(MOZ_WIDGET_GONK) + + nsresult rv = NS_NewNativeLocalFile(nsDependentCString("/data/local"), + true, + getter_AddRefs(updRoot)); + NS_ENSURE_SUCCESS(rv, rv); + +#else + nsCOMPtr<nsIFile> appFile; + bool per = false; + nsresult rv = GetFile(XRE_EXECUTABLE_FILE, &per, getter_AddRefs(appFile)); + NS_ENSURE_SUCCESS(rv, rv); + rv = appFile->GetParent(getter_AddRefs(updRoot)); + NS_ENSURE_SUCCESS(rv, rv); + +#ifdef XP_MACOSX + nsCOMPtr<nsIFile> appRootDirFile; + nsCOMPtr<nsIFile> localDir; + nsAutoString appDirPath; + if (NS_FAILED(appFile->GetParent(getter_AddRefs(appRootDirFile))) || + NS_FAILED(appRootDirFile->GetPath(appDirPath)) || + NS_FAILED(GetUserDataDirectoryHome(getter_AddRefs(localDir), true))) { + return NS_ERROR_FAILURE; + } + + int32_t dotIndex = appDirPath.RFind(".app"); + if (dotIndex == kNotFound) { + dotIndex = appDirPath.Length(); + } + appDirPath = Substring(appDirPath, 1, dotIndex - 1); + + bool hasVendor = gAppData->vendor && strlen(gAppData->vendor) != 0; + if (hasVendor || gAppData->name) { + if (NS_FAILED(localDir->AppendNative(nsDependentCString(hasVendor ? + gAppData->vendor : + gAppData->name)))) { + return NS_ERROR_FAILURE; + } + } else if (NS_FAILED(localDir->AppendNative(NS_LITERAL_CSTRING("Mozilla")))) { + return NS_ERROR_FAILURE; + } + + if (NS_FAILED(localDir->Append(NS_LITERAL_STRING("updates"))) || + NS_FAILED(localDir->AppendRelativePath(appDirPath))) { + return NS_ERROR_FAILURE; + } + + localDir.forget(aResult); + return NS_OK; + +#elif XP_WIN + nsAutoString pathHash; + bool pathHashResult = false; + bool hasVendor = gAppData->vendor && strlen(gAppData->vendor) != 0; + + nsAutoString appDirPath; + if (SUCCEEDED(updRoot->GetPath(appDirPath))) { + + // Figure out where we should check for a cached hash value. If the + // application doesn't have the nsXREAppData vendor value defined check + // under SOFTWARE\Mozilla. + wchar_t regPath[1024] = { L'\0' }; + swprintf_s(regPath, mozilla::ArrayLength(regPath), L"SOFTWARE\\%S\\%S\\TaskBarIDs", + (hasVendor ? gAppData->vendor : "Mozilla"), MOZ_APP_BASENAME); + + // If we pre-computed the hash, grab it from the registry. + pathHashResult = GetCachedHash(HKEY_LOCAL_MACHINE, + nsDependentString(regPath), appDirPath, + pathHash); + if (!pathHashResult) { + pathHashResult = GetCachedHash(HKEY_CURRENT_USER, + nsDependentString(regPath), appDirPath, + pathHash); + } + } + + // Get the local app data directory and if a vendor name exists append it. + // If only a product name exists, append it. If neither exist fallback to + // old handling. We don't use the product name on purpose because we want a + // shared update directory for different apps run from the same path. + nsCOMPtr<nsIFile> localDir; + if (pathHashResult && (hasVendor || gAppData->name) && + NS_SUCCEEDED(GetUserDataDirectoryHome(getter_AddRefs(localDir), true)) && + NS_SUCCEEDED(localDir->AppendNative(nsDependentCString(hasVendor ? + gAppData->vendor : gAppData->name))) && + NS_SUCCEEDED(localDir->Append(NS_LITERAL_STRING("updates"))) && + NS_SUCCEEDED(localDir->Append(pathHash))) { + localDir.forget(aResult); + return NS_OK; + } + + nsAutoString appPath; + rv = updRoot->GetPath(appPath); + NS_ENSURE_SUCCESS(rv, rv); + + // AppDir may be a short path. Convert to long path to make sure + // the consistency of the update folder location + nsString longPath; + wchar_t* buf; + + uint32_t bufLength = longPath.GetMutableData(&buf, MAXPATHLEN); + NS_ENSURE_TRUE(bufLength >= MAXPATHLEN, NS_ERROR_OUT_OF_MEMORY); + + DWORD len = GetLongPathNameW(appPath.get(), buf, bufLength); + + // Failing GetLongPathName() is not fatal. + if (len <= 0 || len >= bufLength) + longPath.Assign(appPath); + else + longPath.SetLength(len); + + // Use <UserLocalDataDir>\updates\<relative path to app dir from + // Program Files> if app dir is under Program Files to avoid the + // folder virtualization mess on Windows Vista + nsAutoString programFiles; + rv = GetShellFolderPath(CSIDL_PROGRAM_FILES, programFiles); + NS_ENSURE_SUCCESS(rv, rv); + + programFiles.Append('\\'); + uint32_t programFilesLen = programFiles.Length(); + + nsAutoString programName; + if (_wcsnicmp(programFiles.get(), longPath.get(), programFilesLen) == 0) { + programName = Substring(longPath, programFilesLen); + } else { + // We need the update root directory to live outside of the installation + // directory, because otherwise the updater writing the log file can cause + // the directory to be locked, which prevents it from being replaced after + // background updates. + programName.AssignASCII(MOZ_APP_NAME); + } + + rv = GetUserLocalDataDirectory(getter_AddRefs(updRoot)); + NS_ENSURE_SUCCESS(rv, rv); + + rv = updRoot->AppendRelativePath(programName); + NS_ENSURE_SUCCESS(rv, rv); + +#endif // XP_WIN +#endif + updRoot.forget(aResult); + return NS_OK; +} + +nsresult +nsXREDirProvider::GetProfileStartupDir(nsIFile* *aResult) +{ + if (mProfileDir) + return mProfileDir->Clone(aResult); + + if (mAppProvider) { + nsCOMPtr<nsIFile> needsclone; + bool dummy; + nsresult rv = mAppProvider->GetFile(NS_APP_PROFILE_DIR_STARTUP, + &dummy, + getter_AddRefs(needsclone)); + if (NS_SUCCEEDED(rv)) + return needsclone->Clone(aResult); + } + + return NS_ERROR_FAILURE; +} + +nsresult +nsXREDirProvider::GetProfileDir(nsIFile* *aResult) +{ + if (mProfileDir) { + if (!mProfileNotified) + return NS_ERROR_FAILURE; + + return mProfileDir->Clone(aResult); + } + + if (mAppProvider) { + nsCOMPtr<nsIFile> needsclone; + bool dummy; + nsresult rv = mAppProvider->GetFile(NS_APP_USER_PROFILE_50_DIR, + &dummy, + getter_AddRefs(needsclone)); + if (NS_SUCCEEDED(rv)) + return needsclone->Clone(aResult); + } + + return NS_GetSpecialDirectory(NS_APP_USER_PROFILE_50_DIR, aResult); +} + +nsresult +nsXREDirProvider::GetUserDataDirectoryHome(nsIFile** aFile, bool aLocal) +{ + // Copied from nsAppFileLocationProvider (more or less) + nsresult rv; + nsCOMPtr<nsIFile> localDir; + +#if defined(XP_MACOSX) + FSRef fsRef; + OSType folderType; + if (aLocal) { + folderType = kCachedDataFolderType; + } else { +#ifdef MOZ_THUNDERBIRD + folderType = kDomainLibraryFolderType; +#else + folderType = kApplicationSupportFolderType; +#endif + } + OSErr err = ::FSFindFolder(kUserDomain, folderType, kCreateFolder, &fsRef); + NS_ENSURE_FALSE(err, NS_ERROR_FAILURE); + + rv = NS_NewNativeLocalFile(EmptyCString(), true, getter_AddRefs(localDir)); + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMPtr<nsILocalFileMac> dirFileMac = do_QueryInterface(localDir); + NS_ENSURE_TRUE(dirFileMac, NS_ERROR_UNEXPECTED); + + rv = dirFileMac->InitWithFSRef(&fsRef); + NS_ENSURE_SUCCESS(rv, rv); + + localDir = do_QueryInterface(dirFileMac, &rv); +#elif defined(XP_IOS) + nsAutoCString userDir; + if (GetUIKitDirectory(aLocal, userDir)) { + rv = NS_NewNativeLocalFile(userDir, true, getter_AddRefs(localDir)); + } else { + rv = NS_ERROR_FAILURE; + } + NS_ENSURE_SUCCESS(rv, rv); +#elif defined(XP_WIN) + nsString path; + if (aLocal) { + rv = GetShellFolderPath(CSIDL_LOCAL_APPDATA, path); + if (NS_FAILED(rv)) + rv = GetRegWindowsAppDataFolder(aLocal, path); + } + if (!aLocal || NS_FAILED(rv)) { + rv = GetShellFolderPath(CSIDL_APPDATA, path); + if (NS_FAILED(rv)) { + if (!aLocal) + rv = GetRegWindowsAppDataFolder(aLocal, path); + } + } + NS_ENSURE_SUCCESS(rv, rv); + + rv = NS_NewLocalFile(path, true, getter_AddRefs(localDir)); +#elif defined(MOZ_WIDGET_GONK) + rv = NS_NewNativeLocalFile(NS_LITERAL_CSTRING("/data/b2g"), true, + getter_AddRefs(localDir)); +#elif defined(XP_UNIX) + const char* homeDir = getenv("HOME"); + if (!homeDir || !*homeDir) + return NS_ERROR_FAILURE; + +#ifdef ANDROID /* We want (ProfD == ProfLD) on Android. */ + aLocal = false; +#endif + + if (aLocal) { + // If $XDG_CACHE_HOME is defined use it, otherwise use $HOME/.cache. + const char* cacheHome = getenv("XDG_CACHE_HOME"); + if (cacheHome && *cacheHome) { + rv = NS_NewNativeLocalFile(nsDependentCString(cacheHome), true, + getter_AddRefs(localDir)); + } else { + rv = NS_NewNativeLocalFile(nsDependentCString(homeDir), true, + getter_AddRefs(localDir)); + if (NS_SUCCEEDED(rv)) + rv = localDir->AppendNative(NS_LITERAL_CSTRING(".cache")); + } + } else { + rv = NS_NewNativeLocalFile(nsDependentCString(homeDir), true, + getter_AddRefs(localDir)); + } +#else +#error "Don't know how to get product dir on your platform" +#endif + + NS_IF_ADDREF(*aFile = localDir); + return rv; +} + +nsresult +nsXREDirProvider::GetSysUserExtensionsDirectory(nsIFile** aFile) +{ + nsCOMPtr<nsIFile> localDir; + nsresult rv = GetUserDataDirectoryHome(getter_AddRefs(localDir), false); + NS_ENSURE_SUCCESS(rv, rv); + + rv = AppendSysUserExtensionPath(localDir); + NS_ENSURE_SUCCESS(rv, rv); + + rv = EnsureDirectoryExists(localDir); + NS_ENSURE_SUCCESS(rv, rv); + + localDir.forget(aFile); + return NS_OK; +} + +#if defined(XP_UNIX) || defined(XP_MACOSX) +nsresult +nsXREDirProvider::GetSystemExtensionsDirectory(nsIFile** aFile) +{ + nsresult rv; + nsCOMPtr<nsIFile> localDir; + + rv = GetSystemParentDirectory(getter_AddRefs(localDir)); + if (NS_SUCCEEDED(rv)) { + NS_NAMED_LITERAL_CSTRING(sExtensions, +#if defined(XP_MACOSX) + "Extensions" +#else + "extensions" +#endif + ); + + rv = localDir->AppendNative(sExtensions); + if (NS_SUCCEEDED(rv)) { + localDir.forget(aFile); + } + } + return rv; +} +#endif + +nsresult +nsXREDirProvider::GetUserDataDirectory(nsIFile** aFile, bool aLocal, + const nsACString* aProfileName, + const nsACString* aAppName, + const nsACString* aVendorName) +{ + nsCOMPtr<nsIFile> localDir; + nsresult rv = GetUserDataDirectoryHome(getter_AddRefs(localDir), aLocal); + NS_ENSURE_SUCCESS(rv, rv); + + rv = AppendProfilePath(localDir, aProfileName, aAppName, aVendorName, aLocal); + NS_ENSURE_SUCCESS(rv, rv); + +#ifdef DEBUG_jungshik + nsAutoCString cwd; + localDir->GetNativePath(cwd); + printf("nsXREDirProvider::GetUserDataDirectory: %s\n", cwd.get()); +#endif + rv = EnsureDirectoryExists(localDir); + NS_ENSURE_SUCCESS(rv, rv); + + localDir.forget(aFile); + return NS_OK; +} + +nsresult +nsXREDirProvider::EnsureDirectoryExists(nsIFile* aDirectory) +{ + bool exists; + nsresult rv = aDirectory->Exists(&exists); + NS_ENSURE_SUCCESS(rv, rv); +#ifdef DEBUG_jungshik + if (!exists) { + nsAutoCString cwd; + aDirectory->GetNativePath(cwd); + printf("nsXREDirProvider::EnsureDirectoryExists: %s does not\n", cwd.get()); + } +#endif + if (!exists) + rv = aDirectory->Create(nsIFile::DIRECTORY_TYPE, 0700); +#ifdef DEBUG_jungshik + if (NS_FAILED(rv)) + NS_WARNING("nsXREDirProvider::EnsureDirectoryExists: create failed"); +#endif + + return rv; +} + +nsresult +nsXREDirProvider::AppendSysUserExtensionPath(nsIFile* aFile) +{ + NS_ASSERTION(aFile, "Null pointer!"); + + nsresult rv; + +#if defined (XP_MACOSX) || defined(XP_WIN) + + static const char* const sXR = "Mozilla"; + rv = aFile->AppendNative(nsDependentCString(sXR)); + NS_ENSURE_SUCCESS(rv, rv); + + static const char* const sExtensions = "Extensions"; + rv = aFile->AppendNative(nsDependentCString(sExtensions)); + NS_ENSURE_SUCCESS(rv, rv); + +#elif defined(XP_UNIX) + + static const char* const sXR = ".mozilla"; + rv = aFile->AppendNative(nsDependentCString(sXR)); + NS_ENSURE_SUCCESS(rv, rv); + + static const char* const sExtensions = "extensions"; + rv = aFile->AppendNative(nsDependentCString(sExtensions)); + NS_ENSURE_SUCCESS(rv, rv); + +#else +#error "Don't know how to get XRE user extension path on your platform" +#endif + return NS_OK; +} + + +nsresult +nsXREDirProvider::AppendProfilePath(nsIFile* aFile, + const nsACString* aProfileName, + const nsACString* aAppName, + const nsACString* aVendorName, + bool aLocal) +{ + NS_ASSERTION(aFile, "Null pointer!"); + + if (!gAppData) { + return NS_ERROR_FAILURE; + } + + nsAutoCString profile; + nsAutoCString appName; + nsAutoCString vendor; + if (aProfileName && !aProfileName->IsEmpty()) { + profile = *aProfileName; + } else if (aAppName) { + appName = *aAppName; + if (aVendorName) { + vendor = *aVendorName; + } + } else if (gAppData->profile) { + profile = gAppData->profile; + } else { + appName = gAppData->name; + vendor = gAppData->vendor; + } + + nsresult rv; + +#if defined (XP_MACOSX) + if (!profile.IsEmpty()) { + rv = AppendProfileString(aFile, profile.get()); + } + else { + // Note that MacOS ignores the vendor when creating the profile hierarchy - + // all application preferences directories live alongside one another in + // ~/Library/Application Support/ + rv = aFile->AppendNative(appName); + } + NS_ENSURE_SUCCESS(rv, rv); + +#elif defined(XP_WIN) + if (!profile.IsEmpty()) { + rv = AppendProfileString(aFile, profile.get()); + } + else { + if (!vendor.IsEmpty()) { + rv = aFile->AppendNative(vendor); + NS_ENSURE_SUCCESS(rv, rv); + } + rv = aFile->AppendNative(appName); + } + NS_ENSURE_SUCCESS(rv, rv); + +#elif defined(ANDROID) + // The directory used for storing profiles + // The parent of this directory is set in GetUserDataDirectoryHome + // XXX: handle gAppData->profile properly + // XXXsmaug ...and the rest of the profile creation! + MOZ_ASSERT(!aAppName, + "Profile creation for external applications is not implemented!"); + rv = aFile->AppendNative(nsDependentCString("mozilla")); + NS_ENSURE_SUCCESS(rv, rv); +#elif defined(XP_UNIX) + nsAutoCString folder; + // Make it hidden (by starting with "."), except when local (the + // profile is already under ~/.cache or XDG_CACHE_HOME). + if (!aLocal) + folder.Assign('.'); + + if (!profile.IsEmpty()) { + // Skip any leading path characters + const char* profileStart = profile.get(); + while (*profileStart == '/' || *profileStart == '\\') + profileStart++; + + // On the off chance that someone wanted their folder to be hidden don't + // let it become ".." + if (*profileStart == '.' && !aLocal) + profileStart++; + + folder.Append(profileStart); + ToLowerCase(folder); + + rv = AppendProfileString(aFile, folder.BeginReading()); + } + else { + if (!vendor.IsEmpty()) { + folder.Append(vendor); + ToLowerCase(folder); + + rv = aFile->AppendNative(folder); + NS_ENSURE_SUCCESS(rv, rv); + + folder.Truncate(); + } + + folder.Append(appName); + ToLowerCase(folder); + + rv = aFile->AppendNative(folder); + } + NS_ENSURE_SUCCESS(rv, rv); + +#else +#error "Don't know how to get profile path on your platform" +#endif + return NS_OK; +} + +nsresult +nsXREDirProvider::AppendProfileString(nsIFile* aFile, const char* aPath) +{ + NS_ASSERTION(aFile, "Null file!"); + NS_ASSERTION(aPath, "Null path!"); + + nsAutoCString pathDup(aPath); + + char* path = pathDup.BeginWriting(); + + nsresult rv; + char* subdir; + while ((subdir = NS_strtok("/\\", &path))) { + rv = aFile->AppendNative(nsDependentCString(subdir)); + NS_ENSURE_SUCCESS(rv, rv); + } + + return NS_OK; +} diff --git a/toolkit/xre/nsXREDirProvider.h b/toolkit/xre/nsXREDirProvider.h new file mode 100644 index 0000000000..7ec64da787 --- /dev/null +++ b/toolkit/xre/nsXREDirProvider.h @@ -0,0 +1,158 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef _nsXREDirProvider_h__ +#define _nsXREDirProvider_h__ + +#include "nsIDirectoryService.h" +#include "nsIProfileMigrator.h" +#include "nsIFile.h" + +#include "nsCOMPtr.h" +#include "nsCOMArray.h" +#include "mozilla/Attributes.h" + +class nsXREDirProvider final : public nsIDirectoryServiceProvider2, + public nsIProfileStartup +{ +public: + // we use a custom isupports implementation (no refcount) + NS_IMETHOD QueryInterface(REFNSIID aIID, void** aInstancePtr) override; + NS_IMETHOD_(MozExternalRefCountType) AddRef(void) override; + NS_IMETHOD_(MozExternalRefCountType) Release(void) override; + + NS_DECL_NSIDIRECTORYSERVICEPROVIDER + NS_DECL_NSIDIRECTORYSERVICEPROVIDER2 + NS_DECL_NSIPROFILESTARTUP + + nsXREDirProvider(); + + // if aXULAppDir is null, use gArgv[0] + nsresult Initialize(nsIFile *aXULAppDir, + nsIFile *aGREDir, + nsIDirectoryServiceProvider* aAppProvider = nullptr); + ~nsXREDirProvider(); + + static nsXREDirProvider* GetSingleton(); + + nsresult GetUserProfilesRootDir(nsIFile** aResult, + const nsACString* aProfileName, + const nsACString* aAppName, + const nsACString* aVendorName); + nsresult GetUserProfilesLocalDir(nsIFile** aResult, + const nsACString* aProfileName, + const nsACString* aAppName, + const nsACString* aVendorName); + + // We only set the profile dir, we don't ensure that it exists; + // that is the responsibility of the toolkit profile service. + // We also don't fire profile-changed notifications... that is + // the responsibility of the apprunner. + nsresult SetProfile(nsIFile* aProfileDir, nsIFile* aProfileLocalDir); + + void DoShutdown(); + + static nsresult GetUserAppDataDirectory(nsIFile* *aFile) { + return GetUserDataDirectory(aFile, false, nullptr, nullptr, nullptr); + } + static nsresult GetUserLocalDataDirectory(nsIFile* *aFile) { + return GetUserDataDirectory(aFile, true, nullptr, nullptr, nullptr); + } + + // By default GetUserDataDirectory gets profile path from gAppData, + // but that can be overridden by using aProfileName/aAppName/aVendorName. + static nsresult GetUserDataDirectory(nsIFile** aFile, bool aLocal, + const nsACString* aProfileName, + const nsACString* aAppName, + const nsACString* aVendorName); + + /* make sure you clone it, if you need to do stuff to it */ + nsIFile* GetGREDir() { return mGREDir; } + nsIFile* GetGREBinDir() { return mGREBinDir; } + nsIFile* GetAppDir() { + if (mXULAppDir) + return mXULAppDir; + return mGREDir; + } + + /** + * Get the directory under which update directory is created. + * This method may be called before XPCOM is started. aResult + * is a clone, it may be modified. + */ + nsresult GetUpdateRootDir(nsIFile* *aResult); + + /** + * Get the profile startup directory as determined by this class or by + * mAppProvider. This method may be called before XPCOM is started. aResult + * is a clone, it may be modified. + */ + nsresult GetProfileStartupDir(nsIFile* *aResult); + + /** + * Get the profile directory as determined by this class or by an + * embedder-provided XPCOM directory provider. Only call this method + * when XPCOM is initialized! aResult is a clone, it may be modified. + */ + nsresult GetProfileDir(nsIFile* *aResult); + +protected: + nsresult GetFilesInternal(const char* aProperty, nsISimpleEnumerator** aResult); + static nsresult GetUserDataDirectoryHome(nsIFile* *aFile, bool aLocal); + static nsresult GetSysUserExtensionsDirectory(nsIFile* *aFile); +#if defined(XP_UNIX) || defined(XP_MACOSX) + static nsresult GetSystemExtensionsDirectory(nsIFile** aFile); +#endif + static nsresult EnsureDirectoryExists(nsIFile* aDirectory); + + // Determine the profile path within the UAppData directory. This is different + // on every major platform. + static nsresult AppendProfilePath(nsIFile* aFile, + const nsACString* aProfileName, + const nsACString* aAppName, + const nsACString* aVendorName, + bool aLocal); + + static nsresult AppendSysUserExtensionPath(nsIFile* aFile); + + // Internal helper that splits a path into components using the '/' and '\\' + // delimiters. + static inline nsresult AppendProfileString(nsIFile* aFile, const char* aPath); + +#if (defined(XP_WIN) || defined(XP_MACOSX)) && defined(MOZ_CONTENT_SANDBOX) + // Load the temp directory for sandboxed content processes + nsresult LoadContentProcessTempDir(); +#endif + + // Calculate and register extension and theme bundle directories. + void LoadExtensionBundleDirectories(); + +#ifdef MOZ_B2G + // Calculate and register app-bundled extension directories. + void LoadAppBundleDirs(); +#endif + + void Append(nsIFile* aDirectory); + + nsCOMPtr<nsIDirectoryServiceProvider> mAppProvider; + // On OSX, mGREDir points to .app/Contents/Resources + nsCOMPtr<nsIFile> mGREDir; + // On OSX, mGREBinDir points to .app/Contents/MacOS + nsCOMPtr<nsIFile> mGREBinDir; + // On OSX, mXULAppDir points to .app/Contents/Resources/browser + nsCOMPtr<nsIFile> mXULAppDir; + nsCOMPtr<nsIFile> mProfileDir; + nsCOMPtr<nsIFile> mProfileLocalDir; + bool mProfileNotified; +#if (defined(XP_WIN) || defined(XP_MACOSX)) && defined(MOZ_CONTENT_SANDBOX) + nsCOMPtr<nsIFile> mContentTempDir; + nsCOMPtr<nsIFile> mContentProcessSandboxTempDir; +#endif + nsCOMArray<nsIFile> mAppBundleDirectories; + nsCOMArray<nsIFile> mExtensionDirectories; + nsCOMArray<nsIFile> mThemeDirectories; +}; + +#endif diff --git a/toolkit/xre/platform.ini b/toolkit/xre/platform.ini new file mode 100644 index 0000000000..01c8b741a1 --- /dev/null +++ b/toolkit/xre/platform.ini @@ -0,0 +1,17 @@ +#if 0 +; This Source Code Form is subject to the terms of the Mozilla Public +; License, v. 2.0. If a copy of the MPL was not distributed with this +; file, You can obtain one at http://mozilla.org/MPL/2.0/. +#endif +#filter substitution +#include @TOPOBJDIR@/buildid.h +#include @TOPOBJDIR@/source-repo.h +[Build] +BuildID=@MOZ_BUILDID@ +Milestone=@GRE_MILESTONE@ +#ifdef MOZ_SOURCE_REPO +SourceRepository=@MOZ_SOURCE_REPO@ +#endif +#ifdef MOZ_SOURCE_STAMP +SourceStamp=@MOZ_SOURCE_STAMP@ +#endif diff --git a/toolkit/xre/test/.eslintrc.js b/toolkit/xre/test/.eslintrc.js new file mode 100644 index 0000000000..e3134a291c --- /dev/null +++ b/toolkit/xre/test/.eslintrc.js @@ -0,0 +1,7 @@ +"use strict"; + +module.exports = { + "extends": [ + "../../../testing/mochitest/mochitest.eslintrc.js" + ] +}; diff --git a/toolkit/xre/test/browser.ini b/toolkit/xre/test/browser.ini new file mode 100644 index 0000000000..deadebc46d --- /dev/null +++ b/toolkit/xre/test/browser.ini @@ -0,0 +1,4 @@ +[DEFAULT] + +[browser_checkdllblockliststate.js] +skip-if = os != "win" diff --git a/toolkit/xre/test/browser_checkdllblockliststate.js b/toolkit/xre/test/browser_checkdllblockliststate.js new file mode 100644 index 0000000000..6b989c3705 --- /dev/null +++ b/toolkit/xre/test/browser_checkdllblockliststate.js @@ -0,0 +1,16 @@ +// Any copyright is dedicated to the Public Domain. +// http://creativecommons.org/publicdomain/zero/1.0/ + +// Tests that the dll blocklist initializes correctly during test runs. +add_task(function* test() { + yield BrowserTestUtils.withNewTab({gBrowser, url: "about:blank" }, function* (browser) { + ok(Components.classes["@mozilla.org/xre/app-info;1"] + .getService(Ci.nsIXULRuntime) + .windowsDLLBlocklistStatus, + "Windows dll blocklist status should be true, indicating it is " + + "running properly. A failure in this test is considered a " + + "release blocker."); + }); +}); + + diff --git a/toolkit/xre/test/mochitest.ini b/toolkit/xre/test/mochitest.ini new file mode 100644 index 0000000000..ccbb08ed97 --- /dev/null +++ b/toolkit/xre/test/mochitest.ini @@ -0,0 +1,3 @@ +[DEFAULT] + +[test_fpuhandler.html] diff --git a/toolkit/xre/test/test_fpuhandler.html b/toolkit/xre/test/test_fpuhandler.html new file mode 100644 index 0000000000..afe70ee64f --- /dev/null +++ b/toolkit/xre/test/test_fpuhandler.html @@ -0,0 +1,38 @@ +<head> + <title>Floating-point exception handler test</title> + <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + +<body onload="runTest()"> + <embed id="plugin1" type="application/x-test" width="400" height="400"></embed> + + <script class="testbody" type="application/javascript"> + SimpleTest.waitForExplicitFinish(); + + function doDiv(x, y) { + var z; + z = x / y; + + for (i = 0 + x; i < 1000; ++i) + z = y / x; + + z = x / y; + return z; + } + + function runTest() + { + ok(isNaN(doDiv(0.0, 0.0)), "Undefined division-by-zero doesn't crash"); + + try { + document.getElementById('plugin1').enableFPExceptions(); + } + catch (e) { + ok(true, "No special code to set the FPU bit in the testplugin."); + SimpleTest.finish(); + return; + } + + ok(isNaN(doDiv(0.0, 0.0)), "Undefined division-by-zero doesn't crash again."); + SimpleTest.finish(); + } + </script> diff --git a/toolkit/xre/test/win/Makefile.in b/toolkit/xre/test/win/Makefile.in new file mode 100644 index 0000000000..331a8a4fdc --- /dev/null +++ b/toolkit/xre/test/win/Makefile.in @@ -0,0 +1,14 @@ +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + +MOZ_WINCONSOLE = 1 + +include $(topsrcdir)/config/rules.mk + +libs:: TestXREMakeCommandLineWin.ini + $(INSTALL) $^ $(FINAL_TARGET)/ + +check:: + @echo 'Running TestXREMakeCommandLineWin tests' + @$(RUN_TEST_PROGRAM) $(FINAL_TARGET)/TestXREMakeCommandLineWin.exe diff --git a/toolkit/xre/test/win/TestDllInterceptor.cpp b/toolkit/xre/test/win/TestDllInterceptor.cpp new file mode 100644 index 0000000000..dff71817fc --- /dev/null +++ b/toolkit/xre/test/win/TestDllInterceptor.cpp @@ -0,0 +1,237 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#if _WIN32_WINNT < 0x0600 +#undef _WIN32_WINNT +#define _WIN32_WINNT 0x0600 +#endif + +#include <shlobj.h> +#include <stdio.h> + +#include "mozilla/WindowsVersion.h" +#include "nsWindowsDllInterceptor.h" +#include "nsWindowsHelpers.h" + +using namespace mozilla; + +struct payload { + UINT64 a; + UINT64 b; + UINT64 c; + + bool operator==(const payload &other) const { + return (a == other.a && + b == other.b && + c == other.c); + } +}; + +extern "C" __declspec(dllexport) __declspec(noinline) payload rotatePayload(payload p) { + UINT64 tmp = p.a; + p.a = p.b; + p.b = p.c; + p.c = tmp; + return p; +} + +static bool patched_func_called = false; + +static payload (*orig_rotatePayload)(payload); + +static payload +patched_rotatePayload(payload p) +{ + patched_func_called = true; + return orig_rotatePayload(p); +} + +bool TestHook(const char *dll, const char *func) +{ + void *orig_func; + bool successful = false; + { + WindowsDllInterceptor TestIntercept; + TestIntercept.Init(dll); + successful = TestIntercept.AddHook(func, 0, &orig_func); + } + + if (successful) { + printf("TEST-PASS | WindowsDllInterceptor | Could hook %s from %s\n", func, dll); + return true; + } else { + printf("TEST-UNEXPECTED-FAIL | WindowsDllInterceptor | Failed to hook %s from %s\n", func, dll); + return false; + } +} + +bool TestDetour(const char *dll, const char *func) +{ + void *orig_func; + bool successful = false; + { + WindowsDllInterceptor TestIntercept; + TestIntercept.Init(dll); + successful = TestIntercept.AddDetour(func, 0, &orig_func); + } + + if (successful) { + printf("TEST-PASS | WindowsDllInterceptor | Could detour %s from %s\n", func, dll); + return true; + } else { + printf("TEST-UNEXPECTED-FAIL | WindowsDllInterceptor | Failed to detour %s from %s\n", func, dll); + return false; + } +} + +bool MaybeTestHook(const bool cond, const char* dll, const char* func) +{ + if (!cond) { + return true; + } + + return TestHook(dll, func); +} + +bool ShouldTestTipTsf() +{ +#if defined(_M_X64) + return false; +#else + if (!IsWin8OrLater()) { + return false; + } + + nsModuleHandle shell32(LoadLibraryW(L"shell32.dll")); + if (!shell32) { + return true; + } + + auto pSHGetKnownFolderPath = reinterpret_cast<decltype(&SHGetKnownFolderPath)>(GetProcAddress(shell32, "SHGetKnownFolderPath")); + if (!pSHGetKnownFolderPath) { + return true; + } + + PWSTR commonFilesPath = nullptr; + if (FAILED(pSHGetKnownFolderPath(FOLDERID_ProgramFilesCommon, 0, nullptr, + &commonFilesPath))) { + return true; + } + + wchar_t fullPath[MAX_PATH + 1] = {}; + wcscpy(fullPath, commonFilesPath); + wcscat(fullPath, L"\\Microsoft Shared\\Ink\\tiptsf.dll"); + CoTaskMemFree(commonFilesPath); + + if (!LoadLibraryW(fullPath)) { + return false; + } + + // Leak the module so that it's loaded for the interceptor test + return true; +#endif +} + +int main() +{ + payload initial = { 0x12345678, 0xfc4e9d31, 0x87654321 }; + payload p0, p1; + ZeroMemory(&p0, sizeof(p0)); + ZeroMemory(&p1, sizeof(p1)); + + p0 = rotatePayload(initial); + + { + WindowsDllInterceptor ExeIntercept; + ExeIntercept.Init("TestDllInterceptor.exe"); + if (ExeIntercept.AddHook("rotatePayload", reinterpret_cast<intptr_t>(patched_rotatePayload), (void**) &orig_rotatePayload)) { + printf("TEST-PASS | WindowsDllInterceptor | Hook added\n"); + } else { + printf("TEST-UNEXPECTED-FAIL | WindowsDllInterceptor | Failed to add hook\n"); + return 1; + } + + p1 = rotatePayload(initial); + + if (patched_func_called) { + printf("TEST-PASS | WindowsDllInterceptor | Hook called\n"); + } else { + printf("TEST-UNEXPECTED-FAIL | WindowsDllInterceptor | Hook was not called\n"); + return 1; + } + + if (p0 == p1) { + printf("TEST-PASS | WindowsDllInterceptor | Hook works properly\n"); + } else { + printf("TEST-UNEXPECTED-FAIL | WindowsDllInterceptor | Hook didn't return the right information\n"); + return 1; + } + } + + patched_func_called = false; + ZeroMemory(&p1, sizeof(p1)); + + p1 = rotatePayload(initial); + + if (!patched_func_called) { + printf("TEST-PASS | WindowsDllInterceptor | Hook was not called after unregistration\n"); + } else { + printf("TEST-UNEXPECTED-FAIL | WindowsDllInterceptor | Hook was still called after unregistration\n"); + return 1; + } + + if (p0 == p1) { + printf("TEST-PASS | WindowsDllInterceptor | Original function worked properly\n"); + } else { + printf("TEST-UNEXPECTED-FAIL | WindowsDllInterceptor | Original function didn't return the right information\n"); + return 1; + } + + if (TestHook("user32.dll", "GetWindowInfo") && +#ifdef _WIN64 + TestHook("user32.dll", "SetWindowLongPtrA") && + TestHook("user32.dll", "SetWindowLongPtrW") && +#else + TestHook("user32.dll", "SetWindowLongA") && + TestHook("user32.dll", "SetWindowLongW") && +#endif + TestHook("user32.dll", "TrackPopupMenu") && +#ifdef _M_IX86 + // We keep this test to hook complex code on x86. (Bug 850957) + TestHook("ntdll.dll", "NtFlushBuffersFile") && +#endif + TestHook("ntdll.dll", "NtCreateFile") && + TestHook("ntdll.dll", "NtReadFile") && + TestHook("ntdll.dll", "NtReadFileScatter") && + TestHook("ntdll.dll", "NtWriteFile") && + TestHook("ntdll.dll", "NtWriteFileGather") && + TestHook("ntdll.dll", "NtQueryFullAttributesFile") && + // Bug 733892: toolkit/crashreporter/nsExceptionHandler.cpp + TestHook("kernel32.dll", "SetUnhandledExceptionFilter") && +#ifdef _M_IX86 + // Bug 670967: xpcom/base/AvailableMemoryTracker.cpp + TestHook("kernel32.dll", "VirtualAlloc") && + TestHook("kernel32.dll", "MapViewOfFile") && + TestHook("gdi32.dll", "CreateDIBSection") && + TestHook("kernel32.dll", "CreateFileW") && +#endif + TestDetour("user32.dll", "CreateWindowExW") && + TestHook("user32.dll", "InSendMessageEx") && + TestHook("imm32.dll", "ImmGetContext") && + TestHook("imm32.dll", "ImmGetCompositionStringW") && + TestHook("imm32.dll", "ImmSetCandidateWindow") && +#ifdef _M_X64 + TestHook("user32.dll", "GetKeyState") && +#endif + MaybeTestHook(ShouldTestTipTsf(), "tiptsf.dll", "ProcessCaretEvents") && +#ifdef _M_IX86 + TestHook("user32.dll", "SendMessageTimeoutW") && +#endif + TestDetour("ntdll.dll", "LdrLoadDll")) { + printf("TEST-PASS | WindowsDllInterceptor | all checks passed\n"); + return 0; + } + + return 1; +} diff --git a/toolkit/xre/test/win/TestXREMakeCommandLineWin.cpp b/toolkit/xre/test/win/TestXREMakeCommandLineWin.cpp new file mode 100644 index 0000000000..00d786aa2b --- /dev/null +++ b/toolkit/xre/test/win/TestXREMakeCommandLineWin.cpp @@ -0,0 +1,265 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include <stdio.h> +#include <stdlib.h> +#include <windows.h> +// Support for _setmode +#include <fcntl.h> +#include <io.h> + +#include "nsWindowsRestart.cpp" + +// CommandLineToArgvW may return different values for argv[0] since it contains +// the path to the binary that was executed so we prepend an argument that is +// quoted with a space to prevent argv[1] being appended to argv[0]. +#define DUMMY_ARG1 L"\"arg 1\" " + +#ifndef MAXPATHLEN +# ifdef PATH_MAX +# define MAXPATHLEN PATH_MAX +# elif defined(MAX_PATH) +# define MAXPATHLEN MAX_PATH +# elif defined(_MAX_PATH) +# define MAXPATHLEN _MAX_PATH +# elif defined(CCHMAXPATH) +# define MAXPATHLEN CCHMAXPATH +# else +# define MAXPATHLEN 1024 +# endif +#endif + +#define TEST_NAME L"XRE MakeCommandLine" +#define MAX_TESTS 100 + +// Verbose output can be enabled by defining VERBOSE 1 +#define VERBOSE 0 + +// Compares compareCmdLine with the output of MakeCommandLine. This is +// accomplished by converting inCmdLine to an argument list with +// CommandLineToArgvW and converting it back to a command line with +// MakeCommandLine. +static int +verifyCmdLineCreation(wchar_t *inCmdLine, + wchar_t *compareCmdLine, + bool passes, int testNum) +{ + int rv = 0; + int i; + int inArgc; + int outArgc; + bool isEqual; + + // When debugging with command lines containing Unicode characters greater + // than 255 you can set the mode for stdout to Unicode so the console will + // receive the correct characters though it won't display them properly unless + // the console's font has been set to one that can display the characters. You + // can also redirect the console output to a file that has been saved as Unicode + // to view the characters. +// _setmode(_fileno(stdout), _O_WTEXT); + + // Prepend an additional argument to the command line. CommandLineToArgvW + // handles argv[0] differently than other arguments since argv[0] is the path + // to the binary being executed and MakeCommandLine only handles argv[1] and + // larger. + wchar_t *inCmdLineNew = (wchar_t *) malloc((wcslen(DUMMY_ARG1) + wcslen(inCmdLine) + 1) * sizeof(wchar_t)); + wcscpy(inCmdLineNew, DUMMY_ARG1); + wcscat(inCmdLineNew, inCmdLine); + LPWSTR *inArgv = CommandLineToArgvW(inCmdLineNew, &inArgc); + + wchar_t *outCmdLine = MakeCommandLine(inArgc - 1, inArgv + 1); + wchar_t *outCmdLineNew = (wchar_t *) malloc((wcslen(DUMMY_ARG1) + wcslen(outCmdLine) + 1) * sizeof(wchar_t)); + wcscpy(outCmdLineNew, DUMMY_ARG1); + wcscat(outCmdLineNew, outCmdLine); + LPWSTR *outArgv = CommandLineToArgvW(outCmdLineNew, &outArgc); + + if (VERBOSE) { + wprintf(L"\n"); + wprintf(L"Verbose Output\n"); + wprintf(L"--------------\n"); + wprintf(L"Input command line : >%s<\n", inCmdLine); + wprintf(L"MakeComandLine output: >%s<\n", outCmdLine); + wprintf(L"Expected command line: >%s<\n", compareCmdLine); + + wprintf(L"input argc : %d\n", inArgc - 1); + wprintf(L"output argc: %d\n", outArgc - 1); + + for (i = 1; i < inArgc; ++i) { + wprintf(L"input argv[%d] : >%s<\n", i - 1, inArgv[i]); + } + + for (i = 1; i < outArgc; ++i) { + wprintf(L"output argv[%d]: >%s<\n", i - 1, outArgv[i]); + } + wprintf(L"\n"); + } + + isEqual = (inArgc == outArgc); + if (!isEqual) { + wprintf(L"TEST-%s-FAIL | %s | ARGC Comparison (check %2d)\n", + passes ? L"UNEXPECTED" : L"KNOWN", TEST_NAME, testNum); + if (passes) { + rv = 1; + } + LocalFree(inArgv); + LocalFree(outArgv); + free(inCmdLineNew); + free(outCmdLineNew); + free(outCmdLine); + return rv; + } + + for (i = 1; i < inArgc; ++i) { + isEqual = (wcscmp(inArgv[i], outArgv[i]) == 0); + if (!isEqual) { + wprintf(L"TEST-%s-FAIL | %s | ARGV Comparison (check %2d)\n", + passes ? L"UNEXPECTED" : L"KNOWN", TEST_NAME, testNum); + if (passes) { + rv = 1; + } + LocalFree(inArgv); + LocalFree(outArgv); + free(inCmdLineNew); + free(outCmdLineNew); + free(outCmdLine); + return rv; + } + } + + isEqual = (wcscmp(outCmdLine, compareCmdLine) == 0); + if (!isEqual) { + wprintf(L"TEST-%s-FAIL | %s | Command Line Comparison (check %2d)\n", + passes ? L"UNEXPECTED" : L"KNOWN", TEST_NAME, testNum); + if (passes) { + rv = 1; + } + LocalFree(inArgv); + LocalFree(outArgv); + free(inCmdLineNew); + free(outCmdLineNew); + free(outCmdLine); + return rv; + } + + if (rv == 0) { + if (passes) { + wprintf(L"TEST-PASS | %s | check %2d\n", TEST_NAME, testNum); + } else { + wprintf(L"TEST-UNEXPECTED-PASS | %s | check %2d\n", TEST_NAME, testNum); + rv = 1; + } + } + + LocalFree(inArgv); + LocalFree(outArgv); + free(inCmdLineNew); + free(outCmdLineNew); + free(outCmdLine); + return rv; +} + +int wmain(int argc, wchar_t *argv[]) +{ + int i; + int rv = 0; + + if (argc > 1 && (_wcsicmp(argv[1], L"-check-one") != 0 || argc != 3)) { + fwprintf(stderr, L"Displays and validates output from MakeCommandLine.\n\n"); + fwprintf(stderr, L"Usage: %s -check-one <test number>\n\n", argv[0]); + fwprintf(stderr, L" <test number>\tSpecifies the test number to run from the\n"); + fwprintf(stderr, L"\t\tTestXREMakeCommandLineWin.ini file.\n"); + return 255; + } + + wchar_t inifile[MAXPATHLEN]; + if (!::GetModuleFileNameW(0, inifile, MAXPATHLEN)) { + wprintf(L"TEST-UNEXPECTED-FAIL | %s | GetModuleFileNameW\n", TEST_NAME); + return 2; + } + + WCHAR *slash = wcsrchr(inifile, '\\'); + if (!slash) { + wprintf(L"TEST-UNEXPECTED-FAIL | %s | wcsrchr\n", TEST_NAME); + return 3; + } + + wcscpy(slash + 1, L"TestXREMakeCommandLineWin.ini\0"); + + for (i = 0; i < MAX_TESTS; ++i) { + wchar_t sInputVal[MAXPATHLEN]; + wchar_t sOutputVal[MAXPATHLEN]; + wchar_t sPassesVal[MAXPATHLEN]; + wchar_t sInputKey[MAXPATHLEN]; + wchar_t sOutputKey[MAXPATHLEN]; + wchar_t sPassesKey[MAXPATHLEN]; + + if (argc > 2 && _wcsicmp(argv[1], L"-check-one") == 0 && argc == 3) { + i = _wtoi(argv[2]); + } + + _snwprintf(sInputKey, MAXPATHLEN, L"input_%d", i); + _snwprintf(sOutputKey, MAXPATHLEN, L"output_%d", i); + _snwprintf(sPassesKey, MAXPATHLEN, L"passes_%d", i); + + if (!GetPrivateProfileStringW(L"MakeCommandLineTests", sInputKey, nullptr, + sInputVal, MAXPATHLEN, inifile)) { + if (i == 0 || (argc > 2 && _wcsicmp(argv[1], L"-check-one") == 0)) { + wprintf(L"TEST-UNEXPECTED-FAIL | %s | see following explanation:\n", TEST_NAME); + wprintf(L"ERROR: Either the TestXREMakeCommandLineWin.ini file doesn't exist\n"); + if (argc > 1 && _wcsicmp(argv[1], L"-check-one") == 0 && argc == 3) { + wprintf(L"ERROR: or the test is not defined in the MakeCommandLineTests section.\n"); + } else { + wprintf(L"ERROR: or it has no tests defined in the MakeCommandLineTests section.\n"); + } + wprintf(L"ERROR: File: %s\n", inifile); + return 4; + } + break; + } + + GetPrivateProfileStringW(L"MakeCommandLineTests", sOutputKey, nullptr, + sOutputVal, MAXPATHLEN, inifile); + GetPrivateProfileStringW(L"MakeCommandLineTests", sPassesKey, nullptr, + sPassesVal, MAXPATHLEN, inifile); + + rv |= verifyCmdLineCreation(sInputVal, sOutputVal, + (_wcsicmp(sPassesVal, L"false") == 0) ? FALSE : TRUE, + i); + + if (argc > 2 && _wcsicmp(argv[1], L"-check-one") == 0) { + break; + } + } + + if (rv == 0) { + wprintf(L"TEST-PASS | %s | all checks passed\n", TEST_NAME); + } else { + wprintf(L"TEST-UNEXPECTED-FAIL | %s | some checks failed\n", TEST_NAME); + } + + return rv; +} + +#ifdef __MINGW32__ + +/* MingW currently does not implement a wide version of the + startup routines. Workaround is to implement something like + it ourselves. See bug 411826 */ + +#include <shellapi.h> + +int main(int argc, char **argv) +{ + LPWSTR commandLine = GetCommandLineW(); + int argcw = 0; + LPWSTR *argvw = CommandLineToArgvW(commandLine, &argcw); + if (!argvw) + return 127; + + int result = wmain(argcw, argvw); + LocalFree(argvw); + return result; +} +#endif /* __MINGW32__ */ diff --git a/toolkit/xre/test/win/TestXREMakeCommandLineWin.ini b/toolkit/xre/test/win/TestXREMakeCommandLineWin.ini new file mode 100644 index 0000000000..dbb529d1b1 --- /dev/null +++ b/toolkit/xre/test/win/TestXREMakeCommandLineWin.ini @@ -0,0 +1,94 @@ +; A typical MakeCommandLine test will contain an input and an output name value +; pair. The value for input_xx is the input command line and the value for +; output_xx is the expected output command line. +; +; A test that is known to fail can be added as follows. If the passes_xx name +; value pair doesn't exist it defaults to true. +; input_99=yabadaba +; output_99=doo +; passes_99=false +; +; If a value starts and ends with single or double quotation marks then it must +; be enclosed in single or double quotation marks due to GetPrivateProfileString +; discarding the outmost quotation marks. See GetPrivateProfileString on MSDN +; for more information. +; http://msdn.microsoft.com/en-us/library/ms724353.aspx + +[MakeCommandLineTests] +input_0=a:\ +output_0=a:\ + +input_1=""a:\"" +output_1=a:\" + +input_2=""a:\b c"" +output_2=""a:\b c"" + +input_3=""a:\b c\"" +output_3=""a:\b c\""" + +input_4=""a:\b c\d e"" +output_4=""a:\b c\d e"" + +input_5=""a:\b c\d e\"" +output_5=""a:\b c\d e\""" + +input_6=""a:\\"" +output_6=a:\ + +input_7="a:\" "b:\c d" +output_7=a:\" "b:\c d" + +input_8="a "b:\" "c:\d e"" +output_8="a "b:\" c:\d" e" + +input_9="abc" d e +output_9=abc d e + +input_10="a b c" d e +output_10="a b c" d e + +input_11=a\\\b d"e f"g h +output_11=a\\\b "de fg" h + +input_12=a b +output_12=a b + +input_13=""a b"" +output_13=""a b"" + +input_14=a\\\"b c d +output_14=a\\\"b c d + +input_15=a\\\"b c" +output_15=a\\\"b c + +input_16=""a\\\b c" +output_16=""a\\\b c"" + +input_17=\"a +output_17=\"a + +input_18=\\"a +output_18=\a + +input_19=\\"\\\\"a +output_19=\\\a + +input_20=\\"\\\\\"a +output_20=\\\\\\\"a + +input_21="a\\\"b c\" d e +output_21=""a\\\"b c\" d e"" + +input_22=a\\\\\"b c" d e" +output_22=a\\\\\"b "c d e" + +input_23=a:\b c\アルファ オメガ\d +output_23=a:\b c\アルファ オメガ\d + +input_24=a:\b "c\アルファ オメガ\d" +output_24=a:\b "c\アルファ オメガ\d" + +input_25=アルファ オメガ +output_25=アルファ オメガ diff --git a/toolkit/xre/test/win/moz.build b/toolkit/xre/test/win/moz.build new file mode 100644 index 0000000000..51ad0bcb37 --- /dev/null +++ b/toolkit/xre/test/win/moz.build @@ -0,0 +1,30 @@ +# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*- +# vim: set filetype=python: +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + +SimplePrograms([ + 'TestXREMakeCommandLineWin', +]) + +CppUnitTests([ + 'TestDllInterceptor', +]) + +DEFINES['NS_NO_XPCOM'] = True + +LOCAL_INCLUDES += [ + '/config', + '/toolkit/xre', +] + +DISABLE_STL_WRAPPING = True +USE_STATIC_LIBS = True + +OS_LIBS += [ + 'comctl32', + 'ole32', + 'shell32', + 'ws2_32', +] diff --git a/toolkit/xre/updaterfileutils_osx.h b/toolkit/xre/updaterfileutils_osx.h new file mode 100644 index 0000000000..75510b2ff5 --- /dev/null +++ b/toolkit/xre/updaterfileutils_osx.h @@ -0,0 +1,13 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef updaterfileutils_osx_h_ +#define updaterfileutils_osx_h_ + +extern "C" { + bool IsRecursivelyWritable(const char* aPath); +} + +#endif
\ No newline at end of file diff --git a/toolkit/xre/updaterfileutils_osx.mm b/toolkit/xre/updaterfileutils_osx.mm new file mode 100644 index 0000000000..f10b94e1a8 --- /dev/null +++ b/toolkit/xre/updaterfileutils_osx.mm @@ -0,0 +1,52 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "updaterfileutils_osx.h" + +#include <Cocoa/Cocoa.h> + +bool IsRecursivelyWritable(const char* aPath) +{ + NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init]; + + NSString* rootPath = [NSString stringWithUTF8String:aPath]; + NSFileManager* fileManager = [NSFileManager defaultManager]; + NSError* error = nil; + NSArray* subPaths = + [fileManager subpathsOfDirectoryAtPath:rootPath + error:&error]; + NSMutableArray* paths = + [NSMutableArray arrayWithCapacity:[subPaths count] + 1]; + [paths addObject:@""]; + [paths addObjectsFromArray:subPaths]; + + if (error) { + [pool drain]; + return false; + } + + for (NSString* currPath in paths) { + NSString* child = [rootPath stringByAppendingPathComponent:currPath]; + + NSDictionary* attributes = + [fileManager attributesOfItemAtPath:child + error:&error]; + if (error) { + [pool drain]; + return false; + } + + // Don't check for writability of files pointed to by symlinks, as they may + // not be descendants of the root path. + if ([attributes fileType] != NSFileTypeSymbolicLink && + [fileManager isWritableFileAtPath:child] == NO) { + [pool drain]; + return false; + } + } + + [pool drain]; + return true; +} |