| // 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 <stddef.h> |
| |
| #include "base/check.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" |
| |
| #if !defined(__has_feature) || !__has_feature(objc_arc) |
| #error "This file requires ARC support." |
| #endif |
| |
| // 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_|. |
| NSArray* _runLoopModes; |
| // The request URL. |
| 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; |
| |
| // Contains code blocks to execute when the connection transitions from paused |
| // to resumed state. |
| NSMutableArray<void (^)()>* _queuedBlocks; |
| } |
| |
| // Performs queued blocks on |clientThread_| using |runLoopModes_|. |
| - (void)runQueuedBlocksOnClientThread; |
| // 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 = [[[[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 = @[ NSRunLoopCommonModes ]; |
| else |
| _runLoopModes = @[ mode, NSRunLoopCommonModes ]; |
| _queuedBlocks = [[NSMutableArray alloc] init]; |
| } |
| return self; |
| } |
| |
| - (void)invalidate { |
| DCHECK([NSThread currentThread] == _clientThread); |
| _protocol = nil; |
| _requestComplete = YES; |
| // Note that there may still be queued blocks 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. |
| _queuedBlocks = nil; |
| } |
| |
| - (void)runQueuedBlocksOnClientThread { |
| DCHECK([NSThread currentThread] == _clientThread); |
| DCHECK(!_requestComplete || !_protocol); |
| // Each of the queued blocks may cause the system network stack to pause |
| // this request, in which case |runQueuedBlocksOnClientThread| 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 blocks are |
| // enqueued on this thread via posted invocations, no new blocks can be |
| // added while this function is running. |
| while (!_paused && _queuedBlocks.count > 0) { |
| void (^block)() = _queuedBlocks[0]; |
| // Since |_queuedBlocks| owns the only reference to each queued |
| // block, this function has to retain another reference before removing |
| // the queued block from the array. |
| block(); |
| [_queuedBlocks removeObjectAtIndex:0]; |
| } |
| } |
| |
| - (void)postBlockToClientThread:(dispatch_block_t)block { |
| DCHECK(block); |
| [self performSelector:@selector(performBlockOnClientThread:) |
| onThread:_clientThread |
| withObject:[block copy] |
| waitUntilDone:NO |
| modes:_runLoopModes]; |
| } |
| |
| - (void)performBlockOnClientThread:(dispatch_block_t)block { |
| DCHECK([NSThread currentThread] == _clientThread); |
| DCHECK(!_requestComplete || !_protocol); |
| DCHECK(block); |
| if (!_paused) { |
| block(); |
| } else { |
| [_queuedBlocks addObject:block]; |
| } |
| } |
| |
| #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 postBlockToClientThread:^{ |
| [self didFailWithErrorOnClientThread:error]; |
| }]; |
| } |
| |
| - (void)didLoadData:(NSData*)data { |
| DCHECK(_clientThread); |
| if (!_protocol) |
| return; |
| [self postBlockToClientThread:^{ |
| [self didLoadDataOnClientThread:data]; |
| }]; |
| } |
| |
| - (void)didReceiveResponse:(NSURLResponse*)response { |
| DCHECK(_clientThread); |
| if (!_protocol) |
| return; |
| [self postBlockToClientThread:^{ |
| [self didReceiveResponseOnClientThread:response]; |
| }]; |
| } |
| |
| - (void)wasRedirectedToRequest:(NSURLRequest*)request |
| nativeRequest:(net::URLRequest*)nativeRequest |
| redirectResponse:(NSURLResponse*)redirectResponse { |
| DCHECK(_clientThread); |
| if (!_protocol) |
| return; |
| [self postBlockToClientThread:^{ |
| [self wasRedirectedToRequestOnClientThread:request |
| redirectResponse:redirectResponse]; |
| }]; |
| } |
| |
| - (void)didFinishLoading { |
| DCHECK(_clientThread); |
| if (!_protocol) |
| return; |
| [self postBlockToClientThread:^{ |
| [self didFinishLoadingOnClientThread]; |
| }]; |
| } |
| |
| // Feature support methods that don't forward to the NSURLProtocolClient. |
| - (void)didCreateNativeRequest:(net::URLRequest*)nativeRequest { |
| // no-op. |
| } |
| |
| #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 runQueuedBlocksOnClientThread]; |
| } |
| |
| @end |