blob: 2427e7e6ddbdc9c1b582194d4fc25dbc990aeb70 [file] [log] [blame]
// Copyright (c) 2012 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include <stdint.h>
#include <tuple>
#include <utility>
#include "base/bind.h"
#include "base/callback_helpers.h"
#include "base/check.h"
#include "base/command_line.h"
#include "base/containers/contains.h"
#include "base/macros.h"
#include "base/memory/ptr_util.h"
#include "base/strings/utf_string_conversions.h"
#include "base/test/bind.h"
#include "base/test/scoped_feature_list.h"
#include "build/build_config.h"
#include "build/chromeos_buildflags.h"
#include "components/download/public/common/download_url_parameters.h"
#include "content/browser/child_process_security_policy_impl.h"
#include "content/browser/media/audio_stream_monitor.h"
#include "content/browser/media/media_web_contents_observer.h"
#include "content/browser/renderer_host/navigation_entry_impl.h"
#include "content/browser/renderer_host/navigator.h"
#include "content/browser/renderer_host/render_frame_host_impl.h"
#include "content/browser/renderer_host/render_frame_proxy_host.h"
#include "content/browser/renderer_host/render_view_host_impl.h"
#include "content/browser/site_instance_impl.h"
#include "content/browser/webui/content_web_ui_controller_factory.h"
#include "content/browser/webui/web_ui_controller_factory_registry.h"
#include "content/common/content_navigation_policy.h"
#include "content/common/frame.mojom.h"
#include "content/public/browser/child_process_security_policy.h"
#include "content/public/browser/content_browser_client.h"
#include "content/public/browser/context_menu_params.h"
#include "content/public/browser/global_request_id.h"
#include "content/public/browser/javascript_dialog_manager.h"
#include "content/public/browser/navigation_details.h"
#include "content/public/browser/notification_details.h"
#include "content/public/browser/notification_source.h"
#include "content/public/browser/render_widget_host_view.h"
#include "content/public/browser/ssl_host_state_delegate.h"
#include "content/public/browser/storage_partition.h"
#include "content/public/browser/web_contents_delegate.h"
#include "content/public/browser/web_contents_observer.h"
#include "content/public/browser/web_ui_controller.h"
#include "content/public/common/bindings_policy.h"
#include "content/public/common/content_constants.h"
#include "content/public/common/content_features.h"
#include "content/public/common/url_constants.h"
#include "content/public/common/url_utils.h"
#include "content/public/test/back_forward_cache_util.h"
#include "content/public/test/fake_local_frame.h"
#include "content/public/test/mock_render_process_host.h"
#include "content/public/test/navigation_simulator.h"
#include "content/public/test/scoped_web_ui_controller_factory_registration.h"
#include "content/public/test/test_browser_context.h"
#include "content/public/test/test_utils.h"
#include "content/test/navigation_simulator_impl.h"
#include "content/test/test_content_browser_client.h"
#include "content/test/test_content_client.h"
#include "content/test/test_render_frame_host.h"
#include "content/test/test_render_view_host.h"
#include "content/test/test_web_contents.h"
#include "net/test/cert_test_util.h"
#include "net/test/test_data_directory.h"
#include "services/network/public/cpp/web_sandbox_flags.h"
#include "skia/ext/skia_utils_base.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "third_party/blink/public/common/chrome_debug_urls.h"
#include "third_party/blink/public/common/input/synthetic_web_input_event_builders.h"
#include "third_party/blink/public/common/security/protocol_handler_security_level.h"
#include "third_party/blink/public/mojom/frame/fullscreen.mojom.h"
#include "third_party/blink/public/mojom/image_downloader/image_downloader.mojom.h"
#include "third_party/blink/public/mojom/page/page_visibility_state.mojom.h"
#include "third_party/skia/include/core/SkColor.h"
#include "ui/gfx/skia_util.h"
#include "url/url_constants.h"
#if BUILDFLAG(IS_CHROMEOS_LACROS)
#include "chromeos/lacros/lacros_test_helper.h"
#endif
namespace content {
namespace {
class WebContentsImplTestBrowserClient : public TestContentBrowserClient {
public:
WebContentsImplTestBrowserClient()
: original_browser_client_(SetBrowserClientForTesting(this)) {}
~WebContentsImplTestBrowserClient() override {
SetBrowserClientForTesting(original_browser_client_);
}
bool ShouldAssignSiteForURL(const GURL& url) override {
if (site_assignment_for_url_.find(url) != site_assignment_for_url_.end()) {
return site_assignment_for_url_[url];
}
return true;
}
void set_assign_site_for_url(bool assign, const GURL& url) {
DCHECK(url.is_valid());
site_assignment_for_url_[url] = assign;
}
private:
std::map<GURL, bool> site_assignment_for_url_;
ContentBrowserClient* original_browser_client_;
};
class WebContentsImplTest : public RenderViewHostImplTestHarness {
public:
void SetUp() override {
RenderViewHostImplTestHarness::SetUp();
if (IsIsolatedOriginRequiredToGuaranteeDedicatedProcess()) {
// Isolate |isolated_cross_site_url()| so it cannot share a process
// with another site.
ChildProcessSecurityPolicyImpl::GetInstance()->AddFutureIsolatedOrigins(
{url::Origin::Create(isolated_cross_site_url())},
ChildProcessSecurityPolicy::IsolatedOriginSource::TEST,
browser_context());
// Reset the WebContents so the isolated origin will be honored by
// all BrowsingInstances used in the test.
SetContents(CreateTestWebContents());
}
}
bool has_audio_wake_lock() {
return contents()
->media_web_contents_observer()
->has_audio_wake_lock_for_testing();
}
GURL isolated_cross_site_url() const {
return GURL("http://isolated-cross-site.com");
}
private:
#if BUILDFLAG(IS_CHROMEOS_LACROS)
// Instantiate LacrosService for WakeLock support.
chromeos::ScopedLacrosServiceTestHelper scoped_lacros_service_test_helper_;
#endif
ScopedWebUIControllerFactoryRegistration factory_registration_{
ContentWebUIControllerFactory::GetInstance()};
};
class TestWebContentsObserver : public WebContentsObserver {
public:
explicit TestWebContentsObserver(WebContents* contents)
: WebContentsObserver(contents) {}
~TestWebContentsObserver() override {
EXPECT_FALSE(expected_capture_handle_config_) << "Unfulfilled expectation.";
}
void DidFinishLoad(RenderFrameHost* render_frame_host,
const GURL& validated_url) override {
last_url_ = validated_url;
}
void DidFailLoad(RenderFrameHost* render_frame_host,
const GURL& validated_url,
int error_code) override {
last_url_ = validated_url;
}
void DidFirstVisuallyNonEmptyPaint() override {
observed_did_first_visually_non_empty_paint_ = true;
EXPECT_TRUE(web_contents()->CompletedFirstVisuallyNonEmptyPaint());
}
void DidChangeThemeColor() override { ++theme_color_change_calls_; }
void DidChangeVerticalScrollDirection(
viz::VerticalScrollDirection scroll_direction) override {
last_vertical_scroll_direction_ = scroll_direction;
}
void OnIsConnectedToBluetoothDeviceChanged(
bool is_connected_to_bluetooth_device) override {
++num_is_connected_to_bluetooth_device_changed_;
last_is_connected_to_bluetooth_device_ = is_connected_to_bluetooth_device;
}
void OnCaptureHandleConfigUpdate(
const blink::mojom::CaptureHandleConfig& config) override {
ASSERT_TRUE(expected_capture_handle_config_) << "Unexpected call.";
EXPECT_EQ(config, *expected_capture_handle_config_);
expected_capture_handle_config_ = nullptr;
}
void ExpectOnCaptureHandleConfigUpdate(
blink::mojom::CaptureHandleConfigPtr config) {
CHECK(config) << "Malformed test.";
ASSERT_FALSE(expected_capture_handle_config_) << "Unfulfilled expectation.";
expected_capture_handle_config_ = std::move(config);
}
const GURL& last_url() const { return last_url_; }
int theme_color_change_calls() const { return theme_color_change_calls_; }
absl::optional<viz::VerticalScrollDirection> last_vertical_scroll_direction()
const {
return last_vertical_scroll_direction_;
}
bool observed_did_first_visually_non_empty_paint() const {
return observed_did_first_visually_non_empty_paint_;
}
int num_is_connected_to_bluetooth_device_changed() const {
return num_is_connected_to_bluetooth_device_changed_;
}
bool last_is_connected_to_bluetooth_device() const {
return last_is_connected_to_bluetooth_device_;
}
private:
GURL last_url_;
int theme_color_change_calls_ = 0;
absl::optional<viz::VerticalScrollDirection> last_vertical_scroll_direction_;
bool observed_did_first_visually_non_empty_paint_ = false;
int num_is_connected_to_bluetooth_device_changed_ = 0;
bool last_is_connected_to_bluetooth_device_ = false;
blink::mojom::CaptureHandleConfigPtr expected_capture_handle_config_;
DISALLOW_COPY_AND_ASSIGN(TestWebContentsObserver);
};
class MockWebContentsDelegate : public WebContentsDelegate {
public:
explicit MockWebContentsDelegate(
blink::ProtocolHandlerSecurityLevel security_level =
blink::ProtocolHandlerSecurityLevel::kStrict)
: security_level_(security_level) {}
MOCK_METHOD2(HandleContextMenu,
bool(RenderFrameHost*, const ContextMenuParams&));
MOCK_METHOD4(RegisterProtocolHandler,
void(RenderFrameHost*, const std::string&, const GURL&, bool));
MOCK_METHOD(void, NavigationStateChanged, (WebContents*, InvalidateTypes));
blink::ProtocolHandlerSecurityLevel GetProtocolHandlerSecurityLevel(
RenderFrameHost*) override {
return security_level_;
}
private:
blink::ProtocolHandlerSecurityLevel security_level_;
};
// Pretends to be a normal browser that receives toggles and transitions to/from
// a fullscreened state.
class FakeFullscreenDelegate : public WebContentsDelegate {
public:
FakeFullscreenDelegate() : fullscreened_contents_(nullptr) {}
~FakeFullscreenDelegate() override {}
void EnterFullscreenModeForTab(
RenderFrameHost* requesting_frame,
const blink::mojom::FullscreenOptions& options) override {
fullscreened_contents_ = WebContents::FromRenderFrameHost(requesting_frame);
}
void ExitFullscreenModeForTab(WebContents* web_contents) override {
fullscreened_contents_ = nullptr;
}
bool IsFullscreenForTabOrPending(const WebContents* web_contents) override {
return fullscreened_contents_ && web_contents == fullscreened_contents_;
}
private:
WebContents* fullscreened_contents_;
DISALLOW_COPY_AND_ASSIGN(FakeFullscreenDelegate);
};
class FakeWebContentsDelegate : public WebContentsDelegate {
public:
FakeWebContentsDelegate() : loading_state_changed_was_called_(false) {}
~FakeWebContentsDelegate() override {}
void LoadingStateChanged(WebContents* source,
bool to_different_document) override {
loading_state_changed_was_called_ = true;
}
bool loading_state_changed_was_called() const {
return loading_state_changed_was_called_;
}
private:
bool loading_state_changed_was_called_;
DISALLOW_COPY_AND_ASSIGN(FakeWebContentsDelegate);
};
class FakeImageDownloader : public blink::mojom::ImageDownloader {
public:
FakeImageDownloader() = default;
~FakeImageDownloader() override = default;
void Init(service_manager::InterfaceProvider* interface_provider) {
service_manager::InterfaceProvider::TestApi test_api(interface_provider);
test_api.SetBinderForName(blink::mojom::ImageDownloader::Name_,
base::BindRepeating(&FakeImageDownloader::Bind,
base::Unretained(this)));
}
void DownloadImage(const GURL& url,
bool is_favicon,
uint32_t preferred_size,
uint32_t max_bitmap_size,
bool bypass_cache,
DownloadImageCallback callback) override {
if (!base::Contains(fake_response_data_per_url_, url)) {
// This could return a 404, but there is no test that currently relies on
// it.
return;
}
const FakeResponseData& response_data = fake_response_data_per_url_[url];
std::move(callback).Run(/*http_status_code=*/200, response_data.bitmaps,
response_data.original_bitmap_sizes);
}
void SetFakeResponseData(
const GURL& url,
const std::vector<SkBitmap>& bitmaps,
const std::vector<gfx::Size>& original_bitmap_sizes) {
fake_response_data_per_url_[url] =
FakeResponseData{bitmaps, original_bitmap_sizes};
}
private:
struct FakeResponseData {
std::vector<SkBitmap> bitmaps;
std::vector<gfx::Size> original_bitmap_sizes;
};
void Bind(mojo::ScopedMessagePipeHandle handle) {
receiver_.Bind(mojo::PendingReceiver<blink::mojom::ImageDownloader>(
std::move(handle)));
}
mojo::Receiver<blink::mojom::ImageDownloader> receiver_{this};
std::map<GURL, FakeResponseData> fake_response_data_per_url_;
};
} // namespace
TEST_F(WebContentsImplTest, SetMainFrameMimeType) {
ASSERT_TRUE(controller().IsInitialNavigation());
std::string mime = "text/html";
main_test_rfh()->GetPage().SetContentsMimeType(mime);
EXPECT_EQ(mime, contents()->GetContentsMimeType());
}
TEST_F(WebContentsImplTest, UpdateTitle) {
FakeWebContentsDelegate fake_delegate;
contents()->SetDelegate(&fake_delegate);
NavigationControllerImpl& cont =
static_cast<NavigationControllerImpl&>(controller());
cont.LoadURL(GURL(url::kAboutBlankURL), Referrer(), ui::PAGE_TRANSITION_TYPED,
std::string());
auto params = mojom::DidCommitProvisionalLoadParams::New();
params->url = GURL(url::kAboutBlankURL);
params->origin = url::Origin::Create(params->url);
params->referrer = blink::mojom::Referrer::New();
params->transition = ui::PAGE_TRANSITION_TYPED;
params->should_update_history = false;
params->did_create_new_entry = true;
params->gesture = NavigationGestureUser;
params->method = "GET";
params->page_state = blink::PageState::CreateFromURL(params->url);
main_test_rfh()->SendNavigateWithParams(std::move(params),
false /* was_within_same_document */);
contents()->UpdateTitle(main_test_rfh(), u" Lots O' Whitespace\n",
base::i18n::LEFT_TO_RIGHT);
// Make sure that title updates get stripped of whitespace.
EXPECT_EQ(u"Lots O' Whitespace", contents()->GetTitle());
EXPECT_FALSE(contents()->IsWaitingForResponse());
EXPECT_TRUE(fake_delegate.loading_state_changed_was_called());
contents()->SetDelegate(nullptr);
}
TEST_F(WebContentsImplTest, UpdateTitleBeforeFirstNavigation) {
ASSERT_TRUE(controller().IsInitialNavigation());
const std::u16string title = u"Initial Entry Title";
contents()->UpdateTitle(main_test_rfh(), title, base::i18n::LEFT_TO_RIGHT);
EXPECT_EQ(title, contents()->GetTitle());
}
TEST_F(WebContentsImplTest, UpdateTitleWhileFirstNavigationIsPending) {
const GURL kGURL(GetWebUIURL("blah"));
controller().LoadURL(kGURL, Referrer(), ui::PAGE_TRANSITION_TYPED,
std::string());
ASSERT_TRUE(!!controller().GetPendingEntry());
const std::u16string title = u"Initial Entry Title";
contents()->UpdateTitle(main_test_rfh(), title, base::i18n::LEFT_TO_RIGHT);
EXPECT_EQ(title, contents()->GetTitle());
}
TEST_F(WebContentsImplTest, DontUsePendingEntryUrlAsTitle) {
const GURL kGURL(GetWebUIURL("blah"));
controller().LoadURL(
kGURL, Referrer(), ui::PAGE_TRANSITION_TYPED, std::string());
EXPECT_EQ(std::u16string(), contents()->GetTitle());
}
TEST_F(WebContentsImplTest, UpdateAndUseTitleFromFirstNavigationPendingEntry) {
const GURL kGURL(GetWebUIURL("blah"));
controller().LoadURL(kGURL, Referrer(), ui::PAGE_TRANSITION_TYPED,
std::string());
MockWebContentsDelegate delegate;
contents()->SetDelegate(&delegate);
EXPECT_CALL(delegate,
NavigationStateChanged(contents(), INVALIDATE_TYPE_TITLE));
const std::u16string title = u"Initial Entry Title";
contents()->UpdateTitleForEntry(controller().GetPendingEntry(), title);
EXPECT_EQ(title, contents()->GetTitle());
}
TEST_F(WebContentsImplTest,
UpdateAndDontUseTitleFromPendingEntryForSecondNavigation) {
const GURL first_gurl("http://www.foo.com");
const GURL second_gurl("http://www.bar.com");
// Complete first navigation.
NavigationSimulator::NavigateAndCommitFromBrowser(contents(), first_gurl);
std::u16string first_title = contents()->GetTitle();
// Start second navigation.
controller().LoadURL(second_gurl, Referrer(), ui::PAGE_TRANSITION_TYPED,
std::string());
// We shouldn't use the title of the second navigation's pending entry, even
// after explicitly setting it - we only use the pending entry's title if it's
// for the first navigation.
contents()->UpdateTitleForEntry(controller().GetPendingEntry(), u"bar");
EXPECT_EQ(contents()->GetTitle(), first_title);
}
// Stub out local frame mojo binding. Intercepts calls to EnableViewSourceMode
// and marks the message as received. This class attaches to the first
// RenderFrameHostImpl created.
class EnableViewSourceLocalFrame : public content::FakeLocalFrame,
public WebContentsObserver {
public:
explicit EnableViewSourceLocalFrame(WebContents* web_contents)
: WebContentsObserver(web_contents) {}
void RenderFrameCreated(RenderFrameHost* render_frame_host) override {
if (!initialized_) {
initialized_ = true;
Init(render_frame_host->GetRemoteAssociatedInterfaces());
}
}
void EnableViewSourceMode() final { enabled_view_source_ = true; }
bool IsViewSourceModeEnabled() const { return enabled_view_source_; }
private:
bool enabled_view_source_ = false;
bool initialized_ = false;
};
// Browser initiated navigations to view-source URLs of WebUI pages should work.
TEST_F(WebContentsImplTest, DirectNavigationToViewSourceWebUI) {
const GURL kGURL("view-source:" + GetWebUIURLString("blah/"));
// NavigationControllerImpl rewrites view-source URLs, simulating that here.
const GURL kRewrittenURL(GetWebUIURL("blah"));
EnableViewSourceLocalFrame local_frame(contents());
NavigationSimulator::NavigateAndCommitFromBrowser(contents(), kGURL);
// Did we get the expected message?
base::RunLoop().RunUntilIdle();
EXPECT_TRUE(local_frame.IsViewSourceModeEnabled());
// This is the virtual URL.
EXPECT_EQ(
kGURL,
contents()->GetController().GetLastCommittedEntry()->GetVirtualURL());
// The actual URL navigated to.
EXPECT_EQ(kRewrittenURL,
contents()->GetController().GetLastCommittedEntry()->GetURL());
}
// Test simple same-SiteInstance navigation.
TEST_F(WebContentsImplTest, SimpleNavigation) {
TestRenderFrameHost* orig_rfh = main_test_rfh();
SiteInstance* instance1 = contents()->GetSiteInstance();
EXPECT_EQ(nullptr, contents()->GetSpeculativePrimaryMainFrame());
// Navigate until ready to commit.
const GURL url("http://www.google.com");
auto navigation =
NavigationSimulator::CreateBrowserInitiated(url, contents());
navigation->ReadyToCommit();
EXPECT_FALSE(contents()->CrossProcessNavigationPending());
EXPECT_EQ(instance1, orig_rfh->GetSiteInstance());
// Controller's pending entry will have a null site instance until we assign
// it in Commit.
EXPECT_EQ(
nullptr,
NavigationEntryImpl::FromNavigationEntry(controller().GetVisibleEntry())->
site_instance());
navigation->Commit();
EXPECT_FALSE(contents()->CrossProcessNavigationPending());
EXPECT_EQ(orig_rfh, main_test_rfh());
EXPECT_EQ(instance1, orig_rfh->GetSiteInstance());
// Controller's entry should now have the SiteInstance, or else we won't be
// able to find it later.
EXPECT_EQ(
instance1,
NavigationEntryImpl::FromNavigationEntry(controller().GetVisibleEntry())->
site_instance());
}
// Test that we reject NavigateToEntry if the url is over kMaxURLChars.
TEST_F(WebContentsImplTest, NavigateToExcessivelyLongURL) {
// Construct a URL that's kMaxURLChars + 1 long of all 'a's.
const GURL url(std::string("http://example.org/").append(
url::kMaxURLChars + 1, 'a'));
controller().LoadURL(
url, Referrer(), ui::PAGE_TRANSITION_GENERATED, std::string());
EXPECT_EQ(nullptr, controller().GetPendingEntry());
}
// Test that we reject NavigateToEntry if the url is invalid.
TEST_F(WebContentsImplTest, NavigateToInvalidURL) {
// Invalid URLs should not trigger a navigation.
const GURL invalid_url("view-source:http://example.org/%00");
controller().LoadURL(
invalid_url, Referrer(), ui::PAGE_TRANSITION_GENERATED, std::string());
EXPECT_EQ(nullptr, controller().GetPendingEntry());
// Empty URLs are supported and should start a navigation.
controller().LoadURL(
GURL(), Referrer(), ui::PAGE_TRANSITION_GENERATED, std::string());
EXPECT_NE(nullptr, controller().GetPendingEntry());
}
// Test that we reject NavigateToEntry if the url is a renderer debug URL
// inside a view-source: URL. This verifies that the navigation is not allowed
// to proceed after the view-source: URL rewriting logic has run.
TEST_F(WebContentsImplTest, NavigateToViewSourceRendererDebugURL) {
const GURL renderer_debug_url(blink::kChromeUIKillURL);
const GURL view_source_debug_url("view-source:" + renderer_debug_url.spec());
EXPECT_TRUE(blink::IsRendererDebugURL(renderer_debug_url));
EXPECT_FALSE(blink::IsRendererDebugURL(view_source_debug_url));
controller().LoadURL(view_source_debug_url, Referrer(),
ui::PAGE_TRANSITION_GENERATED, std::string());
EXPECT_EQ(nullptr, controller().GetPendingEntry());
}
// Test that navigating across a site boundary creates a new RenderViewHost
// with a new SiteInstance. Going back should do the same.
TEST_F(WebContentsImplTest, CrossSiteBoundaries) {
// This test assumes no interaction with the back forward cache.
// Similar coverage when BFCache is on can be found in
// BackForwardCacheBrowserTest.NavigateBackForwardRepeatedly.
contents()->GetController().GetBackForwardCache().DisableForTesting(
BackForwardCache::TEST_ASSUMES_NO_CACHING);
TestRenderFrameHost* orig_rfh = main_test_rfh();
int orig_rvh_delete_count = 0;
orig_rfh->GetRenderViewHost()->set_delete_counter(&orig_rvh_delete_count);
SiteInstance* instance1 = contents()->GetSiteInstance();
// Navigate to URL. First URL should use first RenderViewHost.
const GURL url("http://www.google.com");
NavigationSimulator::NavigateAndCommitFromBrowser(contents(), url);
// Keep the number of active frames in orig_rfh's SiteInstance non-zero so
// that orig_rfh doesn't get deleted when it gets swapped out.
orig_rfh->GetSiteInstance()->IncrementActiveFrameCount();
EXPECT_FALSE(contents()->CrossProcessNavigationPending());
EXPECT_EQ(orig_rfh->GetRenderViewHost(), contents()->GetRenderViewHost());
EXPECT_EQ(url, contents()->GetLastCommittedURL());
EXPECT_EQ(url, contents()->GetVisibleURL());
// Navigate to new site
const GURL url2("http://www.yahoo.com");
auto new_site_navigation =
NavigationSimulator::CreateBrowserInitiated(url2, contents());
new_site_navigation->ReadyToCommit();
EXPECT_TRUE(contents()->CrossProcessNavigationPending());
EXPECT_EQ(url, contents()->GetLastCommittedURL());
EXPECT_EQ(url2, contents()->GetVisibleURL());
TestRenderFrameHost* pending_rfh =
contents()->GetSpeculativePrimaryMainFrame();
EXPECT_TRUE(pending_rfh->GetLastCommittedURL().is_empty());
int pending_rvh_delete_count = 0;
pending_rfh->GetRenderViewHost()->set_delete_counter(
&pending_rvh_delete_count);
// DidNavigate from the pending page.
new_site_navigation->Commit();
SiteInstance* instance2 = contents()->GetSiteInstance();
// Keep the number of active frames in pending_rfh's SiteInstance
// non-zero so that orig_rfh doesn't get deleted when it gets
// swapped out.
pending_rfh->GetSiteInstance()->IncrementActiveFrameCount();
EXPECT_FALSE(contents()->CrossProcessNavigationPending());
EXPECT_EQ(pending_rfh, main_test_rfh());
EXPECT_EQ(url2, contents()->GetLastCommittedURL());
EXPECT_EQ(url2, contents()->GetVisibleURL());
EXPECT_NE(instance1, instance2);
EXPECT_EQ(nullptr, contents()->GetSpeculativePrimaryMainFrame());
// We keep a proxy for the original RFH's SiteInstance.
EXPECT_TRUE(contents()->GetRenderManagerForTesting()->GetRenderFrameProxyHost(
instance1));
EXPECT_EQ(orig_rvh_delete_count, 0);
// Going back should switch SiteInstances again. The first SiteInstance is
// stored in the NavigationEntry, so it should be the same as at the start.
// We should use the same RFH as before, swapping it back in.
auto back_navigation =
NavigationSimulator::CreateHistoryNavigation(-1, contents());
back_navigation->ReadyToCommit();
TestRenderFrameHost* goback_rfh =
contents()->GetSpeculativePrimaryMainFrame();
EXPECT_TRUE(contents()->CrossProcessNavigationPending());
// DidNavigate from the back action.
back_navigation->Commit();
EXPECT_FALSE(contents()->CrossProcessNavigationPending());
EXPECT_EQ(goback_rfh, main_test_rfh());
EXPECT_EQ(url, contents()->GetLastCommittedURL());
EXPECT_EQ(url, contents()->GetVisibleURL());
EXPECT_EQ(instance1, contents()->GetSiteInstance());
// There should be a proxy for the pending RFH SiteInstance.
EXPECT_TRUE(contents()->GetRenderManagerForTesting()->GetRenderFrameProxyHost(
instance2));
EXPECT_EQ(pending_rvh_delete_count, 0);
// Close contents and ensure RVHs are deleted.
DeleteContents();
EXPECT_EQ(orig_rvh_delete_count, 1);
EXPECT_EQ(pending_rvh_delete_count, 1);
}
// Test that navigating across a site boundary after a crash creates a new
// RFH without requiring a cross-site transition (i.e., PENDING state).
TEST_F(WebContentsImplTest, CrossSiteBoundariesAfterCrash) {
// Ensure that the cross-site transition will also be cross-process on
// Android.
IsolateAllSitesForTesting(base::CommandLine::ForCurrentProcess());
TestRenderFrameHost* orig_rfh = main_test_rfh();
int orig_rvh_delete_count = 0;
orig_rfh->GetRenderViewHost()->set_delete_counter(&orig_rvh_delete_count);
SiteInstance* instance1 = contents()->GetSiteInstance();
// Navigate to URL. First URL should use first RenderViewHost.
const GURL url("http://www.google.com");
NavigationSimulator::NavigateAndCommitFromBrowser(contents(), url);
EXPECT_FALSE(contents()->CrossProcessNavigationPending());
EXPECT_EQ(orig_rfh->GetRenderViewHost(), contents()->GetRenderViewHost());
// Simulate a renderer crash.
EXPECT_TRUE(orig_rfh->IsRenderFrameLive());
orig_rfh->GetProcess()->SimulateCrash();
EXPECT_FALSE(orig_rfh->IsRenderFrameLive());
// Start navigating to a new site. We should not go into PENDING.
const GURL url2("http://www.yahoo.com");
auto navigation_to_url2 =
NavigationSimulator::CreateBrowserInitiated(url2, contents());
navigation_to_url2->ReadyToCommit();
TestRenderFrameHost* new_rfh = main_test_rfh();
if (ShouldSkipEarlyCommitPendingForCrashedFrame()) {
EXPECT_TRUE(contents()->CrossProcessNavigationPending());
EXPECT_NE(nullptr, contents()->GetSpeculativePrimaryMainFrame());
EXPECT_EQ(orig_rfh, new_rfh);
EXPECT_EQ(orig_rvh_delete_count, 0);
} else {
EXPECT_FALSE(contents()->CrossProcessNavigationPending());
EXPECT_EQ(nullptr, contents()->GetSpeculativePrimaryMainFrame());
EXPECT_NE(orig_rfh, new_rfh);
EXPECT_EQ(orig_rvh_delete_count, 1);
}
navigation_to_url2->Commit();
SiteInstance* instance2 = contents()->GetSiteInstance();
EXPECT_FALSE(contents()->CrossProcessNavigationPending());
if (ShouldSkipEarlyCommitPendingForCrashedFrame()) {
EXPECT_NE(new_rfh, main_rfh());
} else {
EXPECT_EQ(new_rfh, main_rfh());
}
EXPECT_NE(instance1, instance2);
EXPECT_EQ(nullptr, contents()->GetSpeculativePrimaryMainFrame());
// Close contents and ensure RVHs are deleted.
DeleteContents();
EXPECT_EQ(orig_rvh_delete_count, 1);
}
// Test that opening a new contents in the same SiteInstance and then navigating
// both contentses to a new site will place both contentses in a single
// SiteInstance.
TEST_F(WebContentsImplTest, NavigateTwoTabsCrossSite) {
SiteInstance* instance1 = contents()->GetSiteInstance();
// Navigate to URL. First URL should use first RenderViewHost.
const GURL url("http://www.google.com");
NavigationSimulator::NavigateAndCommitFromBrowser(contents(), url);
// Open a new contents with the same SiteInstance, navigated to the same site.
std::unique_ptr<TestWebContents> contents2(
TestWebContents::Create(browser_context(), instance1));
NavigationSimulator::NavigateAndCommitFromBrowser(contents2.get(), url);
EXPECT_EQ(instance1, contents2->GetSiteInstance());
// Navigate first contents to a new site.
const GURL url2a = isolated_cross_site_url();
auto navigation1 =
NavigationSimulator::CreateBrowserInitiated(url2a, contents());
navigation1->SetTransition(ui::PAGE_TRANSITION_LINK);
navigation1->ReadyToCommit();
EXPECT_TRUE(contents()->CrossProcessNavigationPending());
navigation1->Commit();
SiteInstance* instance2a = contents()->GetSiteInstance();
EXPECT_NE(instance1, instance2a);
// Navigate second contents to the same site as the first tab.
const GURL url2b = isolated_cross_site_url().Resolve("/foo");
auto navigation2 =
NavigationSimulator::CreateBrowserInitiated(url2b, contents2.get());
navigation2->SetTransition(ui::PAGE_TRANSITION_LINK);
navigation2->ReadyToCommit();
EXPECT_TRUE(contents2->CrossProcessNavigationPending());
// NOTE(creis): We used to be in danger of showing a crash page here if the
// second contents hadn't navigated somewhere first (bug 1145430). That case
// is now covered by the CrossSiteBoundariesAfterCrash test.
navigation2->Commit();
SiteInstance* instance2b = contents2->GetSiteInstance();
EXPECT_NE(instance1, instance2b);
// Both contentses should now be in the same SiteInstance.
EXPECT_EQ(instance2a, instance2b);
}
// The embedder can request sites for certain urls not be be assigned to the
// SiteInstance through ShouldAssignSiteForURL() in content browser client,
// allowing to reuse the renderer backing certain chrome urls for subsequent
// navigation. The test verifies that the override is honored.
TEST_F(WebContentsImplTest, NavigateFromSitelessUrl) {
WebContentsImplTestBrowserClient browser_client;
SetBrowserClientForTesting(&browser_client);
TestRenderFrameHost* orig_rfh = main_test_rfh();
int orig_rvh_delete_count = 0;
orig_rfh->GetRenderViewHost()->set_delete_counter(&orig_rvh_delete_count);
SiteInstanceImpl* orig_instance = contents()->GetSiteInstance();
// Navigate to an URL that will not assign a new SiteInstance.
const GURL native_url("non-site-url://stuffandthings");
browser_client.set_assign_site_for_url(false, native_url);
NavigationSimulator::NavigateAndCommitFromBrowser(contents(), native_url);
EXPECT_FALSE(contents()->CrossProcessNavigationPending());
EXPECT_EQ(orig_rfh, main_test_rfh());
EXPECT_EQ(native_url, contents()->GetLastCommittedURL());
EXPECT_EQ(native_url, contents()->GetVisibleURL());
EXPECT_EQ(orig_instance, contents()->GetSiteInstance());
EXPECT_EQ(GURL(), contents()->GetSiteInstance()->GetSiteURL());
EXPECT_FALSE(orig_instance->HasSite());
// Navigate to new site (should keep same site instance).
const GURL url("http://www.google.com");
browser_client.set_assign_site_for_url(true, url);
auto navigation1 =
NavigationSimulator::CreateBrowserInitiated(url, contents());
navigation1->ReadyToCommit();
EXPECT_FALSE(contents()->CrossProcessNavigationPending());
EXPECT_EQ(native_url, contents()->GetLastCommittedURL());
EXPECT_EQ(url, contents()->GetVisibleURL());
EXPECT_FALSE(contents()->GetSpeculativePrimaryMainFrame());
navigation1->Commit();
// The first entry's SiteInstance should be reset to a new, related one. This
// prevents wrongly detecting a SiteInstance mismatch when returning to it
// later.
SiteInstanceImpl* prev_entry_instance = contents()
->GetController()
.GetEntryAtIndex(0)
->root_node()
->frame_entry->site_instance();
EXPECT_NE(prev_entry_instance, orig_instance);
EXPECT_TRUE(orig_instance->IsRelatedSiteInstance(prev_entry_instance));
EXPECT_FALSE(prev_entry_instance->HasSite());
SiteInstanceImpl* curr_entry_instance = contents()
->GetController()
.GetEntryAtIndex(1)
->root_node()
->frame_entry->site_instance();
EXPECT_EQ(curr_entry_instance, orig_instance);
// Keep the number of active frames in orig_rfh's SiteInstance
// non-zero so that orig_rfh doesn't get deleted when it gets
// swapped out.
orig_rfh->GetSiteInstance()->IncrementActiveFrameCount();
EXPECT_EQ(orig_instance, contents()->GetSiteInstance());
if (AreDefaultSiteInstancesEnabled()) {
// Verify that the empty SiteInstance gets converted into a default
// SiteInstance because |url| does not require a dedicated process.
EXPECT_TRUE(contents()->GetSiteInstance()->IsDefaultSiteInstance());
} else {
EXPECT_TRUE(
contents()->GetSiteInstance()->GetSiteURL().DomainIs("google.com"));
}
EXPECT_EQ(url, contents()->GetLastCommittedURL());
// Navigate to another new site (should create a new site instance).
const GURL url2 = isolated_cross_site_url();
auto navigation2 =
NavigationSimulator::CreateBrowserInitiated(url2, contents());
navigation2->ReadyToCommit();
EXPECT_TRUE(contents()->CrossProcessNavigationPending());
EXPECT_EQ(url, contents()->GetLastCommittedURL());
EXPECT_EQ(url2, contents()->GetVisibleURL());
TestRenderFrameHost* pending_rfh =
contents()->GetSpeculativePrimaryMainFrame();
int pending_rvh_delete_count = 0;
pending_rfh->GetRenderViewHost()->set_delete_counter(
&pending_rvh_delete_count);
// DidNavigate from the pending page.
navigation2->Commit();
SiteInstance* new_instance = contents()->GetSiteInstance();
EXPECT_FALSE(contents()->CrossProcessNavigationPending());
EXPECT_EQ(pending_rfh, main_test_rfh());
EXPECT_EQ(url2, contents()->GetLastCommittedURL());
EXPECT_EQ(url2, contents()->GetVisibleURL());
EXPECT_NE(new_instance, orig_instance);
EXPECT_FALSE(contents()->GetSpeculativePrimaryMainFrame());
EXPECT_EQ(orig_rvh_delete_count, 0);
// Close contents and ensure RVHs are deleted.
DeleteContents();
EXPECT_EQ(orig_rvh_delete_count, 1);
EXPECT_EQ(pending_rvh_delete_count, 1);
}
// Regression test for http://crbug.com/386542 - variation of
// NavigateFromSitelessUrl in which the original navigation is a session
// restore.
TEST_F(WebContentsImplTest, NavigateFromRestoredSitelessUrl) {
WebContentsImplTestBrowserClient browser_client;
SetBrowserClientForTesting(&browser_client);
SiteInstanceImpl* orig_instance = contents()->GetSiteInstance();
TestRenderFrameHost* orig_rfh = main_test_rfh();
// Restore a navigation entry for URL that should not assign site to the
// SiteInstance.
const GURL native_url("non-site-url://stuffandthings");
browser_client.set_assign_site_for_url(false, native_url);
std::vector<std::unique_ptr<NavigationEntry>> entries;
std::unique_ptr<NavigationEntry> new_entry =
NavigationController::CreateNavigationEntry(
native_url, Referrer(), absl::nullopt, ui::PAGE_TRANSITION_LINK,
false, std::string(), browser_context(),
nullptr /* blob_url_loader_factory */);
entries.push_back(std::move(new_entry));
controller().Restore(0, RestoreType::kRestored, &entries);
ASSERT_EQ(0u, entries.size());
ASSERT_EQ(1, controller().GetEntryCount());
EXPECT_TRUE(controller().NeedsReload());
controller().LoadIfNecessary();
orig_rfh->SendNavigateWithTransition(0, false, native_url,
ui::PAGE_TRANSITION_RELOAD);
EXPECT_EQ(orig_instance, contents()->GetSiteInstance());
EXPECT_EQ(GURL(), contents()->GetSiteInstance()->GetSiteURL());
EXPECT_FALSE(orig_instance->HasSite());
// Navigate to a regular site and verify that the SiteInstance was kept.
const GURL url("http://www.google.com");
browser_client.set_assign_site_for_url(true, url);
NavigationSimulator::NavigateAndCommitFromBrowser(contents(), url);
EXPECT_EQ(orig_instance, contents()->GetSiteInstance());
// Cleanup.
DeleteContents();
}
// Complement for NavigateFromRestoredSitelessUrl, verifying that when a regular
// tab is restored, the SiteInstance will change upon navigation.
TEST_F(WebContentsImplTest, NavigateFromRestoredRegularUrl) {
WebContentsImplTestBrowserClient browser_client;
SetBrowserClientForTesting(&browser_client);
SiteInstanceImpl* orig_instance = contents()->GetSiteInstance();
TestRenderFrameHost* orig_rfh = main_test_rfh();
// Restore a navigation entry for a regular URL ensuring that the embedder
// ShouldAssignSiteForUrl override is disabled (i.e. returns true).
const GURL regular_url("http://www.yahoo.com");
browser_client.set_assign_site_for_url(true, regular_url);
std::vector<std::unique_ptr<NavigationEntry>> entries;
std::unique_ptr<NavigationEntry> new_entry =
NavigationController::CreateNavigationEntry(
regular_url, Referrer(), absl::nullopt, ui::PAGE_TRANSITION_LINK,
false, std::string(), browser_context(),
nullptr /* blob_url_loader_factory */);
entries.push_back(std::move(new_entry));
controller().Restore(0, RestoreType::kRestored, &entries);
ASSERT_EQ(0u, entries.size());
ASSERT_EQ(1, controller().GetEntryCount());
EXPECT_TRUE(controller().NeedsReload());
controller().LoadIfNecessary();
orig_rfh->PrepareForCommit();
orig_rfh->SendNavigateWithTransition(0, false, regular_url,
ui::PAGE_TRANSITION_RELOAD);
EXPECT_EQ(orig_instance, contents()->GetSiteInstance());
EXPECT_TRUE(orig_instance->HasSite());
EXPECT_EQ(AreDefaultSiteInstancesEnabled(),
orig_instance->IsDefaultSiteInstance());
// Navigate to another site and verify that a new SiteInstance was created.
const GURL url("http://www.google.com");
NavigationSimulator::NavigateAndCommitFromBrowser(contents(), url);
if (AreDefaultSiteInstancesEnabled()) {
// Verify this remains the default SiteInstance since |url| does
// not require a dedicated process.
EXPECT_EQ(orig_instance, contents()->GetSiteInstance());
// Navigate to a URL that does require a dedicated process and verify that
// the SiteInstance changes.
NavigationSimulator::NavigateAndCommitFromBrowser(
contents(), isolated_cross_site_url());
EXPECT_NE(orig_instance, contents()->GetSiteInstance());
} else {
EXPECT_NE(orig_instance, contents()->GetSiteInstance());
}
// Cleanup.
DeleteContents();
}
// Test that we can find an opener RVH even if it's pending.
// http://crbug.com/176252.
TEST_F(WebContentsImplTest, FindOpenerRVHWhenPending) {
// Navigate to a URL.
const GURL url("http://www.google.com");
NavigationSimulator::NavigateAndCommitFromBrowser(contents(), url);
// Start to navigate first tab to a new site, so that it has a pending RVH.
const GURL url2("http://www.yahoo.com");
auto navigation =
NavigationSimulator::CreateBrowserInitiated(url2, contents());
navigation->ReadyToCommit();
TestRenderFrameHost* pending_rfh =
contents()->GetSpeculativePrimaryMainFrame();
SiteInstance* instance = pending_rfh->GetSiteInstance();
// While it is still pending, simulate opening a new tab with the first tab
// as its opener. This will call CreateOpenerProxies on the opener to ensure
// that an RVH exists.
std::unique_ptr<TestWebContents> popup(
TestWebContents::Create(browser_context(), instance));
popup->SetOpener(contents());
contents()->GetRenderManager()->CreateOpenerProxies(instance, nullptr);
// If swapped out is forbidden, a new proxy should be created for the opener
// in |instance|, and we should ensure that its routing ID is returned here.
// Otherwise, we should find the pending RFH and not create a new proxy.
auto opener_frame_token =
popup->GetRenderManager()->GetOpenerFrameToken(instance);
RenderFrameProxyHost* proxy =
contents()->GetRenderManager()->GetRenderFrameProxyHost(instance);
EXPECT_TRUE(proxy);
EXPECT_EQ(*opener_frame_token, proxy->GetFrameToken());
// Ensure that committing the navigation removes the proxy.
navigation->Commit();
EXPECT_FALSE(
contents()->GetRenderManager()->GetRenderFrameProxyHost(instance));
}
// Tests that WebContentsImpl uses the current URL, not the SiteInstance's site,
// to determine whether a navigation is cross-site.
TEST_F(WebContentsImplTest, CrossSiteComparesAgainstCurrentPage) {
// The assumptions this test makes aren't valid with --site-per-process. For
// example, a cross-site URL won't ever commit in the old RFH. The test also
// assumes that default SiteInstances are enabled, and that aggressive
// BrowsingInstance swapping (even on renderer-initiated navigations) is
// disabled.
if (AreAllSitesIsolatedForTesting() || !AreDefaultSiteInstancesEnabled() ||
CanCrossSiteNavigationsProactivelySwapBrowsingInstances()) {
return;
}
TestRenderFrameHost* orig_rfh = main_test_rfh();
SiteInstanceImpl* instance1 = contents()->GetSiteInstance();
const GURL url("http://www.google.com");
// Navigate to URL.
NavigationSimulator::NavigateAndCommitFromBrowser(contents(), url);
// Open a related contents to a second site.
std::unique_ptr<TestWebContents> contents2(
TestWebContents::Create(browser_context(), instance1));
const GURL url2("http://www.yahoo.com");
auto navigation =
NavigationSimulator::CreateBrowserInitiated(url2, contents2.get());
navigation->ReadyToCommit();
// The first RVH in contents2 isn't live yet, so we shortcut the cross site
// pending.
EXPECT_FALSE(contents2->CrossProcessNavigationPending());
navigation->Commit();
SiteInstance* instance2 = contents2->GetSiteInstance();
// With default SiteInstances, navigations in both tabs should
// share the same default SiteInstance, since neither requires a dedicated
// process.
EXPECT_EQ(instance1, instance2);
EXPECT_TRUE(instance1->IsDefaultSiteInstance());
EXPECT_FALSE(contents2->CrossProcessNavigationPending());
// Simulate a link click in first contents to second site. This doesn't
// switch SiteInstances and stays in the default SiteInstance.
NavigationSimulator::NavigateAndCommitFromDocument(url2, orig_rfh);
SiteInstance* instance3 = contents()->GetSiteInstance();
EXPECT_EQ(instance1, instance3);
EXPECT_FALSE(contents()->CrossProcessNavigationPending());
// Navigate same-site. This also stays in the default SiteInstance.
const GURL url3("http://mail.yahoo.com");
NavigationSimulator::NavigateAndCommitFromBrowser(contents(), url3);
SiteInstance* instance4 = contents()->GetSiteInstance();
EXPECT_EQ(instance1, instance4);
}
// Test that the onbeforeunload and onunload handlers run when navigating
// across site boundaries.
TEST_F(WebContentsImplTest, CrossSiteUnloadHandlers) {
TestRenderFrameHost* orig_rfh = main_test_rfh();
SiteInstance* instance1 = contents()->GetSiteInstance();
// Navigate to URL. First URL should use first RenderViewHost.
const GURL url("http://www.google.com");
NavigationSimulator::NavigateAndCommitFromBrowser(contents(), url);
EXPECT_FALSE(contents()->CrossProcessNavigationPending());
EXPECT_EQ(orig_rfh, main_test_rfh());
// Navigate to new site, but simulate an onbeforeunload denial.
const GURL url2("http://www.yahoo.com");
controller().LoadURL(
url2, Referrer(), ui::PAGE_TRANSITION_TYPED, std::string());
EXPECT_TRUE(orig_rfh->is_waiting_for_beforeunload_completion());
orig_rfh->SimulateBeforeUnloadCompleted(false);
EXPECT_FALSE(orig_rfh->is_waiting_for_beforeunload_completion());
EXPECT_FALSE(contents()->CrossProcessNavigationPending());
EXPECT_EQ(orig_rfh, main_test_rfh());
// Navigate again, but simulate an onbeforeunload approval.
controller().LoadURL(
url2, Referrer(), ui::PAGE_TRANSITION_TYPED, std::string());
EXPECT_TRUE(orig_rfh->is_waiting_for_beforeunload_completion());
auto navigation = NavigationSimulator::CreateFromPending(contents());
navigation->ReadyToCommit();
EXPECT_FALSE(orig_rfh->is_waiting_for_beforeunload_completion());
EXPECT_TRUE(contents()->CrossProcessNavigationPending());
TestRenderFrameHost* pending_rfh =
contents()->GetSpeculativePrimaryMainFrame();
// DidNavigate from the pending page.
navigation->Commit();
SiteInstance* instance2 = contents()->GetSiteInstance();
EXPECT_FALSE(contents()->CrossProcessNavigationPending());
EXPECT_EQ(pending_rfh, main_test_rfh());
EXPECT_NE(instance1, instance2);
EXPECT_EQ(nullptr, contents()->GetSpeculativePrimaryMainFrame());
}
// Test that during a slow cross-site navigation, the original renderer can
// navigate to a different URL and have it displayed, canceling the slow
// navigation.
TEST_F(WebContentsImplTest, CrossSiteNavigationPreempted) {
TestRenderFrameHost* orig_rfh = main_test_rfh();
SiteInstance* instance1 = contents()->GetSiteInstance();
// Navigate to URL. First URL should use first RenderFrameHost.
const GURL url("http://www.google.com");
NavigationSimulator::NavigateAndCommitFromBrowser(contents(), url);
EXPECT_FALSE(contents()->CrossProcessNavigationPending());
EXPECT_EQ(orig_rfh, main_test_rfh());
// Navigate to new site.
const GURL url2("http://www.yahoo.com");
controller().LoadURL(
url2, Referrer(), ui::PAGE_TRANSITION_TYPED, std::string());
EXPECT_TRUE(orig_rfh->is_waiting_for_beforeunload_completion());
EXPECT_TRUE(contents()->CrossProcessNavigationPending());
// Suppose the original renderer navigates before the new one is ready.
const GURL url3("http://www.google.com/foo");
NavigationSimulator::NavigateAndCommitFromDocument(url3, orig_rfh);
// Verify that the pending navigation is cancelled.
SiteInstance* instance2 = contents()->GetSiteInstance();
if (CanSameSiteMainFrameNavigationsChangeRenderFrameHosts()) {
// If same-site ProactivelySwapBrowsingInstance or main-frame RenderDocument
// is enabled, the RFH should change.
EXPECT_NE(orig_rfh, main_test_rfh());
} else {
EXPECT_EQ(orig_rfh, main_test_rfh());
}
if (CanSameSiteMainFrameNavigationsChangeSiteInstances()) {
// When ProactivelySwapBrowsingInstance is enabled on same-site navigations,
// the SiteInstance will change.
EXPECT_NE(instance1, instance2);
} else {
EXPECT_EQ(instance1, instance2);
}
EXPECT_FALSE(main_test_rfh()->is_waiting_for_beforeunload_completion());
EXPECT_EQ(main_test_rfh()->GetLastCommittedURL(), url3);
EXPECT_FALSE(contents()->CrossProcessNavigationPending());
EXPECT_EQ(nullptr, contents()->GetSpeculativePrimaryMainFrame());
}
// Tests that if we go back twice (same-site then cross-site), and the same-site
// RFH commits first, the cross-site RFH's navigation is canceled. If the
// same-site navigation is a cross-RFH navigation, however, the same-site
// navigation will get canceled instead and we are left with the newer
// cross-site navigation.
// TODO(avi,creis): Consider changing this behavior to better match the user's
// intent.
TEST_F(WebContentsImplTest, CrossSiteNavigationBackPreempted) {
// The test wants to cover the case where the old page gets deleted, so that
// back navigations can be stopped at ReadyToCommit timing. Disable
// back/forward cache to ensure that it doesn't get preserved in the cache.
DisableBackForwardCacheForTesting(contents(),
BackForwardCache::TEST_ASSUMES_NO_CACHING);
const bool will_change_site_instance =
IsProactivelySwapBrowsingInstanceOnSameSiteNavigationEnabled();
// Start with a web ui page, which gets a new RVH with WebUI bindings.
GURL url1(std::string(kChromeUIScheme) + "://" +
std::string(kChromeUIGpuHost));
NavigationSimulator::NavigateAndCommitFromBrowser(contents(), url1);
TestRenderFrameHost* webui_rfh = main_test_rfh();
NavigationEntry* entry1 = controller().GetLastCommittedEntry();
SiteInstance* instance1 = contents()->GetSiteInstance();
EXPECT_FALSE(contents()->CrossProcessNavigationPending());
EXPECT_EQ(url1, entry1->GetURL());
EXPECT_EQ(instance1,
NavigationEntryImpl::FromNavigationEntry(entry1)->site_instance());
EXPECT_TRUE(webui_rfh->GetEnabledBindings() & BINDINGS_POLICY_WEB_UI);
// Navigate to new site.
const GURL url2("http://www.google.com");
NavigationSimulator::NavigateAndCommitFromBrowser(contents(), url2);
TestRenderFrameHost* google_rfh = main_test_rfh();
NavigationEntry* entry2 = controller().GetLastCommittedEntry();
SiteInstance* instance2 = contents()->GetSiteInstance();
EXPECT_FALSE(contents()->CrossProcessNavigationPending());
EXPECT_NE(instance1, instance2);
EXPECT_FALSE(contents()->GetSpeculativePrimaryMainFrame());
EXPECT_EQ(url2, entry2->GetURL());
EXPECT_EQ(instance2,
NavigationEntryImpl::FromNavigationEntry(entry2)->site_instance());
EXPECT_FALSE(google_rfh->GetEnabledBindings() & BINDINGS_POLICY_WEB_UI);
// Navigate to third page on same site.
const GURL url3("http://news.google.com");
NavigationSimulator::NavigateAndCommitFromBrowser(contents(), url3);
NavigationEntry* entry3 = controller().GetLastCommittedEntry();
SiteInstance* instance3 = contents()->GetSiteInstance();
EXPECT_FALSE(contents()->CrossProcessNavigationPending());
if (will_change_site_instance) {
// If same-site ProactivelySwapBrowsingInstance or main-frame RenderDocument
// is enabled, the RFH should change.
EXPECT_NE(google_rfh, main_test_rfh());
} else {
EXPECT_EQ(google_rfh, main_test_rfh());
}
if (will_change_site_instance) {
// When ProactivelySwapBrowsingInstance is enabled on same-site navigations,
// the SiteInstance will change.
EXPECT_NE(instance2, instance3);
} else {
EXPECT_EQ(instance2, instance3);
}
EXPECT_FALSE(contents()->GetSpeculativePrimaryMainFrame());
EXPECT_EQ(url3, entry3->GetURL());
EXPECT_EQ(instance3,
NavigationEntryImpl::FromNavigationEntry(entry3)->site_instance());
// Go back within the site.
auto back_navigation1 =
NavigationSimulatorImpl::CreateHistoryNavigation(-1, contents());
back_navigation1->Start();
auto* first_pending_rfh = contents()->GetSpeculativePrimaryMainFrame();
GlobalRenderFrameHostId first_pending_rfh_id;
if (will_change_site_instance) {
EXPECT_TRUE(contents()->CrossProcessNavigationPending());
EXPECT_TRUE(first_pending_rfh);
first_pending_rfh_id = first_pending_rfh->GetGlobalId();
} else {
EXPECT_FALSE(contents()->CrossProcessNavigationPending());
EXPECT_FALSE(first_pending_rfh);
}
EXPECT_EQ(entry2, controller().GetPendingEntry());
// Before that commits, go back again.
back_navigation1->ReadyToCommit();
auto back_navigation2 =
NavigationSimulatorImpl::CreateHistoryNavigation(-1, contents());
back_navigation2->Start();
EXPECT_TRUE(contents()->CrossProcessNavigationPending());
EXPECT_TRUE(contents()->GetSpeculativePrimaryMainFrame());
EXPECT_EQ(entry1, controller().GetPendingEntry());
if (will_change_site_instance) {
// When ProactivelySwapBrowsingInstance or RenderDocument is enabled on
// same-site main frame navigation, the first back navigation will create a
// speculative RFH even though it's a same-site navigation, and the
// speculative RFH will be overwritten by the second back-navigation that
// will also create a speculative RFH.
EXPECT_NE(first_pending_rfh_id,
contents()->GetSpeculativePrimaryMainFrame()->GetGlobalId());
// Calling Commit() on the first back navigation below will cause a DCHECK
// failure because we've already called DidFinishNavigaition on it, so we
// will call it on the second back navigation instead.
back_navigation2->Commit();
} else {
// DidNavigate from the first back. This aborts the second back's
// speculative RFH.
back_navigation1->Commit();
}
// We have committed this navigation and forgot about the second back if
// CanSameSiteMainFrameNavigationsChangeRenderFrameHosts() is false, or the
// first back if it's true.
EXPECT_FALSE(contents()->CrossProcessNavigationPending());
EXPECT_FALSE(controller().GetPendingEntry());
EXPECT_EQ(google_rfh, main_test_rfh());
if (will_change_site_instance) {
// We committed the second back navigation and landed on the first page.
EXPECT_EQ(url1, controller().GetLastCommittedEntry()->GetURL());
} else {
// We committed the second back navigation and landed on the second page.
EXPECT_EQ(url2, controller().GetLastCommittedEntry()->GetURL());
}
// We should not have corrupted the NTP entry.
EXPECT_EQ(instance3,
NavigationEntryImpl::FromNavigationEntry(entry3)->site_instance());
EXPECT_EQ(instance2,
NavigationEntryImpl::FromNavigationEntry(entry2)->site_instance());
EXPECT_EQ(instance1,
NavigationEntryImpl::FromNavigationEntry(entry1)->site_instance());
EXPECT_EQ(url1, entry1->GetURL());
}
// Tests that if we go back twice (same-site then cross-site), and the cross-
// site RFH commits first, we ignore the now-swapped-out RFH's commit.
TEST_F(WebContentsImplTest, CrossSiteNavigationBackOldNavigationIgnored) {
// The test assumes the previous page gets deleted after navigation. Disable
// back/forward cache to ensure that it doesn't get preserved in the cache.
DisableBackForwardCacheForTesting(contents(),
BackForwardCache::TEST_ASSUMES_NO_CACHING);
const bool will_change_site_instance =
IsProactivelySwapBrowsingInstanceOnSameSiteNavigationEnabled();
// This test assumes no interaction with the back/forward cache. Indeed, it
// isn't possible to perform the second back navigation in between the
// ReadyToCommit and Commit of the first back/forward cache one. Both steps
// are combined with it, nothing can happen in between.
contents()->GetController().GetBackForwardCache().DisableForTesting(
BackForwardCache::TEST_ASSUMES_NO_CACHING);
// Start with a web ui page, which gets a new RFH with WebUI bindings.
GURL url1(std::string(kChromeUIScheme) + "://" +
std::string(kChromeUIGpuHost));
NavigationSimulator::NavigateAndCommitFromBrowser(contents(), url1);
TestRenderFrameHost* webui_rfh = main_test_rfh();
NavigationEntry* entry1 = controller().GetLastCommittedEntry();
SiteInstance* instance1 = contents()->GetSiteInstance();
EXPECT_FALSE(contents()->CrossProcessNavigationPending());
EXPECT_EQ(url1, entry1->GetURL());
EXPECT_EQ(instance1,
NavigationEntryImpl::FromNavigationEntry(entry1)->site_instance());
EXPECT_TRUE(webui_rfh->GetEnabledBindings() & BINDINGS_POLICY_WEB_UI);
// Navigate to new site.
const GURL url2("http://www.google.com");
NavigationSimulator::NavigateAndCommitFromBrowser(contents(), url2);
TestRenderFrameHost* google_rfh = main_test_rfh();
NavigationEntry* entry2 = controller().GetLastCommittedEntry();
SiteInstance* instance2 = contents()->GetSiteInstance();
EXPECT_FALSE(contents()->CrossProcessNavigationPending());
EXPECT_NE(instance1, instance2);
EXPECT_FALSE(contents()->GetSpeculativePrimaryMainFrame());
EXPECT_EQ(url2, entry2->GetURL());
EXPECT_EQ(instance2,
NavigationEntryImpl::FromNavigationEntry(entry2)->site_instance());
EXPECT_FALSE(google_rfh->GetEnabledBindings() & BINDINGS_POLICY_WEB_UI);
// Navigate to third page on same site.
const GURL url3("http://google.com/foo");
NavigationSimulator::NavigateAndCommitFromBrowser(contents(), url3);
NavigationEntry* entry3 = controller().GetLastCommittedEntry();
SiteInstance* instance3 = contents()->GetSiteInstance();
EXPECT_FALSE(contents()->CrossProcessNavigationPending());
if (will_change_site_instance) {
// If same-site ProactivelySwapBrowsingInstance or main-frame RenderDocument
// is enabled, the RFH should change.
EXPECT_NE(google_rfh, main_test_rfh());
google_rfh = main_test_rfh();
} else {
EXPECT_EQ(google_rfh, main_test_rfh());
}
if (will_change_site_instance) {
// When ProactivelySwapBrowsingInstance is enabled on same-site navigations,
// the SiteInstance will change.
EXPECT_NE(instance2, instance3);
} else {
EXPECT_EQ(instance2, instance3);
}
EXPECT_FALSE(contents()->GetSpeculativePrimaryMainFrame());
EXPECT_EQ(url3, entry3->GetURL());
EXPECT_EQ(instance3,
NavigationEntryImpl::FromNavigationEntry(entry3)->site_instance());
// Go back within the site.
auto back_navigation1 =
NavigationSimulator::CreateHistoryNavigation(-1, contents());
back_navigation1->ReadyToCommit();
if (will_change_site_instance) {
EXPECT_TRUE(contents()->CrossProcessNavigationPending());
} else {
EXPECT_FALSE(contents()->CrossProcessNavigationPending());
}
EXPECT_EQ(entry2, controller().GetPendingEntry());
// Before that commits, go back again.
auto back_navigation2 =
NavigationSimulatorImpl::CreateHistoryNavigation(-1, contents());
back_navigation2->set_drop_unload_ack(true);
back_navigation2->ReadyToCommit();
EXPECT_TRUE(contents()->CrossProcessNavigationPending());
EXPECT_TRUE(contents()->GetSpeculativePrimaryMainFrame());
EXPECT_EQ(entry1, controller().GetPendingEntry());
webui_rfh = contents()->GetSpeculativePrimaryMainFrame();
// DidNavigate from the second back.
// Note that the process in instance1 is gone at this point, but we will
// still use instance1 and entry1 because IsSuitableForUrlInfo will return
// true when there is no process and the site URL matches.
back_navigation2->Commit();
// That should have landed us on the first entry.
EXPECT_EQ(entry1, controller().GetLastCommittedEntry());
// When the second back commits, it should be ignored.
google_rfh->SendNavigateWithTransition(0, false, url2,
ui::PAGE_TRANSITION_TYPED);
EXPECT_EQ(entry1, controller().GetLastCommittedEntry());
// The newly created process for url1 should be locked to chrome://gpu.
RenderProcessHost* new_process = contents()->GetMainFrame()->GetProcess();
auto* policy = content::ChildProcessSecurityPolicy::GetInstance();
EXPECT_TRUE(policy->CanAccessDataForOrigin(new_process->GetID(),
url::Origin::Create(url1)));
EXPECT_FALSE(policy->CanAccessDataForOrigin(new_process->GetID(),
url::Origin::Create(url2)));
}
// Test that during a slow cross-site navigation, a sub-frame navigation in the
// original renderer will not cancel the slow navigation (bug 42029).
TEST_F(WebContentsImplTest, CrossSiteNavigationNotPreemptedByFrame) {
TestRenderFrameHost* orig_rfh = main_test_rfh();
// Navigate to URL. First URL should use the original RenderFrameHost.
const GURL url("http://www.google.com");
NavigationSimulator::NavigateAndCommitFromBrowser(contents(), url);
EXPECT_FALSE(contents()->CrossProcessNavigationPending());
EXPECT_EQ(orig_rfh, main_test_rfh());
// Start navigating to new site.
const GURL url2("http://www.yahoo.com");
controller().LoadURL(
url2, Referrer(), ui::PAGE_TRANSITION_TYPED, std::string());
// Simulate a sub-frame navigation arriving and ensure the RVH is still
// waiting for a before unload response.
TestRenderFrameHost* child_rfh = orig_rfh->AppendChild("subframe");
child_rfh->SendNavigateWithTransition(0, false,
GURL("http://google.com/frame"),
ui::PAGE_TRANSITION_AUTO_SUBFRAME);
EXPECT_TRUE(orig_rfh->is_waiting_for_beforeunload_completion());
// Now simulate the onbeforeunload approval and verify the navigation is
// not canceled.
orig_rfh->PrepareForCommit();
EXPECT_FALSE(orig_rfh->is_waiting_for_beforeunload_completion());
EXPECT_TRUE(contents()->CrossProcessNavigationPending());
}
// Test that a cross-site navigation is not preempted if the previous
// renderer sends a FrameNavigate message just before being told to stop.
// We should only preempt the cross-site navigation if the previous renderer
// has started a new navigation. See http://crbug.com/79176.
TEST_F(WebContentsImplTest, CrossSiteNotPreemptedDuringBeforeUnload) {
const GURL kUrl("http://foo");
NavigationSimulator::NavigateAndCommitFromBrowser(contents(), kUrl);
// First, make a non-user initiated same-site navigation.
const GURL kSameSiteUrl("http://foo/1");
TestRenderFrameHost* orig_rfh = main_test_rfh();
// When ProactivelySwapBrowsingInstance or RenderDocument is enabled on
// same-site main frame navigations, the same-site navigation below will
// create a speculative RFH that will be overwritten when the cross-site
// navigation starts, finishing the same-site navigation, so the scenario in
// this test cannot be tested. We should disable same-site proactive
// BrowsingInstance for |orig_rfh| before continuing.
// Note: this will not disable RenderDocument.
// TODO(crbug.com/936696): Skip this test when main-frame RenderDocument is
// enabled.
DisableProactiveBrowsingInstanceSwapFor(orig_rfh);
auto same_site_navigation = NavigationSimulator::CreateRendererInitiated(
kSameSiteUrl, main_test_rfh());
same_site_navigation->SetHasUserGesture(false);
same_site_navigation->ReadyToCommit();
EXPECT_FALSE(contents()->CrossProcessNavigationPending());
// Navigate to a new site, with the beforeunload request in flight.
const GURL kCrossSiteUrl("http://www.yahoo.com");
auto cross_site_navigation = NavigationSimulatorImpl::CreateBrowserInitiated(
kCrossSiteUrl, contents());
cross_site_navigation->set_block_invoking_before_unload_completed_callback(
true);
cross_site_navigation->Start();
TestRenderFrameHost* pending_rfh =
contents()->GetSpeculativePrimaryMainFrame();
EXPECT_TRUE(contents()->CrossProcessNavigationPending());
EXPECT_TRUE(orig_rfh->is_waiting_for_beforeunload_completion());
EXPECT_NE(orig_rfh, pending_rfh);
// Suppose the first navigation tries to commit now, with a
// blink::mojom::LocalFrame::StopLoading() in flight. This should not cancel
// the pending navigation, but it should act as if the beforeunload completion
// callback had been invoked.
same_site_navigation->Commit();
EXPECT_TRUE(contents()->CrossProcessNavigationPending());
EXPECT_EQ(orig_rfh, main_test_rfh());
EXPECT_FALSE(orig_rfh->is_waiting_for_beforeunload_completion());
// It should commit.
ASSERT_EQ(2, controller().GetEntryCount());
EXPECT_EQ(kSameSiteUrl, controller().GetLastCommittedEntry()->GetURL());
// The pending navigation should be able to commit successfully.
cross_site_navigation->Commit();
EXPECT_FALSE(contents()->CrossProcessNavigationPending());
EXPECT_EQ(pending_rfh, main_test_rfh());
EXPECT_EQ(3, controller().GetEntryCount());
}
// Test that NavigationEntries have the correct page state after going
// forward and back. Prevents regression for bug 1116137.
TEST_F(WebContentsImplTest, NavigationEntryContentState) {
// Navigate to URL. There should be no committed entry yet.
const GURL url("http://www.google.com");
auto navigation =
NavigationSimulator::CreateBrowserInitiated(url, contents());
navigation->ReadyToCommit();
NavigationEntry* entry = controller().GetLastCommittedEntry();
EXPECT_EQ(nullptr, entry);
// Committed entry should have page state.
navigation->Commit();
entry = controller().GetLastCommittedEntry();
EXPECT_TRUE(entry->GetPageState().IsValid());
// Navigate to same site.
const GURL url2("http://images.google.com");
auto navigation2 =
NavigationSimulator::CreateBrowserInitiated(url2, contents());
navigation2->ReadyToCommit();
entry = controller().GetLastCommittedEntry();
EXPECT_TRUE(entry->GetPageState().IsValid());
// Committed entry should have page state.
navigation2->Commit();
entry = controller().GetLastCommittedEntry();
EXPECT_TRUE(entry->GetPageState().IsValid());
// Now go back. Committed entry should still have page state.
NavigationSimulator::GoBack(contents());
entry = controller().GetLastCommittedEntry();
EXPECT_TRUE(entry->GetPageState().IsValid());
}
// Test that NavigationEntries have the correct page state and SiteInstance
// state after opening a new window to about:blank. Prevents regression for
// bugs b/1116137 and http://crbug.com/111975.
TEST_F(WebContentsImplTest, NavigationEntryContentStateNewWindow) {
TestRenderFrameHost* orig_rfh = main_test_rfh();
// Navigate to about:blank.
const GURL url(url::kAboutBlankURL);
NavigationSimulator::NavigateAndCommitFromBrowser(contents(), url);
// Should have a page state here.
NavigationEntry* entry = controller().GetLastCommittedEntry();
EXPECT_TRUE(entry->GetPageState().IsValid());
// The SiteInstance should be available for other navigations to use.
NavigationEntryImpl* entry_impl =
NavigationEntryImpl::FromNavigationEntry(entry);
EXPECT_FALSE(entry_impl->site_instance()->HasSite());
auto site_instance_id = entry_impl->site_instance()->GetId();
// Navigating to a normal page should not cause a process swap.
const GURL new_url("http://www.google.com");
NavigationSimulator::NavigateAndCommitFromBrowser(contents(), new_url);
EXPECT_EQ(orig_rfh, main_test_rfh());
NavigationEntryImpl* entry_impl2 = NavigationEntryImpl::FromNavigationEntry(
controller().GetLastCommittedEntry());
EXPECT_EQ(site_instance_id, entry_impl2->site_instance()->GetId());
EXPECT_TRUE(entry_impl2->site_instance()->HasSite());
}
namespace {
void ExpectTrue(bool value) {
DCHECK(value);
}
void ExpectFalse(bool value) {
DCHECK(!value);
}
} // namespace
// Tests that fullscreen is exited throughout the object hierarchy when
// navigating to a new page.
TEST_F(WebContentsImplTest, NavigationExitsFullscreen) {
FakeFullscreenDelegate fake_delegate;
contents()->SetDelegate(&fake_delegate);
TestRenderFrameHost* orig_rfh = main_test_rfh();
// Navigate to a site.
const GURL url("http://www.google.com");
NavigationSimulator::NavigateAndCommitFromBrowser(contents(), url);
EXPECT_EQ(orig_rfh, main_test_rfh());
// Toggle fullscreen mode on (as if initiated via IPC from renderer).
EXPECT_FALSE(contents()->IsFullscreen());
EXPECT_FALSE(fake_delegate.IsFullscreenForTabOrPending(contents()));
main_test_rfh()->frame_tree_node()->UpdateUserActivationState(
blink::mojom::UserActivationUpdateType::kNotifyActivation,
blink::mojom::UserActivationNotificationType::kTest);
orig_rfh->EnterFullscreen(blink::mojom::FullscreenOptions::New(),
base::BindOnce(&ExpectTrue));
EXPECT_TRUE(contents()->IsFullscreen());
EXPECT_TRUE(fake_delegate.IsFullscreenForTabOrPending(contents()));
// Navigate to a new site.
const GURL url2("http://www.yahoo.com");
NavigationSimulator::NavigateAndCommitFromBrowser(contents(), url2);
// Confirm fullscreen has exited.
EXPECT_FALSE(contents()->IsFullscreen());
EXPECT_FALSE(fake_delegate.IsFullscreenForTabOrPending(contents()));
contents()->SetDelegate(nullptr);
}
// Tests that fullscreen is exited throughout the object hierarchy when
// instructing NavigationController to GoBack() or GoForward().
TEST_F(WebContentsImplTest, HistoryNavigationExitsFullscreen) {
FakeFullscreenDelegate fake_delegate;
contents()->SetDelegate(&fake_delegate);
TestRenderFrameHost* orig_rfh = main_test_rfh();
// Navigate to a site.
const GURL url("http://www.google.com");
NavigationSimulator::NavigateAndCommitFromBrowser(contents(), url);
EXPECT_EQ(orig_rfh, main_test_rfh());
// Now, navigate to another page on the same site.
const GURL url2("http://www.google.com/search?q=kittens");
NavigationSimulator::NavigateAndCommitFromBrowser(contents(), url2);
if (CanSameSiteMainFrameNavigationsChangeRenderFrameHosts()) {
// If ProactivelySwapBrowsingInstance is enabled on same-site navigations,
// the same-site navigation above will use a new RFH.
EXPECT_NE(orig_rfh, main_test_rfh());
} else {
EXPECT_EQ(orig_rfh, main_test_rfh());
}
// Sanity-check: Confirm we're not starting out in fullscreen mode.
EXPECT_FALSE(contents()->IsFullscreen());
EXPECT_FALSE(fake_delegate.IsFullscreenForTabOrPending(contents()));
for (int i = 0; i < 2; ++i) {
// Toggle fullscreen mode on (as if initiated via IPC from renderer).
main_test_rfh()->frame_tree_node()->UpdateUserActivationState(
blink::mojom::UserActivationUpdateType::kNotifyActivation,
blink::mojom::UserActivationNotificationType::kTest);
main_test_rfh()->EnterFullscreen(blink::mojom::FullscreenOptions::New(),
base::BindOnce(&ExpectTrue));
EXPECT_TRUE(contents()->IsFullscreen());
EXPECT_TRUE(fake_delegate.IsFullscreenForTabOrPending(contents()));
// Navigate backward (or forward).
if (i == 0)
NavigationSimulator::GoBack(contents());
else
NavigationSimulator::GoForward(contents());
// Confirm fullscreen has exited.
EXPECT_FALSE(contents()->IsFullscreen());
EXPECT_FALSE(fake_delegate.IsFullscreenForTabOrPending(contents()));
}
contents()->SetDelegate(nullptr);
}
// Tests that fullscreen is exited throughout the object hierarchy on a renderer
// crash.
TEST_F(WebContentsImplTest, CrashExitsFullscreen) {
FakeFullscreenDelegate fake_delegate;
contents()->SetDelegate(&fake_delegate);
// Navigate to a site.
const GURL url("http://www.google.com");
NavigationSimulator::NavigateAndCommitFromBrowser(contents(), url);
// Toggle fullscreen mode on (as if initiated via IPC from renderer).
EXPECT_FALSE(contents()->IsFullscreen());
EXPECT_FALSE(fake_delegate.IsFullscreenForTabOrPending(contents()));
main_test_rfh()->frame_tree_node()->UpdateUserActivationState(
blink::mojom::UserActivationUpdateType::kNotifyActivation,
blink::mojom::UserActivationNotificationType::kTest);
main_test_rfh()->EnterFullscreen(blink::mojom::FullscreenOptions::New(),
base::BindOnce(&ExpectTrue));
EXPECT_TRUE(contents()->IsFullscreen());
EXPECT_TRUE(fake_delegate.IsFullscreenForTabOrPending(contents()));
// Crash the renderer.
main_test_rfh()->GetProcess()->SimulateCrash();
// Confirm fullscreen has exited.
EXPECT_FALSE(contents()->IsFullscreen());
EXPECT_FALSE(fake_delegate.IsFullscreenForTabOrPending(contents()));
contents()->SetDelegate(nullptr);
}
TEST_F(WebContentsImplTest,
FailEnterFullscreenWhenNoUserActivationNoOrientationChange) {
FakeFullscreenDelegate fake_delegate;
contents()->SetDelegate(&fake_delegate);
// Navigate to a site.
const GURL url("http://www.google.com");
NavigationSimulator::NavigateAndCommitFromBrowser(contents(), url);
// Toggle fullscreen mode on (as if initiated via IPC from renderer).
EXPECT_FALSE(contents()->IsFullscreen());
EXPECT_FALSE(fake_delegate.IsFullscreenForTabOrPending(contents()));
// When there is no user activation and no orientation change, entering
// fullscreen will fail.
main_test_rfh()->EnterFullscreen(blink::mojom::FullscreenOptions::New(),
base::BindOnce(&ExpectFalse));
EXPECT_FALSE(contents()->HasSeenRecentScreenOrientationChange());
EXPECT_FALSE(
main_test_rfh()->frame_tree_node()->HasTransientUserActivation());
EXPECT_FALSE(contents()->IsFullscreen());
EXPECT_FALSE(fake_delegate.IsFullscreenForTabOrPending(contents()));
contents()->SetDelegate(nullptr);
}
// Regression test for http://crbug.com/168611 - the URLs passed by the
// DidFinishLoad and DidFailLoadWithError IPCs should get filtered.
TEST_F(WebContentsImplTest, FilterURLs) {
TestWebContentsObserver observer(contents());
// A navigation to about:whatever should always look like a navigation to
// about:blank
GURL url_normalized(url::kAboutBlankURL);
GURL url_from_ipc("about:whatever");
GURL url_blocked(kBlockedURL);
// We navigate the test WebContents to about:blank, since NavigateAndCommit
// will use the given URL to create the NavigationEntry as well, and that
// entry should contain the filtered URL.
contents()->NavigateAndCommit(url_normalized);
// Check that an IPC with about:whatever is correctly normalized.
contents()->TestDidFinishLoad(url_from_ipc);
EXPECT_EQ(url_blocked, observer.last_url());
// Create and navigate another WebContents.
std::unique_ptr<TestWebContents> other_contents(
static_cast<TestWebContents*>(CreateTestWebContents().release()));
TestWebContentsObserver other_observer(other_contents.get());
other_contents->NavigateAndCommit(url_normalized);
// Check that an IPC with about:whatever is correctly normalized.
other_contents->GetMainFrame()->DidFailLoadWithError(url_from_ipc, 1);
EXPECT_EQ(url_blocked, other_observer.last_url());
}
// Test that if a pending contents is deleted before it is shown, we don't
// crash.
TEST_F(WebContentsImplTest, PendingContentsDestroyed) {
auto other_contents = base::WrapUnique(
static_cast<TestWebContents*>(CreateTestWebContents().release()));
content::TestWebContents* test_web_contents = other_contents.get();
contents()->AddPendingContents(std::move(other_contents), GURL());
RenderWidgetHost* widget =
test_web_contents->GetMainFrame()->GetRenderWidgetHost();
int process_id = widget->GetProcess()->GetID();
int widget_id = widget->GetRoutingID();
// TODO(erikchen): Fix ownership semantics of WebContents. Nothing should be
// able to delete it beside from the owner. https://crbug.com/832879.
delete test_web_contents;
EXPECT_FALSE(contents()->GetCreatedWindow(process_id, widget_id).has_value());
}
TEST_F(WebContentsImplTest, PendingContentsShown) {
GURL url("http://example.com");
auto other_contents = base::WrapUnique(
static_cast<TestWebContents*>(CreateTestWebContents().release()));
content::TestWebContents* test_web_contents = other_contents.get();
contents()->AddPendingContents(std::move(other_contents), url);
RenderWidgetHost* widget =
test_web_contents->GetMainFrame()->GetRenderWidgetHost();
int process_id = widget->GetProcess()->GetID();
int widget_id = widget->GetRoutingID();
// The first call to GetCreatedWindow pops it off the pending list.
absl::optional<CreatedWindow> created_window =
contents()->GetCreatedWindow(process_id, widget_id);
EXPECT_TRUE(created_window.has_value());
EXPECT_EQ(test_web_contents, created_window->contents.get());
// Validate target_url.
EXPECT_EQ(url, created_window->target_url);
// A second call should return nullopt, verifying that it's been forgotten.
EXPECT_FALSE(contents()->GetCreatedWindow(process_id, widget_id).has_value());
}
TEST_F(WebContentsImplTest, CaptureHoldsWakeLock) {
EXPECT_FALSE(contents()->IsBeingCaptured());
EXPECT_FALSE(contents()->capture_wake_lock_);
auto expect_wake_lock = [&](bool expect_has_wake_lock) {
base::RunLoop run_loop;
contents()->capture_wake_lock_->HasWakeLockForTests(
base::BindLambdaForTesting([&](bool has_wake_lock) {
EXPECT_EQ(expect_has_wake_lock, has_wake_lock);
run_loop.QuitWhenIdle();
}));
run_loop.Run();
};
// Add capturer which doesn't care to stay awake.
auto handle1 =
contents()->IncrementCapturerCount(gfx::Size(), /*stay_hidden=*/false,
/*stay_awake=*/false);
EXPECT_TRUE(contents()->IsBeingCaptured());
ASSERT_FALSE(contents()->capture_wake_lock_);
// Add capturer and ensure wake lock is held.
auto handle2 =
contents()->IncrementCapturerCount(gfx::Size(), /*stay_hidden=*/false,
/*stay_awake=*/true);
EXPECT_TRUE(contents()->IsBeingCaptured());
ASSERT_TRUE(contents()->capture_wake_lock_);
expect_wake_lock(true);
// Add another capturer and ensure the wake lock is still held.
auto handle3 =
contents()->IncrementCapturerCount(gfx::Size(), /*stay_hidden=*/true,
/*stay_awake=*/true);
EXPECT_TRUE(contents()->IsBeingCaptured());
expect_wake_lock(true);
// Remove one capturer, but one remains so wake lock should still be held.
handle3.RunAndReset();
EXPECT_TRUE(contents()->IsBeingCaptured());
expect_wake_lock(true);
// Remove the last stay_awake capturer and ensure the wake lock is released.
handle2.RunAndReset();
EXPECT_TRUE(contents()->IsBeingCaptured());
expect_wake_lock(false);
handle1.RunAndReset();
EXPECT_FALSE(contents()->IsBeingCaptured());
expect_wake_lock(false);
}
TEST_F(WebContentsImplTest, CapturerOverridesPreferredSize) {
const gfx::Size original_preferred_size(1024, 768);
contents()->UpdateWindowPreferredSize(original_preferred_size);
// With no capturers, expect the preferred size to be the one propagated into
// WebContentsImpl via the RenderViewHostDelegate interface.
EXPECT_FALSE(contents()->IsBeingCaptured());
EXPECT_EQ(original_preferred_size, contents()->GetPreferredSize());
// Increment capturer count, but without specifying a capture size. Expect
// a "not set" preferred size.
auto handle1 =
contents()->IncrementCapturerCount(gfx::Size(), /*stay_hidden=*/false,
/*stay_awake=*/true);
EXPECT_TRUE(contents()->IsBeingCaptured());
EXPECT_EQ(gfx::Size(), contents()->GetPreferredSize());
// Increment capturer count again, but with an overriding capture size.
// Expect preferred size to now be overridden to the capture size.
const gfx::Size capture_size(1280, 720);
auto handle2 =
contents()->IncrementCapturerCount(capture_size, /*stay_hidden=*/false,
/*stay_awake=*/true);
EXPECT_TRUE(contents()->IsBeingCaptured());
EXPECT_EQ(capture_size, contents()->GetPreferredSize());
// Increment capturer count a third time, but the expect that the preferred
// size is still the first capture size.
const gfx::Size another_capture_size(720, 480);
auto handle3 = contents()->IncrementCapturerCount(another_capture_size,
/*stay_hidden=*/false,
/*stay_awake=*/true);
EXPECT_TRUE(contents()->IsBeingCaptured());
EXPECT_EQ(capture_size, contents()->GetPreferredSize());
// Decrement capturer count twice, but expect the preferred size to still be
// overridden.
handle1.RunAndReset();
handle2.RunAndReset();
EXPECT_TRUE(contents()->IsBeingCaptured());
EXPECT_EQ(capture_size, contents()->GetPreferredSize());
// Decrement capturer count, and since the count has dropped to zero, the
// original preferred size should be restored.
handle3.RunAndReset();
EXPECT_FALSE(contents()->IsBeingCaptured());
EXPECT_EQ(original_preferred_size, contents()->GetPreferredSize());
}
TEST_F(WebContentsImplTest, UpdateWebContentsVisibility) {
TestRenderWidgetHostView* view = static_cast<TestRenderWidgetHostView*>(
main_test_rfh()->GetRenderViewHost()->GetWidget()->GetView());
TestWebContentsObserver observer(contents());
EXPECT_FALSE(view->is_showing());
EXPECT_FALSE(view->is_occluded());
// WebContents must be made visible once before it can be hidden.
contents()->UpdateWebContentsVisibility(Visibility::HIDDEN);
EXPECT_FALSE(view->is_showing());
EXPECT_FALSE(view->is_occluded());
EXPECT_EQ(Visibility::VISIBLE, contents()->GetVisibility());
contents()->UpdateWebContentsVisibility(Visibility::VISIBLE);
EXPECT_TRUE(view->is_showing());
EXPECT_FALSE(view->is_occluded());
EXPECT_EQ(Visibility::VISIBLE, contents()->GetVisibility());
// Hiding/occluding/showing the WebContents should hide and show |view|.
contents()->UpdateWebContentsVisibility(Visibility::HIDDEN);
EXPECT_FALSE(view->is_showing());
EXPECT_FALSE(view->is_occluded());
EXPECT_EQ(Visibility::HIDDEN, contents()->GetVisibility());
contents()->UpdateWebContentsVisibility(Visibility::VISIBLE);
EXPECT_TRUE(view->is_showing());
EXPECT_FALSE(view->is_occluded());
EXPECT_EQ(Visibility::VISIBLE, contents()->GetVisibility());
contents()->UpdateWebContentsVisibility(Visibility::OCCLUDED);
EXPECT_TRUE(view->is_showing());
EXPECT_TRUE(view->is_occluded());
EXPECT_EQ(Visibility::OCCLUDED, contents()->GetVisibility());
contents()->UpdateWebContentsVisibility(Visibility::VISIBLE);
EXPECT_TRUE(view->is_showing());
EXPECT_FALSE(view->is_occluded());
EXPECT_EQ(Visibility::VISIBLE, contents()->GetVisibility());
contents()->UpdateWebContentsVisibility(Visibility::OCCLUDED);
EXPECT_TRUE(view->is_showing());
EXPECT_TRUE(view->is_occluded());
EXPECT_EQ(Visibility::OCCLUDED, contents()->GetVisibility());
contents()->UpdateWebContentsVisibility(Visibility::HIDDEN);
EXPECT_FALSE(view->is_showing());
EXPECT_EQ(Visibility::HIDDEN, contents()->GetVisibility());
}
namespace {
void HideOrOccludeWithCapturerTest(WebContentsImpl* contents,
Visibility hidden_or_occluded) {
TestRenderWidgetHostView* view = static_cast<TestRenderWidgetHostView*>(
contents->GetRenderWidgetHostView());
EXPECT_FALSE(view->is_showing());
// WebContents must be made visible once before it can be hidden.
contents->UpdateWebContentsVisibility(Visibility::VISIBLE);
EXPECT_TRUE(view->is_showing());
EXPECT_FALSE(view->is_occluded());
EXPECT_EQ(Visibility::VISIBLE, contents->GetVisibility());
// Add a capturer when the contents is visible and then hide the contents.
// |view| should remain visible.
auto handle1 =
contents->IncrementCapturerCount(gfx::Size(), /*stay_hidden=*/false,
/*stay_awake=*/true);
contents->UpdateWebContentsVisibility(hidden_or_occluded);
EXPECT_TRUE(view->is_showing());
EXPECT_FALSE(view->is_occluded());
EXPECT_EQ(hidden_or_occluded, contents->GetVisibility());
// Remove the capturer when the contents is hidden/occluded. |view| should be
// hidden/occluded.
handle1.RunAndReset();
if (hidden_or_occluded == Visibility::HIDDEN) {
EXPECT_FALSE(view->is_showing());
} else {
EXPECT_TRUE(view->is_showing());
EXPECT_TRUE(view->is_occluded());
}
// Add a capturer when the contents is hidden. |view| should be unoccluded.
auto handle2 =
contents->IncrementCapturerCount(gfx::Size(), /*stay_hidden=*/false,
/*stay_awake=*/true);
EXPECT_FALSE(view->is_occluded());
// Show the contents. The view should be visible.
contents->UpdateWebContentsVisibility(Visibility::VISIBLE);
EXPECT_TRUE(view->is_showing());
EXPECT_FALSE(view->is_occluded());
EXPECT_EQ(Visibility::VISIBLE, contents->GetVisibility());
// Remove the capturer when the contents is visible. The view should remain
// visible.
handle2.RunAndReset();
EXPECT_TRUE(view->is_showing());
EXPECT_FALSE(view->is_occluded());
}
} // namespace
TEST_F(WebContentsImplTest, HideWithCapturer) {
HideOrOccludeWithCapturerTest(contents(), Visibility::HIDDEN);
}
TEST_F(WebContentsImplTest, OccludeWithCapturer) {
HideOrOccludeWithCapturerTest(contents(), Visibility::OCCLUDED);
}
TEST_F(WebContentsImplTest, HiddenCapture) {
TestRenderWidgetHostView* rwhv = static_cast<TestRenderWidgetHostView*>(
contents()->GetRenderWidgetHostView());
contents()->UpdateWebContentsVisibility(Visibility::VISIBLE);
contents()->UpdateWebContentsVisibility(Visibility::HIDDEN);
EXPECT_EQ(Visibility::HIDDEN, contents()->GetVisibility());
auto handle1 =
contents()->IncrementCapturerCount(gfx::Size(), /*stay_hidden=*/true,
/*stay_awake=*/true);
EXPECT_TRUE(rwhv->is_showing());
auto handle2 =
contents()->IncrementCapturerCount(gfx::Size(), /*stay_hidden=*/false,
/*stay_awake=*/true);
EXPECT_TRUE(rwhv->is_showing());
handle1.RunAndReset();
EXPECT_TRUE(rwhv->is_showing());
handle2.RunAndReset();
EXPECT_FALSE(rwhv->is_showing());
}
// Tests that GetLastActiveTime starts with a real, non-zero time and updates
// on activity.
TEST_F(WebContentsImplTest, GetLastActiveTime) {
// The WebContents starts with a valid creation time.
EXPECT_FALSE(contents()->GetLastActiveTime().is_null());
// Reset the last active time to a known-bad value.
contents()->last_active_time_ = base::TimeTicks();
ASSERT_TRUE(contents()->GetLastActiveTime().is_null());
// Simulate activating the WebContents. The active time should update.
contents()->WasShown();
EXPECT_FALSE(contents()->GetLastActiveTime().is_null());
}
class ContentsZoomChangedDelegate : public WebContentsDelegate {
public:
ContentsZoomChangedDelegate() :
contents_zoom_changed_call_count_(0),
last_zoom_in_(false) {
}
int GetAndResetContentsZoomChangedCallCount() {
int count = contents_zoom_changed_call_count_;
contents_zoom_changed_call_count_ = 0;
return count;
}
bool last_zoom_in() const {
return last_zoom_in_;
}
// WebContentsDelegate:
void ContentsZoomChange(bool zoom_in) override {
contents_zoom_changed_call_count_++;
last_zoom_in_ = zoom_in;
}
private:
int contents_zoom_changed_call_count_;
bool last_zoom_in_;
DISALLOW_COPY_AND_ASSIGN(ContentsZoomChangedDelegate);
};
// Tests that some mouseehweel events get turned into browser zoom requests.
TEST_F(WebContentsImplTest, HandleWheelEvent) {
using blink::WebInputEvent;
std::unique_ptr<ContentsZoomChangedDelegate> delegate(
new ContentsZoomChangedDelegate());
contents()->SetDelegate(delegate.get());
int modifiers = 0;
// Verify that normal mouse wheel events do nothing to change the zoom level.
blink::WebMouseWheelEvent event =
blink::SyntheticWebMouseWheelEventBuilder::Build(
0, 0, 0, 1, modifiers, ui::ScrollGranularity::kScrollByPixel);
EXPECT_FALSE(contents()->HandleWheelEvent(event));
EXPECT_EQ(0, delegate->GetAndResetContentsZoomChangedCallCount());
// But whenever the ctrl modifier is applied zoom can be increased or
// decreased. Except on MacOS where we never want to adjust zoom
// with mousewheel.
modifiers = WebInputEvent::kControlKey;
event = blink::SyntheticWebMouseWheelEventBuilder::Build(
0, 0, 0, 1, modifiers, ui::ScrollGranularity::kScrollByPixel);
bool handled = contents()->HandleWheelEvent(event);
#if defined(USE_AURA)
EXPECT_TRUE(handled);
EXPECT_EQ(1, delegate->GetAndResetContentsZoomChangedCallCount());
EXPECT_TRUE(delegate->last_zoom_in());
#else
EXPECT_FALSE(handled);
EXPECT_EQ(0, delegate->GetAndResetContentsZoomChangedCallCount());
#endif
modifiers = WebInputEvent::kControlKey | WebInputEvent::kShiftKey |
WebInputEvent::kAltKey;
event = blink::SyntheticWebMouseWheelEventBuilder::Build(
0, 0, 2, -5, modifiers, ui::ScrollGranularity::kScrollByPixel);
handled = contents()->HandleWheelEvent(event);
#if defined(USE_AURA)
EXPECT_TRUE(handled);
EXPECT_EQ(1, delegate->GetAndResetContentsZoomChangedCallCount());
EXPECT_FALSE(delegate->last_zoom_in());
#else
EXPECT_FALSE(handled);
EXPECT_EQ(0, delegate->GetAndResetContentsZoomChangedCallCount());
#endif
// Unless there is no vertical movement.
event = blink::SyntheticWebMouseWheelEventBuilder::Build(
0, 0, 2, 0, modifiers, ui::ScrollGranularity::kScrollByPixel);
EXPECT_FALSE(contents()->HandleWheelEvent(event));
EXPECT_EQ(0, delegate->GetAndResetContentsZoomChangedCallCount());
// Events containing precise scrolling deltas also shouldn't result in the
// zoom being adjusted, to avoid accidental adjustments caused by
// two-finger-scrolling on a touchpad.
modifiers = WebInputEvent::kControlKey;
event = blink::SyntheticWebMouseWheelEventBuilder::Build(
0, 0, 0, 5, modifiers, ui::ScrollGranularity::kScrollByPrecisePixel);
EXPECT_FALSE(contents()->HandleWheelEvent(event));
EXPECT_EQ(0, delegate->GetAndResetContentsZoomChangedCallCount());
// Ensure pointers to the delegate aren't kept beyond its lifetime.
contents()->SetDelegate(nullptr);
}
// Tests that GetRelatedActiveContentsCount is shared between related
// SiteInstances and includes WebContents that have not navigated yet.
TEST_F(WebContentsImplTest, ActiveContentsCountBasic) {
scoped_refptr<SiteInstance> instance1(
SiteInstance::CreateForURL(browser_context(), GURL("http://a.com")));
scoped_refptr<SiteInstance> instance2(
instance1->GetRelatedSiteInstance(GURL("http://b.com")));
EXPECT_EQ(0u, instance1->GetRelatedActiveContentsCount());
EXPECT_EQ(0u, instance2->GetRelatedActiveContentsCount());
std::unique_ptr<TestWebContents> contents1(
TestWebContents::Create(browser_context(), instance1.get()));
EXPECT_EQ(1u, instance1->GetRelatedActiveContentsCount());
EXPECT_EQ(1u, instance2->GetRelatedActiveContentsCount());
std::unique_ptr<TestWebContents> contents2(
TestWebContents::Create(browser_context(), instance1.get()));
EXPECT_EQ(2u, instance1->GetRelatedActiveContentsCount());
EXPECT_EQ(2u, instance2->GetRelatedActiveContentsCount());
contents1.reset();
EXPECT_EQ(1u, instance1->GetRelatedActiveContentsCount());
EXPECT_EQ(1u, instance2->GetRelatedActiveContentsCount());
contents2.reset();
EXPECT_EQ(0u, instance1->GetRelatedActiveContentsCount());
EXPECT_EQ(0u, instance2->GetRelatedActiveContentsCount());
}
// Tests that GetRelatedActiveContentsCount is preserved correctly across
// same-site and cross-site navigations.
TEST_F(WebContentsImplTest, ActiveContentsCountNavigate) {
scoped_refptr<SiteInstance> instance(
SiteInstance::Create(browser_context()));
EXPECT_EQ(0u, instance->GetRelatedActiveContentsCount());
std::unique_ptr<TestWebContents> contents(
TestWebContents::Create(browser_context(), instance.get()));
EXPECT_EQ(1u, instance->GetRelatedActiveContentsCount());
// Navigate to a URL.
auto navigation1 = NavigationSimulator::CreateBrowserInitiated(
GURL("http://a.com/1"), contents.get());
navigation1->Start();
EXPECT_EQ(1u, instance->GetRelatedActiveContentsCount());
navigation1->Commit();
EXPECT_EQ(1u, instance->GetRelatedActiveContentsCount());
// Navigate to a URL in the same site.
auto navigation2 = NavigationSimulator::CreateBrowserInitiated(
GURL("http://a.com/2"), contents.get());
navigation2->Start();
EXPECT_EQ(1u, instance->GetRelatedActiveContentsCount());
navigation2->Commit();
if (CanSameSiteMainFrameNavigationsChangeSiteInstances()) {
// When ProactivelySwapBrowsingInstance turned on for same-site navigations,
// the BrowsingInstance will change on same-site navigations.
EXPECT_NE(instance, contents->GetSiteInstance());
// Check the previous instance's count.
EXPECT_EQ(0u, instance->GetRelatedActiveContentsCount());
// Update the current instance.
instance = contents->GetSiteInstance();
EXPECT_EQ(1u, instance->GetRelatedActiveContentsCount());
}
// Navigate to a URL in a different site in the same BrowsingInstance.
const GURL kUrl2("http://b.com");
auto navigation3 = NavigationSimulator::CreateRendererInitiated(
kUrl2, contents->GetMainFrame());
navigation3->ReadyToCommit();
EXPECT_EQ(1u, instance->GetRelatedActiveContentsCount());
if (AreAllSitesIsolatedForTesting() ||
CanCrossSiteNavigationsProactivelySwapBrowsingInstances()) {
EXPECT_TRUE(contents->CrossProcessNavigationPending());
} else {
EXPECT_FALSE(contents->CrossProcessNavigationPending());
}
navigation3->Commit();
if (CanCrossSiteNavigationsProactivelySwapBrowsingInstances()) {
// When ProactivelySwapBrowsingInstance turned on, the BrowsingInstance will
// change on cross-site navigations.
EXPECT_NE(instance, contents->GetSiteInstance());
EXPECT_EQ(0u, instance->GetRelatedActiveContentsCount());
// Update the current instance.
instance = contents->GetSiteInstance();
}
EXPECT_EQ(1u, instance->GetRelatedActiveContentsCount());
// Navigate to a URL in a different site and different BrowsingInstance, by
// using a TYPED page transition instead of LINK.
const GURL kUrl3("http://c.com");
auto navigation4 =
NavigationSimulator::CreateBrowserInitiated(kUrl3, contents.get());
navigation4->SetTransition(ui::PAGE_TRANSITION_TYPED);
navigation4->ReadyToCommit();
EXPECT_TRUE(contents->CrossProcessNavigationPending());
EXPECT_EQ(1u, instance->GetRelatedActiveContentsCount());
scoped_refptr<SiteInstance> new_instance =
contents->GetSpeculativePrimaryMainFrame()->GetSiteInstance();
navigation4->Commit();
EXPECT_EQ(0u, instance->GetRelatedActiveContentsCount());
EXPECT_EQ(1u, new_instance->GetRelatedActiveContentsCount());
EXPECT_FALSE(new_instance->IsRelatedSiteInstance(instance.get()));
contents.reset();
EXPECT_EQ(0u, new_instance->GetRelatedActiveContentsCount());
}
// Tests that GetRelatedActiveContentsCount tracks BrowsingInstance changes
// from WebUI.
TEST_F(WebContentsImplTest, ActiveContentsCountChangeBrowsingInstance) {
scoped_refptr<SiteInstance> instance(
SiteInstance::Create(browser_context()));
EXPECT_EQ(0u, instance->GetRelatedActiveContentsCount());
std::unique_ptr<TestWebContents> contents(
TestWebContents::Create(browser_context(), instance.get()));
EXPECT_EQ(1u, instance->GetRelatedActiveContentsCount());
// Navigate to a URL.
contents->NavigateAndCommit(GURL("http://a.com"));
EXPECT_EQ(1u, instance->GetRelatedActiveContentsCount());
// Navigate to a URL which sort of looks like a chrome:// url.
contents->NavigateAndCommit(GURL("http://gpu"));
if (CanCrossSiteNavigationsProactivelySwapBrowsingInstances()) {
// The navigation from "a.com" to "gpu" is using a new BrowsingInstance.
EXPECT_EQ(0u, instance->GetRelatedActiveContentsCount());
// The rest of the test expects |instance| to match the one in the main
// frame.
instance = contents->GetMainFrame()->GetSiteInstance();
}
EXPECT_EQ(1u, instance->GetRelatedActiveContentsCount());
// Navigate to a URL with WebUI. This will change BrowsingInstances.
const GURL kWebUIUrl = GURL(GetWebUIURL(kChromeUIGpuHost));
auto web_ui_navigation =
NavigationSimulator::CreateBrowserInitiated(kWebUIUrl, contents.get());
web_ui_navigation->Start();
EXPECT_TRUE(contents->CrossProcessNavigationPending());
scoped_refptr<SiteInstance> instance_webui(
contents->GetSpeculativePrimaryMainFrame()->GetSiteInstance());
EXPECT_FALSE(instance->IsRelatedSiteInstance(instance_webui.get()));
// At this point, contents still counts for the old BrowsingInstance.
EXPECT_EQ(1u, instance->GetRelatedActiveContentsCount());
EXPECT_EQ(0u, instance_webui->GetRelatedActiveContentsCount());
// Commit and contents counts for the new one.
web_ui_navigation->Commit();
EXPECT_EQ(0u, instance->GetRelatedActiveContentsCount());
EXPECT_EQ(1u, instance_webui->GetRelatedActiveContentsCount());
contents.reset();
EXPECT_EQ(0u, instance->GetRelatedActiveContentsCount());
EXPECT_EQ(0u, instance_webui->GetRelatedActiveContentsCount());
}
class LoadingWebContentsObserver : public WebContentsObserver {
public:
explicit LoadingWebContentsObserver(WebContents* contents)
: WebContentsObserver(contents),
is_loading_(false),
did_receive_response_(false) {}
~LoadingWebContentsObserver() override {}
// The assertions on these messages ensure that they are received in order.
void DidStartLoading() override {
ASSERT_FALSE(did_receive_response_);
ASSERT_FALSE(is_loading_);
is_loading_ = true;
}
void DidReceiveResponse() override {
ASSERT_TRUE(is_loading_);
did_receive_response_ = true;
}
void DidStopLoading() override {
ASSERT_TRUE(is_loading_);
is_loading_ = false;
did_receive_response_ = false;
}
bool is_loading() const { return is_loading_; }
bool did_receive_response() const { return did_receive_response_; }
private:
bool is_loading_;
bool did_receive_response_;
DISALLOW_COPY_AND_ASSIGN(LoadingWebContentsObserver);
};
// Subclass of WebContentsImplTest for cases that need out-of-process iframes.
class WebContentsImplTestWithSiteIsolation : public WebContentsImplTest {
public:
WebContentsImplTestWithSiteIsolation() {
IsolateAllSitesForTesting(base::CommandLine::ForCurrentProcess());
}
};
// Ensure that DidStartLoading/DidStopLoading events balance out properly with
// interleaving cross-process navigations in multiple subframes.
// See https://crbug.com/448601 for details of the underlying issue. The
// sequence of events that reproduce it are as follows:
// * Navigate top-level frame with one subframe.
// * Subframe navigates more than once before the top-level frame has had a
// chance to complete the load.
// The subframe navigations cause the loading_frames_in_progress_ to drop down
// to 0, while the loading_progresses_ map is not reset.
TEST_F(WebContentsImplTestWithSiteIsolation, StartStopEventsBalance) {
// The bug manifests itself in regular mode as well, but browser-initiated
// navigation of subframes is only possible in --site-per-process mode within
// unit tests.
const GURL initial_url("about:blank");
const GURL main_url("http://www.chromium.org");
const GURL foo_url("http://foo.chromium.org");
const GURL bar_url("http://bar.chromium.org");
TestRenderFrameHost* orig_rfh = main_test_rfh();
// Use a WebContentsObserver to observe the behavior of the tab's spinner.
LoadingWebContentsObserver observer(contents());
// Navigate the main RenderFrame and commit. The frame should still be
// loading.
auto main_frame_navigation =
NavigationSimulatorImpl::CreateBrowserInitiated(main_url, contents());
main_frame_navigation->SetKeepLoading(true);
main_frame_navigation->Commit();
EXPECT_FALSE(contents()->CrossProcessNavigationPending());
EXPECT_EQ(orig_rfh, main_test_rfh());
EXPECT_TRUE(contents()->IsLoading());
// The Observer callback implementations contain assertions to ensure that the
// events arrive in the correct order.
EXPECT_TRUE(observer.is_loading());
EXPECT_TRUE(observer.did_receive_response());
// Create a child frame to navigate multiple times.
TestRenderFrameHost* subframe = orig_rfh->AppendChild("subframe");
// Navigate the child frame to about:blank, which will send DidStopLoading
// message.
NavigationSimulator::NavigateAndCommitFromDocument(initial_url, subframe);
// Navigate the frame to another URL, which will send again
// DidStartLoading and DidStopLoading messages.
NavigationSimulator::NavigateAndCommitFromDocument(foo_url, subframe);
// Since the main frame hasn't sent any DidStopLoading messages, it is
// expected that the WebContents is still in loading state.
EXPECT_TRUE(contents()->IsLoading());
EXPECT_TRUE(observer.is_loading());
EXPECT_TRUE(observer.did_receive_response());
// After navigation, the RenderFrameHost may change.
subframe = static_cast<TestRenderFrameHost*>(
contents()->GetFrameTree()->root()->child_at(0)->current_frame_host());
// Navigate the frame again, this time using LoadURLWithParams. This causes
// RenderFrameHost to call into WebContents::DidStartLoading, which starts
// the spinner.
{
auto navigation =
NavigationSimulatorImpl::CreateBrowserInitiated(bar_url, contents());
NavigationController::LoadURLParams load_params(bar_url);
load_params.referrer = Referrer(GURL("http://referrer"),
network::mojom::ReferrerPolicy::kDefault);
load_params.transition_type = ui::PAGE_TRANSITION_MANUAL_SUBFRAME;
load_params.extra_headers = "content-type: text/plain";
load_params.load_type = NavigationController::LOAD_TYPE_DEFAULT;
load_params.is_renderer_initiated = false;
load_params.override_user_agent = NavigationController::UA_OVERRIDE_TRUE;
load_params.frame_tree_node_id =
subframe->frame_tree_node()->frame_tree_node_id();
navigation->SetLoadURLParams(&load_params);
navigation->Commit();
subframe = static_cast<TestRenderFrameHost*>(
navigation->GetFinalRenderFrameHost());
}
// At this point the status should still be loading, since the main frame
// hasn't sent the DidstopLoading message yet.
EXPECT_TRUE(contents()->IsLoading());
EXPECT_TRUE(observer.is_loading());
EXPECT_TRUE(observer.did_receive_response());
// Send the DidStopLoading for the main frame and ensure it isn't loading
// anymore.
main_frame_navigation->StopLoading();
EXPECT_FALSE(contents()->IsLoading());
EXPECT_FALSE(observer.is_loading());
EXPECT_FALSE(observer.did_receive_response());
}
// Tests that WebContentsImpl::IsLoadingToDifferentDocument only reports main
// frame loads. Browser-initiated navigation of subframes is only possible in
// --site-per-process mode within unit tests.
TEST_F(WebContentsImplTestWithSiteIsolation, IsLoadingToDifferentDocument) {
const GURL main_url("http://www.chromium.org");
TestRenderFrameHost* orig_rfh = main_test_rfh();
// Navigate the main RenderFrame and commit. The frame should still be
// loading.
auto navigation =
NavigationSimulatorImpl::CreateBrowserInitiated(main_url, contents());
navigation->SetKeepLoading(true);
navigation->Commit();
EXPECT_FALSE(contents()->CrossProcessNavigationPending());
EXPECT_EQ(orig_rfh, main_test_rfh());
EXPECT_TRUE(contents()->IsLoading());
EXPECT_TRUE(contents()->IsLoadingToDifferentDocument());
// Send the DidStopLoading for the main frame and ensure it isn't loading
// anymore.
navigation->StopLoading();
EXPECT_FALSE(contents()->IsLoading());
EXPECT_FALSE(contents()->IsLoadingToDifferentDocument());
// Create a child frame to navigate.
TestRenderFrameHost* subframe = orig_rfh->AppendChild("subframe");
// Navigate the child frame to about:blank, make sure the web contents is
// marked as "loading" but not "loading to different document".
subframe->SendNavigateWithTransition(0, false, GURL("about:blank"),
ui::PAGE_TRANSITION_AUTO_SUBFRAME);
EXPECT_TRUE(contents()->IsLoading());
EXPECT_FALSE(contents()->IsLoadingToDifferentDocument());
static_cast<mojom::FrameHost*>(subframe)->DidStopLoading();
EXPECT_FALSE(contents()->IsLoading());
}
// Ensure that WebContentsImpl does not stop loading too early when there still
// is a pending renderer. This can happen if a same-process non user-initiated
// navigation completes while there is an ongoing cross-process navigation.
// TODO(clamy): Rewrite that test when the renderer-initiated non-user-initiated
// navigation no longer kills the speculative RenderFrameHost. See
// https://crbug.com/889039.
TEST_F(WebContentsImplTest, DISABLED_NoEarlyStop) {
const GURL kUrl1("http://www.chromium.org");
const GURL kUrl2("http://www.google.com");
const GURL kUrl3("http://www.chromium.org/foo");
contents()->NavigateAndCommit(kUrl1);
TestRenderFrameHost* current_rfh = main_test_rfh();
// Start a browser-initiated cross-process navigation to |kUrl2|. The
// WebContents should be loading.
auto cross_process_navigation =
NavigationSimulator::CreateBrowserInitiated(kUrl2, contents());
cross_process_navigation->ReadyToCommit();
TestRenderFrameHost* pending_rfh =
contents()->GetSpeculativePrimaryMainFrame();
EXPECT_TRUE(contents()->IsLoading());
// The current RenderFrameHost starts a non user-initiated render-initiated
// navigation. The WebContents should still be loading.
auto same_process_navigation =
NavigationSimulator::CreateRendererInitiated(kUrl3, current_rfh);
same_process_navigation->SetHasUserGesture(false);
same_process_navigation->Start();
EXPECT_TRUE(contents()->IsLoading());
// Simulate the commit and DidStopLoading from the renderer-initiated
// navigation in the current RenderFrameHost. There should still be a pending
// RenderFrameHost and the WebContents should still be loading.
same_process_navigation->Commit();
static_cast<mojom::FrameHost*>(current_rfh)->DidStopLoading();
EXPECT_EQ(contents()->GetSpeculativePrimaryMainFrame(), pending_rfh);
EXPECT_TRUE(contents()->IsLoading());
// The same-process navigation should have committed.
ASSERT_EQ(2, controller().GetEntryCount());
EXPECT_EQ(kUrl3, controller().GetLastCommittedEntry()->GetURL());
// Commit the cross-process navigation. The formerly pending RenderFrameHost
// should now be the current RenderFrameHost and the WebContents should still
// be loading.
cross_process_navigation->Commit();
EXPECT_FALSE(contents()->GetSpeculativePrimaryMainFrame());
TestRenderFrameHost* new_current_rfh = main_test_rfh();
EXPECT_EQ(new_current_rfh, pending_rfh);
EXPECT_TRUE(contents()->IsLoading());
EXPECT_EQ(3, controller().GetEntryCount());
// Simulate the new current RenderFrameHost DidStopLoading. The WebContents
// should now have stopped loading.
static_cast<mojom::FrameHost*>(new_current_rfh)->DidStopLoading();
EXPECT_EQ(main_test_rfh(), new_current_rfh);
EXPECT_FALSE(contents()->IsLoading());
}
TEST_F(WebContentsImplTest, MediaWakeLock) {
EXPECT_FALSE(has_audio_wake_lock());
AudioStreamMonitor* monitor = contents()->audio_stream_monitor();
// Ensure RenderFrame is initialized before simulating events coming from it.
main_test_rfh()->InitializeRenderFrameIfNeeded();
// Send a fake audio stream monitor notification. The audio wake lock
// should be created.
monitor->set_was_recently_audible_for_testing(true);
contents()->NotifyNavigationStateChanged(INVALIDATE_TYPE_AUDIO);
EXPECT_TRUE(has_audio_wake_lock());
// Send another fake notification, this time when WasRecentlyAudible() will
// be false. The wake lock should be released.
monitor->set_was_recently_audible_for_testing(false);
contents()->NotifyNavigationStateChanged(INVALIDATE_TYPE_AUDIO);
EXPECT_FALSE(has_audio_wake_lock());
main_test_rfh()->GetProcess()->SimulateCrash();
// Verify that all the wake locks have been released.
EXPECT_FALSE(has_audio_wake_lock());
}
TEST_F(WebContentsImplTest, ThemeColorChangeDependingOnFirstVisiblePaint) {
TestWebContentsObserver observer(contents());
TestRenderFrameHost* rfh = main_test_rfh();
rfh->InitializeRenderFrameIfNeeded();
EXPECT_EQ(absl::nullopt, contents()->GetThemeColor());
EXPECT_EQ(0, observer.theme_color_change_calls());
// Theme color changes should not propagate past the WebContentsImpl before
// the first visually non-empty paint has occurred.
rfh->DidChangeThemeColor(SK_ColorRED);
EXPECT_EQ(SK_ColorRED, contents()->GetThemeColor());
EXPECT_EQ(0, observer.theme_color_change_calls());
// Simulate that the first visually non-empty paint has occurred. This will
// propagate the current theme color to the delegates.
rfh->GetPage().OnFirstVisuallyNonEmptyPaint();
EXPECT_EQ(SK_ColorRED, contents()->GetThemeColor());
EXPECT_EQ(1, observer.theme_color_change_calls());
// Additional changes made by the web contents should propagate as well.
rfh->DidChangeThemeColor(SK_ColorGREEN);
EXPECT_EQ(SK_ColorGREEN, contents()->GetThemeColor());
EXPECT_EQ(2, observer.theme_color_change_calls());
}
TEST_F(WebContentsImplTest, ParseDownloadHeaders) {
download::DownloadUrlParameters::RequestHeadersType request_headers =
WebContentsImpl::ParseDownloadHeaders("A: 1\r\nB: 2\r\nC: 3\r\n\r\n");
ASSERT_EQ(3u, request_headers.size());
EXPECT_EQ("A", request_headers[0].first);
EXPECT_EQ("1", request_headers[0].second);
EXPECT_EQ("B", request_headers[1].first);
EXPECT_EQ("2", request_headers[1].second);
EXPECT_EQ("C", request_headers[2].first);
EXPECT_EQ("3", request_headers[2].second);
request_headers = WebContentsImpl::ParseDownloadHeaders("A:1\r\nA:2\r\n");
ASSERT_EQ(2u, request_headers.size());
EXPECT_EQ("A", request_headers[0].first);
EXPECT_EQ("1", request_headers[0].second);
EXPECT_EQ("A", request_headers[1].first);
EXPECT_EQ("2", request_headers[1].second);
request_headers = WebContentsImpl::ParseDownloadHeaders("A 1\r\nA: 2");
ASSERT_EQ(1u, request_headers.size());
EXPECT_EQ("A", request_headers[0].first);
EXPECT_EQ("2", request_headers[0].second);
request_headers = WebContentsImpl::ParseDownloadHeaders("A: 1");
ASSERT_EQ(1u, request_headers.size());
EXPECT_EQ("A", request_headers[0].first);
EXPECT_EQ("1", request_headers[0].second);
request_headers = WebContentsImpl::ParseDownloadHeaders("A 1");
ASSERT_EQ(0u, request_headers.size());
}
namespace {
class TestJavaScriptDialogManager : public JavaScriptDialogManager {
public:
TestJavaScriptDialogManager() {}
~TestJavaScriptDialogManager() override {}
size_t reset_count() { return reset_count_; }
// JavaScriptDialogManager
void RunJavaScriptDialog(WebContents* web_contents,
RenderFrameHost* render_frame_host,
JavaScriptDialogType dialog_type,
const std::u16string& message_text,
const std::u16string& default_prompt_text,
DialogClosedCallback callback,
bool* did_suppress_message) override {
*did_suppress_message = true;
}
void RunBeforeUnloadDialog(WebContents* web_contents,
RenderFrameHost* render_frame_host,
bool is_reload,
DialogClosedCallback callback) override {}
bool HandleJavaScriptDialog(WebContents* web_contents,
bool accept,
const std::u16string* prompt_override) override {
return true;
}
void CancelDialogs(WebContents* web_contents,
bool reset_state) override {
if (reset_state)
++reset_count_;
}
private:
size_t reset_count_ = 0;
DISALLOW_COPY_AND_ASSIGN(TestJavaScriptDialogManager);
};
} // namespace
TEST_F(WebContentsImplTest, ResetJavaScriptDialogOnUserNavigate) {
const GURL kUrl("http://www.google.com");
const GURL kUrl2("http://www.google.com/sub");
TestJavaScriptDialogManager dialog_manager;
contents()->SetJavaScriptDialogManagerForTesting(&dialog_manager);
// A user-initiated navigation.
NavigationSimulator::NavigateAndCommitFromBrowser(contents(), kUrl);
EXPECT_EQ(1u, dialog_manager.reset_count());
// An automatic navigation.
auto navigation =
NavigationSimulator::CreateRendererInitiated(kUrl2, main_test_rfh());
navigation->SetHasUserGesture(false);
navigation->Commit();
if (CanSameSiteMainFrameNavigationsChangeRenderFrameHosts()) {
// If we changed RenderFrameHost on a renderer-initiated navigation above,
// we would trigger RenderFrameHostManager::UnloadOldFrame, similar to the
// first (user/browser-initiated) navigation, which will trigger dialog
// cancellations and increment the reset_count to 2.
EXPECT_EQ(2u, dialog_manager.reset_count());
} else {
EXPECT_EQ(1u, dialog_manager.reset_count());
}
contents()->SetJavaScriptDialogManagerForTesting(nullptr);
}
TEST_F(WebContentsImplTest, StartingSandboxFlags) {
WebContents::CreateParams params(browser_context());
network::mojom::WebSandboxFlags expected_flags =
network::mojom::WebSandboxFlags::kPopups |
network::mojom::WebSandboxFlags::kModals |
network::mojom::WebSandboxFlags::kTopNavigation;
params.starting_sandbox_flags = expected_flags;
std::unique_ptr<WebContentsImpl> new_contents(
WebContentsImpl::CreateWithOpener(params, nullptr));
FrameTreeNode* root = new_contents->GetFrameTree()->root();
network::mojom::WebSandboxFlags pending_flags =
root->pending_frame_policy().sandbox_flags;
EXPECT_EQ(pending_flags, expected_flags);
network::mojom::WebSandboxFlags effective_flags =
root->effective_frame_policy().sandbox_flags;
EXPECT_EQ(effective_flags, expected_flags);
}
TEST_F(WebContentsImplTest, DidFirstVisuallyNonEmptyPaint) {
TestWebContentsObserver observer(contents());
contents()->GetPrimaryPage().OnFirstVisuallyNonEmptyPaint();
EXPECT_TRUE(observer.observed_did_first_visually_non_empty_paint());
}
TEST_F(WebContentsImplTest, DidChangeVerticalScrollDirection) {
TestWebContentsObserver observer(contents());
EXPECT_FALSE(observer.last_vertical_scroll_direction().has_value());
contents()->OnVerticalScrollDirectionChanged(
viz::VerticalScrollDirection::kUp);
EXPECT_EQ(viz::VerticalScrollDirection::kUp,
observer.last_vertical_scroll_direction().value());
}
TEST_F(WebContentsImplTest, HandleContextMenuDelegate) {
MockWebContentsDelegate delegate;
contents()->SetDelegate(&delegate);
TestRenderFrameHost* rfh = main_test_rfh();
EXPECT_CALL(delegate, HandleContextMenu(rfh, ::testing::_))
.WillOnce(::testing::Return(true));
ContextMenuParams params;
contents()->ShowContextMenu(rfh, mojo::NullAssociatedRemote(), params);
contents()->SetDelegate(nullptr);
}
TEST_F(WebContentsImplTest, RegisterProtocolHandlerDifferentOrigin) {
MockWebContentsDelegate delegate;
contents()->SetDelegate(&delegate);
GURL url("https://www.google.com");
GURL handler_url1("https://www.google.com/handler/%s");
GURL handler_url2("https://www.example.com/handler/%s");
contents()->NavigateAndCommit(url);
// Only the first call to RegisterProtocolHandler should register because the
// other call has a handler from a different origin.
EXPECT_CALL(delegate, RegisterProtocolHandler(main_test_rfh(), "mailto",
handler_url1, true))
.Times(1);
EXPECT_CALL(delegate, RegisterProtocolHandler(main_test_rfh(), "mailto",
handler_url2, true))
.Times(0);
{
contents()->RegisterProtocolHandler(main_test_rfh(), "mailto", handler_url1,
/*user_gesture=*/true);
}
{
contents()->RegisterProtocolHandler(main_test_rfh(), "mailto", handler_url2,
/*user_gesture=*/true);
}
// Check behavior for RegisterProtocolHandler::kUntrustedOrigins.
MockWebContentsDelegate unrestrictive_delegate(
blink::ProtocolHandlerSecurityLevel::kUntrustedOrigins);
contents()->SetDelegate(&unrestrictive_delegate);
EXPECT_CALL(
unrestrictive_delegate,
RegisterProtocolHandler(main_test_rfh(), "mailto", handler_url1, true))
.Times(1);
EXPECT_CALL(
unrestrictive_delegate,
RegisterProtocolHandler(main_test_rfh(), "mailto", handler_url2, true))
.Times(1);
{
contents()->RegisterProtocolHandler(main_test_rfh(), "mailto", handler_url1,
/*user_gesture=*/true);
}
{
contents()->RegisterProtocolHandler(main_test_rfh(), "mailto", handler_url2,
/*user_gesture=*/true);
}
contents()->SetDelegate(nullptr);
}
TEST_F(WebContentsImplTest, RegisterProtocolHandlerDataURL) {
MockWebContentsDelegate delegate;
contents()->SetDelegate(&delegate);
GURL data("data:text/html,<html><body><b>hello world</b></body></html>");
GURL data_handler(data.spec() + "%s");
contents()->NavigateAndCommit(data);
// Data URLs should fail.
EXPECT_CALL(delegate, RegisterProtocolHandler(contents()->GetMainFrame(),
"mailto", data_handler, true))
.Times(0);
{
contents()->RegisterProtocolHandler(main_test_rfh(), "mailto", data_handler,
/*user_gesture=*/true);
}
contents()->SetDelegate(nullptr);
}
TEST_F(WebContentsImplTest, Bluetooth) {
TestWebContentsObserver observer(contents());
EXPECT_EQ(observer.num_is_connected_to_bluetooth_device_changed(), 0);
EXPECT_FALSE(contents()->IsConnectedToBluetoothDevice());
contents()->TestIncrementBluetoothConnectedDeviceCount();
EXPECT_EQ(observer.num_is_connected_to_bluetooth_device_changed(), 1);
EXPECT_TRUE(observer.last_is_connected_to_bluetooth_device());
EXPECT_TRUE(contents()->IsConnectedToBluetoothDevice());
contents()->TestDecrementBluetoothConnectedDeviceCount();
EXPECT_EQ(observer.num_is_connected_to_bluetooth_device_changed(), 2);
EXPECT_FALSE(observer.last_is_connected_to_bluetooth_device());
EXPECT_FALSE(contents()->IsConnectedToBluetoothDevice());
}
TEST_F(WebContentsImplTest, BadDownloadImageResponseFromRenderer) {
// Avoid using TestWebContents, which fakes image download logic without
// exercising the code in WebContentsImpl.
scoped_refptr<SiteInstance> instance =
SiteInstance::Create(GetBrowserContext());
instance->GetProcess()->Init();
WebContents::CreateParams create_params(GetBrowserContext(),
std::move(instance));
create_params.desired_renderer_state = WebContents::CreateParams::
CreateParams::kInitializeAndWarmupRendererProcess;
std::unique_ptr<WebContentsImpl> contents(
WebContentsImpl::CreateWithOpener(create_params, /*opener_rfh=*/nullptr));
ASSERT_FALSE(contents->GetMainFrame()->GetProcess()->ShutdownRequested());
// Set up the fake image downloader.
FakeImageDownloader fake_image_downloader;
fake_image_downloader.Init(contents->GetMainFrame()->GetRemoteInterfaces());
// For the purpose of this test, set up a malformed response with different
// vector sizes.
const GURL kImageUrl = GURL("https://example.com/favicon.ico");
fake_image_downloader.SetFakeResponseData(
kImageUrl,
/*bitmaps=*/{}, /*original_bitmap_sizes=*/{gfx::Size(16, 16)});
base::RunLoop run_loop;
contents->DownloadImage(
kImageUrl,
/*is_favicon=*/true,
/*preferred_size=*/16,
/*max_bitmap_size=*/32,
/*bypass_cache=*/false,
base::BindLambdaForTesting([&](int id, int http_status_code,
const GURL& image_url,
const std::vector<SkBitmap>& bitmaps,
const std::vector<gfx::Size>& sizes) {
EXPECT_EQ(400, http_status_code);
EXPECT_TRUE(bitmaps.empty());
EXPECT_TRUE(sizes.empty());
run_loop.Quit();
}));
run_loop.Run();
// The renderer process should have been killed due to
// WCI_INVALID_DOWNLOAD_IMAGE_RESULT.
EXPECT_TRUE(contents->GetMainFrame()->GetProcess()->ShutdownRequested());
}
TEST_F(WebContentsImplTest,
GetCaptureHandleConfigBeforeSetIsCalledReturnsEmptyConfig) {
const auto empty_config = blink::mojom::CaptureHandleConfig::New();
EXPECT_EQ(contents()->GetCaptureHandleConfig(), *empty_config);
}
TEST_F(WebContentsImplTest, SetAndGetCaptureHandleConfig) {
// Value set - value returned.
{
auto config = blink::mojom::CaptureHandleConfig::New();
config->capture_handle = u"Pay not attention";
contents()->SetCaptureHandleConfig(config->Clone());
EXPECT_EQ(*config, contents()->GetCaptureHandleConfig());
}
// New value set - new value returned.
{
auto config = blink::mojom::CaptureHandleConfig::New();
config->capture_handle = u"to the man behind the curtain.";
contents()->SetCaptureHandleConfig(config->Clone());
EXPECT_EQ(*config, contents()->GetCaptureHandleConfig());
}
}
TEST_F(WebContentsImplTest, NoOnCaptureHandleConfigUpdateCallIfResettingEmpty) {
const auto empty_config = blink::mojom::CaptureHandleConfig::New();
// Reminder - empty in the beginning.
ASSERT_EQ(contents()->GetCaptureHandleConfig(),
*blink::mojom::CaptureHandleConfig::New());
TestWebContentsObserver observer(contents());
// Note that ExpectOnCaptureHandleConfigUpdate() is NOT called.
// If OnCaptureHandleConfigUpdate() is called, the test will fail.
contents()->SetCaptureHandleConfig(empty_config.Clone());
}
TEST_F(WebContentsImplTest,
OnCaptureHandleConfigUpdateCalledWhenHandleChanges) {
{
auto config = blink::mojom::CaptureHandleConfig::New();
config->capture_handle = u"Some handle.";
contents()->SetCaptureHandleConfig(config.Clone());
}
{
auto config = blink::mojom::CaptureHandleConfig::New();
config->capture_handle = u"A different handle.";
TestWebContentsObserver observer(contents());
observer.ExpectOnCaptureHandleConfigUpdate(config.Clone());
contents()->SetCaptureHandleConfig(config.Clone());
}
}
TEST_F(WebContentsImplTest,
OnCaptureHandleConfigUpdateNotCalledWhenResettingAnIdenticalHandle) {
{
auto config = blink::mojom::CaptureHandleConfig::New();
config->capture_handle = u"The ministry of redundancy ministry.";
contents()->SetCaptureHandleConfig(config.Clone());
}
{
auto config = blink::mojom::CaptureHandleConfig::New();
config->capture_handle = u"The ministry of redundancy ministry.";
TestWebContentsObserver observer(contents());
// Note that ExpectOnCaptureHandleConfigUpdate() is NOT called.
// If OnCaptureHandleConfigUpdate() is called, the test will fail.
contents()->SetCaptureHandleConfig(config.Clone());
}
}
TEST_F(WebContentsImplTest,
OnCaptureHandleConfigUpdateCalledWhenClearingTheConfig) {
auto config = blink::mojom::CaptureHandleConfig::New();
config->capture_handle = u"Some handle.";
contents()->SetCaptureHandleConfig(config.Clone());
auto empty_config = blink::mojom::CaptureHandleConfig::New();
TestWebContentsObserver observer(contents());
observer.ExpectOnCaptureHandleConfigUpdate(empty_config.Clone());
contents()->SetCaptureHandleConfig(empty_config.Clone());
}
TEST_F(WebContentsImplTest,
CrossDocumentMainPageNavigationClearsCaptureHandleConfig) {
TestRenderFrameHost* orig_rfh = main_test_rfh();
ASSERT_EQ(orig_rfh, orig_rfh->GetMainFrame());
// Navigate to the first site.
NavigationSimulator::NavigateAndCommitFromBrowser(
contents(), GURL("http://www.google.com/a.html"));
orig_rfh->GetSiteInstance()->IncrementActiveFrameCount();
// Set a capture handle.
auto config = blink::mojom::CaptureHandleConfig::New();
config->capture_handle = u"Some handle.";
contents()->SetCaptureHandleConfig(config.Clone());
// Expect that navigation to a new site will reset the capture handle config.
const auto empty_config = blink::mojom::CaptureHandleConfig::New();
TestWebContentsObserver observer(contents());
observer.ExpectOnCaptureHandleConfigUpdate(empty_config.Clone());
// Navigate to the second site.
auto new_site_navigation = NavigationSimulator::CreateBrowserInitiated(
GURL("http://www.google.com/b.html"), contents());
new_site_navigation->ReadyToCommit();
// Further proof that the config was reset.
EXPECT_EQ(contents()->GetCaptureHandleConfig(), *empty_config);
}
TEST_F(WebContentsImplTest,
SameDocumentMainPageNavigationDoesNotClearCaptureHandleConfig) {
TestRenderFrameHost* orig_rfh = main_test_rfh();
ASSERT_EQ(orig_rfh, orig_rfh->GetMainFrame());
// Navigate to the first site.
NavigationSimulator::NavigateAndCommitFromBrowser(
contents(), GURL("http://www.google.com/index.html"));
orig_rfh->GetSiteInstance()->IncrementActiveFrameCount();
// Set a capture handle.
auto config = blink::mojom::CaptureHandleConfig::New();
config->capture_handle = u"Some handle.";
contents()->SetCaptureHandleConfig(config.Clone());
// ExpectOnCaptureHandleConfigUpdate() not called - the test will fail
// if OnCaptureHandleConfigUpdate() is called.
TestWebContentsObserver observer(contents());
// Navigate to the second site.
auto new_site_navigation = NavigationSimulator::CreateBrowserInitiated(
GURL("http://www.google.com/index.html#same_doc"), contents());
new_site_navigation->ReadyToCommit();
// Further proof that the config was not reset.
EXPECT_EQ(contents()->GetCaptureHandleConfig(), *config);
}
TEST_F(WebContentsImplTest,
CrossDocumentChildPageNavigationDoesNotClearCaptureHandleConfig) {
TestRenderFrameHost* orig_rfh = main_test_rfh();
ASSERT_EQ(orig_rfh, orig_rfh->GetMainFrame());
NavigationSimulator::NavigateAndCommitFromBrowser(
contents(), GURL("http://www.google.com/a.html"));
TestRenderFrameHost* subframe = orig_rfh->AppendChild("subframe");
ASSERT_NE(subframe, subframe->GetMainFrame());
subframe->GetSiteInstance()->IncrementActiveFrameCount();
NavigationSimulator::NavigateAndCommitFromDocument(
GURL("http://www.google.com/b.html"), subframe);
// Set a capture handle.
auto config = blink::mojom::CaptureHandleConfig::New();
config->capture_handle = u"Some handle.";
contents()->SetCaptureHandleConfig(config.Clone());
// ExpectOnCaptureHandleConfigUpdate() not called - the test will fail
// if OnCaptureHandleConfigUpdate() is called.
TestWebContentsObserver observer(contents());
NavigationSimulator::NavigateAndCommitFromDocument(
GURL("http://www.google.com/c.html"), subframe);
// Further proof that the config was not reset.
EXPECT_EQ(contents()->GetCaptureHandleConfig(), *config);
}
class TestCanonicalUrlLocalFrame : public content::FakeLocalFrame,
public WebContentsObserver {
public:
explicit TestCanonicalUrlLocalFrame(WebContents* web_contents,
absl::optional<GURL> canonical_url)
: WebContentsObserver(web_contents), canonical_url_(canonical_url) {}
void RenderFrameCreated(RenderFrameHost* render_frame_host) override {
if (!initialized_) {
initialized_ = true;
Init(render_frame_host->GetRemoteAssociatedInterfaces());
}
}
void GetCanonicalUrlForSharing(
base::OnceCallback<void(const absl::optional<GURL>&)> callback) override {
std::move(callback).Run(canonical_url_);
}
private:
bool initialized_ = false;
absl::optional<GURL> canonical_url_;
};
TEST_F(WebContentsImplTest, CanonicalUrlSchemeHttpsIsAllowed) {
TestCanonicalUrlLocalFrame local_frame(contents(), GURL("https://someurl/"));
NavigationSimulator::NavigateAndCommitFromBrowser(contents(),
GURL("https://site/"));
base::RunLoop run_loop;
absl::optional<GURL> canonical_url;
base::RepeatingClosure quit = run_loop.QuitClosure();
auto on_done = [&](const absl::optional<GURL>& result) {
canonical_url = result;
quit.Run();
};
contents()->GetMainFrame()->GetCanonicalUrl(
base::BindLambdaForTesting(on_done));
run_loop.Run();
ASSERT_TRUE(canonical_url);
EXPECT_EQ(GURL("https://someurl/"), *canonical_url);
}
TEST_F(WebContentsImplTest, CanonicalUrlSchemeChromeIsNotAllowed) {
TestCanonicalUrlLocalFrame local_frame(contents(), GURL("chrome://someurl/"));
NavigationSimulator::NavigateAndCommitFromBrowser(contents(),
GURL("https://site/"));
base::RunLoop run_loop;
absl::optional<GURL> canonical_url;
base::RepeatingClosure quit = run_loop.QuitClosure();
auto on_done = [&](const absl::optional<GURL>& result) {
canonical_url = result;
quit.Run();
};
contents()->GetMainFrame()->GetCanonicalUrl(
base::BindLambdaForTesting(on_done));
run_loop.Run();
ASSERT_FALSE(canonical_url) << "canonical_url=" << *canonical_url;
}
} // namespace content