Simplify calls to LoaderStrategy::startPingLoad()
[WebKit-https.git] / Source / WebKitLegacy / WebCoreSupport / WebResourceLoadScheduler.cpp
1 /*
2     Copyright (C) 1998 Lars Knoll (knoll@mpi-hd.mpg.de)
3     Copyright (C) 2001 Dirk Mueller (mueller@kde.org)
4     Copyright (C) 2002 Waldo Bastian (bastian@kde.org)
5     Copyright (C) 2006 Samuel Weinig (sam.weinig@gmail.com)
6     Copyright (C) 2004-2008, 2015 Apple Inc. All rights reserved.
7     Copyright (C) 2010 Google Inc. All rights reserved.
8
9     This library is free software; you can redistribute it and/or
10     modify it under the terms of the GNU Library General Public
11     License as published by the Free Software Foundation; either
12     version 2 of the License, or (at your option) any later version.
13
14     This library is distributed in the hope that it will be useful,
15     but WITHOUT ANY WARRANTY; without even the implied warranty of
16     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
17     Library General Public License for more details.
18
19     You should have received a copy of the GNU Library General Public License
20     along with this library; see the file COPYING.LIB.  If not, write to
21     the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
22     Boston, MA 02110-1301, USA.
23  */
24
25 #include "WebResourceLoadScheduler.h"
26
27 #include <WebCore/Document.h>
28 #include <WebCore/DocumentLoader.h>
29 #include <WebCore/FetchOptions.h>
30 #include <WebCore/Frame.h>
31 #include <WebCore/FrameLoader.h>
32 #include <WebCore/NetscapePlugInStreamLoader.h>
33 #include <WebCore/PingHandle.h>
34 #include <WebCore/PlatformStrategies.h>
35 #include <WebCore/ResourceHandle.h>
36 #include <WebCore/ResourceRequest.h>
37 #include <WebCore/SubresourceLoader.h>
38 #include <WebCore/URL.h>
39 #include <wtf/MainThread.h>
40 #include <wtf/SetForScope.h>
41 #include <wtf/text/CString.h>
42
43 #if PLATFORM(IOS)
44 #include <WebCore/RuntimeApplicationChecks.h>
45 #endif
46
47 // Match the parallel connection count used by the networking layer.
48 static unsigned maxRequestsInFlightPerHost;
49 #if !PLATFORM(IOS)
50 static const unsigned maxRequestsInFlightForNonHTTPProtocols = 20;
51 #else
52 // Limiting this seems to regress performance in some local cases so let's just make it large.
53 static const unsigned maxRequestsInFlightForNonHTTPProtocols = 10000;
54 #endif
55
56 using namespace WebCore;
57
58 WebResourceLoadScheduler& webResourceLoadScheduler()
59 {
60     return static_cast<WebResourceLoadScheduler&>(*platformStrategies()->loaderStrategy());
61 }
62
63 WebResourceLoadScheduler::HostInformation* WebResourceLoadScheduler::hostForURL(const URL& url, CreateHostPolicy createHostPolicy)
64 {
65     if (!url.protocolIsInHTTPFamily())
66         return m_nonHTTPProtocolHost;
67
68     m_hosts.checkConsistency();
69     String hostName = url.host();
70     HostInformation* host = m_hosts.get(hostName);
71     if (!host && createHostPolicy == CreateIfNotFound) {
72         host = new HostInformation(hostName, maxRequestsInFlightPerHost);
73         m_hosts.add(hostName, host);
74     }
75     return host;
76 }
77
78 WebResourceLoadScheduler::WebResourceLoadScheduler()
79     : m_nonHTTPProtocolHost(new HostInformation(String(), maxRequestsInFlightForNonHTTPProtocols))
80     , m_requestTimer(*this, &WebResourceLoadScheduler::requestTimerFired)
81     , m_suspendPendingRequestsCount(0)
82     , m_isSerialLoadingEnabled(false)
83 {
84     maxRequestsInFlightPerHost = initializeMaximumHTTPConnectionCountPerHost();
85 }
86
87 WebResourceLoadScheduler::~WebResourceLoadScheduler()
88 {
89 }
90
91 RefPtr<SubresourceLoader> WebResourceLoadScheduler::loadResource(Frame& frame, CachedResource& resource, const ResourceRequest& request, const ResourceLoaderOptions& options)
92 {
93     RefPtr<SubresourceLoader> loader = SubresourceLoader::create(frame, resource, request, options);
94     if (loader)
95         scheduleLoad(loader.get());
96 #if PLATFORM(IOS)
97     // Since we defer loader initialization until scheduling on iOS, the frame
98     // load delegate that would be called in SubresourceLoader::create() on
99     // other ports might be called in scheduleLoad() instead. Our contract to
100     // callers of this method is that a null loader is returned if the load was
101     // cancelled by a frame load delegate.
102     if (!loader || loader->reachedTerminalState())
103         return nullptr;
104 #endif
105     return loader;
106 }
107
108 void WebResourceLoadScheduler::loadResourceSynchronously(NetworkingContext* context, unsigned long, const ResourceRequest& request, StoredCredentials storedCredentials, ClientCredentialPolicy, ResourceError& error, ResourceResponse& response, Vector<char>& data)
109 {
110     ResourceHandle::loadResourceSynchronously(context, request, storedCredentials, error, response, data);
111 }
112
113 RefPtr<NetscapePlugInStreamLoader> WebResourceLoadScheduler::schedulePluginStreamLoad(Frame& frame, NetscapePlugInStreamLoaderClient& client, const ResourceRequest& request)
114 {
115     RefPtr<NetscapePlugInStreamLoader> loader = NetscapePlugInStreamLoader::create(frame, client, request);
116     if (loader)
117         scheduleLoad(loader.get());
118     return loader;
119 }
120
121 void WebResourceLoadScheduler::scheduleLoad(ResourceLoader* resourceLoader)
122 {
123     ASSERT(resourceLoader);
124
125 #if PLATFORM(IOS)
126     // If there's a web archive resource for this URL, we don't need to schedule the load since it will never touch the network.
127     if (!isSuspendingPendingRequests() && resourceLoader->documentLoader()->archiveResourceForURL(resourceLoader->iOSOriginalRequest().url())) {
128         resourceLoader->startLoading();
129         return;
130     }
131 #else
132     if (resourceLoader->documentLoader()->archiveResourceForURL(resourceLoader->request().url())) {
133         resourceLoader->start();
134         return;
135     }
136 #endif
137
138 #if PLATFORM(IOS)
139     HostInformation* host = hostForURL(resourceLoader->iOSOriginalRequest().url(), CreateIfNotFound);
140 #else
141     HostInformation* host = hostForURL(resourceLoader->url(), CreateIfNotFound);
142 #endif
143
144     ResourceLoadPriority priority = resourceLoader->request().priority();
145
146     bool hadRequests = host->hasRequests();
147     host->schedule(resourceLoader, priority);
148
149 #if PLATFORM(COCOA) || USE(CFURLCONNECTION)
150     if (ResourceRequest::resourcePrioritiesEnabled() && !isSuspendingPendingRequests()) {
151         // Serve all requests at once to keep the pipeline full at the network layer.
152         // FIXME: Does this code do anything useful, given that we also set maxRequestsInFlightPerHost to effectively unlimited on these platforms?
153         servePendingRequests(host, ResourceLoadPriority::VeryLow);
154         return;
155     }
156 #endif
157
158 #if PLATFORM(IOS)
159     if ((priority > ResourceLoadPriority::Low || !resourceLoader->iOSOriginalRequest().url().protocolIsInHTTPFamily() || (priority == ResourceLoadPriority::Low && !hadRequests)) && !isSuspendingPendingRequests()) {
160         // Try to request important resources immediately.
161         servePendingRequests(host, priority);
162         return;
163     }
164 #else
165     if (priority > ResourceLoadPriority::Low || !resourceLoader->url().protocolIsInHTTPFamily() || (priority == ResourceLoadPriority::Low && !hadRequests)) {
166         // Try to request important resources immediately.
167         servePendingRequests(host, priority);
168         return;
169     }
170 #endif
171
172     // Handle asynchronously so early low priority requests don't
173     // get scheduled before later high priority ones.
174     scheduleServePendingRequests();
175 }
176
177 void WebResourceLoadScheduler::remove(ResourceLoader* resourceLoader)
178 {
179     ASSERT(resourceLoader);
180
181     HostInformation* host = hostForURL(resourceLoader->url());
182     if (host)
183         host->remove(resourceLoader);
184 #if PLATFORM(IOS)
185     // ResourceLoader::url() doesn't start returning the correct value until the load starts. If we get canceled before that, we need to look for originalRequest url instead.
186     // FIXME: ResourceLoader::url() should be made to return a sensible value at all times.
187     if (!resourceLoader->iOSOriginalRequest().isNull()) {
188         HostInformation* originalHost = hostForURL(resourceLoader->iOSOriginalRequest().url());
189         if (originalHost && originalHost != host)
190             originalHost->remove(resourceLoader);
191     }
192 #endif
193     scheduleServePendingRequests();
194 }
195
196 void WebResourceLoadScheduler::setDefersLoading(ResourceLoader*, bool)
197 {
198 }
199
200 void WebResourceLoadScheduler::crossOriginRedirectReceived(ResourceLoader* resourceLoader, const URL& redirectURL)
201 {
202     HostInformation* oldHost = hostForURL(resourceLoader->url());
203     ASSERT(oldHost);
204     if (!oldHost)
205         return;
206
207     HostInformation* newHost = hostForURL(redirectURL, CreateIfNotFound);
208
209     if (oldHost->name() == newHost->name())
210         return;
211
212     newHost->addLoadInProgress(resourceLoader);
213     oldHost->remove(resourceLoader);
214 }
215
216 void WebResourceLoadScheduler::servePendingRequests(ResourceLoadPriority minimumPriority)
217 {
218     if (isSuspendingPendingRequests())
219         return;
220
221     m_requestTimer.stop();
222     
223     servePendingRequests(m_nonHTTPProtocolHost, minimumPriority);
224
225     Vector<HostInformation*> hostsToServe;
226     copyValuesToVector(m_hosts, hostsToServe);
227
228     for (auto* host : hostsToServe) {
229         if (host->hasRequests())
230             servePendingRequests(host, minimumPriority);
231         else
232             delete m_hosts.take(host->name());
233     }
234 }
235
236 void WebResourceLoadScheduler::servePendingRequests(HostInformation* host, ResourceLoadPriority minimumPriority)
237 {
238     auto priority = ResourceLoadPriority::Highest;
239     while (true) {
240         auto& requestsPending = host->requestsPending(priority);
241         while (!requestsPending.isEmpty()) {
242             RefPtr<ResourceLoader> resourceLoader = requestsPending.first();
243
244             // For named hosts - which are only http(s) hosts - we should always enforce the connection limit.
245             // For non-named hosts - everything but http(s) - we should only enforce the limit if the document isn't done parsing 
246             // and we don't know all stylesheets yet.
247             Document* document = resourceLoader->frameLoader() ? resourceLoader->frameLoader()->frame().document() : 0;
248             bool shouldLimitRequests = !host->name().isNull() || (document && (document->parsing() || !document->haveStylesheetsLoaded()));
249             if (shouldLimitRequests && host->limitRequests(priority))
250                 return;
251
252             requestsPending.removeFirst();
253             host->addLoadInProgress(resourceLoader.get());
254 #if PLATFORM(IOS)
255             if (!IOSApplication::isWebProcess()) {
256                 resourceLoader->startLoading();
257                 return;
258             }
259 #endif
260             resourceLoader->start();
261         }
262         if (priority == minimumPriority)
263             return;
264         --priority;
265     }
266 }
267
268 void WebResourceLoadScheduler::suspendPendingRequests()
269 {
270     ++m_suspendPendingRequestsCount;
271 }
272
273 void WebResourceLoadScheduler::resumePendingRequests()
274 {
275     ASSERT(m_suspendPendingRequestsCount);
276     --m_suspendPendingRequestsCount;
277     if (m_suspendPendingRequestsCount)
278         return;
279     if (!m_hosts.isEmpty() || m_nonHTTPProtocolHost->hasRequests())
280         scheduleServePendingRequests();
281 }
282     
283 void WebResourceLoadScheduler::scheduleServePendingRequests()
284 {
285     if (!m_requestTimer.isActive())
286         m_requestTimer.startOneShot(0_s);
287 }
288
289 void WebResourceLoadScheduler::requestTimerFired()
290 {
291     servePendingRequests();
292 }
293
294 WebResourceLoadScheduler::HostInformation::HostInformation(const String& name, unsigned maxRequestsInFlight)
295     : m_name(name)
296     , m_maxRequestsInFlight(maxRequestsInFlight)
297 {
298 }
299
300 WebResourceLoadScheduler::HostInformation::~HostInformation()
301 {
302     ASSERT(!hasRequests());
303 }
304
305 unsigned WebResourceLoadScheduler::HostInformation::priorityToIndex(ResourceLoadPriority priority)
306 {
307     switch (priority) {
308     case ResourceLoadPriority::VeryLow:
309         return 0;
310     case ResourceLoadPriority::Low:
311         return 1;
312     case ResourceLoadPriority::Medium:
313         return 2;
314     case ResourceLoadPriority::High:
315         return 3;
316     case ResourceLoadPriority::VeryHigh:
317         return 4;
318     }
319     ASSERT_NOT_REACHED();
320     return 0;
321 }
322
323 void WebResourceLoadScheduler::HostInformation::schedule(ResourceLoader* resourceLoader, ResourceLoadPriority priority)
324 {
325     m_requestsPending[priorityToIndex(priority)].append(resourceLoader);
326 }
327     
328 void WebResourceLoadScheduler::HostInformation::addLoadInProgress(ResourceLoader* resourceLoader)
329 {
330     m_requestsLoading.add(resourceLoader);
331 }
332     
333 void WebResourceLoadScheduler::HostInformation::remove(ResourceLoader* resourceLoader)
334 {
335     if (m_requestsLoading.remove(resourceLoader))
336         return;
337     
338     for (auto& requestQueue : m_requestsPending) {
339         for (auto it = requestQueue.begin(), end = requestQueue.end(); it != end; ++it) {
340             if (*it == resourceLoader) {
341                 requestQueue.remove(it);
342                 return;
343             }
344         }
345     }
346 }
347
348 bool WebResourceLoadScheduler::HostInformation::hasRequests() const
349 {
350     if (!m_requestsLoading.isEmpty())
351         return true;
352     for (auto& requestQueue : m_requestsPending) {
353         if (!requestQueue.isEmpty())
354             return true;
355     }
356     return false;
357 }
358
359 bool WebResourceLoadScheduler::HostInformation::limitRequests(ResourceLoadPriority priority) const 
360 {
361     if (priority == ResourceLoadPriority::VeryLow && !m_requestsLoading.isEmpty())
362         return true;
363     return m_requestsLoading.size() >= (webResourceLoadScheduler().isSerialLoadingEnabled() ? 1 : m_maxRequestsInFlight);
364 }
365
366 void WebResourceLoadScheduler::startPingLoad(Frame& frame, ResourceRequest& request, const HTTPHeaderMap&, const FetchOptions& options, PingLoadCompletionHandler&& completionHandler)
367 {
368     // PingHandle manages its own lifetime, deleting itself when its purpose has been fulfilled.
369     new PingHandle(frame.loader().networkingContext(), request, options.credentials != FetchOptions::Credentials::Omit, PingHandle::UsesAsyncCallbacks::No, options.redirect == FetchOptions::Redirect::Follow, WTFMove(completionHandler));
370 }
371