[Resource Timing] Gather timing information with reliable responseEnd time
[WebKit-https.git] / Source / WebCore / platform / network / mac / WebCoreResourceHandleAsOperationQueueDelegate.mm
1 /*
2  * Copyright (C) 2004, 2006, 2007, 2008, 2009, 2010, 2011, 2012, 2013 Apple Inc. All rights reserved.
3  *
4  * Redistribution and use in source and binary forms, with or without
5  * modification, are permitted provided that the following conditions
6  * are met:
7  * 1. Redistributions of source code must retain the above copyright
8  *    notice, this list of conditions and the following disclaimer.
9  * 2. Redistributions in binary form must reproduce the above copyright
10  *    notice, this list of conditions and the following disclaimer in the
11  *    documentation and/or other materials provided with the distribution.
12  *
13  * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS''
14  * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
15  * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
16  * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS
17  * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
18  * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
19  * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
20  * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
21  * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
22  * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
23  * THE POSSIBILITY OF SUCH DAMAGE.
24  */
25
26 #import "config.h"
27 #import "WebCoreResourceHandleAsOperationQueueDelegate.h"
28
29 #if !USE(CFURLCONNECTION)
30
31 #import "AuthenticationChallenge.h"
32 #import "AuthenticationMac.h"
33 #import "Logging.h"
34 #import "NSURLRequestSPI.h"
35 #import "ResourceHandle.h"
36 #import "ResourceHandleClient.h"
37 #import "ResourceRequest.h"
38 #import "ResourceResponse.h"
39 #import "SharedBuffer.h"
40 #import "WebCoreURLResponse.h"
41 #import <wtf/MainThread.h>
42
43 using namespace WebCore;
44
45 @implementation WebCoreResourceHandleAsOperationQueueDelegate
46
47 - (id)initWithHandle:(ResourceHandle*)handle
48 {
49     self = [self init];
50     if (!self)
51         return nil;
52
53     m_handle = handle;
54     m_semaphore = dispatch_semaphore_create(0);
55
56     return self;
57 }
58
59 - (void)detachHandle
60 {
61     m_handle = 0;
62
63     m_requestResult = nullptr;
64     m_cachedResponseResult = nullptr;
65     m_boolResult = NO;
66     dispatch_semaphore_signal(m_semaphore); // OK to signal even if we are not waiting.
67 }
68
69 - (void)dealloc
70 {
71     dispatch_release(m_semaphore);
72     [super dealloc];
73 }
74
75 - (void)continueWillSendRequest:(NSURLRequest *)newRequest
76 {
77     m_requestResult = newRequest;
78     dispatch_semaphore_signal(m_semaphore);
79 }
80
81 - (void)continueDidReceiveResponse
82 {
83     dispatch_semaphore_signal(m_semaphore);
84 }
85
86 - (void)continueCanAuthenticateAgainstProtectionSpace:(BOOL)canAuthenticate
87 {
88     m_boolResult = canAuthenticate;
89     dispatch_semaphore_signal(m_semaphore);
90 }
91
92 - (void)continueWillCacheResponse:(NSCachedURLResponse *)response
93 {
94     m_cachedResponseResult = response;
95     dispatch_semaphore_signal(m_semaphore);
96 }
97
98 - (NSURLRequest *)connection:(NSURLConnection *)connection willSendRequest:(NSURLRequest *)newRequest redirectResponse:(NSURLResponse *)redirectResponse
99 {
100     ASSERT(!isMainThread());
101     UNUSED_PARAM(connection);
102
103     redirectResponse = synthesizeRedirectResponseIfNecessary([connection currentRequest], newRequest, redirectResponse);
104
105     // See <rdar://problem/5380697>. This is a workaround for a behavior change in CFNetwork where willSendRequest gets called more often.
106     if (!redirectResponse)
107         return newRequest;
108
109 #if !LOG_DISABLED
110     if ([redirectResponse isKindOfClass:[NSHTTPURLResponse class]])
111         LOG(Network, "Handle %p delegate connection:%p willSendRequest:%@ redirectResponse:%d, Location:<%@>", m_handle, connection, [newRequest description], static_cast<int>([(id)redirectResponse statusCode]), [[(id)redirectResponse allHeaderFields] objectForKey:@"Location"]);
112     else
113         LOG(Network, "Handle %p delegate connection:%p willSendRequest:%@ redirectResponse:non-HTTP", m_handle, connection, [newRequest description]); 
114 #endif
115
116     RetainPtr<id> protector(self);
117
118     dispatch_async(dispatch_get_main_queue(), ^{
119         if (!m_handle) {
120             m_requestResult = nullptr;
121             dispatch_semaphore_signal(m_semaphore);
122             return;
123         }
124
125         m_handle->willSendRequest(newRequest, redirectResponse);
126     });
127
128     dispatch_semaphore_wait(m_semaphore, DISPATCH_TIME_FOREVER);
129     return m_requestResult.autorelease();
130 }
131
132 - (void)connection:(NSURLConnection *)connection didReceiveAuthenticationChallenge:(NSURLAuthenticationChallenge *)challenge
133 {
134     ASSERT(!isMainThread());
135     UNUSED_PARAM(connection);
136
137     LOG(Network, "Handle %p delegate connection:%p didReceiveAuthenticationChallenge:%p", m_handle, connection, challenge);
138
139     dispatch_async(dispatch_get_main_queue(), ^{
140         if (!m_handle) {
141             [[challenge sender] cancelAuthenticationChallenge:challenge];
142             return;
143         }
144         m_handle->didReceiveAuthenticationChallenge(core(challenge));
145     });
146 }
147
148 #if USE(PROTECTION_SPACE_AUTH_CALLBACK)
149 - (BOOL)connection:(NSURLConnection *)connection canAuthenticateAgainstProtectionSpace:(NSURLProtectionSpace *)protectionSpace
150 {
151     ASSERT(!isMainThread());
152     UNUSED_PARAM(connection);
153
154     LOG(Network, "Handle %p delegate connection:%p canAuthenticateAgainstProtectionSpace:%@://%@:%u realm:%@ method:%@ %@%@", m_handle, connection, [protectionSpace protocol], [protectionSpace host], [protectionSpace port], [protectionSpace realm], [protectionSpace authenticationMethod], [protectionSpace isProxy] ? @"proxy:" : @"", [protectionSpace isProxy] ? [protectionSpace proxyType] : @"");
155
156     RetainPtr<id> protector(self);
157
158     dispatch_async(dispatch_get_main_queue(), ^{
159         if (!m_handle) {
160             m_boolResult = NO;
161             dispatch_semaphore_signal(m_semaphore);
162             return;
163         }
164         m_handle->canAuthenticateAgainstProtectionSpace(ProtectionSpace(protectionSpace));
165     });
166
167     dispatch_semaphore_wait(m_semaphore, DISPATCH_TIME_FOREVER);
168     return m_boolResult;
169 }
170 #endif
171
172 - (void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)r
173 {
174     ASSERT(!isMainThread());
175
176     LOG(Network, "Handle %p delegate connection:%p didReceiveResponse:%p (HTTP status %d, reported MIMEType '%s')", m_handle, connection, r, [r respondsToSelector:@selector(statusCode)] ? [(id)r statusCode] : 0, [[r MIMEType] UTF8String]);
177
178     RetainPtr<id> protector(self);
179
180     dispatch_async(dispatch_get_main_queue(), ^{
181         if (!m_handle || !m_handle->client()) {
182             dispatch_semaphore_signal(m_semaphore);
183             return;
184         }
185
186         // Avoid MIME type sniffing if the response comes back as 304 Not Modified.
187         int statusCode = [r respondsToSelector:@selector(statusCode)] ? [(id)r statusCode] : 0;
188         if (statusCode != 304) {
189             bool isMainResourceLoad = m_handle->firstRequest().requester() == ResourceRequest::Requester::Main;
190             adjustMIMETypeIfNecessary([r _CFURLResponse], isMainResourceLoad);
191         }
192
193         if ([m_handle->firstRequest().nsURLRequest(DoNotUpdateHTTPBody) _propertyForKey:@"ForceHTMLMIMEType"])
194             [r _setMIMEType:@"text/html"];
195         
196         ResourceResponse resourceResponse(r);
197 #if ENABLE(WEB_TIMING)
198         ResourceHandle::getConnectionTimingData(connection, resourceResponse.deprecatedNetworkLoadMetrics());
199 #else
200         UNUSED_PARAM(connection);
201 #endif
202         m_handle->didReceiveResponse(WTFMove(resourceResponse));
203     });
204
205     dispatch_semaphore_wait(m_semaphore, DISPATCH_TIME_FOREVER);
206 }
207
208 #if USE(NETWORK_CFDATA_ARRAY_CALLBACK)
209 - (void)connection:(NSURLConnection *)connection didReceiveDataArray:(NSArray *)dataArray
210 {
211     ASSERT(!isMainThread());
212     UNUSED_PARAM(connection);
213
214     LOG(Network, "Handle %p delegate connection:%p didReceiveDataArray:%p arraySize:%d", m_handle, connection, dataArray, [dataArray count]);
215
216     dispatch_async(dispatch_get_main_queue(), ^{
217         if (!dataArray)
218             return;
219
220         if (!m_handle || !m_handle->client())
221             return;
222
223         m_handle->client()->didReceiveBuffer(m_handle, SharedBuffer::wrapCFDataArray(reinterpret_cast<CFArrayRef>(dataArray)), -1);
224         // The call to didReceiveData above can cancel a load, and if so, the delegate (self) could have been deallocated by this point.
225     });
226 }
227 #endif
228
229 - (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data lengthReceived:(long long)lengthReceived
230 {
231     ASSERT(!isMainThread());
232     UNUSED_PARAM(connection);
233     UNUSED_PARAM(lengthReceived);
234
235     LOG(Network, "Handle %p delegate connection:%p didReceiveData:%p lengthReceived:%lld", m_handle, connection, data, lengthReceived);
236
237     dispatch_async(dispatch_get_main_queue(), ^{
238         if (!m_handle || !m_handle->client())
239             return;
240         // FIXME: If we get more than 2B bytes in a single chunk, this code won't do the right thing.
241         // However, with today's computers and networking speeds, this won't happen in practice.
242         // Could be an issue with a giant local file.
243
244         // FIXME: https://bugs.webkit.org/show_bug.cgi?id=19793
245         // -1 means we do not provide any data about transfer size to inspector so it would use
246         // Content-Length headers or content size to show transfer size.
247         m_handle->client()->didReceiveBuffer(m_handle, SharedBuffer::wrapNSData(data), -1);
248     });
249 }
250
251 - (void)connection:(NSURLConnection *)connection didSendBodyData:(NSInteger)bytesWritten totalBytesWritten:(NSInteger)totalBytesWritten totalBytesExpectedToWrite:(NSInteger)totalBytesExpectedToWrite
252 {
253     ASSERT(!isMainThread());
254     UNUSED_PARAM(connection);
255     UNUSED_PARAM(bytesWritten);
256
257     LOG(Network, "Handle %p delegate connection:%p didSendBodyData:%d totalBytesWritten:%d totalBytesExpectedToWrite:%d", m_handle, connection, bytesWritten, totalBytesWritten, totalBytesExpectedToWrite);
258
259     dispatch_async(dispatch_get_main_queue(), ^{
260         if (!m_handle || !m_handle->client())
261             return;
262         m_handle->client()->didSendData(m_handle, totalBytesWritten, totalBytesExpectedToWrite);
263     });
264 }
265
266 - (void)connectionDidFinishLoading:(NSURLConnection *)connection
267 {
268     ASSERT(!isMainThread());
269     UNUSED_PARAM(connection);
270
271     LOG(Network, "Handle %p delegate connectionDidFinishLoading:%p", m_handle, connection);
272
273     dispatch_async(dispatch_get_main_queue(), ^{
274         if (!m_handle || !m_handle->client())
275             return;
276
277         m_handle->client()->didFinishLoading(m_handle);
278     });
279 }
280
281 - (void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error
282 {
283     ASSERT(!isMainThread());
284     UNUSED_PARAM(connection);
285
286     LOG(Network, "Handle %p delegate connection:%p didFailWithError:%@", m_handle, connection, error);
287
288     dispatch_async(dispatch_get_main_queue(), ^{
289         if (!m_handle || !m_handle->client())
290             return;
291
292         m_handle->client()->didFail(m_handle, error);
293     });
294 }
295
296
297 - (NSCachedURLResponse *)connection:(NSURLConnection *)connection willCacheResponse:(NSCachedURLResponse *)cachedResponse
298 {
299     ASSERT(!isMainThread());
300     UNUSED_PARAM(connection);
301
302     LOG(Network, "Handle %p delegate connection:%p willCacheResponse:%p", m_handle, connection, cachedResponse);
303
304     RetainPtr<id> protector(self);
305
306     dispatch_async(dispatch_get_main_queue(), ^{
307         if (!m_handle || !m_handle->client()) {
308             m_cachedResponseResult = nullptr;
309             dispatch_semaphore_signal(m_semaphore);
310             return;
311         }
312
313         m_handle->client()->willCacheResponseAsync(m_handle, cachedResponse);
314     });
315
316     dispatch_semaphore_wait(m_semaphore, DISPATCH_TIME_FOREVER);
317     return m_cachedResponseResult.autorelease();
318 }
319
320 @end
321
322 @implementation WebCoreResourceHandleWithCredentialStorageAsOperationQueueDelegate
323
324 - (BOOL)connectionShouldUseCredentialStorage:(NSURLConnection *)connection
325 {
326     ASSERT(!isMainThread());
327     UNUSED_PARAM(connection);
328     return NO;
329 }
330
331 @end
332
333 #endif // !USE(CFURLCONNECTION)
334