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