blob: bc4ebb73db5e48af351c78fa2c1df8b479011952 [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 "third_party/blink/renderer/core/loader/document_loader.h"
#include <queue>
#include "base/auto_reset.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "third_party/blink/public/common/frame/frame_owner_element_type.h"
#include "third_party/blink/public/platform/platform.h"
#include "third_party/blink/public/platform/web_url_loader_client.h"
#include "third_party/blink/public/platform/web_url_loader_mock_factory.h"
#include "third_party/blink/renderer/core/frame/frame_test_helpers.h"
#include "third_party/blink/renderer/core/frame/web_local_frame_impl.h"
#include "third_party/blink/renderer/core/page/page.h"
#include "third_party/blink/renderer/platform/testing/unit_test_helpers.h"
#include "third_party/blink/renderer/platform/testing/url_test_helpers.h"
namespace blink {
// TODO(dcheng): Ideally, enough of frame_test_helpers would be in core/ that
// placing a test for a core/ class in web/ wouldn't be necessary.
class DocumentLoaderTest : public testing::Test {
protected:
void SetUp() override {
web_view_helper_.Initialize();
url_test_helpers::RegisterMockedURLLoad(
url_test_helpers::ToKURL("https://example.com/foo.html"),
test::CoreTestDataPath("foo.html"));
}
void TearDown() override {
Platform::Current()
->GetURLLoaderMockFactory()
->UnregisterAllURLsAndClearMemoryCache();
}
WebLocalFrameImpl* MainFrame() { return web_view_helper_.LocalMainFrame(); }
frame_test_helpers::WebViewHelper web_view_helper_;
};
TEST_F(DocumentLoaderTest, SingleChunk) {
class TestDelegate : public WebURLLoaderTestDelegate {
public:
void DidReceiveData(WebURLLoaderClient* original_client,
const char* data,
int data_length) override {
EXPECT_EQ(34, data_length) << "foo.html was not served in a single chunk";
original_client->DidReceiveData(data, data_length);
}
} delegate;
Platform::Current()->GetURLLoaderMockFactory()->SetLoaderDelegate(&delegate);
frame_test_helpers::LoadFrame(MainFrame(), "https://example.com/foo.html");
Platform::Current()->GetURLLoaderMockFactory()->SetLoaderDelegate(nullptr);
// TODO(dcheng): How should the test verify that the original callback is
// invoked? The test currently still passes even if the test delegate
// forgets to invoke the callback.
}
// Test normal case of DocumentLoader::dataReceived(): data in multiple chunks,
// with no reentrancy.
TEST_F(DocumentLoaderTest, MultiChunkNoReentrancy) {
class TestDelegate : public WebURLLoaderTestDelegate {
public:
void DidReceiveData(WebURLLoaderClient* original_client,
const char* data,
int data_length) override {
EXPECT_EQ(34, data_length) << "foo.html was not served in a single chunk";
// Chunk the reply into one byte chunks.
for (int i = 0; i < data_length; ++i)
original_client->DidReceiveData(&data[i], 1);
}
} delegate;
Platform::Current()->GetURLLoaderMockFactory()->SetLoaderDelegate(&delegate);
frame_test_helpers::LoadFrame(MainFrame(), "https://example.com/foo.html");
Platform::Current()->GetURLLoaderMockFactory()->SetLoaderDelegate(nullptr);
}
// Finally, test reentrant callbacks to DocumentLoader::dataReceived().
TEST_F(DocumentLoaderTest, MultiChunkWithReentrancy) {
// This test delegate chunks the response stage into three distinct stages:
// 1. The first dataReceived() callback, which triggers frame detach due to
// commiting a provisional load.
// 2. The middle part of the response, which is dispatched to
// dataReceived() reentrantly.
// 3. The final chunk, which is dispatched normally at the top-level.
class ChildDelegate : public WebURLLoaderTestDelegate,
public frame_test_helpers::TestWebFrameClient {
public:
// WebURLLoaderTestDelegate overrides:
void DidReceiveData(WebURLLoaderClient* original_client,
const char* data,
int data_length) override {
EXPECT_EQ(34, data_length) << "foo.html was not served in a single chunk";
loader_client_ = original_client;
for (int i = 0; i < data_length; ++i)
data_.push(data[i]);
{
// Serve the first byte to the real WebURLLoaderCLient, which
// should trigger frameDetach() due to committing a provisional
// load.
base::AutoReset<bool> dispatching(&dispatching_did_receive_data_, true);
DispatchOneByte();
}
// Serve the remaining bytes to complete the load.
EXPECT_FALSE(data_.empty());
while (!data_.empty())
DispatchOneByte();
}
// WebLocalFrameClient overrides:
void FrameDetached(DetachType detach_type) override {
if (dispatching_did_receive_data_) {
// This should be called by the first didReceiveData() call, since
// it should commit the provisional load.
EXPECT_GT(data_.size(), 10u);
// Dispatch dataReceived() callbacks for part of the remaining
// data, saving the rest to be dispatched at the top-level as
// normal.
while (data_.size() > 10)
DispatchOneByte();
served_reentrantly_ = true;
}
TestWebFrameClient::FrameDetached(detach_type);
}
void DispatchOneByte() {
char c = data_.front();
data_.pop();
loader_client_->DidReceiveData(&c, 1);
}
bool ServedReentrantly() const { return served_reentrantly_; }
private:
WebURLLoaderClient* loader_client_ = nullptr;
std::queue<char> data_;
bool dispatching_did_receive_data_ = false;
bool served_reentrantly_ = false;
};
class MainFrameClient : public frame_test_helpers::TestWebFrameClient {
public:
explicit MainFrameClient(TestWebFrameClient& child_client)
: child_client_(child_client) {}
WebLocalFrame* CreateChildFrame(WebLocalFrame* parent,
WebTreeScopeType scope,
const WebString& name,
const WebString& fallback_name,
WebSandboxFlags,
const ParsedFeaturePolicy&,
const WebFrameOwnerProperties&,
FrameOwnerElementType) override {
return CreateLocalChild(*parent, scope, &child_client_);
}
private:
TestWebFrameClient& child_client_;
};
ChildDelegate child_delegate;
MainFrameClient main_frame_client(child_delegate);
web_view_helper_.Initialize(&main_frame_client);
// This doesn't go through the mocked URL load path: it's just intended to
// setup a situation where didReceiveData() can be invoked reentrantly.
frame_test_helpers::LoadHTMLString(MainFrame(), "<iframe></iframe>",
url_test_helpers::ToKURL("about:blank"));
Platform::Current()->GetURLLoaderMockFactory()->SetLoaderDelegate(
&child_delegate);
frame_test_helpers::LoadFrame(MainFrame(), "https://example.com/foo.html");
Platform::Current()->GetURLLoaderMockFactory()->SetLoaderDelegate(nullptr);
EXPECT_TRUE(child_delegate.ServedReentrantly());
// delegate is a WebLocalFrameClient and stack-allocated, so manually reset()
// the WebViewHelper here.
web_view_helper_.Reset();
}
TEST_F(DocumentLoaderTest, isCommittedButEmpty) {
WebViewImpl* web_view_impl =
web_view_helper_.InitializeAndLoad("about:blank");
EXPECT_TRUE(ToLocalFrame(web_view_impl->GetPage()->MainFrame())
->Loader()
.GetDocumentLoader()
->IsCommittedButEmpty());
}
} // namespace blink