7c50dc1add6c2cc3569501d6e50e7fea307c34f5
[WebKit-https.git] / WebCore / loader / ResourceLoadScheduler.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, 2005, 2006, 2007, 2008 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 "config.h"
26 #include "ResourceLoadScheduler.h"
27
28 #include "Document.h"
29 #include "Frame.h"
30 #include "FrameLoader.h"
31 #include "InspectorInstrumentation.h"
32 #include "KURL.h"
33 #include "Logging.h"
34 #include "NetscapePlugInStreamLoader.h"
35 #include "ResourceLoader.h"
36 #include "ResourceRequest.h"
37 #include "SubresourceLoader.h"
38 #include <wtf/text/CString.h>
39
40 #define REQUEST_MANAGEMENT_ENABLED 1
41
42 namespace WebCore {
43
44 #if REQUEST_MANAGEMENT_ENABLED
45 static const unsigned maxRequestsInFlightForNonHTTPProtocols = 20;
46 // Match the parallel connection count used by the networking layer.
47 static unsigned maxRequestsInFlightPerHost;
48 #else
49 static const unsigned maxRequestsInFlightForNonHTTPProtocols = 10000;
50 static const unsigned maxRequestsInFlightPerHost = 10000;
51 #endif
52
53 ResourceLoadScheduler::HostInformation* ResourceLoadScheduler::hostForURL(const KURL& url, CreateHostPolicy createHostPolicy)
54 {
55     if (!url.protocolInHTTPFamily())
56         return m_nonHTTPProtocolHost;
57
58     m_hosts.checkConsistency();
59     String hostName = url.host();
60     HostInformation* host = m_hosts.get(hostName);
61     if (!host && createHostPolicy == CreateIfNotFound) {
62         host = new HostInformation(hostName, maxRequestsInFlightPerHost);
63         m_hosts.add(hostName, host);
64     }
65     return host;
66 }
67
68 ResourceLoadScheduler* resourceLoadScheduler()
69 {
70     ASSERT(isMainThread());
71     DEFINE_STATIC_LOCAL(ResourceLoadScheduler, resourceLoadScheduler, ());
72     return &resourceLoadScheduler;
73 }
74
75 ResourceLoadScheduler::ResourceLoadScheduler()
76     : m_nonHTTPProtocolHost(new HostInformation(String(), maxRequestsInFlightForNonHTTPProtocols))
77     , m_requestTimer(this, &ResourceLoadScheduler::requestTimerFired)
78     , m_isSuspendingPendingRequests(false)
79 {
80 #if REQUEST_MANAGEMENT_ENABLED
81     maxRequestsInFlightPerHost = initializeMaximumHTTPConnectionCountPerHost();
82 #endif
83 }
84
85 PassRefPtr<SubresourceLoader> ResourceLoadScheduler::scheduleSubresourceLoad(Frame* frame, SubresourceLoaderClient* client, const ResourceRequest& request, Priority priority, SecurityCheckPolicy securityCheck, bool sendResourceLoadCallbacks, bool shouldContentSniff)
86 {
87     PassRefPtr<SubresourceLoader> loader = SubresourceLoader::create(frame, client, request, securityCheck, sendResourceLoadCallbacks, shouldContentSniff);
88     if (loader)
89         scheduleLoad(loader.get(), priority);
90     return loader;
91 }
92     
93 PassRefPtr<NetscapePlugInStreamLoader> ResourceLoadScheduler::schedulePluginStreamLoad(Frame* frame, NetscapePlugInStreamLoaderClient* client, const ResourceRequest& request)
94 {
95     PassRefPtr<NetscapePlugInStreamLoader> loader = NetscapePlugInStreamLoader::create(frame, client, request);
96     if (loader)
97         scheduleLoad(loader.get(), Low);
98     return loader;
99 }
100
101 void ResourceLoadScheduler::addMainResourceLoad(ResourceLoader* resourceLoader)
102 {
103     hostForURL(resourceLoader->url(), CreateIfNotFound)->addLoadInProgress(resourceLoader);
104 }
105
106 void ResourceLoadScheduler::scheduleLoad(ResourceLoader* resourceLoader, Priority priority)
107 {
108     ASSERT(resourceLoader);
109 #if !REQUEST_MANAGEMENT_ENABLED
110     priority = HighestPriority;
111 #endif
112
113     LOG(ResourceLoading, "ResourceLoadScheduler::load resource %p '%s'", resourceLoader, resourceLoader->url().string().latin1().data());
114     HostInformation* host = hostForURL(resourceLoader->url(), CreateIfNotFound);    
115     bool hadRequests = host->hasRequests();
116     host->schedule(resourceLoader, priority);
117
118     if (priority > Low || !resourceLoader->url().protocolInHTTPFamily() || (priority == Low && !hadRequests)) {
119         // Try to request important resources immediately.
120         servePendingRequests(host, priority);
121     } else {
122         // Handle asynchronously so early low priority requests don't get scheduled before later high priority ones.
123         InspectorInstrumentation::didScheduleResourceRequest(resourceLoader->frameLoader() ? resourceLoader->frameLoader()->frame()->document() : 0, resourceLoader->url());
124         scheduleServePendingRequests();
125     }
126 }
127
128 void ResourceLoadScheduler::remove(ResourceLoader* resourceLoader)
129 {
130     ASSERT(resourceLoader);
131
132     HostInformation* host = hostForURL(resourceLoader->url());
133     if (host)
134         host->remove(resourceLoader);
135     scheduleServePendingRequests();
136 }
137
138 void ResourceLoadScheduler::crossOriginRedirectReceived(ResourceLoader* resourceLoader, const KURL& redirectURL)
139 {
140     HostInformation* oldHost = hostForURL(resourceLoader->url());
141     ASSERT(oldHost);
142     HostInformation* newHost = hostForURL(redirectURL, CreateIfNotFound);
143
144     if (oldHost->name() == newHost->name())
145         return;
146     
147     newHost->addLoadInProgress(resourceLoader);
148     oldHost->remove(resourceLoader);
149 }
150
151 void ResourceLoadScheduler::servePendingRequests(Priority minimumPriority)
152 {
153     LOG(ResourceLoading, "ResourceLoadScheduler::servePendingRequests. m_isSuspendingPendingRequests=%d", m_isSuspendingPendingRequests); 
154     if (m_isSuspendingPendingRequests)
155         return;
156
157     m_requestTimer.stop();
158     
159     servePendingRequests(m_nonHTTPProtocolHost, minimumPriority);
160
161     Vector<HostInformation*> hostsToServe;
162     m_hosts.checkConsistency();
163     HostMap::iterator end = m_hosts.end();
164     for (HostMap::iterator iter = m_hosts.begin(); iter != end; ++iter)
165         hostsToServe.append(iter->second);
166
167     int size = hostsToServe.size();
168     for (int i = 0; i < size; ++i) {
169         HostInformation* host = hostsToServe[i];
170         if (host->hasRequests())
171             servePendingRequests(host, minimumPriority);
172         else
173             delete m_hosts.take(host->name());
174     }
175 }
176
177 void ResourceLoadScheduler::servePendingRequests(HostInformation* host, Priority minimumPriority)
178 {
179     LOG(ResourceLoading, "ResourceLoadScheduler::servePendingRequests HostInformation.m_name='%s'", host->name().latin1().data());
180
181     for (int priority = HighestPriority; priority >= minimumPriority; --priority) {
182         HostInformation::RequestQueue& requestsPending = host->requestsPending((Priority) priority);
183
184         while (!requestsPending.isEmpty()) {
185             RefPtr<ResourceLoader> resourceLoader = requestsPending.first();
186
187             // For named hosts - which are only http(s) hosts - we should always enforce the connection limit.
188             // For non-named hosts - everything but http(s) - we should only enforce the limit if the document isn't done parsing 
189             // and we don't know all stylesheets yet.
190             Document* document = resourceLoader->frameLoader() ? resourceLoader->frameLoader()->frame()->document() : 0;
191             bool shouldLimitRequests = !host->name().isNull() || (document && (document->parsing() || !document->haveStylesheetsLoaded()));
192             if (shouldLimitRequests && host->limitRequests())
193                 return;
194
195             requestsPending.removeFirst();
196             host->addLoadInProgress(resourceLoader.get());
197             resourceLoader->start();
198         }
199     }
200 }
201
202 void ResourceLoadScheduler::suspendPendingRequests()
203 {
204     ASSERT(!m_isSuspendingPendingRequests);
205     m_isSuspendingPendingRequests = true;
206 }
207
208 void ResourceLoadScheduler::resumePendingRequests()
209 {
210     ASSERT(m_isSuspendingPendingRequests);
211     m_isSuspendingPendingRequests = false;
212     if (!m_hosts.isEmpty() || m_nonHTTPProtocolHost->hasRequests())
213         scheduleServePendingRequests();
214 }
215     
216 void ResourceLoadScheduler::scheduleServePendingRequests()
217 {
218     LOG(ResourceLoading, "ResourceLoadScheduler::scheduleServePendingRequests, m_requestTimer.isActive()=%u", m_requestTimer.isActive());
219     if (!m_requestTimer.isActive())
220         m_requestTimer.startOneShot(0);
221 }
222
223 void ResourceLoadScheduler::requestTimerFired(Timer<ResourceLoadScheduler>*) 
224 {
225     LOG(ResourceLoading, "ResourceLoadScheduler::requestTimerFired\n");
226     servePendingRequests();
227 }
228
229 ResourceLoadScheduler::HostInformation::HostInformation(const String& name, unsigned maxRequestsInFlight)
230     : m_name(name)
231     , m_maxRequestsInFlight(maxRequestsInFlight)
232 {
233 }
234
235 ResourceLoadScheduler::HostInformation::~HostInformation()
236 {
237     ASSERT(m_requestsLoading.isEmpty());
238     for (unsigned p = 0; p <= HighestPriority; p++)
239         ASSERT(m_requestsPending[p].isEmpty());
240 }
241     
242 void ResourceLoadScheduler::HostInformation::schedule(ResourceLoader* resourceLoader, Priority priority)
243 {
244     m_requestsPending[priority].append(resourceLoader);
245 }
246     
247 void ResourceLoadScheduler::HostInformation::addLoadInProgress(ResourceLoader* resourceLoader)
248 {
249     LOG(ResourceLoading, "HostInformation '%s' loading '%s'. Current count %d", m_name.latin1().data(), resourceLoader->url().string().latin1().data(), m_requestsLoading.size());
250     m_requestsLoading.add(resourceLoader);
251 }
252     
253 void ResourceLoadScheduler::HostInformation::remove(ResourceLoader* resourceLoader)
254 {
255     if (m_requestsLoading.contains(resourceLoader)) {
256         m_requestsLoading.remove(resourceLoader);
257         return;
258     }
259     
260     for (int priority = HighestPriority; priority >= LowestPriority; --priority) {  
261         RequestQueue::iterator end = m_requestsPending[priority].end();
262         for (RequestQueue::iterator it = m_requestsPending[priority].begin(); it != end; ++it) {
263             if (*it == resourceLoader) {
264                 m_requestsPending[priority].remove(it);
265                 return;
266             }
267         }
268     }
269 }
270
271 bool ResourceLoadScheduler::HostInformation::hasRequests() const
272 {
273     if (!m_requestsLoading.isEmpty())
274         return true;
275     for (unsigned p = 0; p <= HighestPriority; p++) {
276         if (!m_requestsPending[p].isEmpty())
277             return true;
278     }
279     return false;
280 }
281
282 } // namespace WebCore