Rename WebKitTools to Tools
[WebKit-https.git] / Tools / WebKitLauncher / main.m
1 /*
2  * Copyright (C) 2006, 2007, 2008, 2009 Apple Inc.  All rights reserved.
3  *
4  * Redistribution and use in source and binary forms, with or without
5  * modification, are permitted provided that the following conditions
6  * are met:
7  *
8  * 1.  Redistributions of source code must retain the above copyright
9  *     notice, this list of conditions and the following disclaimer. 
10  * 2.  Redistributions in binary form must reproduce the above copyright
11  *     notice, this list of conditions and the following disclaimer in the
12  *     documentation and/or other materials provided with the distribution. 
13  * 3.  Neither the name of Apple Inc. ("Apple") nor the names of
14  *     its contributors may be used to endorse or promote products derived
15  *     from this software without specific prior written permission. 
16  *
17  * THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY
18  * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
19  * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
20  * DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY
21  * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
22  * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
23  * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
24  * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
25  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
26  * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
27  */
28
29 #import <Cocoa/Cocoa.h>
30 #import <CoreFoundation/CoreFoundation.h>
31
32 // We need to weak-import posix_spawn and friends as they're not available on Tiger.
33 // The BSD-level system headers do not have availability macros, so we redeclare the
34 // functions ourselves with the "weak" attribute.
35
36 #define WEAK_IMPORT __attribute__((weak))
37
38 #define POSIX_SPAWN_SETEXEC 0x0040
39 typedef void *posix_spawnattr_t;
40 typedef void *posix_spawn_file_actions_t;
41 int posix_spawnattr_init(posix_spawnattr_t *) WEAK_IMPORT;
42 int posix_spawn(pid_t * __restrict, const char * __restrict, const posix_spawn_file_actions_t *, const posix_spawnattr_t * __restrict, char *const __argv[ __restrict], char *const __envp[ __restrict]) WEAK_IMPORT;
43 int posix_spawnattr_setbinpref_np(posix_spawnattr_t * __restrict, size_t, cpu_type_t *__restrict, size_t *__restrict) WEAK_IMPORT;
44 int posix_spawnattr_setflags(posix_spawnattr_t *, short) WEAK_IMPORT;
45
46
47 static void displayErrorAndQuit(NSString *title, NSString *message)
48 {
49     NSApplicationLoad();
50     NSRunCriticalAlertPanel(title, message, @"Quit", nil, nil);
51     exit(0);
52 }
53
54 static int getLastVersionShown()
55 {
56     [[NSUserDefaults standardUserDefaults] registerDefaults:[NSDictionary dictionaryWithObject:@"-1" forKey:@"StartPageShownInVersion"]];
57     return [[NSUserDefaults standardUserDefaults] integerForKey:@"StartPageShownInVersion"];
58 }
59
60 static void saveLastVersionShown(int lastVersion)
61 {
62     [[NSUserDefaults standardUserDefaults] setInteger:lastVersion forKey:@"StartPageShownInVersion"];
63     [[NSUserDefaults standardUserDefaults] synchronize];
64 }
65
66 static NSString *getPathForStartPage()
67 {
68     return [[[NSBundle mainBundle] resourcePath] stringByAppendingPathComponent:@"start.html"];
69 }
70
71 static int getCurrentVersion()
72 {
73     return [[[[NSBundle mainBundle] infoDictionary] valueForKey:(NSString *)kCFBundleVersionKey] intValue];
74 }
75
76 static int getShowStartPageVersion()
77 {
78     return getCurrentVersion() + 1;
79 }
80
81 static BOOL startPageDisabled()
82 {
83     return [[NSUserDefaults standardUserDefaults] boolForKey:@"StartPageDisabled"];
84 }
85
86 static void addStartPageToArgumentsIfNeeded(NSMutableArray *arguments)
87 {
88     if (startPageDisabled())
89         return;
90
91     if (getLastVersionShown() < getShowStartPageVersion()) {
92         saveLastVersionShown(getCurrentVersion());
93         NSString *startPagePath = getPathForStartPage();
94         if (startPagePath)
95             [arguments addObject:startPagePath];
96     }
97 }
98
99 static cpu_type_t preferredArchitecture()
100 {
101 #if defined(__ppc__)
102     return CPU_TYPE_POWERPC;
103 #elif defined(__LP64__)
104     return CPU_TYPE_X86_64;
105 #else
106     return CPU_TYPE_X86;
107 #endif
108 }
109
110 static void myExecve(NSString *executable, NSArray *args, NSDictionary *environment)
111 {
112     char **argv = (char **)calloc(sizeof(char *), [args count] + 1);
113     char **env = (char **)calloc(sizeof(char *), [environment count] + 1);
114
115     NSEnumerator *e = [args objectEnumerator];
116     NSString *s;
117     int i = 0;
118     while ((s = [e nextObject]))
119         argv[i++] = (char *) [s UTF8String];
120
121     e = [environment keyEnumerator];
122     i = 0;
123     while ((s = [e nextObject]))
124         env[i++] = (char *) [[NSString stringWithFormat:@"%@=%@", s, [environment objectForKey:s]] UTF8String];
125
126     if (posix_spawnattr_init && posix_spawn && posix_spawnattr_setbinpref_np && posix_spawnattr_setflags) {
127         posix_spawnattr_t attr;
128         posix_spawnattr_init(&attr);
129         cpu_type_t architecturePreference[] = { preferredArchitecture(), CPU_TYPE_X86 };
130         posix_spawnattr_setbinpref_np(&attr, 2, architecturePreference, 0);
131         short flags = POSIX_SPAWN_SETEXEC;
132         posix_spawnattr_setflags(&attr, flags);
133         posix_spawn(NULL, [executable fileSystemRepresentation], NULL, &attr, argv, env);
134     } else
135         execve([executable fileSystemRepresentation], argv, env);
136 }
137
138 static NSBundle *locateSafariBundle()
139 {
140     NSArray *applicationDirectories = NSSearchPathForDirectoriesInDomains(NSApplicationDirectory, NSAllDomainsMask, YES);
141     NSEnumerator *e = [applicationDirectories objectEnumerator];
142     NSString *applicationDirectory;
143     while ((applicationDirectory = [e nextObject])) {
144         NSString *possibleSafariPath = [applicationDirectory stringByAppendingPathComponent:@"Safari.app"];
145         NSBundle *possibleSafariBundle = [NSBundle bundleWithPath:possibleSafariPath];
146         if ([[possibleSafariBundle bundleIdentifier] isEqualToString:@"com.apple.Safari"])
147             return possibleSafariBundle;
148     }
149
150     CFURLRef safariURL = nil;
151     OSStatus err = LSFindApplicationForInfo(kLSUnknownCreator, CFSTR("com.apple.Safari"), nil, nil, &safariURL);
152     if (err != noErr)
153         displayErrorAndQuit(@"Unable to locate Safari", @"Nightly builds of WebKit require Safari to run.  Please check that it is available and then try again.");
154
155     NSBundle *safariBundle = [NSBundle bundleWithPath:[(NSURL *)safariURL path]];
156     CFRelease(safariURL);
157     return safariBundle;
158 }
159
160 static NSString *currentMacOSXVersion()
161 {
162     SInt32 version;
163     if (Gestalt(gestaltSystemVersion, &version) != noErr)
164         return @"10.4";
165
166     return [NSString stringWithFormat:@"%x.%x", (version & 0xFF00) >> 8, (version & 0x00F0) >> 4];
167 }
168
169 static NSString *fallbackMacOSXVersion(NSString *systemVersion)
170 {
171     NSDictionary *fallbackVersionMap = [[NSUserDefaults standardUserDefaults] dictionaryForKey:@"FallbackSystemVersions"];
172     if (!fallbackVersionMap)
173         return nil;
174     NSString *fallbackSystemVersion = [fallbackVersionMap objectForKey:systemVersion];
175     if (!fallbackSystemVersion || ![fallbackSystemVersion isKindOfClass:[NSString class]])
176         return nil;
177     return fallbackSystemVersion;
178 }
179
180 static BOOL checkFrameworkPath(NSString *frameworkPath)
181 {
182     BOOL isDirectory = NO;
183     return [[NSFileManager defaultManager] fileExistsAtPath:frameworkPath isDirectory:&isDirectory] && isDirectory;
184 }
185
186 static BOOL checkSafariVersion(NSBundle *safariBundle)
187 {
188     NSString *safariBundleVersion = [[safariBundle infoDictionary] objectForKey:(NSString *)kCFBundleVersionKey];
189     NSString *majorComponent = [[safariBundleVersion componentsSeparatedByString:@"."] objectAtIndex:0];
190     NSString *majorVersion = [majorComponent substringFromIndex:[majorComponent length] - 3];
191     return [majorVersion intValue] >= 530;
192 }
193
194 int main(int argc, char *argv[])
195 {
196     NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
197
198     NSString *systemVersion = currentMacOSXVersion();
199     NSString *frameworkPath = [[[NSBundle mainBundle] privateFrameworksPath] stringByAppendingPathComponent:systemVersion];
200
201     BOOL frameworkPathIsUsable = checkFrameworkPath(frameworkPath);
202
203     if (!frameworkPathIsUsable) {
204         NSString *fallbackSystemVersion = fallbackMacOSXVersion(systemVersion);
205         if (fallbackSystemVersion) {
206             frameworkPath = [[[NSBundle mainBundle] privateFrameworksPath] stringByAppendingPathComponent:fallbackSystemVersion];
207             frameworkPathIsUsable = checkFrameworkPath(frameworkPath);
208         }
209     }
210
211     if (!frameworkPathIsUsable)
212         displayErrorAndQuit([NSString stringWithFormat:@"Mac OS X %@ is not supported", systemVersion],
213                             [NSString stringWithFormat:@"Nightly builds of WebKit are not supported on Mac OS X %@ at this time.", systemVersion]);
214
215     NSString *pathToEnablerLib = [[NSBundle mainBundle] pathForResource:@"WebKitNightlyEnabler" ofType:@"dylib"];
216
217     NSBundle *safariBundle = locateSafariBundle();
218     NSString *executablePath = [safariBundle executablePath];
219
220     if (!checkSafariVersion(safariBundle)) {
221         NSString *safariVersion = [[safariBundle localizedInfoDictionary] objectForKey:@"CFBundleShortVersionString"];
222         displayErrorAndQuit([NSString stringWithFormat:@"Safari %@ is not supported", safariVersion],
223                             [NSString stringWithFormat:@"Nightly builds of WebKit are not supported with Safari %@ at this time. Please update to a newer version of Safari.", safariVersion]);
224     }
225
226     if ([frameworkPath rangeOfString:@":"].location != NSNotFound ||
227         [pathToEnablerLib rangeOfString:@":"].location != NSNotFound)
228         displayErrorAndQuit(@"Unable to launch Safari",
229                             @"WebKit is located at a path containing an unsupported character.  Please move WebKit to a different location and try again.");
230
231     NSMutableArray *arguments = [NSMutableArray arrayWithObject:executablePath];
232     NSMutableDictionary *environment = [[[NSDictionary dictionaryWithObjectsAndKeys:frameworkPath, @"DYLD_FRAMEWORK_PATH", @"YES", @"WEBKIT_UNSET_DYLD_FRAMEWORK_PATH",
233                                                                                     pathToEnablerLib, @"DYLD_INSERT_LIBRARIES", [[NSBundle mainBundle] executablePath], @"WebKitAppPath", nil] mutableCopy] autorelease];
234     [environment addEntriesFromDictionary:[[NSProcessInfo processInfo] environment]];
235     addStartPageToArgumentsIfNeeded(arguments);
236
237     while (*++argv)
238         [arguments addObject:[NSString stringWithUTF8String:*argv]];
239
240     myExecve(executablePath, arguments, environment);
241
242     char *error = strerror(errno);
243     NSString *errorMessage = [NSString stringWithFormat:@"Launching Safari at %@ failed with the error '%s' (%d)", [safariBundle bundlePath], error, errno];
244     displayErrorAndQuit(@"Unable to launch Safari", errorMessage);
245
246     [pool release];
247     return 0;
248 }