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 64using namespace WebCore; 65using 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 83static bool isKindOfClass(id, NSString* className); 84static void installFlip4MacPlugInWorkaroundIfNecessary(); 85 86 87static 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 312static 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) 488static 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 521static 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 559typedef void (*beginSheetModalForWindowIMP)(id, SEL, NSWindow *, id, SEL, void*); 560static beginSheetModalForWindowIMP original_NSAlert_beginSheetModalForWindow_modalDelegate_didEndSelector_contextInfo_; 561 562typedef void (*alertDidEndIMP)(id, SEL, NSAlert *, NSInteger, void*); 563static alertDidEndIMP original_TSUpdateCheck_alertDidEnd_returnCode_contextInfo_; 564 565static 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 572static 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 580static 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