diff -r 000000000000 -r 4f2f89ce4247 WebKit/mac/Plugins/WebPluginController.mm --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/WebKit/mac/Plugins/WebPluginController.mm Fri Sep 17 09:02:29 2010 +0300 @@ -0,0 +1,601 @@ +/* + * Copyright (C) 2005, 2006 Apple Computer, Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of Apple Computer, Inc. ("Apple") nor the names of + * its contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + + +#import "WebPluginController.h" + +#import "DOMNodeInternal.h" +#import "WebDataSourceInternal.h" +#import "WebFrameInternal.h" +#import "WebFrameView.h" +#import "WebHTMLViewPrivate.h" +#import "WebKitErrorsPrivate.h" +#import "WebKitLogging.h" +#import "WebNSObjectExtras.h" +#import "WebNSURLExtras.h" +#import "WebNSViewExtras.h" +#import "WebPlugin.h" +#import "WebPluginContainer.h" +#import "WebPluginContainerCheck.h" +#import "WebPluginPackage.h" +#import "WebPluginPrivate.h" +#import "WebPluginViewFactory.h" +#import "WebUIDelegate.h" +#import "WebViewInternal.h" +#import +#import +#import +#import +#import +#import +#import +#import +#import +#import +#import +#import +#import + +using namespace WebCore; +using namespace HTMLNames; + +@interface NSView (PluginSecrets) +- (void)setContainingWindow:(NSWindow *)w; +@end + +// For compatibility only. +@interface NSObject (OldPluginAPI) ++ (NSView *)pluginViewWithArguments:(NSDictionary *)arguments; +@end + +@interface NSView (OldPluginAPI) +- (void)pluginInitialize; +- (void)pluginStart; +- (void)pluginStop; +- (void)pluginDestroy; +@end + +static bool isKindOfClass(id, NSString* className); +static void installFlip4MacPlugInWorkaroundIfNecessary(); + + +static NSMutableSet *pluginViews = nil; + +@implementation WebPluginController + ++ (NSView *)plugInViewWithArguments:(NSDictionary *)arguments fromPluginPackage:(WebPluginPackage *)pluginPackage +{ + [pluginPackage load]; + Class viewFactory = [pluginPackage viewFactory]; + + NSView *view = nil; + + if ([viewFactory respondsToSelector:@selector(plugInViewWithArguments:)]) { + JSC::JSLock::DropAllLocks dropAllLocks(JSC::SilenceAssertionsOnly); + view = [viewFactory plugInViewWithArguments:arguments]; + } else if ([viewFactory respondsToSelector:@selector(pluginViewWithArguments:)]) { + JSC::JSLock::DropAllLocks dropAllLocks(JSC::SilenceAssertionsOnly); + view = [viewFactory pluginViewWithArguments:arguments]; + } + + if (view == nil) { + return nil; + } + + if (pluginViews == nil) { + pluginViews = [[NSMutableSet alloc] init]; + } + [pluginViews addObject:view]; + + return view; +} + ++ (BOOL)isPlugInView:(NSView *)view +{ + return [pluginViews containsObject:view]; +} + +- (id)initWithDocumentView:(NSView *)view +{ + [super init]; + _documentView = view; + _views = [[NSMutableArray alloc] init]; + _checksInProgress = (NSMutableSet *)CFMakeCollectable(CFSetCreateMutable(NULL, 0, NULL)); + return self; +} + +- (void)setDataSource:(WebDataSource *)dataSource +{ + _dataSource = dataSource; +} + +- (void)dealloc +{ + [_views release]; + [_checksInProgress release]; +#if ENABLE(PLUGIN_PROXY_FOR_VIDEO) + [_viewsNotInDocument release]; +#endif + [super dealloc]; +} + +- (void)stopOnePlugin:(NSView *)view +{ + if ([view respondsToSelector:@selector(webPlugInStop)]) { + JSC::JSLock::DropAllLocks dropAllLocks(JSC::SilenceAssertionsOnly); + [view webPlugInStop]; + } else if ([view respondsToSelector:@selector(pluginStop)]) { + JSC::JSLock::DropAllLocks dropAllLocks(JSC::SilenceAssertionsOnly); + [view pluginStop]; + } +} + +- (void)destroyOnePlugin:(NSView *)view +{ + if ([view respondsToSelector:@selector(webPlugInDestroy)]) { + JSC::JSLock::DropAllLocks dropAllLocks(JSC::SilenceAssertionsOnly); + [view webPlugInDestroy]; + } else if ([view respondsToSelector:@selector(pluginDestroy)]) { + JSC::JSLock::DropAllLocks dropAllLocks(JSC::SilenceAssertionsOnly); + [view pluginDestroy]; + } +} + +- (void)startAllPlugins +{ + if (_started) + return; + + if ([_views count] > 0) + LOG(Plugins, "starting WebKit plugins : %@", [_views description]); + + int i, count = [_views count]; + for (i = 0; i < count; i++) { + id aView = [_views objectAtIndex:i]; + if ([aView respondsToSelector:@selector(webPlugInStart)]) { + JSC::JSLock::DropAllLocks dropAllLocks(JSC::SilenceAssertionsOnly); + [aView webPlugInStart]; + } else if ([aView respondsToSelector:@selector(pluginStart)]) { + JSC::JSLock::DropAllLocks dropAllLocks(JSC::SilenceAssertionsOnly); + [aView pluginStart]; + } + } + _started = YES; +} + +- (void)stopAllPlugins +{ + if (!_started) + return; + + if ([_views count] > 0) { + LOG(Plugins, "stopping WebKit plugins: %@", [_views description]); + } + + int i, count = [_views count]; + for (i = 0; i < count; i++) + [self stopOnePlugin:[_views objectAtIndex:i]]; + +#if ENABLE(PLUGIN_PROXY_FOR_VIDEO) + count = [_viewsNotInDocument count]; + for (i = 0; i < count; i++) + [self stopOnePlugin:[_viewsNotInDocument objectAtIndex:i]]; +#endif + + _started = NO; +} + +#if ENABLE(PLUGIN_PROXY_FOR_VIDEO) +- (void)pluginViewCreated:(NSView *)view +{ + if (!_viewsNotInDocument) + _viewsNotInDocument= [[NSMutableArray alloc] init]; + if (![_viewsNotInDocument containsObject:view]) + [_viewsNotInDocument addObject:view]; +} + ++ (void)pluginViewHidden:(NSView *)view +{ + [pluginViews removeObject:view]; +} +#endif + +- (void)addPlugin:(NSView *)view +{ + if (!_documentView) { + LOG_ERROR("can't add a plug-in to a defunct WebPluginController"); + return; + } + + if (![_views containsObject:view]) { + [_views addObject:view]; + [[_documentView _webView] addPluginInstanceView:view]; + +#if ENABLE(PLUGIN_PROXY_FOR_VIDEO) + if ([_viewsNotInDocument containsObject:view]) + [_viewsNotInDocument removeObject:view]; +#endif + + BOOL oldDefersCallbacks = [[self webView] defersCallbacks]; + if (!oldDefersCallbacks) + [[self webView] setDefersCallbacks:YES]; + + if (isKindOfClass(view, @"WmvPlugin")) + installFlip4MacPlugInWorkaroundIfNecessary(); + + LOG(Plugins, "initializing plug-in %@", view); + if ([view respondsToSelector:@selector(webPlugInInitialize)]) { + JSC::JSLock::DropAllLocks dropAllLocks(JSC::SilenceAssertionsOnly); + [view webPlugInInitialize]; + } else if ([view respondsToSelector:@selector(pluginInitialize)]) { + JSC::JSLock::DropAllLocks dropAllLocks(JSC::SilenceAssertionsOnly); + [view pluginInitialize]; + } + + if (!oldDefersCallbacks) + [[self webView] setDefersCallbacks:NO]; + + if (_started) { + LOG(Plugins, "starting plug-in %@", view); + if ([view respondsToSelector:@selector(webPlugInStart)]) { + JSC::JSLock::DropAllLocks dropAllLocks(JSC::SilenceAssertionsOnly); + [view webPlugInStart]; + } else if ([view respondsToSelector:@selector(pluginStart)]) { + JSC::JSLock::DropAllLocks dropAllLocks(JSC::SilenceAssertionsOnly); + [view pluginStart]; + } + + if ([view respondsToSelector:@selector(setContainingWindow:)]) { + JSC::JSLock::DropAllLocks dropAllLocks(JSC::SilenceAssertionsOnly); + [view setContainingWindow:[_documentView window]]; + } + } + } +} + +- (void)destroyPlugin:(NSView *)view +{ +#if ENABLE(PLUGIN_PROXY_FOR_VIDEO) + if ([_views containsObject:view] || [_viewsNotInDocument containsObject:view]) { +#else + if ([_views containsObject:view]) { +#endif + if (_started) + [self stopOnePlugin:view]; + [self destroyOnePlugin:view]; + +#if ENABLE(NETSCAPE_PLUGIN_API) + if (Frame* frame = core([self webFrame])) + frame->script()->cleanupScriptObjectsForPlugin(self); +#endif + + [pluginViews removeObject:view]; + [[_documentView _webView] removePluginInstanceView:view]; + [_views removeObject:view]; +#if ENABLE(PLUGIN_PROXY_FOR_VIDEO) + [_viewsNotInDocument removeObject:view]; +#endif + } +} + +- (void)_webPluginContainerCancelCheckIfAllowedToLoadRequest:(id)checkIdentifier +{ + [checkIdentifier cancel]; + [_checksInProgress removeObject:checkIdentifier]; +} + +static void cancelOutstandingCheck(const void *item, void *context) +{ + [(id)item cancel]; +} + +- (void)_cancelOutstandingChecks +{ + if (_checksInProgress) { + CFSetApplyFunction((CFSetRef)_checksInProgress, cancelOutstandingCheck, NULL); + [_checksInProgress release]; + _checksInProgress = nil; + } +} + +- (void)destroyAllPlugins +{ + [self stopAllPlugins]; + + if ([_views count] > 0) { + LOG(Plugins, "destroying WebKit plugins: %@", [_views description]); + } + + [self _cancelOutstandingChecks]; + + int i, count = [_views count]; + for (i = 0; i < count; i++) { + id aView = [_views objectAtIndex:i]; + [self destroyOnePlugin:aView]; + +#if ENABLE(NETSCAPE_PLUGIN_API) + if (Frame* frame = core([self webFrame])) + frame->script()->cleanupScriptObjectsForPlugin(self); +#endif + + [pluginViews removeObject:aView]; + [[_documentView _webView] removePluginInstanceView:aView]; + } + +#if ENABLE(PLUGIN_PROXY_FOR_VIDEO) + count = [_viewsNotInDocument count]; + for (i = 0; i < count; i++) + [self destroyOnePlugin:[_viewsNotInDocument objectAtIndex:i]]; +#endif + + [_views makeObjectsPerformSelector:@selector(removeFromSuperviewWithoutNeedingDisplay)]; + [_views release]; + _views = nil; + + _documentView = nil; +} + +- (id)_webPluginContainerCheckIfAllowedToLoadRequest:(NSURLRequest *)request inFrame:(NSString *)target resultObject:(id)obj selector:(SEL)selector +{ + WebPluginContainerCheck *check = [WebPluginContainerCheck checkWithRequest:request target:target resultObject:obj selector:selector controller:self contextInfo:nil]; + [_checksInProgress addObject:check]; + [check start]; + + return check; +} + +- (void)webPlugInContainerLoadRequest:(NSURLRequest *)request inFrame:(NSString *)target +{ + if (!request) { + LOG_ERROR("nil URL passed"); + return; + } + if (!_documentView) { + LOG_ERROR("could not load URL %@ because plug-in has already been destroyed", request); + return; + } + WebFrame *frame = [_dataSource webFrame]; + if (!frame) { + LOG_ERROR("could not load URL %@ because plug-in has already been stopped", request); + return; + } + if (!target) { + target = @"_top"; + } + NSString *JSString = [[request URL] _webkit_scriptIfJavaScriptURL]; + if (JSString) { + if ([frame findFrameNamed:target] != frame) { + LOG_ERROR("JavaScript requests can only be made on the frame that contains the plug-in"); + return; + } + [frame _stringByEvaluatingJavaScriptFromString:JSString]; + } else { + if (!request) { + LOG_ERROR("could not load URL %@", [request URL]); + return; + } + core(frame)->loader()->load(request, target, false); + } +} + +- (void)webPlugInContainerShowStatus:(NSString *)message +{ + if (!message) + message = @""; + + WebView *v = [_dataSource _webView]; + [[v _UIDelegateForwarder] webView:v setStatusText:message]; +} + +// For compatibility only. +- (void)showStatus:(NSString *)message +{ + [self webPlugInContainerShowStatus:message]; +} + +- (NSColor *)webPlugInContainerSelectionColor +{ + bool primary = true; + if (Frame* frame = core([self webFrame])) + primary = frame->selection()->isFocusedAndActive(); + return primary ? [NSColor selectedTextBackgroundColor] : [NSColor secondarySelectedControlColor]; +} + +// For compatibility only. +- (NSColor *)selectionColor +{ + return [self webPlugInContainerSelectionColor]; +} + +- (WebFrame *)webFrame +{ + return [_dataSource webFrame]; +} + +- (WebView *)webView +{ + return [[self webFrame] webView]; +} + +- (NSString *)URLPolicyCheckReferrer +{ + NSURL *responseURL = [[[[self webFrame] _dataSource] response] URL]; + ASSERT(responseURL); + return [responseURL _web_originalDataAsString]; +} + +- (void)pluginView:(NSView *)pluginView receivedResponse:(NSURLResponse *)response +{ + if ([pluginView respondsToSelector:@selector(webPlugInMainResourceDidReceiveResponse:)]) + [pluginView webPlugInMainResourceDidReceiveResponse:response]; + else { + // Cancel the load since this plug-in does its own loading. + // FIXME: See for a problem with this. + NSError *error = [[NSError alloc] _initWithPluginErrorCode:WebKitErrorPlugInWillHandleLoad + contentURL:[response URL] + pluginPageURL:nil + pluginName:nil // FIXME: Get this from somewhere + MIMEType:[response MIMEType]]; + [_dataSource _documentLoader]->cancelMainResourceLoad(error); + [error release]; + } +} + +- (void)pluginView:(NSView *)pluginView receivedData:(NSData *)data +{ + if ([pluginView respondsToSelector:@selector(webPlugInMainResourceDidReceiveData:)]) + [pluginView webPlugInMainResourceDidReceiveData:data]; +} + +- (void)pluginView:(NSView *)pluginView receivedError:(NSError *)error +{ + if ([pluginView respondsToSelector:@selector(webPlugInMainResourceDidFailWithError:)]) + [pluginView webPlugInMainResourceDidFailWithError:error]; +} + +- (void)pluginViewFinishedLoading:(NSView *)pluginView +{ + if ([pluginView respondsToSelector:@selector(webPlugInMainResourceDidFinishLoading)]) + [pluginView webPlugInMainResourceDidFinishLoading]; +} + +#if ENABLE(PLUGIN_PROXY_FOR_VIDEO) +static WebCore::HTMLMediaElement* mediaProxyClient(DOMElement* element) +{ + if (!element) { + LOG_ERROR("nil element passed"); + return nil; + } + + Element* node = core(element); + if (!node || (!node->hasTagName(HTMLNames::videoTag) && !node->hasTagName(HTMLNames::audioTag))) { + LOG_ERROR("invalid media element passed"); + return nil; + } + + return static_cast(node); +} + +- (void)_webPluginContainerSetMediaPlayerProxy:(WebMediaPlayerProxy *)proxy forElement:(DOMElement *)element +{ + WebCore::HTMLMediaElement* client = mediaProxyClient(element); + if (client) + client->setMediaPlayerProxy(proxy); +} + +- (void)_webPluginContainerPostMediaPlayerNotification:(int)notification forElement:(DOMElement *)element +{ + WebCore::HTMLMediaElement* client = mediaProxyClient(element); + if (client) + client->deliverNotification((MediaPlayerProxyNotificationType)notification); +} +#endif + +@end + +static bool isKindOfClass(id object, NSString *className) +{ + Class cls = NSClassFromString(className); + + if (!cls) + return false; + + return [object isKindOfClass:cls]; +} + + +// Existing versions of the Flip4Mac WebKit plug-in have an object lifetime bug related to an NSAlert that is +// used to notify the user about updates to the plug-in. This bug can result in Safari crashing if the page +// containing the plug-in navigates while the alert is displayed (). +// +// The gist of the bug is thus: Flip4Mac sets an instance of the TSUpdateCheck class as the modal delegate of the +// NSAlert instance. This TSUpdateCheck instance itself has a delegate. The delegate is set to the WmvPlugin +// instance which is the NSView subclass that is exposed to WebKit as the plug-in view. Since this relationship +// is that of delegates the TSUpdateCheck does not retain the WmvPlugin. This leads to a bug if the WmvPlugin +// instance is destroyed before the TSUpdateCheck instance as the TSUpdateCheck instance will be left with a +// pointer to a stale object. This will happen if a page containing the Flip4Mac plug-in triggers a navigation +// while the update sheet is visible as the WmvPlugin instance is removed from the view hierarchy and there are +// no other references to keep the object alive. +// +// We work around this bug by patching the following two messages: +// +// 1) -[NSAlert beginSheetModalForWindow:modalDelegate:didEndSelector:contextInfo:] +// 2) -[TSUpdateCheck alertDidEnd:returnCode:contextInfo:] +// +// Our override of 1) detects whether it is Flip4Mac's update sheet triggering the alert by checking whether the +// modal delegate is an instance of TSUpdateCheck. If it is, it retains the modal delegate's delegate. +// +// Our override of 2) then autoreleases the delegate, balancing the retain we added in 1). +// +// These two overrides have the effect of ensuring that the WmvPlugin instance will always outlive the TSUpdateCheck +// instance, preventing the TSUpdateCheck instance from accessing a stale delegate pointer and crashing the application. + + +typedef void (*beginSheetModalForWindowIMP)(id, SEL, NSWindow *, id, SEL, void*); +static beginSheetModalForWindowIMP original_NSAlert_beginSheetModalForWindow_modalDelegate_didEndSelector_contextInfo_; + +typedef void (*alertDidEndIMP)(id, SEL, NSAlert *, NSInteger, void*); +static alertDidEndIMP original_TSUpdateCheck_alertDidEnd_returnCode_contextInfo_; + +static void WebKit_TSUpdateCheck_alertDidEnd_returnCode_contextInfo_(id object, SEL selector, NSAlert *alert, NSInteger returnCode, void* contextInfo) +{ + [[object delegate] autorelease]; + + original_TSUpdateCheck_alertDidEnd_returnCode_contextInfo_(object, selector, alert, returnCode, contextInfo); +} + +static void WebKit_NSAlert_beginSheetModalForWindow_modalDelegate_didEndSelector_contextInfo_(id object, SEL selector, NSWindow *window, id modalDelegate, SEL didEndSelector, void* contextInfo) +{ + if (isKindOfClass(modalDelegate, @"TSUpdateCheck")) + [[modalDelegate delegate] retain]; + + original_NSAlert_beginSheetModalForWindow_modalDelegate_didEndSelector_contextInfo_(object, selector, window, modalDelegate, didEndSelector, contextInfo); +} + +static void installFlip4MacPlugInWorkaroundIfNecessary() +{ + static bool hasInstalledFlip4MacPlugInWorkaround; + if (!hasInstalledFlip4MacPlugInWorkaround) { + Class TSUpdateCheck = objc_lookUpClass("TSUpdateCheck"); + if (!TSUpdateCheck) + return; + + Method methodToPatch = class_getInstanceMethod(TSUpdateCheck, @selector(alertDidEnd:returnCode:contextInfo:)); + if (!methodToPatch) + return; + + IMP originalMethod = method_setImplementation(methodToPatch, reinterpret_cast(WebKit_TSUpdateCheck_alertDidEnd_returnCode_contextInfo_)); + original_TSUpdateCheck_alertDidEnd_returnCode_contextInfo_ = reinterpret_cast(originalMethod); + + methodToPatch = class_getInstanceMethod(objc_getRequiredClass("NSAlert"), @selector(beginSheetModalForWindow:modalDelegate:didEndSelector:contextInfo:)); + originalMethod = method_setImplementation(methodToPatch, reinterpret_cast(WebKit_NSAlert_beginSheetModalForWindow_modalDelegate_didEndSelector_contextInfo_)); + original_NSAlert_beginSheetModalForWindow_modalDelegate_didEndSelector_contextInfo_ = reinterpret_cast(originalMethod); + + hasInstalledFlip4MacPlugInWorkaround = true; + } +}