blob: 7e5a383a4b97a01eb7514c9e084eb7b69765fe9f [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 "third_party/blink/renderer/core/loader/threadable_loader.h"
#include <memory>
#include "base/memory/ptr_util.h"
#include "base/memory/scoped_refptr.h"
#include "services/network/public/mojom/load_timing_info.mojom.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "third_party/blink/public/mojom/fetch/fetch_api_request.mojom-blink.h"
#include "third_party/blink/public/platform/task_type.h"
#include "third_party/blink/public/platform/web_url_loader_mock_factory.h"
#include "third_party/blink/public/platform/web_url_request.h"
#include "third_party/blink/public/platform/web_url_response.h"
#include "third_party/blink/public/platform/web_worker_fetch_context.h"
#include "third_party/blink/renderer/core/frame/local_dom_window.h"
#include "third_party/blink/renderer/core/loader/threadable_loader.h"
#include "third_party/blink/renderer/core/loader/threadable_loader_client.h"
#include "third_party/blink/renderer/core/loader/worker_fetch_context.h"
#include "third_party/blink/renderer/core/testing/dummy_page_holder.h"
#include "third_party/blink/renderer/core/workers/worker_reporting_proxy.h"
#include "third_party/blink/renderer/core/workers/worker_thread_test_helper.h"
#include "third_party/blink/renderer/platform/geometry/int_size.h"
#include "third_party/blink/renderer/platform/loader/fetch/memory_cache.h"
#include "third_party/blink/renderer/platform/loader/fetch/resource_error.h"
#include "third_party/blink/renderer/platform/loader/fetch/resource_loader_options.h"
#include "third_party/blink/renderer/platform/loader/fetch/resource_request.h"
#include "third_party/blink/renderer/platform/loader/fetch/resource_response.h"
#include "third_party/blink/renderer/platform/loader/fetch/resource_timing_info.h"
#include "third_party/blink/renderer/platform/loader/testing/web_url_loader_factory_with_mock.h"
#include "third_party/blink/renderer/platform/testing/unit_test_helpers.h"
#include "third_party/blink/renderer/platform/testing/url_test_helpers.h"
#include "third_party/blink/renderer/platform/weborigin/kurl.h"
#include "third_party/blink/renderer/platform/weborigin/security_origin.h"
#include "third_party/blink/renderer/platform/wtf/assertions.h"
#include "third_party/blink/renderer/platform/wtf/functional.h"
#include "third_party/blink/renderer/platform/wtf/text/wtf_string.h"
namespace blink {
namespace {
using testing::_;
using testing::InSequence;
using testing::InvokeWithoutArgs;
using testing::StrEq;
using testing::Truly;
using Checkpoint = testing::StrictMock<testing::MockFunction<void(int)>>;
constexpr char kFileName[] = "fox-null-terminated.html";
class MockThreadableLoaderClient final
: public GarbageCollected<MockThreadableLoaderClient>,
public ThreadableLoaderClient {
public:
MockThreadableLoaderClient() = default;
MOCK_METHOD2(DidSendData, void(uint64_t, uint64_t));
MOCK_METHOD2(DidReceiveResponse, void(uint64_t, const ResourceResponse&));
MOCK_METHOD2(DidReceiveData, void(const char*, unsigned));
MOCK_METHOD1(DidReceiveCachedMetadata, void(mojo_base::BigBuffer));
MOCK_METHOD1(DidFinishLoading, void(uint64_t));
MOCK_METHOD1(DidFail, void(const ResourceError&));
MOCK_METHOD0(DidFailRedirectCheck, void());
MOCK_METHOD1(DidDownloadData, void(uint64_t));
};
bool IsCancellation(const ResourceError& error) {
return error.IsCancellation();
}
bool IsNotCancellation(const ResourceError& error) {
return !error.IsCancellation();
}
KURL SuccessURL() {
return KURL("http://example.com/success").Copy();
}
KURL ErrorURL() {
return KURL("http://example.com/error").Copy();
}
KURL RedirectURL() {
return KURL("http://example.com/redirect").Copy();
}
void SetUpSuccessURL() {
// TODO(crbug.com/751425): We should use the mock functionality
// via |dummy_page_holder_|.
url_test_helpers::RegisterMockedURLLoad(
SuccessURL(), test::CoreTestDataPath(kFileName), "text/html");
}
void SetUpErrorURL() {
// TODO(crbug.com/751425): We should use the mock functionality
// via |dummy_page_holder_|.
url_test_helpers::RegisterMockedErrorURLLoad(ErrorURL());
}
void SetUpRedirectURL() {
KURL url = RedirectURL();
network::mojom::LoadTimingInfoPtr timing =
network::mojom::LoadTimingInfo::New();
WebURLResponse response;
response.SetCurrentRequestUrl(url);
response.SetHttpStatusCode(301);
response.SetLoadTiming(*timing);
response.AddHttpHeaderField("Location", SuccessURL().GetString());
response.AddHttpHeaderField("Access-Control-Allow-Origin", "http://fake.url");
// TODO(crbug.com/751425): We should use the mock functionality
// via |dummy_page_holder_|.
url_test_helpers::RegisterMockedURLLoadWithCustomResponse(
url, test::CoreTestDataPath(kFileName), response);
}
void SetUpMockURLs() {
SetUpSuccessURL();
SetUpErrorURL();
SetUpRedirectURL();
}
enum ThreadableLoaderToTest {
kDocumentThreadableLoaderTest,
kWorkerThreadableLoaderTest,
};
class ThreadableLoaderTestHelper final {
public:
ThreadableLoaderTestHelper()
: dummy_page_holder_(std::make_unique<DummyPageHolder>(IntSize(1, 1))) {
KURL url("http://fake.url/");
dummy_page_holder_->GetFrame().Loader().CommitNavigation(
WebNavigationParams::CreateWithHTMLBufferForTesting(
SharedBuffer::Create(), url),
nullptr /* extra_data */);
blink::test::RunPendingTasks();
}
void CreateLoader(ThreadableLoaderClient* client) {
ResourceLoaderOptions resource_loader_options(nullptr /* world */);
loader_ = MakeGarbageCollected<ThreadableLoader>(
*dummy_page_holder_->GetFrame().DomWindow(), client,
resource_loader_options);
}
void StartLoader(ResourceRequest request) {
loader_->Start(std::move(request));
}
void CancelLoader() { loader_->Cancel(); }
void CancelAndClearLoader() {
loader_->Cancel();
loader_ = nullptr;
}
void ClearLoader() { loader_ = nullptr; }
Checkpoint& GetCheckpoint() { return checkpoint_; }
void CallCheckpoint(int n) { checkpoint_.Call(n); }
void OnSetUp() { SetUpMockURLs(); }
void OnServeRequests() { url_test_helpers::ServeAsynchronousRequests(); }
void OnTearDown() {
if (loader_) {
loader_->Cancel();
loader_ = nullptr;
}
url_test_helpers::UnregisterAllURLsAndClearMemoryCache();
}
private:
std::unique_ptr<DummyPageHolder> dummy_page_holder_;
Checkpoint checkpoint_;
Persistent<ThreadableLoader> loader_;
};
class ThreadableLoaderTest : public testing::Test {
public:
ThreadableLoaderTest()
: helper_(std::make_unique<ThreadableLoaderTestHelper>()) {}
void StartLoader(const KURL& url,
network::mojom::RequestMode request_mode =
network::mojom::RequestMode::kNoCors) {
ResourceRequest request(url);
request.SetRequestContext(mojom::blink::RequestContextType::OBJECT);
request.SetMode(request_mode);
request.SetCredentialsMode(network::mojom::CredentialsMode::kOmit);
helper_->StartLoader(std::move(request));
}
void CancelLoader() { helper_->CancelLoader(); }
void CancelAndClearLoader() { helper_->CancelAndClearLoader(); }
void ClearLoader() { helper_->ClearLoader(); }
Checkpoint& GetCheckpoint() { return helper_->GetCheckpoint(); }
void CallCheckpoint(int n) { helper_->CallCheckpoint(n); }
void ServeRequests() { helper_->OnServeRequests(); }
void CreateLoader() { helper_->CreateLoader(Client()); }
MockThreadableLoaderClient* Client() const { return client_.Get(); }
private:
void SetUp() override {
client_ = MakeGarbageCollected<MockThreadableLoaderClient>();
helper_->OnSetUp();
}
void TearDown() override {
helper_->OnTearDown();
client_ = nullptr;
// We need GC here to avoid gmock flakiness.
ThreadState::Current()->CollectAllGarbageForTesting();
}
Persistent<MockThreadableLoaderClient> client_;
std::unique_ptr<ThreadableLoaderTestHelper> helper_;
};
TEST_F(ThreadableLoaderTest, StartAndStop) {}
TEST_F(ThreadableLoaderTest, CancelAfterStart) {
InSequence s;
EXPECT_CALL(GetCheckpoint(), Call(1));
CreateLoader();
CallCheckpoint(1);
EXPECT_CALL(GetCheckpoint(), Call(2))
.WillOnce(InvokeWithoutArgs(this, &ThreadableLoaderTest::CancelLoader));
EXPECT_CALL(*Client(), DidFail(Truly(IsCancellation)));
EXPECT_CALL(GetCheckpoint(), Call(3));
StartLoader(SuccessURL());
CallCheckpoint(2);
CallCheckpoint(3);
ServeRequests();
}
TEST_F(ThreadableLoaderTest, CancelAndClearAfterStart) {
InSequence s;
EXPECT_CALL(GetCheckpoint(), Call(1));
CreateLoader();
CallCheckpoint(1);
EXPECT_CALL(GetCheckpoint(), Call(2))
.WillOnce(
InvokeWithoutArgs(this, &ThreadableLoaderTest::CancelAndClearLoader));
EXPECT_CALL(*Client(), DidFail(Truly(IsCancellation)));
EXPECT_CALL(GetCheckpoint(), Call(3));
StartLoader(SuccessURL());
CallCheckpoint(2);
CallCheckpoint(3);
ServeRequests();
}
TEST_F(ThreadableLoaderTest, CancelInDidReceiveResponse) {
InSequence s;
EXPECT_CALL(GetCheckpoint(), Call(1));
CreateLoader();
CallCheckpoint(1);
EXPECT_CALL(GetCheckpoint(), Call(2));
EXPECT_CALL(*Client(), DidReceiveResponse(_, _))
.WillOnce(InvokeWithoutArgs(this, &ThreadableLoaderTest::CancelLoader));
EXPECT_CALL(*Client(), DidFail(Truly(IsCancellation)));
StartLoader(SuccessURL());
CallCheckpoint(2);
ServeRequests();
}
TEST_F(ThreadableLoaderTest, CancelAndClearInDidReceiveResponse) {
InSequence s;
EXPECT_CALL(GetCheckpoint(), Call(1));
CreateLoader();
CallCheckpoint(1);
EXPECT_CALL(GetCheckpoint(), Call(2));
EXPECT_CALL(*Client(), DidReceiveResponse(_, _))
.WillOnce(
InvokeWithoutArgs(this, &ThreadableLoaderTest::CancelAndClearLoader));
EXPECT_CALL(*Client(), DidFail(Truly(IsCancellation)));
StartLoader(SuccessURL());
CallCheckpoint(2);
ServeRequests();
}
TEST_F(ThreadableLoaderTest, CancelInDidReceiveData) {
InSequence s;
EXPECT_CALL(GetCheckpoint(), Call(1));
CreateLoader();
CallCheckpoint(1);
EXPECT_CALL(GetCheckpoint(), Call(2));
EXPECT_CALL(*Client(), DidReceiveResponse(_, _));
EXPECT_CALL(*Client(), DidReceiveData(_, _))
.WillOnce(InvokeWithoutArgs(this, &ThreadableLoaderTest::CancelLoader));
EXPECT_CALL(*Client(), DidFail(Truly(IsCancellation)));
StartLoader(SuccessURL());
CallCheckpoint(2);
ServeRequests();
}
TEST_F(ThreadableLoaderTest, CancelAndClearInDidReceiveData) {
InSequence s;
EXPECT_CALL(GetCheckpoint(), Call(1));
CreateLoader();
CallCheckpoint(1);
EXPECT_CALL(GetCheckpoint(), Call(2));
EXPECT_CALL(*Client(), DidReceiveResponse(_, _));
EXPECT_CALL(*Client(), DidReceiveData(_, _))
.WillOnce(
InvokeWithoutArgs(this, &ThreadableLoaderTest::CancelAndClearLoader));
EXPECT_CALL(*Client(), DidFail(Truly(IsCancellation)));
StartLoader(SuccessURL());
CallCheckpoint(2);
ServeRequests();
}
TEST_F(ThreadableLoaderTest, DidFinishLoading) {
InSequence s;
EXPECT_CALL(GetCheckpoint(), Call(1));
CreateLoader();
CallCheckpoint(1);
EXPECT_CALL(GetCheckpoint(), Call(2));
EXPECT_CALL(*Client(), DidReceiveResponse(_, _));
EXPECT_CALL(*Client(), DidReceiveData(StrEq("fox"), 4));
EXPECT_CALL(*Client(), DidFinishLoading(_));
StartLoader(SuccessURL());
CallCheckpoint(2);
ServeRequests();
}
TEST_F(ThreadableLoaderTest, CancelInDidFinishLoading) {
InSequence s;
EXPECT_CALL(GetCheckpoint(), Call(1));
CreateLoader();
CallCheckpoint(1);
EXPECT_CALL(GetCheckpoint(), Call(2));
EXPECT_CALL(*Client(), DidReceiveResponse(_, _));
EXPECT_CALL(*Client(), DidReceiveData(_, _));
EXPECT_CALL(*Client(), DidFinishLoading(_))
.WillOnce(InvokeWithoutArgs(this, &ThreadableLoaderTest::CancelLoader));
StartLoader(SuccessURL());
CallCheckpoint(2);
ServeRequests();
}
TEST_F(ThreadableLoaderTest, ClearInDidFinishLoading) {
InSequence s;
EXPECT_CALL(GetCheckpoint(), Call(1));
CreateLoader();
CallCheckpoint(1);
EXPECT_CALL(GetCheckpoint(), Call(2));
EXPECT_CALL(*Client(), DidReceiveResponse(_, _));
EXPECT_CALL(*Client(), DidReceiveData(_, _));
EXPECT_CALL(*Client(), DidFinishLoading(_))
.WillOnce(InvokeWithoutArgs(this, &ThreadableLoaderTest::ClearLoader));
StartLoader(SuccessURL());
CallCheckpoint(2);
ServeRequests();
}
TEST_F(ThreadableLoaderTest, DidFail) {
InSequence s;
EXPECT_CALL(GetCheckpoint(), Call(1));
CreateLoader();
CallCheckpoint(1);
EXPECT_CALL(GetCheckpoint(), Call(2));
EXPECT_CALL(*Client(), DidFail(Truly(IsNotCancellation)));
StartLoader(ErrorURL());
CallCheckpoint(2);
ServeRequests();
}
TEST_F(ThreadableLoaderTest, CancelInDidFail) {
InSequence s;
EXPECT_CALL(GetCheckpoint(), Call(1));
CreateLoader();
CallCheckpoint(1);
EXPECT_CALL(GetCheckpoint(), Call(2));
EXPECT_CALL(*Client(), DidFail(_))
.WillOnce(InvokeWithoutArgs(this, &ThreadableLoaderTest::CancelLoader));
StartLoader(ErrorURL());
CallCheckpoint(2);
ServeRequests();
}
TEST_F(ThreadableLoaderTest, ClearInDidFail) {
InSequence s;
EXPECT_CALL(GetCheckpoint(), Call(1));
CreateLoader();
CallCheckpoint(1);
EXPECT_CALL(GetCheckpoint(), Call(2));
EXPECT_CALL(*Client(), DidFail(_))
.WillOnce(InvokeWithoutArgs(this, &ThreadableLoaderTest::ClearLoader));
StartLoader(ErrorURL());
CallCheckpoint(2);
ServeRequests();
}
TEST_F(ThreadableLoaderTest, RedirectDidFinishLoading) {
InSequence s;
EXPECT_CALL(GetCheckpoint(), Call(1));
CreateLoader();
CallCheckpoint(1);
EXPECT_CALL(GetCheckpoint(), Call(2));
EXPECT_CALL(*Client(), DidReceiveResponse(_, _));
EXPECT_CALL(*Client(), DidReceiveData(StrEq("fox"), 4));
EXPECT_CALL(*Client(), DidFinishLoading(_));
StartLoader(RedirectURL());
CallCheckpoint(2);
ServeRequests();
}
TEST_F(ThreadableLoaderTest, CancelInRedirectDidFinishLoading) {
InSequence s;
EXPECT_CALL(GetCheckpoint(), Call(1));
CreateLoader();
CallCheckpoint(1);
EXPECT_CALL(GetCheckpoint(), Call(2));
EXPECT_CALL(*Client(), DidReceiveResponse(_, _));
EXPECT_CALL(*Client(), DidReceiveData(StrEq("fox"), 4));
EXPECT_CALL(*Client(), DidFinishLoading(_))
.WillOnce(InvokeWithoutArgs(this, &ThreadableLoaderTest::CancelLoader));
StartLoader(RedirectURL());
CallCheckpoint(2);
ServeRequests();
}
TEST_F(ThreadableLoaderTest, ClearInRedirectDidFinishLoading) {
InSequence s;
EXPECT_CALL(GetCheckpoint(), Call(1));
CreateLoader();
CallCheckpoint(1);
EXPECT_CALL(GetCheckpoint(), Call(2));
EXPECT_CALL(*Client(), DidReceiveResponse(_, _));
EXPECT_CALL(*Client(), DidReceiveData(StrEq("fox"), 4));
EXPECT_CALL(*Client(), DidFinishLoading(_))
.WillOnce(InvokeWithoutArgs(this, &ThreadableLoaderTest::ClearLoader));
StartLoader(RedirectURL());
CallCheckpoint(2);
ServeRequests();
}
} // namespace
} // namespace blink