+2004-12-01 Chris Blumenberg <cblu@apple.com>
+
+ Fixed: <rdar://problem/3879870> Flash Player unable to stop data stream from continuing to download by returning -1 from NPP_Write
+ Also improved and cleaned-up the plug-in stream termination code.
+
+ Reviewed by john.
+
+ * Plugins.subproj/WebBaseNetscapePluginStream.h:
+ * Plugins.subproj/WebBaseNetscapePluginStream.m:
+ (+[WebBaseNetscapePluginStream reasonForError:]): return NPRES_DONE for a nil error
+ (-[WebBaseNetscapePluginStream _pluginCancelledConnectionError]): new, factored out from other methods
+ (-[WebBaseNetscapePluginStream errorForReason:]): new
+ (-[WebBaseNetscapePluginStream dealloc]): release MIME type
+ (-[WebBaseNetscapePluginStream setMIMEType:]): new
+ (-[WebBaseNetscapePluginStream startStreamResponseURL:expectedContentLength:lastModifiedDate:MIMEType:]): call setMIMEType so we can use it in _pluginCancelledConnectionError, call renamed methods
+ (-[WebBaseNetscapePluginStream _destroyStream]): prepended underscore, replaced some early returns with asserts as the callers are now smarter
+ (-[WebBaseNetscapePluginStream _destroyStreamWithReason:]): prepended underscore, only call _destroyStream if there is an error or if the load is complete and there is no more data to be streamed
+ (-[WebBaseNetscapePluginStream cancelLoadWithError:]): new, overridden by subclasses to cancel the actual NSURLConnection
+ (-[WebBaseNetscapePluginStream destroyStreamWithError:]): new, calls _destroyStreamWithReason
+ (-[WebBaseNetscapePluginStream finishedLoadingWithData:]): call renamed methods
+ (-[WebBaseNetscapePluginStream _deliverData]): prepended underscore, call cancelLoadAndDestroyStreamWithError if NPP_Write returns a negative number
+ * Plugins.subproj/WebBaseNetscapePluginView.m:
+ (-[WebBaseNetscapePluginView destroyStream:reason:]): call cancelLoadAndDestroyStreamWithError
+ * Plugins.subproj/WebNetscapePluginRepresentation.m:
+ (-[WebNetscapePluginRepresentation receivedError:withDataSource:]): call destroyStreamWithError
+ (-[WebNetscapePluginRepresentation cancelLoadWithError:]): new, override method, tell the data source to stop loading
+ * Plugins.subproj/WebNetscapePluginStream.m:
+ (-[WebNetscapePluginStream cancelLoadWithError:]): new, override method, tell the loader to stop
+ (-[WebNetscapePluginStream stop]): call cancelLoadAndDestroyStreamWithError
+ (-[WebNetscapePluginConnectionDelegate isDone]): new
+ (-[WebNetscapePluginConnectionDelegate didReceiveResponse:]): call cancelLoadAndDestroyStreamWithError
+ (-[WebNetscapePluginConnectionDelegate didFailWithError:]): call destroyStreamWithError
+
2004-12-01 Kevin Decker <kdecker@apple.com>
Reviewed by Harrison.
@class WebNetscapePluginPackage;
@class NSURLResponse;
-#define WEB_REASON_PLUGIN_CANCELLED -1
-
@interface WebBaseNetscapePluginStream : NSObject
{
NSMutableData *deliveryData;
NSURL *requestURL;
NSURL *responseURL;
+ NSString *MIMEType;
+
NPP instance;
uint16 transferMode;
int32 offset;
}
+ (NPReason)reasonForError:(NSError *)error;
+- (NSError *)errorForReason:(NPReason)theReason;
- (id)initWithRequestURL:(NSURL *)theRequestURL
pluginPointer:(NPP)thePluginPointer
- (void)setResponseURL:(NSURL *)theResponseURL;
- (void)setPluginPointer:(NPP)pluginPointer;
+- (uint16)transferMode;
+
- (void)startStreamResponseURL:(NSURL *)theResponseURL
expectedContentLength:(long long)expectedContentLength
lastModifiedDate:(NSDate *)lastModifiedDate
MIMEType:(NSString *)MIMEType;
- (void)startStreamWithResponse:(NSURLResponse *)r;
+
+// cancelLoadWithError cancels the NSURLConnection and informs WebKit of the load error.
+// This method is overriden by subclasses.
+- (void)cancelLoadWithError:(NSError *)error;
+
+// destroyStreamWithError tells the plug-in that the load is completed (error == nil) or ended in error.
+- (void)destroyStreamWithError:(NSError *)error;
+
+// cancelLoadAndDestoryStreamWithError calls cancelLoadWithError: then destroyStreamWithError:.
+- (void)cancelLoadAndDestroyStreamWithError:(NSError *)error;
+
- (void)receivedData:(NSData *)data;
- (void)finishedLoadingWithData:(NSData *)data;
-- (void)receivedError:(NSError *)error;
-- (void)cancelWithReason:(NPReason)theReason;
-- (uint16)transferMode;
@end
#import <WebKit/WebBaseNetscapePluginStream.h>
#import <WebKit/WebBaseNetscapePluginView.h>
+#import <WebKit/WebKitErrorsPrivate.h>
#import <WebKit/WebKitLogging.h>
#import <WebKit/WebNetscapePluginPackage.h>
#import <WebKit/WebNSObjectExtras.h>
static const char *CarbonPathFromPOSIXPath(const char *posixPath);
+#define WEB_REASON_NONE -1
+
@implementation WebBaseNetscapePluginStream
+ (NPReason)reasonForError:(NSError *)error
{
+ if (error == nil) {
+ return NPRES_DONE;
+ }
if ([[error domain] isEqualToString:NSURLErrorDomain] && [error code] == NSURLErrorCancelled) {
return NPRES_USER_BREAK;
}
return NPRES_NETWORK_ERR;
}
+- (NSError *)_pluginCancelledConnectionError
+{
+ return [[[NSError alloc] _initWithPluginErrorCode:WebKitErrorPlugInCancelledConnection
+ contentURL:responseURL != nil ? responseURL : requestURL
+ pluginPageURL:nil
+ pluginName:[plugin name]
+ MIMEType:MIMEType] autorelease];
+}
+
+- (NSError *)errorForReason:(NPReason)theReason
+{
+ if (theReason == NPRES_DONE) {
+ return nil;
+ }
+ if (theReason == NPRES_USER_BREAK) {
+ return [NSError _webKitErrorWithDomain:NSURLErrorDomain
+ code:NSURLErrorCancelled
+ URL:responseURL != nil ? responseURL : requestURL];
+ }
+ return [self _pluginCancelledConnectionError];
+}
+
- (id)initWithRequestURL:(NSURL *)theRequestURL
pluginPointer:(NPP)thePluginPointer
notifyData:(void *)theNotifyData
[requestURL release];
[responseURL release];
+ [MIMEType release];
[plugin release];
[deliveryData release];
NPP_URLNotify = [plugin NPP_URLNotify];
}
+- (void)setMIMEType:(NSString *)theMIMEType
+{
+ [theMIMEType retain];
+ [MIMEType release];
+ MIMEType = theMIMEType;
+}
+
- (void)startStreamResponseURL:(NSURL *)URL
expectedContentLength:(long long)expectedContentLength
lastModifiedDate:(NSDate *)lastModifiedDate
- MIMEType:(NSString *)MIMEType
+ MIMEType:(NSString *)theMIMEType
{
ASSERT(!isTerminated);
}
[self setResponseURL:URL];
+ [self setMIMEType:theMIMEType];
free((void *)stream.url);
stream.url = strdup([responseURL _web_URLCString]);
transferMode = NP_NORMAL;
offset = 0;
- reason = WEB_REASON_PLUGIN_CANCELLED;
+ reason = WEB_REASON_NONE;
// FIXME: Need a way to check if stream is seekable
if (npErr != NPERR_NO_ERROR) {
ERROR("NPP_NewStream failed with error: %d responseURL: %@", npErr, responseURL);
- // Calling cancelWithReason with WEB_REASON_PLUGIN_CANCELLED cancels the load, but doesn't call NPP_DestroyStream.
- [self cancelWithReason:WEB_REASON_PLUGIN_CANCELLED];
+ // Calling cancelLoadWithError: cancels the load, but doesn't call NPP_DestroyStream.
+ [self cancelLoadWithError:[self _pluginCancelledConnectionError]];
return;
}
break;
case NP_SEEK:
ERROR("Stream type: NP_SEEK not yet supported");
- [self cancelWithReason:NPRES_NETWORK_ERR];
+ [self cancelLoadAndDestroyStreamWithError:[self _pluginCancelledConnectionError]];
break;
default:
ERROR("unknown stream type");
MIMEType:[r MIMEType]];
}
-- (void)destroyStream
+- (void)_destroyStream
{
- if (isTerminated || ![plugin isLoaded] || [deliveryData length] > 0 || reason == WEB_REASON_PLUGIN_CANCELLED) {
+ if (isTerminated || ![plugin isLoaded]) {
return;
}
+ ASSERT(reason != WEB_REASON_NONE);
+ ASSERT([deliveryData length] == 0);
+
if (stream.ndata != NULL) {
if (reason == NPRES_DONE && (transferMode == NP_ASFILE || transferMode == NP_ASFILEONLY)) {
ASSERT(path != NULL);
isTerminated = YES;
}
-- (void)destroyStreamWithReason:(NPReason)theReason
+- (void)_destroyStreamWithReason:(NPReason)theReason
{
reason = theReason;
- [self destroyStream];
+ if (reason != NPRES_DONE) {
+ // Stop any pending data from being streamed.
+ [deliveryData setLength:0];
+ } else if ([deliveryData length] > 0) {
+ // There is more data to be streamed, don't destroy the stream now.
+ return;
+ }
+ [self _destroyStream];
+ ASSERT(stream.ndata == nil);
}
-- (void)destroyStreamWithFailingReason:(NPReason)theReason
+- (void)cancelLoadWithError:(NSError *)error
{
- ASSERT(theReason != NPRES_DONE);
- // Stop any pending data from being streamed.
- [deliveryData setLength:0];
- [self destroyStreamWithReason:theReason];
- stream.ndata = nil;
+ // Overridden by subclasses.
+ ASSERT_NOT_REACHED();
}
-- (void)receivedError:(NSError *)error
+- (void)destroyStreamWithError:(NSError *)error
{
- [self destroyStreamWithFailingReason:[[self class] reasonForError:error]];
+ [self _destroyStreamWithReason:[[self class] reasonForError:error]];
}
-- (void)cancelWithReason:(NPReason)theReason
+- (void)cancelLoadAndDestroyStreamWithError:(NSError *)error
{
- [self destroyStreamWithFailingReason:theReason];
+ [self cancelLoadWithError:error];
+ [self destroyStreamWithError:error];
}
- (void)finishedLoadingWithData:(NSData *)data
// This should almost never happen.
ERROR("can't make temporary file, almost certainly a problem with /tmp");
// This is not a network error, but the only error codes are "network error" and "user break".
- [self destroyStreamWithFailingReason:NPRES_NETWORK_ERR];
+ [self _destroyStreamWithReason:NPRES_NETWORK_ERR];
free(path);
path = NULL;
return;
ERROR("error writing to temporary file, errno %d", errno);
close(fd);
// This is not a network error, but the only error codes are "network error" and "user break".
- [self destroyStreamWithFailingReason:NPRES_NETWORK_ERR];
+ [self _destroyStreamWithReason:NPRES_NETWORK_ERR];
free(path);
path = NULL;
return;
close(fd);
}
- [self destroyStreamWithReason:NPRES_DONE];
+ [self _destroyStreamWithReason:NPRES_DONE];
}
-- (void)deliverData
+- (void)_deliverData
{
if (![plugin isLoaded] || !stream.ndata || [deliveryData length] == 0) {
return;
if (deliveryBytes <= 0) {
// Plug-in can't receive anymore data right now. Send it later.
- [self performSelector:@selector(deliverData) withObject:nil afterDelay:0];
+ [self performSelector:@selector(_deliverData) withObject:nil afterDelay:0];
break;
} else {
deliveryBytes = MIN(deliveryBytes, totalBytes - totalBytesDelivered);
NSData *subdata = [deliveryData subdataWithRange:NSMakeRange(totalBytesDelivered, deliveryBytes)];
deliveryBytes = NPP_Write(instance, &stream, offset, [subdata length], (void *)[subdata bytes]);
+ if (deliveryBytes < 0) {
+ // Netscape documentation says that a negative result from NPP_Write means cancel the load.
+ [self cancelLoadAndDestroyStreamWithError:[self _pluginCancelledConnectionError]];
+ return;
+ }
deliveryBytes = MIN((unsigned)deliveryBytes, [subdata length]);
offset += deliveryBytes;
totalBytesDelivered += deliveryBytes;
deliveryData = newDeliveryData;
} else {
[deliveryData setLength:0];
- [self destroyStream];
+ if (reason != WEB_REASON_NONE) {
+ [self _destroyStream];
+ }
}
}
}
deliveryData = [[NSMutableData alloc] initWithCapacity:[data length]];
}
[deliveryData appendData:data];
- [self deliverData];
+ [self _deliverData];
}
}
WebBaseNetscapePluginView *view;
}
- initWithStream:(WebNetscapePluginStream *)theStream view:(WebBaseNetscapePluginView *)theView;
+- (BOOL)isDone;
@end
@implementation WebNetscapePluginStream
}
}
-- (void)cancelWithReason:(NPReason)theReason
+- (void)cancelLoadWithError:(NSError *)error
{
- if (theReason == WEB_REASON_PLUGIN_CANCELLED) {
- NSURLResponse *response = [_loader response];
- NSError *error = [[NSError alloc] _initWithPluginErrorCode:WebKitErrorPlugInCancelledConnection
- contentURL:[response URL]
- pluginPageURL:nil
- pluginName:[plugin name]
- MIMEType:[response MIMEType]];
+ if (![_loader isDone]) {
[_loader cancelWithError:error];
- [error release];
- } else {
- [_loader cancel];
}
- [super cancelWithReason:theReason];
}
- (void)stop
{
- [self cancelWithReason:NPRES_USER_BREAK];
+ [self cancelLoadAndDestroyStreamWithError:[_loader cancelledError]];
}
@end
return self;
}
+- (BOOL)isDone
+{
+ return stream == nil;
+}
+
- (void)releaseResources
{
[stream release];
NSError *error = [NSError _webKitErrorWithDomain:NSURLErrorDomain
code:NSURLErrorFileDoesNotExist
URL:[theResponse URL]];
- [stream receivedError:error];
- [self cancelWithError:error];
+ [stream cancelLoadAndDestroyStreamWithError:error];
}
}
}
[[self dataSource] _removePlugInStreamClient:self];
[[view webView] _receivedError:error fromDataSource:[self dataSource]];
- [stream receivedError:error];
+ [stream destroyStreamWithError:error];
[super didFailWithError:error];
[self release];