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