
In order to try to distinguish navigations leaving "real" content from those that are just the first navigation of a frame, this logs whether the page was the initial empty document also whether it was a http(s) URL. Bug: 1429178 Change-Id: I92e066938fd2cea17b74c89a093e1898dabb8204 Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/4519464 Reviewed-by: Takashi Toyoshima <toyoshim@chromium.org> Auto-Submit: Fergal Daly <fergal@chromium.org> Commit-Queue: Takashi Toyoshima <toyoshim@chromium.org> Cr-Commit-Position: refs/heads/main@{#1142596}
7077 lines
293 KiB
C++
7077 lines
293 KiB
C++
// Copyright 2014 The Chromium Authors
|
|
// Use of this source code is governed by a BSD-style license that can be
|
|
// found in the LICENSE file.
|
|
|
|
#include <stdint.h>
|
|
|
|
#include <memory>
|
|
#include <variant>
|
|
|
|
#include "base/command_line.h"
|
|
#include "base/containers/contains.h"
|
|
#include "base/files/file_util.h"
|
|
#include "base/files/scoped_temp_dir.h"
|
|
#include "base/functional/bind.h"
|
|
#include "base/functional/callback.h"
|
|
#include "base/memory/ptr_util.h"
|
|
#include "base/memory/raw_ptr.h"
|
|
#include "base/run_loop.h"
|
|
#include "base/strings/strcat.h"
|
|
#include "base/strings/stringprintf.h"
|
|
#include "base/strings/utf_string_conversions.h"
|
|
#include "base/task/single_thread_task_runner.h"
|
|
#include "base/test/bind.h"
|
|
#include "base/test/scoped_feature_list.h"
|
|
#include "base/test/test_timeouts.h"
|
|
#include "base/threading/thread_restrictions.h"
|
|
#include "base/uuid.h"
|
|
#include "build/build_config.h"
|
|
#include "components/ukm/test_ukm_recorder.h"
|
|
#include "content/browser/browser_url_handler_impl.h"
|
|
#include "content/browser/child_process_security_policy_impl.h"
|
|
#include "content/browser/renderer_host/navigation_request.h"
|
|
#include "content/browser/renderer_host/render_frame_host_impl.h"
|
|
#include "content/browser/web_contents/web_contents_impl.h"
|
|
#include "content/common/content_navigation_policy.h"
|
|
#include "content/common/features.h"
|
|
#include "content/common/frame_messages.mojom.h"
|
|
#include "content/common/navigation_client.mojom-forward.h"
|
|
#include "content/common/navigation_client.mojom.h"
|
|
#include "content/public/browser/browser_context.h"
|
|
#include "content/public/browser/browser_message_filter.h"
|
|
#include "content/public/browser/browser_task_traits.h"
|
|
#include "content/public/browser/browser_thread.h"
|
|
#include "content/public/browser/browser_url_handler.h"
|
|
#include "content/public/browser/content_browser_client.h"
|
|
#include "content/public/browser/download_manager_delegate.h"
|
|
#include "content/public/browser/navigation_controller.h"
|
|
#include "content/public/browser/notification_service.h"
|
|
#include "content/public/browser/notification_types.h"
|
|
#include "content/public/browser/page_navigator.h"
|
|
#include "content/public/browser/render_frame_host.h"
|
|
#include "content/public/browser/web_contents.h"
|
|
#include "content/public/common/content_client.h"
|
|
#include "content/public/common/content_features.h"
|
|
#include "content/public/common/content_switches.h"
|
|
#include "content/public/common/network_service_util.h"
|
|
#include "content/public/common/url_constants.h"
|
|
#include "content/public/test/back_forward_cache_util.h"
|
|
#include "content/public/test/browser_test.h"
|
|
#include "content/public/test/browser_test_utils.h"
|
|
#include "content/public/test/content_browser_test.h"
|
|
#include "content/public/test/content_browser_test_content_browser_client.h"
|
|
#include "content/public/test/content_browser_test_utils.h"
|
|
#include "content/public/test/content_mock_cert_verifier.h"
|
|
#include "content/public/test/download_test_observer.h"
|
|
#include "content/public/test/hit_test_region_observer.h"
|
|
#include "content/public/test/navigation_handle_observer.h"
|
|
#include "content/public/test/no_renderer_crashes_assertion.h"
|
|
#include "content/public/test/test_navigation_observer.h"
|
|
#include "content/public/test/test_navigation_throttle.h"
|
|
#include "content/public/test/test_navigation_throttle_inserter.h"
|
|
#include "content/public/test/test_utils.h"
|
|
#include "content/public/test/url_loader_monitor.h"
|
|
#include "content/shell/browser/shell.h"
|
|
#include "content/shell/browser/shell_download_manager_delegate.h"
|
|
#include "content/test/content_browser_test_utils_internal.h"
|
|
#include "content/test/did_commit_navigation_interceptor.h"
|
|
#include "content/test/fake_network_url_loader_factory.h"
|
|
#include "content/test/task_runner_deferring_throttle.h"
|
|
#include "content/test/test_render_frame_host_factory.h"
|
|
#include "ipc/ipc_security_test_util.h"
|
|
#include "mojo/public/cpp/bindings/pending_associated_remote.h"
|
|
#include "mojo/public/cpp/bindings/pending_receiver.h"
|
|
#include "net/base/features.h"
|
|
#include "net/base/load_flags.h"
|
|
#include "net/dns/mock_host_resolver.h"
|
|
#include "net/test/embedded_test_server/controllable_http_response.h"
|
|
#include "net/test/embedded_test_server/embedded_test_server.h"
|
|
#include "net/test/embedded_test_server/http_response.h"
|
|
#include "net/test/url_request/url_request_failed_job.h"
|
|
#include "services/metrics/public/cpp/ukm_source_id.h"
|
|
#include "services/network/public/cpp/features.h"
|
|
#include "services/network/public/cpp/web_sandbox_flags.h"
|
|
#include "services/network/public/mojom/url_loader.mojom.h"
|
|
#include "third_party/abseil-cpp/absl/types/optional.h"
|
|
#include "third_party/blink/public/common/loader/url_loader_throttle.h"
|
|
#include "third_party/blink/public/mojom/frame/sudden_termination_disabler_type.mojom-shared.h"
|
|
#include "ui/base/page_transition_types.h"
|
|
#include "url/gurl.h"
|
|
#include "url/url_util.h"
|
|
|
|
namespace content {
|
|
|
|
namespace {
|
|
|
|
class InterceptAndCancelDidCommitProvisionalLoad
|
|
: public DidCommitNavigationInterceptor {
|
|
public:
|
|
explicit InterceptAndCancelDidCommitProvisionalLoad(WebContents* web_contents)
|
|
: DidCommitNavigationInterceptor(web_contents) {}
|
|
~InterceptAndCancelDidCommitProvisionalLoad() override {}
|
|
|
|
void Wait(size_t number_of_messages) {
|
|
while (intercepted_messages_.size() < number_of_messages) {
|
|
loop_ = std::make_unique<base::RunLoop>();
|
|
loop_->Run();
|
|
}
|
|
}
|
|
|
|
const std::vector<NavigationRequest*>& intercepted_navigations() const {
|
|
return intercepted_navigations_;
|
|
}
|
|
|
|
const std::vector<mojom::DidCommitProvisionalLoadParamsPtr>&
|
|
intercepted_messages() const {
|
|
return intercepted_messages_;
|
|
}
|
|
|
|
protected:
|
|
bool WillProcessDidCommitNavigation(
|
|
RenderFrameHost* render_frame_host,
|
|
NavigationRequest* navigation_request,
|
|
mojom::DidCommitProvisionalLoadParamsPtr* params,
|
|
mojom::DidCommitProvisionalLoadInterfaceParamsPtr* interface_params)
|
|
override {
|
|
intercepted_navigations_.push_back(navigation_request);
|
|
intercepted_messages_.push_back(std::move(*params));
|
|
if (loop_)
|
|
loop_->Quit();
|
|
// Do not send the message to the RenderFrameHostImpl.
|
|
return false;
|
|
}
|
|
|
|
// Note: Do not dereference the intercepted_navigations_, they are used as
|
|
// indices in the RenderFrameHostImpl and not for themselves.
|
|
std::vector<NavigationRequest*> intercepted_navigations_;
|
|
std::vector<mojom::DidCommitProvisionalLoadParamsPtr> intercepted_messages_;
|
|
std::unique_ptr<base::RunLoop> loop_;
|
|
};
|
|
|
|
class RenderFrameHostImplForHistoryBackInterceptor
|
|
: public RenderFrameHostImpl {
|
|
public:
|
|
using RenderFrameHostImpl::RenderFrameHostImpl;
|
|
|
|
void GoToEntryAtOffset(int32_t offset,
|
|
bool has_user_gesture,
|
|
absl::optional<blink::scheduler::TaskAttributionId>
|
|
soft_navigation_heuristics_task_id) override {
|
|
if (quit_handler_)
|
|
std::move(quit_handler_).Run();
|
|
}
|
|
|
|
void set_quit_handler(base::OnceClosure handler) {
|
|
quit_handler_ = std::move(handler);
|
|
}
|
|
|
|
private:
|
|
friend class RenderFrameHostFactoryForHistoryBackInterceptor;
|
|
base::OnceClosure quit_handler_;
|
|
};
|
|
|
|
class RenderFrameHostFactoryForHistoryBackInterceptor
|
|
: public TestRenderFrameHostFactory {
|
|
protected:
|
|
std::unique_ptr<RenderFrameHostImpl> CreateRenderFrameHost(
|
|
SiteInstance* site_instance,
|
|
scoped_refptr<RenderViewHostImpl> render_view_host,
|
|
RenderFrameHostDelegate* delegate,
|
|
FrameTree* frame_tree,
|
|
FrameTreeNode* frame_tree_node,
|
|
int32_t routing_id,
|
|
mojo::PendingAssociatedRemote<mojom::Frame> frame_remote,
|
|
const blink::LocalFrameToken& frame_token,
|
|
const blink::DocumentToken& document_token,
|
|
base::UnguessableToken devtools_frame_token,
|
|
bool renderer_initiated_creation,
|
|
RenderFrameHostImpl::LifecycleStateImpl lifecycle_state,
|
|
scoped_refptr<BrowsingContextState> browsing_context_state) override {
|
|
return base::WrapUnique(new RenderFrameHostImplForHistoryBackInterceptor(
|
|
site_instance, std::move(render_view_host), delegate, frame_tree,
|
|
frame_tree_node, routing_id, std::move(frame_remote), frame_token,
|
|
document_token, devtools_frame_token, renderer_initiated_creation,
|
|
lifecycle_state, std::move(browsing_context_state),
|
|
frame_tree_node->frame_owner_element_type(), frame_tree_node->parent(),
|
|
frame_tree_node->fenced_frame_status()));
|
|
}
|
|
};
|
|
|
|
// Simulate embedders of content/ keeping track of the current visible URL using
|
|
// NavigateStateChanged() and GetVisibleURL() API.
|
|
class EmbedderVisibleUrlTracker : public WebContentsDelegate {
|
|
public:
|
|
const GURL& url() { return url_; }
|
|
|
|
// WebContentsDelegate's implementation:
|
|
void NavigationStateChanged(WebContents* source,
|
|
InvalidateTypes changed_flags) override {
|
|
if (!(changed_flags & INVALIDATE_TYPE_URL))
|
|
return;
|
|
url_ = source->GetVisibleURL();
|
|
if (on_url_invalidated_)
|
|
std::move(on_url_invalidated_).Run();
|
|
}
|
|
|
|
void WaitUntilUrlInvalidated() {
|
|
base::RunLoop loop;
|
|
on_url_invalidated_ = loop.QuitClosure();
|
|
loop.Run();
|
|
}
|
|
|
|
private:
|
|
GURL url_;
|
|
base::OnceClosure on_url_invalidated_;
|
|
};
|
|
|
|
// Helper class. Immediately run a callback when a navigation starts.
|
|
class DidStartNavigationCallback final : public WebContentsObserver {
|
|
public:
|
|
explicit DidStartNavigationCallback(
|
|
WebContents* web_contents,
|
|
base::OnceCallback<void(NavigationHandle*)> callback)
|
|
: WebContentsObserver(web_contents), callback_(std::move(callback)) {}
|
|
~DidStartNavigationCallback() override = default;
|
|
|
|
private:
|
|
void DidStartNavigation(NavigationHandle* navigation_handle) override {
|
|
if (callback_)
|
|
std::move(callback_).Run(navigation_handle);
|
|
}
|
|
base::OnceCallback<void(NavigationHandle*)> callback_;
|
|
};
|
|
|
|
// Helper class. Immediately run a callback when a navigation finishes.
|
|
class DidFinishNavigationCallback final : public WebContentsObserver {
|
|
public:
|
|
explicit DidFinishNavigationCallback(
|
|
WebContents* web_contents,
|
|
base::OnceCallback<void(NavigationHandle*)> callback)
|
|
: WebContentsObserver(web_contents), callback_(std::move(callback)) {}
|
|
~DidFinishNavigationCallback() override = default;
|
|
|
|
private:
|
|
void DidFinishNavigation(NavigationHandle* navigation_handle) override {
|
|
if (callback_)
|
|
std::move(callback_).Run(navigation_handle);
|
|
}
|
|
base::OnceCallback<void(NavigationHandle*)> callback_;
|
|
};
|
|
|
|
const char* non_cacheable_html_response =
|
|
"HTTP/1.1 200 OK\n"
|
|
"cache-control: no-cache, no-store, must-revalidate\n"
|
|
"content-type: text/html; charset=UTF-8\n"
|
|
"\n"
|
|
"HTML content.";
|
|
|
|
// Insert a navigation throttle blocking every navigation in its
|
|
// WillProcessResponse handler.
|
|
std::unique_ptr<content::TestNavigationThrottleInserter>
|
|
BlockNavigationWillProcessResponse(WebContentsImpl* web_content) {
|
|
return std::make_unique<content::TestNavigationThrottleInserter>(
|
|
web_content,
|
|
base::BindLambdaForTesting(
|
|
[&](NavigationHandle* handle) -> std::unique_ptr<NavigationThrottle> {
|
|
auto throttle = std::make_unique<TestNavigationThrottle>(handle);
|
|
throttle->SetResponse(TestNavigationThrottle::WILL_PROCESS_RESPONSE,
|
|
TestNavigationThrottle::SYNCHRONOUS,
|
|
NavigationThrottle::BLOCK_RESPONSE);
|
|
|
|
return throttle;
|
|
}));
|
|
}
|
|
|
|
} // namespace
|
|
|
|
// Test about navigation.
|
|
// If you don't need a custom embedded test server, please use the next class
|
|
// below (NavigationBrowserTest), it will automatically start the
|
|
// default server.
|
|
class NavigationBaseBrowserTest : public ContentBrowserTest {
|
|
public:
|
|
NavigationBaseBrowserTest() {}
|
|
|
|
void PreRunTestOnMainThread() override {
|
|
ContentBrowserTest::PreRunTestOnMainThread();
|
|
test_ukm_recorder_ = std::make_unique<ukm::TestAutoSetUkmRecorder>();
|
|
}
|
|
|
|
const ukm::TestAutoSetUkmRecorder& test_ukm_recorder() const {
|
|
return *test_ukm_recorder_;
|
|
}
|
|
|
|
protected:
|
|
void SetUpOnMainThread() override {
|
|
host_resolver()->AddRule("*", "127.0.0.1");
|
|
}
|
|
|
|
WebContentsImpl* web_contents() const {
|
|
return static_cast<WebContentsImpl*>(shell()->web_contents());
|
|
}
|
|
|
|
FrameTreeNode* main_frame() {
|
|
return web_contents()->GetPrimaryFrameTree().root();
|
|
}
|
|
|
|
RenderFrameHostImpl* current_frame_host() {
|
|
return main_frame()->current_frame_host();
|
|
}
|
|
|
|
private:
|
|
std::unique_ptr<ukm::TestAutoSetUkmRecorder> test_ukm_recorder_;
|
|
};
|
|
|
|
class NavigationBrowserTest : public NavigationBaseBrowserTest {
|
|
protected:
|
|
void SetUpOnMainThread() override {
|
|
NavigationBaseBrowserTest::SetUpOnMainThread();
|
|
ASSERT_TRUE(embedded_test_server()->Start());
|
|
}
|
|
};
|
|
|
|
class NavigationGoToEntryAtOffsetBrowserTest : public NavigationBrowserTest {
|
|
public:
|
|
void SetQuitHandlerForGoToEntryAtOffset(base::OnceClosure handler) {
|
|
RenderFrameHostImplForHistoryBackInterceptor* render_frame_host =
|
|
static_cast<RenderFrameHostImplForHistoryBackInterceptor*>(
|
|
current_frame_host());
|
|
render_frame_host->set_quit_handler(std::move(handler));
|
|
}
|
|
|
|
private:
|
|
RenderFrameHostFactoryForHistoryBackInterceptor render_frame_host_factory_;
|
|
};
|
|
|
|
class NetworkIsolationNavigationBrowserTest : public ContentBrowserTest {
|
|
public:
|
|
NetworkIsolationNavigationBrowserTest() = default;
|
|
|
|
protected:
|
|
void SetUpOnMainThread() override {
|
|
ASSERT_TRUE(embedded_test_server()->Start());
|
|
ContentBrowserTest::SetUpOnMainThread();
|
|
}
|
|
};
|
|
|
|
class NavigationBrowserTestReferrerPolicy
|
|
: public NavigationBrowserTest,
|
|
public ::testing::WithParamInterface<network::mojom::ReferrerPolicy> {
|
|
protected:
|
|
network::mojom::ReferrerPolicy GetReferrerPolicy() const {
|
|
return GetParam();
|
|
}
|
|
};
|
|
|
|
INSTANTIATE_TEST_SUITE_P(
|
|
All,
|
|
NavigationBrowserTestReferrerPolicy,
|
|
::testing::Values(
|
|
network::mojom::ReferrerPolicy::kAlways,
|
|
network::mojom::ReferrerPolicy::kDefault,
|
|
network::mojom::ReferrerPolicy::kNoReferrerWhenDowngrade,
|
|
network::mojom::ReferrerPolicy::kNever,
|
|
network::mojom::ReferrerPolicy::kOrigin,
|
|
network::mojom::ReferrerPolicy::kOriginWhenCrossOrigin,
|
|
network::mojom::ReferrerPolicy::kStrictOriginWhenCrossOrigin,
|
|
network::mojom::ReferrerPolicy::kSameOrigin,
|
|
network::mojom::ReferrerPolicy::kStrictOrigin));
|
|
|
|
// Ensure that browser initiated basic navigations work.
|
|
IN_PROC_BROWSER_TEST_F(NavigationBrowserTest, BrowserInitiatedNavigations) {
|
|
// Perform a navigation with no live renderer.
|
|
{
|
|
TestNavigationObserver observer(web_contents());
|
|
GURL url(embedded_test_server()->GetURL("/title1.html"));
|
|
EXPECT_TRUE(NavigateToURL(shell(), url));
|
|
EXPECT_EQ(url, observer.last_navigation_url());
|
|
EXPECT_TRUE(observer.last_navigation_succeeded());
|
|
EXPECT_FALSE(observer.last_initiator_origin().has_value());
|
|
EXPECT_FALSE(observer.last_initiator_frame_token().has_value());
|
|
EXPECT_EQ(ChildProcessHost::kInvalidUniqueID,
|
|
observer.last_initiator_process_id());
|
|
}
|
|
|
|
RenderFrameHost* initial_rfh = current_frame_host();
|
|
|
|
// Perform a same site navigation.
|
|
{
|
|
TestNavigationObserver observer(web_contents());
|
|
GURL url(embedded_test_server()->GetURL("/title2.html"));
|
|
EXPECT_TRUE(NavigateToURL(shell(), url));
|
|
EXPECT_EQ(url, observer.last_navigation_url());
|
|
EXPECT_TRUE(observer.last_navigation_succeeded());
|
|
EXPECT_FALSE(observer.last_initiator_origin().has_value());
|
|
EXPECT_FALSE(observer.last_initiator_frame_token().has_value());
|
|
EXPECT_EQ(ChildProcessHost::kInvalidUniqueID,
|
|
observer.last_initiator_process_id());
|
|
}
|
|
|
|
RenderFrameHost* second_rfh = current_frame_host();
|
|
|
|
if (CanSameSiteMainFrameNavigationsChangeRenderFrameHosts()) {
|
|
// If same-site ProactivelySwapBrowsingInstance or main-frame RenderDocument
|
|
// is enabled, the navigation will result in a new RFH.
|
|
EXPECT_NE(initial_rfh, second_rfh);
|
|
} else {
|
|
EXPECT_EQ(initial_rfh, second_rfh);
|
|
}
|
|
|
|
// Perform a cross-site navigation.
|
|
{
|
|
TestNavigationObserver observer(web_contents());
|
|
GURL url = embedded_test_server()->GetURL("foo.com", "/title3.html");
|
|
EXPECT_TRUE(NavigateToURL(shell(), url));
|
|
EXPECT_EQ(url, observer.last_navigation_url());
|
|
EXPECT_TRUE(observer.last_navigation_succeeded());
|
|
EXPECT_FALSE(observer.last_initiator_origin().has_value());
|
|
EXPECT_FALSE(observer.last_initiator_frame_token().has_value());
|
|
EXPECT_EQ(ChildProcessHost::kInvalidUniqueID,
|
|
observer.last_initiator_process_id());
|
|
}
|
|
|
|
// The RenderFrameHost should have changed.
|
|
EXPECT_NE(second_rfh, current_frame_host());
|
|
|
|
// Check the UKM for navigation responses received.
|
|
EXPECT_EQ(3u, test_ukm_recorder()
|
|
.GetEntriesByName("Navigation.ReceivedResponse")
|
|
.size());
|
|
}
|
|
|
|
// Ensure that renderer initiated same-site navigations work.
|
|
IN_PROC_BROWSER_TEST_F(NavigationBrowserTest,
|
|
RendererInitiatedSameSiteNavigation) {
|
|
// Perform a navigation with no live renderer.
|
|
{
|
|
TestNavigationObserver observer(web_contents());
|
|
GURL url(embedded_test_server()->GetURL("/simple_links.html"));
|
|
EXPECT_TRUE(NavigateToURL(shell(), url));
|
|
EXPECT_EQ(url, observer.last_navigation_url());
|
|
EXPECT_TRUE(observer.last_navigation_succeeded());
|
|
EXPECT_FALSE(observer.last_initiator_origin().has_value());
|
|
EXPECT_FALSE(observer.last_initiator_frame_token().has_value());
|
|
EXPECT_EQ(ChildProcessHost::kInvalidUniqueID,
|
|
observer.last_initiator_process_id());
|
|
}
|
|
|
|
RenderFrameHost* initial_rfh = current_frame_host();
|
|
|
|
blink::LocalFrameToken initial_rfh_frame_token = initial_rfh->GetFrameToken();
|
|
int initial_rfh_process_id = initial_rfh->GetProcess()->GetID();
|
|
|
|
// Simulate clicking on a same-site link.
|
|
{
|
|
TestNavigationObserver observer(web_contents());
|
|
GURL url(embedded_test_server()->GetURL("/title2.html"));
|
|
EXPECT_EQ(true, EvalJs(shell(), "clickSameSiteLink();"));
|
|
EXPECT_TRUE(WaitForLoadStop(web_contents()));
|
|
EXPECT_EQ(url, observer.last_navigation_url());
|
|
EXPECT_TRUE(observer.last_navigation_succeeded());
|
|
|
|
EXPECT_EQ(current_frame_host()->GetLastCommittedOrigin(),
|
|
observer.last_initiator_origin());
|
|
|
|
EXPECT_TRUE(observer.last_initiator_frame_token().has_value());
|
|
if (CanSameSiteMainFrameNavigationsChangeRenderFrameHosts()) {
|
|
// If same-site ProactivelySwapBrowsingInstance or main-frame
|
|
// RenderDocument is enabled, the navigation will result in a new RFH, so
|
|
// we need to compare with |initial_rfh|.
|
|
EXPECT_NE(current_frame_host(), initial_rfh);
|
|
EXPECT_EQ(initial_rfh_frame_token,
|
|
observer.last_initiator_frame_token().value());
|
|
EXPECT_EQ(initial_rfh_process_id, observer.last_initiator_process_id());
|
|
} else {
|
|
EXPECT_EQ(current_frame_host(), initial_rfh);
|
|
EXPECT_EQ(current_frame_host()->GetFrameToken(),
|
|
observer.last_initiator_frame_token().value());
|
|
EXPECT_EQ(current_frame_host()->GetProcess()->GetID(),
|
|
observer.last_initiator_process_id());
|
|
}
|
|
}
|
|
|
|
RenderFrameHost* second_rfh = current_frame_host();
|
|
|
|
if (CanSameSiteMainFrameNavigationsChangeRenderFrameHosts()) {
|
|
// If same-site ProactivelySwapBrowsingInstance or main-frame RenderDocument
|
|
// is enabled, the navigation will result in a new RFH.
|
|
EXPECT_NE(initial_rfh, second_rfh);
|
|
} else {
|
|
EXPECT_EQ(initial_rfh, second_rfh);
|
|
}
|
|
}
|
|
|
|
// Ensure that renderer initiated cross-site navigations work.
|
|
IN_PROC_BROWSER_TEST_F(NavigationBrowserTest,
|
|
RendererInitiatedCrossSiteNavigation) {
|
|
// Perform a navigation with no live renderer.
|
|
{
|
|
TestNavigationObserver observer(web_contents());
|
|
GURL url(embedded_test_server()->GetURL("/simple_links.html"));
|
|
EXPECT_TRUE(NavigateToURL(shell(), url));
|
|
EXPECT_EQ(url, observer.last_navigation_url());
|
|
EXPECT_TRUE(observer.last_navigation_succeeded());
|
|
}
|
|
|
|
RenderFrameHost* initial_rfh = current_frame_host();
|
|
url::Origin initial_origin = initial_rfh->GetLastCommittedOrigin();
|
|
blink::LocalFrameToken initiator_frame_token = initial_rfh->GetFrameToken();
|
|
int initiator_process_id = initial_rfh->GetProcess()->GetID();
|
|
|
|
// Simulate clicking on a cross-site link.
|
|
{
|
|
TestNavigationObserver observer(web_contents());
|
|
const char kReplacePortNumber[] = "setPortNumber(%d);";
|
|
uint16_t port_number = embedded_test_server()->port();
|
|
GURL url = embedded_test_server()->GetURL("foo.com", "/title2.html");
|
|
EXPECT_EQ(true, EvalJs(shell(), base::StringPrintf(kReplacePortNumber,
|
|
port_number)));
|
|
EXPECT_EQ(true, EvalJs(shell(), "clickCrossSiteLink();"));
|
|
EXPECT_TRUE(WaitForLoadStop(web_contents()));
|
|
EXPECT_EQ(url, observer.last_navigation_url());
|
|
EXPECT_TRUE(observer.last_navigation_succeeded());
|
|
EXPECT_EQ(initial_origin, observer.last_initiator_origin().value());
|
|
EXPECT_TRUE(observer.last_initiator_frame_token().has_value());
|
|
EXPECT_EQ(initiator_frame_token,
|
|
observer.last_initiator_frame_token().value());
|
|
EXPECT_EQ(initiator_process_id, observer.last_initiator_process_id());
|
|
}
|
|
|
|
// The RenderFrameHost should have changed unless default SiteInstances
|
|
// are enabled and proactive BrowsingInstance swaps are disabled.
|
|
if (AreDefaultSiteInstancesEnabled() &&
|
|
!CanCrossSiteNavigationsProactivelySwapBrowsingInstances()) {
|
|
EXPECT_EQ(initial_rfh, current_frame_host());
|
|
} else {
|
|
EXPECT_NE(initial_rfh, current_frame_host());
|
|
}
|
|
}
|
|
|
|
// Ensure navigation failures are handled.
|
|
IN_PROC_BROWSER_TEST_F(NavigationBrowserTest, FailedNavigation) {
|
|
// Perform a navigation with no live renderer.
|
|
{
|
|
TestNavigationObserver observer(web_contents());
|
|
GURL url(embedded_test_server()->GetURL("/title1.html"));
|
|
EXPECT_TRUE(NavigateToURL(shell(), url));
|
|
EXPECT_EQ(url, observer.last_navigation_url());
|
|
EXPECT_TRUE(observer.last_navigation_succeeded());
|
|
// Check the UKM for navigation responses received.
|
|
EXPECT_EQ(1u, test_ukm_recorder()
|
|
.GetEntriesByName("Navigation.ReceivedResponse")
|
|
.size());
|
|
}
|
|
|
|
// Now navigate to an unreachable url.
|
|
{
|
|
TestNavigationObserver observer(web_contents());
|
|
GURL error_url(embedded_test_server()->GetURL("/close-socket"));
|
|
GetIOThreadTaskRunner({})->PostTask(
|
|
FROM_HERE, base::BindOnce(&net::URLRequestFailedJob::AddUrlHandler));
|
|
EXPECT_FALSE(NavigateToURL(shell(), error_url));
|
|
EXPECT_EQ(error_url, observer.last_navigation_url());
|
|
NavigationEntry* entry =
|
|
web_contents()->GetController().GetLastCommittedEntry();
|
|
EXPECT_EQ(PAGE_TYPE_ERROR, entry->GetPageType());
|
|
// No response on an unreachable URL, so the ReceivedResponse event should
|
|
// not have increased.
|
|
EXPECT_EQ(1u, test_ukm_recorder()
|
|
.GetEntriesByName("Navigation.ReceivedResponse")
|
|
.size());
|
|
}
|
|
}
|
|
|
|
// Ensure that browser initiated navigations to view-source URLs works.
|
|
IN_PROC_BROWSER_TEST_F(NavigationBrowserTest,
|
|
ViewSourceNavigation_BrowserInitiated) {
|
|
TestNavigationObserver observer(web_contents());
|
|
GURL url(embedded_test_server()->GetURL("/title1.html"));
|
|
GURL view_source_url(content::kViewSourceScheme + std::string(":") +
|
|
url.spec());
|
|
EXPECT_TRUE(NavigateToURL(shell(), view_source_url));
|
|
EXPECT_EQ(url, observer.last_navigation_url());
|
|
EXPECT_TRUE(observer.last_navigation_succeeded());
|
|
}
|
|
|
|
// Ensure that content initiated navigations to view-sources URLs are blocked.
|
|
IN_PROC_BROWSER_TEST_F(NavigationBrowserTest,
|
|
ViewSourceNavigation_RendererInitiated) {
|
|
TestNavigationObserver observer(web_contents());
|
|
GURL kUrl(embedded_test_server()->GetURL("/simple_links.html"));
|
|
EXPECT_TRUE(NavigateToURL(shell(), kUrl));
|
|
EXPECT_EQ(kUrl, observer.last_navigation_url());
|
|
EXPECT_TRUE(observer.last_navigation_succeeded());
|
|
|
|
WebContentsConsoleObserver console_observer(web_contents());
|
|
console_observer.SetPattern(
|
|
"Not allowed to load local resource: view-source:about:blank");
|
|
|
|
EXPECT_EQ(true, EvalJs(web_contents(), "clickViewSourceLink();"));
|
|
ASSERT_TRUE(console_observer.Wait());
|
|
// Original page shouldn't navigate away.
|
|
EXPECT_EQ(kUrl, web_contents()->GetLastCommittedURL());
|
|
EXPECT_FALSE(shell()
|
|
->web_contents()
|
|
->GetController()
|
|
.GetLastCommittedEntry()
|
|
->IsViewSourceMode());
|
|
}
|
|
|
|
// Ensure that content initiated navigations to googlechrome: URLs are blocked.
|
|
IN_PROC_BROWSER_TEST_F(NavigationBrowserTest,
|
|
GoogleChromeNavigation_RendererInitiated) {
|
|
TestNavigationObserver observer(web_contents());
|
|
GURL kUrl(embedded_test_server()->GetURL("/simple_links.html"));
|
|
EXPECT_TRUE(NavigateToURL(shell(), kUrl));
|
|
EXPECT_EQ(kUrl, observer.last_navigation_url());
|
|
EXPECT_TRUE(observer.last_navigation_succeeded());
|
|
|
|
WebContentsConsoleObserver console_observer(web_contents());
|
|
console_observer.SetPattern(
|
|
"Not allowed to load local resource: googlechrome://");
|
|
|
|
EXPECT_EQ(true, EvalJs(web_contents(), "clickGoogleChromeLink();"));
|
|
ASSERT_TRUE(console_observer.Wait());
|
|
// Original page shouldn't navigate away.
|
|
EXPECT_EQ(kUrl, web_contents()->GetLastCommittedURL());
|
|
}
|
|
|
|
// Ensure that closing a page by running its beforeunload handler doesn't hang
|
|
// if there's an ongoing navigation.
|
|
IN_PROC_BROWSER_TEST_F(NavigationBrowserTest, UnloadDuringNavigation) {
|
|
WebContentsDestroyedWatcher close_observer(web_contents());
|
|
GURL url("chrome://resources/css/tabs.css");
|
|
NavigationHandleObserver handle_observer(web_contents(), url);
|
|
shell()->LoadURL(url);
|
|
web_contents()->DispatchBeforeUnload(false /* auto_cancel */);
|
|
close_observer.Wait();
|
|
EXPECT_EQ(net::ERR_ABORTED, handle_observer.net_error_code());
|
|
}
|
|
|
|
// Ensure that the referrer of a navigation is properly sanitized.
|
|
IN_PROC_BROWSER_TEST_F(NavigationBrowserTest, SanitizeReferrer) {
|
|
const GURL kInsecureUrl(embedded_test_server()->GetURL("/title1.html"));
|
|
const Referrer kSecureReferrer(
|
|
GURL("https://secure-url.com"),
|
|
network::mojom::ReferrerPolicy::kNoReferrerWhenDowngrade);
|
|
|
|
// Navigate to an insecure url with a secure referrer with a policy of no
|
|
// referrer on downgrades. The referrer url should be rewritten right away.
|
|
NavigationController::LoadURLParams load_params(kInsecureUrl);
|
|
load_params.referrer = kSecureReferrer;
|
|
TestNavigationManager manager(web_contents(), kInsecureUrl);
|
|
web_contents()->GetController().LoadURLWithParams(load_params);
|
|
EXPECT_TRUE(manager.WaitForRequestStart());
|
|
|
|
// The referrer should have been sanitized.
|
|
ASSERT_TRUE(main_frame()->navigation_request());
|
|
EXPECT_EQ(GURL(), main_frame()->navigation_request()->GetReferrer().url);
|
|
|
|
// The navigation should commit without being blocked.
|
|
EXPECT_TRUE(manager.WaitForResponse());
|
|
ASSERT_TRUE(manager.WaitForNavigationFinished());
|
|
EXPECT_EQ(kInsecureUrl, web_contents()->GetLastCommittedURL());
|
|
}
|
|
|
|
// Ensure the correctness of a navigation request's referrer. This is a
|
|
// regression test for https://crbug.com/1004083.
|
|
IN_PROC_BROWSER_TEST_P(NavigationBrowserTestReferrerPolicy, ReferrerPolicy) {
|
|
const GURL kDestination(embedded_test_server()->GetURL("/title1.html"));
|
|
const GURL kReferrerURL(embedded_test_server()->GetURL("/referrer-page"));
|
|
const url::Origin kReferrerOrigin = url::Origin::Create(kReferrerURL);
|
|
|
|
// It is possible that the referrer URL does not match what the policy
|
|
// demands (e.g., non-empty URL and kNever policy), so we'll test that the
|
|
// correct referrer is generated, and that the navigation succeeds.
|
|
const Referrer referrer(kReferrerURL, GetReferrerPolicy());
|
|
|
|
// Navigate to a resource whose destination URL is same-origin with the
|
|
// navigation's referrer. The final referrer should be generated correctly.
|
|
NavigationController::LoadURLParams load_params(kDestination);
|
|
load_params.referrer = referrer;
|
|
TestNavigationManager manager(web_contents(), kDestination);
|
|
web_contents()->GetController().LoadURLWithParams(load_params);
|
|
EXPECT_TRUE(manager.WaitForRequestStart());
|
|
|
|
// The referrer should have been sanitized.
|
|
ASSERT_TRUE(main_frame()->navigation_request());
|
|
switch (GetReferrerPolicy()) {
|
|
case network::mojom::ReferrerPolicy::kAlways:
|
|
case network::mojom::ReferrerPolicy::kDefault:
|
|
case network::mojom::ReferrerPolicy::kNoReferrerWhenDowngrade:
|
|
case network::mojom::ReferrerPolicy::kOriginWhenCrossOrigin:
|
|
case network::mojom::ReferrerPolicy::kStrictOriginWhenCrossOrigin:
|
|
case network::mojom::ReferrerPolicy::kSameOrigin:
|
|
EXPECT_EQ(kReferrerURL,
|
|
main_frame()->navigation_request()->GetReferrer().url);
|
|
break;
|
|
case network::mojom::ReferrerPolicy::kNever:
|
|
EXPECT_EQ(GURL(), main_frame()->navigation_request()->GetReferrer().url);
|
|
break;
|
|
case network::mojom::ReferrerPolicy::kOrigin:
|
|
case network::mojom::ReferrerPolicy::kStrictOrigin:
|
|
EXPECT_EQ(kReferrerOrigin.GetURL(),
|
|
main_frame()->navigation_request()->GetReferrer().url);
|
|
break;
|
|
}
|
|
|
|
// The navigation should commit without being blocked.
|
|
EXPECT_TRUE(manager.WaitForResponse());
|
|
ASSERT_TRUE(manager.WaitForNavigationFinished());
|
|
EXPECT_EQ(kDestination, web_contents()->GetLastCommittedURL());
|
|
}
|
|
|
|
// Test to verify that an exploited renderer process trying to upload a file
|
|
// it hasn't been explicitly granted permissions to is correctly terminated.
|
|
IN_PROC_BROWSER_TEST_F(NavigationBrowserTest, PostUploadIllegalFilePath) {
|
|
GURL form_url(
|
|
embedded_test_server()->GetURL("/form_that_posts_to_echoall.html"));
|
|
EXPECT_TRUE(NavigateToURL(shell(), form_url));
|
|
|
|
// Prepare a file for the upload form.
|
|
base::ScopedAllowBlockingForTesting allow_blocking;
|
|
base::ScopedTempDir temp_dir;
|
|
base::FilePath file_path;
|
|
std::string file_content("test-file-content");
|
|
ASSERT_TRUE(temp_dir.CreateUniqueTempDir());
|
|
ASSERT_TRUE(base::CreateTemporaryFileInDir(temp_dir.GetPath(), &file_path));
|
|
ASSERT_TRUE(base::WriteFile(file_path, file_content));
|
|
|
|
base::RunLoop run_loop;
|
|
// Fill out the form to refer to the test file.
|
|
std::unique_ptr<FileChooserDelegate> delegate(
|
|
new FileChooserDelegate(file_path, run_loop.QuitClosure()));
|
|
web_contents()->SetDelegate(delegate.get());
|
|
EXPECT_TRUE(
|
|
ExecJs(web_contents(), "document.getElementById('file').click();"));
|
|
run_loop.Run();
|
|
|
|
// Ensure that the process is allowed to access to the chosen file and
|
|
// does not have access to the other file name.
|
|
EXPECT_TRUE(ChildProcessSecurityPolicyImpl::GetInstance()->CanReadFile(
|
|
current_frame_host()->GetProcess()->GetID(), file_path));
|
|
|
|
// Revoke the access to the file and submit the form. The renderer process
|
|
// should be terminated.
|
|
RenderProcessHostBadIpcMessageWaiter process_kill_waiter(
|
|
current_frame_host()->GetProcess());
|
|
ChildProcessSecurityPolicyImpl* security_policy =
|
|
ChildProcessSecurityPolicyImpl::GetInstance();
|
|
security_policy->RevokeAllPermissionsForFile(
|
|
current_frame_host()->GetProcess()->GetID(), file_path);
|
|
|
|
// Use EvalJs and respond back to the browser process before doing the actual
|
|
// submission. This will ensure that the process termination is guaranteed to
|
|
// arrive after the response from the executed JavaScript.
|
|
EXPECT_EQ(
|
|
true,
|
|
EvalJs(
|
|
shell(),
|
|
"setTimeout(() => document.getElementById('file-form').submit(), 0);"
|
|
"true;"));
|
|
EXPECT_EQ(bad_message::ILLEGAL_UPLOAD_PARAMS, process_kill_waiter.Wait());
|
|
}
|
|
|
|
// Test case to verify that redirects to data: URLs are properly disallowed,
|
|
// even when invoked through a reload.
|
|
// See https://crbug.com/723796.
|
|
IN_PROC_BROWSER_TEST_F(NavigationBrowserTest,
|
|
VerifyBlockedErrorPageURL_Reload) {
|
|
NavigationControllerImpl& controller = web_contents()->GetController();
|
|
|
|
GURL start_url(embedded_test_server()->GetURL("/title1.html"));
|
|
EXPECT_TRUE(NavigateToURL(shell(), start_url));
|
|
EXPECT_EQ(0, controller.GetLastCommittedEntryIndex());
|
|
|
|
// Navigate to an URL, which redirects to a data: URL, since it is an
|
|
// unsafe redirect and will result in a blocked navigation and error page.
|
|
GURL redirect_to_blank_url(
|
|
embedded_test_server()->GetURL("/server-redirect?data:text/html,Hello!"));
|
|
EXPECT_FALSE(NavigateToURL(shell(), redirect_to_blank_url));
|
|
EXPECT_EQ(1, controller.GetLastCommittedEntryIndex());
|
|
EXPECT_EQ(PAGE_TYPE_ERROR, controller.GetLastCommittedEntry()->GetPageType());
|
|
|
|
TestNavigationObserver reload_observer(web_contents());
|
|
EXPECT_TRUE(ExecJs(shell(), "location.reload()"));
|
|
reload_observer.Wait();
|
|
|
|
// The expectation is that the blocked URL is present in the NavigationEntry,
|
|
// and shows up in both GetURL and GetVirtualURL.
|
|
EXPECT_EQ(1, controller.GetLastCommittedEntryIndex());
|
|
EXPECT_FALSE(
|
|
controller.GetLastCommittedEntry()->GetURL().SchemeIs(url::kDataScheme));
|
|
EXPECT_EQ(redirect_to_blank_url,
|
|
controller.GetLastCommittedEntry()->GetURL());
|
|
EXPECT_EQ(redirect_to_blank_url,
|
|
controller.GetLastCommittedEntry()->GetVirtualURL());
|
|
}
|
|
|
|
IN_PROC_BROWSER_TEST_F(NavigationBrowserTest, BackFollowedByReload) {
|
|
// First, make two history entries.
|
|
GURL url1(embedded_test_server()->GetURL("/title1.html"));
|
|
GURL url2(embedded_test_server()->GetURL("/title2.html"));
|
|
EXPECT_TRUE(NavigateToURL(shell(), url1));
|
|
EXPECT_TRUE(NavigateToURL(shell(), url2));
|
|
|
|
// Then execute a back navigation in Javascript followed by a reload.
|
|
TestNavigationObserver navigation_observer(web_contents());
|
|
EXPECT_TRUE(ExecJs(web_contents(), "history.back(); location.reload();"));
|
|
navigation_observer.Wait();
|
|
|
|
// The reload should have cancelled the back navigation, and the last
|
|
// committed URL should still be the second URL.
|
|
EXPECT_EQ(url2, web_contents()->GetLastCommittedURL());
|
|
}
|
|
|
|
// Test that a navigation response can be entirely fetched, even after the
|
|
// NavigationURLLoader has been deleted.
|
|
IN_PROC_BROWSER_TEST_F(NavigationBaseBrowserTest,
|
|
FetchResponseAfterNavigationURLLoaderDeleted) {
|
|
net::test_server::ControllableHttpResponse response(embedded_test_server(),
|
|
"/main_document");
|
|
ASSERT_TRUE(embedded_test_server()->Start());
|
|
|
|
// Load a new document.
|
|
GURL url(embedded_test_server()->GetURL("/main_document"));
|
|
TestNavigationManager navigation_manager(web_contents(), url);
|
|
shell()->LoadURL(url);
|
|
|
|
// The navigation starts.
|
|
EXPECT_TRUE(navigation_manager.WaitForRequestStart());
|
|
navigation_manager.ResumeNavigation();
|
|
|
|
// A NavigationRequest exists at this point.
|
|
EXPECT_TRUE(main_frame()->navigation_request());
|
|
|
|
// The response's headers are received.
|
|
response.WaitForRequest();
|
|
response.Send(
|
|
"HTTP/1.1 200 OK\r\n"
|
|
"Content-Type: text/html; charset=utf-8\r\n"
|
|
"\r\n"
|
|
"...");
|
|
EXPECT_TRUE(navigation_manager.WaitForResponse());
|
|
navigation_manager.ResumeNavigation();
|
|
|
|
// The renderer commits the navigation and the browser deletes its
|
|
// NavigationRequest.
|
|
ASSERT_TRUE(navigation_manager.WaitForNavigationFinished());
|
|
EXPECT_FALSE(main_frame()->navigation_request());
|
|
|
|
// The NavigationURLLoader has been deleted by now. Check that the renderer
|
|
// can still receive more bytes.
|
|
DOMMessageQueue dom_message_queue(web_contents());
|
|
response.Send(
|
|
"<script>window.domAutomationController.send('done');</script>");
|
|
std::string done;
|
|
EXPECT_TRUE(dom_message_queue.WaitForMessage(&done));
|
|
EXPECT_EQ("\"done\"", done);
|
|
}
|
|
|
|
IN_PROC_BROWSER_TEST_F(NetworkIsolationNavigationBrowserTest,
|
|
BrowserNavigationNetworkIsolationKey) {
|
|
GURL url(embedded_test_server()->GetURL("/title1.html"));
|
|
url::Origin origin = url::Origin::Create(url);
|
|
URLLoaderMonitor monitor({url});
|
|
EXPECT_TRUE(NavigateToURL(shell(), url));
|
|
monitor.WaitForUrls();
|
|
|
|
absl::optional<network::ResourceRequest> request =
|
|
monitor.GetRequestInfo(url);
|
|
ASSERT_TRUE(request->trusted_params);
|
|
EXPECT_TRUE(net::IsolationInfo::Create(
|
|
net::IsolationInfo::RequestType::kMainFrame, origin, origin,
|
|
net::SiteForCookies::FromOrigin(origin),
|
|
std::set<net::SchemefulSite>())
|
|
.IsEqualForTesting(request->trusted_params->isolation_info));
|
|
}
|
|
|
|
IN_PROC_BROWSER_TEST_F(NetworkIsolationNavigationBrowserTest,
|
|
RenderNavigationIsolationInfo) {
|
|
GURL url(embedded_test_server()->GetURL("/title2.html"));
|
|
url::Origin origin = url::Origin::Create(url);
|
|
EXPECT_TRUE(NavigateToURL(shell(), GURL("about:blank")));
|
|
URLLoaderMonitor monitor({url});
|
|
EXPECT_TRUE(NavigateToURLFromRenderer(shell(), url));
|
|
monitor.WaitForUrls();
|
|
|
|
absl::optional<network::ResourceRequest> request =
|
|
monitor.GetRequestInfo(url);
|
|
ASSERT_TRUE(request->trusted_params);
|
|
EXPECT_TRUE(net::IsolationInfo::Create(
|
|
net::IsolationInfo::RequestType::kMainFrame, origin, origin,
|
|
net::SiteForCookies::FromOrigin(origin),
|
|
std::set<net::SchemefulSite>())
|
|
.IsEqualForTesting(request->trusted_params->isolation_info));
|
|
}
|
|
|
|
IN_PROC_BROWSER_TEST_F(NetworkIsolationNavigationBrowserTest,
|
|
SubframeIsolationInfo) {
|
|
GURL url(embedded_test_server()->GetURL("/page_with_iframe.html"));
|
|
GURL iframe_document = embedded_test_server()->GetURL("/title1.html");
|
|
url::Origin origin = url::Origin::Create(url);
|
|
url::Origin iframe_origin = url::Origin::Create(iframe_document);
|
|
URLLoaderMonitor monitor({iframe_document});
|
|
EXPECT_TRUE(NavigateToURL(shell(), url));
|
|
monitor.WaitForUrls();
|
|
|
|
absl::optional<network::ResourceRequest> main_frame_request =
|
|
monitor.GetRequestInfo(url);
|
|
ASSERT_TRUE(main_frame_request.has_value());
|
|
ASSERT_TRUE(main_frame_request->trusted_params);
|
|
EXPECT_TRUE(net::IsolationInfo::Create(
|
|
net::IsolationInfo::RequestType::kMainFrame, origin, origin,
|
|
net::SiteForCookies::FromOrigin(origin),
|
|
std::set<net::SchemefulSite>())
|
|
.IsEqualForTesting(
|
|
main_frame_request->trusted_params->isolation_info));
|
|
|
|
absl::optional<network::ResourceRequest> iframe_request =
|
|
monitor.GetRequestInfo(iframe_document);
|
|
ASSERT_TRUE(iframe_request->trusted_params);
|
|
EXPECT_TRUE(
|
|
net::IsolationInfo::Create(net::IsolationInfo::RequestType::kSubFrame,
|
|
origin, iframe_origin,
|
|
net::SiteForCookies::FromOrigin(origin),
|
|
std::set<net::SchemefulSite>())
|
|
.IsEqualForTesting(iframe_request->trusted_params->isolation_info));
|
|
}
|
|
|
|
// Tests that the initiator is not set for a browser initiated top frame
|
|
// navigation.
|
|
IN_PROC_BROWSER_TEST_F(NavigationBrowserTest, BrowserNavigationInitiator) {
|
|
GURL url(embedded_test_server()->GetURL("/title1.html"));
|
|
|
|
URLLoaderMonitor monitor;
|
|
|
|
// Perform the actual navigation.
|
|
EXPECT_TRUE(NavigateToURL(shell(), url));
|
|
|
|
absl::optional<network::ResourceRequest> request =
|
|
monitor.GetRequestInfo(url);
|
|
ASSERT_TRUE(request.has_value());
|
|
ASSERT_FALSE(request->request_initiator.has_value());
|
|
}
|
|
|
|
// Test that the initiator is set to the starting page when a renderer initiated
|
|
// navigation goes from the starting page to another page.
|
|
IN_PROC_BROWSER_TEST_F(NavigationBrowserTest, RendererNavigationInitiator) {
|
|
GURL starting_page(embedded_test_server()->GetURL("a.com", "/title1.html"));
|
|
url::Origin starting_page_origin;
|
|
starting_page_origin = starting_page_origin.Create(starting_page);
|
|
|
|
EXPECT_TRUE(NavigateToURL(shell(), starting_page));
|
|
|
|
GURL url(embedded_test_server()->GetURL("/title2.html"));
|
|
|
|
URLLoaderMonitor monitor;
|
|
|
|
// Perform the actual navigation.
|
|
EXPECT_TRUE(NavigateToURLFromRenderer(shell(), url));
|
|
|
|
absl::optional<network::ResourceRequest> request =
|
|
monitor.GetRequestInfo(url);
|
|
ASSERT_TRUE(request.has_value());
|
|
EXPECT_EQ(starting_page_origin, request->request_initiator);
|
|
}
|
|
|
|
// Test that the initiator is set to the starting page when a sub frame is
|
|
// navigated by Javascript from some starting page to another page.
|
|
IN_PROC_BROWSER_TEST_F(NavigationBrowserTest, SubFrameJsNavigationInitiator) {
|
|
GURL starting_page(embedded_test_server()->GetURL("/frame_tree/top.html"));
|
|
EXPECT_TRUE(NavigateToURL(shell(), starting_page));
|
|
|
|
// The main_frame() and subframe should each have a live RenderFrame.
|
|
EXPECT_TRUE(main_frame()
|
|
->current_frame_host()
|
|
->render_view_host()
|
|
->IsRenderViewLive());
|
|
EXPECT_TRUE(main_frame()->current_frame_host()->IsRenderFrameLive());
|
|
EXPECT_TRUE(
|
|
main_frame()->child_at(0)->current_frame_host()->IsRenderFrameLive());
|
|
|
|
GURL url(embedded_test_server()->GetURL("a.com", "/title1.html"));
|
|
|
|
URLLoaderMonitor monitor({url});
|
|
std::string script = "location.href='" + url.spec() + "'";
|
|
|
|
// Perform the actual navigation.
|
|
EXPECT_TRUE(ExecJs(main_frame()->child_at(0)->current_frame_host(), script));
|
|
|
|
EXPECT_TRUE(main_frame()
|
|
->current_frame_host()
|
|
->render_view_host()
|
|
->IsRenderViewLive());
|
|
EXPECT_TRUE(main_frame()->current_frame_host()->IsRenderFrameLive());
|
|
EXPECT_TRUE(
|
|
main_frame()->child_at(0)->current_frame_host()->IsRenderFrameLive());
|
|
|
|
url::Origin starting_page_origin;
|
|
starting_page_origin = starting_page_origin.Create(starting_page);
|
|
|
|
monitor.WaitForUrls();
|
|
absl::optional<network::ResourceRequest> request =
|
|
monitor.GetRequestInfo(url);
|
|
EXPECT_EQ(starting_page_origin, request->request_initiator);
|
|
}
|
|
|
|
// Test that the initiator is set to the starting page when a sub frame,
|
|
// selected by Id, is navigated by Javascript from some starting page to another
|
|
// page.
|
|
IN_PROC_BROWSER_TEST_F(NavigationBrowserTest,
|
|
SubframeNavigationByTopFrameInitiator) {
|
|
// Go to a page on a.com with an iframe that is on b.com
|
|
GURL starting_page(embedded_test_server()->GetURL(
|
|
"a.com", "/cross_site_iframe_factory.html?a(b)"));
|
|
EXPECT_TRUE(NavigateToURL(shell(), starting_page));
|
|
|
|
// The main_frame and subframe should each have a live RenderFrame.
|
|
EXPECT_TRUE(main_frame()
|
|
->current_frame_host()
|
|
->render_view_host()
|
|
->IsRenderViewLive());
|
|
EXPECT_TRUE(main_frame()->current_frame_host()->IsRenderFrameLive());
|
|
EXPECT_TRUE(
|
|
main_frame()->child_at(0)->current_frame_host()->IsRenderFrameLive());
|
|
|
|
GURL url(embedded_test_server()->GetURL("c.com", "/title1.html"));
|
|
|
|
URLLoaderMonitor monitor;
|
|
|
|
// Perform the actual navigation.
|
|
NavigateIframeToURL(web_contents(), "child-0", url);
|
|
|
|
EXPECT_TRUE(main_frame()
|
|
->current_frame_host()
|
|
->render_view_host()
|
|
->IsRenderViewLive());
|
|
EXPECT_TRUE(main_frame()->current_frame_host()->IsRenderFrameLive());
|
|
EXPECT_TRUE(
|
|
main_frame()->child_at(0)->current_frame_host()->IsRenderFrameLive());
|
|
|
|
url::Origin starting_page_origin;
|
|
starting_page_origin = starting_page_origin.Create(starting_page);
|
|
|
|
absl::optional<network::ResourceRequest> request =
|
|
monitor.GetRequestInfo(url);
|
|
ASSERT_TRUE(request.has_value());
|
|
EXPECT_EQ(starting_page_origin, request->request_initiator);
|
|
}
|
|
|
|
IN_PROC_BROWSER_TEST_F(NavigationBrowserTest,
|
|
RendererInitiatedCrossSiteNewWindowInitator) {
|
|
GURL url(embedded_test_server()->GetURL("/simple_links.html"));
|
|
EXPECT_TRUE(NavigateToURL(shell(), url));
|
|
|
|
blink::LocalFrameToken initiator_frame_token =
|
|
current_frame_host()->GetFrameToken();
|
|
int initiator_process_id = current_frame_host()->GetProcess()->GetID();
|
|
|
|
// Simulate clicking on a cross-site link.
|
|
{
|
|
const char kReplacePortNumber[] = "setPortNumber(%d);";
|
|
uint16_t port_number = embedded_test_server()->port();
|
|
url = embedded_test_server()->GetURL("foo.com", "/title2.html");
|
|
EXPECT_TRUE(
|
|
ExecJs(shell(), base::StringPrintf(kReplacePortNumber, port_number)));
|
|
|
|
TestNavigationObserver observer(url);
|
|
observer.StartWatchingNewWebContents();
|
|
EXPECT_EQ(true, EvalJs(shell(), "clickCrossSiteNewWindowLink();"));
|
|
|
|
observer.Wait();
|
|
EXPECT_EQ(url, observer.last_navigation_url());
|
|
EXPECT_TRUE(observer.last_navigation_succeeded());
|
|
EXPECT_TRUE(observer.last_initiator_frame_token().has_value());
|
|
EXPECT_EQ(initiator_frame_token,
|
|
observer.last_initiator_frame_token().value());
|
|
EXPECT_EQ(initiator_process_id, observer.last_initiator_process_id());
|
|
}
|
|
}
|
|
|
|
// Ensure that renderer initiated navigations which have the opener suppressed
|
|
// work.
|
|
IN_PROC_BROWSER_TEST_F(NavigationBrowserTest,
|
|
RendererInitiatedNewWindowNoOpenerNavigation) {
|
|
GURL url(embedded_test_server()->GetURL("/simple_links.html"));
|
|
EXPECT_TRUE(NavigateToURL(shell(), url));
|
|
|
|
RenderFrameHost* initial_rfh = current_frame_host();
|
|
url::Origin initial_origin = initial_rfh->GetLastCommittedOrigin();
|
|
blink::LocalFrameToken initiator_frame_token = initial_rfh->GetFrameToken();
|
|
int initiator_process_id = initial_rfh->GetProcess()->GetID();
|
|
|
|
// Simulate clicking on a cross-site link which has rel="noopener".
|
|
{
|
|
const char kReplacePortNumber[] = "setPortNumber(%d);";
|
|
uint16_t port_number = embedded_test_server()->port();
|
|
url = embedded_test_server()->GetURL("foo.com", "/title2.html");
|
|
EXPECT_TRUE(
|
|
ExecJs(shell(), base::StringPrintf(kReplacePortNumber, port_number)));
|
|
|
|
TestNavigationObserver observer(url);
|
|
observer.StartWatchingNewWebContents();
|
|
EXPECT_EQ(true, EvalJs(shell(), "clickCrossSiteNewWindowNoOpenerLink();"));
|
|
|
|
observer.Wait();
|
|
|
|
EXPECT_EQ(url, observer.last_navigation_url());
|
|
EXPECT_TRUE(observer.last_navigation_succeeded());
|
|
EXPECT_EQ(initial_origin, observer.last_initiator_origin().value());
|
|
EXPECT_TRUE(observer.last_initiator_frame_token().has_value());
|
|
EXPECT_EQ(initiator_frame_token,
|
|
observer.last_initiator_frame_token().value());
|
|
EXPECT_EQ(initiator_process_id, observer.last_initiator_process_id());
|
|
}
|
|
}
|
|
|
|
IN_PROC_BROWSER_TEST_F(NavigationBrowserTest,
|
|
RendererInitiatedWithSubframeInitator) {
|
|
GURL url(embedded_test_server()->GetURL(
|
|
"a.com", "/cross_site_iframe_factory.html?a(a())"));
|
|
EXPECT_TRUE(NavigateToURL(shell(), url));
|
|
|
|
GURL subframe_url =
|
|
embedded_test_server()->GetURL("a.com", "/simple_links.html");
|
|
EXPECT_TRUE(
|
|
NavigateToURLFromRenderer(main_frame()->child_at(0), subframe_url));
|
|
|
|
RenderFrameHostImpl* subframe_rfh =
|
|
current_frame_host()->child_at(0)->current_frame_host();
|
|
blink::LocalFrameToken initiator_frame_token = subframe_rfh->GetFrameToken();
|
|
int initiator_process_id = subframe_rfh->GetProcess()->GetID();
|
|
|
|
// Simulate clicking on a cross-site link.
|
|
{
|
|
const char kReplacePortNumber[] = "setPortNumber(%d);";
|
|
uint16_t port_number = embedded_test_server()->port();
|
|
url = embedded_test_server()->GetURL("foo.com", "/title2.html");
|
|
EXPECT_TRUE(ExecJs(subframe_rfh,
|
|
base::StringPrintf(kReplacePortNumber, port_number)));
|
|
|
|
TestNavigationObserver observer(url);
|
|
observer.StartWatchingNewWebContents();
|
|
EXPECT_EQ(true, EvalJs(subframe_rfh, "clickCrossSiteNewWindowLink();"));
|
|
|
|
observer.Wait();
|
|
EXPECT_EQ(url, observer.last_navigation_url());
|
|
EXPECT_TRUE(observer.last_navigation_succeeded());
|
|
EXPECT_TRUE(observer.last_initiator_frame_token().has_value());
|
|
EXPECT_EQ(initiator_frame_token,
|
|
observer.last_initiator_frame_token().value());
|
|
EXPECT_EQ(initiator_process_id, observer.last_initiator_process_id());
|
|
}
|
|
}
|
|
|
|
IN_PROC_BROWSER_TEST_F(NavigationBrowserTest,
|
|
InitiatorFrameStateConsistentAtDidStartNavigation) {
|
|
GURL form_page_url(embedded_test_server()->GetURL(
|
|
"a.com", "/form_that_posts_to_echoall.html"));
|
|
EXPECT_TRUE(NavigateToURL(shell(), form_page_url));
|
|
|
|
// Give the form an action that will navigate to a slow page.
|
|
GURL form_action_url(embedded_test_server()->GetURL("b.com", "/slow?100"));
|
|
EXPECT_TRUE(
|
|
ExecJs(shell(), JsReplace("document.getElementById('form').action = $1",
|
|
form_action_url)));
|
|
|
|
// Open a new window that can be targeted by the form submission.
|
|
WebContents* form_contents = web_contents();
|
|
ShellAddedObserver new_shell_observer;
|
|
EXPECT_TRUE(ExecJs(shell(), "window.open('about:blank', 'target_frame');"));
|
|
WebContents* popup_contents = new_shell_observer.GetShell()->web_contents();
|
|
|
|
EXPECT_TRUE(
|
|
ExecJs(form_contents,
|
|
"document.getElementById('form').target = 'target_frame';"));
|
|
|
|
TestNavigationManager popup_manager(popup_contents, form_action_url);
|
|
TestNavigationManager form_manager(
|
|
form_contents, embedded_test_server()->GetURL("a.com", "/title2.html"));
|
|
|
|
// Submit the form and navigate the form's page.
|
|
EXPECT_TRUE(ExecJs(form_contents, "window.location.href = 'title2.html'"));
|
|
EXPECT_TRUE(
|
|
ExecJs(form_contents, "document.getElementById('form').submit();"));
|
|
|
|
// The form page's navigation should start prior to the form navigation.
|
|
EXPECT_TRUE(form_manager.WaitForRequestStart());
|
|
EXPECT_FALSE(popup_manager.GetNavigationHandle());
|
|
|
|
// When the navigation starts for the popup, ensure that the original page has
|
|
// not finished navigating. If this was not the case, we could not make any
|
|
// statements on the validity of initiator state during a navigation.
|
|
// Navigation handles are only available prior to DidFinishNavigation().
|
|
EXPECT_TRUE(popup_manager.WaitForRequestStart());
|
|
EXPECT_TRUE(form_manager.GetNavigationHandle());
|
|
}
|
|
|
|
IN_PROC_BROWSER_TEST_F(NavigationBrowserTest,
|
|
RendererInitiatedMiddleClickInitator) {
|
|
GURL url(embedded_test_server()->GetURL("/simple_links.html"));
|
|
EXPECT_TRUE(NavigateToURL(shell(), url));
|
|
|
|
blink::LocalFrameToken initiator_frame_token =
|
|
current_frame_host()->GetFrameToken();
|
|
int initiator_process_id = current_frame_host()->GetProcess()->GetID();
|
|
|
|
// Simulate middle-clicking on a cross-site link.
|
|
{
|
|
const char kReplacePortNumber[] = "setPortNumber(%d);";
|
|
uint16_t port_number = embedded_test_server()->port();
|
|
url = embedded_test_server()->GetURL("foo.com", "/title2.html");
|
|
EXPECT_TRUE(
|
|
ExecJs(shell(), base::StringPrintf(kReplacePortNumber, port_number)));
|
|
|
|
TestNavigationObserver observer(url);
|
|
observer.StartWatchingNewWebContents();
|
|
EXPECT_EQ(true, EvalJs(shell(), R"(
|
|
target = document.getElementById('cross_site_link');
|
|
var evt = new MouseEvent("click", {"button": 1 /* middle_button */});
|
|
target.dispatchEvent(evt);)"));
|
|
|
|
observer.Wait();
|
|
EXPECT_EQ(url, observer.last_navigation_url());
|
|
EXPECT_TRUE(observer.last_navigation_succeeded());
|
|
EXPECT_TRUE(observer.last_initiator_frame_token().has_value());
|
|
EXPECT_EQ(initiator_frame_token,
|
|
observer.last_initiator_frame_token().value());
|
|
EXPECT_EQ(initiator_process_id, observer.last_initiator_process_id());
|
|
}
|
|
}
|
|
|
|
// Data URLs can have a reference fragment like any other URLs. This test makes
|
|
// sure it is taken into account.
|
|
IN_PROC_BROWSER_TEST_F(NavigationBrowserTest, DataURLWithReferenceFragment) {
|
|
GURL url("data:text/html,body#foo");
|
|
EXPECT_TRUE(NavigateToURL(shell(), url));
|
|
|
|
EXPECT_EQ("body", EvalJs(shell(), "document.body.textContent;"));
|
|
|
|
EXPECT_EQ("#foo", EvalJs(shell(), "location.hash;"));
|
|
}
|
|
|
|
// Regression test for https://crbug.com/796561.
|
|
// 1) Start on a document with history.length == 1.
|
|
// 2) Create an iframe and call history.pushState at the same time.
|
|
// 3) history.back() must work.
|
|
IN_PROC_BROWSER_TEST_F(NavigationBrowserTest,
|
|
IframeAndPushStateSimultaneously) {
|
|
GURL main_url = embedded_test_server()->GetURL("/simple_page.html");
|
|
GURL iframe_url = embedded_test_server()->GetURL("/hello.html");
|
|
|
|
// 1) Start on a new document such that history.length == 1.
|
|
{
|
|
EXPECT_TRUE(NavigateToURL(shell(), main_url));
|
|
|
|
EXPECT_EQ(1, EvalJs(shell(), "history.length"));
|
|
}
|
|
|
|
// 2) Create an iframe and call history.pushState at the same time.
|
|
{
|
|
TestNavigationManager iframe_navigation(web_contents(), iframe_url);
|
|
ExecuteScriptAsync(shell(),
|
|
"let iframe = document.createElement('iframe');"
|
|
"iframe.src = '/hello.html';"
|
|
"document.body.appendChild(iframe);");
|
|
EXPECT_TRUE(iframe_navigation.WaitForRequestStart());
|
|
|
|
// The iframe navigation is paused. In the meantime, a pushState navigation
|
|
// begins and ends.
|
|
TestNavigationManager push_state_navigation(web_contents(), main_url);
|
|
ExecuteScriptAsync(shell(), "window.history.pushState({}, null);");
|
|
ASSERT_TRUE(push_state_navigation.WaitForNavigationFinished());
|
|
|
|
// The iframe navigation is resumed.
|
|
ASSERT_TRUE(iframe_navigation.WaitForNavigationFinished());
|
|
}
|
|
|
|
// 3) history.back() must work.
|
|
{
|
|
TestNavigationObserver navigation_observer(web_contents());
|
|
EXPECT_TRUE(ExecJs(web_contents(), "history.back();"));
|
|
navigation_observer.Wait();
|
|
}
|
|
}
|
|
|
|
// Regression test for https://crbug.com/260144
|
|
// Back/Forward navigation in an iframe must not stop ongoing XHR.
|
|
IN_PROC_BROWSER_TEST_F(NavigationBaseBrowserTest,
|
|
IframeNavigationsDoNotStopXHR) {
|
|
// A response for the XHR request. It will be delayed until the end of all the
|
|
// navigations.
|
|
net::test_server::ControllableHttpResponse xhr_response(
|
|
embedded_test_server(), "/xhr");
|
|
EXPECT_TRUE(embedded_test_server()->Start());
|
|
|
|
GURL url(embedded_test_server()->GetURL("/title1.html"));
|
|
EXPECT_TRUE(NavigateToURL(shell(), url));
|
|
|
|
DOMMessageQueue dom_message_queue(web_contents());
|
|
std::string message;
|
|
|
|
// 1) Send an XHR.
|
|
ExecuteScriptAsync(
|
|
shell(),
|
|
"let xhr = new XMLHttpRequest();"
|
|
"xhr.open('GET', './xhr', true);"
|
|
"xhr.onabort = () => window.domAutomationController.send('xhr.onabort');"
|
|
"xhr.onerror = () => window.domAutomationController.send('xhr.onerror');"
|
|
"xhr.onload = () => window.domAutomationController.send('xhr.onload');"
|
|
"xhr.send();");
|
|
|
|
// 2) Create an iframe and wait for the initial load.
|
|
{
|
|
ExecuteScriptAsync(
|
|
shell(),
|
|
"var iframe = document.createElement('iframe');"
|
|
"iframe.src = './title1.html';"
|
|
"iframe.onload = function() {"
|
|
" window.domAutomationController.send('iframe.onload');"
|
|
"};"
|
|
"document.body.appendChild(iframe);");
|
|
|
|
EXPECT_TRUE(dom_message_queue.WaitForMessage(&message));
|
|
EXPECT_EQ("\"iframe.onload\"", message);
|
|
}
|
|
|
|
// 3) Navigate the iframe elsewhere.
|
|
{
|
|
ExecuteScriptAsync(shell(),
|
|
"var iframe = document.querySelector('iframe');"
|
|
"iframe.src = './title2.html';");
|
|
|
|
EXPECT_TRUE(dom_message_queue.WaitForMessage(&message));
|
|
EXPECT_EQ("\"iframe.onload\"", message);
|
|
}
|
|
|
|
// 4) history.back() in the iframe.
|
|
{
|
|
ExecuteScriptAsync(shell(),
|
|
"var iframe = document.querySelector('iframe');"
|
|
"iframe.contentWindow.history.back()");
|
|
|
|
EXPECT_TRUE(dom_message_queue.WaitForMessage(&message));
|
|
EXPECT_EQ("\"iframe.onload\"", message);
|
|
}
|
|
|
|
// 5) history.forward() in the iframe.
|
|
{
|
|
ExecuteScriptAsync(shell(),
|
|
"var iframe = document.querySelector('iframe');"
|
|
"iframe.contentWindow.history.forward()");
|
|
|
|
EXPECT_TRUE(dom_message_queue.WaitForMessage(&message));
|
|
EXPECT_EQ("\"iframe.onload\"", message);
|
|
}
|
|
|
|
// 6) Wait for the XHR.
|
|
{
|
|
xhr_response.WaitForRequest();
|
|
xhr_response.Send(
|
|
"HTTP/1.1 200 OK\r\n"
|
|
"Connection: close\r\n"
|
|
"Content-Length: 2\r\n"
|
|
"Content-Type: text/plain; charset=utf-8\r\n"
|
|
"\r\n"
|
|
"OK");
|
|
xhr_response.Done();
|
|
EXPECT_TRUE(dom_message_queue.WaitForMessage(&message));
|
|
EXPECT_EQ("\"xhr.onload\"", message);
|
|
}
|
|
|
|
EXPECT_FALSE(dom_message_queue.PopMessage(&message));
|
|
}
|
|
|
|
// Regression test for https://crbug.com/856396.
|
|
// Note that original issue for the bug is not applicable anymore, because there
|
|
// is no provisional document loader which has not committed yet. We keep the
|
|
// modified version of this test to check removing iframe from the load event
|
|
// handler.
|
|
IN_PROC_BROWSER_TEST_F(NavigationBaseBrowserTest,
|
|
ReplacingDocumentLoaderFiresLoadEvent) {
|
|
net::test_server::ControllableHttpResponse main_document_response(
|
|
embedded_test_server(), "/main_document");
|
|
net::test_server::ControllableHttpResponse iframe_response(
|
|
embedded_test_server(), "/iframe");
|
|
|
|
ASSERT_TRUE(embedded_test_server()->Start());
|
|
|
|
// 1) Load the main document.
|
|
shell()->LoadURL(embedded_test_server()->GetURL("/main_document"));
|
|
main_document_response.WaitForRequest();
|
|
main_document_response.Send(
|
|
"HTTP/1.1 200 OK\r\n"
|
|
"Content-Type: text/html; charset=utf-8\r\n"
|
|
"\r\n"
|
|
"<script>"
|
|
" var detach_iframe = function() {"
|
|
" var iframe = document.querySelector('iframe');"
|
|
" iframe.parentNode.removeChild(iframe);"
|
|
" }"
|
|
"</script>"
|
|
"<body onload='detach_iframe()'>"
|
|
" <iframe src='/iframe'></iframe>"
|
|
"</body>");
|
|
main_document_response.Done();
|
|
|
|
// 2) The iframe starts to load, but the server only have time to send the
|
|
// response's headers, not the response's body. This should commit the
|
|
// iframe's load.
|
|
iframe_response.WaitForRequest();
|
|
iframe_response.Send(
|
|
"HTTP/1.1 200 OK\r\n"
|
|
"Content-Type: text/html; charset=utf-8\r\n"
|
|
"\r\n");
|
|
|
|
// 3) In the meantime the iframe navigates elsewhere. It causes the previous
|
|
// DocumentLoader to be replaced by the new one. Removing it may
|
|
// trigger the 'load' event and delete the iframe.
|
|
EXPECT_TRUE(
|
|
ExecJs(shell(), "document.querySelector('iframe').src = '/title1.html'"));
|
|
|
|
// 4) Finish the original request.
|
|
iframe_response.Done();
|
|
|
|
// Wait for the iframe to be deleted and check the renderer process is still
|
|
// alive.
|
|
int iframe_count = 1;
|
|
while (iframe_count != 0) {
|
|
iframe_count =
|
|
EvalJs(
|
|
shell(),
|
|
"var iframe_count = document.getElementsByTagName('iframe').length;"
|
|
"iframe_count;")
|
|
.ExtractInt();
|
|
}
|
|
}
|
|
|
|
class NavigationDownloadBrowserTest : public NavigationBaseBrowserTest {
|
|
protected:
|
|
void SetUpOnMainThread() override {
|
|
NavigationBaseBrowserTest::SetUpOnMainThread();
|
|
|
|
// Set up a test download directory, in order to prevent prompting for
|
|
// handling downloads.
|
|
ASSERT_TRUE(downloads_directory_.CreateUniqueTempDir());
|
|
ShellDownloadManagerDelegate* delegate =
|
|
static_cast<ShellDownloadManagerDelegate*>(
|
|
web_contents()->GetBrowserContext()->GetDownloadManagerDelegate());
|
|
delegate->SetDownloadBehaviorForTesting(downloads_directory_.GetPath());
|
|
}
|
|
|
|
private:
|
|
base::ScopedTempDir downloads_directory_;
|
|
};
|
|
|
|
// Regression test for https://crbug.com/855033
|
|
// 1) A page contains many scripts and DOM elements. It forces the parser to
|
|
// yield CPU to other tasks. That way the response body's data are not fully
|
|
// read when URLLoaderClient::OnComplete(..) is received.
|
|
// 2) A script makes the document navigates elsewhere while it is still loading.
|
|
// It cancels the parser of the current document. Due to a bug, the document
|
|
// loader was not marked to be 'loaded' at this step.
|
|
// 3) The request for the new navigation starts and it turns out it is a
|
|
// download. The navigation is dropped.
|
|
// 4) There are no more possibilities for DidStopLoading() to be sent.
|
|
IN_PROC_BROWSER_TEST_F(NavigationDownloadBrowserTest,
|
|
StopLoadingAfterDroppedNavigation) {
|
|
net::test_server::ControllableHttpResponse main_response(
|
|
embedded_test_server(), "/main");
|
|
ASSERT_TRUE(embedded_test_server()->Start());
|
|
|
|
GURL main_url(embedded_test_server()->GetURL("/main"));
|
|
GURL download_url(embedded_test_server()->GetURL("/download-test1.lib"));
|
|
|
|
shell()->LoadURL(main_url);
|
|
main_response.WaitForRequest();
|
|
std::string headers =
|
|
"HTTP/1.1 200 OK\r\n"
|
|
"Content-Type: text/html; charset=utf-8\r\n"
|
|
"\r\n";
|
|
|
|
// Craft special HTML to make the blink::DocumentParser yield CPU to other
|
|
// tasks. The goal is to ensure the response body datapipe is not fully read
|
|
// when URLLoaderClient::OnComplete() is called.
|
|
// This relies on the HTMLParserScheduler::ShouldYield() heuristics.
|
|
std::string mix_of_script_and_div = "<script></script><div></div>";
|
|
for (size_t i = 0; i < 10; ++i) {
|
|
mix_of_script_and_div += mix_of_script_and_div; // Exponential growth.
|
|
}
|
|
|
|
std::string navigate_to_download =
|
|
"<script>location.href='" + download_url.spec() + "'</script>";
|
|
|
|
main_response.Send(headers + navigate_to_download + mix_of_script_and_div);
|
|
main_response.Done();
|
|
|
|
EXPECT_TRUE(WaitForLoadStop(web_contents()));
|
|
}
|
|
|
|
// Renderer initiated back/forward navigation in beforeunload should not prevent
|
|
// the user to navigate away from a website.
|
|
IN_PROC_BROWSER_TEST_F(NavigationBrowserTest, HistoryBackInBeforeUnload) {
|
|
GURL url_1(embedded_test_server()->GetURL("/title1.html"));
|
|
GURL url_2(embedded_test_server()->GetURL("/title2.html"));
|
|
|
|
EXPECT_TRUE(NavigateToURL(shell(), url_1));
|
|
EXPECT_TRUE(ExecJs(web_contents(),
|
|
"onbeforeunload = function() {"
|
|
" history.pushState({}, null, '/');"
|
|
" history.back();"
|
|
"};",
|
|
EXECUTE_SCRIPT_NO_USER_GESTURE));
|
|
EXPECT_TRUE(NavigateToURL(shell(), url_2));
|
|
}
|
|
|
|
// Same as 'HistoryBackInBeforeUnload', but wraps history.back() inside
|
|
// window.setTimeout(). Thus it is executed "outside" of its beforeunload
|
|
// handler and thus avoid basic navigation circumventions.
|
|
// Regression test for: https://crbug.com/879965.
|
|
IN_PROC_BROWSER_TEST_F(NavigationGoToEntryAtOffsetBrowserTest,
|
|
HistoryBackInBeforeUnloadAfterSetTimeout) {
|
|
GURL url_1(embedded_test_server()->GetURL("/title1.html"));
|
|
GURL url_2(embedded_test_server()->GetURL("/title2.html"));
|
|
|
|
EXPECT_TRUE(NavigateToURL(shell(), url_1));
|
|
EXPECT_TRUE(ExecJs(web_contents(),
|
|
"onbeforeunload = function() {"
|
|
" history.pushState({}, null, '/');"
|
|
" setTimeout(()=>history.back());"
|
|
"};",
|
|
EXECUTE_SCRIPT_NO_USER_GESTURE));
|
|
TestNavigationManager navigation(web_contents(), url_2);
|
|
|
|
base::RunLoop run_loop;
|
|
SetQuitHandlerForGoToEntryAtOffset(run_loop.QuitClosure());
|
|
shell()->LoadURL(url_2);
|
|
run_loop.Run();
|
|
|
|
ASSERT_TRUE(navigation.WaitForNavigationFinished());
|
|
|
|
EXPECT_TRUE(navigation.was_successful());
|
|
}
|
|
|
|
// Renderer initiated back/forward navigation can't cancel an ongoing browser
|
|
// initiated navigation if it is not user initiated.
|
|
IN_PROC_BROWSER_TEST_F(NavigationBrowserTest,
|
|
HistoryBackCancelPendingNavigationNoUserGesture) {
|
|
GURL url_1(embedded_test_server()->GetURL("/title1.html"));
|
|
GURL url_2(embedded_test_server()->GetURL("/title2.html"));
|
|
EXPECT_TRUE(NavigateToURL(shell(), url_1));
|
|
|
|
// 1) A pending browser initiated navigation (omnibox, ...) starts.
|
|
TestNavigationManager navigation(web_contents(), url_2);
|
|
shell()->LoadURL(url_2);
|
|
EXPECT_TRUE(navigation.WaitForRequestStart());
|
|
|
|
// 2) history.back() is sent but is not user initiated.
|
|
EXPECT_TRUE(ExecJs(web_contents(),
|
|
"history.pushState({}, null, '/');"
|
|
"history.back();",
|
|
EXECUTE_SCRIPT_NO_USER_GESTURE));
|
|
|
|
// 3) The first pending navigation is not canceled and can continue.
|
|
ASSERT_TRUE(navigation.WaitForNavigationFinished()); // Resume navigation.
|
|
EXPECT_TRUE(navigation.was_successful());
|
|
}
|
|
|
|
// Renderer initiated back/forward navigation can cancel an ongoing browser
|
|
// initiated navigation if it is user initiated.
|
|
IN_PROC_BROWSER_TEST_F(NavigationBrowserTest,
|
|
HistoryBackCancelPendingNavigationUserGesture) {
|
|
GURL url_1(embedded_test_server()->GetURL("/title1.html"));
|
|
GURL url_2(embedded_test_server()->GetURL("/title2.html"));
|
|
EXPECT_TRUE(NavigateToURL(shell(), url_1));
|
|
|
|
// 1) A pending browser initiated navigation (omnibox, ...) starts.
|
|
TestNavigationManager navigation(web_contents(), url_2);
|
|
shell()->LoadURL(url_2);
|
|
EXPECT_TRUE(navigation.WaitForRequestStart());
|
|
|
|
// 2) history.back() is sent and is user initiated.
|
|
EXPECT_TRUE(ExecJs(web_contents(),
|
|
"history.pushState({}, null, '/');"
|
|
"history.back();"));
|
|
|
|
// 3) Check the first pending navigation has been canceled.
|
|
ASSERT_TRUE(navigation.WaitForNavigationFinished()); // Resume navigation.
|
|
EXPECT_FALSE(navigation.was_successful());
|
|
}
|
|
|
|
// Ensure the renderer process doesn't send too many IPC to the browser process
|
|
// when history.pushState() and history.back() are called in a loop.
|
|
// Failing to do so causes the browser to become unresponsive.
|
|
// See https://crbug.com/882238
|
|
IN_PROC_BROWSER_TEST_F(NavigationBrowserTest, IPCFlood_GoToEntryAtOffset) {
|
|
GURL url(embedded_test_server()->GetURL("/title1.html"));
|
|
EXPECT_TRUE(NavigateToURL(shell(), url));
|
|
|
|
WebContentsConsoleObserver console_observer(web_contents());
|
|
console_observer.SetPattern(
|
|
"Throttling navigation to prevent the browser from hanging. See "
|
|
"https://crbug.com/1038223. Command line switch "
|
|
"--disable-ipc-flooding-protection can be used to bypass the "
|
|
"protection");
|
|
|
|
EXPECT_TRUE(ExecJs(shell(), R"(
|
|
for(let i = 0; i<1000; ++i) {
|
|
history.pushState({},"page 2", "bar.html");
|
|
history.back();
|
|
}
|
|
)"));
|
|
|
|
ASSERT_TRUE(console_observer.Wait());
|
|
}
|
|
|
|
// Ensure the renderer process doesn't send too many IPC to the browser process
|
|
// when doing a same-document navigation is requested in a loop.
|
|
// Failing to do so causes the browser to become unresponsive.
|
|
// TODO(arthursonzogni): Make the same test, but when the navigation is
|
|
// requested from a remote frame.
|
|
// See https://crbug.com/882238
|
|
IN_PROC_BROWSER_TEST_F(NavigationBrowserTest, IPCFlood_Navigation) {
|
|
GURL url(embedded_test_server()->GetURL("/title1.html"));
|
|
EXPECT_TRUE(NavigateToURL(shell(), url));
|
|
|
|
WebContentsConsoleObserver console_observer(web_contents());
|
|
console_observer.SetPattern(
|
|
"Throttling navigation to prevent the browser from hanging. See "
|
|
"https://crbug.com/1038223. Command line switch "
|
|
"--disable-ipc-flooding-protection can be used to bypass the "
|
|
"protection");
|
|
|
|
EXPECT_TRUE(ExecJs(shell(), R"(
|
|
for(let i = 0; i<1000; ++i) {
|
|
location.href = "#" + i;
|
|
++i;
|
|
}
|
|
)"));
|
|
|
|
ASSERT_TRUE(console_observer.Wait());
|
|
}
|
|
|
|
// TODO(http://crbug.com/632514): This test currently expects opener downloads
|
|
// go through and UMA is logged, but when the linked bug is resolved the
|
|
// download should be disallowed.
|
|
IN_PROC_BROWSER_TEST_F(NavigationBrowserTest, OpenerNavigation_DownloadPolicy) {
|
|
base::ScopedAllowBlockingForTesting allow_blocking;
|
|
base::ScopedTempDir download_dir;
|
|
ASSERT_TRUE(download_dir.CreateUniqueTempDir());
|
|
ShellDownloadManagerDelegate* delegate =
|
|
static_cast<ShellDownloadManagerDelegate*>(
|
|
web_contents()->GetBrowserContext()->GetDownloadManagerDelegate());
|
|
delegate->SetDownloadBehaviorForTesting(download_dir.GetPath());
|
|
EXPECT_TRUE(
|
|
NavigateToURL(shell(), embedded_test_server()->GetURL("/title1.html")));
|
|
|
|
// Open a popup.
|
|
EXPECT_EQ(true, EvalJs(web_contents(), "!!window.open();"));
|
|
EXPECT_EQ(2u, Shell::windows().size());
|
|
|
|
// Using the popup, navigate its opener to a download.
|
|
base::HistogramTester histograms;
|
|
WebContents* popup = Shell::windows()[1]->web_contents();
|
|
EXPECT_NE(popup, web_contents());
|
|
DownloadTestObserverInProgress observer(
|
|
web_contents()->GetBrowserContext()->GetDownloadManager(),
|
|
1 /* wait_count */);
|
|
EXPECT_TRUE(ExecJs(
|
|
popup,
|
|
"window.opener.location ='data:html/text;base64,'+btoa('payload');",
|
|
EXECUTE_SCRIPT_NO_USER_GESTURE));
|
|
observer.WaitForFinished();
|
|
|
|
// Implies NavigationDownloadType::kOpenerCrossOrigin has 0 count.
|
|
histograms.ExpectUniqueSample("Navigation.DownloadPolicy.LogPerPolicyApplied",
|
|
blink::NavigationDownloadType::kNoGesture, 1);
|
|
}
|
|
|
|
// A variation of the OpenerNavigation_DownloadPolicy test above, but uses a
|
|
// cross-origin URL for the popup window.
|
|
IN_PROC_BROWSER_TEST_F(NavigationBrowserTest,
|
|
CrossOriginOpenerNavigation_DownloadPolicy) {
|
|
base::ScopedAllowBlockingForTesting allow_blocking;
|
|
base::ScopedTempDir download_dir;
|
|
ASSERT_TRUE(download_dir.CreateUniqueTempDir());
|
|
ShellDownloadManagerDelegate* delegate =
|
|
static_cast<ShellDownloadManagerDelegate*>(
|
|
web_contents()->GetBrowserContext()->GetDownloadManagerDelegate());
|
|
delegate->SetDownloadBehaviorForTesting(download_dir.GetPath());
|
|
EXPECT_TRUE(NavigateToURL(
|
|
shell(), embedded_test_server()->GetURL("a.com", "/title1.html")));
|
|
|
|
// Open a popup.
|
|
ShellAddedObserver shell_observer;
|
|
EXPECT_TRUE(EvalJs(web_contents(), JsReplace("!!window.open($1);",
|
|
embedded_test_server()->GetURL(
|
|
"bar.com", "/title1.html")))
|
|
.ExtractBool());
|
|
Shell* new_shell = shell_observer.GetShell();
|
|
EXPECT_EQ(2u, Shell::windows().size());
|
|
|
|
// Wait for the navigation in the popup to complete, so the origin of the
|
|
// document will be correct.
|
|
WebContents* popup = new_shell->web_contents();
|
|
EXPECT_NE(popup, web_contents());
|
|
EXPECT_TRUE(WaitForLoadStop(popup));
|
|
|
|
// Using the popup, navigate its opener to a download.
|
|
base::HistogramTester histograms;
|
|
const GURL data_url("data:html/text;base64,cGF5bG9hZA==");
|
|
TestNavigationManager manager(web_contents(), data_url);
|
|
EXPECT_TRUE(ExecJs(popup, base::StringPrintf("window.opener.location ='%s'",
|
|
data_url.spec().c_str())));
|
|
ASSERT_TRUE(manager.WaitForNavigationFinished());
|
|
|
|
EXPECT_FALSE(manager.was_successful());
|
|
|
|
histograms.ExpectBucketCount(
|
|
"Navigation.DownloadPolicy.LogPerPolicyApplied",
|
|
blink::NavigationDownloadType::kOpenerCrossOrigin, 1);
|
|
}
|
|
|
|
// Regression test for https://crbug.com/872284.
|
|
// A NavigationThrottle cancels a download in WillProcessResponse.
|
|
// The navigation request must be canceled and it must also cancel the network
|
|
// request. Failing to do so resulted in the network socket being leaked.
|
|
IN_PROC_BROWSER_TEST_F(NavigationDownloadBrowserTest,
|
|
CancelDownloadOnResponseStarted) {
|
|
ASSERT_TRUE(embedded_test_server()->Start());
|
|
|
|
GURL url(embedded_test_server()->GetURL("/title1.html"));
|
|
EXPECT_TRUE(NavigateToURL(shell(), url));
|
|
|
|
// Block every iframe in WillProcessResponse.
|
|
content::TestNavigationThrottleInserter throttle_inserter(
|
|
web_contents(),
|
|
base::BindLambdaForTesting(
|
|
[&](NavigationHandle* handle) -> std::unique_ptr<NavigationThrottle> {
|
|
auto throttle = std::make_unique<TestNavigationThrottle>(handle);
|
|
throttle->SetResponse(TestNavigationThrottle::WILL_PROCESS_RESPONSE,
|
|
TestNavigationThrottle::SYNCHRONOUS,
|
|
NavigationThrottle::CANCEL_AND_IGNORE);
|
|
|
|
return throttle;
|
|
}));
|
|
|
|
// Insert enough iframes so that if sockets are not properly released: there
|
|
// will not be enough of them to complete all navigations. As of today, only 6
|
|
// sockets can be used simultaneously. So using 7 iframes is enough. This test
|
|
// uses 33 as a margin.
|
|
EXPECT_TRUE(ExecJs(shell(), R"(
|
|
for(let i = 0; i<33; ++i) {
|
|
let iframe = document.createElement('iframe');
|
|
iframe.src = './download-test1.lib'
|
|
document.body.appendChild(iframe);
|
|
}
|
|
)"));
|
|
|
|
EXPECT_TRUE(WaitForLoadStop(web_contents()));
|
|
}
|
|
|
|
// Add header on redirect.
|
|
IN_PROC_BROWSER_TEST_F(NavigationBaseBrowserTest, AddRequestHeaderOnRedirect) {
|
|
net::test_server::ControllableHttpResponse response_1(embedded_test_server(),
|
|
"", true);
|
|
net::test_server::ControllableHttpResponse response_2(embedded_test_server(),
|
|
"", true);
|
|
ASSERT_TRUE(embedded_test_server()->Start());
|
|
|
|
content::TestNavigationThrottleInserter throttle_inserter(
|
|
web_contents(),
|
|
base::BindLambdaForTesting(
|
|
[](NavigationHandle* handle) -> std::unique_ptr<NavigationThrottle> {
|
|
auto throttle = std::make_unique<TestNavigationThrottle>(handle);
|
|
NavigationRequest* request = NavigationRequest::From(handle);
|
|
throttle->SetCallback(TestNavigationThrottle::WILL_REDIRECT_REQUEST,
|
|
base::BindLambdaForTesting([request]() {
|
|
request->SetRequestHeader("header_name",
|
|
"header_value");
|
|
}));
|
|
return throttle;
|
|
}));
|
|
|
|
// 1) There is no "header_name" header in the initial request.
|
|
shell()->LoadURL(embedded_test_server()->GetURL("/doc"));
|
|
response_1.WaitForRequest();
|
|
EXPECT_FALSE(
|
|
base::Contains(response_1.http_request()->headers, "header_name"));
|
|
response_1.Send(
|
|
"HTTP/1.1 302 Moved Temporarily\r\nLocation: /new_doc\r\n\r\n");
|
|
response_1.Done();
|
|
|
|
// 2) The header is added to the second request after the redirect.
|
|
response_2.WaitForRequest();
|
|
EXPECT_EQ("header_value",
|
|
response_2.http_request()->headers.at("header_name"));
|
|
|
|
// Redirect should not record a ReceivedResponse event.
|
|
EXPECT_EQ(0u, test_ukm_recorder()
|
|
.GetEntriesByName("Navigation.ReceivedResponse")
|
|
.size());
|
|
}
|
|
|
|
// Add header on request start, modify it on redirect.
|
|
IN_PROC_BROWSER_TEST_F(NavigationBaseBrowserTest,
|
|
AddRequestHeaderModifyOnRedirect) {
|
|
net::test_server::ControllableHttpResponse response_1(embedded_test_server(),
|
|
"", true);
|
|
net::test_server::ControllableHttpResponse response_2(embedded_test_server(),
|
|
"", true);
|
|
ASSERT_TRUE(embedded_test_server()->Start());
|
|
|
|
content::TestNavigationThrottleInserter throttle_inserter(
|
|
web_contents(),
|
|
base::BindLambdaForTesting(
|
|
[](NavigationHandle* handle) -> std::unique_ptr<NavigationThrottle> {
|
|
auto throttle = std::make_unique<TestNavigationThrottle>(handle);
|
|
NavigationRequest* request = NavigationRequest::From(handle);
|
|
throttle->SetCallback(TestNavigationThrottle::WILL_START_REQUEST,
|
|
base::BindLambdaForTesting([request]() {
|
|
request->SetRequestHeader("header_name",
|
|
"header_value");
|
|
}));
|
|
throttle->SetCallback(TestNavigationThrottle::WILL_REDIRECT_REQUEST,
|
|
base::BindLambdaForTesting([request]() {
|
|
request->SetRequestHeader("header_name",
|
|
"other_value");
|
|
}));
|
|
return throttle;
|
|
}));
|
|
|
|
// 1) The header is added to the initial request.
|
|
shell()->LoadURL(embedded_test_server()->GetURL("/doc"));
|
|
response_1.WaitForRequest();
|
|
EXPECT_EQ("header_value",
|
|
response_1.http_request()->headers.at("header_name"));
|
|
response_1.Send(
|
|
"HTTP/1.1 302 Moved Temporarily\r\nLocation: /new_doc\r\n\r\n");
|
|
response_1.Done();
|
|
|
|
// 2) The header is modified in the second request after the redirect.
|
|
response_2.WaitForRequest();
|
|
EXPECT_EQ("other_value",
|
|
response_2.http_request()->headers.at("header_name"));
|
|
}
|
|
|
|
// Add header on request start, remove it on redirect.
|
|
IN_PROC_BROWSER_TEST_F(NavigationBaseBrowserTest,
|
|
AddRequestHeaderRemoveOnRedirect) {
|
|
net::test_server::ControllableHttpResponse response_1(embedded_test_server(),
|
|
"", true);
|
|
net::test_server::ControllableHttpResponse response_2(embedded_test_server(),
|
|
"", true);
|
|
ASSERT_TRUE(embedded_test_server()->Start());
|
|
|
|
content::TestNavigationThrottleInserter throttle_inserter(
|
|
web_contents(),
|
|
base::BindLambdaForTesting(
|
|
[](NavigationHandle* handle) -> std::unique_ptr<NavigationThrottle> {
|
|
NavigationRequest* request = NavigationRequest::From(handle);
|
|
auto throttle = std::make_unique<TestNavigationThrottle>(handle);
|
|
throttle->SetCallback(TestNavigationThrottle::WILL_START_REQUEST,
|
|
base::BindLambdaForTesting([request]() {
|
|
request->SetRequestHeader("header_name",
|
|
"header_value");
|
|
}));
|
|
throttle->SetCallback(TestNavigationThrottle::WILL_REDIRECT_REQUEST,
|
|
base::BindLambdaForTesting([request]() {
|
|
request->RemoveRequestHeader("header_name");
|
|
}));
|
|
return throttle;
|
|
}));
|
|
|
|
// 1) The header is added to the initial request.
|
|
shell()->LoadURL(embedded_test_server()->GetURL("/doc"));
|
|
response_1.WaitForRequest();
|
|
EXPECT_EQ("header_value",
|
|
response_1.http_request()->headers.at("header_name"));
|
|
response_1.Send(
|
|
"HTTP/1.1 302 Moved Temporarily\r\nLocation: /new_doc\r\n\r\n");
|
|
response_1.Done();
|
|
|
|
// 2) The header is removed from the second request after the redirect.
|
|
response_2.WaitForRequest();
|
|
EXPECT_FALSE(
|
|
base::Contains(response_2.http_request()->headers, "header_name"));
|
|
}
|
|
|
|
// Name of header used by CorsInjectingUrlLoader.
|
|
const std::string kCorsHeaderName = "test-header";
|
|
|
|
// URLLoaderThrottle that stores the last value of |kCorsHeaderName|.
|
|
class CorsInjectingUrlLoader : public blink::URLLoaderThrottle {
|
|
public:
|
|
explicit CorsInjectingUrlLoader(std::string* last_cors_header_value)
|
|
: last_cors_header_value_(last_cors_header_value) {}
|
|
|
|
// blink::URLLoaderThrottle:
|
|
void WillStartRequest(network::ResourceRequest* request,
|
|
bool* defer) override {
|
|
if (!request->cors_exempt_headers.GetHeader(kCorsHeaderName,
|
|
last_cors_header_value_)) {
|
|
last_cors_header_value_->clear();
|
|
}
|
|
}
|
|
|
|
private:
|
|
// See |NavigationCorsExemptBrowserTest::last_cors_header_value_| for details.
|
|
raw_ptr<std::string> last_cors_header_value_;
|
|
};
|
|
|
|
// ContentBrowserClient responsible for creating CorsInjectingUrlLoader.
|
|
class CorsContentBrowserClient : public ContentBrowserTestContentBrowserClient {
|
|
public:
|
|
explicit CorsContentBrowserClient(std::string* last_cors_header_value)
|
|
: last_cors_header_value_(last_cors_header_value) {}
|
|
|
|
// ContentBrowserClient overrides:
|
|
std::vector<std::unique_ptr<blink::URLLoaderThrottle>>
|
|
CreateURLLoaderThrottles(
|
|
const network::ResourceRequest& request,
|
|
BrowserContext* browser_context,
|
|
const base::RepeatingCallback<WebContents*()>& wc_getter,
|
|
NavigationUIData* navigation_ui_data,
|
|
int frame_tree_node_id) override {
|
|
std::vector<std::unique_ptr<blink::URLLoaderThrottle>> throttles;
|
|
throttles.push_back(
|
|
std::make_unique<CorsInjectingUrlLoader>(last_cors_header_value_));
|
|
return throttles;
|
|
}
|
|
|
|
private:
|
|
// See |NavigationCorsExemptBrowserTest::last_cors_header_value_| for details.
|
|
raw_ptr<std::string> last_cors_header_value_;
|
|
};
|
|
|
|
class NavigationCorsExemptBrowserTest : public NavigationBaseBrowserTest {
|
|
public:
|
|
NavigationCorsExemptBrowserTest() = default;
|
|
|
|
protected:
|
|
const std::string& last_cors_header_value() const {
|
|
return last_cors_header_value_;
|
|
}
|
|
|
|
void SetUpCommandLine(base::CommandLine* command_line) override {
|
|
ShellContentBrowserClient::set_allow_any_cors_exempt_header_for_browser(
|
|
true);
|
|
NavigationBaseBrowserTest::SetUpCommandLine(command_line);
|
|
}
|
|
void SetUpOnMainThread() override {
|
|
cors_content_browser_client_ =
|
|
std::make_unique<CorsContentBrowserClient>(&last_cors_header_value_);
|
|
host_resolver()->AddRule("*", "127.0.0.1");
|
|
}
|
|
void TearDownOnMainThread() override {
|
|
cors_content_browser_client_.reset();
|
|
ShellContentBrowserClient::set_allow_any_cors_exempt_header_for_browser(
|
|
false);
|
|
}
|
|
|
|
private:
|
|
// Last value of kCorsHeaderName. Set by CorsInjectingUrlLoader.
|
|
std::string last_cors_header_value_;
|
|
std::unique_ptr<CorsContentBrowserClient> cors_content_browser_client_;
|
|
};
|
|
|
|
// Verifies a header added by way of SetRequestHeader() makes it into
|
|
// |cors_exempt_headers|.
|
|
IN_PROC_BROWSER_TEST_F(NavigationCorsExemptBrowserTest,
|
|
SetCorsExemptRequestHeader) {
|
|
net::test_server::ControllableHttpResponse response(embedded_test_server(),
|
|
"", true);
|
|
ASSERT_TRUE(embedded_test_server()->Start());
|
|
|
|
const std::string header_value = "value";
|
|
content::TestNavigationThrottleInserter throttle_inserter(
|
|
web_contents(),
|
|
base::BindLambdaForTesting([header_value](NavigationHandle* handle)
|
|
-> std::unique_ptr<NavigationThrottle> {
|
|
NavigationRequest* request = NavigationRequest::From(handle);
|
|
auto throttle = std::make_unique<TestNavigationThrottle>(handle);
|
|
throttle->SetCallback(
|
|
TestNavigationThrottle::WILL_START_REQUEST,
|
|
base::BindLambdaForTesting([request, header_value]() {
|
|
request->SetCorsExemptRequestHeader(kCorsHeaderName,
|
|
header_value);
|
|
}));
|
|
return throttle;
|
|
}));
|
|
shell()->LoadURL(embedded_test_server()->GetURL("/doc"));
|
|
response.WaitForRequest();
|
|
EXPECT_EQ(header_value, response.http_request()->headers.at(kCorsHeaderName));
|
|
EXPECT_EQ(header_value, last_cors_header_value());
|
|
}
|
|
|
|
struct NewWebContentsData {
|
|
NewWebContentsData() = default;
|
|
NewWebContentsData(NewWebContentsData&& other)
|
|
: new_web_contents(std::move(other.new_web_contents)),
|
|
manager(std::move(other.manager)) {}
|
|
|
|
std::unique_ptr<WebContents> new_web_contents;
|
|
std::unique_ptr<TestNavigationManager> manager;
|
|
};
|
|
|
|
class CreateWebContentsOnCrashObserver : public NotificationObserver {
|
|
public:
|
|
CreateWebContentsOnCrashObserver(const GURL& url,
|
|
WebContents* first_web_contents)
|
|
: url_(url), first_web_contents_(first_web_contents) {}
|
|
|
|
CreateWebContentsOnCrashObserver(const CreateWebContentsOnCrashObserver&) =
|
|
delete;
|
|
CreateWebContentsOnCrashObserver& operator=(
|
|
const CreateWebContentsOnCrashObserver&) = delete;
|
|
|
|
void Observe(int type,
|
|
const NotificationSource& source,
|
|
const NotificationDetails& details) override {
|
|
EXPECT_EQ(content::NOTIFICATION_RENDERER_PROCESS_CLOSED, type);
|
|
|
|
// Only do this once in the test.
|
|
if (observed_)
|
|
return;
|
|
observed_ = true;
|
|
|
|
WebContents::CreateParams new_contents_params(
|
|
first_web_contents_->GetBrowserContext(),
|
|
first_web_contents_->GetSiteInstance());
|
|
data_.new_web_contents = WebContents::Create(new_contents_params);
|
|
data_.manager = std::make_unique<TestNavigationManager>(
|
|
data_.new_web_contents.get(), url_);
|
|
NavigationController::LoadURLParams load_params(url_);
|
|
data_.new_web_contents->GetController().LoadURLWithParams(load_params);
|
|
}
|
|
|
|
NewWebContentsData TakeNewWebContentsData() { return std::move(data_); }
|
|
|
|
private:
|
|
NewWebContentsData data_;
|
|
bool observed_ = false;
|
|
|
|
GURL url_;
|
|
raw_ptr<WebContents> first_web_contents_;
|
|
|
|
ScopedAllowRendererCrashes scoped_allow_renderer_crashes_;
|
|
};
|
|
|
|
// This test simulates android webview's behavior in apps that handle
|
|
// renderer crashes by synchronously creating a new WebContents and loads
|
|
// the same page again. This reenters into content code.
|
|
IN_PROC_BROWSER_TEST_F(NavigationBrowserTest, WebViewRendererKillReload) {
|
|
// Webview is limited to one renderer.
|
|
RenderProcessHost::SetMaxRendererProcessCount(1u);
|
|
|
|
// Load a page into first webview.
|
|
GURL url(embedded_test_server()->GetURL("/simple_links.html"));
|
|
{
|
|
TestNavigationObserver observer(web_contents());
|
|
EXPECT_TRUE(NavigateToURL(web_contents(), url));
|
|
EXPECT_EQ(url, observer.last_navigation_url());
|
|
}
|
|
|
|
// Install a crash observer that synchronously creates and loads a new
|
|
// WebContents. Then crash the renderer which triggers the observer.
|
|
CreateWebContentsOnCrashObserver crash_observer(url, web_contents());
|
|
content::NotificationRegistrar notification_registrar;
|
|
notification_registrar.Add(&crash_observer,
|
|
content::NOTIFICATION_RENDERER_PROCESS_CLOSED,
|
|
content::NotificationService::AllSources());
|
|
NavigateToURLBlockUntilNavigationsComplete(web_contents(),
|
|
GetWebUIURL("crash"), 1);
|
|
|
|
// Wait for navigation in new WebContents to finish.
|
|
NewWebContentsData data = crash_observer.TakeNewWebContentsData();
|
|
ASSERT_TRUE(data.manager->WaitForNavigationFinished());
|
|
|
|
// Test passes if renderer is still alive.
|
|
EXPECT_TRUE(ExecJs(data.new_web_contents.get(), "true;"));
|
|
EXPECT_TRUE(
|
|
data.new_web_contents->GetPrimaryMainFrame()->IsRenderFrameLive());
|
|
EXPECT_EQ(
|
|
url, data.new_web_contents->GetPrimaryMainFrame()->GetLastCommittedURL());
|
|
}
|
|
|
|
// Test NavigationRequest::CheckAboutSrcDoc()
|
|
IN_PROC_BROWSER_TEST_F(NavigationBrowserTest, BlockedSrcDocBrowserInitiated) {
|
|
const char* about_srcdoc_urls[] = {"about:srcdoc", "about:srcdoc?foo",
|
|
"about:srcdoc#foo"};
|
|
// 1. Main frame navigations to about:srcdoc and its variations are blocked.
|
|
for (const char* url : about_srcdoc_urls) {
|
|
NavigationHandleObserver handle_observer(web_contents(), GURL(url));
|
|
EXPECT_FALSE(NavigateToURL(shell(), GURL(url)));
|
|
EXPECT_TRUE(handle_observer.has_committed());
|
|
EXPECT_TRUE(handle_observer.is_error());
|
|
EXPECT_EQ(net::ERR_INVALID_URL, handle_observer.net_error_code());
|
|
}
|
|
|
|
// 2. Subframe navigations to variations of about:srcdoc are not blocked.
|
|
for (const char* url : about_srcdoc_urls) {
|
|
GURL main_url =
|
|
embedded_test_server()->GetURL("/frame_tree/page_with_one_frame.html");
|
|
EXPECT_TRUE(NavigateToURL(shell(), main_url));
|
|
|
|
NavigationHandleObserver handle_observer(web_contents(), GURL(url));
|
|
shell()->LoadURLForFrame(GURL(url), "child-name-0",
|
|
ui::PAGE_TRANSITION_FORWARD_BACK);
|
|
EXPECT_TRUE(WaitForLoadStop(web_contents()));
|
|
EXPECT_TRUE(handle_observer.has_committed());
|
|
EXPECT_FALSE(handle_observer.is_error());
|
|
EXPECT_EQ(net::OK, handle_observer.net_error_code());
|
|
}
|
|
}
|
|
|
|
// Test NavigationRequest::CheckAboutSrcDoc().
|
|
IN_PROC_BROWSER_TEST_F(NavigationBrowserTest, BlockedSrcDocRendererInitiated) {
|
|
EXPECT_TRUE(
|
|
NavigateToURL(shell(), embedded_test_server()->GetURL("/title1.html")));
|
|
const char* about_srcdoc_urls[] = {"about:srcdoc", "about:srcdoc?foo",
|
|
"about:srcdoc#foo"};
|
|
|
|
// 1. Main frame navigations to about:srcdoc and its variations are blocked.
|
|
for (const char* url : about_srcdoc_urls) {
|
|
DidStartNavigationObserver start_observer(web_contents());
|
|
NavigationHandleObserver handle_observer(web_contents(), GURL(url));
|
|
// TODO(arthursonzogni): It shouldn't be possible to navigate to
|
|
// about:srcdoc by executing location.href= "about:srcdoc". Other web
|
|
// browsers like Firefox aren't allowing this.
|
|
EXPECT_TRUE(ExecJs(main_frame(), JsReplace("location.href = $1", url)));
|
|
start_observer.Wait();
|
|
WaitForLoadStop(web_contents());
|
|
EXPECT_TRUE(handle_observer.has_committed());
|
|
EXPECT_TRUE(handle_observer.is_error());
|
|
EXPECT_EQ(net::ERR_INVALID_URL, handle_observer.net_error_code());
|
|
}
|
|
|
|
// 2. Subframe navigations to variations of about:srcdoc are not blocked.
|
|
for (const char* url : about_srcdoc_urls) {
|
|
GURL main_url =
|
|
embedded_test_server()->GetURL("/frame_tree/page_with_one_frame.html");
|
|
EXPECT_TRUE(NavigateToURL(shell(), main_url));
|
|
|
|
DidStartNavigationObserver start_observer(web_contents());
|
|
NavigationHandleObserver handle_observer(web_contents(), GURL(url));
|
|
FrameTreeNode* subframe = main_frame()->child_at(0);
|
|
// TODO(arthursonzogni): It shouldn't be possible to navigate to
|
|
// about:srcdoc by executing location.href= "about:srcdoc". Other web
|
|
// browsers like Firefox aren't allowing this.
|
|
EXPECT_TRUE(ExecJs(subframe, JsReplace("location.href = $1", url)));
|
|
start_observer.Wait();
|
|
EXPECT_TRUE(WaitForLoadStop(web_contents()));
|
|
|
|
EXPECT_TRUE(handle_observer.has_committed());
|
|
EXPECT_FALSE(handle_observer.is_error());
|
|
EXPECT_EQ(net::OK, handle_observer.net_error_code());
|
|
}
|
|
}
|
|
|
|
// Test renderer initiated navigations to about:srcdoc are routed through the
|
|
// browser process. It means RenderFrameHostImpl::BeginNavigation() is called.
|
|
IN_PROC_BROWSER_TEST_F(NavigationBrowserTest, AboutSrcDocUsesBeginNavigation) {
|
|
GURL url(embedded_test_server()->GetURL("/title1.html"));
|
|
EXPECT_TRUE(NavigateToURL(shell(), url));
|
|
|
|
// If DidStartNavigation is called before DidCommitProvisionalLoad, then it
|
|
// means the navigation was driven by the browser process, otherwise by the
|
|
// renderer process. This tests it was driven by the browser process:
|
|
InterceptAndCancelDidCommitProvisionalLoad interceptor(web_contents());
|
|
DidStartNavigationObserver observer(web_contents());
|
|
|
|
EXPECT_TRUE(ExecJs(shell(), R"(
|
|
let iframe = document.createElement("iframe");
|
|
iframe.srcdoc = "foo"
|
|
document.body.appendChild(iframe);
|
|
)"));
|
|
|
|
observer.Wait(); // BeginNavigation is called.
|
|
interceptor.Wait(1); // DidCommitNavigation is called.
|
|
}
|
|
|
|
// Regression test for https://crbug.com/996044
|
|
// 1) Navigate an iframe to srcdoc (about:srcdoc);
|
|
// 2) Same-document navigation to about:srcdoc#1.
|
|
// 3) Same-document navigation to about:srcdoc#2.
|
|
// 4) history.back() to about:srcdoc#1.
|
|
IN_PROC_BROWSER_TEST_F(NavigationBrowserTest,
|
|
SrcDocWithFragmentHistoryNavigation) {
|
|
GURL url(embedded_test_server()->GetURL("/title1.html"));
|
|
EXPECT_TRUE(NavigateToURL(shell(), url));
|
|
|
|
// 1) Navigate an iframe to srcdoc (about:srcdoc)
|
|
EXPECT_TRUE(ExecJs(shell(), R"(
|
|
new Promise(async resolve => {
|
|
let iframe = document.createElement('iframe');
|
|
iframe.srcdoc = "test";
|
|
iframe.onload = resolve;
|
|
document.body.appendChild(iframe);
|
|
});
|
|
)"));
|
|
|
|
// 2) Same-document navigation to about:srcdoc#1.
|
|
// 3) Same-document navigation to about:srcdoc#2.
|
|
EXPECT_TRUE(ExecJs(shell(), R"(
|
|
let subwindow = document.querySelector('iframe').contentWindow;
|
|
subwindow.location.hash = "1";
|
|
subwindow.location.hash = "2";
|
|
)"));
|
|
|
|
// Inspect the session history.
|
|
NavigationControllerImpl& controller = web_contents()->GetController();
|
|
ASSERT_EQ(3, controller.GetEntryCount());
|
|
ASSERT_EQ(2, controller.GetCurrentEntryIndex());
|
|
|
|
FrameNavigationEntry* entry[3];
|
|
for (int i = 0; i < 3; ++i) {
|
|
entry[i] = controller.GetEntryAtIndex(i)
|
|
->root_node()
|
|
->children[0]
|
|
->frame_entry.get();
|
|
}
|
|
|
|
EXPECT_EQ(entry[0]->url(), "about:srcdoc");
|
|
EXPECT_EQ(entry[1]->url(), "about:srcdoc#1");
|
|
EXPECT_EQ(entry[2]->url(), "about:srcdoc#2");
|
|
|
|
// 4) history.back() to about:srcdoc#1.
|
|
EXPECT_TRUE(ExecJs(shell(), "history.back()"));
|
|
|
|
ASSERT_EQ(3, controller.GetEntryCount());
|
|
ASSERT_EQ(1, controller.GetCurrentEntryIndex());
|
|
}
|
|
|
|
// Regression test for https://crbug.com/996044.
|
|
// 1) Navigate an iframe to srcdoc (about:srcdoc).
|
|
// 2) Cross-document navigation to about:srcdoc?1.
|
|
// 3) Cross-document navigation to about:srcdoc?2.
|
|
// 4) history.back() to about:srcdoc?1.
|
|
IN_PROC_BROWSER_TEST_F(NavigationBrowserTest,
|
|
SrcDocWithQueryHistoryNavigation) {
|
|
GURL url(embedded_test_server()->GetURL("/title1.html"));
|
|
EXPECT_TRUE(NavigateToURL(shell(), url));
|
|
|
|
// 1) Navigate an iframe to srcdoc (about:srcdoc).
|
|
EXPECT_TRUE(ExecJs(shell(), R"(
|
|
new Promise(async resolve => {
|
|
let iframe = document.createElement('iframe');
|
|
iframe.srcdoc = "test";
|
|
iframe.onload = resolve;
|
|
document.body.appendChild(iframe);
|
|
});
|
|
)"));
|
|
|
|
// 2) Cross-document navigation to about:srcdoc?1.
|
|
{
|
|
TestNavigationManager commit_waiter(web_contents(), GURL("about:srcdoc?1"));
|
|
EXPECT_TRUE(ExecJs(shell(), R"(
|
|
let subwindow = document.querySelector('iframe').contentWindow;
|
|
subwindow.location.search = "1";
|
|
)"));
|
|
ASSERT_TRUE(commit_waiter.WaitForNavigationFinished());
|
|
}
|
|
|
|
// 3) Cross-document navigation to about:srcdoc?2.
|
|
{
|
|
TestNavigationManager commit_waiter(web_contents(), GURL("about:srcdoc?2"));
|
|
EXPECT_TRUE(ExecJs(shell(), R"(
|
|
let subwindow = document.querySelector('iframe').contentWindow;
|
|
subwindow.location.search = "2";
|
|
)"));
|
|
ASSERT_TRUE(commit_waiter.WaitForNavigationFinished());
|
|
}
|
|
|
|
// Inspect the session history.
|
|
NavigationControllerImpl& controller = web_contents()->GetController();
|
|
ASSERT_EQ(3, controller.GetEntryCount());
|
|
ASSERT_EQ(2, controller.GetCurrentEntryIndex());
|
|
|
|
FrameNavigationEntry* entry[3];
|
|
for (int i = 0; i < 3; ++i) {
|
|
entry[i] = controller.GetEntryAtIndex(i)
|
|
->root_node()
|
|
->children[0]
|
|
->frame_entry.get();
|
|
}
|
|
|
|
EXPECT_EQ(entry[0]->url(), "about:srcdoc");
|
|
EXPECT_EQ(entry[1]->url(), "about:srcdoc?1");
|
|
EXPECT_EQ(entry[2]->url(), "about:srcdoc?2");
|
|
|
|
// 4) history.back() to about:srcdoc#1.
|
|
EXPECT_TRUE(ExecJs(shell(), "history.back()"));
|
|
|
|
ASSERT_EQ(3, controller.GetEntryCount());
|
|
ASSERT_EQ(1, controller.GetCurrentEntryIndex());
|
|
}
|
|
|
|
// Make sure embedders are notified about visible URL changes in this scenario:
|
|
// 1. Navigate to A.
|
|
// 2. Navigate to B.
|
|
// 3. Add a forward entry in the history for later (same-document).
|
|
// 4. Start navigation to C.
|
|
// 5. Start history cross-document navigation, cancelling 4.
|
|
// 6. Start history same-document navigation, cancelling 5.
|
|
//
|
|
// Regression test for https://crbug.com/998284.
|
|
IN_PROC_BROWSER_TEST_F(NavigationBaseBrowserTest,
|
|
BackForwardInOldDocumentCancelPendingNavigation) {
|
|
// This test expects a new request to be made when navigating back, which is
|
|
// not happening with back-forward cache enabled.
|
|
// See BackForwardCacheBrowserTest.RestoreWhilePendingCommit which covers the
|
|
// same scenario for back-forward cache.
|
|
web_contents()->GetController().GetBackForwardCache().DisableForTesting(
|
|
BackForwardCacheImpl::TEST_REQUIRES_NO_CACHING);
|
|
|
|
using Response = net::test_server::ControllableHttpResponse;
|
|
Response response_A1(embedded_test_server(), "/A");
|
|
Response response_A2(embedded_test_server(), "/A");
|
|
Response response_B1(embedded_test_server(), "/B");
|
|
Response response_C1(embedded_test_server(), "/C");
|
|
|
|
ASSERT_TRUE(embedded_test_server()->Start());
|
|
|
|
GURL url_a = embedded_test_server()->GetURL("a.com", "/A");
|
|
GURL url_b = embedded_test_server()->GetURL("b.com", "/B");
|
|
GURL url_c = embedded_test_server()->GetURL("c.com", "/C");
|
|
|
|
EmbedderVisibleUrlTracker embedder_url_tracker;
|
|
web_contents()->SetDelegate(&embedder_url_tracker);
|
|
|
|
// 1. Navigate to A.
|
|
shell()->LoadURL(url_a);
|
|
response_A1.WaitForRequest();
|
|
response_A1.Send(non_cacheable_html_response);
|
|
response_A1.Done();
|
|
EXPECT_TRUE(WaitForLoadStop(web_contents()));
|
|
|
|
// 2. Navigate to B.
|
|
shell()->LoadURL(url_b);
|
|
response_B1.WaitForRequest();
|
|
response_B1.Send(non_cacheable_html_response);
|
|
response_B1.Done();
|
|
EXPECT_TRUE(WaitForLoadStop(web_contents()));
|
|
|
|
// 3. Add a forward entry in the history for later (same-document).
|
|
EXPECT_TRUE(ExecJs(web_contents(), R"(
|
|
history.pushState({},'');
|
|
history.back();
|
|
)"));
|
|
|
|
// 4. Start navigation to C.
|
|
{
|
|
EXPECT_EQ(url_b, web_contents()->GetVisibleURL());
|
|
EXPECT_EQ(url_b, embedder_url_tracker.url());
|
|
}
|
|
shell()->LoadURL(url_c);
|
|
// TODO(arthursonzogni): The embedder_url_tracker should update to url_c at
|
|
// this point, but we currently rely on FrameTreeNode::DidStopLoading for
|
|
// invalidation and it does not occur when a prior navigation is already in
|
|
// progress. The browser is still waiting on the same-document
|
|
// "history.back()" to complete.
|
|
{
|
|
EXPECT_EQ(url_c, web_contents()->GetVisibleURL());
|
|
EXPECT_EQ(url_b, embedder_url_tracker.url());
|
|
}
|
|
embedder_url_tracker.WaitUntilUrlInvalidated();
|
|
{
|
|
EXPECT_EQ(url_c, web_contents()->GetVisibleURL());
|
|
EXPECT_EQ(url_c, embedder_url_tracker.url());
|
|
}
|
|
response_C1.WaitForRequest();
|
|
|
|
// 5. Start history cross-document navigation, cancelling 4.
|
|
EXPECT_TRUE(ExecJs(web_contents(), "history.back()"));
|
|
{
|
|
EXPECT_EQ(url_b, web_contents()->GetVisibleURL());
|
|
EXPECT_EQ(url_b, embedder_url_tracker.url());
|
|
}
|
|
response_A2.WaitForRequest();
|
|
{
|
|
EXPECT_EQ(url_b, web_contents()->GetVisibleURL());
|
|
EXPECT_EQ(url_b, embedder_url_tracker.url());
|
|
}
|
|
|
|
// 6. Start history same-document navigation, cancelling 5.
|
|
EXPECT_TRUE(ExecJs(web_contents(), "history.forward()"));
|
|
{
|
|
EXPECT_EQ(url_b, web_contents()->GetVisibleURL());
|
|
EXPECT_EQ(url_b, embedder_url_tracker.url());
|
|
}
|
|
EXPECT_TRUE(WaitForLoadStop(web_contents()));
|
|
{
|
|
EXPECT_EQ(url_b, web_contents()->GetVisibleURL());
|
|
EXPECT_EQ(url_b, embedder_url_tracker.url());
|
|
}
|
|
}
|
|
|
|
// Regression test for https://crbug.com/999932.
|
|
IN_PROC_BROWSER_TEST_F(NavigationBaseBrowserTest, CanceledNavigationBug999932) {
|
|
using Response = net::test_server::ControllableHttpResponse;
|
|
Response response_A1(embedded_test_server(), "/A");
|
|
Response response_A2(embedded_test_server(), "/A");
|
|
Response response_B1(embedded_test_server(), "/B");
|
|
|
|
ASSERT_TRUE(embedded_test_server()->Start());
|
|
|
|
GURL url_a = embedded_test_server()->GetURL("a.com", "/A");
|
|
GURL url_b = embedded_test_server()->GetURL("b.com", "/B");
|
|
|
|
// 1. Navigate to A.
|
|
shell()->LoadURL(url_a);
|
|
response_A1.WaitForRequest();
|
|
response_A1.Send(non_cacheable_html_response);
|
|
response_A1.Done();
|
|
EXPECT_TRUE(WaitForLoadStop(web_contents()));
|
|
|
|
// 2. Start pending navigation to B.
|
|
shell()->LoadURL(url_b);
|
|
EXPECT_EQ(url_b, web_contents()->GetVisibleURL());
|
|
EXPECT_TRUE(web_contents()->GetController().GetPendingEntry());
|
|
|
|
// 3. Cancel (2) with renderer-initiated reload with a UserGesture.
|
|
EXPECT_TRUE(ExecJs(web_contents(), "location.reload()"));
|
|
EXPECT_EQ(url_a, web_contents()->GetVisibleURL());
|
|
EXPECT_FALSE(web_contents()->GetController().GetPendingEntry());
|
|
|
|
// 4. Cancel (3) using document.open();
|
|
EXPECT_TRUE(ExecJs(web_contents(), "document.open()"));
|
|
EXPECT_EQ(url_a, web_contents()->GetVisibleURL());
|
|
EXPECT_FALSE(web_contents()->GetController().GetPendingEntry());
|
|
}
|
|
|
|
// Regression test for https://crbug.com/1001283
|
|
// 1) Load main document with CSP: script-src 'none'
|
|
// 2) Open an about:srcdoc iframe. It inherits the CSP.
|
|
// 3) The iframe navigates elsewhere.
|
|
// 4) The iframe navigates back to about:srcdoc.
|
|
// Check Javascript is never allowed.
|
|
IN_PROC_BROWSER_TEST_F(NavigationBaseBrowserTest,
|
|
SrcDocCSPInheritedAfterSameSiteHistoryNavigation) {
|
|
using Response = net::test_server::ControllableHttpResponse;
|
|
Response main_document_response(embedded_test_server(), "/main_document");
|
|
|
|
ASSERT_TRUE(embedded_test_server()->Start());
|
|
|
|
GURL url_a = embedded_test_server()->GetURL("a.com", "/main_document");
|
|
GURL url_b = embedded_test_server()->GetURL("a.com", "/title1.html");
|
|
|
|
{
|
|
WebContentsConsoleObserver console_observer(web_contents());
|
|
console_observer.SetPattern("Refused to execute inline script *");
|
|
|
|
// 1) Load main document with CSP: script-src 'none'
|
|
// 2) Open an about:srcdoc iframe. It inherits the CSP from its parent.
|
|
shell()->LoadURL(url_a);
|
|
main_document_response.WaitForRequest();
|
|
main_document_response.Send(
|
|
"HTTP/1.1 200 OK\n"
|
|
"content-type: text/html; charset=UTF-8\n"
|
|
"Content-Security-Policy: script-src 'none'\n"
|
|
"\n"
|
|
"<iframe name='theiframe' srcdoc='"
|
|
" <script>"
|
|
" console.error(\"CSP failure\");"
|
|
" </script>"
|
|
"'>"
|
|
"</iframe>");
|
|
main_document_response.Done();
|
|
EXPECT_TRUE(WaitForLoadStop(web_contents()));
|
|
|
|
// Check Javascript was blocked the first time.
|
|
ASSERT_TRUE(console_observer.Wait());
|
|
}
|
|
|
|
// 3) The iframe navigates elsewhere.
|
|
shell()->LoadURLForFrame(url_b, "theiframe",
|
|
ui::PAGE_TRANSITION_MANUAL_SUBFRAME);
|
|
EXPECT_TRUE(WaitForLoadStop(web_contents()));
|
|
|
|
{
|
|
WebContentsConsoleObserver console_observer(web_contents());
|
|
console_observer.SetPattern("Refused to execute inline script *");
|
|
|
|
// 4) The iframe navigates back to about:srcdoc.
|
|
web_contents()->GetController().GoBack();
|
|
EXPECT_TRUE(WaitForLoadStop(web_contents()));
|
|
|
|
// Check Javascript was blocked the second time.
|
|
ASSERT_TRUE(console_observer.Wait());
|
|
}
|
|
}
|
|
|
|
IN_PROC_BROWSER_TEST_F(NavigationBaseBrowserTest,
|
|
SrcDocCSPInheritedAfterCrossSiteHistoryNavigation) {
|
|
using Response = net::test_server::ControllableHttpResponse;
|
|
Response main_document_response(embedded_test_server(), "/main_document");
|
|
|
|
ASSERT_TRUE(embedded_test_server()->Start());
|
|
|
|
GURL url_a = embedded_test_server()->GetURL("a.com", "/main_document");
|
|
GURL url_b = embedded_test_server()->GetURL("b.com", "/title1.html");
|
|
|
|
{
|
|
WebContentsConsoleObserver console_observer(web_contents());
|
|
console_observer.SetPattern("Refused to execute inline script *");
|
|
|
|
// 1) Load main document with CSP: script-src 'none'
|
|
// 2) Open an about:srcdoc iframe. It inherits the CSP from its parent.
|
|
shell()->LoadURL(url_a);
|
|
main_document_response.WaitForRequest();
|
|
main_document_response.Send(
|
|
"HTTP/1.1 200 OK\n"
|
|
"content-type: text/html; charset=UTF-8\n"
|
|
"Content-Security-Policy: script-src 'none'\n"
|
|
"\n"
|
|
"<iframe name='theiframe' srcdoc='"
|
|
" <script>"
|
|
" console.error(\"CSP failure\");"
|
|
" </script>"
|
|
"'>"
|
|
"</iframe>");
|
|
main_document_response.Done();
|
|
EXPECT_TRUE(WaitForLoadStop(web_contents()));
|
|
|
|
// Check Javascript was blocked the first time.
|
|
ASSERT_TRUE(console_observer.Wait());
|
|
}
|
|
|
|
// 3) The iframe navigates elsewhere.
|
|
shell()->LoadURLForFrame(url_b, "theiframe",
|
|
ui::PAGE_TRANSITION_MANUAL_SUBFRAME);
|
|
EXPECT_TRUE(WaitForLoadStop(web_contents()));
|
|
|
|
{
|
|
WebContentsConsoleObserver console_observer(web_contents());
|
|
console_observer.SetPattern("Refused to execute inline script *");
|
|
|
|
// 4) The iframe navigates back to about:srcdoc.
|
|
web_contents()->GetController().GoBack();
|
|
EXPECT_TRUE(WaitForLoadStop(web_contents()));
|
|
|
|
// Check Javascript was blocked the second time.
|
|
ASSERT_TRUE(console_observer.Wait());
|
|
}
|
|
}
|
|
|
|
// Test that NavigationRequest::GetNextPageUkmSourceId returns the eventual
|
|
// value of RenderFrameHost::GetPageUkmSourceId() --- unremarkable top-level
|
|
// navigation case.
|
|
IN_PROC_BROWSER_TEST_F(NavigationBrowserTest,
|
|
NavigationRequest_GetNextPageUkmSourceId_Basic) {
|
|
const GURL kUrl(embedded_test_server()->GetURL("/title1.html"));
|
|
TestNavigationManager manager(web_contents(), kUrl);
|
|
shell()->LoadURL(kUrl);
|
|
|
|
EXPECT_TRUE(manager.WaitForRequestStart());
|
|
ASSERT_TRUE(main_frame()->navigation_request());
|
|
|
|
ukm::SourceId nav_request_id =
|
|
main_frame()->navigation_request()->GetNextPageUkmSourceId();
|
|
|
|
EXPECT_TRUE(manager.WaitForResponse());
|
|
ASSERT_TRUE(manager.WaitForNavigationFinished());
|
|
EXPECT_EQ(current_frame_host()->GetPageUkmSourceId(), nav_request_id);
|
|
}
|
|
|
|
// Test that NavigationRequest::GetNextPageUkmSourceId returns the eventual
|
|
// value of RenderFrameHost::GetPageUkmSourceId() --- child frame case.
|
|
IN_PROC_BROWSER_TEST_F(NavigationBrowserTest,
|
|
NavigationRequest_GetNextPageUkmSourceId_ChildFrame) {
|
|
const GURL kUrl(
|
|
embedded_test_server()->GetURL("/frame_tree/page_with_one_frame.html"));
|
|
const GURL kDestUrl(embedded_test_server()->GetURL("/title1.html"));
|
|
EXPECT_TRUE(NavigateToURL(shell(), kUrl));
|
|
FrameTreeNode* subframe = main_frame()->child_at(0);
|
|
ASSERT_TRUE(subframe);
|
|
|
|
TestNavigationManager manager(web_contents(), kDestUrl);
|
|
EXPECT_TRUE(
|
|
ExecJs(subframe, JsReplace("location.href = $1", kDestUrl.spec())));
|
|
EXPECT_TRUE(manager.WaitForRequestStart());
|
|
ASSERT_TRUE(subframe->navigation_request());
|
|
|
|
ukm::SourceId nav_request_id =
|
|
subframe->navigation_request()->GetNextPageUkmSourceId();
|
|
|
|
EXPECT_TRUE(manager.WaitForResponse());
|
|
ASSERT_TRUE(manager.WaitForNavigationFinished());
|
|
|
|
// Should have the same page UKM ID in navigation as page post commit, and as
|
|
// the top-level frame.
|
|
EXPECT_EQ(current_frame_host()->GetPageUkmSourceId(), nav_request_id);
|
|
EXPECT_EQ(subframe->current_frame_host()->GetPageUkmSourceId(),
|
|
nav_request_id);
|
|
}
|
|
|
|
// Test that NavigationRequest::GetNextPageUkmSourceId returns the eventual
|
|
// value of RenderFrameHost::GetPageUkmSourceId() --- same document navigation.
|
|
IN_PROC_BROWSER_TEST_F(NavigationBrowserTest,
|
|
NavigationRequest_GetNextPageUkmSourceId_SameDocument) {
|
|
const GURL kUrl(embedded_test_server()->GetURL("/title1.html"));
|
|
const GURL kFragment(kUrl.Resolve("#here"));
|
|
EXPECT_TRUE(NavigateToURL(shell(), kUrl));
|
|
|
|
NavigationHandleObserver handle_observer(web_contents(), kFragment);
|
|
EXPECT_TRUE(
|
|
ExecJs(main_frame(), JsReplace("location.href = $1", kFragment.spec())));
|
|
EXPECT_TRUE(WaitForLoadStop(web_contents()));
|
|
|
|
EXPECT_TRUE(handle_observer.is_same_document());
|
|
EXPECT_EQ(current_frame_host()->GetPageUkmSourceId(),
|
|
handle_observer.next_page_ukm_source_id());
|
|
}
|
|
|
|
// Test that NavigationRequest::GetNextPageUkmSourceId returns the eventual
|
|
// value of RenderFrameHost::GetPageUkmSourceId() --- back navigation;
|
|
// this case matters because of back-forward cache.
|
|
IN_PROC_BROWSER_TEST_F(NavigationBrowserTest,
|
|
NavigationRequest_GetNextPageUkmSourceId_Back) {
|
|
const GURL kUrl1(embedded_test_server()->GetURL("a.com", "/title1.html"));
|
|
const GURL kUrl2(embedded_test_server()->GetURL("b.com", "/title2.html"));
|
|
EXPECT_TRUE(NavigateToURL(shell(), kUrl1));
|
|
EXPECT_TRUE(NavigateToURL(shell(), kUrl2));
|
|
|
|
NavigationHandleObserver handle_observer(web_contents(), kUrl1);
|
|
web_contents()->GetController().GoBack();
|
|
EXPECT_TRUE(WaitForLoadStop(web_contents()));
|
|
|
|
EXPECT_EQ(current_frame_host()->GetPageUkmSourceId(),
|
|
handle_observer.next_page_ukm_source_id());
|
|
}
|
|
|
|
// Tests for cookies. Provides an HTTPS server.
|
|
class NavigationCookiesBrowserTest : public NavigationBaseBrowserTest {
|
|
protected:
|
|
NavigationCookiesBrowserTest() = default;
|
|
net::EmbeddedTestServer* https_server() { return &https_server_; }
|
|
|
|
private:
|
|
void SetUpOnMainThread() override {
|
|
NavigationBaseBrowserTest::SetUpOnMainThread();
|
|
mock_cert_verifier_.mock_cert_verifier()->set_default_result(net::OK);
|
|
https_server()->AddDefaultHandlers(GetTestDataFilePath());
|
|
}
|
|
|
|
void SetUpCommandLine(base::CommandLine* command_line) override {
|
|
NavigationBaseBrowserTest::SetUpCommandLine(command_line);
|
|
mock_cert_verifier_.SetUpCommandLine(command_line);
|
|
}
|
|
|
|
void SetUpInProcessBrowserTestFixture() override {
|
|
NavigationBaseBrowserTest::SetUpInProcessBrowserTestFixture();
|
|
mock_cert_verifier_.SetUpInProcessBrowserTestFixture();
|
|
}
|
|
|
|
void TearDownInProcessBrowserTestFixture() override {
|
|
NavigationBaseBrowserTest::TearDownInProcessBrowserTestFixture();
|
|
mock_cert_verifier_.TearDownInProcessBrowserTestFixture();
|
|
}
|
|
|
|
content::ContentMockCertVerifier mock_cert_verifier_;
|
|
net::EmbeddedTestServer https_server_{net::EmbeddedTestServer::TYPE_HTTPS};
|
|
};
|
|
|
|
// Test how cookies are inherited in about:srcdoc iframes.
|
|
//
|
|
// Regression test: https://crbug.com/1003167.
|
|
IN_PROC_BROWSER_TEST_F(NavigationCookiesBrowserTest, CookiesInheritedSrcDoc) {
|
|
using Response = net::test_server::ControllableHttpResponse;
|
|
Response response_1(https_server(), "/response_1");
|
|
Response response_2(https_server(), "/response_2");
|
|
Response response_3(https_server(), "/response_3");
|
|
|
|
ASSERT_TRUE(https_server()->Start());
|
|
|
|
GURL url_a(https_server()->GetURL("a.com", "/title1.html"));
|
|
GURL url_b(https_server()->GetURL("b.com", "/title1.html"));
|
|
EXPECT_TRUE(NavigateToURL(shell(), url_a));
|
|
|
|
EXPECT_TRUE(ExecJs(shell(), R"(
|
|
let iframe = document.createElement("iframe");
|
|
iframe.srcdoc = "foo";
|
|
document.body.appendChild(iframe);
|
|
)"));
|
|
EXPECT_TRUE(WaitForLoadStop(web_contents()));
|
|
|
|
RenderFrameHostImpl* main_document = current_frame_host();
|
|
RenderFrameHostImpl* sub_document_1 =
|
|
main_document->child_at(0)->current_frame_host();
|
|
EXPECT_EQ(url::kAboutSrcdocURL, sub_document_1->GetLastCommittedURL());
|
|
EXPECT_EQ(url::Origin::Create(url_a),
|
|
sub_document_1->GetLastCommittedOrigin());
|
|
EXPECT_EQ(main_document->GetSiteInstance(),
|
|
sub_document_1->GetSiteInstance());
|
|
|
|
// 0. The default state doesn't contain any cookies.
|
|
EXPECT_EQ("", EvalJs(main_document, "document.cookie"));
|
|
EXPECT_EQ("", EvalJs(sub_document_1, "document.cookie"));
|
|
|
|
// 1. Set a cookie in the main document, it affects its child too.
|
|
EXPECT_TRUE(ExecJs(main_document, "document.cookie = 'a=0';"));
|
|
|
|
EXPECT_EQ("a=0", EvalJs(main_document, "document.cookie"));
|
|
EXPECT_EQ("a=0", EvalJs(sub_document_1, "document.cookie"));
|
|
|
|
// 2. Set a cookie in the child, it affects its parent too.
|
|
EXPECT_TRUE(ExecJs(sub_document_1, "document.cookie = 'b=0';"));
|
|
|
|
EXPECT_EQ("a=0; b=0", EvalJs(main_document, "document.cookie"));
|
|
EXPECT_EQ("a=0; b=0", EvalJs(sub_document_1, "document.cookie"));
|
|
|
|
// 3. Checks cookies are sent while requesting resources.
|
|
ExecuteScriptAsync(sub_document_1, "fetch('/response_1');");
|
|
response_1.WaitForRequest();
|
|
EXPECT_EQ("a=0; b=0", response_1.http_request()->headers.at("Cookie"));
|
|
|
|
// 4. Navigate the iframe elsewhere.
|
|
EXPECT_TRUE(ExecJs(sub_document_1, JsReplace("location.href = $1", url_b)));
|
|
EXPECT_TRUE(WaitForLoadStop(web_contents()));
|
|
RenderFrameHostImpl* sub_document_2 =
|
|
main_document->child_at(0)->current_frame_host();
|
|
|
|
EXPECT_EQ("a=0; b=0", EvalJs(main_document, "document.cookie"));
|
|
EXPECT_EQ("", EvalJs(sub_document_2, "document.cookie"));
|
|
|
|
// 5. Set a cookie in the main document. It doesn't affect its child.
|
|
EXPECT_TRUE(ExecJs(main_document, "document.cookie = 'c=0';"));
|
|
|
|
EXPECT_EQ("a=0; b=0; c=0", EvalJs(main_document, "document.cookie"));
|
|
EXPECT_EQ("", EvalJs(sub_document_2, "document.cookie"));
|
|
|
|
// 6. Set a cookie in the child. It doesn't affect its parent.
|
|
EXPECT_TRUE(ExecJs(sub_document_2,
|
|
"document.cookie = 'd=0; SameSite=none; Secure';"));
|
|
|
|
EXPECT_EQ("a=0; b=0; c=0", EvalJs(main_document, "document.cookie"));
|
|
EXPECT_EQ("d=0", EvalJs(sub_document_2, "document.cookie"));
|
|
|
|
// 7. Checks cookies are sent while requesting resources.
|
|
ExecuteScriptAsync(sub_document_2, "fetch('/response_2');");
|
|
response_2.WaitForRequest();
|
|
EXPECT_EQ("d=0", response_2.http_request()->headers.at("Cookie"));
|
|
|
|
// 8. Navigate the iframe back to about:srcdoc.
|
|
web_contents()->GetController().GoBack();
|
|
EXPECT_TRUE(WaitForLoadStop(web_contents()));
|
|
RenderFrameHostImpl* sub_document_3 =
|
|
main_document->child_at(0)->current_frame_host();
|
|
EXPECT_EQ(url_a, main_document->GetLastCommittedURL());
|
|
EXPECT_EQ(url::kAboutSrcdocURL, sub_document_3->GetLastCommittedURL());
|
|
EXPECT_EQ(url::Origin::Create(url_a),
|
|
sub_document_3->GetLastCommittedOrigin());
|
|
EXPECT_EQ(main_document->GetSiteInstance(),
|
|
sub_document_3->GetSiteInstance());
|
|
|
|
EXPECT_EQ("a=0; b=0; c=0", EvalJs(main_document, "document.cookie"));
|
|
EXPECT_EQ("a=0; b=0; c=0", EvalJs(sub_document_3, "document.cookie"));
|
|
|
|
// 9. Set cookie in the main document. It should be inherited by the child.
|
|
EXPECT_TRUE(ExecJs(main_document, "document.cookie = 'e=0';"));
|
|
|
|
EXPECT_EQ("a=0; b=0; c=0; e=0", EvalJs(main_document, "document.cookie"));
|
|
EXPECT_EQ("a=0; b=0; c=0; e=0", EvalJs(sub_document_3, "document.cookie"));
|
|
|
|
// 11. Set cookie in the child document. It should be reflected on its parent.
|
|
EXPECT_TRUE(ExecJs(sub_document_3, "document.cookie = 'f=0';"));
|
|
|
|
EXPECT_EQ("a=0; b=0; c=0; e=0; f=0",
|
|
EvalJs(main_document, "document.cookie"));
|
|
EXPECT_EQ("a=0; b=0; c=0; e=0; f=0",
|
|
EvalJs(sub_document_3, "document.cookie"));
|
|
|
|
// 12. Checks cookies are sent while requesting resources.
|
|
ExecuteScriptAsync(sub_document_3, "fetch('/response_3');");
|
|
response_3.WaitForRequest();
|
|
EXPECT_EQ("a=0; b=0; c=0; e=0; f=0",
|
|
response_3.http_request()->headers.at("Cookie"));
|
|
}
|
|
|
|
// Test how cookies are inherited in about:blank iframes.
|
|
IN_PROC_BROWSER_TEST_F(NavigationCookiesBrowserTest,
|
|
CookiesInheritedAboutBlank) {
|
|
// This test expects several cross-site navigation to happen.
|
|
if (!AreAllSitesIsolatedForTesting())
|
|
return;
|
|
|
|
using Response = net::test_server::ControllableHttpResponse;
|
|
Response response_1(https_server(), "/response_1");
|
|
Response response_2(https_server(), "/response_2");
|
|
Response response_3(https_server(), "/response_3");
|
|
|
|
ASSERT_TRUE(https_server()->Start());
|
|
|
|
GURL url_a(https_server()->GetURL("a.com", "/title1.html"));
|
|
GURL url_b(https_server()->GetURL("b.com", "/title1.html"));
|
|
EXPECT_TRUE(NavigateToURL(shell(), url_a));
|
|
|
|
EXPECT_TRUE(
|
|
ExecJs(shell(), JsReplace("let iframe = document.createElement('iframe');"
|
|
"iframe.src = $1;"
|
|
"document.body.appendChild(iframe);",
|
|
url_b)));
|
|
EXPECT_TRUE(WaitForLoadStop(web_contents()));
|
|
EXPECT_TRUE(ExecJs(shell(), R"(
|
|
document.querySelector('iframe').src = "about:blank"
|
|
)"));
|
|
EXPECT_TRUE(WaitForLoadStop(web_contents()));
|
|
|
|
RenderFrameHostImpl* main_document = current_frame_host();
|
|
RenderFrameHostImpl* sub_document_1 =
|
|
main_document->child_at(0)->current_frame_host();
|
|
|
|
EXPECT_EQ(url::kAboutBlankURL, sub_document_1->GetLastCommittedURL());
|
|
EXPECT_EQ(url::Origin::Create(url_a),
|
|
sub_document_1->GetLastCommittedOrigin());
|
|
EXPECT_EQ(main_document->GetSiteInstance(),
|
|
sub_document_1->GetSiteInstance());
|
|
|
|
// 0. The default state doesn't contain any cookies.
|
|
EXPECT_EQ("", EvalJs(main_document, "document.cookie"));
|
|
EXPECT_EQ("", EvalJs(sub_document_1, "document.cookie"));
|
|
|
|
// 1. Set a cookie in the main document, it affects its child too.
|
|
EXPECT_TRUE(ExecJs(main_document, "document.cookie = 'a=0';"));
|
|
|
|
EXPECT_EQ("a=0", EvalJs(main_document, "document.cookie"));
|
|
EXPECT_EQ("a=0", EvalJs(sub_document_1, "document.cookie"));
|
|
|
|
// 2. Set a cookie in the child, it affects its parent too.
|
|
EXPECT_TRUE(ExecJs(sub_document_1, "document.cookie = 'b=0';"));
|
|
|
|
EXPECT_EQ("a=0; b=0", EvalJs(main_document, "document.cookie"));
|
|
EXPECT_EQ("a=0; b=0", EvalJs(sub_document_1, "document.cookie"));
|
|
|
|
// 3. Checks cookies are sent while requesting resources.
|
|
GURL url_response_1 = https_server()->GetURL("a.com", "/response_1");
|
|
ExecuteScriptAsync(sub_document_1, JsReplace("fetch($1)", url_response_1));
|
|
response_1.WaitForRequest();
|
|
EXPECT_EQ("a=0; b=0", response_1.http_request()->headers.at("Cookie"));
|
|
|
|
// 4. Navigate the iframe elsewhere.
|
|
EXPECT_TRUE(ExecJs(sub_document_1, JsReplace("location.href = $1", url_b)));
|
|
EXPECT_TRUE(WaitForLoadStop(web_contents()));
|
|
RenderFrameHostImpl* sub_document_2 =
|
|
main_document->child_at(0)->current_frame_host();
|
|
|
|
EXPECT_EQ("a=0; b=0", EvalJs(main_document, "document.cookie"));
|
|
EXPECT_EQ("", EvalJs(sub_document_2, "document.cookie"));
|
|
|
|
// 5. Set a cookie in the main document. It doesn't affect its child.
|
|
EXPECT_TRUE(ExecJs(main_document, "document.cookie = 'c=0';"));
|
|
|
|
EXPECT_EQ("a=0; b=0; c=0", EvalJs(main_document, "document.cookie"));
|
|
EXPECT_EQ("", EvalJs(sub_document_2, "document.cookie"));
|
|
|
|
// 6. Set a cookie in the child. It doesn't affect its parent.
|
|
EXPECT_TRUE(ExecJs(sub_document_2,
|
|
"document.cookie = 'd=0; SameSite=none; Secure';"));
|
|
|
|
EXPECT_EQ("a=0; b=0; c=0", EvalJs(main_document, "document.cookie"));
|
|
EXPECT_EQ("d=0", EvalJs(sub_document_2, "document.cookie"));
|
|
|
|
// 7. Checks cookies are sent while requesting resources.
|
|
ExecuteScriptAsync(sub_document_2, "fetch('/response_2');");
|
|
response_2.WaitForRequest();
|
|
EXPECT_EQ("d=0", response_2.http_request()->headers.at("Cookie"));
|
|
|
|
// 8. Navigate the iframe back to about:blank.
|
|
web_contents()->GetController().GoBack();
|
|
EXPECT_TRUE(WaitForLoadStop(web_contents()));
|
|
RenderFrameHostImpl* sub_document_3 =
|
|
main_document->child_at(0)->current_frame_host();
|
|
EXPECT_EQ(url_a, main_document->GetLastCommittedURL());
|
|
EXPECT_EQ(url::kAboutBlankURL, sub_document_3->GetLastCommittedURL());
|
|
EXPECT_EQ(url::Origin::Create(url_a),
|
|
sub_document_3->GetLastCommittedOrigin());
|
|
EXPECT_EQ(main_document->GetSiteInstance(),
|
|
sub_document_3->GetSiteInstance());
|
|
|
|
EXPECT_EQ("a=0; b=0; c=0", EvalJs(main_document, "document.cookie"));
|
|
EXPECT_EQ("a=0; b=0; c=0", EvalJs(sub_document_3, "document.cookie"));
|
|
|
|
// 9. Set cookie in the main document. It affects the iframe.
|
|
EXPECT_TRUE(ExecJs(main_document, "document.cookie = 'e=0';"));
|
|
|
|
EXPECT_EQ("a=0; b=0; c=0; e=0", EvalJs(main_document, "document.cookie"));
|
|
EXPECT_EQ("a=0; b=0; c=0; e=0", EvalJs(sub_document_3, "document.cookie"));
|
|
|
|
// 10. Set cookie in the iframe. It affects the main frame.
|
|
EXPECT_TRUE(ExecJs(sub_document_3, "document.cookie = 'f=0';"));
|
|
EXPECT_EQ("a=0; b=0; c=0; e=0; f=0",
|
|
EvalJs(main_document, "document.cookie"));
|
|
EXPECT_EQ("a=0; b=0; c=0; e=0; f=0",
|
|
EvalJs(sub_document_3, "document.cookie"));
|
|
|
|
// 11. Even if document.cookie is empty, cookies are sent.
|
|
ExecuteScriptAsync(sub_document_3, "fetch('/response_3');");
|
|
response_3.WaitForRequest();
|
|
EXPECT_EQ("a=0; b=0; c=0; e=0; f=0",
|
|
response_3.http_request()->headers.at("Cookie"));
|
|
}
|
|
|
|
// Test how cookies are inherited in about:blank iframes.
|
|
//
|
|
// This is a variation of
|
|
// NavigationCookiesBrowserTest.CookiesInheritedAboutBlank. Instead of
|
|
// requesting an history navigation, a new navigation is requested from the main
|
|
// frame. The navigation is cross-site instead of being same-site.
|
|
IN_PROC_BROWSER_TEST_F(NavigationCookiesBrowserTest,
|
|
CookiesInheritedAboutBlank2) {
|
|
// This test expects several cross-site navigation to happen.
|
|
if (!AreAllSitesIsolatedForTesting())
|
|
return;
|
|
|
|
using Response = net::test_server::ControllableHttpResponse;
|
|
Response response_1(https_server(), "/response_1");
|
|
Response response_2(https_server(), "/response_2");
|
|
Response response_3(https_server(), "/response_3");
|
|
|
|
ASSERT_TRUE(https_server()->Start());
|
|
|
|
GURL url_a(https_server()->GetURL("a.com", "/title1.html"));
|
|
GURL url_b(https_server()->GetURL("b.com", "/title1.html"));
|
|
EXPECT_TRUE(NavigateToURL(shell(), url_a));
|
|
|
|
EXPECT_TRUE(
|
|
ExecJs(shell(), JsReplace("let iframe = document.createElement('iframe');"
|
|
"iframe.src = $1;"
|
|
"document.body.appendChild(iframe);",
|
|
url_b)));
|
|
EXPECT_TRUE(WaitForLoadStop(web_contents()));
|
|
EXPECT_TRUE(ExecJs(shell(), R"(
|
|
document.querySelector('iframe').src = "about:blank"
|
|
)"));
|
|
EXPECT_TRUE(WaitForLoadStop(web_contents()));
|
|
|
|
RenderFrameHostImpl* main_document = current_frame_host();
|
|
RenderFrameHostImpl* sub_document_1 =
|
|
main_document->child_at(0)->current_frame_host();
|
|
EXPECT_EQ(url::kAboutBlankURL, sub_document_1->GetLastCommittedURL());
|
|
EXPECT_EQ(url::Origin::Create(url_a),
|
|
sub_document_1->GetLastCommittedOrigin());
|
|
EXPECT_EQ(main_document->GetSiteInstance(),
|
|
sub_document_1->GetSiteInstance());
|
|
|
|
// 0. The default state doesn't contain any cookies.
|
|
EXPECT_EQ("", EvalJs(main_document, "document.cookie"));
|
|
EXPECT_EQ("", EvalJs(sub_document_1, "document.cookie"));
|
|
|
|
// 1. Set a cookie in the main document, it affects its child too.
|
|
EXPECT_TRUE(ExecJs(main_document, "document.cookie = 'a=0';"));
|
|
|
|
EXPECT_EQ("a=0", EvalJs(main_document, "document.cookie"));
|
|
EXPECT_EQ("a=0", EvalJs(sub_document_1, "document.cookie"));
|
|
|
|
// 2. Set a cookie in the child, it affects its parent too.
|
|
EXPECT_TRUE(ExecJs(sub_document_1, "document.cookie = 'b=0';"));
|
|
|
|
EXPECT_EQ("a=0; b=0", EvalJs(main_document, "document.cookie"));
|
|
EXPECT_EQ("a=0; b=0", EvalJs(sub_document_1, "document.cookie"));
|
|
|
|
// 3. Checks cookies are sent while requesting resources.
|
|
ExecuteScriptAsync(sub_document_1, "fetch('/response_1');");
|
|
response_1.WaitForRequest();
|
|
EXPECT_EQ("a=0; b=0", response_1.http_request()->headers.at("Cookie"));
|
|
|
|
// 4. Navigate the iframe elsewhere.
|
|
EXPECT_TRUE(ExecJs(sub_document_1, JsReplace("location.href = $1", url_b)));
|
|
EXPECT_TRUE(WaitForLoadStop(web_contents()));
|
|
RenderFrameHostImpl* sub_document_2 =
|
|
main_document->child_at(0)->current_frame_host();
|
|
|
|
EXPECT_EQ("a=0; b=0", EvalJs(main_document, "document.cookie"));
|
|
EXPECT_EQ("", EvalJs(sub_document_2, "document.cookie"));
|
|
|
|
// 5. Set a cookie in the main document. It doesn't affect its child.
|
|
EXPECT_TRUE(ExecJs(main_document, "document.cookie = 'c=0';"));
|
|
|
|
EXPECT_EQ("a=0; b=0; c=0", EvalJs(main_document, "document.cookie"));
|
|
EXPECT_EQ("", EvalJs(sub_document_2, "document.cookie"));
|
|
|
|
// 6. Set a cookie in the child. It doesn't affect its parent.
|
|
EXPECT_TRUE(ExecJs(sub_document_2,
|
|
"document.cookie = 'd=0; SameSite=none; Secure';"));
|
|
|
|
EXPECT_EQ("a=0; b=0; c=0", EvalJs(main_document, "document.cookie"));
|
|
EXPECT_EQ("d=0", EvalJs(sub_document_2, "document.cookie"));
|
|
|
|
// 7. Checks cookies are sent while requesting resources.
|
|
ExecuteScriptAsync(sub_document_2, "fetch('/response_2');");
|
|
response_2.WaitForRequest();
|
|
EXPECT_EQ("d=0", response_2.http_request()->headers.at("Cookie"));
|
|
|
|
// 8. Ask the top-level, a.com frame to navigate the subframe to about:blank.
|
|
EXPECT_TRUE(ExecJs(shell(), R"(
|
|
document.querySelector('iframe').src = "about:blank";
|
|
)"));
|
|
EXPECT_TRUE(WaitForLoadStop(web_contents()));
|
|
RenderFrameHostImpl* sub_document_3 =
|
|
main_document->child_at(0)->current_frame_host();
|
|
EXPECT_EQ(url::kAboutBlankURL, sub_document_3->GetLastCommittedURL());
|
|
EXPECT_EQ(url::Origin::Create(url_a),
|
|
sub_document_3->GetLastCommittedOrigin());
|
|
EXPECT_EQ(main_document->GetSiteInstance(),
|
|
sub_document_3->GetSiteInstance());
|
|
|
|
EXPECT_EQ("a=0; b=0; c=0", EvalJs(main_document, "document.cookie"));
|
|
EXPECT_EQ("a=0; b=0; c=0", EvalJs(sub_document_3, "document.cookie"));
|
|
|
|
// 9. Set cookie in the main document.
|
|
EXPECT_TRUE(ExecJs(main_document, "document.cookie = 'e=0';"));
|
|
|
|
EXPECT_EQ("a=0; b=0; c=0; e=0", EvalJs(main_document, "document.cookie"));
|
|
EXPECT_EQ("a=0; b=0; c=0; e=0", EvalJs(sub_document_3, "document.cookie"));
|
|
|
|
// 10. Set cookie in the child document.
|
|
EXPECT_TRUE(ExecJs(sub_document_3, "document.cookie = 'f=0';"));
|
|
|
|
EXPECT_EQ("a=0; b=0; c=0; e=0; f=0",
|
|
EvalJs(main_document, "document.cookie"));
|
|
EXPECT_EQ("a=0; b=0; c=0; e=0; f=0",
|
|
EvalJs(sub_document_3, "document.cookie"));
|
|
|
|
// 11. Checks cookies are sent while requesting resources.
|
|
ExecuteScriptAsync(sub_document_3, "fetch('/response_3');");
|
|
response_3.WaitForRequest();
|
|
EXPECT_EQ("a=0; b=0; c=0; e=0; f=0",
|
|
response_3.http_request()->headers.at("Cookie"));
|
|
}
|
|
|
|
// Test how cookies are inherited in data-URL iframes.
|
|
IN_PROC_BROWSER_TEST_F(NavigationCookiesBrowserTest, CookiesInheritedDataUrl) {
|
|
using Response = net::test_server::ControllableHttpResponse;
|
|
Response response_1(https_server(), "/response_1");
|
|
Response response_2(https_server(), "/response_2");
|
|
Response response_3(https_server(), "/response_3");
|
|
|
|
ASSERT_TRUE(https_server()->Start());
|
|
|
|
GURL url_a(https_server()->GetURL("a.com", "/title1.html"));
|
|
GURL url_b(https_server()->GetURL("b.com", "/title1.html"));
|
|
EXPECT_TRUE(NavigateToURL(shell(), url_a));
|
|
|
|
EXPECT_TRUE(ExecJs(shell(), R"(
|
|
let iframe = document.createElement("iframe");
|
|
iframe.src = "data:text/html,";
|
|
document.body.appendChild(iframe);
|
|
)"));
|
|
EXPECT_TRUE(WaitForLoadStop(web_contents()));
|
|
|
|
RenderFrameHostImpl* main_document = current_frame_host();
|
|
RenderFrameHostImpl* sub_document_1 =
|
|
main_document->child_at(0)->current_frame_host();
|
|
EXPECT_EQ("data:text/html,", sub_document_1->GetLastCommittedURL());
|
|
EXPECT_TRUE(sub_document_1->GetLastCommittedOrigin().opaque());
|
|
EXPECT_EQ(main_document->GetSiteInstance(),
|
|
sub_document_1->GetSiteInstance());
|
|
|
|
// 1. Writing a cookie inside a data-URL document is forbidden.
|
|
{
|
|
WebContentsConsoleObserver console_observer(web_contents());
|
|
console_observer.SetPattern(
|
|
"*Failed to set the 'cookie' property on 'Document': Cookies are "
|
|
"disabled inside 'data:' URLs.*");
|
|
ExecuteScriptAsync(sub_document_1, "document.cookie = 'a=0';");
|
|
ASSERT_TRUE(console_observer.Wait());
|
|
}
|
|
|
|
// 2. Reading a cookie inside a data-URL document is forbidden.
|
|
{
|
|
WebContentsConsoleObserver console_observer(web_contents());
|
|
console_observer.SetPattern(
|
|
"*Failed to read the 'cookie' property from 'Document': Cookies are "
|
|
"disabled inside 'data:' URLs.*");
|
|
ExecuteScriptAsync(sub_document_1, "document.cookie");
|
|
ASSERT_TRUE(console_observer.Wait());
|
|
}
|
|
|
|
// 3. Set cookie in the main document. No cookies are sent when requested from
|
|
// the data-URL.
|
|
EXPECT_TRUE(ExecJs(main_document, "document.cookie = 'a=0;SameSite=Lax'"));
|
|
EXPECT_TRUE(ExecJs(main_document, "document.cookie = 'b=0;SameSite=Strict'"));
|
|
GURL url_response_1 = https_server()->GetURL("a.com", "/response_1");
|
|
ExecuteScriptAsync(sub_document_1, JsReplace("fetch($1)", url_response_1));
|
|
response_1.WaitForRequest();
|
|
EXPECT_EQ(0u, response_1.http_request()->headers.count("Cookie"));
|
|
|
|
// 4. Navigate the iframe elsewhere and back using history navigation.
|
|
EXPECT_TRUE(ExecJs(sub_document_1, JsReplace("location.href = $1", url_b)));
|
|
EXPECT_TRUE(WaitForLoadStop(web_contents()));
|
|
web_contents()->GetController().GoBack();
|
|
EXPECT_TRUE(WaitForLoadStop(web_contents()));
|
|
RenderFrameHostImpl* sub_document_2 =
|
|
main_document->child_at(0)->current_frame_host();
|
|
EXPECT_EQ(url_a, main_document->GetLastCommittedURL());
|
|
EXPECT_EQ("data:text/html,", sub_document_2->GetLastCommittedURL());
|
|
EXPECT_TRUE(sub_document_2->GetLastCommittedOrigin().opaque());
|
|
EXPECT_EQ(main_document->GetSiteInstance(),
|
|
sub_document_2->GetSiteInstance());
|
|
|
|
// 5. Writing a cookie inside a data-URL document is still forbidden.
|
|
{
|
|
WebContentsConsoleObserver console_observer(web_contents());
|
|
console_observer.SetPattern(
|
|
"*Failed to set the 'cookie' property on 'Document': Cookies are "
|
|
"disabled inside 'data:' URLs.*");
|
|
ExecuteScriptAsync(sub_document_2, "document.cookie = 'c=0';");
|
|
ASSERT_TRUE(console_observer.Wait());
|
|
}
|
|
|
|
// 6. Reading a cookie inside a data-URL document is still forbidden.
|
|
{
|
|
WebContentsConsoleObserver console_observer(web_contents());
|
|
console_observer.SetPattern(
|
|
"*Failed to read the 'cookie' property from 'Document': Cookies are "
|
|
"disabled inside 'data:' URLs.*");
|
|
ExecuteScriptAsync(sub_document_2, "document.cookie");
|
|
ASSERT_TRUE(console_observer.Wait());
|
|
}
|
|
|
|
// 7. No cookies are sent when requested from the data-URL.
|
|
GURL url_response_2 = https_server()->GetURL("a.com", "/response_2");
|
|
ExecuteScriptAsync(sub_document_2, JsReplace("fetch($1)", url_response_2));
|
|
response_2.WaitForRequest();
|
|
EXPECT_EQ(0u, response_2.http_request()->headers.count("Cookie"));
|
|
}
|
|
|
|
// Tests for validating URL rewriting behavior like chrome://history to
|
|
// chrome-native://history.
|
|
class NavigationUrlRewriteBrowserTest : public NavigationBaseBrowserTest {
|
|
protected:
|
|
static constexpr const char* kRewriteURL = "http://a.com/rewrite";
|
|
static constexpr const char* kNoAccessScheme = "no-access";
|
|
static constexpr const char* kNoAccessURL = "no-access://testing/";
|
|
|
|
class BrowserClient : public ContentBrowserTestContentBrowserClient {
|
|
public:
|
|
void BrowserURLHandlerCreated(BrowserURLHandler* handler) override {
|
|
handler->AddHandlerPair(RewriteUrl,
|
|
BrowserURLHandlerImpl::null_handler());
|
|
fake_url_loader_factory_ = std::make_unique<FakeNetworkURLLoaderFactory>(
|
|
"HTTP/1.1 200 OK\nContent-Type: text/html\n\n", "This is a test",
|
|
/* network_accessed */ true, net::OK);
|
|
}
|
|
|
|
void RegisterNonNetworkNavigationURLLoaderFactories(
|
|
int frame_tree_node_id,
|
|
ukm::SourceIdObj ukm_source_id,
|
|
NonNetworkURLLoaderFactoryMap* factories) override {
|
|
mojo::PendingRemote<network::mojom::URLLoaderFactory> pending_remote;
|
|
fake_url_loader_factory_->Clone(
|
|
pending_remote.InitWithNewPipeAndPassReceiver());
|
|
factories->emplace(std::string(kNoAccessScheme),
|
|
std::move(pending_remote));
|
|
}
|
|
|
|
static bool RewriteUrl(GURL* url, BrowserContext* browser_context) {
|
|
if (*url == GURL(kRewriteURL)) {
|
|
*url = GURL(kNoAccessURL);
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
private:
|
|
std::unique_ptr<FakeNetworkURLLoaderFactory> fake_url_loader_factory_;
|
|
};
|
|
|
|
NavigationUrlRewriteBrowserTest() {
|
|
url::AddStandardScheme(kNoAccessScheme, url::SCHEME_WITH_HOST);
|
|
url::AddNoAccessScheme(kNoAccessScheme);
|
|
|
|
// This test needs to use an unassigned SiteInstance for kNoAccessScheme,
|
|
// which requires adding it as an empty document scheme.
|
|
url::AddEmptyDocumentScheme(kNoAccessScheme);
|
|
}
|
|
|
|
void SetUpOnMainThread() override {
|
|
NavigationBaseBrowserTest::SetUpOnMainThread();
|
|
ASSERT_TRUE(embedded_test_server()->Start());
|
|
|
|
browser_client_ = std::make_unique<BrowserClient>();
|
|
}
|
|
|
|
void TearDownOnMainThread() override {
|
|
browser_client_.reset();
|
|
|
|
NavigationBaseBrowserTest::TearDownOnMainThread();
|
|
}
|
|
|
|
GURL GetRewriteToNoAccessURL() const { return GURL(kRewriteURL); }
|
|
|
|
private:
|
|
std::unique_ptr<BrowserClient> browser_client_;
|
|
url::ScopedSchemeRegistryForTests scoped_registry_;
|
|
};
|
|
|
|
// Tests navigating to a URL that gets rewritten to a "no access" URL. This
|
|
// mimics the behavior of navigating to special URLs like chrome://newtab and
|
|
// chrome://history which get rewritten to "no access" chrome-native:// URLs.
|
|
IN_PROC_BROWSER_TEST_F(NavigationUrlRewriteBrowserTest, RewriteToNoAccess) {
|
|
// Perform an initial navigation.
|
|
{
|
|
TestNavigationObserver observer(web_contents());
|
|
GURL url = embedded_test_server()->GetURL("a.com", "/title1.html");
|
|
EXPECT_TRUE(NavigateToURL(shell(), url));
|
|
EXPECT_EQ(url, observer.last_navigation_url());
|
|
EXPECT_TRUE(observer.last_navigation_succeeded());
|
|
EXPECT_FALSE(observer.last_initiator_origin().has_value());
|
|
}
|
|
|
|
// Navigate to the URL that will get rewritten to a "no access" URL.
|
|
{
|
|
TestNavigationObserver observer(web_contents());
|
|
|
|
// Note: We are using LoadURLParams here because we need to have the
|
|
// initiator_origin set and NavigateToURL() does not do that.
|
|
NavigationController::LoadURLParams params(GetRewriteToNoAccessURL());
|
|
params.initiator_origin = current_frame_host()->GetLastCommittedOrigin();
|
|
web_contents()->GetController().LoadURLWithParams(params);
|
|
web_contents()->Focus();
|
|
observer.Wait();
|
|
|
|
EXPECT_EQ(GURL(kNoAccessURL), observer.last_navigation_url());
|
|
EXPECT_TRUE(observer.last_navigation_succeeded());
|
|
EXPECT_TRUE(observer.last_initiator_origin().has_value());
|
|
}
|
|
}
|
|
|
|
IN_PROC_BROWSER_TEST_F(NavigationBrowserTest, SameDocumentNavigation) {
|
|
WebContents* wc = shell()->web_contents();
|
|
GURL url1 = embedded_test_server()->GetURL("a.com", "/title1.html#frag1");
|
|
GURL url2 = embedded_test_server()->GetURL("a.com", "/title1.html#frag2");
|
|
NavigationHandleCommitObserver navigation_0(wc, url1);
|
|
NavigationHandleCommitObserver navigation_1(wc, url2);
|
|
|
|
EXPECT_TRUE(NavigateToURL(shell(), url1));
|
|
NavigationEntry* entry =
|
|
web_contents()->GetController().GetLastCommittedEntry();
|
|
EXPECT_TRUE(NavigateToURL(shell(), url2));
|
|
// The NavigationEntry changes on a same-document navigation.
|
|
EXPECT_NE(web_contents()->GetController().GetLastCommittedEntry(), entry);
|
|
|
|
EXPECT_TRUE(navigation_0.has_committed());
|
|
EXPECT_TRUE(navigation_1.has_committed());
|
|
EXPECT_FALSE(navigation_0.was_same_document());
|
|
EXPECT_TRUE(navigation_1.was_same_document());
|
|
}
|
|
|
|
// Some navigations are not allowed, such as when they fail the content security
|
|
// policy, or for trying to load about:srcdoc in the main frame. These result in
|
|
// us redirecting the navigation to an error page via
|
|
// RenderFrameHostImpl::FailedNavigation().
|
|
// Repeating the request with a different URL fragment results in attempting a
|
|
// same-document navigation, but error pages do not support such navigations. In
|
|
// this case treat each failed navigation request as a separate load, with the
|
|
// resulting navigation being performed as a cross-document navigation. This is
|
|
// regression test for https://crbug.com/1018385.
|
|
IN_PROC_BROWSER_TEST_F(NavigationBrowserTest,
|
|
SameDocumentNavigationOnBlockedPage) {
|
|
GURL url1("about:srcdoc#0");
|
|
GURL url2("about:srcdoc#1");
|
|
NavigationHandleCommitObserver navigation_0(web_contents(), url1);
|
|
NavigationHandleCommitObserver navigation_1(web_contents(), url2);
|
|
|
|
// Big warning: about:srcdoc is not supposed to be valid browser-initiated
|
|
// main-frame navigation, it is currently blocked by the NavigationRequest.
|
|
// It is used here to reproduce bug https://crbug.com/1018385. Please avoid
|
|
// copying this kind of navigation in your own tests.
|
|
EXPECT_FALSE(NavigateToURL(shell(), url1));
|
|
EXPECT_FALSE(NavigateToURL(shell(), url2));
|
|
|
|
EXPECT_TRUE(navigation_0.has_committed());
|
|
EXPECT_TRUE(navigation_1.has_committed());
|
|
EXPECT_FALSE(navigation_0.was_same_document());
|
|
EXPECT_FALSE(navigation_1.was_same_document());
|
|
}
|
|
|
|
// This navigation is allowed by the browser, but the network will not be able
|
|
// to connect to the site, so the NavigationRequest fails on the browser side
|
|
// and is redirected to an error page. Performing another navigation should
|
|
// make the full attempt again, in case the network request succeeds this time.
|
|
IN_PROC_BROWSER_TEST_F(NavigationBrowserTest,
|
|
SameDocumentNavigationOnBadServerErrorPage) {
|
|
GURL url1("http://badserver.com:9/");
|
|
GURL url2("http://badserver.com:9/#1");
|
|
NavigationHandleCommitObserver navigation_0(web_contents(), url1);
|
|
NavigationHandleCommitObserver navigation_1(web_contents(), url2);
|
|
|
|
// The navigation is okay from the browser's perspective, so NavigateToURL()
|
|
// will return true. But the network request ultimately fails, so the request
|
|
// is redirected to an error page.
|
|
EXPECT_FALSE(NavigateToURL(shell(), url1));
|
|
EXPECT_TRUE(navigation_0.has_committed());
|
|
EXPECT_FALSE(navigation_0.was_same_document());
|
|
|
|
// The 2nd request shares a URL but it should be another cross-document
|
|
// navigation, rather than trying to navigate inside the error page.
|
|
EXPECT_FALSE(NavigateToURL(shell(), url2));
|
|
EXPECT_TRUE(navigation_1.has_committed());
|
|
EXPECT_FALSE(navigation_1.was_same_document());
|
|
}
|
|
|
|
// This navigation is allowed by the browser, and the request to the server is
|
|
// successful, but it returns 404 error headers, and (optionally) an error page.
|
|
// When another request is made for the same page but with a different fragment,
|
|
// the browser will attempt to perform a same-document navigation but that
|
|
// navigation is intended for the actual document not the error page that has
|
|
// been loaded instead. A same-document navigation in the renderer-loaded error
|
|
// page should be performed as a cross-document navigation in order to attempt
|
|
// to reload the page.
|
|
IN_PROC_BROWSER_TEST_F(NavigationBrowserTest,
|
|
SameDocumentNavigationOn404ErrorPage) {
|
|
// This case is a non-empty 404 page. It makes different choices about where
|
|
// to load the page on a same-document navigation.
|
|
{
|
|
GURL url1 = embedded_test_server()->GetURL("a.com", "/page404.html");
|
|
GURL url2 = embedded_test_server()->GetURL("a.com", "/page404.html#1");
|
|
NavigationHandleCommitObserver navigation_0(web_contents(), url1);
|
|
NavigationHandleCommitObserver navigation_1(web_contents(), url2);
|
|
|
|
EXPECT_TRUE(NavigateToURL(shell(), url1));
|
|
EXPECT_TRUE(navigation_0.has_committed());
|
|
EXPECT_FALSE(navigation_0.was_same_document());
|
|
|
|
// This is another navigation to the non-existent URL, but with a different
|
|
// fragment. We have successfully loaded content from a.com. The fact that
|
|
// it is 404 response does not mean it is an error page, since the term
|
|
// "error page" is used for cases where the browser encounters an error
|
|
// loading a document from the origin. HTTP responses with >400 status codes
|
|
// are just like regular documents from the origin and we render their
|
|
// response body just like we would a 200 response. This is why it can make
|
|
// sense for a same document navigation to be performed from a 404 page.
|
|
EXPECT_TRUE(NavigateToURL(shell(), url2));
|
|
EXPECT_TRUE(navigation_1.has_committed());
|
|
EXPECT_TRUE(navigation_1.was_same_document());
|
|
}
|
|
// This case is an empty 404 page. It makes different choices about where
|
|
// to load the page on a same-document navigation. Since the server has only
|
|
// replied with an error, the browser will display its own error page and
|
|
// therefore it is not one coming from the server's origin.
|
|
{
|
|
GURL url1 = embedded_test_server()->GetURL("a.com", "/empty404.html");
|
|
GURL url2 = embedded_test_server()->GetURL("a.com", "/empty404.html#1");
|
|
NavigationHandleCommitObserver navigation_0(web_contents(), url1);
|
|
NavigationHandleCommitObserver navigation_1(web_contents(), url2);
|
|
|
|
EXPECT_FALSE(NavigateToURL(shell(), url1));
|
|
EXPECT_TRUE(navigation_0.has_committed());
|
|
EXPECT_FALSE(navigation_0.was_same_document());
|
|
|
|
// This is another navigation to the non-existent URL, but with a different
|
|
// fragment. Since we did not load a document from the server (we got
|
|
// `false` from `NavigateToURL()`) there is no server-provided document to
|
|
// navigate within. The result should be a cross-document navigation in
|
|
// order to attempt to load the document at the given path from the server
|
|
// again.
|
|
EXPECT_FALSE(NavigateToURL(shell(), url2));
|
|
EXPECT_TRUE(navigation_1.has_committed());
|
|
EXPECT_FALSE(navigation_1.was_same_document());
|
|
}
|
|
|
|
// This case is also an empty 404 page, but we do replaceState and pushState
|
|
// afterwards, creating successful same-document navigations.
|
|
{
|
|
// Navigate to empty 404, committing an error page.
|
|
GURL url1 = embedded_test_server()->GetURL("a.com", "/empty404.html");
|
|
NavigationHandleCommitObserver navigation(web_contents(), url1);
|
|
EXPECT_FALSE(NavigateToURL(shell(), url1));
|
|
EXPECT_TRUE(navigation.has_committed());
|
|
EXPECT_FALSE(navigation.was_same_document());
|
|
|
|
// replaceState on an error page, without changing the URL.
|
|
{
|
|
FrameNavigateParamsCapturer capturer(main_frame());
|
|
capturer.set_wait_for_load(false);
|
|
EXPECT_TRUE(ExecJs(shell(), "history.replaceState('foo', '')"));
|
|
capturer.Wait();
|
|
EXPECT_TRUE(capturer.is_same_document());
|
|
}
|
|
|
|
// pushState on an error page, without changing the URL.
|
|
{
|
|
FrameNavigateParamsCapturer capturer(main_frame());
|
|
capturer.set_wait_for_load(false);
|
|
EXPECT_TRUE(ExecJs(shell(), "history.pushState('foo', '')"));
|
|
capturer.Wait();
|
|
EXPECT_TRUE(capturer.is_same_document());
|
|
}
|
|
}
|
|
}
|
|
|
|
IN_PROC_BROWSER_TEST_F(NavigationBrowserTest,
|
|
SameDocumentNavigationFromCrossDocumentRedirect) {
|
|
WebContents* wc = shell()->web_contents();
|
|
GURL url0 = embedded_test_server()->GetURL("/title1.html#frag1");
|
|
GURL url1 =
|
|
embedded_test_server()->GetURL("/server-redirect?title1.html#frag2");
|
|
GURL url2 = embedded_test_server()->GetURL("/title1.html#frag2");
|
|
NavigationHandleCommitObserver navigation_0(wc, url0);
|
|
NavigationHandleCommitObserver navigation_1(wc, url1);
|
|
NavigationHandleCommitObserver navigation_2(wc, url2);
|
|
|
|
EXPECT_TRUE(NavigateToURL(shell(), url0));
|
|
// Since the redirect does not land at the URL we passed in, we get a false
|
|
// return here.
|
|
EXPECT_FALSE(NavigateToURL(shell(), url1));
|
|
|
|
// The navigation to |url1| is redirected and so |url1| does not commit. Then
|
|
// the resulting navigation to |url2| lands at the same document URL as |url0|
|
|
// which would be a same-document navigation if there wasn't a redirect
|
|
// involved. But since it started as a cross-document navigation it results in
|
|
// loading a new document instead of doing a same-document navigation.
|
|
EXPECT_TRUE(navigation_0.has_committed());
|
|
EXPECT_FALSE(navigation_1.has_committed());
|
|
EXPECT_TRUE(navigation_2.has_committed());
|
|
EXPECT_FALSE(navigation_0.was_same_document());
|
|
EXPECT_FALSE(navigation_1.was_same_document());
|
|
EXPECT_FALSE(navigation_2.was_same_document());
|
|
|
|
EXPECT_EQ(wc->GetPrimaryMainFrame()->GetLastCommittedURL(), url2);
|
|
|
|
// Redirect should not record a ReceivedResponse event.
|
|
EXPECT_EQ(1u, test_ukm_recorder()
|
|
.GetEntriesByName("Navigation.ReceivedResponse")
|
|
.size());
|
|
}
|
|
|
|
// 1. The browser navigates to a.html.
|
|
// 2. The renderer uses history.pushState() to change the URL of the current
|
|
// document from a.html to b.html.
|
|
// 3. The browser tries to perform a same-document navigation to a.html#foo,
|
|
// since it did not hear about the document's URL changing yet. When it gets
|
|
// to the renderer, we discover a race has happened.
|
|
// 4. Meanwhile, the browser hears about the URL change to b.html and applies
|
|
// it.
|
|
// Now - how do we resolve the race?
|
|
// 5. We will reorder the a.html#foo navigation to start over in the browser
|
|
// after the b.html navigation.
|
|
// Technically, this is still a same-document navigation! The URL changed but
|
|
// the document did not. Currently, however, the browser only considers the URL
|
|
// when performing a non-history navigation to decide if it's a same-document
|
|
// navigation, so..
|
|
// 6. The browser will perform a cross-document navigation to a.html#foo.
|
|
//
|
|
// TODO(https://crbug.com/1262032): Test is flaky on various platforms.
|
|
IN_PROC_BROWSER_TEST_F(NavigationBrowserTest,
|
|
DISABLED_SameDocumentNavigationRacesPushStateURLChange) {
|
|
WebContents* wc = shell()->web_contents();
|
|
GURL url0 = embedded_test_server()->GetURL("/title1.html");
|
|
GURL url1 = embedded_test_server()->GetURL("/title2.html");
|
|
GURL url2 = embedded_test_server()->GetURL("/title1.html#frag2");
|
|
NavigationHandleCommitObserver navigation_0(wc, url0);
|
|
NavigationHandleCommitObserver navigation_1(wc, url1);
|
|
NavigationHandleCommitObserver navigation_2(wc, url2);
|
|
|
|
// Start at `url0`.
|
|
EXPECT_TRUE(NavigateToURL(shell(), url0));
|
|
|
|
// Have the renderer `history.pushState()` to `url1`, which leaves it on the
|
|
// `url0` document, but with a different URL now.
|
|
ExecuteScriptAsync(shell(), JsReplace("history.pushState('', '', $1);"
|
|
"window.location.href == $1;",
|
|
url1));
|
|
|
|
// The browser didn't hear about the change yet.
|
|
EXPECT_EQ(wc->GetPrimaryMainFrame()->GetLastCommittedURL(), url0);
|
|
|
|
{
|
|
// We will wait for 2 navigations: one will be the pushState() and the other
|
|
// will be the navigation to `url2` started below.
|
|
TestNavigationObserver nav_observer(wc, 2);
|
|
|
|
// Start a same-document navigation to url2 that is racing with the
|
|
// renderer's history.pushState().
|
|
shell()->LoadURL(url2);
|
|
|
|
nav_observer.Wait();
|
|
}
|
|
|
|
// The last navigation to resolve is the one to `url2` as it's reordered to
|
|
// come after the race with the already-completed history.pushState().
|
|
EXPECT_EQ(wc->GetPrimaryMainFrame()->GetLastCommittedURL(), url2);
|
|
|
|
// Navigation 0 was a cross-document navigation, to initially load the
|
|
// document.
|
|
EXPECT_TRUE(navigation_0.has_committed());
|
|
EXPECT_FALSE(navigation_0.was_same_document());
|
|
|
|
// Navigation 1 was a same-document navigation, from the renderer's
|
|
// history.pushState() call.
|
|
EXPECT_TRUE(navigation_1.has_committed());
|
|
EXPECT_TRUE(navigation_1.was_same_document());
|
|
|
|
// Navigation 2 was restarted and came after. When it restarted, it saw the
|
|
// URL did not match and did a cross-document navigation. Technically the same
|
|
// document was still loaded from `url0`, but the browser makes its choice
|
|
// on the document's current URL.
|
|
EXPECT_TRUE(navigation_2.has_committed());
|
|
EXPECT_FALSE(navigation_2.was_same_document());
|
|
}
|
|
|
|
class GetEffectiveUrlClient : public ContentBrowserTestContentBrowserClient {
|
|
public:
|
|
GURL GetEffectiveURL(content::BrowserContext* browser_context,
|
|
const GURL& url) override {
|
|
if (effective_url_)
|
|
return *effective_url_;
|
|
return url;
|
|
}
|
|
|
|
bool IsSuitableHost(RenderProcessHost* process_host,
|
|
const GURL& site_url) override {
|
|
if (!disallowed_process_id_)
|
|
return true;
|
|
return process_host->GetID() != disallowed_process_id_;
|
|
}
|
|
|
|
void set_effective_url(const GURL& url) { effective_url_ = url; }
|
|
|
|
void set_disallowed_process(int id) { disallowed_process_id_ = id; }
|
|
|
|
private:
|
|
absl::optional<GURL> effective_url_;
|
|
int disallowed_process_id_ = 0;
|
|
};
|
|
|
|
// While a document is open, state in the browser may change such that loading
|
|
// the document would choose a different SiteInstance. A cross-document
|
|
// navigation would pick up this different SiteInstance, but a same-document
|
|
// navigation should not. It should just navigate inside the currently loaded
|
|
// document instead of reloading the document.
|
|
IN_PROC_BROWSER_TEST_F(NavigationBrowserTest,
|
|
SameDocumentNavigationWhenSiteInstanceWouldChange) {
|
|
auto* wc = static_cast<WebContentsImpl*>(shell()->web_contents());
|
|
GURL url0 = embedded_test_server()->GetURL("a.com", "/title1.html#ref1");
|
|
GURL url1 = embedded_test_server()->GetURL("a.com", "/title1.html#ref2");
|
|
|
|
GetEffectiveUrlClient new_client;
|
|
|
|
NavigationHandleCommitObserver navigation_0(wc, url0);
|
|
EXPECT_TRUE(NavigateToURL(shell(), url0));
|
|
EXPECT_TRUE(navigation_0.has_committed());
|
|
EXPECT_FALSE(navigation_0.was_same_document());
|
|
|
|
RenderFrameHost* main_frame_host = wc->GetPrimaryMainFrame();
|
|
RenderProcessHost* main_frame_process_host = main_frame_host->GetProcess();
|
|
|
|
// When we both change the effective URL and also disallow the current
|
|
// renderer process, a new load of the current document would get a different
|
|
// SiteInstance.
|
|
GURL modified_url0 =
|
|
embedded_test_server()->GetURL("c.com", "/title1.html#ref1");
|
|
new_client.set_effective_url(modified_url0);
|
|
new_client.set_disallowed_process(main_frame_process_host->GetID());
|
|
|
|
NavigationHandleCommitObserver navigation_1(wc, url1);
|
|
EXPECT_TRUE(NavigateToURL(shell(), url1));
|
|
EXPECT_TRUE(navigation_1.has_committed());
|
|
EXPECT_TRUE(navigation_1.was_same_document());
|
|
|
|
// The RenderFrameHost should not have changed, we should perform the
|
|
// navigation in the currently loaded document.
|
|
EXPECT_EQ(main_frame_host, wc->GetPrimaryMainFrame());
|
|
EXPECT_EQ(main_frame_process_host, wc->GetPrimaryMainFrame()->GetProcess());
|
|
}
|
|
|
|
// This tests the same ideas as the above test except in this case the same-
|
|
// document navigation is done through a history navigation, which exercises
|
|
// different codepaths in the NavigationControllerImpl.
|
|
IN_PROC_BROWSER_TEST_F(
|
|
NavigationBrowserTest,
|
|
SameDocumentHistoryNavigationWhenSiteInstanceWouldChange) {
|
|
auto* wc = static_cast<WebContentsImpl*>(shell()->web_contents());
|
|
GURL url0 = embedded_test_server()->GetURL("a.com", "/title1.html#ref1");
|
|
GURL url1 = embedded_test_server()->GetURL("a.com", "/title1.html#ref2");
|
|
NavigationHandleCommitObserver navigation_0(wc, url0);
|
|
NavigationHandleCommitObserver navigation_1(wc, url1);
|
|
|
|
GetEffectiveUrlClient new_client;
|
|
|
|
EXPECT_TRUE(NavigateToURL(shell(), url0));
|
|
EXPECT_TRUE(navigation_0.has_committed());
|
|
EXPECT_FALSE(navigation_0.was_same_document());
|
|
|
|
EXPECT_TRUE(NavigateToURL(shell(), url1));
|
|
EXPECT_TRUE(navigation_1.has_committed());
|
|
EXPECT_TRUE(navigation_1.was_same_document());
|
|
|
|
RenderFrameHost* main_frame_host = wc->GetPrimaryMainFrame();
|
|
RenderProcessHost* main_frame_process_host = main_frame_host->GetProcess();
|
|
|
|
// When we both change the effective URL and also disallow the current
|
|
// renderer process, a new load of the current document would get a different
|
|
// SiteInstance.
|
|
GURL modified_url0 =
|
|
embedded_test_server()->GetURL("c.com", "/title1.html#ref1");
|
|
new_client.set_effective_url(modified_url0);
|
|
new_client.set_disallowed_process(main_frame_process_host->GetID());
|
|
|
|
// Navigates to the same-document. Since the SiteInstance changed, we would
|
|
// normally try isolate this navigation by using a different RenderProcessHost
|
|
// and RenderFrameHost. But since it is same-document, we want to avoid that
|
|
// and perform the navigation inside the loaded |url0| document.
|
|
wc->GetController().GoBack();
|
|
EXPECT_TRUE(WaitForLoadStop(wc));
|
|
|
|
// The RenderFrameHost should not have changed, we should perform the
|
|
// navigation in the currently loaded document.
|
|
EXPECT_EQ(main_frame_host, wc->GetPrimaryMainFrame());
|
|
EXPECT_EQ(main_frame_process_host, wc->GetPrimaryMainFrame()->GetProcess());
|
|
}
|
|
|
|
IN_PROC_BROWSER_TEST_F(NavigationBrowserTest,
|
|
NonDeterministicUrlRewritesUseLastUrl) {
|
|
// Lambda expressions cannot be assigned to function pointers if they use
|
|
// captures, so track how many times the handler is called using a non-const
|
|
// static variable.
|
|
static int rewrite_count;
|
|
rewrite_count = 0;
|
|
|
|
BrowserURLHandler::URLHandler handler_method =
|
|
[](GURL* url, BrowserContext* browser_context) {
|
|
GURL::Replacements replace_path;
|
|
if (rewrite_count > 0) {
|
|
replace_path.SetPathStr("title2.html");
|
|
} else {
|
|
replace_path.SetPathStr("title1.html");
|
|
}
|
|
*url = url->ReplaceComponents(replace_path);
|
|
rewrite_count++;
|
|
return true;
|
|
};
|
|
BrowserURLHandler::GetInstance()->AddHandlerPair(
|
|
handler_method, BrowserURLHandler::null_handler());
|
|
|
|
TestNavigationObserver observer(web_contents());
|
|
shell()->LoadURL(embedded_test_server()->GetURL("/virtual-url.html"));
|
|
observer.Wait();
|
|
EXPECT_EQ("/title2.html", observer.last_navigation_url().path());
|
|
EXPECT_EQ(2, rewrite_count);
|
|
}
|
|
|
|
// Create two windows. When the second is deleted, it initiates a navigation in
|
|
// the first. This is a situation where the navigation has an initiator frame
|
|
// token, but no corresponding RenderFrameHost.
|
|
IN_PROC_BROWSER_TEST_F(NavigationBrowserTest,
|
|
RendererInitiatedCrossWindowNavigationInUnload) {
|
|
GURL url(embedded_test_server()->GetURL("/empty.html"));
|
|
GURL always_referrer_url(embedded_test_server()->GetURL(
|
|
"/set-header?Referrer-Policy: unsafe-url"));
|
|
|
|
// Setup the opener window.
|
|
EXPECT_TRUE(NavigateToURL(shell(), url));
|
|
|
|
// Setup the openee window;
|
|
ShellAddedObserver new_shell_observer;
|
|
EXPECT_TRUE(
|
|
ExecJs(shell(), JsReplace("window.open($1);", always_referrer_url)));
|
|
Shell* openee_shell = new_shell_observer.GetShell();
|
|
EXPECT_TRUE(WaitForLoadStop(openee_shell->web_contents()));
|
|
|
|
// When deleted, the openee will initiate a navigation in its opener.
|
|
EXPECT_TRUE(ExecJs(openee_shell, R"(
|
|
window.addEventListener("unload", () => {
|
|
opener.location.href = "about:blank";
|
|
})
|
|
)"));
|
|
|
|
RenderFrameHost* openee_rfh =
|
|
static_cast<WebContentsImpl*>(openee_shell->web_contents())
|
|
->GetPrimaryMainFrame();
|
|
blink::LocalFrameToken initiator_frame_token = openee_rfh->GetFrameToken();
|
|
int initiator_process_id = openee_rfh->GetProcess()->GetID();
|
|
base::RunLoop loop;
|
|
DidStartNavigationCallback callback(
|
|
web_contents(), base::BindLambdaForTesting([&](NavigationHandle* handle) {
|
|
auto* request = NavigationRequest::From(handle);
|
|
|
|
const absl::optional<blink::LocalFrameToken>& frame_token =
|
|
request->GetInitiatorFrameToken();
|
|
EXPECT_TRUE(frame_token.has_value());
|
|
EXPECT_EQ(initiator_frame_token, frame_token.value());
|
|
EXPECT_EQ(initiator_process_id, request->GetInitiatorProcessID());
|
|
|
|
auto* initiator_rfh = RenderFrameHostImpl::FromFrameToken(
|
|
request->GetInitiatorProcessID(), *frame_token);
|
|
ASSERT_FALSE(initiator_rfh);
|
|
|
|
// Even if the initiator RenderFrameHost is gone, its policy container
|
|
// should still be around since the LocalFrame has not been destroyed
|
|
// yet.
|
|
auto* initiator_policy_container =
|
|
PolicyContainerHost::FromFrameToken(frame_token.value());
|
|
ASSERT_TRUE(initiator_policy_container);
|
|
ASSERT_EQ(network::mojom::ReferrerPolicy::kAlways,
|
|
initiator_policy_container->referrer_policy());
|
|
|
|
// Even if the initiator RenderFrameHost is gone, the navigation request
|
|
// (to "about:blank") should have inherited its policy container.
|
|
auto* initiator_policies =
|
|
request->GetInitiatorPolicyContainerPolicies();
|
|
ASSERT_TRUE(initiator_policies);
|
|
ASSERT_EQ(network::mojom::ReferrerPolicy::kAlways,
|
|
initiator_policies->referrer_policy);
|
|
|
|
loop.Quit();
|
|
}));
|
|
|
|
// Delete the openee, which trigger the navigation in the opener.
|
|
openee_shell->Close();
|
|
loop.Run();
|
|
}
|
|
|
|
// A document initiates a form submission in another frame, then deletes itself.
|
|
// Check the initiator frame token.
|
|
IN_PROC_BROWSER_TEST_F(NavigationBrowserTest, FormSubmissionThenDeleteFrame) {
|
|
GURL url(embedded_test_server()->GetURL("/empty.html"));
|
|
GURL always_referrer_url(embedded_test_server()->GetURL(
|
|
"/set-header?Referrer-Policy: unsafe-url"));
|
|
|
|
// Setup the opener window.
|
|
EXPECT_TRUE(NavigateToURL(shell(), url));
|
|
|
|
// Setup the openee window;
|
|
ShellAddedObserver new_shell_observer;
|
|
EXPECT_TRUE(ExecJs(shell(), JsReplace("window.open($1);", url)));
|
|
Shell* openee_shell = new_shell_observer.GetShell();
|
|
|
|
// Create a 'named' iframe in the first window. This will be the target of the
|
|
// form submission.
|
|
EXPECT_TRUE(ExecJs(shell(), R"(
|
|
new Promise(resolve => {
|
|
let iframe = document.createElement("iframe");
|
|
iframe.onload = resolve;
|
|
iframe.name = 'form-submission-target';
|
|
iframe.src = location.href;
|
|
document.body.appendChild(iframe);
|
|
});
|
|
)"));
|
|
|
|
// Create an iframe in the second window. It will be initiating a form
|
|
// submission and removing itself before the scheduled form navigation occurs.
|
|
// This iframe will have referrer policy "unsafe-url".
|
|
EXPECT_TRUE(WaitForLoadStop(openee_shell->web_contents()));
|
|
EXPECT_TRUE(ExecJs(openee_shell, JsReplace(R"(
|
|
new Promise(resolve => {
|
|
let iframe = document.createElement('iframe');
|
|
iframe.onload = resolve;
|
|
iframe.src = $1;
|
|
document.body.appendChild(iframe);
|
|
});
|
|
)",
|
|
always_referrer_url)));
|
|
EXPECT_TRUE(WaitForLoadStop(openee_shell->web_contents()));
|
|
|
|
RenderFrameHost* initiator_rfh =
|
|
static_cast<WebContentsImpl*>(openee_shell->web_contents())
|
|
->GetPrimaryMainFrame()
|
|
->child_at(0)
|
|
->current_frame_host();
|
|
blink::LocalFrameToken initiator_frame_token = initiator_rfh->GetFrameToken();
|
|
int initiator_process_id = initiator_rfh->GetProcess()->GetID();
|
|
base::RunLoop loop;
|
|
DidStartNavigationCallback callback(
|
|
web_contents(), base::BindLambdaForTesting([&](NavigationHandle* handle) {
|
|
auto* request = NavigationRequest::From(handle);
|
|
ASSERT_TRUE(request->IsPost());
|
|
|
|
const absl::optional<blink::LocalFrameToken>& frame_token =
|
|
request->GetInitiatorFrameToken();
|
|
EXPECT_TRUE(frame_token.has_value());
|
|
EXPECT_EQ(initiator_frame_token, frame_token.value());
|
|
EXPECT_EQ(initiator_process_id, request->GetInitiatorProcessID());
|
|
|
|
auto* initiator_rfh = RenderFrameHostImpl::FromFrameToken(
|
|
request->GetInitiatorProcessID(), frame_token.value());
|
|
ASSERT_FALSE(initiator_rfh);
|
|
|
|
// Even if the initiator RenderFrameHost is gone, its policy container
|
|
// should still be around since the LocalFrame has not been destroyed
|
|
// yet.
|
|
auto* initiator_policy_container =
|
|
PolicyContainerHost::FromFrameToken(frame_token.value());
|
|
ASSERT_TRUE(initiator_policy_container);
|
|
EXPECT_EQ(network::mojom::ReferrerPolicy::kAlways,
|
|
initiator_policy_container->referrer_policy());
|
|
|
|
auto* initiator_policies =
|
|
request->GetInitiatorPolicyContainerPolicies();
|
|
ASSERT_TRUE(initiator_policies);
|
|
ASSERT_EQ(network::mojom::ReferrerPolicy::kAlways,
|
|
initiator_policies->referrer_policy);
|
|
|
|
loop.Quit();
|
|
}));
|
|
|
|
// Initiate a form submission into the first window and delete the initiator.
|
|
EXPECT_TRUE(WaitForLoadStop(openee_shell->web_contents()));
|
|
ExecuteScriptAsync(initiator_rfh, R"(
|
|
let input = document.createElement("input");
|
|
input.setAttribute("type", "hidden");
|
|
input.setAttribute("name", "my_token");
|
|
input.setAttribute("value", "my_value");
|
|
|
|
// Schedule a form submission navigation (which will occur in a separate
|
|
// task).
|
|
let form = document.createElement('form');
|
|
form.appendChild(input);
|
|
form.setAttribute("method", "POST");
|
|
form.setAttribute("action", "about:blank");
|
|
form.setAttribute("target", "form-submission-target");
|
|
document.body.appendChild(form);
|
|
form.submit();
|
|
|
|
// Delete this frame before the scheduled navigation occurs in the target
|
|
// frame.
|
|
parent.document.querySelector("iframe").remove();
|
|
)");
|
|
loop.Run();
|
|
}
|
|
|
|
// Same as the previous test, but for a remote frame navigation:
|
|
// A document initiates a form submission in a cross-origin frame, then deletes
|
|
// itself. Check the initiator frame token.
|
|
IN_PROC_BROWSER_TEST_F(NavigationBrowserTest,
|
|
FormSubmissionInRemoteFrameThenDeleteFrame) {
|
|
GURL url(embedded_test_server()->GetURL("/empty.html"));
|
|
GURL cross_origin_always_referrer_url(embedded_test_server()->GetURL(
|
|
"foo.com", "/set-header?Referrer-Policy: unsafe-url"));
|
|
|
|
// Setup the main page.
|
|
EXPECT_TRUE(NavigateToURL(shell(), url));
|
|
|
|
// Create a cross origin child iframe. This iframe will embed another iframe,
|
|
// which will initiate the navigation. The only purpose of this iframe is to
|
|
// allow its child to delete itself by issuing
|
|
// parent.document.querySelector("iframe").remove();
|
|
// (The main frame cannot do it because it is cross-origin.)
|
|
EXPECT_TRUE(WaitForLoadStop(shell()->web_contents()));
|
|
EXPECT_TRUE(ExecJs(shell(), JsReplace(R"(
|
|
let iframe = document.createElement('iframe');
|
|
iframe.src = $1;
|
|
document.body.appendChild(iframe);
|
|
)",
|
|
cross_origin_always_referrer_url)));
|
|
EXPECT_TRUE(WaitForLoadStop(shell()->web_contents()));
|
|
|
|
RenderFrameHostImpl* middle_rfh =
|
|
current_frame_host()->child_at(0)->current_frame_host();
|
|
|
|
// Now create a grandchild iframe, which is same-origin with the parent (but
|
|
// cross-origin with the grandparent). The grandchild will initiate a form
|
|
// submission in the top frame and remove itself before the scheduled form
|
|
// navigation occurs. This iframe will have referrer policy "unsafe-url".
|
|
EXPECT_TRUE(ExecJs(middle_rfh, JsReplace(R"(
|
|
let iframe = document.createElement('iframe');
|
|
iframe.src = $1;
|
|
document.body.appendChild(iframe);
|
|
)",
|
|
cross_origin_always_referrer_url)));
|
|
EXPECT_TRUE(WaitForLoadStop(shell()->web_contents()));
|
|
|
|
RenderFrameHost* initiator_rfh =
|
|
middle_rfh->child_at(0)->current_frame_host();
|
|
blink::LocalFrameToken initiator_frame_token = initiator_rfh->GetFrameToken();
|
|
int initiator_process_id = initiator_rfh->GetProcess()->GetID();
|
|
|
|
base::RunLoop loop;
|
|
DidStartNavigationCallback callback(
|
|
shell()->web_contents(),
|
|
base::BindLambdaForTesting([&](NavigationHandle* handle) {
|
|
auto* request = NavigationRequest::From(handle);
|
|
ASSERT_TRUE(request->IsPost());
|
|
|
|
const absl::optional<blink::LocalFrameToken>& frame_token =
|
|
request->GetInitiatorFrameToken();
|
|
EXPECT_TRUE(frame_token.has_value());
|
|
EXPECT_EQ(initiator_frame_token, frame_token.value());
|
|
EXPECT_EQ(initiator_process_id, request->GetInitiatorProcessID());
|
|
|
|
auto* initiator_rfh = RenderFrameHostImpl::FromFrameToken(
|
|
request->GetInitiatorProcessID(), frame_token.value());
|
|
ASSERT_FALSE(initiator_rfh);
|
|
|
|
// Even if the initiator RenderFrameHost is gone, its policy container
|
|
// should still be around since the LocalFrame has not been destroyed
|
|
// yet.
|
|
auto* initiator_policy_container =
|
|
PolicyContainerHost::FromFrameToken(frame_token.value());
|
|
ASSERT_TRUE(initiator_policy_container);
|
|
EXPECT_EQ(network::mojom::ReferrerPolicy::kAlways,
|
|
initiator_policy_container->referrer_policy());
|
|
EXPECT_EQ(
|
|
network::mojom::ReferrerPolicy::kAlways,
|
|
request->GetInitiatorPolicyContainerPolicies()->referrer_policy);
|
|
|
|
loop.Quit();
|
|
}));
|
|
|
|
// Initiate a form submission into the main frame and delete the initiator.
|
|
ExecuteScriptAsync(initiator_rfh, R"(
|
|
let input = document.createElement("input");
|
|
input.setAttribute("type", "hidden");
|
|
input.setAttribute("name", "my_token");
|
|
input.setAttribute("value", "my_value");
|
|
|
|
// Schedule a form submission navigation (which will occur in a separate
|
|
// task).
|
|
let form = document.createElement('form');
|
|
form.appendChild(input);
|
|
form.setAttribute("method", "POST");
|
|
form.setAttribute("action", "about:blank");
|
|
form.setAttribute("target", "_top");
|
|
document.body.appendChild(form);
|
|
form.submit();
|
|
|
|
// Delete this frame before the scheduled navigation occurs in the main
|
|
// frame.
|
|
parent.document.querySelector("iframe").remove();
|
|
)");
|
|
loop.Run();
|
|
}
|
|
|
|
using MediaNavigationBrowserTest = NavigationBaseBrowserTest;
|
|
|
|
// Media navigations synchronously complete the time of the `CommitNavigation`
|
|
// IPC call. Ensure that the renderer does not crash if the media navigation
|
|
// results in an HTTP error with no body, since the renderer will reentrantly
|
|
// commit an error page while handling the `CommitNavigation` IPC.
|
|
IN_PROC_BROWSER_TEST_F(MediaNavigationBrowserTest, FailedNavigation) {
|
|
embedded_test_server()->RegisterRequestHandler(base::BindRepeating(
|
|
[](const net::test_server::HttpRequest& request)
|
|
-> std::unique_ptr<net::test_server::HttpResponse> {
|
|
auto response = std::make_unique<net::test_server::BasicHttpResponse>();
|
|
response->set_code(net::HTTP_NOT_FOUND);
|
|
response->set_content_type("video/mp4");
|
|
return response;
|
|
}));
|
|
ASSERT_TRUE(embedded_test_server()->Start());
|
|
|
|
const GURL error_url(embedded_test_server()->GetURL("/moo.mp4"));
|
|
EXPECT_FALSE(NavigateToURL(shell(), error_url));
|
|
EXPECT_EQ(error_url, current_frame_host()->GetLastCommittedURL());
|
|
NavigationEntry* entry =
|
|
web_contents()->GetController().GetLastCommittedEntry();
|
|
EXPECT_EQ(PAGE_TYPE_ERROR, entry->GetPageType());
|
|
}
|
|
|
|
class DocumentPolicyBrowserTest : public NavigationBaseBrowserTest {
|
|
public:
|
|
DocumentPolicyBrowserTest() {
|
|
feature_list_.InitAndEnableFeature(features::kDocumentPolicy);
|
|
}
|
|
|
|
private:
|
|
base::test::ScopedFeatureList feature_list_;
|
|
};
|
|
|
|
// Test that scroll restoration can be disabled with
|
|
// Document-Policy: force-load-at-top
|
|
IN_PROC_BROWSER_TEST_F(DocumentPolicyBrowserTest,
|
|
ScrollRestorationDisabledByDocumentPolicy) {
|
|
net::test_server::ControllableHttpResponse response(embedded_test_server(),
|
|
"/target.html");
|
|
ASSERT_TRUE(embedded_test_server()->Start());
|
|
GURL url(embedded_test_server()->GetURL("/target.html"));
|
|
TestNavigationManager navigation_manager(web_contents(), url);
|
|
// This test expects the document is freshly loaded on the back navigation
|
|
// so that the document policy to force-load-at-top will run. This will not
|
|
// happen if the document is back-forward cached, so we need to disable it.
|
|
DisableBackForwardCacheForTesting(web_contents(),
|
|
BackForwardCache::TEST_REQUIRES_NO_CACHING);
|
|
|
|
// Load the document with document policy force-load-at-top
|
|
shell()->LoadURL(url);
|
|
EXPECT_TRUE(navigation_manager.WaitForRequestStart());
|
|
navigation_manager.ResumeNavigation();
|
|
response.WaitForRequest();
|
|
response.Send(
|
|
"HTTP/1.1 200 OK\r\n"
|
|
"Content-Type: text/html; charset=utf-8\r\n"
|
|
"Document-Policy: force-load-at-top\r\n"
|
|
"\r\n"
|
|
"<p style='position: absolute; top: 10000px;'>Some text</p>");
|
|
response.Done();
|
|
|
|
EXPECT_TRUE(navigation_manager.WaitForResponse());
|
|
navigation_manager.ResumeNavigation();
|
|
ASSERT_TRUE(navigation_manager.WaitForNavigationFinished());
|
|
EXPECT_TRUE(WaitForLoadStop(web_contents()));
|
|
EXPECT_TRUE(WaitForRenderFrameReady(current_frame_host()));
|
|
|
|
{
|
|
RenderFrameSubmissionObserver frame_observer(web_contents());
|
|
// Scroll down the page a bit
|
|
EXPECT_TRUE(ExecJs(web_contents(), "window.scrollTo(0, 1000)"));
|
|
frame_observer.WaitForScrollOffsetAtTop(false);
|
|
}
|
|
|
|
// Navigate away
|
|
EXPECT_TRUE(ExecJs(web_contents(), "window.location = 'about:blank'"));
|
|
EXPECT_TRUE(WaitForLoadStop(web_contents()));
|
|
EXPECT_TRUE(WaitForRenderFrameReady(current_frame_host()));
|
|
|
|
// Navigate back
|
|
EXPECT_TRUE(ExecJs(web_contents(), "history.back()"));
|
|
EXPECT_TRUE(WaitForLoadStop(web_contents()));
|
|
EXPECT_TRUE(WaitForRenderFrameReady(current_frame_host()));
|
|
|
|
// Wait a short amount of time to ensure the page does not scroll.
|
|
base::RunLoop run_loop;
|
|
base::SingleThreadTaskRunner::GetCurrentDefault()->PostDelayedTask(
|
|
FROM_HERE, run_loop.QuitClosure(), TestTimeouts::tiny_timeout());
|
|
run_loop.Run();
|
|
RunUntilInputProcessed(RenderWidgetHostImpl::From(
|
|
web_contents()->GetRenderViewHost()->GetWidget()));
|
|
const cc::RenderFrameMetadata& last_metadata =
|
|
RenderFrameSubmissionObserver(web_contents()).LastRenderFrameMetadata();
|
|
EXPECT_TRUE(last_metadata.is_scroll_offset_at_top);
|
|
}
|
|
|
|
// Test that scroll restoration works as expected with
|
|
// Document-Policy: force-load-at-top=?0
|
|
IN_PROC_BROWSER_TEST_F(DocumentPolicyBrowserTest,
|
|
ScrollRestorationEnabledByDocumentPolicy) {
|
|
net::test_server::ControllableHttpResponse response(embedded_test_server(),
|
|
"/target.html");
|
|
ASSERT_TRUE(embedded_test_server()->Start());
|
|
GURL url(embedded_test_server()->GetURL("/target.html"));
|
|
RenderFrameSubmissionObserver frame_observer(web_contents());
|
|
TestNavigationManager navigation_manager(web_contents(), url);
|
|
|
|
// Load the document with document policy force-load-at-top set to false.
|
|
shell()->LoadURL(url);
|
|
EXPECT_TRUE(navigation_manager.WaitForRequestStart());
|
|
navigation_manager.ResumeNavigation();
|
|
response.WaitForRequest();
|
|
response.Send(
|
|
"HTTP/1.1 200 OK\r\n"
|
|
"Content-Type: text/html; charset=utf-8\r\n"
|
|
"Document-Policy: force-load-at-top=?0\r\n"
|
|
"\r\n"
|
|
"<p style='position: absolute; top: 10000px;'>Some text</p>");
|
|
response.Done();
|
|
|
|
EXPECT_TRUE(navigation_manager.WaitForResponse());
|
|
navigation_manager.ResumeNavigation();
|
|
ASSERT_TRUE(navigation_manager.WaitForNavigationFinished());
|
|
EXPECT_TRUE(WaitForLoadStop(web_contents()));
|
|
EXPECT_TRUE(WaitForRenderFrameReady(current_frame_host()));
|
|
|
|
// Scroll down the page a bit
|
|
EXPECT_TRUE(ExecJs(web_contents(), "window.scrollTo(0, 1000)"));
|
|
frame_observer.WaitForScrollOffsetAtTop(false);
|
|
|
|
// Navigate away
|
|
EXPECT_TRUE(ExecJs(web_contents(), "window.location = 'about:blank'"));
|
|
EXPECT_TRUE(WaitForLoadStop(web_contents()));
|
|
EXPECT_TRUE(WaitForRenderFrameReady(current_frame_host()));
|
|
|
|
// Navigate back
|
|
EXPECT_TRUE(ExecJs(web_contents(), "history.back()"));
|
|
EXPECT_TRUE(WaitForLoadStop(web_contents()));
|
|
EXPECT_TRUE(WaitForRenderFrameReady(current_frame_host()));
|
|
|
|
// Ensure scroll restoration activated
|
|
frame_observer.WaitForScrollOffsetAtTop(false);
|
|
const cc::RenderFrameMetadata& last_metadata =
|
|
RenderFrameSubmissionObserver(web_contents()).LastRenderFrameMetadata();
|
|
EXPECT_FALSE(last_metadata.is_scroll_offset_at_top);
|
|
}
|
|
|
|
// Test that element fragment anchor scrolling can be disabled with
|
|
// Document-Policy: force-load-at-top
|
|
IN_PROC_BROWSER_TEST_F(DocumentPolicyBrowserTest,
|
|
FragmentAnchorDisabledByDocumentPolicy) {
|
|
net::test_server::ControllableHttpResponse response(embedded_test_server(),
|
|
"/target.html");
|
|
|
|
ASSERT_TRUE(embedded_test_server()->Start());
|
|
GURL url(embedded_test_server()->GetURL("/target.html#text"));
|
|
|
|
// Load the target document
|
|
TestNavigationManager navigation_manager(web_contents(), url);
|
|
shell()->LoadURL(url);
|
|
|
|
// Start navigation
|
|
EXPECT_TRUE(navigation_manager.WaitForRequestStart());
|
|
navigation_manager.ResumeNavigation();
|
|
|
|
// Send Document-Policy header
|
|
response.WaitForRequest();
|
|
response.Send(
|
|
"HTTP/1.1 200 OK\r\n"
|
|
"Content-Type: text/html; charset=utf-8\r\n"
|
|
"Document-Policy: force-load-at-top\r\n"
|
|
"\r\n"
|
|
"<p id='text' style='position: absolute; top: 10000px;'>Some text</p>");
|
|
response.Done();
|
|
|
|
EXPECT_TRUE(navigation_manager.WaitForResponse());
|
|
navigation_manager.ResumeNavigation();
|
|
ASSERT_TRUE(navigation_manager.WaitForNavigationFinished());
|
|
|
|
EXPECT_TRUE(WaitForLoadStop(web_contents()));
|
|
EXPECT_TRUE(WaitForRenderFrameReady(current_frame_host()));
|
|
// Wait a short amount of time to ensure the page does not scroll.
|
|
base::RunLoop run_loop;
|
|
base::SingleThreadTaskRunner::GetCurrentDefault()->PostDelayedTask(
|
|
FROM_HERE, run_loop.QuitClosure(), TestTimeouts::tiny_timeout());
|
|
run_loop.Run();
|
|
RunUntilInputProcessed(RenderWidgetHostImpl::From(
|
|
web_contents()->GetRenderViewHost()->GetWidget()));
|
|
const cc::RenderFrameMetadata& last_metadata =
|
|
RenderFrameSubmissionObserver(web_contents()).LastRenderFrameMetadata();
|
|
EXPECT_TRUE(last_metadata.is_scroll_offset_at_top);
|
|
}
|
|
|
|
// Test that element fragment anchor scrolling works as expected with
|
|
// Document-Policy: force-load-at-top=?0
|
|
IN_PROC_BROWSER_TEST_F(DocumentPolicyBrowserTest,
|
|
FragmentAnchorEnabledByDocumentPolicy) {
|
|
net::test_server::ControllableHttpResponse response(embedded_test_server(),
|
|
"/target.html");
|
|
|
|
ASSERT_TRUE(embedded_test_server()->Start());
|
|
GURL url(embedded_test_server()->GetURL("/target.html#text"));
|
|
RenderFrameSubmissionObserver frame_observer(web_contents());
|
|
|
|
// Load the target document
|
|
TestNavigationManager navigation_manager(web_contents(), url);
|
|
shell()->LoadURL(url);
|
|
|
|
// Start navigation
|
|
EXPECT_TRUE(navigation_manager.WaitForRequestStart());
|
|
navigation_manager.ResumeNavigation();
|
|
|
|
// Send Document-Policy header
|
|
response.WaitForRequest();
|
|
response.Send(
|
|
"HTTP/1.1 200 OK\r\n"
|
|
"Content-Type: text/html; charset=utf-8\r\n"
|
|
"Document-Policy: force-load-at-top=?0\r\n"
|
|
"\r\n"
|
|
"<p id='text' style='position: absolute; top: 10000px;'>Some text</p>");
|
|
response.Done();
|
|
|
|
EXPECT_TRUE(navigation_manager.WaitForResponse());
|
|
navigation_manager.ResumeNavigation();
|
|
ASSERT_TRUE(navigation_manager.WaitForNavigationFinished());
|
|
|
|
EXPECT_TRUE(WaitForLoadStop(web_contents()));
|
|
EXPECT_TRUE(WaitForRenderFrameReady(current_frame_host()));
|
|
frame_observer.WaitForScrollOffsetAtTop(
|
|
/*expected_scroll_offset_at_top=*/false);
|
|
const cc::RenderFrameMetadata& last_metadata =
|
|
RenderFrameSubmissionObserver(web_contents()).LastRenderFrameMetadata();
|
|
EXPECT_FALSE(last_metadata.is_scroll_offset_at_top);
|
|
}
|
|
|
|
IN_PROC_BROWSER_TEST_F(NavigationBrowserTest, OriginToCommitBasic) {
|
|
GURL url = embedded_test_server()->GetURL("a.com", "/empty.html");
|
|
auto origin_expected = url::Origin::Create(url);
|
|
TestNavigationManager manager(web_contents(), url);
|
|
shell()->LoadURL(url);
|
|
EXPECT_TRUE(manager.WaitForResponse());
|
|
NavigationRequest* navigation = main_frame()->navigation_request();
|
|
absl::optional<url::Origin> origin_to_commit =
|
|
navigation->GetOriginToCommit();
|
|
ASSERT_TRUE(origin_to_commit.has_value());
|
|
ASSERT_TRUE(manager.WaitForNavigationFinished());
|
|
url::Origin origin_committed = current_frame_host()->GetLastCommittedOrigin();
|
|
|
|
EXPECT_FALSE(origin_to_commit->opaque());
|
|
EXPECT_FALSE(origin_committed.opaque());
|
|
EXPECT_EQ(origin_expected, *origin_to_commit);
|
|
EXPECT_EQ(origin_expected, origin_committed);
|
|
}
|
|
|
|
IN_PROC_BROWSER_TEST_F(NavigationBrowserTest, OriginToCommit204) {
|
|
GURL url = embedded_test_server()->GetURL("a.com", "/nocontent");
|
|
TestNavigationManager manager(web_contents(), url);
|
|
shell()->LoadURL(url);
|
|
EXPECT_TRUE(manager.WaitForResponse());
|
|
NavigationRequest* navigation = main_frame()->navigation_request();
|
|
absl::optional<url::Origin> origin_to_commit =
|
|
navigation->GetOriginToCommit();
|
|
EXPECT_FALSE(origin_to_commit.has_value());
|
|
ASSERT_TRUE(manager.WaitForNavigationFinished());
|
|
}
|
|
|
|
IN_PROC_BROWSER_TEST_F(NavigationBrowserTest,
|
|
OriginToCommitSandboxFromResponse) {
|
|
GURL url = embedded_test_server()->GetURL(
|
|
"a.com", "/set-header?Content-Security-Policy: sandbox");
|
|
TestNavigationManager manager(web_contents(), url);
|
|
shell()->LoadURL(url);
|
|
EXPECT_TRUE(manager.WaitForResponse());
|
|
NavigationRequest* navigation = main_frame()->navigation_request();
|
|
url::Origin origin_to_commit = navigation->GetOriginToCommit().value();
|
|
ASSERT_TRUE(manager.WaitForNavigationFinished());
|
|
url::Origin origin_committed = current_frame_host()->GetLastCommittedOrigin();
|
|
|
|
EXPECT_TRUE(origin_to_commit.opaque());
|
|
EXPECT_TRUE(origin_committed.opaque());
|
|
// TODO(https://crbug.com/888079). The nonce must match.
|
|
EXPECT_NE(origin_to_commit, origin_committed);
|
|
}
|
|
|
|
IN_PROC_BROWSER_TEST_F(NavigationBrowserTest,
|
|
OriginToCommitSandboxFromParentDocument) {
|
|
GURL url_top = embedded_test_server()->GetURL(
|
|
"a.com", "/set-header?Content-Security-Policy: sandbox allow-scripts");
|
|
EXPECT_TRUE(NavigateToURL(shell(), url_top));
|
|
GURL url_iframe = embedded_test_server()->GetURL("a.com", "/empty.html");
|
|
TestNavigationManager manager(web_contents(), url_iframe);
|
|
ExecuteScriptAsync(current_frame_host(), R"(
|
|
let iframe = document.createElement("iframe");
|
|
iframe.src = "./empty.html";
|
|
document.body.appendChild(iframe);
|
|
)");
|
|
EXPECT_TRUE(manager.WaitForResponse());
|
|
FrameTreeNode* iframe = current_frame_host()->child_at(0);
|
|
NavigationRequest* navigation = iframe->navigation_request();
|
|
url::Origin origin_to_commit = navigation->GetOriginToCommit().value();
|
|
ASSERT_TRUE(manager.WaitForNavigationFinished());
|
|
url::Origin origin_committed =
|
|
iframe->current_frame_host()->GetLastCommittedOrigin();
|
|
|
|
EXPECT_TRUE(origin_to_commit.opaque());
|
|
EXPECT_TRUE(origin_committed.opaque());
|
|
// TODO(https://crbug.com/888079). The nonce must match.
|
|
EXPECT_NE(origin_to_commit, origin_committed);
|
|
|
|
// Both document have the same URL. Only the first sets CSP:sandbox, but both
|
|
// are sandboxed. They get an opaque origin different from each others.
|
|
EXPECT_NE(current_frame_host()->GetLastCommittedOrigin(), origin_committed);
|
|
}
|
|
|
|
// Regression test for https://crbug.com/1158306.
|
|
// Navigate to a response, which set Content-Security-Policy: sandbox AND block
|
|
// the response. The error page shouldn't set sandbox flags.
|
|
IN_PROC_BROWSER_TEST_F(NavigationBrowserTest, ErrorPageFromCspSandboxResponse) {
|
|
// Block every navigation in WillProcessResponse.
|
|
std::unique_ptr<content::TestNavigationThrottleInserter> blocker =
|
|
BlockNavigationWillProcessResponse(web_contents());
|
|
|
|
// Navigate toward a document witch sets CSP:sandbox.
|
|
GURL url = embedded_test_server()->GetURL(
|
|
"a.com", "/set-header?Content-Security-Policy: sandbox");
|
|
TestNavigationManager manager(web_contents(), url);
|
|
shell()->LoadURL(url);
|
|
ASSERT_TRUE(manager.WaitForNavigationFinished());
|
|
|
|
// An error page committed. It doesn't have any sandbox flags, despite the
|
|
// original response headers.
|
|
EXPECT_TRUE(current_frame_host()->IsErrorDocument());
|
|
EXPECT_EQ(network::mojom::WebSandboxFlags::kNone,
|
|
current_frame_host()->active_sandbox_flags());
|
|
|
|
EXPECT_EQ(url, current_frame_host()->GetLastCommittedURL());
|
|
EXPECT_TRUE(current_frame_host()->GetLastCommittedOrigin().opaque());
|
|
EXPECT_TRUE(
|
|
current_frame_host()->GetLastCommittedOrigin().CanBeDerivedFrom(url));
|
|
}
|
|
|
|
IN_PROC_BROWSER_TEST_F(NavigationBrowserTest,
|
|
ProcessShutdownDuringDeferredNavigationThrottle) {
|
|
GURL url = embedded_test_server()->GetURL("a.com", "/empty.html");
|
|
EXPECT_TRUE(NavigateToURL(shell(), url));
|
|
|
|
class ShutdownThrottle : public TaskRunnerDeferringThrottle,
|
|
WebContentsObserver {
|
|
public:
|
|
explicit ShutdownThrottle(WebContents* web_contents,
|
|
NavigationHandle* handle)
|
|
: TaskRunnerDeferringThrottle(
|
|
base::SingleThreadTaskRunner::GetCurrentDefault(),
|
|
/*defer_start=*/false,
|
|
/*defer_redirect=*/false,
|
|
/*defer_response=*/true,
|
|
handle),
|
|
web_contents_(web_contents) {
|
|
WebContentsObserver::Observe(web_contents_);
|
|
}
|
|
|
|
void AsyncResume() override {
|
|
// Shutdown the renderer and delay Resume() until then.
|
|
web_contents_->GetPrimaryMainFrame()->GetProcess()->Shutdown(1);
|
|
}
|
|
|
|
void RenderFrameDeleted(RenderFrameHost* frame_host) override {
|
|
TaskRunnerDeferringThrottle::AsyncResume();
|
|
}
|
|
|
|
private:
|
|
raw_ptr<WebContents> web_contents_;
|
|
};
|
|
|
|
auto inserter = std::make_unique<TestNavigationThrottleInserter>(
|
|
shell()->web_contents(),
|
|
base::BindLambdaForTesting(
|
|
[&](NavigationHandle* handle) -> std::unique_ptr<NavigationThrottle> {
|
|
return std::make_unique<ShutdownThrottle>(shell()->web_contents(),
|
|
handle);
|
|
}));
|
|
|
|
class DoesNotReadyToCommitObserver : public WebContentsObserver {
|
|
public:
|
|
explicit DoesNotReadyToCommitObserver(WebContents* contents)
|
|
: WebContentsObserver(contents) {}
|
|
|
|
// WebContentsObserver overrides.
|
|
void ReadyToCommitNavigation(NavigationHandle* handle) override {
|
|
// This method should not happen. Since the process is destroyed before
|
|
// we become ready to commit, we can not ever reach
|
|
// ReadyToCommitNavigation. Doing so would fail because the renderer is
|
|
// gone.
|
|
ADD_FAILURE() << "ReadyToCommitNavigation but renderer has crashed. "
|
|
"IsRenderFrameLive: "
|
|
<< handle->GetRenderFrameHost()->IsRenderFrameLive();
|
|
navigation_was_ready_to_commit_ = true;
|
|
}
|
|
|
|
void DidFinishNavigation(NavigationHandle* handle) override {
|
|
navigation_finished_ = true;
|
|
navigation_committed_ = handle->HasCommitted();
|
|
}
|
|
|
|
bool navigation_was_ready_to_commit() {
|
|
return navigation_was_ready_to_commit_;
|
|
}
|
|
bool navigation_finished() { return navigation_finished_; }
|
|
bool navigation_committed() { return navigation_committed_; }
|
|
|
|
private:
|
|
bool navigation_was_ready_to_commit_ = false;
|
|
bool navigation_finished_ = false;
|
|
bool navigation_committed_ = false;
|
|
};
|
|
|
|
// Watch that ReadyToCommitNavigation() will not happen when the renderer is
|
|
// gone.
|
|
DoesNotReadyToCommitObserver no_commit_obs(shell()->web_contents());
|
|
|
|
// We will shutdown the renderer during this navigation.
|
|
ScopedAllowRendererCrashes scoped_allow_renderer_crashes;
|
|
|
|
// Important: This is a browser-initiated navigation, so the NavigationRequest
|
|
// does not have an open connection (NavigationClient) to the renderer that it
|
|
// is listening to for termination while running NavigationThrottles.
|
|
//
|
|
// Expect this navigation to be aborted, so we stop waiting after the
|
|
// uncommitted navigation is done.
|
|
GURL url2 = embedded_test_server()->GetURL("a.com", "/title1.html");
|
|
NavigateToURLBlockUntilNavigationsComplete(
|
|
shell(), url2, /*number_of_navigations=*/1,
|
|
/*ignore_uncommitted_navigations=*/false);
|
|
|
|
// The renderer was shutdown mid-navigation.
|
|
EXPECT_FALSE(
|
|
shell()->web_contents()->GetPrimaryMainFrame()->IsRenderFrameLive());
|
|
|
|
// The navigation was aborted, which means it finished but did not commit, and
|
|
// _importantly_ it never reported "ReadyToCommitNavigation" without a live
|
|
// renderer.
|
|
EXPECT_TRUE(no_commit_obs.navigation_finished());
|
|
EXPECT_FALSE(no_commit_obs.navigation_was_ready_to_commit());
|
|
EXPECT_FALSE(no_commit_obs.navigation_committed());
|
|
}
|
|
|
|
// Sandbox flags defined by the parent must not apply to Chrome's error page.
|
|
IN_PROC_BROWSER_TEST_F(NavigationBrowserTest, ErrorPageFromInSandboxedIframe) {
|
|
GURL url = embedded_test_server()->GetURL("a.com", "/empty.html");
|
|
EXPECT_TRUE(NavigateToURL(shell(), url));
|
|
|
|
// Block every navigation in WillProcessResponse.
|
|
std::unique_ptr<content::TestNavigationThrottleInserter> blocker =
|
|
BlockNavigationWillProcessResponse(web_contents());
|
|
|
|
TestNavigationManager manager(web_contents(), url);
|
|
ExecuteScriptAsync(current_frame_host(), R"(
|
|
let iframe = document.createElement("iframe");
|
|
iframe.src = location.href;
|
|
iframe.sandbox = "allow-orientation-lock";
|
|
document.body.appendChild(iframe);
|
|
)");
|
|
ASSERT_TRUE(manager.WaitForNavigationFinished());
|
|
|
|
RenderFrameHostImpl* child_rfh =
|
|
current_frame_host()->child_at(0)->current_frame_host();
|
|
|
|
EXPECT_TRUE(child_rfh->IsErrorDocument());
|
|
EXPECT_EQ(network::mojom::WebSandboxFlags::kNone,
|
|
child_rfh->active_sandbox_flags());
|
|
}
|
|
|
|
IN_PROC_BROWSER_TEST_F(NavigationBrowserTest, OriginToCommitSandboxFromFrame) {
|
|
GURL url = embedded_test_server()->GetURL("a.com", "/empty.html");
|
|
EXPECT_TRUE(NavigateToURL(shell(), url));
|
|
TestNavigationManager manager(web_contents(), url);
|
|
ExecuteScriptAsync(current_frame_host(), R"(
|
|
let iframe = document.createElement("iframe");
|
|
iframe.src = location.href;
|
|
iframe.sandbox = "";
|
|
document.body.appendChild(iframe);
|
|
)");
|
|
EXPECT_TRUE(manager.WaitForResponse());
|
|
FrameTreeNode* iframe = current_frame_host()->child_at(0);
|
|
NavigationRequest* navigation = iframe->navigation_request();
|
|
url::Origin origin_to_commit = navigation->GetOriginToCommit().value();
|
|
ASSERT_TRUE(manager.WaitForNavigationFinished());
|
|
url::Origin origin_committed =
|
|
iframe->current_frame_host()->GetLastCommittedOrigin();
|
|
|
|
EXPECT_TRUE(origin_to_commit.opaque());
|
|
EXPECT_TRUE(origin_committed.opaque());
|
|
// TODO(https://crbug.com/888079). Make the nonce to match.
|
|
EXPECT_NE(origin_to_commit, origin_committed);
|
|
}
|
|
|
|
IN_PROC_BROWSER_TEST_F(NavigationBrowserTest,
|
|
NavigateToAboutBlankWhileFirstNavigationPending) {
|
|
GURL url_a = embedded_test_server()->GetURL("a.com", "/empty.html");
|
|
GURL url_b = embedded_test_server()->GetURL("b.com", "/empty.html");
|
|
|
|
EXPECT_TRUE(NavigateToURL(shell(), url_a));
|
|
|
|
ShellAddedObserver new_shell_observer;
|
|
ExecuteScriptAsync(
|
|
current_frame_host(),
|
|
JsReplace("window.open($1, '_blank').location = 'about:blank'", url_b));
|
|
|
|
WebContents* popup_contents = new_shell_observer.GetShell()->web_contents();
|
|
TestNavigationManager manager_1(popup_contents, url_b);
|
|
TestNavigationManager manager_2(popup_contents, GURL("about:blank"));
|
|
|
|
ASSERT_TRUE(manager_1.WaitForNavigationFinished());
|
|
ASSERT_TRUE(manager_2.WaitForNavigationFinished());
|
|
|
|
EXPECT_EQ(popup_contents->GetLastCommittedURL(), "about:blank");
|
|
}
|
|
|
|
class NetworkIsolationSplitCacheAppendIframeOrigin
|
|
: public NavigationBaseBrowserTest {
|
|
public:
|
|
NetworkIsolationSplitCacheAppendIframeOrigin() {
|
|
feature_list_.InitAndEnableFeature(
|
|
net::features::kSplitCacheByNetworkIsolationKey);
|
|
}
|
|
|
|
private:
|
|
base::test::ScopedFeatureList feature_list_;
|
|
};
|
|
|
|
// Make a main document, have it request a cacheable subresources. Then make a
|
|
// same-site document in an iframe that serves the CSP:Sandbox header. Stop the
|
|
// test server, have the sandboxed document requests the same subresource. The
|
|
// request should fail. To make sure the request is actually in the cache, the
|
|
// main document should be able to request it again.
|
|
IN_PROC_BROWSER_TEST_F(NetworkIsolationSplitCacheAppendIframeOrigin,
|
|
SandboxedUsesDifferentCache) {
|
|
auto server = std::make_unique<net::EmbeddedTestServer>();
|
|
server->AddDefaultHandlers(GetTestDataFilePath());
|
|
EXPECT_TRUE(server->Start());
|
|
|
|
GURL url_main_document = server->GetURL("a.com", "/empty.html");
|
|
|
|
EXPECT_TRUE(NavigateToURL(shell(), url_main_document));
|
|
EXPECT_TRUE(ExecJs(current_frame_host(), R"(
|
|
new Promise(resolve => {
|
|
let iframe = document.createElement("iframe");
|
|
iframe.onload = resolve;
|
|
iframe.src = "/set-header?Content-Security-Policy: sandbox allow-scripts";
|
|
document.body.appendChild(iframe);
|
|
})
|
|
)"));
|
|
EXPECT_TRUE(WaitForLoadStop(web_contents()));
|
|
|
|
RenderFrameHostImpl* main_rfh = current_frame_host();
|
|
RenderFrameHostImpl* sub_rfh = main_rfh->child_at(0)->current_frame_host();
|
|
|
|
EXPECT_FALSE(main_rfh->GetLastCommittedOrigin().opaque());
|
|
EXPECT_TRUE(sub_rfh->GetLastCommittedOrigin().opaque());
|
|
|
|
const char* fetch_cacheable = R"(
|
|
fetch("cacheable.svg")
|
|
.then(() => "success")
|
|
.catch(() => "error")
|
|
)";
|
|
|
|
EXPECT_EQ("success", EvalJs(main_rfh, fetch_cacheable));
|
|
|
|
server.reset();
|
|
|
|
EXPECT_EQ("error", EvalJs(sub_rfh, fetch_cacheable));
|
|
EXPECT_EQ("success", EvalJs(main_rfh, fetch_cacheable));
|
|
}
|
|
|
|
// The Content Security Policy directive 'treat-as-public-address' is parsed
|
|
// into the parsed headers by services/network and applied there. That directive
|
|
// is ignored in report-only policies. Here we check that Blink reports a
|
|
// console message if 'treat-as-public-address' is delivered in a report-only
|
|
// policy. This serves also as a regression test for https://crbug.com/1150314
|
|
IN_PROC_BROWSER_TEST_F(NavigationBrowserTest,
|
|
TreatAsPublicAddressInReportOnly) {
|
|
WebContentsConsoleObserver console_observer(web_contents());
|
|
console_observer.SetPattern(
|
|
"The Content Security Policy directive 'treat-as-public-address' is "
|
|
"ignored when delivered in a report-only policy.");
|
|
|
|
GURL url = embedded_test_server()->GetURL(
|
|
"/set-header?"
|
|
"Content-Security-Policy-Report-Only: treat-as-public-address");
|
|
EXPECT_TRUE(NavigateToURL(shell(), url));
|
|
|
|
ASSERT_TRUE(console_observer.Wait());
|
|
}
|
|
|
|
// The Content Security Policy directive 'plugin-types' has been removed. Here
|
|
// we check that Blink reports a console message if 'plugin-type' is delivered
|
|
// in a policy.
|
|
IN_PROC_BROWSER_TEST_F(NavigationBrowserTest,
|
|
ContentSecurityPolicyErrorPluginTypes) {
|
|
WebContentsConsoleObserver console_observer(web_contents());
|
|
console_observer.SetPattern(
|
|
"The Content-Security-Policy directive 'plugin-types' has been removed "
|
|
"from the specification. "
|
|
"If you want to block plugins, consider specifying \"object-src 'none'\" "
|
|
"instead.");
|
|
|
|
GURL url = embedded_test_server()->GetURL(
|
|
"/set-header?"
|
|
"Content-Security-Policy: plugin-types application/pdf");
|
|
EXPECT_TRUE(NavigateToURL(shell(), url));
|
|
|
|
ASSERT_TRUE(console_observer.Wait());
|
|
}
|
|
|
|
class SubresourceLoadingTest : public NavigationBrowserTest {
|
|
public:
|
|
SubresourceLoadingTest() = default;
|
|
SubresourceLoadingTest(const SubresourceLoadingTest&) = delete;
|
|
SubresourceLoadingTest& operator=(const SubresourceLoadingTest&) = delete;
|
|
|
|
void DontTestNetworkServiceCrashes() {
|
|
test_network_service_crashes_ = false;
|
|
}
|
|
|
|
void VerifyResultsOfAboutBlankNavigation(RenderFrameHost* target_frame,
|
|
RenderFrameHost* initiator_frame) {
|
|
// Verify that `target_frame` has been navigated to "about:blank".
|
|
EXPECT_EQ(GURL(url::kAboutBlankURL), target_frame->GetLastCommittedURL());
|
|
|
|
// Verify that "about:blank" committed with the expected origin, and in the
|
|
// expected SiteInstance.
|
|
EXPECT_EQ(target_frame->GetLastCommittedOrigin(),
|
|
initiator_frame->GetLastCommittedOrigin());
|
|
EXPECT_EQ(target_frame->GetSiteInstance(),
|
|
initiator_frame->GetSiteInstance());
|
|
|
|
// Ask for cookies in the `target_frame`. One implicit verification here
|
|
// is whether this step will hit any `cookie_url`-related NOTREACHED or DwoC
|
|
// in RestrictedCookieManager::ValidateAccessToCookiesAt. This verification
|
|
// is non-racey, because `document.cookie` must have heard back from the
|
|
// RestrictedCookieManager before returning the value of cookies (this
|
|
// ignores possible Blink-side caching, but this is the first time the
|
|
// renderer needs the cookies and so this is okay for this test).
|
|
EXPECT_EQ("", EvalJs(target_frame, "document.cookie"));
|
|
|
|
// Verify that the "about:blank" frame is able to load an image.
|
|
VerifyImageSubresourceLoads(target_frame);
|
|
}
|
|
|
|
void VerifyImageSubresourceLoads(
|
|
const ToRenderFrameHost& target,
|
|
const std::string& target_document = "document") {
|
|
RenderFrameHostImpl* target_frame =
|
|
static_cast<RenderFrameHostImpl*>(target.render_frame_host());
|
|
VerifySingleImageSubresourceLoad(target_frame, target_document);
|
|
|
|
// Verify detecting and recovering from a NetworkService crash (e.g. via the
|
|
// `network_service_disconnect_handler_holder_mojo` field and the
|
|
// UpdateSubresourceLoaderFactories method of RenderFrameHostImpl).
|
|
if (!IsInProcessNetworkService() && test_network_service_crashes_) {
|
|
SimulateNetworkServiceCrash();
|
|
|
|
// In addition to waiting (inside SimulateNetworkServiceCrash above) for
|
|
// getting notified about being disconnected from
|
|
// network::mojom::NetworkServiceTest, we also want to make sure that the
|
|
// relevant RenderFrameHost realizes that the NetworkService has crashed.
|
|
// Which RenderFrameHost is relevant varies from test to test, so we
|
|
// flush multiple frames and use kDoNothingIfNoNetworkServiceConnection.
|
|
FlushNetworkInterfacesInOpenerChain(target_frame);
|
|
|
|
// Rerun the test after the NetworkService crash.
|
|
VerifySingleImageSubresourceLoad(target_frame, target_document);
|
|
}
|
|
}
|
|
|
|
private:
|
|
void FlushNetworkInterfacesInOpenerChain(RenderFrameHostImpl* current_frame) {
|
|
std::set<WebContents*> visited_contents;
|
|
while (true) {
|
|
// Check if we've already visited the current frame tree.
|
|
DCHECK(current_frame);
|
|
WebContents* current_contents =
|
|
WebContents::FromRenderFrameHost(current_frame);
|
|
DCHECK(current_contents);
|
|
if (base::Contains(visited_contents, current_contents))
|
|
break;
|
|
visited_contents.insert(current_contents);
|
|
|
|
// Flush all the frames in the `current_contents's active page.
|
|
current_contents->GetPrimaryMainFrame()->ForEachRenderFrameHost(
|
|
[](RenderFrameHost* frame_to_flush) {
|
|
constexpr bool kDoNothingIfNoNetworkServiceConnection = true;
|
|
frame_to_flush->FlushNetworkAndNavigationInterfacesForTesting(
|
|
kDoNothingIfNoNetworkServiceConnection);
|
|
});
|
|
|
|
// Traverse the `current_frame`'s opener chain.
|
|
if (FrameTreeNode* opener_node =
|
|
current_frame->frame_tree_node()->opener()) {
|
|
current_frame = opener_node->current_frame_host();
|
|
} else {
|
|
break; // Break out of the loop if there is no opener.
|
|
}
|
|
}
|
|
}
|
|
|
|
void VerifySingleImageSubresourceLoad(RenderFrameHost* target,
|
|
const std::string& target_document) {
|
|
// Use a random, GUID-based hostname, to avoid hitting the network cache.
|
|
GURL image_url = embedded_test_server()->GetURL(
|
|
base::Uuid::GenerateRandomV4().AsLowercaseString() + ".com",
|
|
"/blank.jpg");
|
|
const char kScriptTemplate[] = R"(
|
|
new Promise(resolve => {
|
|
let img = document.createElement('img');
|
|
img.src = $1; // `$1` is replaced with the value of `image_url`.
|
|
img.addEventListener('load', () => {
|
|
resolve('allowed');
|
|
});
|
|
img.addEventListener('error', err => {
|
|
resolve(`error: ${err}`);
|
|
});
|
|
|
|
// `%%s` is replaced with the value of `target_document`.
|
|
%s.body.appendChild(img);
|
|
}); )";
|
|
std::string script = base::StringPrintf(
|
|
JsReplace(kScriptTemplate, image_url).c_str(), target_document.c_str());
|
|
EXPECT_EQ("allowed", EvalJs(target, script));
|
|
}
|
|
|
|
bool test_network_service_crashes_ = true;
|
|
};
|
|
|
|
// The test below verifies that an "about:blank" navigation commits with the
|
|
// right origin, even when the initiator of the navigation is not the parent or
|
|
// opener of the frame targeted by the navigation. In the
|
|
// GrandchildToAboutBlank... testcases, the navigation is initiated by the
|
|
// grandparent of the target frame.
|
|
//
|
|
// In this test case there are no process swaps and the parent of the navigated
|
|
// frame is a local frame (even in presence of site-per-process). See also
|
|
// GrandchildToAboutBlank_ABA_CrossSite and
|
|
// GrandchildToAboutBlank_ABB_CrossSite.
|
|
IN_PROC_BROWSER_TEST_F(SubresourceLoadingTest,
|
|
GrandchildToAboutBlank_ABA_SameSite) {
|
|
GURL url(embedded_test_server()->GetURL(
|
|
"a.example.com",
|
|
"/cross_site_iframe_factory.html?"
|
|
"a.example.com(b.example.com(a.example.com))"));
|
|
EXPECT_TRUE(NavigateToURL(shell(), url));
|
|
|
|
// Verify the desired properties of the test setup.
|
|
RenderFrameHostImpl* main_frame = static_cast<RenderFrameHostImpl*>(
|
|
shell()->web_contents()->GetPrimaryMainFrame());
|
|
RenderFrameHostImpl* child_frame =
|
|
main_frame->child_at(0)->current_frame_host();
|
|
RenderFrameHostImpl* grandchild_frame =
|
|
child_frame->child_at(0)->current_frame_host();
|
|
EXPECT_EQ(main_frame->GetSiteInstance(), child_frame->GetSiteInstance());
|
|
EXPECT_EQ(main_frame->GetSiteInstance(), grandchild_frame->GetSiteInstance());
|
|
EXPECT_EQ(main_frame->GetLastCommittedOrigin(),
|
|
grandchild_frame->GetLastCommittedOrigin());
|
|
EXPECT_NE(main_frame->GetLastCommittedOrigin(),
|
|
child_frame->GetLastCommittedOrigin());
|
|
|
|
// Navigate the grandchild frame to about:blank
|
|
ASSERT_TRUE(ExecJs(grandchild_frame, "window.name = 'grandchild'"));
|
|
TestNavigationObserver nav_observer(shell()->web_contents(), 1);
|
|
ASSERT_TRUE(
|
|
ExecJs(main_frame,
|
|
"grandchild_window = window.open('about:blank', 'grandchild')"));
|
|
nav_observer.Wait();
|
|
|
|
// Verify that the grandchild has the same origin as the main frame (*not* the
|
|
// origin of the parent frame).
|
|
main_frame = static_cast<RenderFrameHostImpl*>(
|
|
shell()->web_contents()->GetPrimaryMainFrame());
|
|
child_frame = main_frame->child_at(0)->current_frame_host();
|
|
grandchild_frame = child_frame->child_at(0)->current_frame_host();
|
|
VerifyResultsOfAboutBlankNavigation(grandchild_frame, main_frame);
|
|
}
|
|
|
|
// The test below verifies that an "about:blank" navigation commits with the
|
|
// right origin, even when the initiator of the navigation is not the parent or
|
|
// opener of the frame targeted by the navigation. In the
|
|
// GrandchildToAboutBlank... testcases, the navigation is initiated by the
|
|
// grandparent of the target frame.
|
|
//
|
|
// In this test case there are no process swaps and the parent of the navigated
|
|
// frame is a remote frame (in presence of site-per-process). See also
|
|
// GrandchildToAboutBlank_ABA_SameSite and GrandchildToAboutBlank_ABB_CrossSite.
|
|
IN_PROC_BROWSER_TEST_F(SubresourceLoadingTest,
|
|
GrandchildToAboutBlank_ABA_CrossSite) {
|
|
GURL url(embedded_test_server()->GetURL(
|
|
"a.com", "/cross_site_iframe_factory.html?a(b(a))"));
|
|
EXPECT_TRUE(NavigateToURL(shell(), url));
|
|
|
|
// Verify the desired properties of the test setup.
|
|
RenderFrameHostImpl* main_frame = static_cast<RenderFrameHostImpl*>(
|
|
shell()->web_contents()->GetPrimaryMainFrame());
|
|
RenderFrameHostImpl* child_frame =
|
|
main_frame->child_at(0)->current_frame_host();
|
|
RenderFrameHostImpl* grandchild_frame =
|
|
child_frame->child_at(0)->current_frame_host();
|
|
if (AreDefaultSiteInstancesEnabled()) {
|
|
EXPECT_EQ(main_frame->GetSiteInstance(), child_frame->GetSiteInstance());
|
|
} else {
|
|
EXPECT_NE(main_frame->GetSiteInstance(), child_frame->GetSiteInstance());
|
|
}
|
|
EXPECT_EQ(main_frame->GetSiteInstance(), grandchild_frame->GetSiteInstance());
|
|
EXPECT_EQ(main_frame->GetLastCommittedOrigin(),
|
|
grandchild_frame->GetLastCommittedOrigin());
|
|
EXPECT_NE(main_frame->GetLastCommittedOrigin(),
|
|
child_frame->GetLastCommittedOrigin());
|
|
|
|
// Navigate the grandchild frame to about:blank
|
|
ASSERT_TRUE(ExecJs(grandchild_frame, "window.name = 'grandchild'"));
|
|
TestNavigationObserver nav_observer(shell()->web_contents(), 1);
|
|
ASSERT_TRUE(
|
|
ExecJs(main_frame,
|
|
"grandchild_window = window.open('about:blank', 'grandchild')"));
|
|
nav_observer.Wait();
|
|
|
|
// Verify that the grandchild has the same origin as the main frame (*not* the
|
|
// origin of the parent frame).
|
|
main_frame = static_cast<RenderFrameHostImpl*>(
|
|
shell()->web_contents()->GetPrimaryMainFrame());
|
|
child_frame = main_frame->child_at(0)->current_frame_host();
|
|
grandchild_frame = child_frame->child_at(0)->current_frame_host();
|
|
VerifyResultsOfAboutBlankNavigation(grandchild_frame, main_frame);
|
|
}
|
|
|
|
// The test below verifies that an "about:blank" navigation commits with the
|
|
// right origin, even when the initiator of the navigation is not the parent or
|
|
// opener of the frame targeted by the navigation. In the
|
|
// GrandchildToAboutBlank... testcases, the navigation is initiated by the
|
|
// grandparent of the target frame.
|
|
//
|
|
// In this test case the navigation forces a process swap of the target frame.
|
|
// See also GrandchildToAboutBlank_ABA_SameSite and
|
|
// GrandchildToAboutBlank_ABA_CrossSite.
|
|
IN_PROC_BROWSER_TEST_F(SubresourceLoadingTest,
|
|
GrandchildToAboutBlank_ABB_CrossSite) {
|
|
GURL url(embedded_test_server()->GetURL(
|
|
"a.com", "/cross_site_iframe_factory.html?a(b(b))"));
|
|
EXPECT_TRUE(NavigateToURL(shell(), url));
|
|
|
|
// Verify the desired properties of the test setup.
|
|
RenderFrameHostImpl* main_frame = static_cast<RenderFrameHostImpl*>(
|
|
shell()->web_contents()->GetPrimaryMainFrame());
|
|
RenderFrameHostImpl* child_frame =
|
|
main_frame->child_at(0)->current_frame_host();
|
|
RenderFrameHostImpl* grandchild_frame =
|
|
child_frame->child_at(0)->current_frame_host();
|
|
if (AreDefaultSiteInstancesEnabled()) {
|
|
EXPECT_EQ(main_frame->GetSiteInstance(), child_frame->GetSiteInstance());
|
|
} else {
|
|
EXPECT_NE(main_frame->GetSiteInstance(), child_frame->GetSiteInstance());
|
|
}
|
|
EXPECT_EQ(child_frame->GetSiteInstance(),
|
|
grandchild_frame->GetSiteInstance());
|
|
EXPECT_EQ(child_frame->GetLastCommittedOrigin(),
|
|
grandchild_frame->GetLastCommittedOrigin());
|
|
EXPECT_NE(main_frame->GetLastCommittedOrigin(),
|
|
grandchild_frame->GetLastCommittedOrigin());
|
|
|
|
// Navigate the grandchild frame to about:blank
|
|
ASSERT_TRUE(ExecJs(grandchild_frame, "window.name = 'grandchild'"));
|
|
TestNavigationObserver nav_observer(shell()->web_contents(), 1);
|
|
ASSERT_TRUE(
|
|
ExecJs(main_frame,
|
|
"grandchild_window = window.open('about:blank', 'grandchild')"));
|
|
nav_observer.Wait();
|
|
|
|
// Verify that the grandchild has the same origin as the main frame (*not* the
|
|
// origin of the parent frame).
|
|
main_frame = static_cast<RenderFrameHostImpl*>(
|
|
shell()->web_contents()->GetPrimaryMainFrame());
|
|
child_frame = main_frame->child_at(0)->current_frame_host();
|
|
grandchild_frame = child_frame->child_at(0)->current_frame_host();
|
|
VerifyResultsOfAboutBlankNavigation(grandchild_frame, main_frame);
|
|
}
|
|
|
|
// The test below verifies that an "about:blank" navigation commits with the
|
|
// right origin, even when the initiator of the navigation is not the parent or
|
|
// opener of the frame targeted by the navigation. In the
|
|
// TopToAboutBlank_CrossSite testcase, the top-level navigation is initiated by
|
|
// a cross-site subframe.
|
|
IN_PROC_BROWSER_TEST_F(SubresourceLoadingTest, TopToAboutBlank_CrossSite) {
|
|
GURL url(embedded_test_server()->GetURL(
|
|
"a.com", "/cross_site_iframe_factory.html?a(b)"));
|
|
EXPECT_TRUE(NavigateToURL(shell(), url));
|
|
|
|
// Verify the desired properties of the test setup.
|
|
RenderFrameHostImpl* main_frame = static_cast<RenderFrameHostImpl*>(
|
|
shell()->web_contents()->GetPrimaryMainFrame());
|
|
RenderFrameHostImpl* child_frame =
|
|
main_frame->child_at(0)->current_frame_host();
|
|
if (AreDefaultSiteInstancesEnabled()) {
|
|
EXPECT_EQ(main_frame->GetSiteInstance(), child_frame->GetSiteInstance());
|
|
} else {
|
|
EXPECT_NE(main_frame->GetSiteInstance(), child_frame->GetSiteInstance());
|
|
}
|
|
url::Origin a_origin =
|
|
url::Origin::Create(embedded_test_server()->GetURL("a.com", "/"));
|
|
url::Origin b_origin =
|
|
url::Origin::Create(embedded_test_server()->GetURL("b.com", "/"));
|
|
EXPECT_EQ(a_origin, main_frame->GetLastCommittedOrigin());
|
|
EXPECT_EQ(b_origin, child_frame->GetLastCommittedOrigin());
|
|
|
|
// Have the subframe initiate navigation of the main frame to about:blank.
|
|
//
|
|
// (Note that this scenario is a bit artificial/silly, because the final
|
|
// about:blank frame won't have any same-origin friends that could populate
|
|
// it. OTOH, it is still important to maintain all the invariants in this
|
|
// scenario. And it is still possible that a same-origin frame (e.g. in
|
|
// another window in the same BrowsingInstance) exists and can populate the
|
|
// about:blank frame.
|
|
TestNavigationObserver nav_observer(shell()->web_contents(), 1);
|
|
ASSERT_TRUE(ExecJs(child_frame, "window.top.location = 'about:blank'"));
|
|
nav_observer.Wait();
|
|
|
|
// Verify that the main frame is the only remaining frame and that it has the
|
|
// same origin as the navigation initiator.
|
|
main_frame = static_cast<RenderFrameHostImpl*>(
|
|
shell()->web_contents()->GetPrimaryMainFrame());
|
|
EXPECT_EQ(0u, main_frame->child_count());
|
|
EXPECT_EQ(b_origin, main_frame->GetLastCommittedOrigin());
|
|
EXPECT_EQ(GURL(url::kAboutBlankURL), main_frame->GetLastCommittedURL());
|
|
}
|
|
|
|
// The test below verifies that an "about:blank" navigation commits with the
|
|
// right origin, even when the initiator of the navigation is not the parent or
|
|
// opener of the frame targeted by the navigation. In the
|
|
// SameSiteSiblingToAboutBlank_CrossSiteTop testcase, the navigation is
|
|
// initiated by a same-origin sibling (notably, not by one of target frame's
|
|
// ancestors) and both siblings are subframes of a cross-site main frame.
|
|
IN_PROC_BROWSER_TEST_F(SubresourceLoadingTest,
|
|
SameSiteSiblingToAboutBlank_CrossSiteTop) {
|
|
GURL url(embedded_test_server()->GetURL(
|
|
"a.com", "/cross_site_iframe_factory.html?a(b,b)"));
|
|
EXPECT_TRUE(NavigateToURL(shell(), url));
|
|
|
|
// Name the 2nd child.
|
|
RenderFrameHostImpl* main_frame = static_cast<RenderFrameHostImpl*>(
|
|
shell()->web_contents()->GetPrimaryMainFrame());
|
|
RenderFrameHostImpl* child_frame1 =
|
|
main_frame->child_at(0)->current_frame_host();
|
|
RenderFrameHostImpl* child_frame2 =
|
|
main_frame->child_at(1)->current_frame_host();
|
|
ASSERT_TRUE(ExecJs(child_frame2, "window.name = 'child2'"));
|
|
|
|
// Grab `child2` window from the 1st child...
|
|
ASSERT_TRUE(ExecJs(child_frame1, "child2 = window.open('', 'child2')"));
|
|
// ...but make sure that child2's opener doesn't point to child1.
|
|
ASSERT_TRUE(ExecJs(main_frame, "child2 = window.open('', 'child2')"));
|
|
EXPECT_EQ(true, EvalJs(child_frame2, "window.opener == window.top"));
|
|
|
|
// From child1 initiate navigation of child2 to about:blank.
|
|
TestNavigationObserver nav_observer(shell()->web_contents(), 1);
|
|
ASSERT_TRUE(ExecJs(child_frame1, "child2.location = 'about:blank'"));
|
|
nav_observer.Wait();
|
|
|
|
// Verify that child2 has the origin of the initiator of the navigation.
|
|
main_frame = static_cast<RenderFrameHostImpl*>(
|
|
shell()->web_contents()->GetPrimaryMainFrame());
|
|
child_frame1 = main_frame->child_at(0)->current_frame_host();
|
|
child_frame2 = main_frame->child_at(1)->current_frame_host();
|
|
VerifyResultsOfAboutBlankNavigation(child_frame2, child_frame1);
|
|
}
|
|
|
|
// The test below verifies that an initial empty document has a functional
|
|
// URLLoaderFactory. Note that some aspects of the current behavior (e.g. the
|
|
// synchronous re-navigation) are not spec-compliant - see
|
|
// https://crbug.com/778318 and https://github.com/whatwg/html/issues/3267.
|
|
// Note that the same behavior is expected in the ...NewFrameWithoutSrc and
|
|
// in the ...NewFrameWithAboutBlank testcases.
|
|
IN_PROC_BROWSER_TEST_F(SubresourceLoadingTest,
|
|
URLLoaderFactoryInInitialEmptyDoc_NewFrameWithoutSrc) {
|
|
GURL opener_url(embedded_test_server()->GetURL("/title1.html"));
|
|
EXPECT_TRUE(NavigateToURL(shell(), opener_url));
|
|
|
|
// This inserts an `iframe` element without an `src` attribute. According to
|
|
// some specs "the browsing context will remain at the initial about:blank
|
|
// page", although other specs suggest that there is an explicit, separate
|
|
// navigation. See:
|
|
// https://html.spec.whatwg.org/dev/iframe-embed-object.html#the-iframe-element
|
|
// https://html.spec.whatwg.org/multipage/iframe-embed-object.html#shared-attribute-processing-steps-for-iframe-and-frame-elements
|
|
ASSERT_TRUE(ExecJs(shell(), R"( let ifr = document.createElement('iframe');
|
|
document.body.appendChild(ifr); )"));
|
|
WaitForLoadStop(shell()->web_contents());
|
|
RenderFrameHost* main_frame = shell()->web_contents()->GetPrimaryMainFrame();
|
|
RenderFrameHost* subframe = ChildFrameAt(main_frame, 0);
|
|
|
|
VerifyResultsOfAboutBlankNavigation(subframe, main_frame);
|
|
}
|
|
|
|
// See the doc comment for the
|
|
// URLLoaderFactoryInInitialEmptyDoc_NewFrameWithoutSrc test case.
|
|
IN_PROC_BROWSER_TEST_F(
|
|
SubresourceLoadingTest,
|
|
URLLoaderFactoryInInitialEmptyDoc_NewFrameWithAboutBlank) {
|
|
GURL opener_url(embedded_test_server()->GetURL("/title1.html"));
|
|
EXPECT_TRUE(NavigateToURL(shell(), opener_url));
|
|
|
|
ASSERT_TRUE(ExecJs(shell(), R"( ifr = document.createElement('iframe');
|
|
ifr.src = 'about:blank';
|
|
document.body.appendChild(ifr); )"));
|
|
WaitForLoadStop(shell()->web_contents());
|
|
RenderFrameHost* main_frame = shell()->web_contents()->GetPrimaryMainFrame();
|
|
RenderFrameHost* subframe = ChildFrameAt(main_frame, 0);
|
|
|
|
VerifyResultsOfAboutBlankNavigation(subframe, main_frame);
|
|
}
|
|
|
|
IN_PROC_BROWSER_TEST_F(NavigationBrowserTest,
|
|
SameOriginFlagOfSameOriginAboutBlankNavigation) {
|
|
GURL parent_url(embedded_test_server()->GetURL("a.com", "/empty.html"));
|
|
GURL iframe_url(embedded_test_server()->GetURL("a.com", "/empty.html"));
|
|
EXPECT_TRUE(NavigateToURL(shell(), parent_url));
|
|
|
|
EXPECT_TRUE(ExecJs(current_frame_host(), JsReplace(R"(
|
|
let iframe = document.createElement('iframe');
|
|
iframe.src = $1;
|
|
document.body.appendChild(iframe);
|
|
)",
|
|
iframe_url)));
|
|
WaitForLoadStop(shell()->web_contents());
|
|
|
|
base::RunLoop loop;
|
|
DidFinishNavigationCallback callback(
|
|
shell()->web_contents(),
|
|
base::BindLambdaForTesting([&](NavigationHandle* handle) {
|
|
ASSERT_TRUE(handle->HasCommitted());
|
|
EXPECT_TRUE(handle->IsSameOrigin());
|
|
loop.Quit();
|
|
}));
|
|
|
|
// Changing the src to trigger DidFinishNavigationCallback
|
|
EXPECT_TRUE(ExecJs(current_frame_host(), R"(
|
|
document.querySelector("iframe").src = 'about:blank';
|
|
)"));
|
|
loop.Run();
|
|
}
|
|
|
|
IN_PROC_BROWSER_TEST_F(NavigationBrowserTest,
|
|
SameOriginFlagOfCrossOriginAboutBlankNavigation) {
|
|
GURL parent_url(embedded_test_server()->GetURL("a.com", "/empty.html"));
|
|
GURL iframe_url(embedded_test_server()->GetURL("b.com", "/empty.html"));
|
|
EXPECT_TRUE(NavigateToURL(shell(), parent_url));
|
|
|
|
EXPECT_TRUE(ExecJs(current_frame_host(), JsReplace(R"(
|
|
let iframe = document.createElement('iframe');
|
|
iframe.src = $1;
|
|
document.body.appendChild(iframe);
|
|
)",
|
|
iframe_url)));
|
|
WaitForLoadStop(shell()->web_contents());
|
|
|
|
base::RunLoop loop;
|
|
DidFinishNavigationCallback callback(
|
|
shell()->web_contents(),
|
|
base::BindLambdaForTesting([&](NavigationHandle* handle) {
|
|
ASSERT_TRUE(handle->HasCommitted());
|
|
EXPECT_FALSE(handle->IsSameOrigin());
|
|
loop.Quit();
|
|
}));
|
|
|
|
// Changing the src to trigger DidFinishNavigationCallback
|
|
EXPECT_TRUE(ExecJs(current_frame_host(), R"(
|
|
document.querySelector("iframe").src = 'about:blank';
|
|
)"));
|
|
loop.Run();
|
|
}
|
|
|
|
IN_PROC_BROWSER_TEST_F(NavigationBrowserTest,
|
|
SameOriginFlagOfSrcdocNavigation) {
|
|
GURL url = embedded_test_server()->GetURL("a.com", "/empty.html");
|
|
GURL cross_origin = embedded_test_server()->GetURL("b.com", "/empty.html");
|
|
EXPECT_TRUE(NavigateToURL(shell(), url));
|
|
|
|
// Navigating to about:srcdoc from the initial empty document is always a
|
|
// same-origin navigation:
|
|
// - about:srcdoc is same-origin with the parent.
|
|
// - the initial empty document is same-origin with the parent.
|
|
{
|
|
base::RunLoop loop;
|
|
DidFinishNavigationCallback callback(
|
|
shell()->web_contents(),
|
|
base::BindLambdaForTesting([&](NavigationHandle* handle) {
|
|
ASSERT_TRUE(handle->HasCommitted());
|
|
EXPECT_TRUE(handle->IsSameOrigin());
|
|
loop.Quit();
|
|
}));
|
|
EXPECT_TRUE(ExecJs(current_frame_host(), R"(
|
|
let iframe = document.createElement('iframe');
|
|
iframe.srcdoc = "dummy content";
|
|
document.body.appendChild(iframe);
|
|
)"));
|
|
loop.Run();
|
|
}
|
|
|
|
// Now, navigate cross-origin, and back to about:srcdoc with a brand new
|
|
// iframe. The navigation is now considered cross-origin.
|
|
// - the previous document is cross-origin with the parent.
|
|
// - about:srcdoc is same-origin with the parent.
|
|
{
|
|
EXPECT_TRUE(ExecJs(current_frame_host(), JsReplace(R"(
|
|
let iframe2 = document.createElement('iframe');
|
|
iframe2.src = $1;
|
|
iframe2.id = 'iframe2';
|
|
document.body.appendChild(iframe2);
|
|
)",
|
|
cross_origin)));
|
|
WaitForLoadStop(shell()->web_contents());
|
|
|
|
base::RunLoop loop;
|
|
DidFinishNavigationCallback callback(
|
|
shell()->web_contents(),
|
|
base::BindLambdaForTesting([&](NavigationHandle* handle) {
|
|
ASSERT_TRUE(handle->HasCommitted());
|
|
EXPECT_FALSE(handle->IsSameOrigin());
|
|
loop.Quit();
|
|
}));
|
|
EXPECT_TRUE(ExecJs(current_frame_host(), R"(
|
|
document.getElementById("iframe2").srcdoc = "dummy content";
|
|
)"));
|
|
loop.Run();
|
|
}
|
|
}
|
|
|
|
IN_PROC_BROWSER_TEST_F(NavigationBrowserTest,
|
|
SameOriginFlagOfAboutBlankToAboutBlankNavigation) {
|
|
GURL parent_url(embedded_test_server()->GetURL("a.com", "/empty.html"));
|
|
GURL iframe_url(embedded_test_server()->GetURL("b.com", "/empty.html"));
|
|
EXPECT_TRUE(NavigateToURL(shell(), parent_url));
|
|
|
|
EXPECT_TRUE(ExecJs(main_frame(), JsReplace(R"(
|
|
let iframe = document.createElement('iframe');
|
|
iframe.src = $1;
|
|
document.body.appendChild(iframe);
|
|
)",
|
|
iframe_url)));
|
|
WaitForLoadStop(shell()->web_contents());
|
|
|
|
// Test a same-origin about:blank navigation
|
|
{
|
|
base::RunLoop loop;
|
|
DidFinishNavigationCallback callback(
|
|
shell()->web_contents(),
|
|
base::BindLambdaForTesting([&](NavigationHandle* handle) {
|
|
ASSERT_TRUE(handle->HasCommitted());
|
|
EXPECT_TRUE(handle->IsSameOrigin());
|
|
loop.Quit();
|
|
}));
|
|
RenderFrameHostImpl* child_document =
|
|
current_frame_host()->child_at(0)->current_frame_host();
|
|
EXPECT_TRUE(ExecJs(child_document, R"(location.href = "about:blank";)"));
|
|
loop.Run();
|
|
}
|
|
|
|
// Test another same-origin about:blank navigation
|
|
{
|
|
base::RunLoop loop;
|
|
DidFinishNavigationCallback callback(
|
|
shell()->web_contents(),
|
|
base::BindLambdaForTesting([&](NavigationHandle* handle) {
|
|
ASSERT_TRUE(handle->HasCommitted());
|
|
EXPECT_TRUE(handle->IsSameOrigin());
|
|
loop.Quit();
|
|
}));
|
|
RenderFrameHostImpl* child_document =
|
|
current_frame_host()->child_at(0)->current_frame_host();
|
|
EXPECT_TRUE(ExecJs(child_document, R"(location.href = "about:blank";)"));
|
|
loop.Run();
|
|
}
|
|
|
|
// Test a cross-origin about:blank navigation
|
|
{
|
|
base::RunLoop loop;
|
|
DidFinishNavigationCallback callback(
|
|
shell()->web_contents(),
|
|
base::BindLambdaForTesting([&](NavigationHandle* handle) {
|
|
ASSERT_TRUE(handle->HasCommitted());
|
|
EXPECT_FALSE(handle->IsSameOrigin());
|
|
loop.Quit();
|
|
}));
|
|
EXPECT_TRUE(ExecJs(current_frame_host(), R"(
|
|
document.querySelector('iframe').src = "about:blank";
|
|
)"));
|
|
loop.Run();
|
|
}
|
|
}
|
|
|
|
IN_PROC_BROWSER_TEST_F(NavigationBrowserTest, SameOriginOfSandboxedIframe) {
|
|
EXPECT_TRUE(NavigateToURL(
|
|
shell(), embedded_test_server()->GetURL("a.com", "/empty.html")));
|
|
|
|
base::RunLoop loop;
|
|
DidFinishNavigationCallback callback(
|
|
shell()->web_contents(),
|
|
base::BindLambdaForTesting([&](NavigationHandle* handle) {
|
|
ASSERT_TRUE(handle->HasCommitted());
|
|
// TODO(https://crbug.com/888079) Take sandbox into account. Same Origin
|
|
// should be true
|
|
EXPECT_FALSE(handle->IsSameOrigin());
|
|
loop.Quit();
|
|
}));
|
|
EXPECT_TRUE(ExecJs(current_frame_host(), R"(
|
|
let iframe = document.createElement('iframe');
|
|
iframe.sandbox = "allow-scripts";
|
|
iframe.src = "/empty.html";
|
|
document.body.appendChild(iframe);
|
|
)"));
|
|
loop.Run();
|
|
}
|
|
|
|
// The test below verifies that an initial empty document has a functional
|
|
// URLLoaderFactory.
|
|
IN_PROC_BROWSER_TEST_F(SubresourceLoadingTest,
|
|
URLLoaderFactoryInInitialEmptyDoc_NewPopupToEmptyUrl) {
|
|
GURL opener_url(embedded_test_server()->GetURL("/title1.html"));
|
|
EXPECT_TRUE(NavigateToURL(shell(), opener_url));
|
|
|
|
WebContentsImpl* popup = nullptr;
|
|
{
|
|
WebContentsAddedObserver popup_observer;
|
|
ASSERT_TRUE(ExecJs(shell(), "window.open('', '_blank')"));
|
|
popup = static_cast<WebContentsImpl*>(popup_observer.GetWebContents());
|
|
}
|
|
WaitForLoadStop(popup);
|
|
|
|
// Verify that we are at the initial empty document.
|
|
EXPECT_EQ(1, popup->GetController().GetEntryCount());
|
|
EXPECT_TRUE(popup->GetController().GetLastCommittedEntry()->IsInitialEntry());
|
|
EXPECT_TRUE(
|
|
popup->GetPrimaryFrameTree().root()->is_on_initial_empty_document());
|
|
|
|
// Verify that the `popup` is at "about:blank", with expected origin, with
|
|
// working `document.cookie`, and with working subresource loads.
|
|
VerifyResultsOfAboutBlankNavigation(
|
|
popup->GetPrimaryMainFrame(),
|
|
shell()->web_contents()->GetPrimaryMainFrame());
|
|
}
|
|
|
|
// See the doc comment for the
|
|
// URLLoaderFactoryInInitialEmptyDoc_NewPopupToEmptyUrl test case.
|
|
IN_PROC_BROWSER_TEST_F(SubresourceLoadingTest,
|
|
URLLoaderFactoryInInitialEmptyDoc_NewPopupToAboutBlank) {
|
|
GURL opener_url(embedded_test_server()->GetURL("/title1.html"));
|
|
EXPECT_TRUE(NavigateToURL(shell(), opener_url));
|
|
|
|
WebContentsImpl* popup = nullptr;
|
|
{
|
|
WebContentsAddedObserver popup_observer;
|
|
ASSERT_TRUE(ExecJs(shell(), "window.open('about:blank', '_blank')"));
|
|
popup = static_cast<WebContentsImpl*>(popup_observer.GetWebContents());
|
|
}
|
|
WaitForLoadStop(popup);
|
|
|
|
// Verify that we are at the synchronously committed about:blank document.
|
|
EXPECT_EQ(1, popup->GetController().GetEntryCount());
|
|
EXPECT_TRUE(popup->GetController().GetLastCommittedEntry()->IsInitialEntry());
|
|
EXPECT_TRUE(
|
|
popup->GetPrimaryFrameTree().root()->is_on_initial_empty_document());
|
|
|
|
// Verify other about:blank things.
|
|
VerifyResultsOfAboutBlankNavigation(
|
|
popup->GetPrimaryMainFrame(),
|
|
shell()->web_contents()->GetPrimaryMainFrame());
|
|
}
|
|
|
|
// The test below verifies that error pages have a functional URLLoaderFactory.
|
|
IN_PROC_BROWSER_TEST_F(SubresourceLoadingTest, URLLoaderFactoryInErrorPage) {
|
|
GURL error_url(embedded_test_server()->GetURL("/close-socket"));
|
|
EXPECT_FALSE(NavigateToURL(shell(), error_url));
|
|
VerifyImageSubresourceLoads(shell()->web_contents()->GetPrimaryMainFrame());
|
|
}
|
|
|
|
// The test below verifies that an initial empty document has a functional
|
|
// URLLoaderFactory.
|
|
IN_PROC_BROWSER_TEST_F(
|
|
SubresourceLoadingTest,
|
|
URLLoaderFactoryInInitialEmptyDoc_HungNavigationInSubframe) {
|
|
ASSERT_TRUE(NavigateToURL(
|
|
shell(), embedded_test_server()->GetURL("a.com", "/title1.html")));
|
|
|
|
// Add a subframe that will never commit a navigation (i.e. that will be stuck
|
|
// on the initial empty document).
|
|
const GURL hung_url = embedded_test_server()->GetURL("a.com", "/hung");
|
|
ASSERT_TRUE(
|
|
ExecJs(shell(), JsReplace(R"(ifr = document.createElement('iframe');
|
|
ifr.src = $1;
|
|
document.body.appendChild(ifr); )",
|
|
hung_url)));
|
|
|
|
// No process swaps are expected before ReadyToCommit (which will never happen
|
|
// for a navigation to "/hung"). This test assertion double-checks that the
|
|
// test will cover inheriting URLLoaderFactory from the creator/opener/parent
|
|
// frame.
|
|
RenderFrameHost* main_frame = shell()->web_contents()->GetPrimaryMainFrame();
|
|
RenderFrameHost* subframe = ChildFrameAt(main_frame, 0);
|
|
EXPECT_EQ(main_frame->GetProcess()->GetID(), subframe->GetProcess()->GetID());
|
|
|
|
// Ask the parent to script the same-origin subframe and trigger some HTTP
|
|
// subresource loads within the subframe.
|
|
//
|
|
// This tests the functionality of the URLLoaderFactory that gets used by the
|
|
// initial empty document. In this test, the `request_initiator` will be a
|
|
// non-opaque origin - it requires that the URLLoaderFactory will have a
|
|
// matching `request_initiator_origin_lock` (e.g. inherited from the parent).
|
|
VerifyImageSubresourceLoads(shell(), "ifr.contentDocument");
|
|
}
|
|
|
|
// The test below verifies that an initial empty document has a functional
|
|
// URLLoaderFactory.
|
|
IN_PROC_BROWSER_TEST_F(
|
|
SubresourceLoadingTest,
|
|
URLLoaderFactoryInInitialEmptyDoc_HungNavigationInPopup) {
|
|
ASSERT_TRUE(NavigateToURL(
|
|
shell(), embedded_test_server()->GetURL("a.com", "/title1.html")));
|
|
|
|
// Open a popup window that will never commit a navigation (i.e. that will be
|
|
// stuck on the initial empty document).
|
|
const GURL hung_url = embedded_test_server()->GetURL("a.com", "/hung");
|
|
WebContents* popup = nullptr;
|
|
{
|
|
WebContentsAddedObserver popup_observer;
|
|
ASSERT_TRUE(
|
|
ExecJs(shell(), JsReplace("popup = window.open($1)", hung_url)));
|
|
popup = popup_observer.GetWebContents();
|
|
}
|
|
|
|
// No process swaps are expected before ReadyToCommit (which will never happen
|
|
// for a navigation to "/hung"). This test assertion double-checks that the
|
|
// test will cover inheriting URLLoaderFactory from the creator/opener/parent
|
|
// frame.
|
|
RenderFrameHost* opener_frame =
|
|
shell()->web_contents()->GetPrimaryMainFrame();
|
|
RenderFrameHost* popup_frame = popup->GetPrimaryMainFrame();
|
|
EXPECT_EQ(opener_frame->GetProcess()->GetID(),
|
|
popup_frame->GetProcess()->GetID());
|
|
|
|
// Ask the opener to script the (same-origin) popup window and trigger some
|
|
// HTTP subresource loads within the popup.
|
|
//
|
|
// This tests the functionality of the URLLoaderFactory that gets used by the
|
|
// initial empty document. In this test, the `request_initiator` will be a
|
|
// non-opaque origin - it requires that the URLLoaderFactory will have a
|
|
// matching `request_initiator_origin_lock` (e.g. inherited from the opener).
|
|
VerifyImageSubresourceLoads(shell(), "popup.document");
|
|
|
|
// TODO(https://crbug.com/1194763): Crash recovery doesn't work when there is
|
|
// no opener.
|
|
DontTestNetworkServiceCrashes();
|
|
// Test again after closing the opener..
|
|
shell()->Close();
|
|
VerifyImageSubresourceLoads(popup);
|
|
}
|
|
|
|
// The test below verifies that an initial empty document has a functional
|
|
// URLLoaderFactory. The ...WithClearedOpener testcase is a regression test for
|
|
// https://crbug.com/1191203.
|
|
IN_PROC_BROWSER_TEST_F(
|
|
SubresourceLoadingTest,
|
|
URLLoaderFactoryInInitialEmptyDoc_HungNavigationInPopupWithClearedOpener) {
|
|
ASSERT_TRUE(NavigateToURL(
|
|
shell(), embedded_test_server()->GetURL("a.com", "/title1.html")));
|
|
|
|
// Open a new window that will never commit a navigation (i.e. that will be
|
|
// stuck on the initial empty document). Clearing of `popup.opener` tests if
|
|
// inheriting of URLLoaderFactory from the opener will work when the opener
|
|
// has been cleared in DOM/Javascript.
|
|
const GURL hung_url = embedded_test_server()->GetURL("a.com", "/hung");
|
|
const char kScriptTemplate[] = R"(
|
|
popup = window.open($1);
|
|
popup.opener = null;
|
|
)";
|
|
content::WebContents* popup = nullptr;
|
|
{
|
|
WebContentsAddedObserver popup_observer;
|
|
ASSERT_TRUE(ExecJs(shell(), JsReplace(kScriptTemplate, hung_url)));
|
|
popup = popup_observer.GetWebContents();
|
|
}
|
|
|
|
// No process swaps are expected before ReadyToCommit (which will never happen
|
|
// for a navigation to "/hung"). This test assertion double-checks that the
|
|
// test will cover inheriting URLLoaderFactory from the creator/opener/parent
|
|
// frame. This differentiates the test from the "noopener" case covered in
|
|
// another testcase.
|
|
RenderFrameHost* opener_frame =
|
|
shell()->web_contents()->GetPrimaryMainFrame();
|
|
RenderFrameHost* popup_frame = popup->GetPrimaryMainFrame();
|
|
EXPECT_EQ(opener_frame->GetProcess()->GetID(),
|
|
popup_frame->GetProcess()->GetID());
|
|
|
|
// Double-check that the popup didn't commit any navigation and that it has
|
|
// an the same origin as the initial opener.
|
|
EXPECT_EQ(GURL(), popup->GetPrimaryMainFrame()->GetLastCommittedURL());
|
|
EXPECT_NE("null", EvalJs(popup, "window.origin"));
|
|
EXPECT_EQ(shell()
|
|
->web_contents()
|
|
->GetPrimaryMainFrame()
|
|
->GetLastCommittedOrigin()
|
|
.Serialize(),
|
|
EvalJs(popup, "window.origin"));
|
|
|
|
// Use the parent frame's `popup` reference to script the same-origin popup
|
|
// window and trigger some HTTP subresource loads within the popup.
|
|
//
|
|
// This tests the functionality of the URLLoaderFactory that gets used by the
|
|
// initial empty document. In this test, the `request_initiator` will be a
|
|
// non-opaque origin - it requires that the URLLoaderFactory will have a
|
|
// matching `request_initiator_origin_lock` (e.g. inherited from the opener).
|
|
VerifyImageSubresourceLoads(popup);
|
|
|
|
// TODO(https://crbug.com/1194763): Crash recovery doesn't work when there is
|
|
// no opener.
|
|
DontTestNetworkServiceCrashes();
|
|
// Test again after closing the opener..
|
|
shell()->Close();
|
|
VerifyImageSubresourceLoads(popup);
|
|
}
|
|
|
|
// The test below verifies that an initial empty document has a functional
|
|
// URLLoaderFactory.
|
|
IN_PROC_BROWSER_TEST_F(SubresourceLoadingTest,
|
|
URLLoaderFactoryInInitialEmptyDoc_204NoOpenerPopup) {
|
|
ASSERT_TRUE(NavigateToURL(
|
|
shell(), embedded_test_server()->GetURL("a.com", "/title1.html")));
|
|
|
|
// Open a new window by following a no-opener link to /nocontent (204).
|
|
const GURL no_content_url =
|
|
embedded_test_server()->GetURL("a.com", "/nocontent");
|
|
const char kScriptTemplate[] = R"(
|
|
let anchor = document.createElement('a');
|
|
anchor.href = $1;
|
|
anchor.rel = 'noopener';
|
|
anchor.target = '_blank';
|
|
anchor.innerText = 'test link';
|
|
document.body.appendChild(anchor);
|
|
anchor.click();
|
|
)";
|
|
content::WebContents* popup = nullptr;
|
|
{
|
|
WebContentsAddedObserver popup_observer;
|
|
ASSERT_TRUE(ExecJs(shell(), JsReplace(kScriptTemplate, no_content_url)));
|
|
popup = popup_observer.GetWebContents();
|
|
}
|
|
WaitForLoadStop(popup);
|
|
|
|
// Double-check that the `popup` didn't commit any navigation and that it has
|
|
// an opaque origin.
|
|
EXPECT_EQ(GURL(), popup->GetPrimaryMainFrame()->GetLastCommittedURL());
|
|
EXPECT_EQ("null", EvalJs(popup, "window.origin"));
|
|
|
|
// Process swap is expected because of 'noopener'. This test assertion
|
|
// double-checks that in the test it is not possible to inheriting
|
|
// URLLoaderFactory from the creator/opener/parent frame (because the popup is
|
|
// in another process).
|
|
RenderFrameHost* opener_frame =
|
|
shell()->web_contents()->GetPrimaryMainFrame();
|
|
RenderFrameHost* popup_frame = popup->GetPrimaryMainFrame();
|
|
EXPECT_NE(opener_frame->GetProcess()->GetID(),
|
|
popup_frame->GetProcess()->GetID());
|
|
|
|
// Inject Javascript that triggers some subresource loads over HTTP.
|
|
//
|
|
// To some extent, this simulates an ability of 1) Android WebView (see
|
|
// https://crbug.com/1189838) and 2) Chrome Extensions, to inject Javascript
|
|
// into an initial empty document (even when no web/renderer content has
|
|
// access to the document).
|
|
//
|
|
// This tests the functionality of the URLLoaderFactory that gets used by the
|
|
// initial empty document. In this test, the `request_initiator` will be an
|
|
// opaque, unique origin (since nothing has committed yet) and will be
|
|
// compatible with `request_initiator_origin_lock` of the URLLoaderFactory.
|
|
VerifyImageSubresourceLoads(popup);
|
|
}
|
|
|
|
// The test below verifies that an initial empty document has a functional
|
|
// URLLoaderFactory.
|
|
IN_PROC_BROWSER_TEST_F(
|
|
SubresourceLoadingTest,
|
|
URLLoaderFactoryInInitialEmptyDoc_HungNavigationInNewWindow) {
|
|
// Open a new shell, starting at the "/hung" URL.
|
|
const GURL hung_url = embedded_test_server()->GetURL("a.com", "/hung");
|
|
Shell* new_shell =
|
|
Shell::CreateNewWindow(shell()->web_contents()->GetBrowserContext(),
|
|
hung_url, nullptr, gfx::Size());
|
|
|
|
// Wait until the renderer process launches (this will flush the CreateView
|
|
// IPC and make sure that ExecJs and EvalJs are able to work).
|
|
RenderFrameHost* main_frame =
|
|
new_shell->web_contents()->GetPrimaryMainFrame();
|
|
{
|
|
RenderProcessHostWatcher process_watcher(
|
|
main_frame->GetProcess(),
|
|
RenderProcessHostWatcher::WATCH_FOR_PROCESS_READY);
|
|
process_watcher.Wait();
|
|
}
|
|
|
|
// Double-check that the new shell didn't commit any navigation and that it
|
|
// has an opaque origin.
|
|
EXPECT_EQ(1, new_shell->web_contents()->GetController().GetEntryCount());
|
|
EXPECT_TRUE(new_shell->web_contents()
|
|
->GetController()
|
|
.GetLastCommittedEntry()
|
|
->IsInitialEntry());
|
|
EXPECT_EQ(GURL(), main_frame->GetLastCommittedURL());
|
|
EXPECT_EQ("null", EvalJs(main_frame, "window.origin"));
|
|
|
|
// Inject Javascript that triggers some subresource loads over HTTP.
|
|
//
|
|
// To some extent, this simulates an ability of 1) Android WebView (see
|
|
// https://crbug.com/1189838) and 2) Chrome Extensions, to inject Javascript
|
|
// into an initial empty document (even when no web/renderer content has
|
|
// access to the document).
|
|
//
|
|
// This tests the functionality of the URLLoaderFactory that gets used by the
|
|
// initial empty document. In this test, the `request_initiator` will be an
|
|
// opaque, unique origin (since nothing has committed yet) and will be
|
|
// compatible with `request_initiator_origin_lock` of the URLLoaderFactory.
|
|
VerifyImageSubresourceLoads(main_frame);
|
|
}
|
|
|
|
namespace {
|
|
|
|
struct Result {
|
|
GURL url;
|
|
absl::optional<url::Origin> origin;
|
|
bool committed;
|
|
};
|
|
|
|
class NavigationLogger : public WebContentsObserver {
|
|
public:
|
|
explicit NavigationLogger(WebContents* contents)
|
|
: WebContentsObserver(contents) {}
|
|
|
|
// WebContentsObserver overrides:
|
|
void DidFinishNavigation(NavigationHandle* handle) override {
|
|
if (handle->HasCommitted()) {
|
|
EXPECT_EQ(handle->GetRenderFrameHost()->GetLastCommittedURL(),
|
|
handle->GetURL());
|
|
RenderFrameHost* rfh = handle->GetRenderFrameHost();
|
|
results_.push_back({.url = rfh->GetLastCommittedURL(),
|
|
.origin = rfh->GetLastCommittedOrigin(),
|
|
.committed = true});
|
|
} else {
|
|
results_.push_back({.url = handle->GetURL(), .committed = false});
|
|
}
|
|
}
|
|
|
|
const std::vector<Result>& results() const { return results_; }
|
|
|
|
private:
|
|
std::vector<Result> results_;
|
|
};
|
|
|
|
} // namespace
|
|
|
|
class UndoCommitNavigationBrowserTest : public NavigationBrowserTest {
|
|
public:
|
|
UndoCommitNavigationBrowserTest() {
|
|
std::map<std::string, std::string> parameters = {
|
|
{"level", "none"},
|
|
};
|
|
feature_list_.InitAndEnableFeatureWithParameters(
|
|
kQueueNavigationsWhileWaitingForCommit, parameters);
|
|
}
|
|
|
|
void SetUpOnMainThread() override {
|
|
// These navigation tests require full site isolation since they test races
|
|
// with committing a navigation in a speculative RenderFrameHost..
|
|
if (!AreAllSitesIsolatedForTesting()) {
|
|
GTEST_SKIP() << "Site isolation is not enabled!";
|
|
}
|
|
|
|
NavigationBrowserTest::SetUpOnMainThread();
|
|
}
|
|
|
|
void SetUpCommandLine(base::CommandLine* command_line) override {
|
|
NavigationBrowserTest::SetUpCommandLine(command_line);
|
|
|
|
// PerformanceManager maintains its own parallel frame tree and has
|
|
// sometimes been confused by things like `UndoCommitNavigation()`.
|
|
// Force-enable it for test coverage; otherwise, by default,
|
|
// PerformanceManager uses the dummy implementation.
|
|
//
|
|
// TODO(https://crbug.com/1222647): Enable this by default in content_shell.
|
|
command_line->AppendSwitchASCII(switches::kEnableBlinkFeatures,
|
|
"PerformanceManagerInstrumentation");
|
|
}
|
|
|
|
private:
|
|
base::test::ScopedFeatureList feature_list_;
|
|
};
|
|
|
|
// A helper that invokes `functor` on the next `DidStartNavigation()`.
|
|
template <typename F>
|
|
void OnNextDidStartNavigation(WebContents* web_contents, F&& functor) {
|
|
class Observer : public WebContentsObserver {
|
|
public:
|
|
using Callback = base::OnceCallback<void(NavigationHandle*)>;
|
|
|
|
explicit Observer(WebContents* web_contents, Callback callback)
|
|
: WebContentsObserver(web_contents), callback_(std::move(callback)) {}
|
|
|
|
// WebContentsObserver overrides:
|
|
void DidStartNavigation(NavigationHandle* handle) override {
|
|
std::move(callback_).Run(handle);
|
|
delete this;
|
|
}
|
|
|
|
private:
|
|
Callback callback_;
|
|
};
|
|
|
|
new Observer(web_contents,
|
|
base::BindLambdaForTesting(std::forward<F>(functor)));
|
|
}
|
|
|
|
IN_PROC_BROWSER_TEST_F(UndoCommitNavigationBrowserTest,
|
|
PerformanceManagerFrameTreeConsistency) {
|
|
// PerformanceManager reports when a remote frame is attached to a local
|
|
// parent, and it was previously getting confused by the fact that a
|
|
// `blink::RemoteFrame` with matching RemoteFrameTokens was being reported as
|
|
// attached twice: once by the initial page loaded in the next statement, and
|
|
// the next when the browser needs to send a `UndoCommitNavigation()` to the
|
|
// a.com renderer.
|
|
ASSERT_TRUE(NavigateToURL(
|
|
shell(), embedded_test_server()->GetURL(
|
|
"a.com", "/cross_site_iframe_factory.html?a(b)")));
|
|
|
|
WebContentsImpl* web_contents =
|
|
static_cast<WebContentsImpl*>(shell()->web_contents());
|
|
FrameTreeNode* first_subframe_node =
|
|
web_contents->GetPrimaryMainFrame()->child_at(0);
|
|
RenderProcessHost* const a_com_render_process_host =
|
|
web_contents->GetPrimaryFrameTree()
|
|
.root()
|
|
->render_manager()
|
|
->current_frame_host()
|
|
->GetProcess();
|
|
|
|
NavigationLogger logger(web_contents);
|
|
|
|
// Start a navigation that will create a speculative RFH in the existing
|
|
// render process for a.com.
|
|
const GURL infinitely_loading_url =
|
|
embedded_test_server()->GetURL("a.com", "/infinitely_loading_image.html");
|
|
ASSERT_TRUE(BeginNavigateToURLFromRenderer(first_subframe_node,
|
|
infinitely_loading_url));
|
|
|
|
// Ensure the speculative RFH is in the expected process.
|
|
RenderFrameHostImpl* speculative_render_frame_host =
|
|
first_subframe_node->render_manager()->speculative_frame_host();
|
|
ASSERT_TRUE(speculative_render_frame_host);
|
|
EXPECT_EQ(a_com_render_process_host,
|
|
speculative_render_frame_host->GetProcess());
|
|
|
|
// Pause (and ignore) the next `DidCommitProvisionalLoad()` for a.com.
|
|
CommitNavigationPauser commit_pauser(speculative_render_frame_host);
|
|
commit_pauser.WaitForCommitAndPause();
|
|
|
|
// Update the id attribute to exercise a PerformanceManager-specific code
|
|
// path: when the renderer swaps in a `blink::RemoteFrame` to undo the
|
|
// `CommitNavigation()`, it will report the iframe attribution data again.
|
|
// PerformanceManager should not complain that V8ContextTracker already has
|
|
// the iframe attribution data, nor should it update the iframe attribution
|
|
// data, to preserve existing behavior (unfortunately, the latter part is not
|
|
// really tested in this browser test).
|
|
EXPECT_TRUE(ExecJs(web_contents,
|
|
"document.querySelector('iframe').id = 'new-name';"));
|
|
|
|
// Now begin a new navigation to c.com while the previous a.com navigation
|
|
// above is paused in the pending commit state.
|
|
const GURL final_url =
|
|
embedded_test_server()->GetURL("c.com", "/title1.html");
|
|
ASSERT_TRUE(BeginNavigateToURLFromRenderer(first_subframe_node, final_url));
|
|
|
|
EXPECT_TRUE(WaitForLoadStop(web_contents));
|
|
EXPECT_EQ(final_url, first_subframe_node->render_manager()
|
|
->current_frame_host()
|
|
->GetLastCommittedURL());
|
|
|
|
auto results = logger.results();
|
|
ASSERT_EQ(2u, results.size());
|
|
// This test always uses UndoCommitNavigation, so navigation corresponding to
|
|
// the paused commit should never commit.
|
|
EXPECT_FALSE(results[0].committed);
|
|
EXPECT_EQ(absl::nullopt, results[0].origin);
|
|
EXPECT_EQ(infinitely_loading_url, results[0].url);
|
|
EXPECT_TRUE(results[1].committed);
|
|
EXPECT_EQ(embedded_test_server()->GetOrigin("c.com"), results[1].origin);
|
|
EXPECT_EQ(final_url, results[1].url);
|
|
}
|
|
|
|
// Regression test for https://crbug.com/1223837. Previously, if a child frame
|
|
// was in the middle of committing a navigation to a provisional frame in render
|
|
// process B while render process A simultaneously detaches that child frame,
|
|
// the detach message would never be received by render process B.
|
|
IN_PROC_BROWSER_TEST_F(UndoCommitNavigationBrowserTest,
|
|
DetachAfterCommitNavigationInSubFrame) {
|
|
ASSERT_TRUE(NavigateToURL(
|
|
shell(), embedded_test_server()->GetURL(
|
|
"a.com", "/cross_site_iframe_factory.html?a(b,a)")));
|
|
|
|
WebContentsImpl* const web_contents =
|
|
static_cast<WebContentsImpl*>(shell()->web_contents());
|
|
FrameTreeNode* const first_subframe_node =
|
|
web_contents->GetPrimaryMainFrame()->child_at(0);
|
|
FrameTreeNode* const second_subframe_node =
|
|
web_contents->GetPrimaryMainFrame()->child_at(1);
|
|
RenderProcessHost* const b_com_render_process_host =
|
|
first_subframe_node->render_manager()->current_frame_host()->GetProcess();
|
|
|
|
// Start a navigation in the second child frame that will create a speculative
|
|
// RFH in the existing render process for b.com. The first child frame is
|
|
// already hosted in the render process for b.com: this is to ensure the
|
|
// render process remains live even after the second child frame is detached
|
|
// later in this test.
|
|
ASSERT_TRUE(BeginNavigateToURLFromRenderer(
|
|
second_subframe_node,
|
|
embedded_test_server()->GetURL("b.com", "/title1.html")));
|
|
|
|
// Ensure the speculative RFH is in the expected process.
|
|
RenderFrameHostImpl* speculative_render_frame_host =
|
|
second_subframe_node->render_manager()->speculative_frame_host();
|
|
ASSERT_TRUE(speculative_render_frame_host);
|
|
EXPECT_EQ(b_com_render_process_host,
|
|
speculative_render_frame_host->GetProcess());
|
|
|
|
// Pause (and ignore) the next `DidCommitProvisionalLoad()` for b.com.
|
|
CommitNavigationPauser commit_pauser(speculative_render_frame_host);
|
|
commit_pauser.WaitForCommitAndPause();
|
|
|
|
// At this point, the b.com renderer has already committed the RenderFrame,
|
|
// but on the browser side, the RenderFrameHost is still speculative.
|
|
|
|
// Intentionally do not wait for script completion here. This runs an event
|
|
// loop that pumps incoming messages, but IPCs from b.com would be processed
|
|
// out of order, since the `DidCommitProvisionalLoad()` attempt was previously
|
|
// paused above.
|
|
ExecuteScriptAsync(
|
|
web_contents,
|
|
JsReplace("document.querySelectorAll('iframe')[1].remove()"));
|
|
|
|
// However, since it's not possible to wait for `remove()` to take effect,
|
|
// the test must cheat a little and directly call the Mojo IPC that the JS
|
|
// above would eventually trigger.
|
|
second_subframe_node->render_manager()->current_frame_host()->Detach();
|
|
|
|
EXPECT_TRUE(WaitForLoadStop(web_contents));
|
|
// Validate that render process for b.com has handled the detach message for
|
|
// the provisional frame that was committing. Before the fix, the render
|
|
// process for b.com still had the proxy for the second child frame, because
|
|
// the browser process's request to delete it was sent via a broken message
|
|
// pipe. Thus, the frame tree in the render process for b.com incorrectly
|
|
// thought there were still two child frames.
|
|
EXPECT_EQ(1, EvalJs(first_subframe_node, "top.length"));
|
|
}
|
|
|
|
class CommitNavigationRaceBrowserTest
|
|
: public NavigationBrowserTest,
|
|
public ::testing::WithParamInterface<bool> {
|
|
public:
|
|
CommitNavigationRaceBrowserTest() {
|
|
std::map<std::string, std::string> parameters = {
|
|
{"level", GetParam() ? "full" : "none"},
|
|
};
|
|
feature_list_.InitAndEnableFeatureWithParameters(
|
|
kQueueNavigationsWhileWaitingForCommit, parameters);
|
|
}
|
|
|
|
void SetUpOnMainThread() override {
|
|
// These navigation tests require full site isolation since they test races
|
|
// with committing a navigation in a speculative RenderFrameHost..
|
|
if (!AreAllSitesIsolatedForTesting()) {
|
|
GTEST_SKIP();
|
|
}
|
|
|
|
NavigationBrowserTest::SetUpOnMainThread();
|
|
}
|
|
|
|
static std::string DescribeParams(
|
|
const testing::TestParamInfo<ParamType>& info) {
|
|
return info.param ? "NavigationQueueing" : "UndoCommitNavigation";
|
|
}
|
|
|
|
private:
|
|
base::test::ScopedFeatureList feature_list_;
|
|
};
|
|
|
|
class ResumeCommitClosureSetWaiter {
|
|
public:
|
|
explicit ResumeCommitClosureSetWaiter(NavigationHandle* handle) {
|
|
// `Helper` "observes" the set via its own destruction, since the navigation
|
|
// code setting a resume commit closure will replace the testing one
|
|
// installed here.
|
|
NavigationRequest::From(handle)->set_resume_commit_closure(
|
|
base::DoNothingWithBoundArgs(std::make_unique<Helper>(loop_)));
|
|
}
|
|
|
|
void Wait() { loop_.Run(); }
|
|
|
|
private:
|
|
class Helper {
|
|
public:
|
|
explicit Helper(base::RunLoop& loop) : loop_(loop) {}
|
|
|
|
~Helper() { loop_->Quit(); }
|
|
|
|
private:
|
|
raw_ref<base::RunLoop> loop_;
|
|
};
|
|
|
|
base::RunLoop loop_;
|
|
};
|
|
|
|
IN_PROC_BROWSER_TEST_P(CommitNavigationRaceBrowserTest,
|
|
BeginNewNavigationDuringCommitNavigationInMainFrame) {
|
|
ASSERT_TRUE(NavigateToURL(
|
|
shell(), embedded_test_server()->GetURL("a.com", "/title1.html")));
|
|
|
|
// Prior to implementing the UndoCommitNavigation() workaround, the race
|
|
// condition being tested would result in a crash in the b.com renderer. Open
|
|
// another b.com window in the same browsing instance to verify that the b.com
|
|
// renderer does not unexpectedly crash even if the b.com speculative
|
|
// RenderFrameHost is discarded.
|
|
ASSERT_TRUE(ExecJs(
|
|
shell(), JsReplace("window.open($1)", embedded_test_server()->GetURL(
|
|
"b.com", "/title1.html"))));
|
|
ASSERT_EQ(2u, Shell::windows().size());
|
|
WebContentsImpl* new_web_contents =
|
|
static_cast<WebContentsImpl*>(Shell::windows()[1]->web_contents());
|
|
WaitForLoadStop(new_web_contents);
|
|
RenderProcessHost* const b_com_render_process_host =
|
|
new_web_contents->GetPrimaryMainFrame()->GetProcess();
|
|
|
|
NavigationLogger logger(shell()->web_contents());
|
|
|
|
// Start a navigation that will create a speculative RFH in the existing
|
|
// render process for b.com.
|
|
const GURL infinitely_loading_url =
|
|
embedded_test_server()->GetURL("b.com", "/infinitely_loading_image.html");
|
|
ASSERT_TRUE(BeginNavigateToURLFromRenderer(shell(), infinitely_loading_url));
|
|
|
|
// Ensure the speculative RFH is in the expected process (i.e. the b.com
|
|
// process that was created for the navigation in the new window earlier).
|
|
WebContentsImpl* web_contents =
|
|
static_cast<WebContentsImpl*>(shell()->web_contents());
|
|
RenderFrameHostImpl* speculative_render_frame_host =
|
|
web_contents->GetPrimaryFrameTree()
|
|
.root()
|
|
->render_manager()
|
|
->speculative_frame_host();
|
|
ASSERT_TRUE(speculative_render_frame_host);
|
|
EXPECT_EQ(b_com_render_process_host,
|
|
speculative_render_frame_host->GetProcess());
|
|
|
|
// Pause (and potentially ignore, if navigation queueing is disabled) the next
|
|
// `DidCommitProvisionalLoad()` for b.com.
|
|
CommitNavigationPauser commit_pauser(speculative_render_frame_host);
|
|
commit_pauser.WaitForCommitAndPause();
|
|
|
|
absl::optional<ResumeCommitClosureSetWaiter> resume_commit_closure_set_waiter;
|
|
if (ShouldQueueNavigationsWhenPendingCommitRFHExists()) {
|
|
// If navigation queueing is enabled, the test should verify that a resume
|
|
// commit closure is actually set. Install a watcher now, before beginning
|
|
// the next navigation, since the resume commit closure may be synchronously
|
|
// set while handling the `BeginNavigation()` IPC in the browser.
|
|
OnNextDidStartNavigation(web_contents, [&](NavigationHandle* handle) {
|
|
resume_commit_closure_set_waiter.emplace(handle);
|
|
});
|
|
}
|
|
|
|
// Now begin a new navigation to c.com while the previous b.com navigation
|
|
// above is paused in the pending commit state.
|
|
const GURL final_url =
|
|
embedded_test_server()->GetURL("c.com", "/title1.html");
|
|
ASSERT_TRUE(BeginNavigateToURLFromRenderer(web_contents, final_url));
|
|
|
|
if (ShouldQueueNavigationsWhenPendingCommitRFHExists()) {
|
|
resume_commit_closure_set_waiter->Wait();
|
|
commit_pauser.ResumePausedCommit();
|
|
}
|
|
|
|
EXPECT_TRUE(WaitForLoadStop(web_contents));
|
|
EXPECT_EQ(final_url, web_contents->GetLastCommittedURL());
|
|
|
|
auto results = logger.results();
|
|
ASSERT_EQ(2u, results.size());
|
|
EXPECT_EQ(infinitely_loading_url, results[0].url);
|
|
// If navigation queueing is enabled, the first navigation will complete the
|
|
// commit as the new navigation gets queued until the first navigation's
|
|
// commit finished. If navigation queueing is disabled, the pending commit
|
|
// navigation will be cancelled.
|
|
if (ShouldQueueNavigationsWhenPendingCommitRFHExists()) {
|
|
EXPECT_TRUE(results[0].committed);
|
|
EXPECT_EQ(embedded_test_server()->GetOrigin("b.com"), results[0].origin);
|
|
} else {
|
|
EXPECT_FALSE(results[0].committed);
|
|
EXPECT_EQ(absl::nullopt, results[0].origin);
|
|
}
|
|
EXPECT_TRUE(results[1].committed);
|
|
EXPECT_EQ(embedded_test_server()->GetOrigin("c.com"), results[1].origin);
|
|
EXPECT_EQ(final_url, results[1].url);
|
|
}
|
|
|
|
IN_PROC_BROWSER_TEST_P(CommitNavigationRaceBrowserTest,
|
|
BeginNewNavigationDuringCommitNavigationInSubFrame) {
|
|
ASSERT_TRUE(NavigateToURL(
|
|
shell(), embedded_test_server()->GetURL(
|
|
"b.com", "/cross_site_iframe_factory.html?b(a)")));
|
|
|
|
WebContentsImpl* web_contents =
|
|
static_cast<WebContentsImpl*>(shell()->web_contents());
|
|
FrameTreeNode* first_subframe_node =
|
|
web_contents->GetPrimaryMainFrame()->child_at(0);
|
|
RenderProcessHost* const b_com_render_process_host =
|
|
web_contents->GetPrimaryFrameTree()
|
|
.root()
|
|
->render_manager()
|
|
->current_frame_host()
|
|
->GetProcess();
|
|
|
|
NavigationLogger logger(web_contents);
|
|
|
|
// Start a navigation that will create a speculative RFH in the existing
|
|
// render process for b.com.
|
|
const GURL infinitely_loading_url =
|
|
embedded_test_server()->GetURL("b.com", "/infinitely_loading_image.html");
|
|
ASSERT_TRUE(BeginNavigateToURLFromRenderer(first_subframe_node,
|
|
infinitely_loading_url));
|
|
|
|
// Ensure the speculative RFH is in the expected process.
|
|
RenderFrameHostImpl* speculative_render_frame_host =
|
|
first_subframe_node->render_manager()->speculative_frame_host();
|
|
ASSERT_TRUE(speculative_render_frame_host);
|
|
EXPECT_EQ(b_com_render_process_host,
|
|
speculative_render_frame_host->GetProcess());
|
|
|
|
// Pause (and potentially ignore, if navigation queueing is disabled) the next
|
|
// `DidCommitProvisionalLoad()` for b.com.
|
|
CommitNavigationPauser commit_pauser(speculative_render_frame_host);
|
|
commit_pauser.WaitForCommitAndPause();
|
|
|
|
absl::optional<ResumeCommitClosureSetWaiter> resume_commit_closure_set_waiter;
|
|
if (ShouldQueueNavigationsWhenPendingCommitRFHExists()) {
|
|
// If navigation queueing is enabled, the test should verify that a resume
|
|
// commit closure is actually set. Install a watcher now, before beginning
|
|
// the next navigation, since the resume commit closure may be synchronously
|
|
// set while handling the `BeginNavigation()` IPC in the browser.
|
|
OnNextDidStartNavigation(web_contents, [&](NavigationHandle* handle) {
|
|
resume_commit_closure_set_waiter.emplace(handle);
|
|
});
|
|
}
|
|
|
|
// Now begin a new navigation to c.com while the previous b.com navigation
|
|
// above is paused in the pending commit state.
|
|
const GURL final_url =
|
|
embedded_test_server()->GetURL("c.com", "/title1.html");
|
|
ASSERT_TRUE(BeginNavigateToURLFromRenderer(first_subframe_node, final_url));
|
|
|
|
if (ShouldQueueNavigationsWhenPendingCommitRFHExists()) {
|
|
resume_commit_closure_set_waiter->Wait();
|
|
commit_pauser.ResumePausedCommit();
|
|
}
|
|
|
|
EXPECT_TRUE(WaitForLoadStop(web_contents));
|
|
EXPECT_EQ(final_url, first_subframe_node->render_manager()
|
|
->current_frame_host()
|
|
->GetLastCommittedURL());
|
|
|
|
auto results = logger.results();
|
|
ASSERT_EQ(2u, results.size());
|
|
// If navigation queueing is enabled, the first navigation will complete the
|
|
// commit as the new navigation gets queued until the first navigation's
|
|
// commit finished. If navigation queueing is disabled, the pending commit
|
|
// navigation will be cancelled.
|
|
if (ShouldQueueNavigationsWhenPendingCommitRFHExists()) {
|
|
EXPECT_TRUE(results[0].committed);
|
|
EXPECT_EQ(embedded_test_server()->GetOrigin("b.com"), results[0].origin);
|
|
} else {
|
|
EXPECT_FALSE(results[0].committed);
|
|
EXPECT_EQ(absl::nullopt, results[0].origin);
|
|
}
|
|
EXPECT_EQ(infinitely_loading_url, results[0].url);
|
|
EXPECT_TRUE(results[1].committed);
|
|
EXPECT_EQ(embedded_test_server()->GetOrigin("c.com"), results[1].origin);
|
|
EXPECT_EQ(final_url, results[1].url);
|
|
}
|
|
|
|
// Test the behavior of a navigation that gets suspended in the pending commit
|
|
// state followed by another navigation that results in a failed navigation. A
|
|
// failed navigation is not a navigation that results in an HTTP error page; it
|
|
// is a situation where the network request itself fails, e.g. DNS resolution
|
|
// failed, and Chrome commits an error page instead.
|
|
IN_PROC_BROWSER_TEST_P(
|
|
CommitNavigationRaceBrowserTest,
|
|
BeginNewNavigationDuringCommitFailedNavigationInMainFrame) {
|
|
ASSERT_TRUE(NavigateToURL(
|
|
shell(), embedded_test_server()->GetURL("a.com", "/title1.html")));
|
|
|
|
// Prior to implementing the UndoCommitNavigation() workaround, the race
|
|
// condition being tested would result in a crash in the b.com renderer. Open
|
|
// another b.com window in the same browsing instance to verify that the b.com
|
|
// renderer does not unexpectedly crash even if the b.com speculative
|
|
// RenderFrameHost is discarded.
|
|
ASSERT_TRUE(ExecJs(
|
|
shell(), JsReplace("window.open($1)", embedded_test_server()->GetURL(
|
|
"b.com", "/title1.html"))));
|
|
ASSERT_EQ(2u, Shell::windows().size());
|
|
WebContentsImpl* new_web_contents =
|
|
static_cast<WebContentsImpl*>(Shell::windows()[1]->web_contents());
|
|
WaitForLoadStop(new_web_contents);
|
|
RenderProcessHost* const b_com_render_process_host =
|
|
new_web_contents->GetPrimaryMainFrame()->GetProcess();
|
|
|
|
NavigationLogger logger(shell()->web_contents());
|
|
|
|
// Start a navigation that will create a speculative RFH in the existing
|
|
// render process for b.com.
|
|
const GURL infinitely_loading_url =
|
|
embedded_test_server()->GetURL("b.com", "/infinitely_loading_image.html");
|
|
ASSERT_TRUE(BeginNavigateToURLFromRenderer(shell(), infinitely_loading_url));
|
|
|
|
// Ensure the speculative RFH is in the expected process (i.e. the b.com
|
|
// process that was created for the navigation in the new window earlier).
|
|
WebContentsImpl* web_contents =
|
|
static_cast<WebContentsImpl*>(shell()->web_contents());
|
|
RenderFrameHostImpl* speculative_render_frame_host =
|
|
web_contents->GetPrimaryFrameTree()
|
|
.root()
|
|
->render_manager()
|
|
->speculative_frame_host();
|
|
ASSERT_TRUE(speculative_render_frame_host);
|
|
EXPECT_EQ(b_com_render_process_host,
|
|
speculative_render_frame_host->GetProcess());
|
|
|
|
// Pause (and potentially ignore, if navigation queueing is disabled) the next
|
|
// `DidCommitProvisionalLoad()` for b.com.
|
|
CommitNavigationPauser commit_pauser(speculative_render_frame_host);
|
|
commit_pauser.WaitForCommitAndPause();
|
|
|
|
absl::optional<ResumeCommitClosureSetWaiter> resume_commit_closure_set_waiter;
|
|
if (ShouldQueueNavigationsWhenPendingCommitRFHExists()) {
|
|
// If navigation queueing is enabled, the test should verify that a resume
|
|
// commit closure is actually set. Install a watcher now, before beginning
|
|
// the next navigation, since the resume commit closure may be synchronously
|
|
// set while handling the `BeginNavigation()` IPC in the browser.
|
|
OnNextDidStartNavigation(web_contents, [&](NavigationHandle* handle) {
|
|
resume_commit_closure_set_waiter.emplace(handle);
|
|
});
|
|
}
|
|
|
|
// Now begin a new navigation to c.com while the previous b.com navigation
|
|
// above is paused in the pending commit state. This navigation will fail and
|
|
// commit an error page.
|
|
const GURL final_url =
|
|
embedded_test_server()->GetURL("c.com", "/title1.html");
|
|
std::unique_ptr<URLLoaderInterceptor> url_interceptor =
|
|
URLLoaderInterceptor::SetupRequestFailForURL(final_url,
|
|
net::ERR_DNS_TIMED_OUT);
|
|
ASSERT_TRUE(BeginNavigateToURLFromRenderer(web_contents, final_url));
|
|
|
|
if (ShouldQueueNavigationsWhenPendingCommitRFHExists()) {
|
|
resume_commit_closure_set_waiter->Wait();
|
|
commit_pauser.ResumePausedCommit();
|
|
}
|
|
|
|
// The top-level page completes loading but is an error page, so
|
|
// `WaitForLoadStop()` should return false, since the navigation entry will
|
|
// have `PAGE_TYPE_ERROR`.
|
|
EXPECT_FALSE(WaitForLoadStop(web_contents));
|
|
EXPECT_EQ(final_url, web_contents->GetLastCommittedURL());
|
|
EXPECT_TRUE(web_contents->GetPrimaryMainFrame()->IsErrorDocument());
|
|
|
|
auto results = logger.results();
|
|
ASSERT_EQ(2u, results.size());
|
|
EXPECT_EQ(infinitely_loading_url, results[0].url);
|
|
// If navigation queueing is enabled, the first navigation will complete the
|
|
// commit as the new navigation gets queued until the first navigation's
|
|
// commit finished. If navigation queueing is disabled, the pending commit
|
|
// navigation will be cancelled.
|
|
if (ShouldQueueNavigationsWhenPendingCommitRFHExists()) {
|
|
EXPECT_TRUE(results[0].committed);
|
|
EXPECT_EQ(embedded_test_server()->GetOrigin("b.com"), results[0].origin);
|
|
} else {
|
|
EXPECT_FALSE(results[0].committed);
|
|
EXPECT_EQ(absl::nullopt, results[0].origin);
|
|
}
|
|
EXPECT_TRUE(results[1].committed);
|
|
EXPECT_TRUE(results[1].origin->opaque());
|
|
EXPECT_EQ(embedded_test_server()
|
|
->GetOrigin("c.com")
|
|
.GetTupleOrPrecursorTupleIfOpaque(),
|
|
results[1].origin->GetTupleOrPrecursorTupleIfOpaque());
|
|
EXPECT_EQ(final_url, results[1].url);
|
|
}
|
|
|
|
IN_PROC_BROWSER_TEST_P(
|
|
CommitNavigationRaceBrowserTest,
|
|
BeginNewNavigationDuringCommitFailedNavigationInSubFrame) {
|
|
ASSERT_TRUE(NavigateToURL(
|
|
shell(), embedded_test_server()->GetURL(
|
|
"b.com", "/cross_site_iframe_factory.html?b(a)")));
|
|
|
|
WebContentsImpl* web_contents =
|
|
static_cast<WebContentsImpl*>(shell()->web_contents());
|
|
FrameTreeNode* first_subframe_node =
|
|
web_contents->GetPrimaryMainFrame()->child_at(0);
|
|
RenderProcessHost* const b_com_render_process_host =
|
|
web_contents->GetPrimaryFrameTree()
|
|
.root()
|
|
->render_manager()
|
|
->current_frame_host()
|
|
->GetProcess();
|
|
|
|
NavigationLogger logger(web_contents);
|
|
|
|
// Start a navigation that will create a speculative RFH in the existing
|
|
// render process for b.com.
|
|
const GURL infinitely_loading_url =
|
|
embedded_test_server()->GetURL("b.com", "/infinitely_loading_image.html");
|
|
ASSERT_TRUE(BeginNavigateToURLFromRenderer(first_subframe_node,
|
|
infinitely_loading_url));
|
|
|
|
// Ensure the speculative RFH is in the expected process.
|
|
RenderFrameHostImpl* speculative_render_frame_host =
|
|
first_subframe_node->render_manager()->speculative_frame_host();
|
|
ASSERT_TRUE(speculative_render_frame_host);
|
|
EXPECT_EQ(b_com_render_process_host,
|
|
speculative_render_frame_host->GetProcess());
|
|
|
|
// Pause (and potentially ignore, if navigation queueing is disabled) the next
|
|
// `DidCommitProvisionalLoad()` for b.com.
|
|
CommitNavigationPauser commit_pauser(speculative_render_frame_host);
|
|
commit_pauser.WaitForCommitAndPause();
|
|
|
|
absl::optional<ResumeCommitClosureSetWaiter> resume_commit_closure_set_waiter;
|
|
if (ShouldQueueNavigationsWhenPendingCommitRFHExists()) {
|
|
// If navigation queueing is enabled, the test should verify that a resume
|
|
// commit closure is actually set. Install a watcher now, before beginning
|
|
// the next navigation, since the resume commit closure may be synchronously
|
|
// set while handling the `BeginNavigation()` IPC in the browser.
|
|
OnNextDidStartNavigation(web_contents, [&](NavigationHandle* handle) {
|
|
resume_commit_closure_set_waiter.emplace(handle);
|
|
});
|
|
}
|
|
|
|
// Now begin a new navigation to c.com while the previous b.com navigation
|
|
// above is paused in the pending commit state. This navigation will fail and
|
|
// commit an error page.
|
|
const GURL final_url =
|
|
embedded_test_server()->GetURL("c.com", "/title1.html");
|
|
std::unique_ptr<URLLoaderInterceptor> url_interceptor =
|
|
URLLoaderInterceptor::SetupRequestFailForURL(final_url,
|
|
net::ERR_DNS_TIMED_OUT);
|
|
ASSERT_TRUE(BeginNavigateToURLFromRenderer(first_subframe_node, final_url));
|
|
|
|
if (ShouldQueueNavigationsWhenPendingCommitRFHExists()) {
|
|
resume_commit_closure_set_waiter->Wait();
|
|
commit_pauser.ResumePausedCommit();
|
|
}
|
|
|
|
// The top-level page completes loading. Unlike the main frame variant of this
|
|
// test, `WaitForLoadStop()` should return true, since the navigation entry
|
|
// will have `PAGE_TYPE_NORMAL` as only a subframe failed to load.
|
|
EXPECT_TRUE(WaitForLoadStop(web_contents));
|
|
EXPECT_EQ(final_url, first_subframe_node->render_manager()
|
|
->current_frame_host()
|
|
->GetLastCommittedURL());
|
|
EXPECT_TRUE(first_subframe_node->render_manager()
|
|
->current_frame_host()
|
|
->IsErrorDocument());
|
|
|
|
auto results = logger.results();
|
|
ASSERT_EQ(2u, results.size());
|
|
// If navigation queueing is enabled, the first navigation will complete the
|
|
// commit as the new navigation gets queued until the first navigation's
|
|
// commit finished. If navigation queueing is disabled, the pending commit
|
|
// navigation will be cancelled.
|
|
if (ShouldQueueNavigationsWhenPendingCommitRFHExists()) {
|
|
EXPECT_TRUE(results[0].committed);
|
|
EXPECT_EQ(embedded_test_server()->GetOrigin("b.com"), results[0].origin);
|
|
} else {
|
|
EXPECT_FALSE(results[0].committed);
|
|
EXPECT_EQ(absl::nullopt, results[0].origin);
|
|
}
|
|
EXPECT_EQ(infinitely_loading_url, results[0].url);
|
|
EXPECT_TRUE(results[1].committed);
|
|
EXPECT_TRUE(results[1].origin->opaque());
|
|
EXPECT_EQ(embedded_test_server()
|
|
->GetOrigin("c.com")
|
|
.GetTupleOrPrecursorTupleIfOpaque(),
|
|
results[1].origin->GetTupleOrPrecursorTupleIfOpaque());
|
|
EXPECT_EQ(final_url, results[1].url);
|
|
}
|
|
|
|
// about:blank navigations do not require a URL loader and go through a
|
|
// different path to commit the navigation in the renderer.
|
|
IN_PROC_BROWSER_TEST_P(
|
|
CommitNavigationRaceBrowserTest,
|
|
BeginNewNavigationWithNoUrlLoaderDuringCommitNavigationInMainFrame) {
|
|
ASSERT_TRUE(NavigateToURL(
|
|
shell(), embedded_test_server()->GetURL("a.com", "/title1.html")));
|
|
|
|
// Prior to implementing the UndoCommitNavigation() workaround, the race
|
|
// condition being tested would result in a crash in the b.com renderer. Open
|
|
// another b.com window in the same browsing instance to verify that the b.com
|
|
// renderer does not unexpectedly crash even if the b.com speculative
|
|
// RenderFrameHost is discarded.
|
|
ASSERT_TRUE(ExecJs(
|
|
shell(), JsReplace("window.open($1)", embedded_test_server()->GetURL(
|
|
"b.com", "/title1.html"))));
|
|
ASSERT_EQ(2u, Shell::windows().size());
|
|
WebContentsImpl* new_web_contents =
|
|
static_cast<WebContentsImpl*>(Shell::windows()[1]->web_contents());
|
|
EXPECT_TRUE(WaitForLoadStop(new_web_contents));
|
|
RenderProcessHost* const b_com_render_process_host =
|
|
new_web_contents->GetPrimaryMainFrame()->GetProcess();
|
|
|
|
NavigationLogger logger(shell()->web_contents());
|
|
|
|
// Start a navigation that will create a speculative RFH in the existing
|
|
// render process for b.com.
|
|
const GURL infinitely_loading_url =
|
|
embedded_test_server()->GetURL("b.com", "/infinitely_loading_image.html");
|
|
ASSERT_TRUE(BeginNavigateToURLFromRenderer(shell(), infinitely_loading_url));
|
|
|
|
// Ensure the speculative RFH is in the expected process (i.e. the b.com
|
|
// process that was created for the navigation in the new window earlier).
|
|
WebContentsImpl* web_contents =
|
|
static_cast<WebContentsImpl*>(shell()->web_contents());
|
|
RenderFrameHostImpl* speculative_render_frame_host =
|
|
web_contents->GetPrimaryFrameTree()
|
|
.root()
|
|
->render_manager()
|
|
->speculative_frame_host();
|
|
ASSERT_TRUE(speculative_render_frame_host);
|
|
EXPECT_EQ(b_com_render_process_host,
|
|
speculative_render_frame_host->GetProcess());
|
|
|
|
// Pause (and potentially ignore, if navigation queueing is disabled) the next
|
|
// `DidCommitProvisionalLoad()` for b.com.
|
|
CommitNavigationPauser commit_pauser(speculative_render_frame_host);
|
|
commit_pauser.WaitForCommitAndPause();
|
|
|
|
absl::optional<ResumeCommitClosureSetWaiter> resume_commit_closure_set_waiter;
|
|
if (ShouldQueueNavigationsWhenPendingCommitRFHExists()) {
|
|
// If navigation queueing is enabled, the test should verify that a resume
|
|
// commit closure is actually set. Install a watcher now, before beginning
|
|
// the next navigation, since the resume commit closure may be synchronously
|
|
// set while handling the `BeginNavigation()` IPC in the browser.
|
|
OnNextDidStartNavigation(web_contents, [&](NavigationHandle* handle) {
|
|
resume_commit_closure_set_waiter.emplace(handle);
|
|
});
|
|
}
|
|
|
|
// Note that this navigation is initiated by the a.com renderer, as the a.com
|
|
// renderer is still the current frame host for the main frame.
|
|
const GURL final_url("about:blank");
|
|
ASSERT_TRUE(BeginNavigateToURLFromRenderer(web_contents, final_url));
|
|
|
|
if (ShouldQueueNavigationsWhenPendingCommitRFHExists()) {
|
|
resume_commit_closure_set_waiter->Wait();
|
|
commit_pauser.ResumePausedCommit();
|
|
}
|
|
|
|
EXPECT_TRUE(WaitForLoadStop(web_contents));
|
|
EXPECT_EQ(final_url, web_contents->GetLastCommittedURL());
|
|
|
|
auto results = logger.results();
|
|
ASSERT_EQ(2u, results.size());
|
|
EXPECT_EQ(infinitely_loading_url, results[0].url);
|
|
// If navigation queueing is enabled, the first navigation will complete the
|
|
// commit as the new navigation gets queued until the first navigation's
|
|
// commit finished. If navigation queueing is disabled, the pending commit
|
|
// navigation will be cancelled.
|
|
if (ShouldQueueNavigationsWhenPendingCommitRFHExists()) {
|
|
EXPECT_TRUE(results[0].committed);
|
|
EXPECT_EQ(embedded_test_server()->GetOrigin("b.com"), results[0].origin);
|
|
} else {
|
|
EXPECT_FALSE(results[0].committed);
|
|
EXPECT_EQ(absl::nullopt, results[0].origin);
|
|
}
|
|
EXPECT_TRUE(results[1].committed);
|
|
EXPECT_EQ(embedded_test_server()->GetOrigin("a.com"), results[1].origin);
|
|
EXPECT_EQ(final_url, results[1].url);
|
|
}
|
|
|
|
IN_PROC_BROWSER_TEST_P(
|
|
CommitNavigationRaceBrowserTest,
|
|
BeginNewNavigationWithNoUrlLoaderDuringCommitNavigationInSubFrame) {
|
|
ASSERT_TRUE(NavigateToURL(
|
|
shell(), embedded_test_server()->GetURL(
|
|
"b.com", "/cross_site_iframe_factory.html?b(a)")));
|
|
|
|
WebContentsImpl* web_contents =
|
|
static_cast<WebContentsImpl*>(shell()->web_contents());
|
|
FrameTreeNode* first_subframe_node =
|
|
web_contents->GetPrimaryMainFrame()->child_at(0);
|
|
RenderProcessHost* const b_com_render_process_host =
|
|
web_contents->GetPrimaryFrameTree()
|
|
.root()
|
|
->render_manager()
|
|
->current_frame_host()
|
|
->GetProcess();
|
|
|
|
NavigationLogger logger(web_contents);
|
|
|
|
// Start a navigation that will create a speculative RFH in the existing
|
|
// render process for b.com.
|
|
const GURL infinitely_loading_url =
|
|
embedded_test_server()->GetURL("b.com", "/infinitely_loading_image.html");
|
|
ASSERT_TRUE(BeginNavigateToURLFromRenderer(first_subframe_node,
|
|
infinitely_loading_url));
|
|
|
|
// Ensure the speculative RFH is in the expected process.
|
|
RenderFrameHostImpl* speculative_render_frame_host =
|
|
first_subframe_node->render_manager()->speculative_frame_host();
|
|
ASSERT_TRUE(speculative_render_frame_host);
|
|
EXPECT_EQ(b_com_render_process_host,
|
|
speculative_render_frame_host->GetProcess());
|
|
|
|
CommitNavigationPauser commit_pauser(speculative_render_frame_host);
|
|
commit_pauser.WaitForCommitAndPause();
|
|
|
|
absl::optional<ResumeCommitClosureSetWaiter> resume_commit_closure_set_waiter;
|
|
if (ShouldQueueNavigationsWhenPendingCommitRFHExists()) {
|
|
// If navigation queueing is enabled, the test should verify that a resume
|
|
// commit closure is actually set. Install a watcher now, before beginning
|
|
// the next navigation, since the resume commit closure may be synchronously
|
|
// set while handling the `BeginNavigation()` IPC in the browser.
|
|
OnNextDidStartNavigation(web_contents, [&](NavigationHandle* handle) {
|
|
resume_commit_closure_set_waiter.emplace(handle);
|
|
});
|
|
}
|
|
|
|
// Note that this navigation is initiated by the a.com renderer, as the a.com
|
|
// renderer is still the current frame host for the main frame.
|
|
const GURL final_url("about:blank");
|
|
ASSERT_TRUE(BeginNavigateToURLFromRenderer(first_subframe_node, final_url));
|
|
|
|
if (ShouldQueueNavigationsWhenPendingCommitRFHExists()) {
|
|
resume_commit_closure_set_waiter->Wait();
|
|
commit_pauser.ResumePausedCommit();
|
|
}
|
|
|
|
EXPECT_TRUE(WaitForLoadStop(web_contents));
|
|
EXPECT_EQ(final_url, first_subframe_node->render_manager()
|
|
->current_frame_host()
|
|
->GetLastCommittedURL());
|
|
|
|
auto results = logger.results();
|
|
ASSERT_EQ(2u, results.size());
|
|
// If navigation queueing is enabled, the first navigation will complete the
|
|
// commit as the new navigation gets queued until the first navigation's
|
|
// commit finished. If navigation queueing is disabled, the pending commit
|
|
// navigation will be cancelled.
|
|
if (ShouldQueueNavigationsWhenPendingCommitRFHExists()) {
|
|
EXPECT_TRUE(results[0].committed);
|
|
EXPECT_EQ(embedded_test_server()->GetOrigin("b.com"), results[0].origin);
|
|
} else {
|
|
EXPECT_FALSE(results[0].committed);
|
|
EXPECT_EQ(absl::nullopt, results[0].origin);
|
|
}
|
|
EXPECT_EQ(infinitely_loading_url, results[0].url);
|
|
EXPECT_TRUE(results[1].committed);
|
|
EXPECT_EQ(embedded_test_server()->GetOrigin("a.com"), results[1].origin);
|
|
EXPECT_EQ(final_url, results[1].url);
|
|
}
|
|
|
|
INSTANTIATE_TEST_SUITE_P(,
|
|
CommitNavigationRaceBrowserTest,
|
|
::testing::Bool(),
|
|
&CommitNavigationRaceBrowserTest::DescribeParams);
|
|
|
|
// The following test checks what happens if a WebContentsDelegate navigates
|
|
// away in response to the NavigationStateChanged event. Previously
|
|
// (https://crbug.com/1210234), this was triggering a crash when creating the
|
|
// new NavigationRequest, because it was trying to access the current
|
|
// RenderFrameHost's PolicyContainerHost, which had not been set up yet by
|
|
// RenderFrameHostImpl::DidNavigate.
|
|
IN_PROC_BROWSER_TEST_F(NavigationBrowserTest, Bug1210234) {
|
|
class NavigationWebContentsDelegate : public WebContentsDelegate {
|
|
public:
|
|
NavigationWebContentsDelegate(const GURL& url_to_intercept,
|
|
const GURL& url_to_navigate_to)
|
|
: url_to_intercept_(url_to_intercept),
|
|
url_to_navigate_to_(url_to_navigate_to) {}
|
|
void NavigationStateChanged(WebContents* source,
|
|
InvalidateTypes changed_flags) override {
|
|
if (!navigated_ && source->GetLastCommittedURL() == url_to_intercept_) {
|
|
navigated_ = true;
|
|
source->GetController().LoadURL(url_to_navigate_to_, Referrer(),
|
|
ui::PAGE_TRANSITION_AUTO_TOPLEVEL,
|
|
std::string());
|
|
}
|
|
}
|
|
|
|
private:
|
|
bool navigated_ = false;
|
|
GURL url_to_intercept_;
|
|
GURL url_to_navigate_to_;
|
|
};
|
|
|
|
GURL warmup_url = embedded_test_server()->GetURL("a.com", "/title1.html");
|
|
GURL initial_url = embedded_test_server()->GetURL("b.com", "/title1.html");
|
|
GURL redirection_url =
|
|
embedded_test_server()->GetURL("c.com", "/title1.html");
|
|
|
|
NavigationWebContentsDelegate delegate(initial_url, redirection_url);
|
|
web_contents()->SetDelegate(&delegate);
|
|
|
|
ASSERT_TRUE(NavigateToURL(shell(), warmup_url));
|
|
|
|
// Note that since we committed a navigation, the next cross-origin navigation
|
|
// will create a speculative RenderFrameHost (when site isolation is enabled).
|
|
|
|
// Start the navigation to `initial_url` and wait until the web contents
|
|
// navigates to `redirection_url`. We cannot use helper functions like
|
|
// `NavigateToURLBlockUntilNavigationsComplete` because they wait for
|
|
// DidStopLoading and check the LastCommittedURL when they receive it.
|
|
// However, without SiteIsolation, an earlier DidStopLoading might be received
|
|
// when the WebContents has not yet committed the `redirection_url`.
|
|
|
|
// Prepare for the navigation.
|
|
WaitForLoadStop(web_contents());
|
|
TestNavigationObserver navigation_observer(redirection_url);
|
|
navigation_observer.WatchExistingWebContents();
|
|
|
|
shell()->LoadURL(initial_url);
|
|
|
|
navigation_observer.Wait();
|
|
|
|
EXPECT_TRUE(IsLastCommittedEntryOfPageType(web_contents(), PAGE_TYPE_NORMAL));
|
|
EXPECT_EQ(redirection_url, web_contents()->GetLastCommittedURL());
|
|
}
|
|
|
|
class NavigationBrowserTestCredentiallessIframe : public NavigationBrowserTest {
|
|
public:
|
|
void SetUpCommandLine(base::CommandLine* command_line) override {
|
|
NavigationBrowserTest::SetUpCommandLine(command_line);
|
|
|
|
command_line->AppendSwitch(switches::kEnableBlinkTestFeatures);
|
|
}
|
|
};
|
|
|
|
IN_PROC_BROWSER_TEST_F(NavigationBrowserTestCredentiallessIframe,
|
|
CredentiallessAttributeIsHonoredByNavigation) {
|
|
GURL main_url = embedded_test_server()->GetURL("/page_with_iframe.html");
|
|
GURL iframe_url_1 = embedded_test_server()->GetURL("/title1.html");
|
|
GURL iframe_url_2 = embedded_test_server()->GetURL("/title2.html");
|
|
EXPECT_TRUE(NavigateToURL(shell(), main_url));
|
|
|
|
// The main page has a child iframe with url `iframe_url_1`.
|
|
EXPECT_EQ(1U, main_frame()->child_count());
|
|
FrameTreeNode* child = main_frame()->child_at(0);
|
|
EXPECT_EQ(iframe_url_1, child->current_url());
|
|
EXPECT_FALSE(child->Credentialless());
|
|
EXPECT_FALSE(child->current_frame_host()->IsCredentialless());
|
|
EXPECT_EQ(false,
|
|
EvalJs(child->current_frame_host(), "window.credentialless"));
|
|
|
|
// Changes to the iframe 'credentialless' attribute are propagated to the
|
|
// FrameTreeNode. The RenderFrameHost, however, is updated only on navigation.
|
|
EXPECT_TRUE(
|
|
ExecJs(main_frame(),
|
|
"document.getElementById('test_iframe').credentialless = true;"));
|
|
EXPECT_TRUE(child->Credentialless());
|
|
EXPECT_FALSE(child->current_frame_host()->IsCredentialless());
|
|
EXPECT_EQ(false,
|
|
EvalJs(child->current_frame_host(), "window.credentialless"));
|
|
|
|
// Create a grandchild iframe.
|
|
EXPECT_TRUE(ExecJs(
|
|
child, JsReplace("let grandchild = document.createElement('iframe');"
|
|
"grandchild.src = $1;"
|
|
"document.body.appendChild(grandchild);",
|
|
iframe_url_2)));
|
|
WaitForLoadStop(web_contents());
|
|
EXPECT_EQ(1U, child->child_count());
|
|
FrameTreeNode* grandchild = child->child_at(0);
|
|
|
|
// The grandchild FrameTreeNode does not set the 'credentialless'
|
|
// attribute. The grandchild RenderFrameHost is not credentialless, since its
|
|
// parent RenderFrameHost is not credentialless.
|
|
EXPECT_FALSE(grandchild->Credentialless());
|
|
EXPECT_FALSE(grandchild->current_frame_host()->IsCredentialless());
|
|
EXPECT_EQ(false,
|
|
EvalJs(grandchild->current_frame_host(), "window.credentialless"));
|
|
|
|
// Navigate the child iframe same-document. This does not change anything.
|
|
EXPECT_TRUE(ExecJs(main_frame(),
|
|
JsReplace("document.getElementById('test_iframe')"
|
|
" .contentWindow.location.href = $1;",
|
|
iframe_url_1.Resolve("#here").spec())));
|
|
WaitForLoadStop(web_contents());
|
|
EXPECT_TRUE(child->Credentialless());
|
|
EXPECT_FALSE(child->current_frame_host()->IsCredentialless());
|
|
EXPECT_EQ(false,
|
|
EvalJs(child->current_frame_host(), "window.credentialless"));
|
|
|
|
// Now navigate the child iframe cross-document.
|
|
EXPECT_TRUE(ExecJs(
|
|
main_frame(), JsReplace("document.getElementById('test_iframe').src = $1",
|
|
iframe_url_2)));
|
|
WaitForLoadStop(web_contents());
|
|
EXPECT_TRUE(child->Credentialless());
|
|
EXPECT_TRUE(child->current_frame_host()->IsCredentialless());
|
|
EXPECT_EQ(true, EvalJs(child->current_frame_host(), "window.credentialless"));
|
|
// A credentialless document has a storage key with a nonce.
|
|
EXPECT_TRUE(child->current_frame_host()->storage_key().nonce().has_value());
|
|
base::UnguessableToken credentialless_nonce =
|
|
current_frame_host()->credentialless_iframes_nonce();
|
|
EXPECT_EQ(credentialless_nonce,
|
|
child->current_frame_host()->storage_key().nonce().value());
|
|
|
|
// Create a grandchild iframe.
|
|
EXPECT_TRUE(ExecJs(
|
|
child, JsReplace("let grandchild = document.createElement('iframe');"
|
|
"grandchild.id = 'grandchild_iframe';"
|
|
"document.body.appendChild(grandchild);",
|
|
iframe_url_1)));
|
|
EXPECT_EQ(1U, child->child_count());
|
|
grandchild = child->child_at(0);
|
|
|
|
// The grandchild does not set the 'credentialless' attribute, but the
|
|
// grandchild document is credentialless.
|
|
EXPECT_FALSE(grandchild->Credentialless());
|
|
EXPECT_TRUE(grandchild->current_frame_host()->IsCredentialless());
|
|
EXPECT_EQ(true,
|
|
EvalJs(grandchild->current_frame_host(), "window.credentialless"));
|
|
|
|
// The storage key's nonce is the same for all credentialless documents in the
|
|
// same page.
|
|
EXPECT_TRUE(child->current_frame_host()->storage_key().nonce().has_value());
|
|
EXPECT_EQ(credentialless_nonce,
|
|
child->current_frame_host()->storage_key().nonce().value());
|
|
|
|
// Now navigate the grandchild iframe.
|
|
EXPECT_TRUE(ExecJs(
|
|
child, JsReplace("document.getElementById('grandchild_iframe').src = $1",
|
|
iframe_url_2)));
|
|
WaitForLoadStop(web_contents());
|
|
EXPECT_TRUE(grandchild->current_frame_host()->IsCredentialless());
|
|
EXPECT_EQ(true,
|
|
EvalJs(grandchild->current_frame_host(), "window.credentialless"));
|
|
|
|
// The storage key's nonce is still the same.
|
|
EXPECT_TRUE(child->current_frame_host()->storage_key().nonce().has_value());
|
|
EXPECT_EQ(credentialless_nonce,
|
|
child->current_frame_host()->storage_key().nonce().value());
|
|
|
|
// Remove the 'credentialless' attribute from the iframe. This propagates to
|
|
// the FrameTreeNode. The RenderFrameHost, however, is updated only on
|
|
// navigation.
|
|
EXPECT_TRUE(
|
|
ExecJs(main_frame(),
|
|
"document.getElementById('test_iframe').credentialless = false;"));
|
|
EXPECT_FALSE(child->Credentialless());
|
|
EXPECT_TRUE(child->current_frame_host()->IsCredentialless());
|
|
EXPECT_EQ(true, EvalJs(child->current_frame_host(), "window.credentialless"));
|
|
EXPECT_TRUE(child->current_frame_host()->storage_key().nonce().has_value());
|
|
EXPECT_EQ(credentialless_nonce,
|
|
child->current_frame_host()->storage_key().nonce().value());
|
|
|
|
// Create another grandchild iframe. Even if the parent iframe element does
|
|
// not have the 'credentialless' attribute anymore, the grandchild document is
|
|
// still loaded inside of a credentialless RenderFrameHost, so it will be
|
|
// credentialless.
|
|
EXPECT_TRUE(ExecJs(
|
|
child, JsReplace("let grandchild2 = document.createElement('iframe');"
|
|
"document.body.appendChild(grandchild2);",
|
|
iframe_url_1)));
|
|
EXPECT_EQ(2U, child->child_count());
|
|
FrameTreeNode* grandchild2 = child->child_at(1);
|
|
EXPECT_FALSE(grandchild2->Credentialless());
|
|
EXPECT_TRUE(grandchild2->current_frame_host()->IsCredentialless());
|
|
EXPECT_EQ(true,
|
|
EvalJs(grandchild2->current_frame_host(), "window.credentialless"));
|
|
EXPECT_TRUE(
|
|
grandchild2->current_frame_host()->storage_key().nonce().has_value());
|
|
EXPECT_EQ(credentialless_nonce,
|
|
grandchild2->current_frame_host()->storage_key().nonce().value());
|
|
|
|
// Navigate the child iframe. Since the iframe element does not set the
|
|
// 'credentialless' attribute, the resulting RenderFrameHost will not be
|
|
// credentialless.
|
|
EXPECT_TRUE(
|
|
ExecJs(main_frame(),
|
|
JsReplace("document.getElementById('test_iframe').src = $1;",
|
|
iframe_url_2)));
|
|
WaitForLoadStop(web_contents());
|
|
EXPECT_FALSE(child->Credentialless());
|
|
EXPECT_FALSE(child->current_frame_host()->IsCredentialless());
|
|
EXPECT_EQ(false,
|
|
EvalJs(child->current_frame_host(), "window.credentialless"));
|
|
EXPECT_FALSE(child->current_frame_host()->storage_key().nonce().has_value());
|
|
|
|
// Now navigate the whole page away.
|
|
GURL main_url_b = embedded_test_server()->GetURL(
|
|
"b.com", "/page_with_credentialless_iframe.html");
|
|
GURL iframe_url_b = embedded_test_server()->GetURL("b.com", "/title1.html");
|
|
EXPECT_TRUE(NavigateToURL(shell(), main_url_b));
|
|
|
|
// The main page has a credentialless child iframe with url `iframe_url_b`.
|
|
EXPECT_EQ(1U, main_frame()->child_count());
|
|
FrameTreeNode* child_b = main_frame()->child_at(0);
|
|
EXPECT_EQ(iframe_url_b, child_b->current_url());
|
|
EXPECT_TRUE(child_b->Credentialless());
|
|
EXPECT_TRUE(child_b->current_frame_host()->IsCredentialless());
|
|
EXPECT_EQ(true,
|
|
EvalJs(child_b->current_frame_host(), "window.credentialless"));
|
|
|
|
EXPECT_TRUE(child_b->current_frame_host()->storage_key().nonce().has_value());
|
|
base::UnguessableToken credentialless_nonce_b =
|
|
current_frame_host()->credentialless_iframes_nonce();
|
|
EXPECT_NE(credentialless_nonce, credentialless_nonce_b);
|
|
EXPECT_EQ(credentialless_nonce_b,
|
|
child_b->current_frame_host()->storage_key().nonce().value());
|
|
}
|
|
|
|
// Ensures that OpenURLParams::FromNavigationHandle translates navigation params
|
|
// correctly when used to initiate a navigation in another WebContents.
|
|
IN_PROC_BROWSER_TEST_F(
|
|
NavigationBrowserTest,
|
|
FromNavigationHandleTranslatesNavigationParamsCorrectly) {
|
|
// Test that the params are translated correctly for a redirected navigation.
|
|
const GURL kRedirectedURL(
|
|
embedded_test_server()->GetURL("/server-redirect?/simple_page.html"));
|
|
NavigationController::LoadURLParams load_params(kRedirectedURL);
|
|
TestNavigationManager first_tab_manager(web_contents(), kRedirectedURL);
|
|
web_contents()->GetController().LoadURLWithParams(load_params);
|
|
|
|
// Wait for response to allow the navigation to resolve the redirect.
|
|
EXPECT_TRUE(first_tab_manager.WaitForResponse());
|
|
|
|
// Create LoadURLParams from the navigation after redirection.
|
|
NavigationController::LoadURLParams load_url_params(
|
|
OpenURLParams::FromNavigationHandle(
|
|
first_tab_manager.GetNavigationHandle()));
|
|
Shell* second_tab = CreateBrowser();
|
|
TestNavigationManager second_tab_manager(second_tab->web_contents(),
|
|
load_url_params.url);
|
|
second_tab->web_contents()->GetController().LoadURLWithParams(
|
|
load_url_params);
|
|
|
|
EXPECT_TRUE(second_tab_manager.WaitForResponse());
|
|
|
|
// Ensure params from the navigation in the first tab are translated to the
|
|
// navigation in the second tab as expected.
|
|
auto* first_tab_handle = first_tab_manager.GetNavigationHandle();
|
|
auto* second_tab_handle = second_tab_manager.GetNavigationHandle();
|
|
EXPECT_EQ(embedded_test_server()->GetURL("/simple_page.html"),
|
|
second_tab_handle->GetURL());
|
|
EXPECT_EQ(first_tab_handle->GetReferrer(), second_tab_handle->GetReferrer());
|
|
EXPECT_TRUE(
|
|
ui::PageTransitionCoreTypeIs(first_tab_handle->GetPageTransition(),
|
|
second_tab_handle->GetPageTransition()));
|
|
EXPECT_EQ(first_tab_handle->IsRendererInitiated(),
|
|
second_tab_handle->IsRendererInitiated());
|
|
EXPECT_EQ(first_tab_handle->GetInitiatorOrigin(),
|
|
second_tab_handle->GetInitiatorOrigin());
|
|
EXPECT_EQ(first_tab_handle->GetSourceSiteInstance(),
|
|
second_tab_handle->GetSourceSiteInstance());
|
|
EXPECT_EQ(first_tab_handle->HasUserGesture(),
|
|
second_tab_handle->HasUserGesture());
|
|
EXPECT_EQ(first_tab_handle->WasStartedFromContextMenu(),
|
|
second_tab_handle->WasStartedFromContextMenu());
|
|
EXPECT_EQ(first_tab_handle->GetHrefTranslate(),
|
|
second_tab_handle->GetHrefTranslate());
|
|
EXPECT_EQ(first_tab_handle->GetReloadType(),
|
|
second_tab_handle->GetReloadType());
|
|
EXPECT_EQ(first_tab_handle->GetRedirectChain(),
|
|
second_tab_handle->GetRedirectChain());
|
|
}
|
|
|
|
// Regression test for https://crbug.com/1392653. Ensure that loading a URL
|
|
// that doesn't go through the network stack but does assign a site for its
|
|
// SiteInstance in an unassigned SiteInstance does not fail. An example of
|
|
// such a URL is about:srcdoc. This ensures that the SiteInstance's site is set
|
|
// even on the WillCommitWithoutUrlLoader() path in NavigationRequest.
|
|
IN_PROC_BROWSER_TEST_F(NavigationBrowserTest,
|
|
AboutSrcdocInjectedOnAboutBlankPage) {
|
|
// Start on an about:blank page, which should stay in an unassigned
|
|
// SiteInstance.
|
|
EXPECT_TRUE(NavigateToURL(shell(), GURL(url::kAboutBlankURL)));
|
|
SiteInstanceImpl* site_instance = current_frame_host()->GetSiteInstance();
|
|
EXPECT_FALSE(site_instance->HasSite());
|
|
|
|
// The process should be considered unused at this point.
|
|
EXPECT_TRUE(site_instance->GetProcess()->IsUnused());
|
|
|
|
// Inject a srcdoc iframe into the blank document. This shouldn't really be
|
|
// possible on the open web, since an about:blank page with an unassigned
|
|
// SiteInstance shouldn't be scriptable by other pages, but it could still
|
|
// happen in automation scenarios or through DevTools.
|
|
TestNavigationObserver navigation_observer(web_contents());
|
|
EXPECT_TRUE(ExecJs(current_frame_host(), JsReplace(R"(
|
|
let frame = document.createElement('iframe');
|
|
frame.srcdoc = 'test';
|
|
document.body.appendChild(frame);
|
|
)")));
|
|
navigation_observer.Wait();
|
|
EXPECT_TRUE(navigation_observer.last_navigation_succeeded());
|
|
EXPECT_EQ("about:srcdoc", navigation_observer.last_navigation_url());
|
|
|
|
// The srcdoc child should stay in its about:blank parent SiteInstance.
|
|
EXPECT_EQ(1U, main_frame()->child_count());
|
|
FrameTreeNode* child = main_frame()->child_at(0);
|
|
EXPECT_EQ(child->current_frame_host()->GetSiteInstance(), site_instance);
|
|
|
|
// Committing an about:srcdoc navigation currently forces the SiteInstance's
|
|
// site to be set. Prior to fixing https://crbug.com/1392653, this happened
|
|
// after the actual commit was processed at DidNavigate() time, which is a
|
|
// path that is no longer supported, and hence this triggered a NOTREACHED().
|
|
// Now, the site should be set before we send the CommitNavigation IPC.
|
|
EXPECT_TRUE(site_instance->HasSite());
|
|
|
|
if (AreDefaultSiteInstancesEnabled()) {
|
|
EXPECT_TRUE(site_instance->IsDefaultSiteInstance());
|
|
EXPECT_EQ(SiteInstanceImpl::GetDefaultSiteURL(),
|
|
site_instance->GetSiteInfo().site_url());
|
|
} else {
|
|
// When we get into this situation with strict site isolation, the site URL
|
|
// currently used is "about:". This may be changed in the future (e.g., to
|
|
// an opaque ID).
|
|
EXPECT_EQ("about:", site_instance->GetSiteInfo().site_url());
|
|
}
|
|
|
|
// Ensure that the process was marked as used as part of setting the site.
|
|
EXPECT_FALSE(site_instance->GetProcess()->IsUnused());
|
|
}
|
|
|
|
class CacheTransparencyNavigationBrowserTest : public ContentBrowserTest {
|
|
public:
|
|
CacheTransparencyNavigationBrowserTest() {
|
|
EXPECT_TRUE(embedded_test_server()->Start());
|
|
|
|
pervasive_payload_url_ = embedded_test_server()->GetURL(kPervasivePayload);
|
|
std::string pervasive_payloads_params = base::StrCat(
|
|
{"1,", pervasive_payload_url_.spec(),
|
|
",2478392C652868C0AAF0316A28284610DBDACF02D66A00B39F3BA75D887F4829"});
|
|
|
|
feature_list_.InitWithFeaturesAndParameters(
|
|
{{features::kNetworkServiceInProcess, {}},
|
|
{network::features::kPervasivePayloadsList,
|
|
{{"pervasive-payloads", pervasive_payloads_params}}},
|
|
{network::features::kCacheTransparency, {}},
|
|
{net::features::kSplitCacheByNetworkIsolationKey, {}}},
|
|
{/* disabled_features */});
|
|
}
|
|
|
|
void ExpectCacheUsed() const {
|
|
histogram_tester_.ExpectUniqueSample(kCacheUsedHistogram, 0, 1);
|
|
}
|
|
|
|
void ExpectCacheNotUsed() const {
|
|
histogram_tester_.ExpectTotalCount(kCacheUsedHistogram, 0);
|
|
}
|
|
|
|
private:
|
|
static constexpr char kPervasivePayload[] =
|
|
"/cache_transparency/pervasive.js";
|
|
static constexpr char kCacheUsedHistogram[] =
|
|
"Network.CacheTransparency2.SingleKeyedCacheIsUsed";
|
|
|
|
base::test::ScopedFeatureList feature_list_;
|
|
GURL pervasive_payload_url_;
|
|
base::HistogramTester histogram_tester_;
|
|
};
|
|
|
|
IN_PROC_BROWSER_TEST_F(CacheTransparencyNavigationBrowserTest,
|
|
SuccessfulPervasivePayload) {
|
|
GURL url_main_document =
|
|
embedded_test_server()->GetURL("/cache_transparency/pervasive.html");
|
|
|
|
EXPECT_TRUE(NavigateToURL(shell(), url_main_document));
|
|
|
|
ExpectCacheUsed();
|
|
}
|
|
|
|
IN_PROC_BROWSER_TEST_F(CacheTransparencyNavigationBrowserTest,
|
|
NotAPervasivePayload) {
|
|
GURL url_main_document =
|
|
embedded_test_server()->GetURL("/cache_transparency/cacheable.html");
|
|
|
|
EXPECT_TRUE(NavigateToURL(shell(), url_main_document));
|
|
|
|
ExpectCacheNotUsed();
|
|
}
|
|
|
|
class NavigationBrowserTestWarnSandboxIneffective
|
|
: public NavigationBrowserTest {
|
|
public:
|
|
void SetUpCommandLine(base::CommandLine* command_line) override {
|
|
NavigationBrowserTest::SetUpCommandLine(command_line);
|
|
command_line->AppendSwitchASCII(switches::kEnableBlinkFeatures,
|
|
"WarnSandboxIneffective");
|
|
}
|
|
|
|
static constexpr char kSandboxEscapeWarningMessage[] =
|
|
"An iframe which has both allow-scripts and allow-same-origin for its "
|
|
"sandbox attribute can escape its sandboxing.";
|
|
};
|
|
|
|
IN_PROC_BROWSER_TEST_F(NavigationBrowserTestWarnSandboxIneffective,
|
|
WarnEscapableSandboxSameOrigin) {
|
|
EXPECT_TRUE(NavigateToURL(
|
|
shell(), embedded_test_server()->GetURL("a.com", "/empty.html")));
|
|
|
|
WebContentsConsoleObserver console_observer(web_contents());
|
|
console_observer.SetPattern(kSandboxEscapeWarningMessage);
|
|
|
|
// Create same-origin iframe.
|
|
EXPECT_TRUE(ExecJs(current_frame_host(), R"(
|
|
const iframe = document.createElement("iframe");
|
|
iframe.src = location.href; // Same-origin iframe.
|
|
iframe.sandbox = "allow-same-origin allow-scripts";
|
|
document.body.appendChild(iframe);
|
|
)"));
|
|
ASSERT_TRUE(console_observer.Wait());
|
|
}
|
|
|
|
IN_PROC_BROWSER_TEST_F(NavigationBrowserTestWarnSandboxIneffective,
|
|
WarnEscapableSandboxCrossOrigin) {
|
|
EXPECT_TRUE(NavigateToURL(
|
|
shell(), embedded_test_server()->GetURL("a.com", "/empty.html")));
|
|
|
|
WebContentsConsoleObserver console_observer(web_contents());
|
|
console_observer.SetPattern(kSandboxEscapeWarningMessage);
|
|
|
|
// Create cross-origin iframe.
|
|
EXPECT_TRUE(ExecJs(current_frame_host(), R"(
|
|
const iframe = document.createElement("iframe");
|
|
// Cross-origin iframe:
|
|
iframe.src = location.href.replace("a.com", "b.com");
|
|
iframe.sandbox = "allow-same-origin allow-scripts";
|
|
document.body.appendChild(iframe);
|
|
)"));
|
|
|
|
EXPECT_TRUE(WaitForLoadStop(web_contents()));
|
|
EXPECT_EQ(console_observer.messages().size(), 0u);
|
|
}
|
|
|
|
IN_PROC_BROWSER_TEST_F(NavigationBrowserTestWarnSandboxIneffective,
|
|
WarnEscapableSandboxSameOriginGrandChild) {
|
|
EXPECT_TRUE(NavigateToURL(
|
|
shell(), embedded_test_server()->GetURL("a.com", "/empty.html")));
|
|
|
|
WebContentsConsoleObserver console_observer(web_contents());
|
|
console_observer.SetPattern(kSandboxEscapeWarningMessage);
|
|
|
|
// Create a same-origin doubly nested sandboxed iframe.
|
|
EXPECT_TRUE(ExecJs(current_frame_host(), R"(
|
|
const child = document.createElement("iframe");
|
|
document.body.appendChild(child);
|
|
|
|
const grand_child = child.contentDocument.createElement("iframe");
|
|
grand_child.src = location.href;
|
|
grand_child.sandbox = "allow-same-origin allow-scripts";
|
|
child.contentDocument.body.appendChild(grand_child);
|
|
)"));
|
|
|
|
EXPECT_TRUE(WaitForLoadStop(web_contents()));
|
|
EXPECT_EQ(console_observer.messages().size(), 0u);
|
|
}
|
|
|
|
// We may have an unload handler in the main frame or a subframe or nowhere.
|
|
enum class UnloadFrameType {
|
|
kMainFrame,
|
|
kSubFrame,
|
|
kNone,
|
|
};
|
|
|
|
static std::string ToString(UnloadFrameType v) {
|
|
switch (v) {
|
|
case UnloadFrameType::kMainFrame:
|
|
return "MainFrame";
|
|
case UnloadFrameType::kSubFrame:
|
|
return "SubFrame";
|
|
case UnloadFrameType::kNone:
|
|
return "None";
|
|
}
|
|
}
|
|
|
|
// We may navigate the main frame, the subframe that may have an unload handler
|
|
// or another subframe that will never have an unload handler.
|
|
enum class NavigateFrameType {
|
|
kMainFrame,
|
|
kSubFrame,
|
|
kOther,
|
|
};
|
|
|
|
static std::string ToString(NavigateFrameType v) {
|
|
switch (v) {
|
|
case NavigateFrameType::kMainFrame:
|
|
return "MainFrame";
|
|
case NavigateFrameType::kSubFrame:
|
|
return "SubFrame";
|
|
case NavigateFrameType::kOther:
|
|
return "Other";
|
|
}
|
|
}
|
|
|
|
void AddUnloadHandler(RenderFrameHostImpl* rfh) {
|
|
ASSERT_TRUE(ExecJs(rfh, "addEventListener('unload', () => {})"));
|
|
ASSERT_TRUE(rfh->GetSuddenTerminationDisablerState(
|
|
blink::mojom::SuddenTerminationDisablerType::kUnloadHandler));
|
|
}
|
|
|
|
class NavigationSuddenTerminationDisablerTypeBrowserTest
|
|
: public NavigationBrowserTest,
|
|
public ::testing::WithParamInterface<
|
|
std::tuple<UnloadFrameType, NavigateFrameType>> {
|
|
public:
|
|
static std::string DescribeParams(
|
|
const ::testing::TestParamInfo<ParamType>& info) {
|
|
return "Unload" + ToString(std::get<0>(info.param)) + "Navigate" +
|
|
ToString(std::get<1>(info.param));
|
|
}
|
|
|
|
protected:
|
|
UnloadFrameType GetUnloadFrameType() { return std::get<0>(GetParam()); }
|
|
NavigateFrameType GetNavigateFrameType() { return std::get<1>(GetParam()); }
|
|
|
|
void MaybeAddUnloadHandler() {
|
|
RenderFrameHostImpl* rfh = nullptr;
|
|
switch (GetUnloadFrameType()) {
|
|
case UnloadFrameType::kMainFrame:
|
|
rfh = current_frame_host();
|
|
break;
|
|
case UnloadFrameType::kSubFrame:
|
|
rfh = DescendantRenderFrameHostImplAt(current_frame_host(), {0});
|
|
break;
|
|
case UnloadFrameType::kNone:
|
|
break;
|
|
}
|
|
if (rfh) {
|
|
AddUnloadHandler(rfh);
|
|
}
|
|
}
|
|
|
|
RenderFrameHostImpl* GetFrameToNavigate() {
|
|
switch (GetNavigateFrameType()) {
|
|
case NavigateFrameType::kMainFrame:
|
|
return current_frame_host();
|
|
case NavigateFrameType::kSubFrame:
|
|
return DescendantRenderFrameHostImplAt(current_frame_host(), {0});
|
|
case NavigateFrameType::kOther:
|
|
return DescendantRenderFrameHostImplAt(current_frame_host(), {1});
|
|
}
|
|
}
|
|
|
|
bool NavigatedFrameHasUnload() {
|
|
switch (GetNavigateFrameType()) {
|
|
case NavigateFrameType::kOther:
|
|
// It never has unload.
|
|
return false;
|
|
case NavigateFrameType::kMainFrame:
|
|
// Navigating the main will have unload if there is any unload.
|
|
return GetUnloadFrameType() != UnloadFrameType::kNone;
|
|
case NavigateFrameType::kSubFrame:
|
|
// Navigating the subframe will have unload if the subframe has unload.
|
|
return GetUnloadFrameType() == UnloadFrameType::kSubFrame;
|
|
}
|
|
}
|
|
};
|
|
|
|
// Set up a page with 2 subframes. The main frame or one of the subframes may
|
|
// have an unload handler. Then navigate one of the frames and verify that we
|
|
// correctly record which type of frame navigates combined with whether it
|
|
// involved an unload handler.
|
|
IN_PROC_BROWSER_TEST_P(NavigationSuddenTerminationDisablerTypeBrowserTest,
|
|
RecordUma) {
|
|
ASSERT_TRUE(NavigateToURL(
|
|
shell(), embedded_test_server()->GetURL(
|
|
"a.com", "/cross_site_iframe_factory.html?a(a,a)")));
|
|
|
|
// Set up the unload handler if needed.
|
|
MaybeAddUnloadHandler();
|
|
|
|
// Navigate the relevant frame and capture histograms.
|
|
base::HistogramTester histograms;
|
|
ASSERT_TRUE(NavigateFrameToURL(GetFrameToNavigate()->frame_tree_node(),
|
|
GURL("about:blank")));
|
|
|
|
// Check that we got the expected histogram value.
|
|
uint32_t expected_histogram_value = 0;
|
|
if (GetNavigateFrameType() == NavigateFrameType::kMainFrame) {
|
|
expected_histogram_value |= RenderFrameHostImpl::
|
|
NavigationSuddenTerminationDisablerType::kMainFrame;
|
|
}
|
|
if (NavigatedFrameHasUnload()) {
|
|
expected_histogram_value |=
|
|
RenderFrameHostImpl::NavigationSuddenTerminationDisablerType::kUnload;
|
|
}
|
|
histograms.ExpectUniqueSample(
|
|
"Navigation.SuddenTerminationDisabler.AllOrigins",
|
|
expected_histogram_value, 1);
|
|
histograms.ExpectUniqueSample(
|
|
"Navigation.SuddenTerminationDisabler.SameOrigin",
|
|
expected_histogram_value, 1);
|
|
}
|
|
|
|
INSTANTIATE_TEST_SUITE_P(
|
|
All,
|
|
NavigationSuddenTerminationDisablerTypeBrowserTest,
|
|
::testing::Combine(::testing::Values(UnloadFrameType::kMainFrame,
|
|
UnloadFrameType::kSubFrame,
|
|
UnloadFrameType::kNone),
|
|
::testing::Values(NavigateFrameType::kMainFrame,
|
|
NavigateFrameType::kSubFrame,
|
|
NavigateFrameType::kOther)),
|
|
&NavigationSuddenTerminationDisablerTypeBrowserTest::DescribeParams);
|
|
|
|
// Test that "SameOrigin" only considers frames that have an unbroken path of
|
|
// same-origin frames from the frame that navigates.
|
|
IN_PROC_BROWSER_TEST_F(
|
|
NavigationBrowserTest,
|
|
NavigationSuddenTerminationDisablerTypeRecordUmaSameOrigin) {
|
|
ASSERT_TRUE(NavigateToURL(
|
|
shell(), embedded_test_server()->GetURL(
|
|
"a.com", "/cross_site_iframe_factory.html?a(b(a))")));
|
|
|
|
// Set up the unload handler in the a.com subframe.
|
|
AddUnloadHandler(
|
|
DescendantRenderFrameHostImplAt(current_frame_host(), {0, 0}));
|
|
// Navigate the main frame and capture histograms.
|
|
base::HistogramTester histograms;
|
|
ASSERT_TRUE(NavigateToURL(shell(), GURL("about:blank")));
|
|
|
|
histograms.ExpectUniqueSample(
|
|
"Navigation.SuddenTerminationDisabler.AllOrigins",
|
|
RenderFrameHostImpl::NavigationSuddenTerminationDisablerType::kMainFrame |
|
|
RenderFrameHostImpl::NavigationSuddenTerminationDisablerType::kUnload,
|
|
1);
|
|
histograms.ExpectUniqueSample(
|
|
"Navigation.SuddenTerminationDisabler.SameOrigin",
|
|
RenderFrameHostImpl::NavigationSuddenTerminationDisablerType::kMainFrame,
|
|
1);
|
|
}
|
|
|
|
// Test that we record when the navigation involves restoring from BFCache.
|
|
// This is tested because the code path for a navigation involving activation
|
|
// is different from one involving a pageload.
|
|
IN_PROC_BROWSER_TEST_F(
|
|
NavigationBrowserTest,
|
|
NavigationSuddenTerminationDisablerTypeRecordUmaActivation) {
|
|
ASSERT_TRUE(NavigateToURL(
|
|
shell(), embedded_test_server()->GetURL("a.com", "/title1.html")));
|
|
|
|
ASSERT_TRUE(NavigateToURL(
|
|
shell(), embedded_test_server()->GetURL("b.com", "/title1.html")));
|
|
|
|
// Set up the unload handler in the b.com page.
|
|
AddUnloadHandler(current_frame_host());
|
|
// Navigate the main frame and capture histograms.
|
|
base::HistogramTester histograms;
|
|
ASSERT_TRUE(HistoryGoBack(web_contents()));
|
|
|
|
histograms.ExpectUniqueSample(
|
|
"Navigation.SuddenTerminationDisabler.AllOrigins",
|
|
RenderFrameHostImpl::NavigationSuddenTerminationDisablerType::kMainFrame |
|
|
RenderFrameHostImpl::NavigationSuddenTerminationDisablerType::kUnload,
|
|
1);
|
|
histograms.ExpectUniqueSample(
|
|
"Navigation.SuddenTerminationDisabler.SameOrigin",
|
|
RenderFrameHostImpl::NavigationSuddenTerminationDisablerType::kMainFrame |
|
|
RenderFrameHostImpl::NavigationSuddenTerminationDisablerType::kUnload,
|
|
1);
|
|
}
|
|
|
|
// Ensure that the first navigation of a subframe away from the initial empty
|
|
// document is recorded correctly. This does not test all possibilities of
|
|
// histogram value, just that the scenario is counted under the correct
|
|
// histogram.
|
|
IN_PROC_BROWSER_TEST_F(
|
|
NavigationBrowserTest,
|
|
NavigationSuddenTerminationDisablerTypeRecordUmaInitialEmptyDocument) {
|
|
GURL url = embedded_test_server()->GetURL("a.com", "/title1.html");
|
|
ASSERT_TRUE(NavigateToURL(shell(), url));
|
|
|
|
// Create a subframe with an unload handler.
|
|
ASSERT_TRUE(ExecJs(web_contents(), R"(
|
|
var i = document.createElement("iframe");
|
|
document.body.appendChild(i);
|
|
)"));
|
|
|
|
AddUnloadHandler(DescendantRenderFrameHostImplAt(current_frame_host(), {0}));
|
|
|
|
// Navigate the subframe and capture histograms.
|
|
base::HistogramTester histograms;
|
|
uint32_t expected_histogram_value =
|
|
RenderFrameHostImpl::NavigationSuddenTerminationDisablerType::kUnload |
|
|
RenderFrameHostImpl::NavigationSuddenTerminationDisablerType::
|
|
kInitialEmptyDocument |
|
|
RenderFrameHostImpl::NavigationSuddenTerminationDisablerType::kNotHttp;
|
|
ASSERT_TRUE(NavigateToURLFromRenderer(
|
|
DescendantRenderFrameHostImplAt(current_frame_host(), {0}), url));
|
|
histograms.ExpectUniqueSample(
|
|
"Navigation.SuddenTerminationDisabler.AllOrigins",
|
|
expected_histogram_value, 1);
|
|
histograms.ExpectUniqueSample(
|
|
"Navigation.SuddenTerminationDisabler.SameOrigin",
|
|
expected_histogram_value, 1);
|
|
}
|
|
|
|
// Ensure that navigations from non-HTTP(S) pages are recorded correctly.
|
|
IN_PROC_BROWSER_TEST_F(
|
|
NavigationBrowserTest,
|
|
NavigationSuddenTerminationDisablerTypeRecordUmaNotHttp) {
|
|
GURL blank_url("about:blank");
|
|
GURL url = embedded_test_server()->GetURL("a.com", "/title1.html");
|
|
ASSERT_TRUE(NavigateToURL(shell(), blank_url));
|
|
|
|
AddUnloadHandler(current_frame_host());
|
|
|
|
// Navigate the subframe and capture histograms.
|
|
base::HistogramTester histograms;
|
|
uint32_t expected_histogram_value =
|
|
RenderFrameHostImpl::NavigationSuddenTerminationDisablerType::kMainFrame |
|
|
RenderFrameHostImpl::NavigationSuddenTerminationDisablerType::kUnload |
|
|
RenderFrameHostImpl::NavigationSuddenTerminationDisablerType::kNotHttp;
|
|
ASSERT_TRUE(NavigateToURL(web_contents(), url));
|
|
histograms.ExpectUniqueSample(
|
|
"Navigation.SuddenTerminationDisabler.AllOrigins",
|
|
expected_histogram_value, 1);
|
|
histograms.ExpectUniqueSample(
|
|
"Navigation.SuddenTerminationDisabler.SameOrigin",
|
|
expected_histogram_value, 1);
|
|
}
|
|
|
|
} // namespace content
|