blob: 4bb120c4e38f5980e98b92e544db23c5ece2b64e [file] [log] [blame]
// Copyright 2016 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/html/parser/CSSPreloadScanner.h"
#include <memory>
#include "core/frame/Settings.h"
#include "core/html/parser/HTMLResourcePreloader.h"
#include "core/testing/DummyPageHolder.h"
#include "platform/exported/WrappedResourceResponse.h"
#include "platform/heap/Heap.h"
#include "platform/loader/fetch/FetchContext.h"
#include "platform/loader/fetch/Resource.h"
#include "platform/loader/fetch/ResourceError.h"
#include "platform/loader/fetch/ResourceFetcher.h"
#include "platform/loader/fetch/ResourceRequest.h"
#include "platform/weborigin/KURL.h"
#include "platform/wtf/text/TextEncoding.h"
#include "public/platform/Platform.h"
#include "public/platform/WebURLLoaderMockFactory.h"
#include "testing/gtest/include/gtest/gtest.h"
namespace blink {
namespace {
class MockHTMLResourcePreloader : public HTMLResourcePreloader {
WTF_MAKE_NONCOPYABLE(MockHTMLResourcePreloader);
public:
explicit MockHTMLResourcePreloader(Document& document,
const char* expected_referrer = nullptr)
: HTMLResourcePreloader(document),
expected_referrer_(expected_referrer) {}
void Preload(std::unique_ptr<PreloadRequest> preload_request,
const NetworkHintsInterface&) override {
if (expected_referrer_) {
Resource* resource = preload_request->Start(GetDocument());
if (resource) {
EXPECT_EQ(expected_referrer_,
resource->GetResourceRequest().HttpReferrer());
}
}
}
const char* expected_referrer_;
};
class PreloadRecordingCSSPreloaderResourceClient final
: public CSSPreloaderResourceClient {
public:
PreloadRecordingCSSPreloaderResourceClient(Resource* resource,
HTMLResourcePreloader* preloader)
: CSSPreloaderResourceClient(resource, preloader) {}
void FetchPreloads(PreloadRequestStream& preloads) override {
for (const auto& it : preloads) {
preload_urls_.push_back(it->ResourceURL());
preload_referrer_policies_.push_back(it->GetReferrerPolicy());
}
CSSPreloaderResourceClient::FetchPreloads(preloads);
}
Vector<String> preload_urls_;
Vector<ReferrerPolicy> preload_referrer_policies_;
};
class CSSPreloadScannerTest : public ::testing::Test {};
} // namespace
TEST_F(CSSPreloadScannerTest, ScanFromResourceClient) {
std::unique_ptr<DummyPageHolder> dummy_page_holder =
DummyPageHolder::Create(IntSize(500, 500));
dummy_page_holder->GetDocument()
.GetSettings()
->SetCSSExternalScannerNoPreload(true);
MockHTMLResourcePreloader* preloader =
new MockHTMLResourcePreloader(dummy_page_holder->GetDocument());
KURL url(kParsedURLString, "http://127.0.0.1/foo.css");
CSSStyleSheetResource* resource =
CSSStyleSheetResource::CreateForTest(url, UTF8Encoding());
resource->SetStatus(ResourceStatus::kPending);
PreloadRecordingCSSPreloaderResourceClient* resource_client =
new PreloadRecordingCSSPreloaderResourceClient(resource, preloader);
const char* data = "@import url('http://127.0.0.1/preload.css');";
resource->AppendData(data, strlen(data));
EXPECT_EQ(1u, resource_client->preload_urls_.size());
EXPECT_EQ("http://127.0.0.1/preload.css",
resource_client->preload_urls_.front());
}
// Regression test for crbug.com/608310 where the client is destroyed but was
// not removed from the resource's client list.
TEST_F(CSSPreloadScannerTest, DestroyClientBeforeDataSent) {
std::unique_ptr<DummyPageHolder> dummy_page_holder =
DummyPageHolder::Create(IntSize(500, 500));
dummy_page_holder->GetDocument()
.GetSettings()
->SetCSSExternalScannerNoPreload(true);
Persistent<MockHTMLResourcePreloader> preloader =
new MockHTMLResourcePreloader(dummy_page_holder->GetDocument());
KURL url(kParsedURLString, "http://127.0.0.1/foo.css");
Persistent<CSSStyleSheetResource> resource =
CSSStyleSheetResource::CreateForTest(url, UTF8Encoding());
resource->SetStatus(ResourceStatus::kPending);
new PreloadRecordingCSSPreloaderResourceClient(resource, preloader);
// Destroys the resourceClient.
ThreadState::Current()->CollectAllGarbage();
const char* data = "@import url('http://127.0.0.1/preload.css');";
// Should not crash.
resource->AppendData(data, strlen(data));
}
// Regression test for crbug.com/646869 where the client's data is cleared
// before didAppendFirstData is called.
TEST_F(CSSPreloadScannerTest, DontReadFromClearedData) {
std::unique_ptr<DummyPageHolder> dummy_page_holder =
DummyPageHolder::Create(IntSize(500, 500));
dummy_page_holder->GetDocument()
.GetSettings()
->SetCSSExternalScannerNoPreload(true);
MockHTMLResourcePreloader* preloader =
new MockHTMLResourcePreloader(dummy_page_holder->GetDocument());
KURL url(kParsedURLString, "http://127.0.0.1/foo.css");
CSSStyleSheetResource* resource =
CSSStyleSheetResource::CreateForTest(url, UTF8Encoding());
const char* data = "@import url('http://127.0.0.1/preload.css');";
resource->AppendData(data, strlen(data));
ResourceError error(kErrorDomainBlinkInternal, 0, url.GetString(), "");
resource->FinishAsError(error);
// Should not crash.
PreloadRecordingCSSPreloaderResourceClient* resource_client =
new PreloadRecordingCSSPreloaderResourceClient(resource, preloader);
EXPECT_EQ(0u, resource_client->preload_urls_.size());
}
// Regression test for crbug.com/645331, where a resource client gets callbacks
// after the document is shutdown and we have no frame.
TEST_F(CSSPreloadScannerTest, DoNotExpectValidDocument) {
std::unique_ptr<DummyPageHolder> dummy_page_holder =
DummyPageHolder::Create(IntSize(500, 500));
dummy_page_holder->GetDocument()
.GetSettings()
->SetCSSExternalScannerNoPreload(true);
MockHTMLResourcePreloader* preloader =
new MockHTMLResourcePreloader(dummy_page_holder->GetDocument());
KURL url(kParsedURLString, "http://127.0.0.1/foo.css");
CSSStyleSheetResource* resource =
CSSStyleSheetResource::CreateForTest(url, UTF8Encoding());
resource->SetStatus(ResourceStatus::kPending);
PreloadRecordingCSSPreloaderResourceClient* resource_client =
new PreloadRecordingCSSPreloaderResourceClient(resource, preloader);
dummy_page_holder->GetDocument().Shutdown();
const char* data = "@import url('http://127.0.0.1/preload.css');";
resource->AppendData(data, strlen(data));
// Do not expect to gather any preloads, as the document loader is invalid,
// which means we can't notify WebLoadingBehaviorData of the preloads.
EXPECT_EQ(0u, resource_client->preload_urls_.size());
}
TEST_F(CSSPreloadScannerTest, ReferrerPolicyHeader) {
std::unique_ptr<DummyPageHolder> dummy_page_holder =
DummyPageHolder::Create(IntSize(500, 500));
dummy_page_holder->GetDocument().GetSettings()->SetCSSExternalScannerPreload(
true);
MockHTMLResourcePreloader* preloader = new MockHTMLResourcePreloader(
dummy_page_holder->GetDocument(), "http://127.0.0.1/foo.css");
KURL url(kParsedURLString, "http://127.0.0.1/foo.css");
ResourceResponse response;
response.SetURL(url);
response.SetHTTPStatusCode(200);
response.SetHTTPHeaderField("referrer-policy", "unsafe-url");
CSSStyleSheetResource* resource =
CSSStyleSheetResource::CreateForTest(url, UTF8Encoding());
resource->SetStatus(ResourceStatus::kPending);
resource->SetResponse(response);
PreloadRecordingCSSPreloaderResourceClient* resource_client =
new PreloadRecordingCSSPreloaderResourceClient(resource, preloader);
KURL preload_url(kParsedURLString, "http://127.0.0.1/preload.css");
Platform::Current()->GetURLLoaderMockFactory()->RegisterURL(
preload_url, WrappedResourceResponse(ResourceResponse()), "");
const char* data = "@import url('http://127.0.0.1/preload.css');";
resource->AppendData(data, strlen(data));
EXPECT_EQ(1u, resource_client->preload_urls_.size());
EXPECT_EQ("http://127.0.0.1/preload.css",
resource_client->preload_urls_.front());
EXPECT_EQ(kReferrerPolicyAlways,
resource_client->preload_referrer_policies_.front());
}
} // namespace blink