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