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