Expose WebPageProxy identifier to the Network Process
[WebKit-https.git] / Source / WebKit / NetworkProcess / NetworkLoadChecker.cpp
1 /*
2  * Copyright (C) 2018-2019 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 #include "config.h"
27 #include "NetworkLoadChecker.h"
28
29 #include "Download.h"
30 #include "Logging.h"
31 #include "NetworkCORSPreflightChecker.h"
32 #include "NetworkProcess.h"
33 #include <WebCore/ContentRuleListResults.h>
34 #include <WebCore/ContentSecurityPolicy.h>
35 #include <WebCore/CrossOriginAccessControl.h>
36 #include <WebCore/CrossOriginPreflightResultCache.h>
37 #include <WebCore/SchemeRegistry.h>
38 #include <wtf/Scope.h>
39
40 #define RELEASE_LOG_IF_ALLOWED(fmt, ...) RELEASE_LOG_IF(m_sessionID.isAlwaysOnLoggingAllowed(), Network, "%p - NetworkLoadChecker::" fmt, this, ##__VA_ARGS__)
41
42 namespace WebKit {
43
44 using namespace WebCore;
45
46 static inline bool isSameOrigin(const URL& url, const SecurityOrigin* origin)
47 {
48     return url.protocolIsData() || url.protocolIsBlob() || !origin || origin->canRequest(url);
49 }
50
51 NetworkLoadChecker::NetworkLoadChecker(NetworkProcess& networkProcess, FetchOptions&& options, PAL::SessionID sessionID, WebPageProxyIdentifier webPageProxyID, HTTPHeaderMap&& originalRequestHeaders, URL&& url, RefPtr<SecurityOrigin>&& sourceOrigin, RefPtr<SecurityOrigin>&& topOrigin, PreflightPolicy preflightPolicy, String&& referrer, bool isHTTPSUpgradeEnabled, bool shouldCaptureExtraNetworkLoadMetrics, LoadType requestLoadType)
52     : m_options(WTFMove(options))
53     , m_sessionID(sessionID)
54     , m_networkProcess(networkProcess)
55     , m_webPageProxyID(webPageProxyID)
56     , m_originalRequestHeaders(WTFMove(originalRequestHeaders))
57     , m_url(WTFMove(url))
58     , m_origin(WTFMove(sourceOrigin))
59     , m_topOrigin(WTFMove(topOrigin))
60     , m_preflightPolicy(preflightPolicy)
61     , m_referrer(WTFMove(referrer))
62     , m_shouldCaptureExtraNetworkLoadMetrics(shouldCaptureExtraNetworkLoadMetrics)
63     , m_isHTTPSUpgradeEnabled(isHTTPSUpgradeEnabled)
64     , m_requestLoadType(requestLoadType)
65 {
66     m_isSameOriginRequest = isSameOrigin(m_url, m_origin.get());
67     switch (options.credentials) {
68     case FetchOptions::Credentials::Include:
69         m_storedCredentialsPolicy = StoredCredentialsPolicy::Use;
70         break;
71     case FetchOptions::Credentials::SameOrigin:
72         m_storedCredentialsPolicy = m_isSameOriginRequest ? StoredCredentialsPolicy::Use : StoredCredentialsPolicy::DoNotUse;
73         break;
74     case FetchOptions::Credentials::Omit:
75         m_storedCredentialsPolicy = StoredCredentialsPolicy::DoNotUse;
76         break;
77     }
78 }
79
80 NetworkLoadChecker::~NetworkLoadChecker() = default;
81
82 void NetworkLoadChecker::check(ResourceRequest&& request, ContentSecurityPolicyClient* client, ValidationHandler&& handler)
83 {
84     ASSERT(!isChecking());
85
86     if (m_shouldCaptureExtraNetworkLoadMetrics)
87         m_loadInformation.request = request;
88
89     m_firstRequestHeaders = request.httpHeaderFields();
90     checkRequest(WTFMove(request), client, WTFMove(handler));
91 }
92
93 static inline NetworkLoadChecker::RedirectionRequestOrError redirectionError(const ResourceResponse& redirectResponse, String&& errorMessage)
94 {
95     return makeUnexpected(ResourceError { String { }, 0, redirectResponse.url(), WTFMove(errorMessage), ResourceError::Type::AccessControl });
96 }
97
98 void NetworkLoadChecker::checkRedirection(ResourceRequest&& request, ResourceRequest&& redirectRequest, ResourceResponse&& redirectResponse, ContentSecurityPolicyClient* client, RedirectionValidationHandler&& handler)
99 {
100     ASSERT(!isChecking());
101
102     auto error = validateResponse(redirectResponse);
103     if (!error.isNull()) {
104         handler(redirectionError(redirectResponse, makeString("Cross-origin redirection to ", redirectRequest.url().string(), " denied by Cross-Origin Resource Sharing policy: ", error.localizedDescription())));
105         return;
106     }
107
108     if (m_options.redirect == FetchOptions::Redirect::Error) {
109         handler(redirectionError(redirectResponse, makeString("Not allowed to follow a redirection while loading ", redirectResponse.url().string())));
110         return;
111     }
112     if (m_options.redirect == FetchOptions::Redirect::Manual) {
113         handler(RedirectionTriplet { WTFMove(request), WTFMove(redirectRequest), WTFMove(redirectResponse) });
114         return;
115     }
116
117     // FIXME: We should check that redirections are only HTTP(s) as per fetch spec.
118     // See https://github.com/whatwg/fetch/issues/393
119
120     if (++m_redirectCount > 20) {
121         handler(redirectionError(redirectResponse, "Load cannot follow more than 20 redirections"_s));
122         return;
123     }
124
125     m_previousURL = WTFMove(m_url);
126     m_url = redirectRequest.url();
127
128     checkRequest(WTFMove(redirectRequest), client, [handler = WTFMove(handler), request = WTFMove(request), redirectResponse = WTFMove(redirectResponse)](auto&& result) mutable {
129         WTF::switchOn(result,
130             [&handler] (ResourceError& error) mutable {
131                 handler(makeUnexpected(WTFMove(error)));
132             },
133             [&handler, &request, &redirectResponse] (RedirectionTriplet& triplet) mutable {
134                 // FIXME: if checkRequest returns a RedirectionTriplet, it means the requested URL has changed and we should update the redirectResponse to match.
135                 handler(RedirectionTriplet { WTFMove(request), WTFMove(triplet.redirectRequest), WTFMove(redirectResponse) });
136             },
137             [&handler, &request, &redirectResponse] (ResourceRequest& redirectRequest) mutable {
138                 handler(RedirectionTriplet { WTFMove(request), WTFMove(redirectRequest), WTFMove(redirectResponse) });
139             }
140         );
141     });
142 }
143
144 ResourceError NetworkLoadChecker::validateResponse(ResourceResponse& response)
145 {
146     if (m_redirectCount)
147         response.setRedirected(true);
148
149     if (response.type() == ResourceResponse::Type::Opaqueredirect) {
150         response.setTainting(ResourceResponse::Tainting::Opaqueredirect);
151         return { };
152     }
153
154     if (m_options.mode == FetchOptions::Mode::Navigate || m_isSameOriginRequest) {
155         response.setTainting(ResourceResponse::Tainting::Basic);
156         return { };
157     }
158
159     if (m_options.mode == FetchOptions::Mode::NoCors) {
160         if (auto error = validateCrossOriginResourcePolicy(*m_origin, m_url, response))
161             return WTFMove(*error);
162
163         response.setTainting(ResourceResponse::Tainting::Opaque);
164         return { };
165     }
166
167     ASSERT(m_options.mode == FetchOptions::Mode::Cors);
168
169     // If we have a 304, the cached response is in WebProcess so we let WebProcess do the CORS check on the cached response.
170     if (response.httpStatusCode() == 304)
171         return { };
172
173     String errorMessage;
174     if (!passesAccessControlCheck(response, m_storedCredentialsPolicy, *m_origin, errorMessage))
175         return ResourceError { String { }, 0, m_url, WTFMove(errorMessage), ResourceError::Type::AccessControl };
176
177     response.setTainting(ResourceResponse::Tainting::Cors);
178     return { };
179 }
180
181 auto NetworkLoadChecker::accessControlErrorForValidationHandler(String&& message) -> RequestOrRedirectionTripletOrError
182 {
183     return ResourceError { String { }, 0, m_url, WTFMove(message), ResourceError::Type::AccessControl };
184 }
185
186 void NetworkLoadChecker::applyHTTPSUpgradeIfNeeded(ResourceRequest&& request, CompletionHandler<void(ResourceRequest&&)>&& handler) const
187 {
188 #if PLATFORM(COCOA)
189     if (!m_isHTTPSUpgradeEnabled || m_requestLoadType != LoadType::MainFrame) {
190         handler(WTFMove(request));
191         return;
192     }
193
194     auto& url = request.url();
195
196     // Only upgrade http urls.
197     if (!url.protocolIs("http")) {
198         handler(WTFMove(request));
199         return;
200     }
201
202     auto& httpsUpgradeChecker = m_networkProcess->networkHTTPSUpgradeChecker();
203
204     // Do not wait for httpsUpgradeChecker to complete its setup.
205     if (!httpsUpgradeChecker.didSetupCompleteSuccessfully()) {
206         handler(WTFMove(request));
207         return;
208     }
209
210     httpsUpgradeChecker.query(url.host().toString(), m_sessionID, [request = WTFMove(request), handler = WTFMove(handler)] (bool foundHost) mutable {
211         if (foundHost) {
212             auto newURL = request.url();
213             newURL.setProtocol("https"_s);
214             request.setURL(newURL);
215         }
216
217         handler(WTFMove(request));
218     });
219 #else
220     handler(WTFMove(request));
221 #endif
222 }
223
224 void NetworkLoadChecker::checkRequest(ResourceRequest&& request, ContentSecurityPolicyClient* client, ValidationHandler&& handler)
225 {
226     ResourceRequest originalRequest = request;
227
228     applyHTTPSUpgradeIfNeeded(WTFMove(request), [this, weakThis = makeWeakPtr(*this), client, handler = WTFMove(handler), originalRequest = WTFMove(originalRequest)](auto request) mutable {
229         if (!weakThis)
230             return handler({ ResourceError { ResourceError::Type::Cancellation }});
231
232         if (auto* contentSecurityPolicy = this->contentSecurityPolicy()) {
233             if (this->isRedirected()) {
234                 auto type = m_options.mode == FetchOptions::Mode::Navigate ? ContentSecurityPolicy::InsecureRequestType::Navigation : ContentSecurityPolicy::InsecureRequestType::Load;
235                 contentSecurityPolicy->upgradeInsecureRequestIfNeeded(request, type);
236             }
237             if (!this->isAllowedByContentSecurityPolicy(request, client)) {
238                 handler(this->accessControlErrorForValidationHandler("Blocked by Content Security Policy."_s));
239                 return;
240             }
241         }
242
243 #if ENABLE(CONTENT_EXTENSIONS)
244         this->processContentRuleListsForLoad(WTFMove(request), [this, weakThis = WTFMove(weakThis), handler = WTFMove(handler), originalRequest = WTFMove(originalRequest)](auto result) mutable {
245             if (!result.has_value()) {
246                 ASSERT(result.error().isCancellation());
247                 handler(WTFMove(result.error()));
248                 return;
249             }
250             if (result.value().results.summary.blockedLoad) {
251                 handler(this->accessControlErrorForValidationHandler("Blocked by content extension"_s));
252                 return;
253             }
254
255             if (!weakThis)
256                 return handler({ ResourceError { ResourceError::Type::Cancellation }});
257             this->continueCheckingRequestOrDoSyntheticRedirect(WTFMove(originalRequest), WTFMove(result.value().request), WTFMove(handler));
258         });
259 #else
260         this->continueCheckingRequestOrDoSyntheticRedirect(WTFMove(originalRequest), WTFMove(request), WTFMove(handler));
261 #endif
262     });
263 }
264
265 void NetworkLoadChecker::continueCheckingRequestOrDoSyntheticRedirect(ResourceRequest&& originalRequest, ResourceRequest&& currentRequest, ValidationHandler&& handler)
266 {
267     // If main frame load and request has been modified, trigger a synthetic redirect.
268     if (m_requestLoadType == LoadType::MainFrame && currentRequest.url() != originalRequest.url()) {
269         ResourceResponse redirectResponse = ResourceResponse::syntheticRedirectResponse(originalRequest.url(), currentRequest.url());
270         handler(RedirectionTriplet { WTFMove(originalRequest), WTFMove(currentRequest), WTFMove(redirectResponse) });
271         return;
272     }
273     this->continueCheckingRequest(WTFMove(currentRequest), WTFMove(handler));
274 }
275
276 bool NetworkLoadChecker::isAllowedByContentSecurityPolicy(const ResourceRequest& request, WebCore::ContentSecurityPolicyClient* client)
277 {
278     auto* contentSecurityPolicy = this->contentSecurityPolicy();
279     contentSecurityPolicy->setClient(client);
280     auto clearContentSecurityPolicyClient = makeScopeExit([&] {
281         contentSecurityPolicy->setClient(nullptr);
282     });
283
284     auto redirectResponseReceived = isRedirected() ? ContentSecurityPolicy::RedirectResponseReceived::Yes : ContentSecurityPolicy::RedirectResponseReceived::No;
285     switch (m_options.destination) {
286     case FetchOptions::Destination::Worker:
287     case FetchOptions::Destination::Serviceworker:
288     case FetchOptions::Destination::Sharedworker:
289         return contentSecurityPolicy->allowChildContextFromSource(request.url(), redirectResponseReceived);
290     case FetchOptions::Destination::Script:
291         if (request.requester() == ResourceRequest::Requester::ImportScripts && !contentSecurityPolicy->allowScriptFromSource(request.url(), redirectResponseReceived))
292             return false;
293         // FIXME: Check CSP for non-importScripts() initiated loads.
294         return true;
295     case FetchOptions::Destination::EmptyString:
296         return contentSecurityPolicy->allowConnectToSource(request.url(), redirectResponseReceived);
297     case FetchOptions::Destination::Audio:
298     case FetchOptions::Destination::Document:
299     case FetchOptions::Destination::Embed:
300     case FetchOptions::Destination::Font:
301     case FetchOptions::Destination::Image:
302     case FetchOptions::Destination::Manifest:
303     case FetchOptions::Destination::Object:
304     case FetchOptions::Destination::Report:
305     case FetchOptions::Destination::Style:
306     case FetchOptions::Destination::Track:
307     case FetchOptions::Destination::Video:
308     case FetchOptions::Destination::Xslt:
309         // FIXME: Check CSP for these destinations.
310         return true;
311     }
312     ASSERT_NOT_REACHED();
313     return true;
314 }
315
316 void NetworkLoadChecker::continueCheckingRequest(ResourceRequest&& request, ValidationHandler&& handler)
317 {
318     if (m_options.credentials == FetchOptions::Credentials::SameOrigin)
319         m_storedCredentialsPolicy = m_isSameOriginRequest && m_origin->canRequest(request.url()) ? StoredCredentialsPolicy::Use : StoredCredentialsPolicy::DoNotUse;
320
321     m_isSameOriginRequest = m_isSameOriginRequest && isSameOrigin(request.url(), m_origin.get());
322
323     if (doesNotNeedCORSCheck(request.url())) {
324         handler(WTFMove(request));
325         return;
326     }
327
328     if (m_options.mode == FetchOptions::Mode::SameOrigin) {
329         String message = makeString("Unsafe attempt to load URL ", request.url().stringCenterEllipsizedToLength(), " from origin ", m_origin->toString(), ". Domains, protocols and ports must match.\n");
330         handler(accessControlErrorForValidationHandler(WTFMove(message)));
331         return;
332     }
333
334     if (isRedirected()) {
335         RELEASE_LOG_IF_ALLOWED("checkRequest - Redirect requires CORS checks");
336         checkCORSRedirectedRequest(WTFMove(request), WTFMove(handler));
337         return;
338     }
339
340     checkCORSRequest(WTFMove(request), WTFMove(handler));
341 }
342
343 void NetworkLoadChecker::checkCORSRequest(ResourceRequest&& request, ValidationHandler&& handler)
344 {
345     ASSERT(m_options.mode == FetchOptions::Mode::Cors);
346
347     // Except in case where preflight is needed, loading should be able to continue on its own.
348     switch (m_preflightPolicy) {
349     case PreflightPolicy::Force:
350         checkCORSRequestWithPreflight(WTFMove(request), WTFMove(handler));
351         break;
352     case PreflightPolicy::Consider:
353         if (!m_isSimpleRequest || !isSimpleCrossOriginAccessRequest(request.httpMethod(), m_originalRequestHeaders)) {
354             checkCORSRequestWithPreflight(WTFMove(request), WTFMove(handler));
355             return;
356         }
357         FALLTHROUGH;
358     case PreflightPolicy::Prevent:
359         updateRequestForAccessControl(request, *m_origin, m_storedCredentialsPolicy);
360         handler(WTFMove(request));
361         break;
362     }
363 }
364
365 void NetworkLoadChecker::checkCORSRedirectedRequest(ResourceRequest&& request, ValidationHandler&& handler)
366 {
367     ASSERT(m_options.mode == FetchOptions::Mode::Cors);
368     ASSERT(isRedirected());
369
370     // Force any subsequent request to use these checks.
371     m_isSameOriginRequest = false;
372
373     if (!m_origin->canRequest(m_previousURL) && !protocolHostAndPortAreEqual(m_previousURL, request.url())) {
374         // Use a unique origin for subsequent loads if needed.
375         // https://fetch.spec.whatwg.org/#concept-http-redirect-fetch (Step 10).
376         if (!m_origin || !m_origin->isUnique())
377             m_origin = SecurityOrigin::createUnique();
378     }
379
380     // FIXME: We should set the request referrer according the referrer policy.
381
382     // Let's fetch the request with the original headers (equivalent to request cloning specified by fetch algorithm).
383     if (!request.httpHeaderFields().contains(HTTPHeaderName::Authorization))
384         m_firstRequestHeaders.remove(HTTPHeaderName::Authorization);
385     request.setHTTPHeaderFields(m_firstRequestHeaders);
386
387     checkCORSRequest(WTFMove(request), WTFMove(handler));
388 }
389
390 void NetworkLoadChecker::checkCORSRequestWithPreflight(ResourceRequest&& request, ValidationHandler&& handler)
391 {
392     ASSERT(m_options.mode == FetchOptions::Mode::Cors);
393
394     m_isSimpleRequest = false;
395     // FIXME: We should probably partition preflight result cache by session ID.
396     if (CrossOriginPreflightResultCache::singleton().canSkipPreflight(m_origin->toString(), request.url(), m_storedCredentialsPolicy, request.httpMethod(), m_originalRequestHeaders)) {
397         RELEASE_LOG_IF_ALLOWED("checkCORSRequestWithPreflight - preflight can be skipped thanks to cached result");
398         updateRequestForAccessControl(request, *m_origin, m_storedCredentialsPolicy);
399         handler(WTFMove(request));
400         return;
401     }
402
403     auto requestForPreflight = request;
404     // We need to set header fields to m_originalRequestHeaders to correctly compute Access-Control-Request-Headers header value.
405     requestForPreflight.setHTTPHeaderFields(m_originalRequestHeaders);
406     NetworkCORSPreflightChecker::Parameters parameters = {
407         WTFMove(requestForPreflight),
408         *m_origin,
409         m_topOrigin,
410         request.httpReferrer(),
411         request.httpUserAgent(),
412         m_sessionID,
413         m_webPageProxyID,
414         m_storedCredentialsPolicy
415     };
416     m_corsPreflightChecker = makeUnique<NetworkCORSPreflightChecker>(m_networkProcess.get(), WTFMove(parameters), m_shouldCaptureExtraNetworkLoadMetrics, [this, request = WTFMove(request), handler = WTFMove(handler), isRedirected = isRedirected()](auto&& error) mutable {
417         RELEASE_LOG_IF_ALLOWED("checkCORSRequestWithPreflight - makeCrossOriginAccessRequestWithPreflight preflight complete, success: %d forRedirect? %d", error.isNull(), isRedirected);
418
419         if (!error.isNull()) {
420             handler(WTFMove(error));
421             return;
422         }
423
424         if (m_shouldCaptureExtraNetworkLoadMetrics)
425             m_loadInformation.transactions.append(m_corsPreflightChecker->takeInformation());
426
427         auto corsPreflightChecker = WTFMove(m_corsPreflightChecker);
428         updateRequestForAccessControl(request, *m_origin, m_storedCredentialsPolicy);
429         handler(WTFMove(request));
430     });
431     m_corsPreflightChecker->startPreflight();
432 }
433
434 bool NetworkLoadChecker::doesNotNeedCORSCheck(const URL& url) const
435 {
436     if (m_options.mode == FetchOptions::Mode::NoCors || m_options.mode == FetchOptions::Mode::Navigate)
437         return true;
438
439     if (!SchemeRegistry::shouldTreatURLSchemeAsCORSEnabled(url.protocol().toStringWithoutCopying()))
440         return true;
441
442     return m_isSameOriginRequest;
443 }
444
445 ContentSecurityPolicy* NetworkLoadChecker::contentSecurityPolicy()
446 {
447     if (!m_contentSecurityPolicy && m_cspResponseHeaders) {
448         // FIXME: Pass the URL of the protected resource instead of its origin.
449         m_contentSecurityPolicy = makeUnique<ContentSecurityPolicy>(URL { URL { }, m_origin->toString() });
450         m_contentSecurityPolicy->didReceiveHeaders(*m_cspResponseHeaders, String { m_referrer }, ContentSecurityPolicy::ReportParsingErrors::No);
451     }
452     return m_contentSecurityPolicy.get();
453 }
454
455 #if ENABLE(CONTENT_EXTENSIONS)
456 void NetworkLoadChecker::processContentRuleListsForLoad(ResourceRequest&& request, ContentExtensionCallback&& callback)
457 {
458     // FIXME: Enable content blockers for navigation loads.
459     if (!m_checkContentExtensions || !m_userContentControllerIdentifier || m_options.mode == FetchOptions::Mode::Navigate) {
460         ContentRuleListResults results;
461         callback(ContentExtensionResult { WTFMove(request), results });
462         return;
463     }
464
465     m_networkProcess->networkContentRuleListManager().contentExtensionsBackend(*m_userContentControllerIdentifier, [this, weakThis = makeWeakPtr(this), request = WTFMove(request), callback = WTFMove(callback)](auto& backend) mutable {
466         if (!weakThis) {
467             callback(makeUnexpected(ResourceError { ResourceError::Type::Cancellation }));
468             return;
469         }
470
471         auto results = backend.processContentRuleListsForPingLoad(request.url(), m_mainDocumentURL);
472         WebCore::ContentExtensions::applyResultsToRequest(ContentRuleListResults { results }, nullptr, request);
473         callback(ContentExtensionResult { WTFMove(request), results });
474     });
475 }
476 #endif // ENABLE(CONTENT_EXTENSIONS)
477
478 void NetworkLoadChecker::storeRedirectionIfNeeded(const ResourceRequest& request, const ResourceResponse& response)
479 {
480     if (!m_shouldCaptureExtraNetworkLoadMetrics)
481         return;
482     m_loadInformation.transactions.append(NetworkTransactionInformation { NetworkTransactionInformation::Type::Redirection, ResourceRequest { request }, ResourceResponse { response }, { } });
483 }
484
485 } // namespace WebKit
486
487 #undef RELEASE_LOG_IF_ALLOWED