Home | History | Annotate | Download | only in loader
      1 /*
      2     Copyright (C) 1998 Lars Knoll (knoll (at) mpi-hd.mpg.de)
      3     Copyright (C) 2001 Dirk Mueller (mueller (at) kde.org)
      4     Copyright (C) 2002 Waldo Bastian (bastian (at) kde.org)
      5     Copyright (C) 2006 Samuel Weinig (sam.weinig (at) 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     , m_isSerialLoadingEnabled(false)
     80 {
     81 #if REQUEST_MANAGEMENT_ENABLED
     82     maxRequestsInFlightPerHost = initializeMaximumHTTPConnectionCountPerHost();
     83 #endif
     84 }
     85 
     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 
     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 
    103 void ResourceLoadScheduler::addMainResourceLoad(ResourceLoader* resourceLoader)
    104 {
    105     hostForURL(resourceLoader->url(), CreateIfNotFound)->addLoadInProgress(resourceLoader);
    106 }
    107 
    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 
    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 
    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 
    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 
    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 
    206 void ResourceLoadScheduler::suspendPendingRequests()
    207 {
    208     ASSERT(!m_isSuspendingPendingRequests);
    209     m_isSuspendingPendingRequests = true;
    210 }
    211 
    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 
    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 
    227 void ResourceLoadScheduler::requestTimerFired(Timer<ResourceLoadScheduler>*)
    228 {
    229     LOG(ResourceLoading, "ResourceLoadScheduler::requestTimerFired\n");
    230     servePendingRequests();
    231 }
    232 
    233 ResourceLoadScheduler::HostInformation::HostInformation(const String& name, unsigned maxRequestsInFlight)
    234     : m_name(name)
    235     , m_maxRequestsInFlight(maxRequestsInFlight)
    236 {
    237 }
    238 
    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 
    246 void ResourceLoadScheduler::HostInformation::schedule(ResourceLoader* resourceLoader, ResourceLoadPriority priority)
    247 {
    248     m_requestsPending[priority].append(resourceLoader);
    249 }
    250 
    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 
    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 
    275 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 
    286 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