<rdar://problem/
4480737> Flash crashes after it replaces itself via a document.write()
I kind of hate to do this, but this is the best way to work around buggy plug-ins like Flash that assume that
NPP_Destroy() cannot be called while the browser is calling one of its other plug-in functions. The classic
situation is a plug-in that replaces itself via an NPN_Invoke() that executes a document.write().
* Plugins/WebBaseNetscapePluginView.h:
* Plugins/WebBaseNetscapePluginView.m:
(-[WebBaseNetscapePluginView sendEvent:]):
Call -willCallPlugInFunction and -didCallPlugInFunction around calls to the NPP_* functions.
(-[WebBaseNetscapePluginView setWindowIfNecessary]):
ditto
(-[WebBaseNetscapePluginView start]):
It should not be possible to start a plug-in instance while we are calling into it (one of those chicken/egg
problems). Added a sanity-checking assertion.
(-[WebBaseNetscapePluginView stop]):
If we're already calling a plug-in function, do not call NPP_Destroy(). The plug-in function we are calling
may assume that its instance->pdata, or other memory freed by NPP_Destroy(), is valid and unchanged until said
plugin-function returns.
(-[WebBaseNetscapePluginView pluginScriptableObject]):
Call -willCallPlugInFunction and -didCallPlugInFunction around calls to the NPP_* functions.
(-[WebBaseNetscapePluginView willCallPlugInFunction]):
Increment plug-in function call depth.
(-[WebBaseNetscapePluginView didCallPlugInFunction]):
Decrement plug-in function call depth. Stop if we're supposed to stop.
(-[WebBaseNetscapePluginView evaluateJavaScriptPluginRequest:]):
Call -willCallPlugInFunction and -didCallPlugInFunction around calls to the NPP_* functions.
(-[WebBaseNetscapePluginView webFrame:didFinishLoadWithReason:]):
ditto
(-[WebBaseNetscapePluginView _printedPluginBitmap]):
ditto
* Plugins/WebBaseNetscapePluginStream.m:
(-[WebBaseNetscapePluginStream startStreamResponseURL:expectedContentLength:lastModifiedDate:MIMEType:]):
Call -willCallPlugInFunction and -didCallPlugInFunction around calls to the NPP_* functions.
(-[WebBaseNetscapePluginStream _destroyStream]):
ditto
(-[WebBaseNetscapePluginStream _deliverData]):
ditto
git-svn-id: https://svn.webkit.org/repository/webkit/trunk@15750
268f45cc-cd09-0410-ab3c-
d52691b4dbfc
+2006-08-01 Tim Omernick <timo@apple.com>
+
+ Reviewed by John Sullivan.
+
+ <rdar://problem/4480737> Flash crashes after it replaces itself via a document.write()
+
+ I kind of hate to do this, but this is the best way to work around buggy plug-ins like Flash that assume that
+ NPP_Destroy() cannot be called while the browser is calling one of its other plug-in functions. The classic
+ situation is a plug-in that replaces itself via an NPN_Invoke() that executes a document.write().
+
+ * Plugins/WebBaseNetscapePluginView.h:
+ * Plugins/WebBaseNetscapePluginView.m:
+ (-[WebBaseNetscapePluginView sendEvent:]):
+ Call -willCallPlugInFunction and -didCallPlugInFunction around calls to the NPP_* functions.
+ (-[WebBaseNetscapePluginView setWindowIfNecessary]):
+ ditto
+ (-[WebBaseNetscapePluginView start]):
+ It should not be possible to start a plug-in instance while we are calling into it (one of those chicken/egg
+ problems). Added a sanity-checking assertion.
+ (-[WebBaseNetscapePluginView stop]):
+ If we're already calling a plug-in function, do not call NPP_Destroy(). The plug-in function we are calling
+ may assume that its instance->pdata, or other memory freed by NPP_Destroy(), is valid and unchanged until said
+ plugin-function returns.
+ (-[WebBaseNetscapePluginView pluginScriptableObject]):
+ Call -willCallPlugInFunction and -didCallPlugInFunction around calls to the NPP_* functions.
+ (-[WebBaseNetscapePluginView willCallPlugInFunction]):
+ Increment plug-in function call depth.
+ (-[WebBaseNetscapePluginView didCallPlugInFunction]):
+ Decrement plug-in function call depth. Stop if we're supposed to stop.
+ (-[WebBaseNetscapePluginView evaluateJavaScriptPluginRequest:]):
+ Call -willCallPlugInFunction and -didCallPlugInFunction around calls to the NPP_* functions.
+ (-[WebBaseNetscapePluginView webFrame:didFinishLoadWithReason:]):
+ ditto
+ (-[WebBaseNetscapePluginView _printedPluginBitmap]):
+ ditto
+
+ * Plugins/WebBaseNetscapePluginStream.m:
+ (-[WebBaseNetscapePluginStream startStreamResponseURL:expectedContentLength:lastModifiedDate:MIMEType:]):
+ Call -willCallPlugInFunction and -didCallPlugInFunction around calls to the NPP_* functions.
+ (-[WebBaseNetscapePluginStream _destroyStream]):
+ ditto
+ (-[WebBaseNetscapePluginStream _deliverData]):
+ ditto
+
2006-08-01 Maciej Stachowiak <mjs@apple.com>
- fix build after last change
// FIXME: Need a way to check if stream is seekable
+ [pluginView willCallPlugInFunction];
NPError npErr = NPP_NewStream(instance, (char *)[MIMEType UTF8String], &stream, NO, &transferMode);
+ [pluginView didCallPlugInFunction];
LOG(Plugins, "NPP_NewStream URL=%@ MIME=%@ error=%d", responseURL, MIMEType, npErr);
if (npErr != NPERR_NO_ERROR) {
ASSERT(path != NULL);
char *carbonPath = CarbonPathFromPOSIXPath(path);
ASSERT(carbonPath != NULL);
+ [pluginView willCallPlugInFunction];
NPP_StreamAsFile(instance, &stream, carbonPath);
+ [pluginView didCallPlugInFunction];
// Delete the file after calling NPP_StreamAsFile(), instead of in -dealloc/-finalize. It should be OK
// to delete the file here -- NPP_StreamAsFile() is always called immediately before NPP_DestroyStream()
}
NPError npErr;
+ [pluginView willCallPlugInFunction];
npErr = NPP_DestroyStream(instance, &stream, reason);
+ [pluginView didCallPlugInFunction];
LOG(Plugins, "NPP_DestroyStream responseURL=%@ error=%d", responseURL, npErr);
stream.ndata = nil;
if (sendNotification) {
// NPP_URLNotify expects the request URL, not the response URL.
+ [pluginView willCallPlugInFunction];
NPP_URLNotify(instance, [requestURL _web_URLCString], reason, notifyData);
+ [pluginView didCallPlugInFunction];
LOG(Plugins, "NPP_URLNotify requestURL=%@ reason=%d", requestURL, reason);
}
int32 totalBytesDelivered = 0;
while (totalBytesDelivered < totalBytes) {
+ [pluginView willCallPlugInFunction];
int32 deliveryBytes = NPP_WriteReady(instance, &stream);
+ [pluginView didCallPlugInFunction];
LOG(Plugins, "NPP_WriteReady responseURL=%@ bytes=%d", responseURL, deliveryBytes);
if (deliveryBytes <= 0) {
} else {
deliveryBytes = MIN(deliveryBytes, totalBytes - totalBytesDelivered);
NSData *subdata = [deliveryData subdataWithRange:NSMakeRange(totalBytesDelivered, deliveryBytes)];
+ [pluginView willCallPlugInFunction];
deliveryBytes = NPP_Write(instance, &stream, offset, [subdata length], (void *)[subdata bytes]);
+ [pluginView didCallPlugInFunction];
if (deliveryBytes < 0) {
// Netscape documentation says that a negative result from NPP_Write means cancel the load.
[self cancelLoadAndDestroyStreamWithError:[self _pluginCancelledConnectionError]];
BOOL currentEventIsUserGesture;
BOOL isTransparent;
BOOL isCompletelyObscured;
+ BOOL shouldStopSoon;
+ unsigned pluginFunctionCallDepth;
DOMElement *element;
- (void)viewWillMoveToHostWindow:(NSWindow *)hostWindow;
- (void)viewDidMoveToHostWindow;
-/* Returns the NPObject that represents the plugin interface. */
+// Returns the NPObject that represents the plugin interface.
- (void *)pluginScriptableObject;
+// -willCallPlugInFunction must be called before calling any of the NPP_* functions for this view's NPP instance.
+// This is necessary to ensure that plug-ins are not destroyed while WebKit calls into them. Some plug-ins (Flash
+// at least) are written with the assumption that nothing they do in their plug-in functions can cause NPP_Destroy()
+// to be called. Unfortunately, this is not true, especially if the plug-in uses NPN_Invoke() to execute a
+// document.write(), which clears the document and destroys the plug-in.
+// See <rdar://problem/4480737>.
+- (void)willCallPlugInFunction;
+
+// -didCallPlugInFunction should be called after returning from a plug-in function. It should be called exactly
+// once for every call to -willCallPlugInFunction.
+// See <rdar://problem/4480737>.
+- (void)didCallPlugInFunction;
+
@end
// Temporarily retain self in case the plug-in view is released while sending an event.
[[self retain] autorelease];
+ [self willCallPlugInFunction];
BOOL acceptedEvent = NPP_HandleEvent(instance, event);
+ [self didCallPlugInFunction];
currentEventIsUserGesture = NO;
// A CoreGraphics or OpenGL plugin's window may only be set while the plugin is being updated
ASSERT((drawingModel != NPDrawingModelCoreGraphics && drawingModel != NPDrawingModelOpenGL) || [NSView focusView] == self);
+ [self willCallPlugInFunction];
npErr = NPP_SetWindow(instance, &window);
+ [self didCallPlugInFunction];
inSetWindow = NO;
#ifndef NDEBUG
// browser window. Windowless plug-ins are rendered off-screen, then copied into the main browser window.
window.type = NPWindowTypeWindow;
+ // NPN_New(), which creates the plug-in instance, should never be called while calling a plug-in function for that instance.
+ ASSERT(pluginFunctionCallDepth == 0);
+
[[self class] setCurrentPluginView:self];
NPError npErr = NPP_New((char *)[MIMEType cString], instance, mode, argsCount, cAttributes, cValues, NULL);
[[self class] setCurrentPluginView:nil];
- (void)stop
{
+ // If we're already calling a plug-in function, do not call NPP_Destroy(). The plug-in function we are calling
+ // may assume that its instance->pdata, or other memory freed by NPP_Destroy(), is valid and unchanged until said
+ // plugin-function returns.
+ // See <rdar://problem/4480737>.
+ if (pluginFunctionCallDepth > 0) {
+ shouldStopSoon = YES;
+ return;
+ }
+
[self removeTrackingRect];
if (!isStarted) {
{
if (NPP_GetValue) {
void *value = 0;
+ [self willCallPlugInFunction];
NPError npErr = NPP_GetValue (instance, NPPVpluginScriptableNPObject, (void *)&value);
+ [self didCallPlugInFunction];
if (npErr == NPERR_NO_ERROR) {
return value;
}
return (void *)0;
}
+- (void)willCallPlugInFunction
+{
+ // Could try to prevent infinite recursion here, but it's probably not worth the effort.
+ pluginFunctionCallDepth++;
+}
+
+- (void)didCallPlugInFunction
+{
+ ASSERT(pluginFunctionCallDepth > 0);
+ pluginFunctionCallDepth--;
+
+ // If -stop was called while we were calling into a plug-in function, and we're no longer
+ // inside a plug-in function, stop now.
+ if (pluginFunctionCallDepth == 0 && shouldStopSoon) {
+ shouldStopSoon = NO;
+ [self stop];
+ }
+}
@end
// FIXME: If the result is a string, we probably want to put that string into the frame, just
// like we do in KHTMLPartBrowserExtension::openURLRequest.
if ([JSPluginRequest sendNotification]) {
+ [self willCallPlugInFunction];
NPP_URLNotify(instance, [URL _web_URLCString], NPRES_DONE, [JSPluginRequest notifyData]);
+ [self didCallPlugInFunction];
}
} else if ([result length] > 0) {
// Don't call NPP_NewStream and other stream methods if there is no JS result to deliver. This is what Mozilla does.
ASSERT(pluginRequest != nil);
ASSERT([pluginRequest sendNotification]);
+ [self willCallPlugInFunction];
NPP_URLNotify(instance, [[[pluginRequest request] URL] _web_URLCString], reason, [pluginRequest notifyData]);
+ [self didCallPlugInFunction];
[pendingFrameLoads removeObjectForKey:webFrame];
[webFrame _setInternalLoadDelegate:nil];
npPrint.print.embedPrint.platformPrint = printGWorld;
// Tell the plugin to print into the GWorld
+ [self willCallPlugInFunction];
NPP_Print(instance, &npPrint);
+ [self didCallPlugInFunction];
// Don't need the GWorld anymore
DisposeGWorld(printGWorld);