blob: d76a4d986fec051287e0015ca92a717942f7cdc9 [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/DocumentLoader.h"
#include "core/page/Page.h"
#include "platform/testing/URLTestHelpers.h"
#include "public/platform/Platform.h"
#include "public/platform/WebURLLoaderClient.h"
#include "public/platform/WebURLLoaderMockFactory.h"
#include "public/web/WebCache.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "web/WebLocalFrameImpl.h"
#include "web/tests/FrameTestHelpers.h"
#include "wtf/AutoReset.h"
#include <queue>
namespace blink {
// TODO(dcheng): Ideally, enough of FrameTestHelpers 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 {
m_webViewHelper.initialize();
URLTestHelpers::registerMockedURLLoad(
URLTestHelpers::toKURL("https://example.com/foo.html"), "foo.html");
}
void TearDown() override {
Platform::current()->getURLLoaderMockFactory()->unregisterAllURLs();
WebCache::clear();
}
WebLocalFrameImpl* mainFrame() {
return m_webViewHelper.webView()->mainFrameImpl();
}
FrameTestHelpers::WebViewHelper m_webViewHelper;
};
TEST_F(DocumentLoaderTest, SingleChunk) {
class TestDelegate : public WebURLLoaderTestDelegate {
public:
void didReceiveData(WebURLLoaderClient* originalClient,
WebURLLoader* loader,
const char* data,
int dataLength,
int encodedDataLength) override {
EXPECT_EQ(34, dataLength) << "foo.html was not served in a single chunk";
originalClient->didReceiveData(loader, data, dataLength,
encodedDataLength, dataLength);
}
} delegate;
Platform::current()->getURLLoaderMockFactory()->setLoaderDelegate(&delegate);
FrameTestHelpers::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* originalClient,
WebURLLoader* loader,
const char* data,
int dataLength,
int encodedDataLength) override {
EXPECT_EQ(34, dataLength) << "foo.html was not served in a single chunk";
// Chunk the reply into one byte chunks.
for (int i = 0; i < dataLength; ++i)
originalClient->didReceiveData(loader, &data[i], 1, 1, 1);
}
} delegate;
Platform::current()->getURLLoaderMockFactory()->setLoaderDelegate(&delegate);
FrameTestHelpers::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 TestDelegate : public WebURLLoaderTestDelegate,
public FrameTestHelpers::TestWebFrameClient {
public:
TestDelegate()
: m_loaderClient(nullptr),
m_loader(nullptr),
m_dispatchingDidReceiveData(false),
m_servedReentrantly(false) {}
// WebURLLoaderTestDelegate overrides:
void didReceiveData(WebURLLoaderClient* originalClient,
WebURLLoader* loader,
const char* data,
int dataLength,
int encodedDataLength) override {
EXPECT_EQ(34, dataLength) << "foo.html was not served in a single chunk";
m_loaderClient = originalClient;
m_loader = loader;
for (int i = 0; i < dataLength; ++i)
m_data.push(data[i]);
{
// Serve the first byte to the real WebURLLoaderCLient, which
// should trigger frameDetach() due to committing a provisional
// load.
AutoReset<bool> dispatching(&m_dispatchingDidReceiveData, true);
dispatchOneByte();
}
// Serve the remaining bytes to complete the load.
EXPECT_FALSE(m_data.empty());
while (!m_data.empty())
dispatchOneByte();
}
// WebFrameClient overrides:
void frameDetached(WebLocalFrame* frame, DetachType detachType) override {
if (m_dispatchingDidReceiveData) {
// This should be called by the first didReceiveData() call, since
// it should commit the provisional load.
EXPECT_GT(m_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 (m_data.size() > 10)
dispatchOneByte();
m_servedReentrantly = true;
}
TestWebFrameClient::frameDetached(frame, detachType);
}
void dispatchOneByte() {
char c = m_data.front();
m_data.pop();
m_loaderClient->didReceiveData(m_loader, &c, 1, 1, 1);
}
bool servedReentrantly() const { return m_servedReentrantly; }
private:
WebURLLoaderClient* m_loaderClient;
WebURLLoader* m_loader;
std::queue<char> m_data;
bool m_dispatchingDidReceiveData;
bool m_servedReentrantly;
} delegate;
m_webViewHelper.initialize(false, &delegate);
// This doesn't go through the mocked URL load path: it's just intended to
// setup a situation where didReceiveData() can be invoked reentrantly.
FrameTestHelpers::loadHTMLString(mainFrame(), "<iframe></iframe>",
URLTestHelpers::toKURL("about:blank"));
Platform::current()->getURLLoaderMockFactory()->setLoaderDelegate(&delegate);
FrameTestHelpers::loadFrame(mainFrame(), "https://example.com/foo.html");
Platform::current()->getURLLoaderMockFactory()->setLoaderDelegate(nullptr);
EXPECT_TRUE(delegate.servedReentrantly());
// delegate is a WebFrameClient and stack-allocated, so manually reset() the
// WebViewHelper here.
m_webViewHelper.reset();
}
TEST_F(DocumentLoaderTest, isCommittedButEmpty) {
WebViewImpl* webViewImpl =
m_webViewHelper.initializeAndLoad("about:blank", true);
EXPECT_TRUE(toLocalFrame(webViewImpl->page()->mainFrame())
->loader()
.documentLoader()
->isCommittedButEmpty());
}
} // namespace blink