diff -r 000000000000 -r 4f2f89ce4247 WebKitTools/WebKitLauncher/WebKitNightlyEnabler.m --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/WebKitTools/WebKitLauncher/WebKitNightlyEnabler.m Fri Sep 17 09:02:29 2010 +0300 @@ -0,0 +1,236 @@ +/* + * Copyright (C) 2006, 2007, 2008, 2009 Apple Inc. All rights reserved. + * Copyright (C) 2006 Graham Dennis. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of Apple Inc. ("Apple") nor the names of + * its contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#import +#import "WebKitNightlyEnablerSparkle.h" + +static void enableWebKitNightlyBehaviour() __attribute__ ((constructor)); + +static NSString *WKNERunState = @"WKNERunState"; +static NSString *WKNEShouldMonitorShutdowns = @"WKNEShouldMonitorShutdowns"; + +typedef enum { + RunStateShutDown, + RunStateInitializing, + RunStateRunning +} WKNERunStates; + +static char *webKitAppPath; +static bool extensionBundlesWereLoaded = NO; +static NSSet *extensionPaths = nil; + +static int32_t systemVersion() +{ + static SInt32 version = 0; + if (!version) + Gestalt(gestaltSystemVersion, &version); + + return version; +} + + +static void myBundleDidLoad(CFNotificationCenterRef center, void *observer, CFStringRef name, const void *object, CFDictionaryRef userInfo) +{ + NSBundle *bundle = (NSBundle *)object; + NSString *bundlePath = [[bundle bundlePath] stringByAbbreviatingWithTildeInPath]; + NSString *bundleFileName = [bundlePath lastPathComponent]; + + // Explicitly ignore SIMBL.bundle, as its only purpose is to load extensions + // on a per-application basis. It's presence indicates a user has application + // extensions, but not that any will be loaded into Safari + if ([bundleFileName isEqualToString:@"SIMBL.bundle"]) + return; + + // If the bundle lives inside a known extension path, flag it as an extension + NSEnumerator *e = [extensionPaths objectEnumerator]; + NSString *path = nil; + while ((path = [e nextObject])) { + if ([bundlePath length] < [path length]) + continue; + + if ([[bundlePath substringToIndex:[path length]] isEqualToString:path]) { + NSLog(@"Extension detected: %@", bundlePath); + extensionBundlesWereLoaded = YES; + break; + } + } +} + +static void myApplicationWillFinishLaunching(CFNotificationCenterRef center, void *observer, CFStringRef name, const void *object, CFDictionaryRef userInfo) +{ + CFNotificationCenterRemoveObserver(CFNotificationCenterGetLocalCenter(), &myApplicationWillFinishLaunching, NULL, NULL); + CFNotificationCenterRemoveObserver(CFNotificationCenterGetLocalCenter(), &myBundleDidLoad, NULL, NULL); + [extensionPaths release]; + extensionPaths = nil; + + NSUserDefaults *userDefaults = [NSUserDefaults standardUserDefaults]; + [userDefaults setInteger:RunStateRunning forKey:WKNERunState]; + [userDefaults synchronize]; + + if (extensionBundlesWereLoaded) + NSRunInformationalAlertPanel(@"Safari extensions detected", + @"Safari extensions were detected on your system. Extensions are incompatible with nightly builds of WebKit, and may cause crashes or incorrect behavior. Please disable them if you experience such behavior.", @"Continue", + nil, nil); + + initializeSparkle(); +} + +static void myApplicationWillTerminate(CFNotificationCenterRef center, void *observer, CFStringRef name, const void *object, CFDictionaryRef userInfo) +{ + NSUserDefaults *userDefaults = [NSUserDefaults standardUserDefaults]; + [userDefaults setInteger:RunStateShutDown forKey:WKNERunState]; + [userDefaults synchronize]; +} + +NSBundle *webKitLauncherBundle() +{ + NSString *executablePath = [NSString stringWithUTF8String:webKitAppPath]; + NSRange appLocation = [executablePath rangeOfString:@".app/" options:NSBackwardsSearch]; + NSString *appPath = [executablePath substringToIndex:appLocation.location + appLocation.length]; + return [NSBundle bundleWithPath:appPath]; +} + +extern char **_CFGetProcessPath() __attribute__((weak)); +extern OSStatus _RegisterApplication(CFDictionaryRef additionalAppInfoRef, ProcessSerialNumber* myPSN) __attribute__((weak)); + +static void poseAsWebKitApp() +{ + webKitAppPath = strdup(getenv("WebKitAppPath")); + if (!webKitAppPath) + return; + + unsetenv("WebKitAppPath"); + + // Set up the main bundle early so it points at Safari.app + CFBundleGetMainBundle(); + + if (systemVersion() < 0x1060) { + if (!_CFGetProcessPath) + return; + + // Fiddle with CoreFoundation to have it pick up the executable path as being within WebKit.app + char **processPath = _CFGetProcessPath(); + *processPath = NULL; + setenv("CFProcessPath", webKitAppPath, 1); + _CFGetProcessPath(); + unsetenv("CFProcessPath"); + } else { + if (!_RegisterApplication) + return; + + // Register the application with LaunchServices, passing a customized registration dictionary that + // uses the WebKit launcher as the application bundle. + NSBundle *bundle = webKitLauncherBundle(); + NSMutableDictionary *checkInDictionary = [[bundle infoDictionary] mutableCopy]; + [checkInDictionary setObject:[bundle bundlePath] forKey:@"LSBundlePath"]; + [checkInDictionary setObject:[checkInDictionary objectForKey:(NSString *)kCFBundleNameKey] forKey:@"LSDisplayName"]; + _RegisterApplication((CFDictionaryRef)checkInDictionary, 0); + [checkInDictionary release]; + } +} + +static BOOL insideSafari4OnTigerTrampoline() +{ + // If we're not on Tiger then we can't be in the trampoline state. + if ((systemVersion() & 0xFFF0) != 0x1040) + return NO; + + // If we're running Safari < 4.0 then we can't be in the trampoline state. + CFBundleRef safariBundle = CFBundleGetMainBundle(); + CFStringRef safariVersion = CFBundleGetValueForInfoDictionaryKey(safariBundle, CFSTR("CFBundleShortVersionString")); + if ([(NSString *)safariVersion intValue] < 4) + return NO; + + const char* frameworkPath = getenv("DYLD_FRAMEWORK_PATH"); + if (!frameworkPath) + frameworkPath = ""; + + // If the framework search path is empty or otherwise does not contain the Safari + // framework's Frameworks directory then we are in the trampoline state. + const char safariFrameworkSearchPath[] = "/System/Library/PrivateFrameworks/Safari.framework/Frameworks"; + return strstr(frameworkPath, safariFrameworkSearchPath) == 0; +} + +static void enableWebKitNightlyBehaviour() +{ + // If we're inside Safari in its trampoline state, it will very shortly relaunch itself. + // We bail out here so that we'll be called again in the freshly-launched Safari process. + if (insideSafari4OnTigerTrampoline()) + return; + + NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; + + unsetenv("DYLD_INSERT_LIBRARIES"); + poseAsWebKitApp(); + + extensionPaths = [[NSSet alloc] initWithObjects:@"~/Library/InputManagers/", @"/Library/InputManagers/", + @"~/Library/Application Support/SIMBL/Plugins/", @"/Library/Application Support/SIMBL/Plugins/", + @"~/Library/Application Enhancers/", @"/Library/Application Enhancers/", + nil]; + + // As of 2008-11 attempting to load Saft would cause a crash on launch, so prevent it from being loaded. + NSArray *disabledInputManagers = [NSArray arrayWithObjects:@"Saft", nil]; + + NSUserDefaults *userDefaults = [NSUserDefaults standardUserDefaults]; + NSDictionary *defaultPrefs = [NSDictionary dictionaryWithObjectsAndKeys:[NSNumber numberWithBool:RunStateShutDown], WKNERunState, + [NSNumber numberWithBool:YES], WKNEShouldMonitorShutdowns, + disabledInputManagers, @"NSDisabledInputManagers", nil]; + [userDefaults registerDefaults:defaultPrefs]; + if ([userDefaults boolForKey:WKNEShouldMonitorShutdowns]) { + WKNERunStates savedState = (WKNERunStates)[userDefaults integerForKey:WKNERunState]; + if (savedState == RunStateInitializing) { + // Use CoreFoundation here as AppKit hasn't been initialized at this stage of Safari's lifetime + CFOptionFlags responseFlags; + CFUserNotificationDisplayAlert(0, kCFUserNotificationCautionAlertLevel, + NULL, NULL, NULL, + CFSTR("WebKit failed to open correctly"), + CFSTR("WebKit failed to open correctly on your previous attempt. Please disable any Safari extensions that you may have installed. If the problem continues to occur, please file a bug report at http://webkit.org/quality/reporting.html"), + CFSTR("Continue"), NULL, NULL, &responseFlags); + } + else if (savedState == RunStateRunning) { + NSLog(@"WebKit failed to shut down cleanly. Checking for Safari extensions."); + CFNotificationCenterAddObserver(CFNotificationCenterGetLocalCenter(), &myBundleDidLoad, + myBundleDidLoad, (CFStringRef) NSBundleDidLoadNotification, + NULL, CFNotificationSuspensionBehaviorDeliverImmediately); + } + } + [userDefaults setInteger:RunStateInitializing forKey:WKNERunState]; + [userDefaults synchronize]; + + CFNotificationCenterAddObserver(CFNotificationCenterGetLocalCenter(), &myApplicationWillFinishLaunching, + myApplicationWillFinishLaunching, (CFStringRef) NSApplicationWillFinishLaunchingNotification, + NULL, CFNotificationSuspensionBehaviorDeliverImmediately); + CFNotificationCenterAddObserver(CFNotificationCenterGetLocalCenter(), &myApplicationWillTerminate, + myApplicationWillTerminate, (CFStringRef) NSApplicationWillTerminateNotification, + NULL, CFNotificationSuspensionBehaviorDeliverImmediately); + + NSLog(@"WebKit %@ initialized.", [webKitLauncherBundle() objectForInfoDictionaryKey:@"CFBundleShortVersionString"]); + + [pool release]; +}