Home | History | Annotate | Download | only in WebKitLauncher
      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 
     28 void 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.
     41 static 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 static NSPanel *updateAlertPanel(id updateItem, id host)
     47 {
     48     NSString *hostName = objc_msgSend(host, @selector(name));
     49     NSPanel *panel = NSGetInformationalAlertPanel([NSString stringWithFormat:@"Would you like to download and install %@ %@ now?", hostName, objc_msgSend(updateItem, @selector(displayVersionString))],
     50                                                   [NSString stringWithFormat:@"You are currently running %@ %@.", hostName, objc_msgSend(host, @selector(displayVersion))],
     51                                                   @"Install Update", @"Skip This Version", @"Remind Me Later");
     52     NSArray *subviews = [[panel contentView] subviews];
     53     NSEnumerator *e = [subviews objectEnumerator];
     54     NSView *view;
     55     while ((view = [e nextObject])) {
     56         if (![view isKindOfClass:[NSButton class]])
     57             continue;
     58 
     59         NSButton *button = (NSButton *)view;
     60         [button setAction:@selector(webKitHandleButtonPress:)];
     61         if ([button tag] == NSAlertOtherReturn)
     62             [button setKeyEquivalent:@"\033"];
     63     }
     64     [panel center];
     65     return panel;
     66 }
     67 
     68 // Sparkle's udpate alert panel looks odd with the release notes hidden, so we
     69 // swap it out with a standard NSAlert-style panel instead.
     70 static id updateAlertInitForAlertPanel(id self, SEL _cmd, id updateItem, id host)
     71 {
     72     NSPanel *panel = updateAlertPanel(updateItem, host);
     73     [panel setDelegate:self];
     74 
     75     self = [self initWithWindow:panel];
     76     if (!self)
     77         return nil;
     78 
     79     [updateItem retain];
     80     [host retain];
     81 
     82     object_setInstanceVariable(self, "updateItem", (void*)updateItem);
     83     object_setInstanceVariable(self, "host", (void*)host);
     84 
     85     [self setShouldCascadeWindows:NO];
     86 
     87     return self;
     88 }
     89 
     90 @implementation NSAlert (WebKitLauncherExtensions)
     91 
     92 - (void)webKitHandleButtonPress:(id)sender
     93 {
     94     // We rely on the fact that NSAlertOtherReturn == -1, NSAlertAlternateReturn == 0 and NSAlertDefaultReturn == 1
     95     // to map the button tag to the corresponding selector
     96     SEL selectors[] = { @selector(remindMeLater:), @selector(skipThisVersion:), @selector(installUpdate:) };
     97     SEL selector = selectors[[sender tag] + 1];
     98 
     99     id delegate = [[sender window] delegate];
    100     objc_msgSend(delegate, selector, sender);
    101 }
    102 
    103 @end
    104 
    105 #if __LP64__
    106 
    107 #define setMethodImplementation method_setImplementation
    108 
    109 #else
    110 
    111 static void setMethodImplementation(Method m, IMP imp)
    112 {
    113     m->method_imp = imp;
    114 }
    115 
    116 #endif
    117 
    118 static NSString *userAgentStringForSparkle()
    119 {
    120     NSBundle *safariBundle = [NSBundle mainBundle];
    121     NSString *safariVersion = [[safariBundle localizedInfoDictionary] valueForKey:@"CFBundleShortVersionString"];
    122     NSString *safariBuild = [[[safariBundle infoDictionary] valueForKey:(NSString *)kCFBundleVersionKey] substringFromIndex:1];
    123     NSString *webKitRevision = [[webKitLauncherBundle() infoDictionary] valueForKey:(NSString *)kCFBundleVersionKey];
    124     NSString *applicationName = [NSString stringWithFormat:@"Version/%@ Safari/%@ WebKitRevision/%@", safariVersion, safariBuild, webKitRevision];
    125     Class WebView = objc_lookUpClass("WebView");
    126     return objc_msgSend(WebView, @selector(_standardUserAgentWithApplicationName:), applicationName);
    127 }
    128 
    129 void initializeSparkle()
    130 {
    131     // Override some Sparkle behaviour
    132     Method methodToPatch = class_getInstanceMethod(objc_getRequiredClass("SUUpdatePermissionPrompt"), @selector(promptDescription));
    133     setMethodImplementation(methodToPatch, (IMP)updatePermissionPromptDescription);
    134 
    135     methodToPatch = class_getInstanceMethod(objc_getRequiredClass("SUUpdateAlert"), @selector(initWithAppcastItem:host:));
    136     setMethodImplementation(methodToPatch, (IMP)updateAlertInitForAlertPanel);
    137 
    138     SUUpdater *updater = [SUUpdater updaterForBundle:webKitLauncherBundle()];
    139     [updater setUserAgentString:userAgentStringForSparkle()];
    140 
    141     // Find the first separator on the Safari menu
    142     NSMenu *applicationSubmenu = [[[NSApp mainMenu] itemAtIndex:0] submenu];
    143     int i = 0;
    144     for (; i < [applicationSubmenu numberOfItems]; i++) {
    145         if ([[applicationSubmenu itemAtIndex:i] isSeparatorItem])
    146             break;
    147     }
    148 
    149     //  and insert a menu item that can be used to manually trigger update checks.
    150     NSMenuItem *updateMenuItem = [[NSMenuItem alloc] initWithTitle:@"Check for WebKit Updates" action:@selector(checkForUpdates:) keyEquivalent:@""];
    151     [updateMenuItem setTarget:updater];
    152     [applicationSubmenu insertItem:updateMenuItem atIndex:i];
    153     [updateMenuItem release];
    154 }
    155 
    156 #endif // ENABLE_SPARKLE
    157