blob: 2d8f8c5117a33b951f13f52a18cc8d852833525e [file] [log] [blame]
// Copyright 2015 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "core/loader/LinkLoader.h"
#include <base/macros.h>
#include <memory>
#include "core/frame/Settings.h"
#include "core/html/LinkRelAttribute.h"
#include "core/loader/DocumentLoader.h"
#include "core/loader/LinkLoaderClient.h"
#include "core/loader/NetworkHintsInterface.h"
#include "core/testing/DummyPageHolder.h"
#include "platform/loader/fetch/MemoryCache.h"
#include "platform/loader/fetch/ResourceFetcher.h"
#include "platform/loader/fetch/ResourceLoadPriority.h"
#include "platform/testing/URLTestHelpers.h"
#include "public/platform/Platform.h"
#include "public/platform/WebURLLoaderMockFactory.h"
#include "testing/gtest/include/gtest/gtest.h"
namespace blink {
namespace {
class MockLinkLoaderClient final
: public GarbageCollectedFinalized<MockLinkLoaderClient>,
public LinkLoaderClient {
USING_GARBAGE_COLLECTED_MIXIN(MockLinkLoaderClient);
public:
static MockLinkLoaderClient* create(bool shouldLoad) {
return new MockLinkLoaderClient(shouldLoad);
}
DEFINE_INLINE_VIRTUAL_TRACE() { LinkLoaderClient::trace(visitor); }
bool shouldLoadLink() override { return m_shouldLoad; }
void linkLoaded() override {}
void linkLoadingErrored() override {}
void didStartLinkPrerender() override {}
void didStopLinkPrerender() override {}
void didSendLoadForLinkPrerender() override {}
void didSendDOMContentLoadedForLinkPrerender() override {}
RefPtr<WebTaskRunner> getLoadingTaskRunner() override {
return Platform::current()->currentThread()->getWebTaskRunner();
}
private:
explicit MockLinkLoaderClient(bool shouldLoad) : m_shouldLoad(shouldLoad) {}
const bool m_shouldLoad;
};
class NetworkHintsMock : public NetworkHintsInterface {
public:
NetworkHintsMock() {}
void dnsPrefetchHost(const String& host) const override {
m_didDnsPrefetch = true;
}
void preconnectHost(
const KURL& host,
const CrossOriginAttributeValue crossOrigin) const override {
m_didPreconnect = true;
m_isHTTPS = host.protocolIs("https");
m_isCrossOrigin = (crossOrigin == CrossOriginAttributeAnonymous);
}
bool didDnsPrefetch() { return m_didDnsPrefetch; }
bool didPreconnect() { return m_didPreconnect; }
bool isHTTPS() { return m_isHTTPS; }
bool isCrossOrigin() { return m_isCrossOrigin; }
private:
mutable bool m_didDnsPrefetch = false;
mutable bool m_didPreconnect = false;
mutable bool m_isHTTPS = false;
mutable bool m_isCrossOrigin = false;
};
struct PreloadTestParams {
const char* href;
const char* as;
const char* type;
const char* media;
const ReferrerPolicy referrerPolicy;
const ResourceLoadPriority priority;
const WebURLRequest::RequestContext context;
const bool linkLoaderShouldLoadValue;
const bool expectingLoad;
const ReferrerPolicy expectedReferrerPolicy;
};
class LinkLoaderPreloadTest
: public ::testing::TestWithParam<PreloadTestParams> {
public:
~LinkLoaderPreloadTest() {
Platform::current()
->getURLLoaderMockFactory()
->unregisterAllURLsAndClearMemoryCache();
}
};
TEST_P(LinkLoaderPreloadTest, Preload) {
const auto& testCase = GetParam();
std::unique_ptr<DummyPageHolder> dummyPageHolder =
DummyPageHolder::create(IntSize(500, 500));
ResourceFetcher* fetcher = dummyPageHolder->document().fetcher();
ASSERT_TRUE(fetcher);
dummyPageHolder->frame().settings()->setScriptEnabled(true);
Persistent<MockLinkLoaderClient> loaderClient =
MockLinkLoaderClient::create(testCase.linkLoaderShouldLoadValue);
LinkLoader* loader = LinkLoader::create(loaderClient.get());
KURL hrefURL = KURL(KURL(), testCase.href);
URLTestHelpers::registerMockedErrorURLLoad(hrefURL);
loader->loadLink(LinkRelAttribute("preload"), CrossOriginAttributeNotSet,
testCase.type, testCase.as, testCase.media,
testCase.referrerPolicy, hrefURL,
dummyPageHolder->document(), NetworkHintsMock());
if (testCase.expectingLoad &&
testCase.priority != ResourceLoadPriorityUnresolved) {
ASSERT_EQ(1, fetcher->countPreloads());
Resource* resource = loader->linkPreloadedResourceForTesting();
ASSERT_NE(resource, nullptr);
EXPECT_TRUE(fetcher->containsAsPreloadForTesting(resource));
EXPECT_EQ(testCase.priority, resource->resourceRequest().priority());
EXPECT_EQ(testCase.context, resource->resourceRequest().requestContext());
if (testCase.expectedReferrerPolicy != ReferrerPolicyDefault) {
EXPECT_EQ(testCase.expectedReferrerPolicy,
resource->resourceRequest().getReferrerPolicy());
}
} else {
ASSERT_EQ(0, fetcher->countPreloads());
}
}
constexpr PreloadTestParams kPreloadTestParams[] = {
{"http://example.test/cat.jpg", "image", "", "", ReferrerPolicyDefault,
ResourceLoadPriorityLow, WebURLRequest::RequestContextImage, true, true,
ReferrerPolicyDefault},
{"http://example.test/cat.js", "script", "", "", ReferrerPolicyDefault,
ResourceLoadPriorityHigh, WebURLRequest::RequestContextScript, true, true,
ReferrerPolicyDefault},
{"http://example.test/cat.css", "style", "", "", ReferrerPolicyDefault,
ResourceLoadPriorityVeryHigh, WebURLRequest::RequestContextStyle, true,
true, ReferrerPolicyDefault},
// TODO(yoav): It doesn't seem like the audio context is ever used. That
// should probably be fixed (or we can consolidate audio and video).
{"http://example.test/cat.wav", "audio", "", "", ReferrerPolicyDefault,
ResourceLoadPriorityLow, WebURLRequest::RequestContextVideo, true, true,
ReferrerPolicyDefault},
{"http://example.test/cat.mp4", "video", "", "", ReferrerPolicyDefault,
ResourceLoadPriorityLow, WebURLRequest::RequestContextVideo, true, true,
ReferrerPolicyDefault},
{"http://example.test/cat.vtt", "track", "", "", ReferrerPolicyDefault,
ResourceLoadPriorityLow, WebURLRequest::RequestContextTrack, true, true,
ReferrerPolicyDefault},
{"http://example.test/cat.woff", "font", "", "", ReferrerPolicyDefault,
ResourceLoadPriorityHigh, WebURLRequest::RequestContextFont, true, true,
ReferrerPolicyDefault},
// TODO(yoav): subresource should be *very* low priority (rather than
// low).
{"http://example.test/cat.empty", "", "", "", ReferrerPolicyDefault,
ResourceLoadPriorityHigh, WebURLRequest::RequestContextSubresource, true,
true, ReferrerPolicyDefault},
{"http://example.test/cat.blob", "blabla", "", "", ReferrerPolicyDefault,
ResourceLoadPriorityLow, WebURLRequest::RequestContextSubresource, false,
false, ReferrerPolicyDefault},
{"bla://example.test/cat.gif", "image", "", "", ReferrerPolicyDefault,
ResourceLoadPriorityUnresolved, WebURLRequest::RequestContextImage, false,
false, ReferrerPolicyDefault},
// MIME type tests
{"http://example.test/cat.webp", "image", "image/webp", "",
ReferrerPolicyDefault, ResourceLoadPriorityLow,
WebURLRequest::RequestContextImage, true, true, ReferrerPolicyDefault},
{"http://example.test/cat.svg", "image", "image/svg+xml", "",
ReferrerPolicyDefault, ResourceLoadPriorityLow,
WebURLRequest::RequestContextImage, true, true, ReferrerPolicyDefault},
{"http://example.test/cat.jxr", "image", "image/jxr", "",
ReferrerPolicyDefault, ResourceLoadPriorityUnresolved,
WebURLRequest::RequestContextImage, false, false, ReferrerPolicyDefault},
{"http://example.test/cat.js", "script", "text/javascript", "",
ReferrerPolicyDefault, ResourceLoadPriorityHigh,
WebURLRequest::RequestContextScript, true, true, ReferrerPolicyDefault},
{"http://example.test/cat.js", "script", "text/coffeescript", "",
ReferrerPolicyDefault, ResourceLoadPriorityUnresolved,
WebURLRequest::RequestContextScript, false, false, ReferrerPolicyDefault},
{"http://example.test/cat.css", "style", "text/css", "",
ReferrerPolicyDefault, ResourceLoadPriorityVeryHigh,
WebURLRequest::RequestContextStyle, true, true, ReferrerPolicyDefault},
{"http://example.test/cat.css", "style", "text/sass", "",
ReferrerPolicyDefault, ResourceLoadPriorityUnresolved,
WebURLRequest::RequestContextStyle, false, false, ReferrerPolicyDefault},
{"http://example.test/cat.wav", "audio", "audio/wav", "",
ReferrerPolicyDefault, ResourceLoadPriorityLow,
WebURLRequest::RequestContextVideo, true, true, ReferrerPolicyDefault},
{"http://example.test/cat.wav", "audio", "audio/mp57", "",
ReferrerPolicyDefault, ResourceLoadPriorityUnresolved,
WebURLRequest::RequestContextVideo, false, false, ReferrerPolicyDefault},
{"http://example.test/cat.webm", "video", "video/webm", "",
ReferrerPolicyDefault, ResourceLoadPriorityLow,
WebURLRequest::RequestContextVideo, true, true, ReferrerPolicyDefault},
{"http://example.test/cat.mp199", "video", "video/mp199", "",
ReferrerPolicyDefault, ResourceLoadPriorityUnresolved,
WebURLRequest::RequestContextVideo, false, false, ReferrerPolicyDefault},
{"http://example.test/cat.vtt", "track", "text/vtt", "",
ReferrerPolicyDefault, ResourceLoadPriorityLow,
WebURLRequest::RequestContextTrack, true, true, ReferrerPolicyDefault},
{"http://example.test/cat.vtt", "track", "text/subtitlething", "",
ReferrerPolicyDefault, ResourceLoadPriorityUnresolved,
WebURLRequest::RequestContextTrack, false, false, ReferrerPolicyDefault},
{"http://example.test/cat.woff", "font", "font/woff2", "",
ReferrerPolicyDefault, ResourceLoadPriorityHigh,
WebURLRequest::RequestContextFont, true, true, ReferrerPolicyDefault},
{"http://example.test/cat.woff", "font", "font/woff84", "",
ReferrerPolicyDefault, ResourceLoadPriorityUnresolved,
WebURLRequest::RequestContextFont, false, false, ReferrerPolicyDefault},
{"http://example.test/cat.empty", "", "foo/bar", "", ReferrerPolicyDefault,
ResourceLoadPriorityHigh, WebURLRequest::RequestContextSubresource, true,
true, ReferrerPolicyDefault},
{"http://example.test/cat.blob", "blabla", "foo/bar", "",
ReferrerPolicyDefault, ResourceLoadPriorityLow,
WebURLRequest::RequestContextSubresource, false, false,
ReferrerPolicyDefault},
// Media tests
{"http://example.test/cat.gif", "image", "image/gif", "(max-width: 600px)",
ReferrerPolicyDefault, ResourceLoadPriorityLow,
WebURLRequest::RequestContextImage, true, true, ReferrerPolicyDefault},
{"http://example.test/cat.gif", "image", "image/gif", "(max-width: 400px)",
ReferrerPolicyDefault, ResourceLoadPriorityUnresolved,
WebURLRequest::RequestContextImage, true, false, ReferrerPolicyDefault},
{"http://example.test/cat.gif", "image", "image/gif", "(max-width: 600px)",
ReferrerPolicyDefault, ResourceLoadPriorityLow,
WebURLRequest::RequestContextImage, false, false, ReferrerPolicyDefault},
// Referrer Policy
{"http://example.test/cat.gif", "image", "image/gif", "",
ReferrerPolicyOrigin, ResourceLoadPriorityLow,
WebURLRequest::RequestContextImage, true, true, ReferrerPolicyOrigin},
{"http://example.test/cat.gif", "image", "image/gif", "",
ReferrerPolicyOriginWhenCrossOrigin, ResourceLoadPriorityLow,
WebURLRequest::RequestContextImage, true, true,
ReferrerPolicyOriginWhenCrossOrigin},
{"http://example.test/cat.gif", "image", "image/gif", "",
ReferrerPolicyNever, ResourceLoadPriorityLow,
WebURLRequest::RequestContextImage, true, true, ReferrerPolicyNever}};
INSTANTIATE_TEST_CASE_P(LinkLoaderPreloadTest,
LinkLoaderPreloadTest,
::testing::ValuesIn(kPreloadTestParams));
TEST(LinkLoaderTest, Prefetch) {
struct TestCase {
const char* href;
// TODO(yoav): Add support for type and media crbug.com/662687
const char* type;
const char* media;
const ReferrerPolicy referrerPolicy;
const bool linkLoaderShouldLoadValue;
const bool expectingLoad;
const ReferrerPolicy expectedReferrerPolicy;
} cases[] = {
// Referrer Policy
{"http://example.test/cat.jpg", "image/jpg", "", ReferrerPolicyOrigin,
true, true, ReferrerPolicyOrigin},
{"http://example.test/cat.jpg", "image/jpg", "",
ReferrerPolicyOriginWhenCrossOrigin, true, true,
ReferrerPolicyOriginWhenCrossOrigin},
{"http://example.test/cat.jpg", "image/jpg", "", ReferrerPolicyNever,
true, true, ReferrerPolicyNever},
};
// Test the cases with a single header
for (const auto& testCase : cases) {
std::unique_ptr<DummyPageHolder> dummyPageHolder =
DummyPageHolder::create(IntSize(500, 500));
dummyPageHolder->frame().settings()->setScriptEnabled(true);
Persistent<MockLinkLoaderClient> loaderClient =
MockLinkLoaderClient::create(testCase.linkLoaderShouldLoadValue);
LinkLoader* loader = LinkLoader::create(loaderClient.get());
KURL hrefURL = KURL(KURL(), testCase.href);
URLTestHelpers::registerMockedErrorURLLoad(hrefURL);
loader->loadLink(LinkRelAttribute("prefetch"), CrossOriginAttributeNotSet,
testCase.type, "", testCase.media, testCase.referrerPolicy,
hrefURL, dummyPageHolder->document(), NetworkHintsMock());
ASSERT_TRUE(dummyPageHolder->document().fetcher());
Resource* resource = loader->resource();
if (testCase.expectingLoad) {
EXPECT_TRUE(resource);
} else {
EXPECT_FALSE(resource);
}
if (resource) {
if (testCase.expectedReferrerPolicy != ReferrerPolicyDefault) {
EXPECT_EQ(testCase.expectedReferrerPolicy,
resource->resourceRequest().getReferrerPolicy());
}
}
Platform::current()
->getURLLoaderMockFactory()
->unregisterAllURLsAndClearMemoryCache();
}
}
TEST(LinkLoaderTest, DNSPrefetch) {
struct {
const char* href;
const bool shouldLoad;
} cases[] = {
{"http://example.com/", true},
{"https://example.com/", true},
{"//example.com/", true},
{"//example.com/", false},
};
// Test the cases with a single header
for (const auto& testCase : cases) {
std::unique_ptr<DummyPageHolder> dummyPageHolder =
DummyPageHolder::create(IntSize(500, 500));
dummyPageHolder->document().settings()->setDNSPrefetchingEnabled(true);
Persistent<MockLinkLoaderClient> loaderClient =
MockLinkLoaderClient::create(testCase.shouldLoad);
LinkLoader* loader = LinkLoader::create(loaderClient.get());
KURL hrefURL =
KURL(KURL(ParsedURLStringTag(), String("http://example.com")),
testCase.href);
NetworkHintsMock networkHints;
loader->loadLink(LinkRelAttribute("dns-prefetch"),
CrossOriginAttributeNotSet, String(), String(), String(),
ReferrerPolicyDefault, hrefURL,
dummyPageHolder->document(), networkHints);
EXPECT_FALSE(networkHints.didPreconnect());
EXPECT_EQ(testCase.shouldLoad, networkHints.didDnsPrefetch());
}
}
TEST(LinkLoaderTest, Preconnect) {
struct {
const char* href;
CrossOriginAttributeValue crossOrigin;
const bool shouldLoad;
const bool isHTTPS;
const bool isCrossOrigin;
} cases[] = {
{"http://example.com/", CrossOriginAttributeNotSet, true, false, false},
{"https://example.com/", CrossOriginAttributeNotSet, true, true, false},
{"http://example.com/", CrossOriginAttributeAnonymous, true, false, true},
{"//example.com/", CrossOriginAttributeNotSet, true, false, false},
{"http://example.com/", CrossOriginAttributeNotSet, false, false, false},
};
// Test the cases with a single header
for (const auto& testCase : cases) {
std::unique_ptr<DummyPageHolder> dummyPageHolder =
DummyPageHolder::create(IntSize(500, 500));
Persistent<MockLinkLoaderClient> loaderClient =
MockLinkLoaderClient::create(testCase.shouldLoad);
LinkLoader* loader = LinkLoader::create(loaderClient.get());
KURL hrefURL =
KURL(KURL(ParsedURLStringTag(), String("http://example.com")),
testCase.href);
NetworkHintsMock networkHints;
loader->loadLink(LinkRelAttribute("preconnect"), testCase.crossOrigin,
String(), String(), String(), ReferrerPolicyDefault,
hrefURL, dummyPageHolder->document(), networkHints);
EXPECT_EQ(testCase.shouldLoad, networkHints.didPreconnect());
EXPECT_EQ(testCase.isHTTPS, networkHints.isHTTPS());
EXPECT_EQ(testCase.isCrossOrigin, networkHints.isCrossOrigin());
}
}
} // namespace
} // namespace blink