[go: nahoru, domu]

blob: 3750906326d0b3a5d8e1090259408a2f6f361628 [file] [log] [blame]
// Copyright 2015 The Chromium Authors
// 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 <utility>
#include "base/auto_reset.h"
#include "base/test/metrics/histogram_tester.h"
#include "base/test/scoped_feature_list.h"
#include "base/unguessable_token.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "third_party/blink/public/common/features.h"
#include "third_party/blink/public/platform/platform.h"
#include "third_party/blink/public/platform/web_encoding_data.h"
#include "third_party/blink/public/platform/web_string.h"
#include "third_party/blink/renderer/core/frame/frame_test_helpers.h"
#include "third_party/blink/renderer/core/frame/local_dom_window.h"
#include "third_party/blink/renderer/core/frame/web_local_frame_impl.h"
#include "third_party/blink/renderer/core/inspector/console_message.h"
#include "third_party/blink/renderer/core/page/page.h"
#include "third_party/blink/renderer/core/testing/scoped_fake_plugin_registry.h"
#include "third_party/blink/renderer/core/testing/sim/sim_request.h"
#include "third_party/blink/renderer/core/testing/sim/sim_test.h"
#include "third_party/blink/renderer/platform/loader/fetch/url_loader/url_loader_client.h"
#include "third_party/blink/renderer/platform/loader/static_data_navigation_body_loader.h"
#include "third_party/blink/renderer/platform/storage/blink_storage_key.h"
#include "third_party/blink/renderer/platform/testing/unit_test_helpers.h"
#include "third_party/blink/renderer/platform/testing/url_loader_mock_factory.h"
#include "third_party/blink/renderer/platform/testing/url_test_helpers.h"
#include "third_party/blink/renderer/platform/wtf/deque.h"
namespace blink {
namespace {
// Forwards calls from BodyDataReceived() to DecodedBodyDataReceived().
class DecodedBodyLoader : public StaticDataNavigationBodyLoader {
public:
void StartLoadingBody(Client* client) override {
client_ = std::make_unique<DecodedDataPassthroughClient>(client);
StaticDataNavigationBodyLoader::StartLoadingBody(client_.get());
}
private:
class DecodedDataPassthroughClient : public WebNavigationBodyLoader::Client {
public:
explicit DecodedDataPassthroughClient(Client* client) : client_(client) {}
void BodyDataReceived(base::span<const char> data) override {
client_->DecodedBodyDataReceived(
String(data.data(), data.size()).UpperASCII(),
WebEncodingData{.encoding = "utf-8"}, data);
}
void DecodedBodyDataReceived(const WebString& data,
const WebEncodingData& encoding_data,
base::span<const char> encoded_data) override {
client_->DecodedBodyDataReceived(data, encoding_data, encoded_data);
}
void BodyLoadingFinished(base::TimeTicks completion_time,
int64_t total_encoded_data_length,
int64_t total_encoded_body_length,
int64_t total_decoded_body_length,
const std::optional<WebURLError>& error) override {
client_->BodyLoadingFinished(completion_time, total_encoded_data_length,
total_encoded_body_length,
total_decoded_body_length, error);
}
private:
Client* client_;
};
std::unique_ptr<DecodedDataPassthroughClient> client_;
};
class BodyLoaderTestDelegate : public URLLoaderTestDelegate {
public:
explicit BodyLoaderTestDelegate(
std::unique_ptr<StaticDataNavigationBodyLoader> body_loader)
: body_loader_(std::move(body_loader)),
body_loader_raw_(body_loader_.get()) {}
// URLLoaderTestDelegate overrides:
bool FillNavigationParamsResponse(WebNavigationParams* params) override {
params->response = WebURLResponse(params->url);
params->response.SetMimeType("text/html");
params->response.SetHttpStatusCode(200);
params->body_loader = std::move(body_loader_);
return true;
}
void Write(const char* data) { body_loader_raw_->Write(data, strlen(data)); }
void Finish() { body_loader_raw_->Finish(); }
private:
std::unique_ptr<StaticDataNavigationBodyLoader> body_loader_;
StaticDataNavigationBodyLoader* body_loader_raw_;
};
class DocumentLoaderTest : public testing::TestWithParam<bool> {
protected:
void SetUp() override {
if (IsThirdPartyStoragePartitioningEnabled()) {
scoped_feature_list_.InitAndEnableFeature(
net::features::kThirdPartyStoragePartitioning);
} else {
scoped_feature_list_.InitAndDisableFeature(
net::features::kThirdPartyStoragePartitioning);
}
web_view_helper_.Initialize();
url_test_helpers::RegisterMockedURLLoad(
url_test_helpers::ToKURL("http://example.com/foo.html"),
test::CoreTestDataPath("foo.html"));
url_test_helpers::RegisterMockedURLLoad(
url_test_helpers::ToKURL("http://user:@example.com/foo.html"),
test::CoreTestDataPath("foo.html"));
url_test_helpers::RegisterMockedURLLoad(
url_test_helpers::ToKURL("http://:pass@example.com/foo.html"),
test::CoreTestDataPath("foo.html"));
url_test_helpers::RegisterMockedURLLoad(
url_test_helpers::ToKURL("http://user:pass@example.com/foo.html"),
test::CoreTestDataPath("foo.html"));
url_test_helpers::RegisterMockedURLLoad(
url_test_helpers::ToKURL("https://example.com/foo.html"),
test::CoreTestDataPath("foo.html"));
url_test_helpers::RegisterMockedURLLoad(
url_test_helpers::ToKURL("https://example.com:8000/foo.html"),
test::CoreTestDataPath("foo.html"));
url_test_helpers::RegisterMockedURLLoad(
url_test_helpers::ToKURL("http://192.168.1.1/foo.html"),
test::CoreTestDataPath("foo.html"), WebString::FromUTF8("text/html"),
URLLoaderMockFactory::GetSingletonInstance(),
network::mojom::IPAddressSpace::kPrivate);
url_test_helpers::RegisterMockedURLLoad(
url_test_helpers::ToKURL("https://192.168.1.1/foo.html"),
test::CoreTestDataPath("foo.html"), WebString::FromUTF8("text/html"),
URLLoaderMockFactory::GetSingletonInstance(),
network::mojom::IPAddressSpace::kPrivate);
url_test_helpers::RegisterMockedURLLoad(
url_test_helpers::ToKURL("http://somethinglocal/foo.html"),
test::CoreTestDataPath("foo.html"), WebString::FromUTF8("text/html"),
URLLoaderMockFactory::GetSingletonInstance(),
network::mojom::IPAddressSpace::kLocal);
}
void TearDown() override {
url_test_helpers::UnregisterAllURLsAndClearMemoryCache();
}
bool IsThirdPartyStoragePartitioningEnabled() const { return GetParam(); }
class ScopedLoaderDelegate {
public:
explicit ScopedLoaderDelegate(URLLoaderTestDelegate* delegate) {
url_test_helpers::SetLoaderDelegate(delegate);
}
~ScopedLoaderDelegate() { url_test_helpers::SetLoaderDelegate(nullptr); }
};
WebLocalFrameImpl* MainFrame() { return web_view_helper_.LocalMainFrame(); }
test::TaskEnvironment task_environment_;
frame_test_helpers::WebViewHelper web_view_helper_;
base::test::ScopedFeatureList scoped_feature_list_;
};
INSTANTIATE_TEST_SUITE_P(DocumentLoaderTest,
DocumentLoaderTest,
::testing::Bool());
TEST_P(DocumentLoaderTest, SingleChunk) {
class TestDelegate : public URLLoaderTestDelegate {
public:
void DidReceiveData(URLLoaderClient* original_client,
const char* data,
size_t data_length) override {
EXPECT_EQ(34u, data_length)
<< "foo.html was not served in a single chunk";
original_client->DidReceiveData(data, data_length);
}
} delegate;
ScopedLoaderDelegate loader_delegate(&delegate);
frame_test_helpers::LoadFrame(MainFrame(), "https://example.com/foo.html");
// 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_P(DocumentLoaderTest, MultiChunkNoReentrancy) {
class TestDelegate : public URLLoaderTestDelegate {
public:
void DidReceiveData(URLLoaderClient* original_client,
const char* data,
size_t data_length) override {
EXPECT_EQ(34u, data_length)
<< "foo.html was not served in a single chunk";
// Chunk the reply into one byte chunks.
for (size_t i = 0; i < data_length; ++i) {
original_client->DidReceiveData(&data[i], 1);
}
}
} delegate;
ScopedLoaderDelegate loader_delegate(&delegate);
frame_test_helpers::LoadFrame(MainFrame(), "https://example.com/foo.html");
}
// Finally, test reentrant callbacks to DocumentLoader::BodyDataReceived().
TEST_P(DocumentLoaderTest, MultiChunkWithReentrancy) {
// This test delegate chunks the response stage into three distinct stages:
// 1. The first BodyDataReceived() callback, which triggers frame detach
// due to committing a provisional load.
// 2. The middle part of the response, which is dispatched to
// BodyDataReceived() reentrantly.
// 3. The final chunk, which is dispatched normally at the top-level.
class MainFrameClient : public URLLoaderTestDelegate,
public frame_test_helpers::TestWebFrameClient {
public:
// URLLoaderTestDelegate overrides:
bool FillNavigationParamsResponse(WebNavigationParams* params) override {
params->response = WebURLResponse(params->url);
params->response.SetMimeType("application/x-webkit-test-webplugin");
params->response.SetHttpStatusCode(200);
String data("<html><body>foo</body></html>");
for (wtf_size_t i = 0; i < data.length(); i++)
data_.push_back(data[i]);
auto body_loader = std::make_unique<StaticDataNavigationBodyLoader>();
body_loader_ = body_loader.get();
params->body_loader = std::move(body_loader);
return true;
}
void Serve() {
{
// Serve the first byte to the real URLLoaderClient, 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();
body_loader_->Finish();
body_loader_ = nullptr;
}
// WebLocalFrameClient overrides:
void RunScriptsAtDocumentElementAvailable() override {
if (dispatching_did_receive_data_) {
// This should be called by the first BodyDataReceived() call, since
// it should create a plugin document structure and trigger this.
EXPECT_GT(data_.size(), 10u);
// Dispatch BodyDataReceived() 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::RunScriptsAtDocumentElementAvailable();
}
void DispatchOneByte() {
char c = data_.TakeFirst();
body_loader_->Write(&c, 1);
}
bool ServedReentrantly() const { return served_reentrantly_; }
private:
Deque<char> data_;
bool dispatching_did_receive_data_ = false;
bool served_reentrantly_ = false;
StaticDataNavigationBodyLoader* body_loader_ = nullptr;
};
// We use a plugin document triggered by "application/x-webkit-test-webplugin"
// mime type, because that gives us reliable way to get a WebLocalFrameClient
// callback from inside BodyDataReceived() call.
ScopedFakePluginRegistry fake_plugins;
MainFrameClient main_frame_client;
web_view_helper_.Initialize(&main_frame_client);
web_view_helper_.GetWebView()->GetPage()->GetSettings().SetPluginsEnabled(
true);
{
ScopedLoaderDelegate loader_delegate(&main_frame_client);
frame_test_helpers::LoadFrameDontWait(
MainFrame(), url_test_helpers::ToKURL("https://example.com/foo.html"));
main_frame_client.Serve();
frame_test_helpers::PumpPendingRequestsForFrameToLoad(MainFrame());
}
// Sanity check that we did actually test reeentrancy.
EXPECT_TRUE(main_frame_client.ServedReentrantly());
// MainFrameClient is stack-allocated, so manually Reset to avoid UAF.
web_view_helper_.Reset();
}
TEST_P(DocumentLoaderTest, isCommittedButEmpty) {
WebViewImpl* web_view_impl =
web_view_helper_.InitializeAndLoad("about:blank");
EXPECT_TRUE(To<LocalFrame>(web_view_impl->GetPage()->MainFrame())
->Loader()
.GetDocumentLoader()
->IsCommittedButEmpty());
}
class DocumentLoaderSimTest : public SimTest {};
TEST_F(DocumentLoaderSimTest, DocumentOpenUpdatesUrl) {
SimRequest main_resource("https://example.com", "text/html");
LoadURL("https://example.com");
main_resource.Write("<iframe src='javascript:42;'></iframe>");
auto* child_frame = To<WebLocalFrameImpl>(MainFrame().FirstChild());
auto* child_document = child_frame->GetFrame()->GetDocument();
EXPECT_TRUE(child_document->HasPendingJavaScriptUrlsForTest());
main_resource.Write(
"<script>"
"window[0].document.open();"
"window[0].document.write('hello');"
"window[0].document.close();"
"</script>");
main_resource.Finish();
// document.open() should have cancelled the pending JavaScript URLs.
EXPECT_FALSE(child_document->HasPendingJavaScriptUrlsForTest());
// Per https://whatwg.org/C/dynamic-markup-insertion.html#document-open-steps,
// the URL associated with the Document should match the URL of the entry
// Document.
EXPECT_EQ(KURL("https://example.com"), child_document->Url());
// Similarly, the URL of the DocumentLoader should also match.
EXPECT_EQ(KURL("https://example.com"), child_document->Loader()->Url());
}
TEST_F(DocumentLoaderSimTest, FramePolicyIntegrityOnNavigationCommit) {
SimRequest main_resource("https://example.com", "text/html");
SimRequest iframe_resource("https://example.com/foo.html", "text/html");
LoadURL("https://example.com");
main_resource.Write(R"(
<iframe id='frame1'></iframe>
<script>
const iframe = document.getElementById('frame1');
iframe.src = 'https://example.com/foo.html'; // navigation triggered
iframe.allow = "payment 'none'"; // should not take effect until the
// next navigation on iframe
</script>
)");
main_resource.Finish();
iframe_resource.Finish();
auto* child_frame = To<WebLocalFrameImpl>(MainFrame().FirstChild());
auto* child_window = child_frame->GetFrame()->DomWindow();
EXPECT_TRUE(child_window->IsFeatureEnabled(
mojom::blink::PermissionsPolicyFeature::kPayment));
}
TEST_P(DocumentLoaderTest, CommitsDeferredOnSameOriginNavigation) {
const KURL& requestor_url =
KURL(NullURL(), "https://www.example.com/foo.html");
WebViewImpl* web_view_impl =
web_view_helper_.InitializeAndLoad("https://example.com/foo.html");
const KURL& same_origin_url =
KURL(NullURL(), "https://www.example.com/bar.html");
std::unique_ptr<WebNavigationParams> params =
WebNavigationParams::CreateWithHTMLBufferForTesting(
SharedBuffer::Create(), same_origin_url);
params->requestor_origin = WebSecurityOrigin::Create(WebURL(requestor_url));
LocalFrame* local_frame =
To<LocalFrame>(web_view_impl->GetPage()->MainFrame());
local_frame->Loader().CommitNavigation(std::move(params), nullptr);
EXPECT_TRUE(local_frame->GetDocument()->DeferredCompositorCommitIsAllowed());
}
TEST_P(DocumentLoaderTest,
CommitsNotDeferredOnDifferentOriginNavigationWithCrossOriginDisabled) {
base::test::ScopedFeatureList scoped_feature_list;
scoped_feature_list.InitAndDisableFeature(features::kPaintHoldingCrossOrigin);
const KURL& requestor_url =
KURL(NullURL(), "https://www.example.com/foo.html");
WebViewImpl* web_view_impl =
web_view_helper_.InitializeAndLoad("https://example.com/foo.html");
const KURL& other_origin_url =
KURL(NullURL(), "https://www.another.com/bar.html");
std::unique_ptr<WebNavigationParams> params =
WebNavigationParams::CreateWithHTMLBufferForTesting(
SharedBuffer::Create(), other_origin_url);
params->requestor_origin = WebSecurityOrigin::Create(WebURL(requestor_url));
LocalFrame* local_frame =
To<LocalFrame>(web_view_impl->GetPage()->MainFrame());
local_frame->Loader().CommitNavigation(std::move(params), nullptr);
EXPECT_FALSE(local_frame->GetDocument()->DeferredCompositorCommitIsAllowed());
}
TEST_P(DocumentLoaderTest,
CommitsDeferredOnDifferentOriginNavigationWithCrossOriginEnabled) {
base::test::ScopedFeatureList scoped_feature_list;
scoped_feature_list.InitAndEnableFeature(features::kPaintHoldingCrossOrigin);
const KURL& requestor_url =
KURL(NullURL(), "https://www.example.com/foo.html");
WebViewImpl* web_view_impl =
web_view_helper_.InitializeAndLoad("https://example.com/foo.html");
const KURL& other_origin_url =
KURL(NullURL(), "https://www.another.com/bar.html");
std::unique_ptr<WebNavigationParams> params =
WebNavigationParams::CreateWithHTMLBufferForTesting(
SharedBuffer::Create(), other_origin_url);
params->requestor_origin = WebSecurityOrigin::Create(WebURL(requestor_url));
LocalFrame* local_frame =
To<LocalFrame>(web_view_impl->GetPage()->MainFrame());
local_frame->Loader().CommitNavigation(std::move(params), nullptr);
EXPECT_TRUE(local_frame->GetDocument()->DeferredCompositorCommitIsAllowed());
}
TEST_P(DocumentLoaderTest,
CommitsNotDeferredOnDifferentPortNavigationWithCrossOriginDisabled) {
base::test::ScopedFeatureList scoped_feature_list;
scoped_feature_list.InitAndDisableFeature(features::kPaintHoldingCrossOrigin);
const KURL& requestor_url =
KURL(NullURL(), "https://www.example.com:8000/foo.html");
WebViewImpl* web_view_impl =
web_view_helper_.InitializeAndLoad("https://example.com:8000/foo.html");
const KURL& different_port_url =
KURL(NullURL(), "https://www.example.com:8080/bar.html");
std::unique_ptr<WebNavigationParams> params =
WebNavigationParams::CreateWithHTMLBufferForTesting(
SharedBuffer::Create(), different_port_url);
params->requestor_origin = WebSecurityOrigin::Create(WebURL(requestor_url));
LocalFrame* local_frame =
To<LocalFrame>(web_view_impl->GetPage()->MainFrame());
local_frame->Loader().CommitNavigation(std::move(params), nullptr);
EXPECT_FALSE(local_frame->GetDocument()->DeferredCompositorCommitIsAllowed());
}
TEST_P(DocumentLoaderTest,
CommitsDeferredOnDifferentPortNavigationWithCrossOriginEnabled) {
base::test::ScopedFeatureList scoped_feature_list;
scoped_feature_list.InitAndEnableFeature(features::kPaintHoldingCrossOrigin);
const KURL& requestor_url =
KURL(NullURL(), "https://www.example.com:8000/foo.html");
WebViewImpl* web_view_impl =
web_view_helper_.InitializeAndLoad("https://example.com:8000/foo.html");
const KURL& different_port_url =
KURL(NullURL(), "https://www.example.com:8080/bar.html");
std::unique_ptr<WebNavigationParams> params =
WebNavigationParams::CreateWithHTMLBufferForTesting(
SharedBuffer::Create(), different_port_url);
params->requestor_origin = WebSecurityOrigin::Create(WebURL(requestor_url));
LocalFrame* local_frame =
To<LocalFrame>(web_view_impl->GetPage()->MainFrame());
local_frame->Loader().CommitNavigation(std::move(params), nullptr);
EXPECT_TRUE(local_frame->GetDocument()->DeferredCompositorCommitIsAllowed());
}
TEST_P(DocumentLoaderTest, CommitsNotDeferredOnDataURLNavigation) {
const KURL& requestor_url =
KURL(NullURL(), "https://www.example.com/foo.html");
WebViewImpl* web_view_impl =
web_view_helper_.InitializeAndLoad("https://example.com/foo.html");
const KURL& data_url = KURL(NullURL(), "data:,Hello%2C%20World!");
std::unique_ptr<WebNavigationParams> params =
WebNavigationParams::CreateWithHTMLBufferForTesting(
SharedBuffer::Create(), data_url);
params->requestor_origin = WebSecurityOrigin::Create(WebURL(requestor_url));
LocalFrame* local_frame =
To<LocalFrame>(web_view_impl->GetPage()->MainFrame());
local_frame->Loader().CommitNavigation(std::move(params), nullptr);
EXPECT_FALSE(local_frame->GetDocument()->DeferredCompositorCommitIsAllowed());
}
TEST_P(DocumentLoaderTest,
CommitsNotDeferredOnDataURLNavigationWithCrossOriginEnabled) {
base::test::ScopedFeatureList scoped_feature_list;
scoped_feature_list.InitAndEnableFeature(features::kPaintHoldingCrossOrigin);
const KURL& requestor_url =
KURL(NullURL(), "https://www.example.com/foo.html");
WebViewImpl* web_view_impl =
web_view_helper_.InitializeAndLoad("https://example.com/foo.html");
const KURL& data_url = KURL(NullURL(), "data:,Hello%2C%20World!");
std::unique_ptr<WebNavigationParams> params =
WebNavigationParams::CreateWithHTMLBufferForTesting(
SharedBuffer::Create(), data_url);
params->requestor_origin = WebSecurityOrigin::Create(WebURL(requestor_url));
LocalFrame* local_frame =
To<LocalFrame>(web_view_impl->GetPage()->MainFrame());
local_frame->Loader().CommitNavigation(std::move(params), nullptr);
EXPECT_FALSE(local_frame->GetDocument()->DeferredCompositorCommitIsAllowed());
}
TEST_P(DocumentLoaderTest, NavigationToAboutBlank) {
const KURL& requestor_url =
KURL(NullURL(), "https://subdomain.example.com/foo.html");
WebViewImpl* web_view_impl =
web_view_helper_.InitializeAndLoad("https://example.com/foo.html");
const KURL& about_blank_url = KURL(NullURL(), "about:blank");
std::unique_ptr<WebNavigationParams> params =
std::make_unique<WebNavigationParams>();
params->url = about_blank_url;
params->requestor_origin = WebSecurityOrigin::Create(WebURL(requestor_url));
LocalFrame* local_frame =
To<LocalFrame>(web_view_impl->GetPage()->MainFrame());
params->storage_key = local_frame->DomWindow()->GetStorageKey();
local_frame->Loader().CommitNavigation(std::move(params), nullptr);
EXPECT_EQ(
BlinkStorageKey::CreateFirstParty(SecurityOrigin::Create(requestor_url)),
local_frame->DomWindow()->GetStorageKey());
}
TEST_P(DocumentLoaderTest, SameOriginNavigation) {
const KURL& requestor_url =
KURL(NullURL(), "https://www.example.com/foo.html");
WebViewImpl* web_view_impl =
web_view_helper_.InitializeAndLoad("https://example.com/foo.html");
const KURL& same_origin_url =
KURL(NullURL(), "https://www.example.com/bar.html");
std::unique_ptr<WebNavigationParams> params =
WebNavigationParams::CreateWithHTMLBufferForTesting(
SharedBuffer::Create(), same_origin_url);
params->requestor_origin = WebSecurityOrigin::Create(WebURL(requestor_url));
params->storage_key = BlinkStorageKey::CreateFirstParty(
SecurityOrigin::Create(same_origin_url));
LocalFrame* local_frame =
To<LocalFrame>(web_view_impl->GetPage()->MainFrame());
local_frame->Loader().CommitNavigation(std::move(params), nullptr);
EXPECT_EQ(BlinkStorageKey::CreateFirstParty(
SecurityOrigin::Create(same_origin_url)),
local_frame->DomWindow()->GetStorageKey());
EXPECT_FALSE(local_frame->DomWindow()->HasStorageAccess());
EXPECT_TRUE(local_frame->Loader()
.GetDocumentLoader()
->LastNavigationHadTrustedInitiator());
}
TEST_P(DocumentLoaderTest, SameOriginNavigation_WithStorageAccess) {
const KURL& requestor_url =
KURL(NullURL(), "https://www.example.com/foo.html");
WebViewImpl* web_view_impl =
web_view_helper_.InitializeAndLoad("https://example.com/foo.html");
const KURL& same_origin_url =
KURL(NullURL(), "https://www.example.com/bar.html");
std::unique_ptr<WebNavigationParams> params =
WebNavigationParams::CreateWithHTMLBufferForTesting(
SharedBuffer::Create(), same_origin_url);
params->requestor_origin = WebSecurityOrigin::Create(WebURL(requestor_url));
params->load_with_storage_access = true;
LocalFrame* local_frame =
To<LocalFrame>(web_view_impl->GetPage()->MainFrame());
base::HistogramTester histogram_tester;
local_frame->Loader().CommitNavigation(std::move(params), nullptr);
EXPECT_TRUE(local_frame->DomWindow()->HasStorageAccess());
EXPECT_TRUE(local_frame->Loader()
.GetDocumentLoader()
->LastNavigationHadTrustedInitiator());
histogram_tester.ExpectUniqueSample(
"API.StorageAccess.DocumentLoadedWithStorageAccess", /*sample=*/true,
/*expected_bucket_count=*/1);
histogram_tester.ExpectUniqueSample(
"API.StorageAccess.DocumentInheritedStorageAccess", /*sample=*/true,
/*expected_bucket_count=*/1);
}
TEST_P(DocumentLoaderTest, CrossOriginNavigation) {
const KURL& requestor_url =
KURL(NullURL(), "https://www.example.com/foo.html");
WebViewImpl* web_view_impl =
web_view_helper_.InitializeAndLoad("https://example.com/foo.html");
const KURL& other_origin_url =
KURL(NullURL(), "https://www.another.com/bar.html");
std::unique_ptr<WebNavigationParams> params =
WebNavigationParams::CreateWithHTMLBufferForTesting(
SharedBuffer::Create(), other_origin_url);
params->requestor_origin = WebSecurityOrigin::Create(WebURL(requestor_url));
params->storage_key = BlinkStorageKey::CreateFirstParty(
SecurityOrigin::Create(other_origin_url));
LocalFrame* local_frame =
To<LocalFrame>(web_view_impl->GetPage()->MainFrame());
base::HistogramTester histogram_tester;
local_frame->Loader().CommitNavigation(std::move(params), nullptr);
EXPECT_EQ(BlinkStorageKey::CreateFirstParty(
SecurityOrigin::Create(other_origin_url)),
local_frame->DomWindow()->GetStorageKey());
EXPECT_FALSE(local_frame->Loader()
.GetDocumentLoader()
->LastNavigationHadTrustedInitiator());
histogram_tester.ExpectUniqueSample(
"API.StorageAccess.DocumentLoadedWithStorageAccess", /*sample=*/false,
/*expected_bucket_count=*/1);
histogram_tester.ExpectUniqueSample(
"API.StorageAccess.DocumentInheritedStorageAccess", /*sample=*/false,
/*expected_bucket_count=*/1);
}
TEST_P(DocumentLoaderTest, StorageKeyFromNavigationParams) {
const KURL& requestor_url =
KURL(NullURL(), "https://www.example.com/foo.html");
WebViewImpl* web_view_impl =
web_view_helper_.InitializeAndLoad("https://example.com/foo.html");
const KURL& other_origin_url =
KURL(NullURL(), "https://www.another.com/bar.html");
std::unique_ptr<WebNavigationParams> params =
WebNavigationParams::CreateWithHTMLBufferForTesting(
SharedBuffer::Create(), other_origin_url);
params->requestor_origin = WebSecurityOrigin::Create(WebURL(requestor_url));
url::Origin origin;
auto nonce = base::UnguessableToken::Create();
StorageKey storage_key_to_commit = StorageKey::CreateWithNonce(origin, nonce);
params->storage_key = storage_key_to_commit;
LocalFrame* local_frame =
To<LocalFrame>(web_view_impl->GetPage()->MainFrame());
local_frame->Loader().CommitNavigation(std::move(params), nullptr);
EXPECT_EQ(
BlinkStorageKey::CreateWithNonce(SecurityOrigin::Create(other_origin_url),
storage_key_to_commit.nonce().value()),
local_frame->DomWindow()->GetStorageKey());
}
TEST_P(DocumentLoaderTest, StorageKeyCrossSiteFromNavigationParams) {
const KURL& requestor_url =
KURL(NullURL(), "https://www.example.com/foo.html");
WebViewImpl* web_view_impl =
web_view_helper_.InitializeAndLoad("https://example.com/foo.html");
const KURL& other_origin_url =
KURL(NullURL(), "https://www.another.com/bar.html");
std::unique_ptr<WebNavigationParams> params =
WebNavigationParams::CreateWithHTMLBufferForTesting(
SharedBuffer::Create(), other_origin_url);
params->requestor_origin = WebSecurityOrigin::Create(WebURL(requestor_url));
net::SchemefulSite top_level_site =
net::SchemefulSite(url::Origin::Create(GURL("https://foo.com")));
StorageKey storage_key_to_commit =
StorageKey::Create(url::Origin::Create(GURL(other_origin_url)),
top_level_site, mojom::AncestorChainBit::kCrossSite);
params->storage_key = storage_key_to_commit;
LocalFrame* local_frame =
To<LocalFrame>(web_view_impl->GetPage()->MainFrame());
local_frame->Loader().CommitNavigation(std::move(params), nullptr);
EXPECT_EQ(BlinkStorageKey::Create(SecurityOrigin::Create(other_origin_url),
BlinkSchemefulSite(top_level_site),
mojom::AncestorChainBit::kCrossSite),
local_frame->DomWindow()->GetStorageKey());
}
// Tests that committing a Javascript URL keeps the storage key's nonce of the
// previous document, ensuring that
// `DocumentLoader::CreateWebNavigationParamsToCloneDocument` works correctly
// w.r.t. storage key.
TEST_P(DocumentLoaderTest, JavascriptURLKeepsStorageKeyNonce) {
WebViewImpl* web_view_impl = web_view_helper_.Initialize();
BlinkStorageKey storage_key = BlinkStorageKey::CreateWithNonce(
SecurityOrigin::CreateUniqueOpaque(), base::UnguessableToken::Create());
LocalFrame* frame = To<LocalFrame>(web_view_impl->GetPage()->MainFrame());
frame->DomWindow()->SetStorageKey(storage_key);
frame->LoadJavaScriptURL(
url_test_helpers::ToKURL("javascript:'<p>hello world</p>'"));
EXPECT_EQ(storage_key.GetNonce(),
frame->DomWindow()->GetStorageKey().GetNonce());
}
TEST_P(DocumentLoaderTest, PublicSecureNotCounted) {
// Checking to make sure secure pages served in the public address space
// aren't counted for WebFeature::kMainFrameNonSecurePrivateAddressSpace
WebViewImpl* web_view_impl =
web_view_helper_.InitializeAndLoad("https://example.com/foo.html");
Document* document =
To<LocalFrame>(web_view_impl->GetPage()->MainFrame())->GetDocument();
EXPECT_FALSE(document->IsUseCounted(
WebFeature::kMainFrameNonSecurePrivateAddressSpace));
}
TEST_P(DocumentLoaderTest, PublicNonSecureNotCounted) {
// Checking to make sure non-secure pages served in the public address space
// aren't counted for WebFeature::kMainFrameNonSecurePrivateAddressSpace
WebViewImpl* web_view_impl =
web_view_helper_.InitializeAndLoad("http://example.com/foo.html");
Document* document =
To<LocalFrame>(web_view_impl->GetPage()->MainFrame())->GetDocument();
EXPECT_FALSE(document->IsUseCounted(
WebFeature::kMainFrameNonSecurePrivateAddressSpace));
}
TEST_P(DocumentLoaderTest, PrivateSecureNotCounted) {
// Checking to make sure secure pages served in the private address space
// aren't counted for WebFeature::kMainFrameNonSecurePrivateAddressSpace
WebViewImpl* web_view_impl =
web_view_helper_.InitializeAndLoad("https://192.168.1.1/foo.html");
Document* document =
To<LocalFrame>(web_view_impl->GetPage()->MainFrame())->GetDocument();
EXPECT_FALSE(document->IsUseCounted(
WebFeature::kMainFrameNonSecurePrivateAddressSpace));
}
TEST_P(DocumentLoaderTest, PrivateNonSecureIsCounted) {
// Checking to make sure non-secure pages served in the private address space
// are counted for WebFeature::kMainFrameNonSecurePrivateAddressSpace
WebViewImpl* web_view_impl =
web_view_helper_.InitializeAndLoad("http://192.168.1.1/foo.html");
Document* document =
To<LocalFrame>(web_view_impl->GetPage()->MainFrame())->GetDocument();
EXPECT_TRUE(document->IsUseCounted(
WebFeature::kMainFrameNonSecurePrivateAddressSpace));
}
TEST_P(DocumentLoaderTest, LocalNonSecureIsCounted) {
// Checking to make sure non-secure pages served in the local address space
// are counted for WebFeature::kMainFrameNonSecurePrivateAddressSpace
WebViewImpl* web_view_impl =
web_view_helper_.InitializeAndLoad("http://somethinglocal/foo.html");
Document* document =
To<LocalFrame>(web_view_impl->GetPage()->MainFrame())->GetDocument();
EXPECT_TRUE(document->IsUseCounted(
WebFeature::kMainFrameNonSecurePrivateAddressSpace));
}
TEST_F(DocumentLoaderSimTest, PrivateNonSecureChildFrameNotCounted) {
// Checking to make sure non-secure iframes served in the private address
// space are not counted for
// WebFeature::kMainFrameNonSecurePrivateAddressSpace
SimRequest main_resource("http://example.com", "text/html");
SimRequest iframe_resource("http://192.168.1.1/foo.html", "text/html");
LoadURL("http://example.com");
main_resource.Write(R"(
<iframe id='frame1'></iframe>
<script>
const iframe = document.getElementById('frame1');
iframe.src = 'http://192.168.1.1/foo.html'; // navigation triggered
</script>
)");
main_resource.Finish();
iframe_resource.Finish();
auto* child_frame = To<WebLocalFrameImpl>(MainFrame().FirstChild());
auto* child_document = child_frame->GetFrame()->GetDocument();
EXPECT_FALSE(child_document->IsUseCounted(
WebFeature::kMainFrameNonSecurePrivateAddressSpace));
}
TEST_P(DocumentLoaderTest, DecodedBodyData) {
BodyLoaderTestDelegate delegate(std::make_unique<DecodedBodyLoader>());
ScopedLoaderDelegate loader_delegate(&delegate);
frame_test_helpers::LoadFrameDontWait(
MainFrame(), url_test_helpers::ToKURL("https://example.com/foo.html"));
delegate.Write("<html>");
delegate.Write("<body>fo");
delegate.Write("o</body>");
delegate.Write("</html>");
delegate.Finish();
frame_test_helpers::PumpPendingRequestsForFrameToLoad(MainFrame());
// DecodedBodyLoader uppercases all data.
EXPECT_EQ(MainFrame()->GetDocument().Body().TextContent(), "FOO");
}
TEST_P(DocumentLoaderTest, DecodedBodyDataWithBlockedParser) {
BodyLoaderTestDelegate delegate(std::make_unique<DecodedBodyLoader>());
ScopedLoaderDelegate loader_delegate(&delegate);
frame_test_helpers::LoadFrameDontWait(
MainFrame(), url_test_helpers::ToKURL("https://example.com/foo.html"));
delegate.Write("<html>");
// Blocking the parser tests whether we buffer decoded data correctly.
MainFrame()->GetDocumentLoader()->BlockParser();
delegate.Write("<body>fo");
delegate.Write("o</body>");
MainFrame()->GetDocumentLoader()->ResumeParser();
delegate.Write("</html>");
delegate.Finish();
frame_test_helpers::PumpPendingRequestsForFrameToLoad(MainFrame());
// DecodedBodyLoader uppercases all data.
EXPECT_EQ(MainFrame()->GetDocument().Body().TextContent(), "FOO");
}
TEST_P(DocumentLoaderTest, EmbeddedCredentialsNavigation) {
struct TestCase {
const char* url;
const bool useCounted;
} test_cases[] = {{"http://example.com/foo.html", false},
{"http://user:@example.com/foo.html", true},
{"http://:pass@example.com/foo.html", true},
{"http://user:pass@example.com/foo.html", true}};
for (const auto& test_case : test_cases) {
WebViewImpl* web_view_impl =
web_view_helper_.InitializeAndLoad(test_case.url);
Document* document =
To<LocalFrame>(web_view_impl->GetPage()->MainFrame())->GetDocument();
EXPECT_EQ(test_case.useCounted,
document->IsUseCounted(
WebFeature::kTopLevelDocumentWithEmbeddedCredentials));
}
}
} // namespace
} // namespace blink