1/* 2 * Copyright (C) 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 * 1. Redistributions of source code must retain the above copyright 8 * notice, this list of conditions and the following disclaimer. 9 * 2. Redistributions in binary form must reproduce the above copyright 10 * notice, this list of conditions and the following disclaimer in the 11 * documentation and/or other materials provided with the distribution. 12 * 13 * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY 14 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 15 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 16 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR 17 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, 18 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, 19 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR 20 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY 21 * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 22 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 23 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 24 */ 25 26#if !ENABLE_SPARKLE 27 28void initializeSparkle() 29{ 30 // No-op. 31} 32 33#else // ENABLE_SPARKLE 34 35#import <Cocoa/Cocoa.h> 36#import <Sparkle/SUUpdater.h> 37#import <objc/objc-runtime.h> 38#import "WebKitNightlyEnabler.h" 39 40// We need to tweak the wording of the prompt to make sense in the context of WebKit and Safari. 41static NSString* updatePermissionPromptDescription(id self, SEL _cmd) 42{ 43 return @"Should WebKit automatically check for updates? You can always check for updates manually from the Safari menu."; 44} 45 46// -[SUBasicUpdateDriver downloadDidFinish:] requires that the download be served over SSL or signed 47// using a public key. We're not interested in dealing with that hassle just at the moment. 48static void skipSignatureVerificationInDownloadDidFinish(id self, SEL _cmd, id download) 49{ 50 objc_msgSend(self, @selector(extractUpdate)); 51} 52 53static NSPanel *updateAlertPanel(id updateItem, id host) 54{ 55 NSString *hostName = objc_msgSend(host, @selector(name)); 56 NSPanel *panel = NSGetInformationalAlertPanel([NSString stringWithFormat:@"Would you like to download and install %@ %@ now?", hostName, objc_msgSend(updateItem, @selector(displayVersionString))], 57 [NSString stringWithFormat:@"You are currently running %@ %@.", hostName, objc_msgSend(host, @selector(displayVersion))], 58 @"Install Update", @"Skip This Version", @"Remind Me Later"); 59 NSArray *subviews = [[panel contentView] subviews]; 60 NSEnumerator *e = [subviews objectEnumerator]; 61 NSView *view; 62 while ((view = [e nextObject])) { 63 if (![view isKindOfClass:[NSButton class]]) 64 continue; 65 66 NSButton *button = (NSButton *)view; 67 [button setAction:@selector(webKitHandleButtonPress:)]; 68 if ([button tag] == NSAlertOtherReturn) 69 [button setKeyEquivalent:@"\033"]; 70 } 71 [panel center]; 72 return panel; 73} 74 75// Sparkle's udpate alert panel looks odd with the release notes hidden, so we 76// swap it out with a standard NSAlert-style panel instead. 77static id updateAlertInitForAlertPanel(id self, SEL _cmd, id updateItem, id host) 78{ 79 NSPanel *panel = updateAlertPanel(updateItem, host); 80 [panel setDelegate:self]; 81 82 self = [self initWithWindow:panel]; 83 if (!self) 84 return nil; 85 86 [updateItem retain]; 87 [host retain]; 88 89 object_setInstanceVariable(self, "updateItem", (void*)updateItem); 90 object_setInstanceVariable(self, "host", (void*)host); 91 92 [self setShouldCascadeWindows:NO]; 93 94 return self; 95} 96 97@implementation NSAlert (WebKitLauncherExtensions) 98 99- (void)webKitHandleButtonPress:(id)sender 100{ 101 // We rely on the fact that NSAlertOtherReturn == -1, NSAlertAlternateReturn == 0 and NSAlertDefaultReturn == 1 102 // to map the button tag to the corresponding selector 103 SEL selectors[] = { @selector(remindMeLater:), @selector(skipThisVersion:), @selector(installUpdate:) }; 104 SEL selector = selectors[[sender tag] + 1]; 105 106 id delegate = [[sender window] delegate]; 107 objc_msgSend(delegate, selector, sender); 108} 109 110@end 111 112#if __LP64__ 113 114#define setMethodImplementation method_setImplementation 115 116#else 117 118static void setMethodImplementation(Method m, IMP imp) 119{ 120 m->method_imp = imp; 121} 122 123#endif 124 125static NSString *userAgentStringForSparkle() 126{ 127 NSBundle *safariBundle = [NSBundle mainBundle]; 128 NSString *safariVersion = [[safariBundle localizedInfoDictionary] valueForKey:@"CFBundleShortVersionString"]; 129 NSString *safariBuild = [[[safariBundle infoDictionary] valueForKey:(NSString *)kCFBundleVersionKey] substringFromIndex:1]; 130 NSString *webKitRevision = [[webKitLauncherBundle() infoDictionary] valueForKey:(NSString *)kCFBundleVersionKey]; 131 NSString *applicationName = [NSString stringWithFormat:@"Version/%@ Safari/%@ WebKitRevision/%@", safariVersion, safariBuild, webKitRevision]; 132 Class WebView = objc_lookUpClass("WebView"); 133 return objc_msgSend(WebView, @selector(_standardUserAgentWithApplicationName:), applicationName); 134} 135 136void initializeSparkle() 137{ 138 // Override some Sparkle behaviour 139 Method methodToPatch = class_getInstanceMethod(objc_getRequiredClass("SUUpdatePermissionPrompt"), @selector(promptDescription)); 140 setMethodImplementation(methodToPatch, (IMP)updatePermissionPromptDescription); 141 142 methodToPatch = class_getInstanceMethod(objc_getRequiredClass("SUBasicUpdateDriver"), @selector(downloadDidFinish:)); 143 setMethodImplementation(methodToPatch, (IMP)skipSignatureVerificationInDownloadDidFinish); 144 145 methodToPatch = class_getInstanceMethod(objc_getRequiredClass("SUUpdateAlert"), @selector(initWithAppcastItem:host:)); 146 setMethodImplementation(methodToPatch, (IMP)updateAlertInitForAlertPanel); 147 148 SUUpdater *updater = [SUUpdater updaterForBundle:webKitLauncherBundle()]; 149 [updater setUserAgentString:userAgentStringForSparkle()]; 150 151 // Find the first separator on the Safari menu… 152 NSMenu *applicationSubmenu = [[[NSApp mainMenu] itemAtIndex:0] submenu]; 153 int i = 0; 154 for (; i < [applicationSubmenu numberOfItems]; i++) { 155 if ([[applicationSubmenu itemAtIndex:i] isSeparatorItem]) 156 break; 157 } 158 159 // … and insert a menu item that can be used to manually trigger update checks. 160 NSMenuItem *updateMenuItem = [[NSMenuItem alloc] initWithTitle:@"Check for WebKit Updates…" action:@selector(checkForUpdates:) keyEquivalent:@""]; 161 [updateMenuItem setTarget:updater]; 162 [applicationSubmenu insertItem:updateMenuItem atIndex:i]; 163 [updateMenuItem release]; 164} 165 166#endif // ENABLE_SPARKLE 167