1 // Copyright 2012 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
5 #import "ios/net/crn_http_protocol_handler_proxy_with_client_thread.h"
7 #include "base/logging.h"
8 #import "base/mac/scoped_nsobject.h"
9 #include "base/time/time.h"
10 #import "ios/net/protocol_handler_util.h"
11 #include "net/base/auth.h"
12 #include "net/url_request/url_request.h"
14 // When the protocol is invalidated, no synchronization (lock) is needed:
15 // - The actual calls to the protocol and its invalidation are all done on
16 // clientThread_ and thus are serialized.
17 // - When a proxy method is called, the protocol is compared to nil. There may
18 // be a conflict at this point, in the case the protocol is being invalidated
19 // during this comparison. However, in such a case, the actual value of the
20 // pointer does not matter: an invalid pointer will behave as a valid one and
21 // post a task on the clientThread_, and that task will be handled correctly,
22 // as described by the item above.
24 @interface CRNHTTPProtocolHandlerProxyWithClientThread () {
25 __weak NSURLProtocol* _protocol;
26 // Thread used to call the client back.
27 // This thread does not have a base::MessageLoop, and thus does not work with
28 // the usual task posting functions.
29 __weak NSThread* _clientThread;
30 // The run loop modes to use when posting tasks to |clientThread_|.
31 base::scoped_nsobject<NSArray> _runLoopModes;
33 base::scoped_nsobject<NSString> _url;
34 // The creation time of the request.
35 base::Time _creationTime;
36 // |requestComplete_| is used in debug to check that the client is not called
38 BOOL _requestComplete;
41 base::scoped_nsobject<NSMutableArray> _queuedInvocations;
44 // Performs the selector on |clientThread_| using |runLoopModes_|.
45 - (void)runInvocationQueueOnClientThread;
46 - (void)postToClientThread:(SEL)aSelector, ... NS_REQUIRES_NIL_TERMINATION;
47 - (void)invokeOnClientThread:(NSInvocation*)invocation;
48 // These functions are just wrappers around the corresponding
49 // NSURLProtocolClient methods, used for task posting.
50 - (void)didFailWithErrorOnClientThread:(NSError*)error;
51 - (void)didLoadDataOnClientThread:(NSData*)data;
52 - (void)didReceiveResponseOnClientThread:(NSURLResponse*)response;
53 - (void)wasRedirectedToRequestOnClientThread:(NSURLRequest*)request
54 redirectResponse:(NSURLResponse*)response;
55 - (void)didFinishLoadingOnClientThread;
58 @implementation CRNHTTPProtocolHandlerProxyWithClientThread
60 - (instancetype)initWithProtocol:(NSURLProtocol*)protocol
61 clientThread:(NSThread*)clientThread
62 runLoopMode:(NSString*)mode {
65 if ((self = [super init])) {
67 _url.reset([[[[protocol request] URL] absoluteString] copy]);
68 _creationTime = base::Time::Now();
69 _clientThread = clientThread;
70 // Use the common run loop mode in addition to the client thread mode, in
71 // hope that our tasks are executed even if the client thread changes mode
73 if ([mode isEqualToString:NSRunLoopCommonModes])
74 _runLoopModes.reset([@[ NSRunLoopCommonModes ] retain]);
76 _runLoopModes.reset([@[ mode, NSRunLoopCommonModes ] retain]);
77 _queuedInvocations.reset([[NSMutableArray alloc] init]);
83 DCHECK([NSThread currentThread] == _clientThread);
85 _requestComplete = YES;
86 // Note that there may still be queued invocations here, if the chrome network
87 // stack continues to emit events after the system network stack has paused
88 // the request, and then the system network stack destroys the request.
89 _queuedInvocations.reset();
92 - (void)runInvocationQueueOnClientThread {
93 DCHECK([NSThread currentThread] == _clientThread);
94 DCHECK(!_requestComplete || !_protocol);
95 // Each of the queued invocations may cause the system network stack to pause
96 // this request, in which case |runInvocationQueueOnClientThread| should
97 // immediately stop running further queued invocations. The queue will be
98 // drained again the next time the system network stack calls |resume|.
100 // Specifically, the system stack can call back into |pause| with this
101 // function still on the call stack. However, since new invocations are
102 // enqueued on this thread via posted invocations, no new invocations can be
103 // added while this function is running.
104 while (!_paused && _queuedInvocations.get().count > 0) {
105 NSInvocation* invocation = [_queuedInvocations objectAtIndex:0];
106 // Since |_queuedInvocations| owns the only reference to each queued
107 // invocation, this function has to retain another reference before removing
108 // the queued invocation from the array.
110 [_queuedInvocations removeObjectAtIndex:0];
114 - (void)postToClientThread:(SEL)aSelector, ... {
115 // Build an NSInvocation representing an invocation of |aSelector| on |self|
116 // with the supplied varargs passed as arguments to the invocation.
117 NSMethodSignature* sig = [self methodSignatureForSelector:aSelector];
119 NSInvocation* inv = [NSInvocation invocationWithMethodSignature:sig];
120 [inv setTarget:self];
121 [inv setSelector:aSelector];
122 [inv retainArguments];
124 size_t arg_index = 2;
126 va_start(args, aSelector);
127 NSObject* arg = va_arg(args, NSObject*);
129 [inv setArgument:&arg atIndex:arg_index];
130 arg = va_arg(args, NSObject*);
135 DCHECK(arg_index == sig.numberOfArguments);
136 [self performSelector:@selector(invokeOnClientThread:)
137 onThread:_clientThread
140 modes:_runLoopModes];
143 - (void)invokeOnClientThread:(NSInvocation*)invocation {
144 DCHECK([NSThread currentThread] == _clientThread);
145 DCHECK(!_requestComplete || !_protocol);
149 [_queuedInvocations addObject:invocation];
153 #pragma mark Proxy methods called from any thread.
155 - (void)didFailWithNSErrorCode:(NSInteger)nsErrorCode
156 netErrorCode:(int)netErrorCode {
157 DCHECK(_clientThread);
161 net::GetIOSError(nsErrorCode, netErrorCode, _url, _creationTime);
162 [self postToClientThread:@selector(didFailWithErrorOnClientThread:), error,
166 - (void)didLoadData:(NSData*)data {
167 DCHECK(_clientThread);
170 [self postToClientThread:@selector(didLoadDataOnClientThread:), data, nil];
173 - (void)didReceiveResponse:(NSURLResponse*)response {
174 DCHECK(_clientThread);
177 [self postToClientThread:@selector(didReceiveResponseOnClientThread:),
181 - (void)wasRedirectedToRequest:(NSURLRequest*)request
182 nativeRequest:(net::URLRequest*)nativeRequest
183 redirectResponse:(NSURLResponse*)redirectResponse {
184 DCHECK(_clientThread);
187 [self postToClientThread:@selector(wasRedirectedToRequestOnClientThread:
189 request, redirectResponse, nil];
192 - (void)didFinishLoading {
193 DCHECK(_clientThread);
196 [self postToClientThread:@selector(didFinishLoadingOnClientThread), nil];
199 // Feature support methods that don't forward to the NSURLProtocolClient.
200 - (void)didCreateNativeRequest:(net::URLRequest*)nativeRequest {
204 - (void)didRecieveAuthChallenge:(net::AuthChallengeInfo*)authInfo
205 nativeRequest:(const net::URLRequest&)nativeRequest
206 callback:(const network_client::AuthCallback&)callback {
207 // If we get this far, authentication has failed.
208 base::string16 empty;
209 callback.Run(false, empty, empty);
212 - (void)cancelAuthRequest {
216 - (void)setUnderlyingClient:(id<CRNNetworkClientProtocol>)underlyingClient {
217 // This is the lowest level.
218 DCHECK(!underlyingClient);
221 #pragma mark Proxy methods called from the client thread.
223 - (void)didFailWithErrorOnClientThread:(NSError*)error {
224 _requestComplete = YES;
225 [[_protocol client] URLProtocol:_protocol didFailWithError:error];
228 - (void)didLoadDataOnClientThread:(NSData*)data {
229 [[_protocol client] URLProtocol:_protocol didLoadData:data];
232 - (void)didReceiveResponseOnClientThread:(NSURLResponse*)response {
233 [[_protocol client] URLProtocol:_protocol
234 didReceiveResponse:response
235 cacheStoragePolicy:NSURLCacheStorageNotAllowed];
238 - (void)wasRedirectedToRequestOnClientThread:(NSURLRequest*)request
239 redirectResponse:(NSURLResponse*)redirectResponse {
240 [[_protocol client] URLProtocol:_protocol
241 wasRedirectedToRequest:request
242 redirectResponse:redirectResponse];
245 - (void)didFinishLoadingOnClientThread {
246 _requestComplete = YES;
247 [[_protocol client] URLProtocolDidFinishLoading:_protocol];
251 DCHECK([NSThread currentThread] == _clientThread);
252 // It's legal (in fact, required) for |pause| to be called after the request
253 // has already finished, so the usual invalidation DCHECK is missing here.
258 DCHECK([NSThread currentThread] == _clientThread);
259 DCHECK(!_requestComplete || !_protocol);
261 [self runInvocationQueueOnClientThread];