|
1 /* |
|
2 * Copyright (C) 2006, 2007, 2008, 2009 Apple Inc. All rights reserved. |
|
3 * Copyright (C) 2006 Graham Dennis. All rights reserved. |
|
4 * |
|
5 * Redistribution and use in source and binary forms, with or without |
|
6 * modification, are permitted provided that the following conditions |
|
7 * are met: |
|
8 * |
|
9 * 1. Redistributions of source code must retain the above copyright |
|
10 * notice, this list of conditions and the following disclaimer. |
|
11 * 2. Redistributions in binary form must reproduce the above copyright |
|
12 * notice, this list of conditions and the following disclaimer in the |
|
13 * documentation and/or other materials provided with the distribution. |
|
14 * 3. Neither the name of Apple Inc. ("Apple") nor the names of |
|
15 * its contributors may be used to endorse or promote products derived |
|
16 * from this software without specific prior written permission. |
|
17 * |
|
18 * THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY |
|
19 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED |
|
20 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE |
|
21 * DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY |
|
22 * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES |
|
23 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; |
|
24 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND |
|
25 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT |
|
26 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF |
|
27 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
|
28 */ |
|
29 |
|
30 #import <Cocoa/Cocoa.h> |
|
31 #import "WebKitNightlyEnablerSparkle.h" |
|
32 |
|
33 static void enableWebKitNightlyBehaviour() __attribute__ ((constructor)); |
|
34 |
|
35 static NSString *WKNERunState = @"WKNERunState"; |
|
36 static NSString *WKNEShouldMonitorShutdowns = @"WKNEShouldMonitorShutdowns"; |
|
37 |
|
38 typedef enum { |
|
39 RunStateShutDown, |
|
40 RunStateInitializing, |
|
41 RunStateRunning |
|
42 } WKNERunStates; |
|
43 |
|
44 static char *webKitAppPath; |
|
45 static bool extensionBundlesWereLoaded = NO; |
|
46 static NSSet *extensionPaths = nil; |
|
47 |
|
48 static int32_t systemVersion() |
|
49 { |
|
50 static SInt32 version = 0; |
|
51 if (!version) |
|
52 Gestalt(gestaltSystemVersion, &version); |
|
53 |
|
54 return version; |
|
55 } |
|
56 |
|
57 |
|
58 static void myBundleDidLoad(CFNotificationCenterRef center, void *observer, CFStringRef name, const void *object, CFDictionaryRef userInfo) |
|
59 { |
|
60 NSBundle *bundle = (NSBundle *)object; |
|
61 NSString *bundlePath = [[bundle bundlePath] stringByAbbreviatingWithTildeInPath]; |
|
62 NSString *bundleFileName = [bundlePath lastPathComponent]; |
|
63 |
|
64 // Explicitly ignore SIMBL.bundle, as its only purpose is to load extensions |
|
65 // on a per-application basis. It's presence indicates a user has application |
|
66 // extensions, but not that any will be loaded into Safari |
|
67 if ([bundleFileName isEqualToString:@"SIMBL.bundle"]) |
|
68 return; |
|
69 |
|
70 // If the bundle lives inside a known extension path, flag it as an extension |
|
71 NSEnumerator *e = [extensionPaths objectEnumerator]; |
|
72 NSString *path = nil; |
|
73 while ((path = [e nextObject])) { |
|
74 if ([bundlePath length] < [path length]) |
|
75 continue; |
|
76 |
|
77 if ([[bundlePath substringToIndex:[path length]] isEqualToString:path]) { |
|
78 NSLog(@"Extension detected: %@", bundlePath); |
|
79 extensionBundlesWereLoaded = YES; |
|
80 break; |
|
81 } |
|
82 } |
|
83 } |
|
84 |
|
85 static void myApplicationWillFinishLaunching(CFNotificationCenterRef center, void *observer, CFStringRef name, const void *object, CFDictionaryRef userInfo) |
|
86 { |
|
87 CFNotificationCenterRemoveObserver(CFNotificationCenterGetLocalCenter(), &myApplicationWillFinishLaunching, NULL, NULL); |
|
88 CFNotificationCenterRemoveObserver(CFNotificationCenterGetLocalCenter(), &myBundleDidLoad, NULL, NULL); |
|
89 [extensionPaths release]; |
|
90 extensionPaths = nil; |
|
91 |
|
92 NSUserDefaults *userDefaults = [NSUserDefaults standardUserDefaults]; |
|
93 [userDefaults setInteger:RunStateRunning forKey:WKNERunState]; |
|
94 [userDefaults synchronize]; |
|
95 |
|
96 if (extensionBundlesWereLoaded) |
|
97 NSRunInformationalAlertPanel(@"Safari extensions detected", |
|
98 @"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", |
|
99 nil, nil); |
|
100 |
|
101 initializeSparkle(); |
|
102 } |
|
103 |
|
104 static void myApplicationWillTerminate(CFNotificationCenterRef center, void *observer, CFStringRef name, const void *object, CFDictionaryRef userInfo) |
|
105 { |
|
106 NSUserDefaults *userDefaults = [NSUserDefaults standardUserDefaults]; |
|
107 [userDefaults setInteger:RunStateShutDown forKey:WKNERunState]; |
|
108 [userDefaults synchronize]; |
|
109 } |
|
110 |
|
111 NSBundle *webKitLauncherBundle() |
|
112 { |
|
113 NSString *executablePath = [NSString stringWithUTF8String:webKitAppPath]; |
|
114 NSRange appLocation = [executablePath rangeOfString:@".app/" options:NSBackwardsSearch]; |
|
115 NSString *appPath = [executablePath substringToIndex:appLocation.location + appLocation.length]; |
|
116 return [NSBundle bundleWithPath:appPath]; |
|
117 } |
|
118 |
|
119 extern char **_CFGetProcessPath() __attribute__((weak)); |
|
120 extern OSStatus _RegisterApplication(CFDictionaryRef additionalAppInfoRef, ProcessSerialNumber* myPSN) __attribute__((weak)); |
|
121 |
|
122 static void poseAsWebKitApp() |
|
123 { |
|
124 webKitAppPath = strdup(getenv("WebKitAppPath")); |
|
125 if (!webKitAppPath) |
|
126 return; |
|
127 |
|
128 unsetenv("WebKitAppPath"); |
|
129 |
|
130 // Set up the main bundle early so it points at Safari.app |
|
131 CFBundleGetMainBundle(); |
|
132 |
|
133 if (systemVersion() < 0x1060) { |
|
134 if (!_CFGetProcessPath) |
|
135 return; |
|
136 |
|
137 // Fiddle with CoreFoundation to have it pick up the executable path as being within WebKit.app |
|
138 char **processPath = _CFGetProcessPath(); |
|
139 *processPath = NULL; |
|
140 setenv("CFProcessPath", webKitAppPath, 1); |
|
141 _CFGetProcessPath(); |
|
142 unsetenv("CFProcessPath"); |
|
143 } else { |
|
144 if (!_RegisterApplication) |
|
145 return; |
|
146 |
|
147 // Register the application with LaunchServices, passing a customized registration dictionary that |
|
148 // uses the WebKit launcher as the application bundle. |
|
149 NSBundle *bundle = webKitLauncherBundle(); |
|
150 NSMutableDictionary *checkInDictionary = [[bundle infoDictionary] mutableCopy]; |
|
151 [checkInDictionary setObject:[bundle bundlePath] forKey:@"LSBundlePath"]; |
|
152 [checkInDictionary setObject:[checkInDictionary objectForKey:(NSString *)kCFBundleNameKey] forKey:@"LSDisplayName"]; |
|
153 _RegisterApplication((CFDictionaryRef)checkInDictionary, 0); |
|
154 [checkInDictionary release]; |
|
155 } |
|
156 } |
|
157 |
|
158 static BOOL insideSafari4OnTigerTrampoline() |
|
159 { |
|
160 // If we're not on Tiger then we can't be in the trampoline state. |
|
161 if ((systemVersion() & 0xFFF0) != 0x1040) |
|
162 return NO; |
|
163 |
|
164 // If we're running Safari < 4.0 then we can't be in the trampoline state. |
|
165 CFBundleRef safariBundle = CFBundleGetMainBundle(); |
|
166 CFStringRef safariVersion = CFBundleGetValueForInfoDictionaryKey(safariBundle, CFSTR("CFBundleShortVersionString")); |
|
167 if ([(NSString *)safariVersion intValue] < 4) |
|
168 return NO; |
|
169 |
|
170 const char* frameworkPath = getenv("DYLD_FRAMEWORK_PATH"); |
|
171 if (!frameworkPath) |
|
172 frameworkPath = ""; |
|
173 |
|
174 // If the framework search path is empty or otherwise does not contain the Safari |
|
175 // framework's Frameworks directory then we are in the trampoline state. |
|
176 const char safariFrameworkSearchPath[] = "/System/Library/PrivateFrameworks/Safari.framework/Frameworks"; |
|
177 return strstr(frameworkPath, safariFrameworkSearchPath) == 0; |
|
178 } |
|
179 |
|
180 static void enableWebKitNightlyBehaviour() |
|
181 { |
|
182 // If we're inside Safari in its trampoline state, it will very shortly relaunch itself. |
|
183 // We bail out here so that we'll be called again in the freshly-launched Safari process. |
|
184 if (insideSafari4OnTigerTrampoline()) |
|
185 return; |
|
186 |
|
187 NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; |
|
188 |
|
189 unsetenv("DYLD_INSERT_LIBRARIES"); |
|
190 poseAsWebKitApp(); |
|
191 |
|
192 extensionPaths = [[NSSet alloc] initWithObjects:@"~/Library/InputManagers/", @"/Library/InputManagers/", |
|
193 @"~/Library/Application Support/SIMBL/Plugins/", @"/Library/Application Support/SIMBL/Plugins/", |
|
194 @"~/Library/Application Enhancers/", @"/Library/Application Enhancers/", |
|
195 nil]; |
|
196 |
|
197 // As of 2008-11 attempting to load Saft would cause a crash on launch, so prevent it from being loaded. |
|
198 NSArray *disabledInputManagers = [NSArray arrayWithObjects:@"Saft", nil]; |
|
199 |
|
200 NSUserDefaults *userDefaults = [NSUserDefaults standardUserDefaults]; |
|
201 NSDictionary *defaultPrefs = [NSDictionary dictionaryWithObjectsAndKeys:[NSNumber numberWithBool:RunStateShutDown], WKNERunState, |
|
202 [NSNumber numberWithBool:YES], WKNEShouldMonitorShutdowns, |
|
203 disabledInputManagers, @"NSDisabledInputManagers", nil]; |
|
204 [userDefaults registerDefaults:defaultPrefs]; |
|
205 if ([userDefaults boolForKey:WKNEShouldMonitorShutdowns]) { |
|
206 WKNERunStates savedState = (WKNERunStates)[userDefaults integerForKey:WKNERunState]; |
|
207 if (savedState == RunStateInitializing) { |
|
208 // Use CoreFoundation here as AppKit hasn't been initialized at this stage of Safari's lifetime |
|
209 CFOptionFlags responseFlags; |
|
210 CFUserNotificationDisplayAlert(0, kCFUserNotificationCautionAlertLevel, |
|
211 NULL, NULL, NULL, |
|
212 CFSTR("WebKit failed to open correctly"), |
|
213 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"), |
|
214 CFSTR("Continue"), NULL, NULL, &responseFlags); |
|
215 } |
|
216 else if (savedState == RunStateRunning) { |
|
217 NSLog(@"WebKit failed to shut down cleanly. Checking for Safari extensions."); |
|
218 CFNotificationCenterAddObserver(CFNotificationCenterGetLocalCenter(), &myBundleDidLoad, |
|
219 myBundleDidLoad, (CFStringRef) NSBundleDidLoadNotification, |
|
220 NULL, CFNotificationSuspensionBehaviorDeliverImmediately); |
|
221 } |
|
222 } |
|
223 [userDefaults setInteger:RunStateInitializing forKey:WKNERunState]; |
|
224 [userDefaults synchronize]; |
|
225 |
|
226 CFNotificationCenterAddObserver(CFNotificationCenterGetLocalCenter(), &myApplicationWillFinishLaunching, |
|
227 myApplicationWillFinishLaunching, (CFStringRef) NSApplicationWillFinishLaunchingNotification, |
|
228 NULL, CFNotificationSuspensionBehaviorDeliverImmediately); |
|
229 CFNotificationCenterAddObserver(CFNotificationCenterGetLocalCenter(), &myApplicationWillTerminate, |
|
230 myApplicationWillTerminate, (CFStringRef) NSApplicationWillTerminateNotification, |
|
231 NULL, CFNotificationSuspensionBehaviorDeliverImmediately); |
|
232 |
|
233 NSLog(@"WebKit %@ initialized.", [webKitLauncherBundle() objectForInfoDictionaryKey:@"CFBundleShortVersionString"]); |
|
234 |
|
235 [pool release]; |
|
236 } |