Index: ios/net/crn_http_protocol_handler_proxy_with_client_thread.mm |
diff --git a/ios/net/crn_http_protocol_handler_proxy_with_client_thread.mm b/ios/net/crn_http_protocol_handler_proxy_with_client_thread.mm |
new file mode 100644 |
index 0000000000000000000000000000000000000000..0289e81d630f848a358f9dac2a4aae8fb0025f61 |
--- /dev/null |
+++ b/ios/net/crn_http_protocol_handler_proxy_with_client_thread.mm |
@@ -0,0 +1,201 @@ |
+// 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; |
+} |
+ |
+// Performs the selector on |clientThread_| using |runLoopModes_|. |
+- (void)performSelectorOnClientThread:(SEL)aSelector withObject:(id)arg; |
+// 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:(NSArray*)params; |
+- (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]); |
+ } |
+ return self; |
+} |
+ |
+- (void)invalidate { |
+ DCHECK([NSThread currentThread] == _clientThread); |
+ _protocol = nil; |
+ _requestComplete = YES; |
+} |
+ |
+- (void)performSelectorOnClientThread:(SEL)aSelector withObject:(id)arg { |
+ [self performSelector:aSelector |
+ onThread:_clientThread |
+ withObject:arg |
+ waitUntilDone:NO |
+ modes:_runLoopModes]; |
+} |
+ |
+#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 performSelectorOnClientThread:@selector(didFailWithErrorOnClientThread:) |
+ withObject:error]; |
+} |
+ |
+- (void)didLoadData:(NSData*)data { |
+ DCHECK(_clientThread); |
+ if (!_protocol) |
+ return; |
+ [self performSelectorOnClientThread:@selector(didLoadDataOnClientThread:) |
+ withObject:data]; |
+} |
+ |
+- (void)didReceiveResponse:(NSURLResponse*)response { |
+ DCHECK(_clientThread); |
+ if (!_protocol) |
+ return; |
+ [self |
+ performSelectorOnClientThread:@selector(didReceiveResponseOnClientThread:) |
+ withObject:response]; |
+} |
+ |
+- (void)wasRedirectedToRequest:(NSURLRequest*)request |
+ nativeRequest:(net::URLRequest*)nativeRequest |
+ redirectResponse:(NSURLResponse*)redirectResponse { |
+ DCHECK(_clientThread); |
+ if (!_protocol) |
+ return; |
+ [self performSelectorOnClientThread:@selector( |
+ wasRedirectedToRequestOnClientThread:) |
+ withObject:@[ request, redirectResponse ]]; |
+} |
+ |
+- (void)didFinishLoading { |
+ DCHECK(_clientThread); |
+ if (!_protocol) |
+ return; |
+ [self performSelectorOnClientThread:@selector(didFinishLoadingOnClientThread) |
+ withObject: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 { |
+ DCHECK([NSThread currentThread] == _clientThread); |
+ DCHECK(!_requestComplete || !_protocol); |
+ _requestComplete = YES; |
+ [[_protocol client] URLProtocol:_protocol didFailWithError:error]; |
+} |
+ |
+- (void)didLoadDataOnClientThread:(NSData*)data { |
+ DCHECK([NSThread currentThread] == _clientThread); |
+ DCHECK(!_requestComplete || !_protocol); |
+ [[_protocol client] URLProtocol:_protocol didLoadData:data]; |
+} |
+ |
+- (void)didReceiveResponseOnClientThread:(NSURLResponse*)response { |
+ DCHECK([NSThread currentThread] == _clientThread); |
+ DCHECK(!_requestComplete || !_protocol); |
+ [[_protocol client] URLProtocol:_protocol |
+ didReceiveResponse:response |
+ cacheStoragePolicy:NSURLCacheStorageNotAllowed]; |
+} |
+ |
+- (void)wasRedirectedToRequestOnClientThread:(NSArray*)params { |
+ DCHECK([NSThread currentThread] == _clientThread); |
+ DCHECK_EQ(2u, [params count]); |
+ DCHECK([params[0] isKindOfClass:[NSURLRequest class]]); |
+ DCHECK([params[1] isKindOfClass:[NSURLResponse class]]); |
+ DCHECK(!_requestComplete || !_protocol); |
+ [[_protocol client] URLProtocol:_protocol |
+ wasRedirectedToRequest:params[0] |
+ redirectResponse:params[1]]; |
+} |
+ |
+- (void)didFinishLoadingOnClientThread { |
+ DCHECK([NSThread currentThread] == _clientThread); |
+ DCHECK(!_requestComplete || !_protocol); |
+ _requestComplete = YES; |
+ [[_protocol client] URLProtocolDidFinishLoading:_protocol]; |
+} |
+ |
+@end |