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