diff -r 000000000000 -r 4f2f89ce4247 WebCore/loader/loader.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/WebCore/loader/loader.cpp Fri Sep 17 09:02:29 2010 +0300 @@ -0,0 +1,621 @@ +/* + Copyright (C) 1998 Lars Knoll (knoll@mpi-hd.mpg.de) + Copyright (C) 2001 Dirk Mueller (mueller@kde.org) + Copyright (C) 2002 Waldo Bastian (bastian@kde.org) + Copyright (C) 2006 Samuel Weinig (sam.weinig@gmail.com) + Copyright (C) 2004, 2005, 2006, 2007, 2008 Apple Inc. All rights reserved. + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public License + along with this library; see the file COPYING.LIB. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + Boston, MA 02110-1301, USA. +*/ + +#include "config.h" +#include "loader.h" + +#include "Cache.h" +#include "CachedImage.h" +#include "CachedResource.h" +#include "DocLoader.h" +#include "InspectorTimelineAgent.h" +#include "Frame.h" +#include "FrameLoader.h" +#include "HTMLDocument.h" +#include "Request.h" +#include "ResourceHandle.h" +#include "ResourceRequest.h" +#include "ResourceResponse.h" +#include "SecurityOrigin.h" +#include "SharedBuffer.h" +#include "SubresourceLoader.h" +#include +#include + +#define REQUEST_MANAGEMENT_ENABLED 1 +#define REQUEST_DEBUG 0 + +namespace WebCore { + +#if REQUEST_MANAGEMENT_ENABLED +// Match the parallel connection count used by the networking layer +static unsigned maxRequestsInFlightPerHost; +// Having a limit might still help getting more important resources first +static const unsigned maxRequestsInFlightForNonHTTPProtocols = 20; +#else +static const unsigned maxRequestsInFlightPerHost = 10000; +static const unsigned maxRequestsInFlightForNonHTTPProtocols = 10000; +#endif + +Loader::Loader() + : m_requestTimer(this, &Loader::requestTimerFired) + , m_isSuspendingPendingRequests(false) +{ + m_nonHTTPProtocolHost = Host::create(AtomicString(), maxRequestsInFlightForNonHTTPProtocols); +#if REQUEST_MANAGEMENT_ENABLED + maxRequestsInFlightPerHost = initializeMaximumHTTPConnectionCountPerHost(); +#endif +} + +Loader::~Loader() +{ + ASSERT_NOT_REACHED(); +} + +static ResourceRequest::TargetType cachedResourceTypeToTargetType(CachedResource::Type type) +{ + switch (type) { + case CachedResource::CSSStyleSheet: +#if ENABLE(XSLT) + case CachedResource::XSLStyleSheet: +#endif +#if ENABLE(XBL) + case CachedResource::XBL: +#endif + return ResourceRequest::TargetIsStyleSheet; + case CachedResource::Script: + return ResourceRequest::TargetIsScript; + case CachedResource::FontResource: + return ResourceRequest::TargetIsFontResource; + case CachedResource::ImageResource: + return ResourceRequest::TargetIsImage; +#if ENABLE(LINK_PREFETCH) + case CachedResource::LinkPrefetch: + return ResourceRequest::TargetIsPrefetch; +#endif + } + ASSERT_NOT_REACHED(); + return ResourceRequest::TargetIsSubresource; +} + +Loader::Priority Loader::determinePriority(const CachedResource* resource) const +{ +#if REQUEST_MANAGEMENT_ENABLED + switch (resource->type()) { + case CachedResource::CSSStyleSheet: +#if ENABLE(XSLT) + case CachedResource::XSLStyleSheet: +#endif +#if ENABLE(XBL) + case CachedResource::XBL: +#endif + return High; + case CachedResource::Script: + case CachedResource::FontResource: + return Medium; + case CachedResource::ImageResource: + return Low; +#if ENABLE(LINK_PREFETCH) + case CachedResource::LinkPrefetch: + return VeryLow; +#endif + } + ASSERT_NOT_REACHED(); + return Low; +#else + return High; +#endif +} + +void Loader::load(DocLoader* docLoader, CachedResource* resource, bool incremental, SecurityCheckPolicy securityCheck, bool sendResourceLoadCallbacks) +{ + ASSERT(docLoader); + Request* request = new Request(docLoader, resource, incremental, securityCheck, sendResourceLoadCallbacks); + + RefPtr host; + KURL url(ParsedURLString, resource->url()); + if (url.protocolInHTTPFamily()) { + m_hosts.checkConsistency(); + AtomicString hostName = url.host(); + host = m_hosts.get(hostName.impl()); + if (!host) { + host = Host::create(hostName, maxRequestsInFlightPerHost); + m_hosts.add(hostName.impl(), host); + } + } else + host = m_nonHTTPProtocolHost; + + bool hadRequests = host->hasRequests(); + Priority priority = determinePriority(resource); + host->addRequest(request, priority); + docLoader->incrementRequestCount(request->cachedResource()); + + if (priority > Low || !url.protocolInHTTPFamily() || (priority == Low && !hadRequests)) { + // Try to request important resources immediately + host->servePendingRequests(priority); + } else { + // Handle asynchronously so early low priority requests don't get scheduled before later high priority ones +#if ENABLE(INSPECTOR) + if (InspectorTimelineAgent::instanceCount()) { + InspectorTimelineAgent* agent = docLoader->doc()->inspectorTimelineAgent(); + if (agent) + agent->didScheduleResourceRequest(resource->url()); + } +#endif // ENABLE(INSPECTOR) + scheduleServePendingRequests(); + } +} + +void Loader::scheduleServePendingRequests() +{ + if (!m_requestTimer.isActive()) + m_requestTimer.startOneShot(0); +} + +void Loader::requestTimerFired(Timer*) +{ + servePendingRequests(); +} + +void Loader::servePendingRequests(Priority minimumPriority) +{ + if (m_isSuspendingPendingRequests) + return; + + m_requestTimer.stop(); + + m_nonHTTPProtocolHost->servePendingRequests(minimumPriority); + + Vector hostsToServe; + m_hosts.checkConsistency(); + HostMap::iterator i = m_hosts.begin(); + HostMap::iterator end = m_hosts.end(); + for (;i != end; ++i) + hostsToServe.append(i->second.get()); + + for (unsigned n = 0; n < hostsToServe.size(); ++n) { + Host* host = hostsToServe[n]; + if (host->hasRequests()) + host->servePendingRequests(minimumPriority); + else if (!host->processingResource()){ + AtomicString name = host->name(); + m_hosts.remove(name.impl()); + } + } +} + +void Loader::suspendPendingRequests() +{ + ASSERT(!m_isSuspendingPendingRequests); + m_isSuspendingPendingRequests = true; +} + +void Loader::resumePendingRequests() +{ + ASSERT(m_isSuspendingPendingRequests); + m_isSuspendingPendingRequests = false; + if (!m_hosts.isEmpty() || m_nonHTTPProtocolHost->hasRequests()) + scheduleServePendingRequests(); +} + +void Loader::nonCacheRequestInFlight(const KURL& url) +{ + if (!url.protocolInHTTPFamily()) + return; + + AtomicString hostName = url.host(); + m_hosts.checkConsistency(); + RefPtr host = m_hosts.get(hostName.impl()); + if (!host) { + host = Host::create(hostName, maxRequestsInFlightPerHost); + m_hosts.add(hostName.impl(), host); + } + + host->nonCacheRequestInFlight(); +} + +void Loader::nonCacheRequestComplete(const KURL& url) +{ + if (!url.protocolInHTTPFamily()) + return; + + AtomicString hostName = url.host(); + m_hosts.checkConsistency(); + RefPtr host = m_hosts.get(hostName.impl()); + ASSERT(host); + if (!host) + return; + + host->nonCacheRequestComplete(); +} + +void Loader::cancelRequests(DocLoader* docLoader) +{ + docLoader->clearPendingPreloads(); + + if (m_nonHTTPProtocolHost->hasRequests()) + m_nonHTTPProtocolHost->cancelRequests(docLoader); + + Vector hostsToCancel; + m_hosts.checkConsistency(); + HostMap::iterator i = m_hosts.begin(); + HostMap::iterator end = m_hosts.end(); + for (;i != end; ++i) + hostsToCancel.append(i->second.get()); + + for (unsigned n = 0; n < hostsToCancel.size(); ++n) { + Host* host = hostsToCancel[n]; + if (host->hasRequests()) + host->cancelRequests(docLoader); + } + + scheduleServePendingRequests(); + + ASSERT(docLoader->requestCount() == (docLoader->loadInProgress() ? 1 : 0)); +} + +Loader::Host::Host(const AtomicString& name, unsigned maxRequestsInFlight) + : m_name(name) + , m_maxRequestsInFlight(maxRequestsInFlight) + , m_numResourcesProcessing(0) + , m_nonCachedRequestsInFlight(0) +{ +} + +Loader::Host::~Host() +{ + ASSERT(m_requestsLoading.isEmpty()); + for (unsigned p = 0; p <= High; p++) + ASSERT(m_requestsPending[p].isEmpty()); +} + +void Loader::Host::addRequest(Request* request, Priority priority) +{ + m_requestsPending[priority].append(request); +} + +void Loader::Host::nonCacheRequestInFlight() +{ + ++m_nonCachedRequestsInFlight; +} + +void Loader::Host::nonCacheRequestComplete() +{ + --m_nonCachedRequestsInFlight; + ASSERT(m_nonCachedRequestsInFlight >= 0); + + cache()->loader()->scheduleServePendingRequests(); +} + +bool Loader::Host::hasRequests() const +{ + if (!m_requestsLoading.isEmpty()) + return true; + for (unsigned p = 0; p <= High; p++) { + if (!m_requestsPending[p].isEmpty()) + return true; + } + return false; +} + +void Loader::Host::servePendingRequests(Loader::Priority minimumPriority) +{ + if (cache()->loader()->isSuspendingPendingRequests()) + return; + + bool serveMore = true; + for (int priority = High; priority >= minimumPriority && serveMore; --priority) + servePendingRequests(m_requestsPending[priority], serveMore); +} + +void Loader::Host::servePendingRequests(RequestQueue& requestsPending, bool& serveLowerPriority) +{ + while (!requestsPending.isEmpty()) { + Request* request = requestsPending.first(); + DocLoader* docLoader = request->docLoader(); + bool resourceIsCacheValidator = request->cachedResource()->isCacheValidator(); + + // For named hosts - which are only http(s) hosts - we should always enforce the connection limit. + // For non-named hosts - everything but http(s) - we should only enforce the limit if the document isn't done parsing + // and we don't know all stylesheets yet. + bool shouldLimitRequests = !m_name.isNull() || docLoader->doc()->parsing() || !docLoader->doc()->haveStylesheetsLoaded(); + if (shouldLimitRequests && m_requestsLoading.size() + m_nonCachedRequestsInFlight >= m_maxRequestsInFlight) { + serveLowerPriority = false; + return; + } + requestsPending.removeFirst(); + + ResourceRequest resourceRequest(request->cachedResource()->url()); + resourceRequest.setTargetType(cachedResourceTypeToTargetType(request->cachedResource()->type())); + + if (!request->cachedResource()->accept().isEmpty()) + resourceRequest.setHTTPAccept(request->cachedResource()->accept()); + + // Do not set the referrer or HTTP origin here. That's handled by SubresourceLoader::create. + + if (resourceIsCacheValidator) { + CachedResource* resourceToRevalidate = request->cachedResource()->resourceToRevalidate(); + ASSERT(resourceToRevalidate->canUseCacheValidator()); + ASSERT(resourceToRevalidate->isLoaded()); + const String& lastModified = resourceToRevalidate->response().httpHeaderField("Last-Modified"); + const String& eTag = resourceToRevalidate->response().httpHeaderField("ETag"); + if (!lastModified.isEmpty() || !eTag.isEmpty()) { + ASSERT(docLoader->cachePolicy() != CachePolicyReload); + if (docLoader->cachePolicy() == CachePolicyRevalidate) + resourceRequest.setHTTPHeaderField("Cache-Control", "max-age=0"); + if (!lastModified.isEmpty()) + resourceRequest.setHTTPHeaderField("If-Modified-Since", lastModified); + if (!eTag.isEmpty()) + resourceRequest.setHTTPHeaderField("If-None-Match", eTag); + } + } + + RefPtr loader = SubresourceLoader::create(docLoader->doc()->frame(), + this, resourceRequest, request->shouldDoSecurityCheck(), request->sendResourceLoadCallbacks()); + if (loader) { + m_requestsLoading.add(loader.release(), request); + request->cachedResource()->setRequestedFromNetworkingLayer(); +#if REQUEST_DEBUG + printf("HOST %s COUNT %d LOADING %s\n", resourceRequest.url().host().latin1().data(), m_requestsLoading.size(), request->cachedResource()->url().latin1().data()); +#endif + } else { + docLoader->decrementRequestCount(request->cachedResource()); + docLoader->setLoadInProgress(true); + request->cachedResource()->error(); + docLoader->setLoadInProgress(false); + delete request; + } + } +} + +void Loader::Host::didFinishLoading(SubresourceLoader* loader) +{ + RefPtr myProtector(this); + + RequestMap::iterator i = m_requestsLoading.find(loader); + if (i == m_requestsLoading.end()) + return; + + Request* request = i->second; + m_requestsLoading.remove(i); + DocLoader* docLoader = request->docLoader(); + // Prevent the document from being destroyed before we are done with + // the docLoader that it will delete when the document gets deleted. + RefPtr protector(docLoader->doc()); + if (!request->isMultipart()) + docLoader->decrementRequestCount(request->cachedResource()); + + CachedResource* resource = request->cachedResource(); + ASSERT(!resource->resourceToRevalidate()); + + // If we got a 4xx response, we're pretending to have received a network + // error, so we can't send the successful data() and finish() callbacks. + if (!resource->errorOccurred()) { + docLoader->setLoadInProgress(true); + resource->data(loader->resourceData(), true); + resource->finish(); + } + + delete request; + + docLoader->setLoadInProgress(false); + + docLoader->checkForPendingPreloads(); + +#if REQUEST_DEBUG + KURL u(ParsedURLString, resource->url()); + printf("HOST %s COUNT %d RECEIVED %s\n", u.host().latin1().data(), m_requestsLoading.size(), resource->url().latin1().data()); +#endif + servePendingRequests(); +} + +void Loader::Host::didFail(SubresourceLoader* loader, const ResourceError&) +{ + didFail(loader); +} + +void Loader::Host::didFail(SubresourceLoader* loader, bool cancelled) +{ + RefPtr myProtector(this); + + loader->clearClient(); + + RequestMap::iterator i = m_requestsLoading.find(loader); + if (i == m_requestsLoading.end()) + return; + + Request* request = i->second; + m_requestsLoading.remove(i); + DocLoader* docLoader = request->docLoader(); + // Prevent the document from being destroyed before we are done with + // the docLoader that it will delete when the document gets deleted. + RefPtr protector(docLoader->doc()); + if (!request->isMultipart()) + docLoader->decrementRequestCount(request->cachedResource()); + + CachedResource* resource = request->cachedResource(); + + if (resource->resourceToRevalidate()) + cache()->revalidationFailed(resource); + + if (!cancelled) { + docLoader->setLoadInProgress(true); + resource->error(); + } + + docLoader->setLoadInProgress(false); + if (cancelled || !resource->isPreloaded()) + cache()->remove(resource); + + delete request; + + docLoader->checkForPendingPreloads(); + + servePendingRequests(); +} + +void Loader::Host::didReceiveResponse(SubresourceLoader* loader, const ResourceResponse& response) +{ + RefPtr protector(this); + + Request* request = m_requestsLoading.get(loader); + + // FIXME: This is a workaround for + // If a load starts while the frame is still in the provisional state + // (this can be the case when loading the user style sheet), committing the load then causes all + // requests to be removed from the m_requestsLoading map. This means that request might be null here. + // In that case we just return early. + // ASSERT(request); + if (!request) + return; + + CachedResource* resource = request->cachedResource(); + + if (resource->isCacheValidator()) { + if (response.httpStatusCode() == 304) { + // 304 Not modified / Use local copy + m_requestsLoading.remove(loader); + loader->clearClient(); + request->docLoader()->decrementRequestCount(request->cachedResource()); + + // Existing resource is ok, just use it updating the expiration time. + cache()->revalidationSucceeded(resource, response); + + if (request->docLoader()->frame()) + request->docLoader()->frame()->loader()->checkCompleted(); + + delete request; + + servePendingRequests(); + return; + } + // Did not get 304 response, continue as a regular resource load. + cache()->revalidationFailed(resource); + } + + resource->setResponse(response); + + String encoding = response.textEncodingName(); + if (!encoding.isNull()) + resource->setEncoding(encoding); + + if (request->isMultipart()) { + ASSERT(resource->isImage()); + static_cast(resource)->clear(); + if (request->docLoader()->frame()) + request->docLoader()->frame()->loader()->checkCompleted(); + } else if (response.isMultipart()) { + request->setIsMultipart(true); + + // We don't count multiParts in a DocLoader's request count + request->docLoader()->decrementRequestCount(request->cachedResource()); + + // If we get a multipart response, we must have a handle + ASSERT(loader->handle()); + if (!resource->isImage()) + loader->handle()->cancel(); + } +} + +void Loader::Host::didReceiveData(SubresourceLoader* loader, const char* data, int size) +{ + RefPtr protector(this); + + Request* request = m_requestsLoading.get(loader); + if (!request) + return; + + CachedResource* resource = request->cachedResource(); + ASSERT(!resource->isCacheValidator()); + + if (resource->errorOccurred()) + return; + + if (resource->response().httpStatusCode() / 100 == 4) { + // Treat a 4xx response like a network error for all resources but images (which will ignore the error and continue to load for + // legacy compatibility). + resource->httpStatusCodeError(); + return; + } + + // Set the data. + if (request->isMultipart()) { + // The loader delivers the data in a multipart section all at once, send eof. + // The resource data will change as the next part is loaded, so we need to make a copy. + RefPtr copiedData = SharedBuffer::create(data, size); + resource->data(copiedData.release(), true); + } else if (request->isIncremental()) + resource->data(loader->resourceData(), false); +} + +void Loader::Host::didReceiveCachedMetadata(SubresourceLoader* loader, const char* data, int size) +{ + RefPtr protector(this); + + Request* request = m_requestsLoading.get(loader); + if (!request) + return; + + CachedResource* resource = request->cachedResource(); + ASSERT(!resource->isCacheValidator()); + + resource->setSerializedCachedMetadata(data, size); +} + +void Loader::Host::cancelPendingRequests(RequestQueue& requestsPending, DocLoader* docLoader) +{ + RequestQueue remaining; + RequestQueue::iterator end = requestsPending.end(); + for (RequestQueue::iterator it = requestsPending.begin(); it != end; ++it) { + Request* request = *it; + if (request->docLoader() == docLoader) { + cache()->remove(request->cachedResource()); + docLoader->decrementRequestCount(request->cachedResource()); + delete request; + } else + remaining.append(request); + } + requestsPending.swap(remaining); +} + +void Loader::Host::cancelRequests(DocLoader* docLoader) +{ + for (unsigned p = 0; p <= High; p++) + cancelPendingRequests(m_requestsPending[p], docLoader); + + Vector loadersToCancel; + + RequestMap::iterator end = m_requestsLoading.end(); + for (RequestMap::iterator i = m_requestsLoading.begin(); i != end; ++i) { + Request* r = i->second; + if (r->docLoader() == docLoader) + loadersToCancel.append(i->first.get()); + } + + for (unsigned i = 0; i < loadersToCancel.size(); ++i) { + SubresourceLoader* loader = loadersToCancel[i]; + didFail(loader, true); + } +} + +} //namespace WebCore