Roll src/third_party/WebKit 9f7fb92:f103b33 (svn 202621:202622)
[chromium-blink-merge.git] / ios / net / crn_http_protocol_handler_proxy_with_client_thread.mm
blobd6b99b0803a397ae727be2f6dbe3887d8529d649
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;
32   // The request URL.
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
37   // after completion.
38   BOOL _requestComplete;
39   BOOL _paused;
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;
56 @end
58 @implementation CRNHTTPProtocolHandlerProxyWithClientThread
60 - (instancetype)initWithProtocol:(NSURLProtocol*)protocol
61                     clientThread:(NSThread*)clientThread
62                      runLoopMode:(NSString*)mode {
63   DCHECK(protocol);
64   DCHECK(clientThread);
65   if ((self = [super init])) {
66     _protocol = protocol;
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
72     // later on.
73     if ([mode isEqualToString:NSRunLoopCommonModes])
74       _runLoopModes.reset([@[ NSRunLoopCommonModes ] retain]);
75     else
76       _runLoopModes.reset([@[ mode, NSRunLoopCommonModes ] retain]);
77     _queuedInvocations.reset([[NSMutableArray alloc] init]);
78   }
79   return self;
82 - (void)invalidate {
83   DCHECK([NSThread currentThread] == _clientThread);
84   _protocol = nil;
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|.
99   //
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.
109     [invocation invoke];
110     [_queuedInvocations removeObjectAtIndex:0];
111   }
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];
118   DCHECK(sig != nil);
119   NSInvocation* inv = [NSInvocation invocationWithMethodSignature:sig];
120   [inv setTarget:self];
121   [inv setSelector:aSelector];
122   [inv retainArguments];
124   size_t arg_index = 2;
125   va_list args;
126   va_start(args, aSelector);
127   NSObject* arg = va_arg(args, NSObject*);
128   while (arg != nil) {
129     [inv setArgument:&arg atIndex:arg_index];
130     arg = va_arg(args, NSObject*);
131     arg_index++;
132   }
133   va_end(args);
135   DCHECK(arg_index == sig.numberOfArguments);
136   [self performSelector:@selector(invokeOnClientThread:)
137                onThread:_clientThread
138              withObject:inv
139           waitUntilDone:NO
140                   modes:_runLoopModes];
143 - (void)invokeOnClientThread:(NSInvocation*)invocation {
144   DCHECK([NSThread currentThread] == _clientThread);
145   DCHECK(!_requestComplete || !_protocol);
146   if (!_paused) {
147     [invocation invoke];
148   } else {
149     [_queuedInvocations addObject:invocation];
150   }
153 #pragma mark Proxy methods called from any thread.
155 - (void)didFailWithNSErrorCode:(NSInteger)nsErrorCode
156                   netErrorCode:(int)netErrorCode {
157   DCHECK(_clientThread);
158   if (!_protocol)
159     return;
160   NSError* error =
161       net::GetIOSError(nsErrorCode, netErrorCode, _url, _creationTime);
162   [self postToClientThread:@selector(didFailWithErrorOnClientThread:), error,
163                            nil];
166 - (void)didLoadData:(NSData*)data {
167   DCHECK(_clientThread);
168   if (!_protocol)
169     return;
170   [self postToClientThread:@selector(didLoadDataOnClientThread:), data, nil];
173 - (void)didReceiveResponse:(NSURLResponse*)response {
174   DCHECK(_clientThread);
175   if (!_protocol)
176     return;
177   [self postToClientThread:@selector(didReceiveResponseOnClientThread:),
178                            response, nil];
181 - (void)wasRedirectedToRequest:(NSURLRequest*)request
182                  nativeRequest:(net::URLRequest*)nativeRequest
183               redirectResponse:(NSURLResponse*)redirectResponse {
184   DCHECK(_clientThread);
185   if (!_protocol)
186     return;
187   [self postToClientThread:@selector(wasRedirectedToRequestOnClientThread:
188                                                          redirectResponse:),
189                            request, redirectResponse, nil];
192 - (void)didFinishLoading {
193   DCHECK(_clientThread);
194   if (!_protocol)
195     return;
196   [self postToClientThread:@selector(didFinishLoadingOnClientThread), nil];
199 // Feature support methods that don't forward to the NSURLProtocolClient.
200 - (void)didCreateNativeRequest:(net::URLRequest*)nativeRequest {
201   // no-op.
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 {
213   // no-op.
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];
250 - (void)pause {
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.
254   _paused = YES;
257 - (void)resume {
258   DCHECK([NSThread currentThread] == _clientThread);
259   DCHECK(!_requestComplete || !_protocol);
260   _paused = NO;
261   [self runInvocationQueueOnClientThread];
264 @end