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