• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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