diff options
Diffstat (limited to 'src/3rdparty/webkit/WebCore/loader/loader.cpp')
-rw-r--r-- | src/3rdparty/webkit/WebCore/loader/loader.cpp | 487 |
1 files changed, 487 insertions, 0 deletions
diff --git a/src/3rdparty/webkit/WebCore/loader/loader.cpp b/src/3rdparty/webkit/WebCore/loader/loader.cpp new file mode 100644 index 0000000..26b8ad1 --- /dev/null +++ b/src/3rdparty/webkit/WebCore/loader/loader.cpp @@ -0,0 +1,487 @@ +/* + 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 "CString.h" +#include "DocLoader.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 "SubresourceLoader.h" +#include <wtf/Assertions.h> +#include <wtf/Vector.h> + +#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 +// FIXME should not hardcode something like this +static const unsigned maxRequestsInFlightPerHost = 4; +// 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_nonHTTPProtocolHost(AtomicString(), maxRequestsInFlightForNonHTTPProtocols) + , m_requestTimer(this, &Loader::requestTimerFired) +{ +} + +Loader::~Loader() +{ + ASSERT_NOT_REACHED(); +} + +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; + } + ASSERT_NOT_REACHED(); + return Low; +#else + return High; +#endif +} + +void Loader::load(DocLoader* docLoader, CachedResource* resource, bool incremental, bool skipCanLoadCheck, bool sendResourceLoadCallbacks) +{ + ASSERT(docLoader); + Request* request = new Request(docLoader, resource, incremental, skipCanLoadCheck, sendResourceLoadCallbacks); + + Host* host; + KURL url(resource->url()); + bool isHTTP = url.protocolIs("http") || url.protocolIs("https"); + if (isHTTP) { + AtomicString hostName = url.host(); + host = m_hosts.get(hostName.impl()); + if (!host) { + host = new Host(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(); + + if (priority > Low || !isHTTP || !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 + scheduleServePendingRequests(); + } +} + +void Loader::scheduleServePendingRequests() +{ + if (!m_requestTimer.isActive()) + m_requestTimer.startOneShot(0); +} + +void Loader::requestTimerFired(Timer<Loader>*) +{ + servePendingRequests(); +} + +void Loader::servePendingRequests(Priority minimumPriority) +{ + m_requestTimer.stop(); + + m_nonHTTPProtocolHost.servePendingRequests(minimumPriority); + + Vector<Host*> hostsToServe; + copyValuesToVector(m_hosts, hostsToServe); + 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(); + delete host; + m_hosts.remove(name.impl()); + } + } +} + +void Loader::cancelRequests(DocLoader* docLoader) +{ + if (m_nonHTTPProtocolHost.hasRequests()) + m_nonHTTPProtocolHost.cancelRequests(docLoader); + + Vector<Host*> hostsToCancel; + copyValuesToVector(m_hosts, hostsToCancel); + for (unsigned n = 0; n < hostsToCancel.size(); ++n) { + Host* host = hostsToCancel[n]; + if (host->hasRequests()) + host->cancelRequests(docLoader); + } + + scheduleServePendingRequests(); + + if (docLoader->loadInProgress()) + ASSERT(docLoader->requestCount() == 1); + else + ASSERT(docLoader->requestCount() == 0); +} + +Loader::Host::Host(const AtomicString& name, unsigned maxRequestsInFlight) + : m_name(name) + , m_maxRequestsInFlight(maxRequestsInFlight) + , m_numResourcesProcessing(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); +} + +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) +{ + 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(); + // If the document is fully parsed and there are no pending stylesheets there won't be any more + // resources that we would want to push to the front of the queue. Just hand off the remaining resources + // to the networking layer. + bool parsedAndStylesheetsKnown = !docLoader->doc()->parsing() && docLoader->doc()->haveStylesheetsLoaded(); + if (!parsedAndStylesheetsKnown && !resourceIsCacheValidator && m_requestsLoading.size() >= m_maxRequestsInFlight) { + serveLowerPriority = false; + return; + } + requestsPending.removeFirst(); + + ResourceRequest resourceRequest(request->cachedResource()->url()); + + if (!request->cachedResource()->accept().isEmpty()) + resourceRequest.setHTTPAccept(request->cachedResource()->accept()); + + KURL referrer = docLoader->doc()->url(); + if ((referrer.protocolIs("http") || referrer.protocolIs("https")) && referrer.path().isEmpty()) + referrer.setPath("/"); + resourceRequest.setHTTPReferrer(referrer.string()); + FrameLoader::addHTTPOriginIfNeeded(resourceRequest, docLoader->doc()->securityOrigin()->toString()); + + 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<SubresourceLoader> loader = SubresourceLoader::create(docLoader->doc()->frame(), + this, resourceRequest, request->shouldSkipCanLoadCheck(), 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(); + docLoader->setLoadInProgress(true); + request->cachedResource()->error(); + docLoader->setLoadInProgress(false); + delete request; + } + } +} + +void Loader::Host::didFinishLoading(SubresourceLoader* loader) +{ + RequestMap::iterator i = m_requestsLoading.find(loader); + if (i == m_requestsLoading.end()) + return; + + ProcessingResource processingResource(this); + + Request* request = i->second; + m_requestsLoading.remove(i); + DocLoader* docLoader = request->docLoader(); + if (!request->isMultipart()) + docLoader->decrementRequestCount(); + + 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(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) +{ + loader->clearClient(); + + RequestMap::iterator i = m_requestsLoading.find(loader); + if (i == m_requestsLoading.end()) + return; + + ProcessingResource processingResource(this); + + Request* request = i->second; + m_requestsLoading.remove(i); + DocLoader* docLoader = request->docLoader(); + if (!request->isMultipart()) + docLoader->decrementRequestCount(); + + 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) +{ + Request* request = m_requestsLoading.get(loader); + + // FIXME: This is a workaround for <rdar://problem/5236843> + // 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(); + + // 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<CachedImage*>(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(); + + // 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) +{ + Request* request = m_requestsLoading.get(loader); + if (!request) + return; + + CachedResource* resource = request->cachedResource(); + ASSERT(!resource->isCacheValidator()); + + if (resource->errorOccurred()) + return; + + ProcessingResource processingResource(this); + + if (resource->response().httpStatusCode() / 100 == 4) { + // Treat a 4xx response like a network error. + resource->error(); + 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<SharedBuffer> copiedData = SharedBuffer::create(data, size); + resource->data(copiedData.release(), true); + } else if (request->isIncremental()) + resource->data(loader->resourceData(), false); +} + +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()); + delete request; + docLoader->decrementRequestCount(); + } 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<SubresourceLoader*, 256> 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 |