blob: d6b99b0803a397ae727be2f6dbe3887d8529d649 [file] [log] [blame]
// Copyright 2012 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#import "ios/net/crn_http_protocol_handler_proxy_with_client_thread.h"
#include "base/logging.h"
#import "base/mac/scoped_nsobject.h"
#include "base/time/time.h"
#import "ios/net/protocol_handler_util.h"
#include "net/base/auth.h"
#include "net/url_request/url_request.h"
// When the protocol is invalidated, no synchronization (lock) is needed:
// - The actual calls to the protocol and its invalidation are all done on
// clientThread_ and thus are serialized.
// - When a proxy method is called, the protocol is compared to nil. There may
// be a conflict at this point, in the case the protocol is being invalidated
// during this comparison. However, in such a case, the actual value of the
// pointer does not matter: an invalid pointer will behave as a valid one and
// post a task on the clientThread_, and that task will be handled correctly,
// as described by the item above.
@interface CRNHTTPProtocolHandlerProxyWithClientThread () {
__weak NSURLProtocol* _protocol;
// Thread used to call the client back.
// This thread does not have a base::MessageLoop, and thus does not work with
// the usual task posting functions.
__weak NSThread* _clientThread;
// The run loop modes to use when posting tasks to |clientThread_|.
base::scoped_nsobject<NSArray> _runLoopModes;
// The request URL.
base::scoped_nsobject<NSString> _url;
// The creation time of the request.
base::Time _creationTime;
// |requestComplete_| is used in debug to check that the client is not called
// after completion.
BOOL _requestComplete;
BOOL _paused;
base::scoped_nsobject<NSMutableArray> _queuedInvocations;
}
// Performs the selector on |clientThread_| using |runLoopModes_|.
- (void)runInvocationQueueOnClientThread;
- (void)postToClientThread:(SEL)aSelector, ... NS_REQUIRES_NIL_TERMINATION;
- (void)invokeOnClientThread:(NSInvocation*)invocation;
// These functions are just wrappers around the corresponding
// NSURLProtocolClient methods, used for task posting.
- (void)didFailWithErrorOnClientThread:(NSError*)error;
- (void)didLoadDataOnClientThread:(NSData*)data;
- (void)didReceiveResponseOnClientThread:(NSURLResponse*)response;
- (void)wasRedirectedToRequestOnClientThread:(NSURLRequest*)request
redirectResponse:(NSURLResponse*)response;
- (void)didFinishLoadingOnClientThread;
@end
@implementation CRNHTTPProtocolHandlerProxyWithClientThread
- (instancetype)initWithProtocol:(NSURLProtocol*)protocol
clientThread:(NSThread*)clientThread
runLoopMode:(NSString*)mode {
DCHECK(protocol);
DCHECK(clientThread);
if ((self = [super init])) {
_protocol = protocol;
_url.reset([[[[protocol request] URL] absoluteString] copy]);
_creationTime = base::Time::Now();
_clientThread = clientThread;
// Use the common run loop mode in addition to the client thread mode, in
// hope that our tasks are executed even if the client thread changes mode
// later on.
if ([mode isEqualToString:NSRunLoopCommonModes])
_runLoopModes.reset([@[ NSRunLoopCommonModes ] retain]);
else
_runLoopModes.reset([@[ mode, NSRunLoopCommonModes ] retain]);
_queuedInvocations.reset([[NSMutableArray alloc] init]);
}
return self;
}
- (void)invalidate {
DCHECK([NSThread currentThread] == _clientThread);
_protocol = nil;
_requestComplete = YES;
// Note that there may still be queued invocations here, if the chrome network
// stack continues to emit events after the system network stack has paused
// the request, and then the system network stack destroys the request.
_queuedInvocations.reset();
}
- (void)runInvocationQueueOnClientThread {
DCHECK([NSThread currentThread] == _clientThread);
DCHECK(!_requestComplete || !_protocol);
// Each of the queued invocations may cause the system network stack to pause
// this request, in which case |runInvocationQueueOnClientThread| should
// immediately stop running further queued invocations. The queue will be
// drained again the next time the system network stack calls |resume|.
//
// Specifically, the system stack can call back into |pause| with this
// function still on the call stack. However, since new invocations are
// enqueued on this thread via posted invocations, no new invocations can be
// added while this function is running.
while (!_paused && _queuedInvocations.get().count > 0) {
NSInvocation* invocation = [_queuedInvocations objectAtIndex:0];
// Since |_queuedInvocations| owns the only reference to each queued
// invocation, this function has to retain another reference before removing
// the queued invocation from the array.
[invocation invoke];
[_queuedInvocations removeObjectAtIndex:0];
}
}
- (void)postToClientThread:(SEL)aSelector, ... {
// Build an NSInvocation representing an invocation of |aSelector| on |self|
// with the supplied varargs passed as arguments to the invocation.
NSMethodSignature* sig = [self methodSignatureForSelector:aSelector];
DCHECK(sig != nil);
NSInvocation* inv = [NSInvocation invocationWithMethodSignature:sig];
[inv setTarget:self];
[inv setSelector:aSelector];
[inv retainArguments];
size_t arg_index = 2;
va_list args;
va_start(args, aSelector);
NSObject* arg = va_arg(args, NSObject*);
while (arg != nil) {
[inv setArgument:&arg atIndex:arg_index];
arg = va_arg(args, NSObject*);
arg_index++;
}
va_end(args);
DCHECK(arg_index == sig.numberOfArguments);
[self performSelector:@selector(invokeOnClientThread:)
onThread:_clientThread
withObject:inv
waitUntilDone:NO
modes:_runLoopModes];
}
- (void)invokeOnClientThread:(NSInvocation*)invocation {
DCHECK([NSThread currentThread] == _clientThread);
DCHECK(!_requestComplete || !_protocol);
if (!_paused) {
[invocation invoke];
} else {
[_queuedInvocations addObject:invocation];
}
}
#pragma mark Proxy methods called from any thread.
- (void)didFailWithNSErrorCode:(NSInteger)nsErrorCode
netErrorCode:(int)netErrorCode {
DCHECK(_clientThread);
if (!_protocol)
return;
NSError* error =
net::GetIOSError(nsErrorCode, netErrorCode, _url, _creationTime);
[self postToClientThread:@selector(didFailWithErrorOnClientThread:), error,
nil];
}
- (void)didLoadData:(NSData*)data {
DCHECK(_clientThread);
if (!_protocol)
return;
[self postToClientThread:@selector(didLoadDataOnClientThread:), data, nil];
}
- (void)didReceiveResponse:(NSURLResponse*)response {
DCHECK(_clientThread);
if (!_protocol)
return;
[self postToClientThread:@selector(didReceiveResponseOnClientThread:),
response, nil];
}
- (void)wasRedirectedToRequest:(NSURLRequest*)request
nativeRequest:(net::URLRequest*)nativeRequest
redirectResponse:(NSURLResponse*)redirectResponse {
DCHECK(_clientThread);
if (!_protocol)
return;
[self postToClientThread:@selector(wasRedirectedToRequestOnClientThread:
redirectResponse:),
request, redirectResponse, nil];
}
- (void)didFinishLoading {
DCHECK(_clientThread);
if (!_protocol)
return;
[self postToClientThread:@selector(didFinishLoadingOnClientThread), nil];
}
// Feature support methods that don't forward to the NSURLProtocolClient.
- (void)didCreateNativeRequest:(net::URLRequest*)nativeRequest {
// no-op.
}
- (void)didRecieveAuthChallenge:(net::AuthChallengeInfo*)authInfo
nativeRequest:(const net::URLRequest&)nativeRequest
callback:(const network_client::AuthCallback&)callback {
// If we get this far, authentication has failed.
base::string16 empty;
callback.Run(false, empty, empty);
}
- (void)cancelAuthRequest {
// no-op.
}
- (void)setUnderlyingClient:(id<CRNNetworkClientProtocol>)underlyingClient {
// This is the lowest level.
DCHECK(!underlyingClient);
}
#pragma mark Proxy methods called from the client thread.
- (void)didFailWithErrorOnClientThread:(NSError*)error {
_requestComplete = YES;
[[_protocol client] URLProtocol:_protocol didFailWithError:error];
}
- (void)didLoadDataOnClientThread:(NSData*)data {
[[_protocol client] URLProtocol:_protocol didLoadData:data];
}
- (void)didReceiveResponseOnClientThread:(NSURLResponse*)response {
[[_protocol client] URLProtocol:_protocol
didReceiveResponse:response
cacheStoragePolicy:NSURLCacheStorageNotAllowed];
}
- (void)wasRedirectedToRequestOnClientThread:(NSURLRequest*)request
redirectResponse:(NSURLResponse*)redirectResponse {
[[_protocol client] URLProtocol:_protocol
wasRedirectedToRequest:request
redirectResponse:redirectResponse];
}
- (void)didFinishLoadingOnClientThread {
_requestComplete = YES;
[[_protocol client] URLProtocolDidFinishLoading:_protocol];
}
- (void)pause {
DCHECK([NSThread currentThread] == _clientThread);
// It's legal (in fact, required) for |pause| to be called after the request
// has already finished, so the usual invalidation DCHECK is missing here.
_paused = YES;
}
- (void)resume {
DCHECK([NSThread currentThread] == _clientThread);
DCHECK(!_requestComplete || !_protocol);
_paused = NO;
[self runInvocationQueueOnClientThread];
}
@end