--- /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 <Foundation/NSURLRequest.h>
+#import <WebCore/DocumentLoader.h>
+#import <WebCore/Frame.h>
+#import <WebCore/FrameLoader.h>
+#import <WebCore/HTMLMediaElement.h>
+#import <WebCore/HTMLNames.h>
+#import <WebCore/MediaPlayerProxy.h>
+#import <WebCore/PlatformString.h>
+#import <WebCore/ResourceRequest.h>
+#import <WebCore/ScriptController.h>
+#import <WebCore/WebCoreURLResponse.h>
+#import <objc/objc-runtime.h>
+#import <runtime/JSLock.h>
+
+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 <rdar://problem/4258008> 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<WebCore::HTMLMediaElement*>(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 (<rdar://problem/7313430>).
+//
+// 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<IMP>(WebKit_TSUpdateCheck_alertDidEnd_returnCode_contextInfo_));
+ original_TSUpdateCheck_alertDidEnd_returnCode_contextInfo_ = reinterpret_cast<alertDidEndIMP>(originalMethod);
+
+ methodToPatch = class_getInstanceMethod(objc_getRequiredClass("NSAlert"), @selector(beginSheetModalForWindow:modalDelegate:didEndSelector:contextInfo:));
+ originalMethod = method_setImplementation(methodToPatch, reinterpret_cast<IMP>(WebKit_NSAlert_beginSheetModalForWindow_modalDelegate_didEndSelector_contextInfo_));
+ original_NSAlert_beginSheetModalForWindow_modalDelegate_didEndSelector_contextInfo_ = reinterpret_cast<beginSheetModalForWindowIMP>(originalMethod);
+
+ hasInstalledFlip4MacPlugInWorkaround = true;
+ }
+}