WebResourceLoadScheduler.cpp [plain text]
#include "WebResourceLoadScheduler.h"
#include "PingHandle.h"
#include <WebCore/Document.h>
#include <WebCore/DocumentLoader.h>
#include <WebCore/FetchOptions.h>
#include <WebCore/Frame.h>
#include <WebCore/FrameLoader.h>
#include <WebCore/NetscapePlugInStreamLoader.h>
#include <WebCore/NetworkStateNotifier.h>
#include <WebCore/PlatformStrategies.h>
#include <WebCore/ResourceRequest.h>
#include <WebCore/SubresourceLoader.h>
#include <WebCore/URL.h>
#include <wtf/MainThread.h>
#include <wtf/SetForScope.h>
#include <wtf/text/CString.h>
#if PLATFORM(IOS)
#include <WebCore/RuntimeApplicationChecks.h>
#endif
static unsigned maxRequestsInFlightPerHost;
#if !PLATFORM(IOS)
static const unsigned maxRequestsInFlightForNonHTTPProtocols = 20;
#else
static const unsigned maxRequestsInFlightForNonHTTPProtocols = 10000;
#endif
using namespace WebCore;
WebResourceLoadScheduler& webResourceLoadScheduler()
{
return static_cast<WebResourceLoadScheduler&>(*platformStrategies()->loaderStrategy());
}
WebResourceLoadScheduler::HostInformation* WebResourceLoadScheduler::hostForURL(const URL& url, CreateHostPolicy createHostPolicy)
{
if (!url.protocolIsInHTTPFamily())
return m_nonHTTPProtocolHost;
m_hosts.checkConsistency();
String hostName = url.host().toString();
HostInformation* host = m_hosts.get(hostName);
if (!host && createHostPolicy == CreateIfNotFound) {
host = new HostInformation(hostName, maxRequestsInFlightPerHost);
m_hosts.add(hostName, host);
}
return host;
}
WebResourceLoadScheduler::WebResourceLoadScheduler()
: m_nonHTTPProtocolHost(new HostInformation(String(), maxRequestsInFlightForNonHTTPProtocols))
, m_requestTimer(*this, &WebResourceLoadScheduler::requestTimerFired)
, m_suspendPendingRequestsCount(0)
, m_isSerialLoadingEnabled(false)
{
maxRequestsInFlightPerHost = initializeMaximumHTTPConnectionCountPerHost();
}
WebResourceLoadScheduler::~WebResourceLoadScheduler()
{
}
void WebResourceLoadScheduler::loadResource(Frame& frame, CachedResource& resource, ResourceRequest&& request, const ResourceLoaderOptions& options, CompletionHandler<void(RefPtr<WebCore::SubresourceLoader>&&)>&& completionHandler)
{
SubresourceLoader::create(frame, resource, WTFMove(request), options, [this, completionHandler = WTFMove(completionHandler)] (RefPtr<WebCore::SubresourceLoader>&& loader) mutable {
if (loader)
scheduleLoad(loader.get());
#if PLATFORM(IOS)
if (!loader || loader->reachedTerminalState())
return completionHandler(nullptr);
#endif
completionHandler(WTFMove(loader));
});
}
void WebResourceLoadScheduler::loadResourceSynchronously(FrameLoader& frameLoader, unsigned long, const ResourceRequest& request, ClientCredentialPolicy, const FetchOptions& options, const HTTPHeaderMap&, ResourceError& error, ResourceResponse& response, Vector<char>& data)
{
ResourceHandle::loadResourceSynchronously(frameLoader.networkingContext(), request, options.credentials == FetchOptions::Credentials::Omit ? StoredCredentialsPolicy::DoNotUse : StoredCredentialsPolicy::Use, error, response, data);
}
void WebResourceLoadScheduler::pageLoadCompleted(uint64_t )
{
}
void WebResourceLoadScheduler::schedulePluginStreamLoad(Frame& frame, NetscapePlugInStreamLoaderClient& client, ResourceRequest&& request, CompletionHandler<void(RefPtr<WebCore::NetscapePlugInStreamLoader>&&)>&& completionHandler)
{
NetscapePlugInStreamLoader::create(frame, client, WTFMove(request), [this, completionHandler = WTFMove(completionHandler)] (RefPtr<WebCore::NetscapePlugInStreamLoader>&& loader) mutable {
if (loader)
scheduleLoad(loader.get());
completionHandler(WTFMove(loader));
});
}
void WebResourceLoadScheduler::scheduleLoad(ResourceLoader* resourceLoader)
{
ASSERT(resourceLoader);
#if PLATFORM(IOS)
if (!isSuspendingPendingRequests() && resourceLoader->documentLoader()->archiveResourceForURL(resourceLoader->iOSOriginalRequest().url())) {
resourceLoader->startLoading();
return;
}
#else
if (resourceLoader->documentLoader()->archiveResourceForURL(resourceLoader->request().url())) {
resourceLoader->start();
return;
}
#endif
#if PLATFORM(IOS)
HostInformation* host = hostForURL(resourceLoader->iOSOriginalRequest().url(), CreateIfNotFound);
#else
HostInformation* host = hostForURL(resourceLoader->url(), CreateIfNotFound);
#endif
ResourceLoadPriority priority = resourceLoader->request().priority();
bool hadRequests = host->hasRequests();
host->schedule(resourceLoader, priority);
#if PLATFORM(COCOA)
if (ResourceRequest::resourcePrioritiesEnabled() && !isSuspendingPendingRequests()) {
servePendingRequests(host, ResourceLoadPriority::VeryLow);
return;
}
#endif
#if PLATFORM(IOS)
if ((priority > ResourceLoadPriority::Low || !resourceLoader->iOSOriginalRequest().url().protocolIsInHTTPFamily() || (priority == ResourceLoadPriority::Low && !hadRequests)) && !isSuspendingPendingRequests()) {
servePendingRequests(host, priority);
return;
}
#else
if (priority > ResourceLoadPriority::Low || !resourceLoader->url().protocolIsInHTTPFamily() || (priority == ResourceLoadPriority::Low && !hadRequests)) {
servePendingRequests(host, priority);
return;
}
#endif
scheduleServePendingRequests();
}
void WebResourceLoadScheduler::remove(ResourceLoader* resourceLoader)
{
ASSERT(resourceLoader);
HostInformation* host = hostForURL(resourceLoader->url());
if (host)
host->remove(resourceLoader);
#if PLATFORM(IOS)
if (!resourceLoader->iOSOriginalRequest().isNull()) {
HostInformation* originalHost = hostForURL(resourceLoader->iOSOriginalRequest().url());
if (originalHost && originalHost != host)
originalHost->remove(resourceLoader);
}
#endif
scheduleServePendingRequests();
}
void WebResourceLoadScheduler::setDefersLoading(ResourceLoader*, bool)
{
}
void WebResourceLoadScheduler::crossOriginRedirectReceived(ResourceLoader* resourceLoader, const URL& redirectURL)
{
HostInformation* oldHost = hostForURL(resourceLoader->url());
ASSERT(oldHost);
if (!oldHost)
return;
HostInformation* newHost = hostForURL(redirectURL, CreateIfNotFound);
if (oldHost->name() == newHost->name())
return;
newHost->addLoadInProgress(resourceLoader);
oldHost->remove(resourceLoader);
}
void WebResourceLoadScheduler::servePendingRequests(ResourceLoadPriority minimumPriority)
{
if (isSuspendingPendingRequests())
return;
m_requestTimer.stop();
servePendingRequests(m_nonHTTPProtocolHost, minimumPriority);
for (auto* host : copyToVector(m_hosts.values())) {
if (host->hasRequests())
servePendingRequests(host, minimumPriority);
else
delete m_hosts.take(host->name());
}
}
void WebResourceLoadScheduler::servePendingRequests(HostInformation* host, ResourceLoadPriority minimumPriority)
{
auto priority = ResourceLoadPriority::Highest;
while (true) {
auto& requestsPending = host->requestsPending(priority);
while (!requestsPending.isEmpty()) {
RefPtr<ResourceLoader> resourceLoader = requestsPending.first();
Document* document = resourceLoader->frameLoader() ? resourceLoader->frameLoader()->frame().document() : 0;
bool shouldLimitRequests = !host->name().isNull() || (document && (document->parsing() || !document->haveStylesheetsLoaded()));
if (shouldLimitRequests && host->limitRequests(priority))
return;
requestsPending.removeFirst();
host->addLoadInProgress(resourceLoader.get());
#if PLATFORM(IOS)
if (!IOSApplication::isWebProcess()) {
resourceLoader->startLoading();
return;
}
#endif
resourceLoader->start();
}
if (priority == minimumPriority)
return;
--priority;
}
}
void WebResourceLoadScheduler::suspendPendingRequests()
{
++m_suspendPendingRequestsCount;
}
void WebResourceLoadScheduler::resumePendingRequests()
{
ASSERT(m_suspendPendingRequestsCount);
--m_suspendPendingRequestsCount;
if (m_suspendPendingRequestsCount)
return;
if (!m_hosts.isEmpty() || m_nonHTTPProtocolHost->hasRequests())
scheduleServePendingRequests();
}
void WebResourceLoadScheduler::scheduleServePendingRequests()
{
if (!m_requestTimer.isActive())
m_requestTimer.startOneShot(0_s);
}
void WebResourceLoadScheduler::requestTimerFired()
{
servePendingRequests();
}
WebResourceLoadScheduler::HostInformation::HostInformation(const String& name, unsigned maxRequestsInFlight)
: m_name(name)
, m_maxRequestsInFlight(maxRequestsInFlight)
{
}
WebResourceLoadScheduler::HostInformation::~HostInformation()
{
ASSERT(!hasRequests());
}
unsigned WebResourceLoadScheduler::HostInformation::priorityToIndex(ResourceLoadPriority priority)
{
switch (priority) {
case ResourceLoadPriority::VeryLow:
return 0;
case ResourceLoadPriority::Low:
return 1;
case ResourceLoadPriority::Medium:
return 2;
case ResourceLoadPriority::High:
return 3;
case ResourceLoadPriority::VeryHigh:
return 4;
}
ASSERT_NOT_REACHED();
return 0;
}
void WebResourceLoadScheduler::HostInformation::schedule(ResourceLoader* resourceLoader, ResourceLoadPriority priority)
{
m_requestsPending[priorityToIndex(priority)].append(resourceLoader);
}
void WebResourceLoadScheduler::HostInformation::addLoadInProgress(ResourceLoader* resourceLoader)
{
m_requestsLoading.add(resourceLoader);
}
void WebResourceLoadScheduler::HostInformation::remove(ResourceLoader* resourceLoader)
{
if (m_requestsLoading.remove(resourceLoader))
return;
for (auto& requestQueue : m_requestsPending) {
for (auto it = requestQueue.begin(), end = requestQueue.end(); it != end; ++it) {
if (*it == resourceLoader) {
requestQueue.remove(it);
return;
}
}
}
}
bool WebResourceLoadScheduler::HostInformation::hasRequests() const
{
if (!m_requestsLoading.isEmpty())
return true;
for (auto& requestQueue : m_requestsPending) {
if (!requestQueue.isEmpty())
return true;
}
return false;
}
bool WebResourceLoadScheduler::HostInformation::limitRequests(ResourceLoadPriority priority) const
{
if (priority == ResourceLoadPriority::VeryLow && !m_requestsLoading.isEmpty())
return true;
return m_requestsLoading.size() >= (webResourceLoadScheduler().isSerialLoadingEnabled() ? 1 : m_maxRequestsInFlight);
}
void WebResourceLoadScheduler::startPingLoad(Frame& frame, ResourceRequest& request, const HTTPHeaderMap&, const FetchOptions& options, PingLoadCompletionHandler&& completionHandler)
{
new PingHandle(frame.loader().networkingContext(), request, options.credentials != FetchOptions::Credentials::Omit, options.redirect == FetchOptions::Redirect::Follow, WTFMove(completionHandler));
}
bool WebResourceLoadScheduler::isOnLine() const
{
return NetworkStateNotifier::singleton().onLine();
}
void WebResourceLoadScheduler::addOnlineStateChangeListener(WTF::Function<void(bool)>&& listener)
{
NetworkStateNotifier::singleton().addListener(WTFMove(listener));
}
void WebResourceLoadScheduler::preconnectTo(FrameLoader&, const URL&, StoredCredentialsPolicy, PreconnectCompletionHandler&&)
{
}