Commit 7eacc60e authored by csharrison's avatar csharrison Committed by Commit bot

Don't wait for AppCache for link rel preloads

The preload scanner currently waits for the Application Cache to be
initialized before sending out any preloads. This is less important for link
rel preloads, which are directives to send a fetch as soon as possible.

This patch bypasses the fetch queuing for these preloads.

For link preloads in headers, we can issue these as early as the commit
point (for those which don't have a media attribute).

BUG=629420

Review-Url: https://codereview.chromium.org/2165653004
Cr-Commit-Position: refs/heads/master@{#407227}
parent ac3dfbdb
<?php
header("Link: </resources/dummy.css>;rel=preload", false);
header("Link: </resources/square.png>;rel=preload;as=image;media=(min-width: 1px)", false);
?>
<!DOCTYPE html>
<html>
<body>
<script src="/resources/testharness.js"></script>
<script src="/resources/testharnessreport.js"></script>
<link rel="preload" href="/resources/dummy.js" as="script">
<script>
var t = async_test('Makes sure that Link headers preload resources');
</script>
<script src="/resources/slow-script.pl?delay=200"></script>
<script>
window.addEventListener("load", t.step_func(function() {
var nonMediaHeader = performance.getEntriesByName(
"http://127.0.0.1:8000/resources/dummy.css")[0];
var mediaHeader = performance.getEntriesByName(
"http://127.0.0.1:8000/resources/square.png")[0];
var markup = performance.getEntriesByName(
"http://127.0.0.1:8000/resources/dummy.js")[0];
var normalResource = performance.getEntriesByName(
"http://127.0.0.1:8000/resources/testharness.js")[0];
// The non-media header can be processed at commit time.
// The media header needs to tokenize the first chunk of html, and the
// markup still needs to be tokenized.
// Note: the link preloads can be preloaded before the normal resource
// because they don't need to wait for the document element to be
// available (ApplicationCache initialization time).
assert_greater_than(markup.startTime, nonMediaHeader.startTime);
assert_greater_than(mediaHeader.startTime, nonMediaHeader.startTime);
assert_greater_than(normalResource.startTime, mediaHeader.startTime);
assert_greater_than(normalResource.startTime, markup.startTime);
t.done();
}));
</script>
</body>
</html>
......@@ -298,17 +298,32 @@ void HTMLDocumentParser::notifyPendingParsedChunks()
// ApplicationCache needs to be initialized before issuing preloads.
// We suspend preload until HTMLHTMLElement is inserted and
// ApplicationCache is initialized.
// ApplicationCache is initialized. Note: link rel preloads don't follow
// this policy per the spec. These directives should initiate a fetch as
// fast as possible.
if (!m_triedLoadingLinkHeaders && document()->loader() && !pendingChunks.isEmpty()) {
// Note that on commit, the loader dispatched preloads for all the
// non-media links.
document()->loader()->dispatchLinkHeaderPreloads(&pendingChunks.first()->viewport, LinkLoader::OnlyLoadMedia);
m_triedLoadingLinkHeaders = true;
}
if (!document()->documentElement()) {
PreloadRequestStream linkRelPreloads;
for (auto& chunk : pendingChunks) {
for (auto& request : chunk->preloads)
m_queuedPreloads.append(std::move(request));
for (auto& request : chunk->preloads) {
if (request->isLinkRelPreload())
linkRelPreloads.append(std::move(request));
else
m_queuedPreloads.append(std::move(request));
}
for (auto& index : chunk->likelyDocumentWriteScriptIndices) {
const CompactHTMLToken& token = chunk->tokens->at(index);
ASSERT(token.type() == HTMLToken::TokenType::Character);
m_queuedDocumentWriteScripts.append(token.data());
}
}
m_preloader->takeAndPreload(linkRelPreloads);
} else {
// We can safely assume that there are no queued preloads request after
// the document element is available, as we empty the queue immediately
......@@ -468,16 +483,6 @@ size_t HTMLDocumentParser::processParsedChunkFromBackgroundParser(std::unique_pt
if (isStopped())
break;
if (!m_triedLoadingLinkHeaders && document()->loader()) {
String linkHeader = document()->loader()->response().httpHeaderField(HTTPNames::Link);
if (!linkHeader.isEmpty()) {
ASSERT(chunk);
LinkLoader::loadLinksFromHeader(linkHeader, document()->loader()->response().url(),
document(), NetworkHintsInterfaceImpl(), LinkLoader::OnlyLoadResources, &(chunk->viewport));
m_triedLoadingLinkHeaders = true;
}
}
if (isWaitingForScripts()) {
ASSERT(it + 1 == tokens->end()); // The </script> is assumed to be the last token of this bunch.
runScriptsForPausedTreeBuilder();
......
......@@ -56,6 +56,7 @@ public:
float resourceWidth() const { return m_resourceWidth.isSet ? m_resourceWidth.width : 0; }
const KURL& baseURL() const { return m_baseURL; }
bool isPreconnect() const { return m_requestType == RequestTypePreconnect; }
bool isLinkRelPreload() const { return m_requestType == RequestTypeLinkRelPreload; }
const ClientHintsPreferences& preferences() const { return m_clientHintsPreferences; }
ReferrerPolicy getReferrerPolicy() const { return m_referrerPolicy; }
void setIntegrityMetadata(const IntegrityMetadataSet& metadataSet)
......
......@@ -208,6 +208,11 @@ Resource* DocumentLoader::startPreload(Resource::Type type, FetchRequest& reques
return resource;
}
void DocumentLoader::dispatchLinkHeaderPreloads(ViewportDescriptionWrapper* viewport, LinkLoader::MediaPreloadPolicy mediaPolicy)
{
LinkLoader::loadLinksFromHeader(response().httpHeaderField(HTTPNames::Link), response().url(), m_frame->document(), NetworkHintsInterfaceImpl(), LinkLoader::OnlyLoadResources, mediaPolicy, viewport);
}
void DocumentLoader::didChangePerformanceTiming()
{
if (frame() && frame()->isMainFrame() && m_state >= Committed) {
......
......@@ -41,6 +41,7 @@
#include "core/loader/DocumentLoadTiming.h"
#include "core/loader/DocumentWriter.h"
#include "core/loader/FrameLoaderTypes.h"
#include "core/loader/LinkLoader.h"
#include "core/loader/NavigationPolicy.h"
#include "platform/SharedBuffer.h"
#include "platform/network/ResourceError.h"
......@@ -60,6 +61,7 @@ class LocalFrame;
class FrameLoader;
class ResourceLoader;
class WebDocumentSubresourceFilter;
struct ViewportDescriptionWrapper;
class CORE_EXPORT DocumentLoader : public GarbageCollectedFinalized<DocumentLoader>, private RawResourceClient {
public:
......@@ -146,6 +148,8 @@ public:
void setWasBlockedAfterXFrameOptionsOrCSP() { m_wasBlockedAfterXFrameOptionsOrCSP = true; }
bool wasBlockedAfterXFrameOptionsOrCSP() { return m_wasBlockedAfterXFrameOptionsOrCSP; }
void dispatchLinkHeaderPreloads(ViewportDescriptionWrapper*, LinkLoader::MediaPreloadPolicy);
Resource* startPreload(Resource::Type, FetchRequest&);
DECLARE_VIRTUAL_TRACE();
......
......@@ -797,7 +797,7 @@ void FrameFetchContext::dispatchDidReceiveResponseInternal(unsigned long identif
// When response is received with a provisional docloader, the resource haven't committed yet, and we cannot load resources, only preconnect.
resourceLoadingPolicy = LinkLoader::DoNotLoadResources;
}
LinkLoader::loadLinksFromHeader(response.httpHeaderField(HTTPNames::Link), response.url(), frame()->document(), NetworkHintsInterfaceImpl(), resourceLoadingPolicy, nullptr);
LinkLoader::loadLinksFromHeader(response.httpHeaderField(HTTPNames::Link), response.url(), frame()->document(), NetworkHintsInterfaceImpl(), resourceLoadingPolicy, LinkLoader::LoadAll, nullptr);
if (response.hasMajorCertificateErrors())
MixedContentChecker::handleCertificateError(frame(), response, frameType, requestContext);
......
......@@ -445,6 +445,11 @@ void FrameLoader::receivedFirstData()
if (client()->isControlledByServiceWorker(*m_documentLoader))
client()->didObserveLoadingBehavior(WebLoadingBehaviorServiceWorkerControlled);
// Links with media values need more information (like viewport
// information). This happens after the first chunk is parsed in
// HTMLDocumentParser.
m_documentLoader->dispatchLinkHeaderPreloads(nullptr, LinkLoader::OnlyLoadNonMedia);
TRACE_EVENT1("devtools.timeline", "CommitLoad", "data", InspectorCommitLoadEvent::data(m_frame));
InspectorInstrumentation::didCommitLoad(m_frame, m_documentLoader.get());
m_frame->page()->didCommitLoad(m_frame);
......
......@@ -302,7 +302,7 @@ static Resource* preloadIfNeeded(const LinkRelAttribute& relAttribute, const KUR
}
void LinkLoader::loadLinksFromHeader(const String& headerValue, const KURL& baseURL, Document* document, const NetworkHintsInterface& networkHintsInterface,
CanLoadResources canLoadResources, ViewportDescriptionWrapper* viewportDescriptionWrapper)
CanLoadResources canLoadResources, MediaPreloadPolicy mediaPolicy, ViewportDescriptionWrapper* viewportDescriptionWrapper)
{
if (!document || headerValue.isEmpty())
return;
......@@ -311,6 +311,11 @@ void LinkLoader::loadLinksFromHeader(const String& headerValue, const KURL& base
if (!header.valid() || header.url().isEmpty() || header.rel().isEmpty())
continue;
if (mediaPolicy == OnlyLoadMedia && header.media().isEmpty())
continue;
if (mediaPolicy == OnlyLoadNonMedia && !header.media().isEmpty())
continue;
LinkRelAttribute relAttribute(header.rel());
KURL url(baseURL, header.url());
// Sanity check to avoid re-entrancy here.
......
......@@ -75,7 +75,10 @@ public:
void released();
bool loadLink(const LinkRelAttribute&, CrossOriginAttributeValue, const String& type, const String& as, const String& media, const KURL&, Document&, const NetworkHintsInterface&);
enum CanLoadResources { OnlyLoadResources, DoNotLoadResources, LoadResourcesAndPreconnect };
static void loadLinksFromHeader(const String& headerValue, const KURL& baseURL, Document*, const NetworkHintsInterface&, CanLoadResources, ViewportDescriptionWrapper*);
// Media links cannot be preloaded until the first chunk is parsed. The rest
// can be preloaded at commit time.
enum MediaPreloadPolicy { LoadAll, OnlyLoadNonMedia, OnlyLoadMedia };
static void loadLinksFromHeader(const String& headerValue, const KURL& baseURL, Document*, const NetworkHintsInterface&, CanLoadResources, MediaPreloadPolicy, ViewportDescriptionWrapper*);
static bool getResourceTypeFromAsAttribute(const String& as, Resource::Type&);
DECLARE_TRACE();
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment