Home | History | Annotate | Download | only in Plugins
      1 /*
      2  * Copyright (C) 2005, 2006 Apple Computer, 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 Computer, 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 
     30 #import "WebPluginController.h"
     31 
     32 #import "DOMNodeInternal.h"
     33 #import "WebDataSourceInternal.h"
     34 #import "WebFrameInternal.h"
     35 #import "WebFrameView.h"
     36 #import "WebHTMLViewPrivate.h"
     37 #import "WebKitErrorsPrivate.h"
     38 #import "WebKitLogging.h"
     39 #import "WebNSObjectExtras.h"
     40 #import "WebNSURLExtras.h"
     41 #import "WebNSViewExtras.h"
     42 #import "WebPlugin.h"
     43 #import "WebPluginContainer.h"
     44 #import "WebPluginContainerCheck.h"
     45 #import "WebPluginPackage.h"
     46 #import "WebPluginPrivate.h"
     47 #import "WebPluginViewFactory.h"
     48 #import "WebUIDelegate.h"
     49 #import "WebViewInternal.h"
     50 #import <Foundation/NSURLRequest.h>
     51 #import <WebCore/DocumentLoader.h>
     52 #import <WebCore/Frame.h>
     53 #import <WebCore/FrameLoader.h>
     54 #import <WebCore/HTMLMediaElement.h>
     55 #import <WebCore/HTMLNames.h>
     56 #import <WebCore/MediaPlayerProxy.h>
     57 #import <WebCore/PlatformString.h>
     58 #import <WebCore/ResourceRequest.h>
     59 #import <WebCore/ScriptController.h>
     60 #import <WebCore/WebCoreURLResponse.h>
     61 #import <objc/objc-runtime.h>
     62 #import <runtime/JSLock.h>
     63 
     64 using namespace WebCore;
     65 using namespace HTMLNames;
     66 
     67 @interface NSView (PluginSecrets)
     68 - (void)setContainingWindow:(NSWindow *)w;
     69 @end
     70 
     71 // For compatibility only.
     72 @interface NSObject (OldPluginAPI)
     73 + (NSView *)pluginViewWithArguments:(NSDictionary *)arguments;
     74 @end
     75 
     76 @interface NSView (OldPluginAPI)
     77 - (void)pluginInitialize;
     78 - (void)pluginStart;
     79 - (void)pluginStop;
     80 - (void)pluginDestroy;
     81 @end
     82 
     83 static bool isKindOfClass(id, NSString* className);
     84 static void installFlip4MacPlugInWorkaroundIfNecessary();
     85 
     86 
     87 static NSMutableSet *pluginViews = nil;
     88 
     89 @implementation WebPluginController
     90 
     91 + (NSView *)plugInViewWithArguments:(NSDictionary *)arguments fromPluginPackage:(WebPluginPackage *)pluginPackage
     92 {
     93     [pluginPackage load];
     94     Class viewFactory = [pluginPackage viewFactory];
     95 
     96     NSView *view = nil;
     97 
     98     if ([viewFactory respondsToSelector:@selector(plugInViewWithArguments:)]) {
     99         JSC::JSLock::DropAllLocks dropAllLocks(JSC::SilenceAssertionsOnly);
    100         view = [viewFactory plugInViewWithArguments:arguments];
    101     } else if ([viewFactory respondsToSelector:@selector(pluginViewWithArguments:)]) {
    102         JSC::JSLock::DropAllLocks dropAllLocks(JSC::SilenceAssertionsOnly);
    103         view = [viewFactory pluginViewWithArguments:arguments];
    104     }
    105 
    106     if (view == nil) {
    107         return nil;
    108     }
    109 
    110     if (pluginViews == nil) {
    111         pluginViews = [[NSMutableSet alloc] init];
    112     }
    113     [pluginViews addObject:view];
    114 
    115     return view;
    116 }
    117 
    118 + (BOOL)isPlugInView:(NSView *)view
    119 {
    120     return [pluginViews containsObject:view];
    121 }
    122 
    123 - (id)initWithDocumentView:(NSView *)view
    124 {
    125     [super init];
    126     _documentView = view;
    127     _views = [[NSMutableArray alloc] init];
    128     _checksInProgress = (NSMutableSet *)CFMakeCollectable(CFSetCreateMutable(NULL, 0, NULL));
    129     return self;
    130 }
    131 
    132 - (void)setDataSource:(WebDataSource *)dataSource
    133 {
    134     _dataSource = dataSource;
    135 }
    136 
    137 - (void)dealloc
    138 {
    139     [_views release];
    140     [_checksInProgress release];
    141 #if ENABLE(PLUGIN_PROXY_FOR_VIDEO)
    142     [_viewsNotInDocument release];
    143 #endif
    144     [super dealloc];
    145 }
    146 
    147 - (void)stopOnePlugin:(NSView *)view
    148 {
    149     if ([view respondsToSelector:@selector(webPlugInStop)]) {
    150         JSC::JSLock::DropAllLocks dropAllLocks(JSC::SilenceAssertionsOnly);
    151         [view webPlugInStop];
    152     } else if ([view respondsToSelector:@selector(pluginStop)]) {
    153         JSC::JSLock::DropAllLocks dropAllLocks(JSC::SilenceAssertionsOnly);
    154         [view pluginStop];
    155     }
    156 }
    157 
    158 - (void)destroyOnePlugin:(NSView *)view
    159 {
    160     if ([view respondsToSelector:@selector(webPlugInDestroy)]) {
    161         JSC::JSLock::DropAllLocks dropAllLocks(JSC::SilenceAssertionsOnly);
    162         [view webPlugInDestroy];
    163     } else if ([view respondsToSelector:@selector(pluginDestroy)]) {
    164         JSC::JSLock::DropAllLocks dropAllLocks(JSC::SilenceAssertionsOnly);
    165         [view pluginDestroy];
    166     }
    167 }
    168 
    169 - (void)startAllPlugins
    170 {
    171     if (_started)
    172         return;
    173 
    174     if ([_views count] > 0)
    175         LOG(Plugins, "starting WebKit plugins : %@", [_views description]);
    176 
    177     int count = [_views count];
    178     for (int i = 0; i < count; i++) {
    179         id aView = [_views objectAtIndex:i];
    180         if ([aView respondsToSelector:@selector(webPlugInStart)]) {
    181             JSC::JSLock::DropAllLocks dropAllLocks(JSC::SilenceAssertionsOnly);
    182             [aView webPlugInStart];
    183         } else if ([aView respondsToSelector:@selector(pluginStart)]) {
    184             JSC::JSLock::DropAllLocks dropAllLocks(JSC::SilenceAssertionsOnly);
    185             [aView pluginStart];
    186         }
    187     }
    188     _started = YES;
    189 }
    190 
    191 - (void)stopAllPlugins
    192 {
    193     if (!_started)
    194         return;
    195 
    196     if ([_views count] > 0) {
    197         LOG(Plugins, "stopping WebKit plugins: %@", [_views description]);
    198     }
    199 
    200     int viewsCount = [_views count];
    201     for (int i = 0; i < viewsCount; i++)
    202         [self stopOnePlugin:[_views objectAtIndex:i]];
    203 
    204 #if ENABLE(PLUGIN_PROXY_FOR_VIDEO)
    205     int viewsNotInDocumentCount = [_viewsNotInDocument count];
    206     for (int i = 0; i < viewsNotInDocumentCount; i++)
    207         [self stopOnePlugin:[_viewsNotInDocument objectAtIndex:i]];
    208 #endif
    209 
    210     _started = NO;
    211 }
    212 
    213 #if ENABLE(PLUGIN_PROXY_FOR_VIDEO)
    214 - (void)pluginViewCreated:(NSView *)view
    215 {
    216     if (!_viewsNotInDocument)
    217         _viewsNotInDocument= [[NSMutableArray alloc] init];
    218     if (![_viewsNotInDocument containsObject:view])
    219         [_viewsNotInDocument addObject:view];
    220 }
    221 
    222 + (void)pluginViewHidden:(NSView *)view
    223 {
    224     [pluginViews removeObject:view];
    225 }
    226 #endif
    227 
    228 - (void)addPlugin:(NSView *)view
    229 {
    230     if (!_documentView) {
    231         LOG_ERROR("can't add a plug-in to a defunct WebPluginController");
    232         return;
    233     }
    234 
    235     if (![_views containsObject:view]) {
    236         [_views addObject:view];
    237         [[_documentView _webView] addPluginInstanceView:view];
    238 
    239 #if ENABLE(PLUGIN_PROXY_FOR_VIDEO)
    240         if ([_viewsNotInDocument containsObject:view])
    241             [_viewsNotInDocument removeObject:view];
    242 #endif
    243 
    244         BOOL oldDefersCallbacks = [[self webView] defersCallbacks];
    245         if (!oldDefersCallbacks)
    246             [[self webView] setDefersCallbacks:YES];
    247 
    248         if (isKindOfClass(view, @"WmvPlugin"))
    249             installFlip4MacPlugInWorkaroundIfNecessary();
    250 
    251         LOG(Plugins, "initializing plug-in %@", view);
    252         if ([view respondsToSelector:@selector(webPlugInInitialize)]) {
    253             JSC::JSLock::DropAllLocks dropAllLocks(JSC::SilenceAssertionsOnly);
    254             [view webPlugInInitialize];
    255         } else if ([view respondsToSelector:@selector(pluginInitialize)]) {
    256             JSC::JSLock::DropAllLocks dropAllLocks(JSC::SilenceAssertionsOnly);
    257             [view pluginInitialize];
    258         }
    259 
    260         if (!oldDefersCallbacks)
    261             [[self webView] setDefersCallbacks:NO];
    262 
    263         if (_started) {
    264             LOG(Plugins, "starting plug-in %@", view);
    265             if ([view respondsToSelector:@selector(webPlugInStart)]) {
    266                 JSC::JSLock::DropAllLocks dropAllLocks(JSC::SilenceAssertionsOnly);
    267                 [view webPlugInStart];
    268             } else if ([view respondsToSelector:@selector(pluginStart)]) {
    269                 JSC::JSLock::DropAllLocks dropAllLocks(JSC::SilenceAssertionsOnly);
    270                 [view pluginStart];
    271             }
    272 
    273             if ([view respondsToSelector:@selector(setContainingWindow:)]) {
    274                 JSC::JSLock::DropAllLocks dropAllLocks(JSC::SilenceAssertionsOnly);
    275                 [view setContainingWindow:[_documentView window]];
    276             }
    277         }
    278     }
    279 }
    280 
    281 - (void)destroyPlugin:(NSView *)view
    282 {
    283 #if ENABLE(PLUGIN_PROXY_FOR_VIDEO)
    284     if ([_views containsObject:view] || [_viewsNotInDocument containsObject:view]) {
    285 #else
    286     if ([_views containsObject:view]) {
    287 #endif
    288         if (_started)
    289             [self stopOnePlugin:view];
    290         [self destroyOnePlugin:view];
    291 
    292 #if ENABLE(NETSCAPE_PLUGIN_API)
    293         if (Frame* frame = core([self webFrame]))
    294             frame->script()->cleanupScriptObjectsForPlugin(self);
    295 #endif
    296 
    297         [pluginViews removeObject:view];
    298         [[_documentView _webView] removePluginInstanceView:view];
    299         [_views removeObject:view];
    300 #if ENABLE(PLUGIN_PROXY_FOR_VIDEO)
    301         [_viewsNotInDocument removeObject:view];
    302 #endif
    303     }
    304 }
    305 
    306 - (void)_webPluginContainerCancelCheckIfAllowedToLoadRequest:(id)checkIdentifier
    307 {
    308     [checkIdentifier cancel];
    309     [_checksInProgress removeObject:checkIdentifier];
    310 }
    311 
    312 static void cancelOutstandingCheck(const void *item, void *context)
    313 {
    314     [(id)item cancel];
    315 }
    316 
    317 - (void)_cancelOutstandingChecks
    318 {
    319     if (_checksInProgress) {
    320         CFSetApplyFunction((CFSetRef)_checksInProgress, cancelOutstandingCheck, NULL);
    321         [_checksInProgress release];
    322         _checksInProgress = nil;
    323     }
    324 }
    325 
    326 - (void)destroyAllPlugins
    327 {
    328     [self stopAllPlugins];
    329 
    330     if ([_views count] > 0) {
    331         LOG(Plugins, "destroying WebKit plugins: %@", [_views description]);
    332     }
    333 
    334     [self _cancelOutstandingChecks];
    335 
    336     int viewsCount = [_views count];
    337     for (int i = 0; i < viewsCount; i++) {
    338         id aView = [_views objectAtIndex:i];
    339         [self destroyOnePlugin:aView];
    340 
    341 #if ENABLE(NETSCAPE_PLUGIN_API)
    342         if (Frame* frame = core([self webFrame]))
    343             frame->script()->cleanupScriptObjectsForPlugin(self);
    344 #endif
    345 
    346         [pluginViews removeObject:aView];
    347         [[_documentView _webView] removePluginInstanceView:aView];
    348     }
    349 
    350 #if ENABLE(PLUGIN_PROXY_FOR_VIDEO)
    351     int viewsNotInDocumentCount = [_viewsNotInDocument count];
    352     for (int i = 0; i < viewsNotInDocumentCount; i++)
    353         [self destroyOnePlugin:[_viewsNotInDocument objectAtIndex:i]];
    354 #endif
    355 
    356     [_views makeObjectsPerformSelector:@selector(removeFromSuperviewWithoutNeedingDisplay)];
    357     [_views release];
    358     _views = nil;
    359 
    360     _documentView = nil;
    361 }
    362 
    363 - (id)_webPluginContainerCheckIfAllowedToLoadRequest:(NSURLRequest *)request inFrame:(NSString *)target resultObject:(id)obj selector:(SEL)selector
    364 {
    365     WebPluginContainerCheck *check = [WebPluginContainerCheck checkWithRequest:request target:target resultObject:obj selector:selector controller:self contextInfo:nil];
    366     [_checksInProgress addObject:check];
    367     [check start];
    368 
    369     return check;
    370 }
    371 
    372 - (void)webPlugInContainerLoadRequest:(NSURLRequest *)request inFrame:(NSString *)target
    373 {
    374     if (!request) {
    375         LOG_ERROR("nil URL passed");
    376         return;
    377     }
    378     if (!_documentView) {
    379         LOG_ERROR("could not load URL %@ because plug-in has already been destroyed", request);
    380         return;
    381     }
    382     WebFrame *frame = [_dataSource webFrame];
    383     if (!frame) {
    384         LOG_ERROR("could not load URL %@ because plug-in has already been stopped", request);
    385         return;
    386     }
    387     if (!target) {
    388         target = @"_top";
    389     }
    390     NSString *JSString = [[request URL] _webkit_scriptIfJavaScriptURL];
    391     if (JSString) {
    392         if ([frame findFrameNamed:target] != frame) {
    393             LOG_ERROR("JavaScript requests can only be made on the frame that contains the plug-in");
    394             return;
    395         }
    396         [frame _stringByEvaluatingJavaScriptFromString:JSString];
    397     } else {
    398         if (!request) {
    399             LOG_ERROR("could not load URL %@", [request URL]);
    400             return;
    401         }
    402         core(frame)->loader()->load(request, target, false);
    403     }
    404 }
    405 
    406 - (void)webPlugInContainerShowStatus:(NSString *)message
    407 {
    408     if (!message)
    409         message = @"";
    410 
    411     WebView *v = [_dataSource _webView];
    412     [[v _UIDelegateForwarder] webView:v setStatusText:message];
    413 }
    414 
    415 // For compatibility only.
    416 - (void)showStatus:(NSString *)message
    417 {
    418     [self webPlugInContainerShowStatus:message];
    419 }
    420 
    421 - (NSColor *)webPlugInContainerSelectionColor
    422 {
    423     bool primary = true;
    424     if (Frame* frame = core([self webFrame]))
    425         primary = frame->selection()->isFocusedAndActive();
    426     return primary ? [NSColor selectedTextBackgroundColor] : [NSColor secondarySelectedControlColor];
    427 }
    428 
    429 // For compatibility only.
    430 - (NSColor *)selectionColor
    431 {
    432     return [self webPlugInContainerSelectionColor];
    433 }
    434 
    435 - (WebFrame *)webFrame
    436 {
    437     return [_dataSource webFrame];
    438 }
    439 
    440 - (WebView *)webView
    441 {
    442     return [[self webFrame] webView];
    443 }
    444 
    445 - (NSString *)URLPolicyCheckReferrer
    446 {
    447     NSURL *responseURL = [[[[self webFrame] _dataSource] response] URL];
    448     ASSERT(responseURL);
    449     return [responseURL _web_originalDataAsString];
    450 }
    451 
    452 - (void)pluginView:(NSView *)pluginView receivedResponse:(NSURLResponse *)response
    453 {
    454     if ([pluginView respondsToSelector:@selector(webPlugInMainResourceDidReceiveResponse:)])
    455         [pluginView webPlugInMainResourceDidReceiveResponse:response];
    456     else {
    457         // Cancel the load since this plug-in does its own loading.
    458         // FIXME: See <rdar://problem/4258008> for a problem with this.
    459         NSError *error = [[NSError alloc] _initWithPluginErrorCode:WebKitErrorPlugInWillHandleLoad
    460                                                         contentURL:[response URL]
    461                                                      pluginPageURL:nil
    462                                                         pluginName:nil // FIXME: Get this from somewhere
    463                                                           MIMEType:[response MIMEType]];
    464         [_dataSource _documentLoader]->cancelMainResourceLoad(error);
    465         [error release];
    466     }
    467 }
    468 
    469 - (void)pluginView:(NSView *)pluginView receivedData:(NSData *)data
    470 {
    471     if ([pluginView respondsToSelector:@selector(webPlugInMainResourceDidReceiveData:)])
    472         [pluginView webPlugInMainResourceDidReceiveData:data];
    473 }
    474 
    475 - (void)pluginView:(NSView *)pluginView receivedError:(NSError *)error
    476 {
    477     if ([pluginView respondsToSelector:@selector(webPlugInMainResourceDidFailWithError:)])
    478         [pluginView webPlugInMainResourceDidFailWithError:error];
    479 }
    480 
    481 - (void)pluginViewFinishedLoading:(NSView *)pluginView
    482 {
    483     if ([pluginView respondsToSelector:@selector(webPlugInMainResourceDidFinishLoading)])
    484         [pluginView webPlugInMainResourceDidFinishLoading];
    485 }
    486 
    487 #if ENABLE(PLUGIN_PROXY_FOR_VIDEO)
    488 static WebCore::HTMLMediaElement* mediaProxyClient(DOMElement* element)
    489 {
    490     if (!element) {
    491         LOG_ERROR("nil element passed");
    492         return nil;
    493     }
    494 
    495     Element* node = core(element);
    496     if (!node || (!node->hasTagName(HTMLNames::videoTag) && !node->hasTagName(HTMLNames::audioTag))) {
    497         LOG_ERROR("invalid media element passed");
    498         return nil;
    499     }
    500 
    501     return static_cast<WebCore::HTMLMediaElement*>(node);
    502 }
    503 
    504 - (void)_webPluginContainerSetMediaPlayerProxy:(WebMediaPlayerProxy *)proxy forElement:(DOMElement *)element
    505 {
    506     WebCore::HTMLMediaElement* client = mediaProxyClient(element);
    507     if (client)
    508         client->setMediaPlayerProxy(proxy);
    509 }
    510 
    511 - (void)_webPluginContainerPostMediaPlayerNotification:(int)notification forElement:(DOMElement *)element
    512 {
    513     WebCore::HTMLMediaElement* client = mediaProxyClient(element);
    514     if (client)
    515         client->deliverNotification((MediaPlayerProxyNotificationType)notification);
    516 }
    517 #endif
    518 
    519 @end
    520 
    521 static bool isKindOfClass(id object, NSString *className)
    522 {
    523     Class cls = NSClassFromString(className);
    524 
    525     if (!cls)
    526         return false;
    527 
    528     return [object isKindOfClass:cls];
    529 }
    530 
    531 
    532 // Existing versions of the Flip4Mac WebKit plug-in have an object lifetime bug related to an NSAlert that is
    533 // used to notify the user about updates to the plug-in. This bug can result in Safari crashing if the page
    534 // containing the plug-in navigates while the alert is displayed (<rdar://problem/7313430>).
    535 //
    536 // The gist of the bug is thus: Flip4Mac sets an instance of the TSUpdateCheck class as the modal delegate of the
    537 // NSAlert instance. This TSUpdateCheck instance itself has a delegate. The delegate is set to the WmvPlugin
    538 // instance which is the NSView subclass that is exposed to WebKit as the plug-in view. Since this relationship
    539 // is that of delegates the TSUpdateCheck does not retain the WmvPlugin. This leads to a bug if the WmvPlugin
    540 // instance is destroyed before the TSUpdateCheck instance as the TSUpdateCheck instance will be left with a
    541 // pointer to a stale object. This will happen if a page containing the Flip4Mac plug-in triggers a navigation
    542 // while the update sheet is visible as the WmvPlugin instance is removed from the view hierarchy and there are
    543 // no other references to keep the object alive.
    544 //
    545 // We work around this bug by patching the following two messages:
    546 //
    547 // 1) -[NSAlert beginSheetModalForWindow:modalDelegate:didEndSelector:contextInfo:]
    548 // 2) -[TSUpdateCheck alertDidEnd:returnCode:contextInfo:]
    549 //
    550 // Our override of 1) detects whether it is Flip4Mac's update sheet triggering the alert by checking whether the
    551 // modal delegate is an instance of TSUpdateCheck. If it is, it retains the modal delegate's delegate.
    552 //
    553 // Our override of 2) then autoreleases the delegate, balancing the retain we added in 1).
    554 //
    555 // These two overrides have the effect of ensuring that the WmvPlugin instance will always outlive the TSUpdateCheck
    556 // instance, preventing the TSUpdateCheck instance from accessing a stale delegate pointer and crashing the application.
    557 
    558 
    559 typedef void (*beginSheetModalForWindowIMP)(id, SEL, NSWindow *, id, SEL, void*);
    560 static beginSheetModalForWindowIMP original_NSAlert_beginSheetModalForWindow_modalDelegate_didEndSelector_contextInfo_;
    561 
    562 typedef void (*alertDidEndIMP)(id, SEL, NSAlert *, NSInteger, void*);
    563 static alertDidEndIMP original_TSUpdateCheck_alertDidEnd_returnCode_contextInfo_;
    564 
    565 static void WebKit_TSUpdateCheck_alertDidEnd_returnCode_contextInfo_(id object, SEL selector, NSAlert *alert, NSInteger returnCode, void* contextInfo)
    566 {
    567     [[object delegate] autorelease];
    568 
    569     original_TSUpdateCheck_alertDidEnd_returnCode_contextInfo_(object, selector, alert, returnCode, contextInfo);
    570 }
    571 
    572 static void WebKit_NSAlert_beginSheetModalForWindow_modalDelegate_didEndSelector_contextInfo_(id object, SEL selector, NSWindow *window, id modalDelegate, SEL didEndSelector, void* contextInfo)
    573 {
    574     if (isKindOfClass(modalDelegate, @"TSUpdateCheck"))
    575         [[modalDelegate delegate] retain];
    576 
    577     original_NSAlert_beginSheetModalForWindow_modalDelegate_didEndSelector_contextInfo_(object, selector, window, modalDelegate, didEndSelector, contextInfo);
    578 }
    579 
    580 static void installFlip4MacPlugInWorkaroundIfNecessary()
    581 {
    582     static bool hasInstalledFlip4MacPlugInWorkaround;
    583     if (!hasInstalledFlip4MacPlugInWorkaround) {
    584         Class TSUpdateCheck = objc_lookUpClass("TSUpdateCheck");
    585         if (!TSUpdateCheck)
    586             return;
    587 
    588         Method methodToPatch = class_getInstanceMethod(TSUpdateCheck, @selector(alertDidEnd:returnCode:contextInfo:));
    589         if (!methodToPatch)
    590             return;
    591 
    592         IMP originalMethod = method_setImplementation(methodToPatch, reinterpret_cast<IMP>(WebKit_TSUpdateCheck_alertDidEnd_returnCode_contextInfo_));
    593         original_TSUpdateCheck_alertDidEnd_returnCode_contextInfo_ = reinterpret_cast<alertDidEndIMP>(originalMethod);
    594 
    595         methodToPatch = class_getInstanceMethod(objc_getRequiredClass("NSAlert"), @selector(beginSheetModalForWindow:modalDelegate:didEndSelector:contextInfo:));
    596         originalMethod = method_setImplementation(methodToPatch, reinterpret_cast<IMP>(WebKit_NSAlert_beginSheetModalForWindow_modalDelegate_didEndSelector_contextInfo_));
    597         original_NSAlert_beginSheetModalForWindow_modalDelegate_didEndSelector_contextInfo_ = reinterpret_cast<beginSheetModalForWindowIMP>(originalMethod);
    598 
    599         hasInstalledFlip4MacPlugInWorkaround = true;
    600     }
    601 }
    602