blob: 2aeaa7462b56dbc2e68ca1d2e75aa0a4ce762327 [file] [log] [blame]
// Copyright 2014 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "content/test/content_browser_test_utils_internal.h"
#include <stddef.h>
#include <algorithm>
#include <map>
#include <memory>
#include <set>
#include <utility>
#include <vector>
#include "base/containers/stack.h"
#include "base/functional/bind.h"
#include "base/functional/callback_helpers.h"
#include "base/strings/escape.h"
#include "base/strings/stringprintf.h"
#include "base/task/thread_pool.h"
#include "base/test/bind.h"
#include "base/test/test_future.h"
#include "base/test/test_timeouts.h"
#include "build/build_config.h"
#include "content/browser/preloading/prerender/prerender_host_registry.h"
#include "content/browser/renderer_host/delegated_frame_host.h"
#include "content/browser/renderer_host/frame_tree_node.h"
#include "content/browser/renderer_host/navigation_request.h"
#include "content/browser/renderer_host/navigator.h"
#include "content/browser/renderer_host/render_frame_host_delegate.h"
#include "content/browser/renderer_host/render_frame_proxy_host.h"
#include "content/browser/renderer_host/render_widget_host_factory.h"
#include "content/browser/site_instance_impl.h"
#include "content/browser/web_contents/web_contents_impl.h"
#include "content/common/frame_messages.mojom.h"
#include "content/public/browser/browser_task_traits.h"
#include "content/public/browser/browser_thread.h"
#include "content/public/browser/file_select_listener.h"
#include "content/public/browser/navigation_handle.h"
#include "content/public/browser/render_frame_host.h"
#include "content/public/browser/site_isolation_policy.h"
#include "content/public/browser/web_contents.h"
#include "content/public/test/browser_test_utils.h"
#include "content/public/test/content_browser_test_utils.h"
#include "content/public/test/test_frame_navigation_observer.h"
#include "content/public/test/test_navigation_observer.h"
#include "content/shell/browser/shell.h"
#include "content/shell/browser/shell_javascript_dialog_manager.h"
#include "net/test/embedded_test_server/http_request.h"
#include "net/test/embedded_test_server/http_response.h"
#include "net/test/embedded_test_server/request_handler_util.h"
#include "third_party/blink/public/common/frame/frame_visual_properties.h"
namespace content {
bool NavigateFrameToURL(FrameTreeNode* node, const GURL& url) {
TestFrameNavigationObserver observer(node);
NavigationController::LoadURLParams params(url);
params.transition_type = ui::PAGE_TRANSITION_LINK;
params.frame_tree_node_id = node->frame_tree_node_id();
FrameTree& frame_tree = node->frame_tree();
node->navigator().controller().LoadURLWithParams(params);
observer.Wait();
if (!observer.last_navigation_succeeded()) {
DLOG(WARNING) << "Navigation did not succeed: " << url;
return false;
}
// It's possible for JS handlers triggered during the navigation to remove
// the node, so retrieve it by ID again to check if that occurred.
node = frame_tree.FindByID(params.frame_tree_node_id);
if (node && url != node->current_url()) {
DLOG(WARNING) << "Expected URL " << url << " but observed "
<< node->current_url();
return false;
}
return true;
}
void SetShouldProceedOnBeforeUnload(Shell* shell, bool proceed, bool success) {
ShellJavaScriptDialogManager* manager =
static_cast<ShellJavaScriptDialogManager*>(
shell->GetJavaScriptDialogManager(shell->web_contents()));
manager->set_should_proceed_on_beforeunload(proceed, success);
}
RenderFrameHost* ConvertToRenderFrameHost(FrameTreeNode* frame_tree_node) {
return frame_tree_node->current_frame_host();
}
bool NavigateToURLInSameBrowsingInstance(Shell* window, const GURL& url) {
TestNavigationObserver observer(window->web_contents());
// Using a PAGE_TRANSITION_LINK transition with a browser-initiated
// navigation forces it to stay in the current BrowsingInstance, as normally
// that transition is used by renderer-initiated navigations.
window->LoadURLForFrame(url, std::string(),
ui::PageTransitionFromInt(ui::PAGE_TRANSITION_LINK));
observer.Wait();
if (!IsLastCommittedEntryOfPageType(window->web_contents(),
PAGE_TYPE_NORMAL)) {
NavigationEntry* last_entry =
window->web_contents()->GetController().GetLastCommittedEntry();
DLOG(WARNING) << "last_entry->GetPageType() = "
<< (last_entry ? last_entry->GetPageType() : -1);
return false;
}
if (window->web_contents()->GetLastCommittedURL() != url) {
DLOG(WARNING) << "window->web_contents()->GetLastCommittedURL() = "
<< window->web_contents()->GetLastCommittedURL()
<< "; url = " << url;
return false;
}
return true;
}
bool IsExpectedSubframeErrorTransition(SiteInstance* start_site_instance,
SiteInstance* end_site_instance) {
bool site_instances_are_equal = (start_site_instance == end_site_instance);
// AgentClusterKey mismatch will trigger a SiteInstance switch.
if (static_cast<SiteInstanceImpl*>(start_site_instance)
->GetSiteInfo()
.agent_cluster_key() !=
static_cast<SiteInstanceImpl*>(end_site_instance)
->GetSiteInfo()
.agent_cluster_key() &&
!site_instances_are_equal) {
return true;
}
bool is_error_page_site_instance =
(static_cast<SiteInstanceImpl*>(end_site_instance)
->GetSiteInfo()
.is_error_page());
if (!SiteIsolationPolicy::IsErrorPageIsolationEnabled(
/*in_main_frame=*/false)) {
return site_instances_are_equal && !is_error_page_site_instance;
} else {
return !site_instances_are_equal && is_error_page_site_instance;
}
}
RenderFrameHost* CreateSubframe(WebContentsImpl* web_contents,
std::string frame_id,
const GURL& url,
bool wait_for_navigation) {
return CreateSubframe(
web_contents->GetPrimaryFrameTree().root()->current_frame_host(),
frame_id, url, wait_for_navigation, {});
}
RenderFrameHost* CreateSubframe(RenderFrameHost* parent,
std::string frame_id,
const GURL& url,
bool wait_for_navigation) {
return CreateSubframe(parent, frame_id, url, wait_for_navigation, {});
}
RenderFrameHost* CreateSubframe(RenderFrameHost* parent,
std::string frame_id,
const GURL& url,
bool wait_for_navigation,
ExtraParams extra_params) {
WebContents* web_contents = WebContents::FromRenderFrameHost(parent);
RenderFrameHostCreatedObserver subframe_created_observer(web_contents);
TestNavigationObserver subframe_nav_observer(web_contents);
EXPECT_TRUE(
ExecJs(parent, JsReplace(R"(
var iframe = document.createElement('iframe');
iframe.id = $1; //frame_id
if ($2) {
iframe.src = $2; // url
}
if ($3) {
iframe.sandbox = $3; // extra_params.sandbox_flags
}
document.body.appendChild(iframe);
)",
frame_id, url, extra_params.sandbox_flags)));
subframe_created_observer.Wait();
if (wait_for_navigation)
subframe_nav_observer.Wait();
FrameTreeNode* root =
static_cast<RenderFrameHostImpl*>(parent)->frame_tree_node();
return root->child_at(root->child_count() - 1)->current_frame_host();
}
std::vector<RenderFrameHostImpl*> CollectAllRenderFrameHosts(
RenderFrameHostImpl* starting_rfh) {
std::vector<RenderFrameHostImpl*> visited_frames;
starting_rfh->ForEachRenderFrameHostImpl(
[&](RenderFrameHostImpl* rfh) { visited_frames.push_back(rfh); });
return visited_frames;
}
std::vector<RenderFrameHostImpl*>
CollectAllRenderFrameHostsIncludingSpeculative(
RenderFrameHostImpl* starting_rfh) {
std::vector<RenderFrameHostImpl*> visited_frames;
starting_rfh->ForEachRenderFrameHostImplIncludingSpeculative(
[&](RenderFrameHostImpl* rfh) { visited_frames.push_back(rfh); });
return visited_frames;
}
std::vector<RenderFrameHostImpl*> CollectAllRenderFrameHosts(
WebContentsImpl* web_contents) {
std::vector<RenderFrameHostImpl*> visited_frames;
web_contents->ForEachRenderFrameHostImpl(
[&](RenderFrameHostImpl* rfh) { visited_frames.push_back(rfh); });
return visited_frames;
}
std::vector<RenderFrameHostImpl*>
CollectAllRenderFrameHostsIncludingSpeculative(WebContentsImpl* web_contents) {
std::vector<RenderFrameHostImpl*> visited_frames;
web_contents->ForEachRenderFrameHostImplIncludingSpeculative(
[&](RenderFrameHostImpl* rfh) { visited_frames.push_back(rfh); });
return visited_frames;
}
Shell* OpenBlankWindow(WebContentsImpl* web_contents) {
FrameTreeNode* root = web_contents->GetPrimaryFrameTree().root();
ShellAddedObserver new_shell_observer;
EXPECT_TRUE(ExecJs(root, "last_opened_window = window.open()"));
Shell* new_shell = new_shell_observer.GetShell();
EXPECT_NE(new_shell->web_contents(), web_contents);
EXPECT_TRUE(new_shell->web_contents()
->GetController()
.GetLastCommittedEntry()
->IsInitialEntry());
EXPECT_EQ(1, new_shell->web_contents()->GetController().GetEntryCount());
return new_shell;
}
Shell* OpenWindow(WebContentsImpl* web_contents, const GURL& url) {
FrameTreeNode* root = web_contents->GetPrimaryFrameTree().root();
ShellAddedObserver new_shell_observer;
EXPECT_TRUE(
ExecJs(root, JsReplace("last_opened_window = window.open($1)", url)));
Shell* new_shell = new_shell_observer.GetShell();
EXPECT_NE(new_shell->web_contents(), web_contents);
return new_shell;
}
FrameTreeVisualizer::FrameTreeVisualizer() = default;
FrameTreeVisualizer::~FrameTreeVisualizer() = default;
std::string FrameTreeVisualizer::DepictFrameTree(FrameTreeNode* root) {
// Tracks the sites actually used in this depiction.
std::map<std::string, SiteInstance*> legend;
// Traversal 1: Assign names to current frames. This ensures that the first
// call to the pretty-printer will result in a naming of the site instances
// that feels natural and stable.
base::stack<FrameTreeNode*> to_explore;
for (to_explore.push(root); !to_explore.empty();) {
FrameTreeNode* node = to_explore.top();
to_explore.pop();
for (size_t i = node->child_count(); i-- != 0;) {
to_explore.push(node->child_at(i));
}
RenderFrameHost* current = node->render_manager()->current_frame_host();
legend[GetName(current->GetSiteInstance())] = current->GetSiteInstance();
}
// Traversal 2: Assign names to the pending/speculative frames. For stability
// of assigned names it's important to do this before trying to name the
// proxies, which have a less well defined order.
for (to_explore.push(root); !to_explore.empty();) {
FrameTreeNode* node = to_explore.top();
to_explore.pop();
for (size_t i = node->child_count(); i-- != 0;) {
to_explore.push(node->child_at(i));
}
RenderFrameHost* spec = node->render_manager()->speculative_frame_host();
if (spec)
legend[GetName(spec->GetSiteInstance())] = spec->GetSiteInstance();
}
// Traversal 3: Assign names to the SiteInstances within each group's proxies
// (which are associated with SiteInstanceGroups instead of SiteInstances) and
// add them to |legend| too. Typically, only openers should have their names
// assigned this way.
for (to_explore.push(root); !to_explore.empty();) {
FrameTreeNode* node = to_explore.top();
to_explore.pop();
for (size_t i = node->child_count(); i-- != 0;) {
to_explore.push(node->child_at(i));
}
// Sort the proxies by SiteInstanceGroup ID to avoid unordered_map ordering.
std::vector<SiteInstance*> site_instances;
for (const auto& proxy_pair :
node->render_manager()->GetAllProxyHostsForTesting()) {
SiteInstanceGroup* group = proxy_pair.second->site_instance_group();
for (raw_ptr<SiteInstanceImpl> instance :
group->site_instances_for_testing()) {
site_instances.push_back(instance);
}
}
std::sort(site_instances.begin(), site_instances.end(),
[](SiteInstance* lhs, SiteInstance* rhs) {
return lhs->GetId() < rhs->GetId();
});
for (SiteInstance* site_instance : site_instances)
legend[GetName(site_instance)] = site_instance;
}
// Traversal 4: Now that all names are assigned, make a big loop to pretty-
// print the tree. Each iteration produces exactly one line of format.
std::string result;
for (to_explore.push(root); !to_explore.empty();) {
FrameTreeNode* node = to_explore.top();
to_explore.pop();
for (size_t i = node->child_count(); i-- != 0;) {
to_explore.push(node->child_at(i));
}
// Draw the feeler line tree graphics by walking up to the root. A feeler
// line is needed for each ancestor that is the last child of its parent.
// This creates the ASCII art that looks like:
// Foo
// |--Foo
// |--Foo
// | |--Foo
// | +--Foo
// | +--Foo
// +--Foo
// +--Foo
//
// TODO(nick): Make this more elegant.
std::string line;
if (node != root) {
if (node->parent()->child_at(node->parent()->child_count() - 1) != node)
line = " |--";
else
line = " +--";
for (RenderFrameHostImpl* up = node->parent();
up != root->current_frame_host(); up = up->GetParent()) {
if (up->GetParent()
->child_at(up->GetParent()->child_count() - 1)
->current_frame_host() != up)
line = " | " + line;
else
line = " " + line;
}
}
// Prefix one extra space of padding for two reasons. First, this helps the
// diagram aligns nicely with the legend. Second, this makes it easier to
// read the diffs that gtest spits out on EXPECT_EQ failure.
line = " " + line;
// Summarize the FrameTreeNode's state. Always show the site of the current
// RenderFrameHost, and show any exceptional state of the node, like a
// pending or speculative RenderFrameHost.
RenderFrameHost* current = node->render_manager()->current_frame_host();
RenderFrameHost* spec = node->render_manager()->speculative_frame_host();
base::StringAppendF(&line, "Site %s",
GetName(current->GetSiteInstance()).c_str());
if (spec) {
base::StringAppendF(&line, " (%s speculative)",
GetName(spec->GetSiteInstance()).c_str());
}
// Show the SiteInstances of the RenderFrameProxyHosts of this node.
const auto& proxy_host_map =
node->render_manager()->GetAllProxyHostsForTesting();
if (!proxy_host_map.empty()) {
// Show a dashed line of variable length before the proxy list. Always at
// least two dashes.
line.append(" --");
// To make proxy lists align vertically for the first three tree levels,
// pad with dashes up to a first tab stop at column 19 (which works out to
// text editor column 28 in the typical diagram fed to EXPECT_EQ as a
// string literal). Lining the lists up vertically makes differences in
// the proxy sets easier to spot visually. We choose not to use the
// *actual* tree height here, because that would make the diagram's
// appearance less stable as the tree's shape evolves.
while (line.length() < 20) {
line.append("-");
}
line.append(" proxies for");
// Sort these alphabetically, to avoid hash_map ordering dependency.
std::vector<std::string> sorted_proxy_hosts;
for (const auto& proxy_pair : proxy_host_map) {
sorted_proxy_hosts.push_back(
GetGroupName(proxy_pair.second->site_instance_group()));
}
std::sort(sorted_proxy_hosts.begin(), sorted_proxy_hosts.end());
for (std::string& proxy_name : sorted_proxy_hosts) {
base::StringAppendF(&line, " %s", proxy_name.c_str());
}
}
if (node != root)
result.append("\n");
result.append(line);
}
// Finally, show a legend with details of the site instances.
const char* prefix = "Where ";
for (auto& legend_entry : legend) {
SiteInstanceImpl* site_instance =
static_cast<SiteInstanceImpl*>(legend_entry.second);
std::string description =
GetUrlWithoutPort(site_instance->GetSiteURL()).spec();
// data: URLs have site URLs of the form data:nonce, where the nonce is an
// UnguessableToken. Make these deterministic for testing by using the
// abbreviated letter for the site in the nonce. For example,
// "data:nonce_A".
if (site_instance->GetSiteURL().SchemeIs(url::kDataScheme)) {
description =
base::StringPrintf("data:nonce_%s", legend_entry.first.c_str());
}
base::StringAppendF(&result, "\n%s%s = %s", prefix,
legend_entry.first.c_str(), description.c_str());
// Highlight some exceptionable conditions.
if (site_instance->GetSiteInfo().is_sandboxed()) {
result.append(" (sandboxed)");
}
if (site_instance->group()->active_frame_count() == 0)
result.append(" (active_frame_count == 0)");
if (!site_instance->GetProcess()->IsInitializedAndNotDead())
result.append(" (no process)");
prefix = " ";
}
return result;
}
std::string FrameTreeVisualizer::GetName(SiteInstance* site_instance) {
// Indices into the vector correspond to letters of the alphabet.
size_t index =
std::ranges::find(seen_site_instance_ids_, site_instance->GetId()) -
seen_site_instance_ids_.begin();
if (index == seen_site_instance_ids_.size())
seen_site_instance_ids_.push_back(site_instance->GetId());
// Whosoever writes a test using >=26 site instances shall be a lucky ducky.
if (index < 25)
return base::StringPrintf("%c", 'A' + static_cast<char>(index));
else
return base::StringPrintf("Z%d", static_cast<int>(index - 25));
}
std::string FrameTreeVisualizer::GetGroupName(SiteInstanceGroup* group) {
// If there's only one SiteInstance in `group`, get the name of the
// SiteInstance directly. This preserves test expectations for DepictFrameTree
// uses that predate SiteInstanceGroup.
if (group->site_instances_for_testing().size() == 1) {
return GetName(*group->site_instances_for_testing().begin());
}
// Alphabetically sort the SiteInstances within the group.
std::vector<std::string> sorted_instance_names;
for (auto& site_instance : group->site_instances_for_testing()) {
sorted_instance_names.push_back(GetName(site_instance));
}
std::sort(sorted_instance_names.begin(), sorted_instance_names.end());
// Name the group using set notation.
CHECK(sorted_instance_names.size() >= 1u);
std::string result = "{";
for (auto& site_instance_name : sorted_instance_names) {
base::StringAppendF(&result, "%s,", site_instance_name.c_str());
}
result.resize(result.length() - 1);
result.append("}");
return result;
}
GURL FrameTreeVisualizer::GetUrlWithoutPort(const GURL& url) {
GURL::Replacements replacements;
replacements.ClearPort();
return url.ReplaceComponents(replacements);
}
std::string DepictFrameTree(FrameTreeNode& root) {
return FrameTreeVisualizer().DepictFrameTree(&root);
}
Shell* OpenPopup(const ToRenderFrameHost& opener,
const GURL& url,
const std::string& name) {
return OpenPopup(opener, url, name, "", true);
}
Shell* OpenPopup(const ToRenderFrameHost& opener,
const GURL& url,
const std::string& name,
const std::string& features,
bool expect_return_from_window_open) {
TestNavigationObserver observer(url);
observer.StartWatchingNewWebContents();
ShellAddedObserver new_shell_observer;
std::string popup_script = "!!window.open('" + url.spec() + "', '" + name +
"', '" + features + "');";
bool did_create_popup = EvalJs(opener, popup_script).ExtractBool();
if (!(did_create_popup || !expect_return_from_window_open)) {
return nullptr;
}
observer.Wait();
Shell* new_shell = new_shell_observer.GetShell();
EXPECT_EQ(
url,
new_shell->web_contents()->GetPrimaryMainFrame()->GetLastCommittedURL());
return new_shell_observer.GetShell();
}
FileChooserDelegate::FileChooserDelegate(std::vector<base::FilePath> files,
const base::FilePath& base_dir,
base::OnceClosure callback)
: files_(std::move(files)),
base_dir_(base_dir),
callback_(std::move(callback)) {}
FileChooserDelegate::FileChooserDelegate(const base::FilePath& file,
base::OnceClosure callback)
: FileChooserDelegate(std::vector<base::FilePath>(1, file),
base::FilePath(),
std::move(callback)) {}
FileChooserDelegate::~FileChooserDelegate() = default;
void FileChooserDelegate::RunFileChooser(
RenderFrameHost* render_frame_host,
scoped_refptr<content::FileSelectListener> listener,
const blink::mojom::FileChooserParams& params) {
// |base_dir_| should be set for and only for |kUploadFolder| mode.
DCHECK(base_dir_.empty() ==
(params.mode != blink::mojom::FileChooserParams::Mode::kUploadFolder));
// Send the selected files to the renderer process.
std::vector<blink::mojom::FileChooserFileInfoPtr> files;
for (const auto& file : files_) {
auto file_info = blink::mojom::FileChooserFileInfo::NewNativeFile(
blink::mojom::NativeFileInfo::New(file, std::u16string(),
std::vector<std::u16string>()));
files.push_back(std::move(file_info));
}
listener->FileSelected(std::move(files), base_dir_, params.mode);
params_ = params.Clone();
if (callback_)
std::move(callback_).Run();
}
FrameTestNavigationManager::FrameTestNavigationManager(
FrameTreeNodeId filtering_frame_tree_node_id,
WebContents* web_contents,
const GURL& url)
: TestNavigationManager(web_contents, url),
filtering_frame_tree_node_id_(filtering_frame_tree_node_id) {}
bool FrameTestNavigationManager::ShouldMonitorNavigation(
NavigationHandle* handle) {
return TestNavigationManager::ShouldMonitorNavigation(handle) &&
handle->GetFrameTreeNodeId() == filtering_frame_tree_node_id_;
}
UrlCommitObserver::UrlCommitObserver(FrameTreeNode* frame_tree_node,
const GURL& url)
: content::WebContentsObserver(WebContents::FromRenderFrameHost(
frame_tree_node->current_frame_host())),
frame_tree_node_id_(frame_tree_node->frame_tree_node_id()),
url_(url) {}
UrlCommitObserver::~UrlCommitObserver() {}
void UrlCommitObserver::Wait() {
run_loop_.Run();
}
void UrlCommitObserver::DidFinishNavigation(
NavigationHandle* navigation_handle) {
if (navigation_handle->HasCommitted() &&
!navigation_handle->IsErrorPage() &&
navigation_handle->GetURL() == url_ &&
navigation_handle->GetFrameTreeNodeId() == frame_tree_node_id_) {
run_loop_.Quit();
}
}
RenderProcessHostBadIpcMessageWaiter::RenderProcessHostBadIpcMessageWaiter(
RenderProcessHost* render_process_host)
: internal_waiter_(render_process_host,
"Stability.BadMessageTerminated.Content") {}
std::optional<bad_message::BadMessageReason>
RenderProcessHostBadIpcMessageWaiter::Wait() {
std::optional<int> internal_result = internal_waiter_.Wait();
if (!internal_result.has_value())
return std::nullopt;
return static_cast<bad_message::BadMessageReason>(internal_result.value());
}
UnresponsiveRendererObserver::UnresponsiveRendererObserver(
WebContents* web_contents)
: WebContentsObserver(web_contents) {}
UnresponsiveRendererObserver::~UnresponsiveRendererObserver() = default;
RenderProcessHost* UnresponsiveRendererObserver::Wait(base::TimeDelta timeout) {
if (!captured_render_process_host_) {
base::OneShotTimer timer;
timer.Start(FROM_HERE, timeout, run_loop_.QuitClosure());
run_loop_.Run();
timer.Stop();
}
return captured_render_process_host_;
}
void UnresponsiveRendererObserver::OnRendererUnresponsive(
RenderProcessHost* render_process_host) {
captured_render_process_host_ = render_process_host;
run_loop_.Quit();
}
BeforeUnloadBlockingDelegate::BeforeUnloadBlockingDelegate(
WebContentsImpl* web_contents)
: web_contents_(web_contents) {
web_contents_->SetDelegate(this);
}
BeforeUnloadBlockingDelegate::~BeforeUnloadBlockingDelegate() {
if (!callback_.is_null())
std::move(callback_).Run(true, std::u16string());
web_contents_->SetDelegate(nullptr);
}
void BeforeUnloadBlockingDelegate::Wait() {
run_loop_->Run();
run_loop_ = std::make_unique<base::RunLoop>();
}
JavaScriptDialogManager*
BeforeUnloadBlockingDelegate::GetJavaScriptDialogManager(WebContents* source) {
return this;
}
bool BeforeUnloadBlockingDelegate::IsBackForwardCacheSupported(
WebContents& web_contents) {
return true;
}
void BeforeUnloadBlockingDelegate::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) {
NOTREACHED();
}
void BeforeUnloadBlockingDelegate::RunBeforeUnloadDialog(
WebContents* web_contents,
RenderFrameHost* render_frame_host,
bool is_reload,
DialogClosedCallback callback) {
callback_ = std::move(callback);
run_loop_->Quit();
}
bool BeforeUnloadBlockingDelegate::HandleJavaScriptDialog(
WebContents* web_contents,
bool accept,
const std::u16string* prompt_override) {
NOTREACHED();
}
FrameNavigateParamsCapturer::FrameNavigateParamsCapturer(WebContents* contents)
: WebContentsObserver(contents) {}
FrameNavigateParamsCapturer::FrameNavigateParamsCapturer(FrameTreeNode* node)
: WebContentsObserver(
WebContents::FromRenderFrameHost(node->current_frame_host())),
frame_tree_node_id_(node->frame_tree_node_id()) {}
FrameNavigateParamsCapturer::~FrameNavigateParamsCapturer() = default;
void FrameNavigateParamsCapturer::DidFinishNavigation(
NavigationHandle* navigation_handle) {
if (!navigation_handle->HasCommitted() ||
(frame_tree_node_id_.has_value() &&
navigation_handle->GetFrameTreeNodeId() !=
frame_tree_node_id_.value()) ||
navigations_remaining_ == 0) {
return;
}
--navigations_remaining_;
transitions_.push_back(navigation_handle->GetPageTransition());
urls_.push_back(navigation_handle->GetURL());
navigation_types_.push_back(
NavigationRequest::From(navigation_handle)->navigation_type());
is_same_documents_.push_back(navigation_handle->IsSameDocument());
did_replace_entries_.push_back(navigation_handle->DidReplaceEntry());
is_renderer_initiateds_.push_back(navigation_handle->IsRendererInitiated());
has_user_gestures_.push_back(navigation_handle->HasUserGesture());
is_overriding_user_agents_.push_back(
NavigationRequest::From(navigation_handle)->is_overriding_user_agent());
is_error_pages_.push_back(navigation_handle->IsErrorPage());
if (!navigations_remaining_ &&
(!web_contents()->IsLoading() || !wait_for_load_))
loop_.Quit();
}
void FrameNavigateParamsCapturer::Wait() {
loop_.Run();
}
void FrameNavigateParamsCapturer::DidStopLoading() {
if (!navigations_remaining_)
loop_.Quit();
}
RenderFrameHostCreatedObserver::RenderFrameHostCreatedObserver(
WebContents* web_contents)
: WebContentsObserver(web_contents) {}
RenderFrameHostCreatedObserver::RenderFrameHostCreatedObserver(
WebContents* web_contents,
int expected_frame_count)
: WebContentsObserver(web_contents),
expected_frame_count_(expected_frame_count) {}
RenderFrameHostCreatedObserver::RenderFrameHostCreatedObserver(
WebContents* web_contents,
OnRenderFrameHostCreatedCallback on_rfh_created)
: WebContentsObserver(web_contents),
on_rfh_created_(std::move(on_rfh_created)) {}
RenderFrameHostCreatedObserver::~RenderFrameHostCreatedObserver() = default;
RenderFrameHost* RenderFrameHostCreatedObserver::Wait() {
if (frames_created_ < expected_frame_count_)
run_loop_.Run();
return last_rfh_;
}
void RenderFrameHostCreatedObserver::RenderFrameCreated(
RenderFrameHost* render_frame_host) {
frames_created_++;
last_rfh_ = render_frame_host;
if (on_rfh_created_)
on_rfh_created_.Run(render_frame_host);
if (frames_created_ == expected_frame_count_)
run_loop_.Quit();
}
BackForwardCache::DisabledReason RenderFrameHostDisabledForTestingReason() {
static const BackForwardCache::DisabledReason reason =
BackForwardCache::DisabledReason(
BackForwardCache::DisabledSource::kTesting, 0, "disabled for testing",
/*context=*/"", "disabled");
return reason;
}
void DisableBFCacheForRFHForTesting(
content::RenderFrameHost* render_frame_host) {
content::BackForwardCache::DisableForRenderFrameHost(
render_frame_host, RenderFrameHostDisabledForTestingReason());
}
void DisableBFCacheForRFHForTesting(content::GlobalRenderFrameHostId id) {
content::BackForwardCache::DisableForRenderFrameHost(
id, RenderFrameHostDisabledForTestingReason());
}
void UserAgentInjector::DidStartNavigation(
NavigationHandle* navigation_handle) {
web_contents()->SetUserAgentOverride(user_agent_override_, false);
navigation_handle->SetIsOverridingUserAgent(is_overriding_user_agent_);
}
RenderFrameHostImplWrapper::RenderFrameHostImplWrapper(RenderFrameHost* rfh)
: RenderFrameHostWrapper(rfh) {}
RenderFrameHostImpl* RenderFrameHostImplWrapper::get() const {
return static_cast<RenderFrameHostImpl*>(RenderFrameHostWrapper::get());
}
RenderFrameHostImpl& RenderFrameHostImplWrapper::operator*() const {
DCHECK(get());
return *get();
}
RenderFrameHostImpl* RenderFrameHostImplWrapper::operator->() const {
DCHECK(get());
return get();
}
InactiveRenderFrameHostDeletionObserver::
InactiveRenderFrameHostDeletionObserver(WebContents* content)
: WebContentsObserver(content) {}
InactiveRenderFrameHostDeletionObserver::
~InactiveRenderFrameHostDeletionObserver() = default;
void InactiveRenderFrameHostDeletionObserver::Wait() {
// Some RenderFrameHost may remain in the BackForwardCache and or as
// prerendered pages. Trigger deletion for them asynchronously.
static_cast<WebContentsImpl*>(web_contents())
->GetController()
.GetBackForwardCache()
.Flush();
static_cast<WebContentsImpl*>(web_contents())
->GetPrerenderHostRegistry()
->CancelAllHostsForTesting();
for (RenderFrameHost* rfh : CollectAllRenderFrameHosts(web_contents())) {
// Keep track of all currently inactive RenderFrameHosts so that we can wait
// for all of them to be deleted.
if (!rfh->IsActive() && rfh->IsRenderFrameLive())
inactive_rfhs_.insert(rfh);
}
loop_ = std::make_unique<base::RunLoop>();
CheckCondition();
loop_->Run();
}
void InactiveRenderFrameHostDeletionObserver::RenderFrameDeleted(
RenderFrameHost* rfh) {
if (inactive_rfhs_.count(rfh) == 0)
return;
inactive_rfhs_.erase(rfh);
CheckCondition();
}
void InactiveRenderFrameHostDeletionObserver::CheckCondition() {
if (loop_ && inactive_rfhs_.empty())
loop_->Quit();
}
void TestNavigationObserverInternal::OnDidFinishNavigation(
NavigationHandle* navigation_handle) {
last_navigation_type_ =
navigation_handle->HasCommitted()
? static_cast<NavigationRequest*>(navigation_handle)
->navigation_type()
: NAVIGATION_TYPE_UNKNOWN;
TestNavigationObserver::OnDidFinishNavigation(navigation_handle);
}
RenderFrameHostImpl* DescendantRenderFrameHostAtInternal(
RenderFrameHostImpl* rfh,
std::string path,
std::vector<size_t>& descendant_indices) {
if (descendant_indices.size() == 0)
return rfh;
size_t index = descendant_indices[0];
descendant_indices.erase(descendant_indices.begin());
CHECK_LT(index, rfh->child_count()) << path;
FrameTreeNode* node = rfh->child_at(index);
path = base::StringPrintf("%s[%zu]", path.c_str(), index);
return DescendantRenderFrameHostAtInternal(node->current_frame_host(), path,
descendant_indices);
}
RenderFrameHostImpl* DescendantRenderFrameHostImplAt(
const ToRenderFrameHost& adapter,
std::vector<size_t> descendant_indices) {
return DescendantRenderFrameHostAtInternal(
static_cast<RenderFrameHostImpl*>(adapter.render_frame_host()), "rfh",
descendant_indices);
}
EffectiveURLContentBrowserTestContentBrowserClient::
EffectiveURLContentBrowserTestContentBrowserClient(
bool requires_dedicated_process)
: helper_(requires_dedicated_process) {}
EffectiveURLContentBrowserTestContentBrowserClient::
EffectiveURLContentBrowserTestContentBrowserClient(
const GURL& url_to_modify,
const GURL& url_to_return,
bool requires_dedicated_process)
: helper_(requires_dedicated_process) {
AddTranslation(url_to_modify, url_to_return);
}
EffectiveURLContentBrowserTestContentBrowserClient::
~EffectiveURLContentBrowserTestContentBrowserClient() = default;
void EffectiveURLContentBrowserTestContentBrowserClient::AddTranslation(
const GURL& url_to_modify,
const GURL& url_to_return) {
helper_.AddTranslation(url_to_modify, url_to_return);
}
std::optional<GURL>
EffectiveURLContentBrowserTestContentBrowserClient::GetEffectiveURL(
BrowserContext* browser_context,
const GURL& url) {
return helper_.GetEffectiveURL(url);
}
bool EffectiveURLContentBrowserTestContentBrowserClient::
DoesSiteRequireDedicatedProcess(BrowserContext* browser_context,
const GURL& effective_site_url) {
return helper_.DoesSiteRequireDedicatedProcess(browser_context,
effective_site_url);
}
CustomStoragePartitionBrowserClient::CustomStoragePartitionBrowserClient(
const GURL& site_to_isolate)
: site_to_isolate_(site_to_isolate) {}
StoragePartitionConfig
CustomStoragePartitionBrowserClient::GetStoragePartitionConfigForSite(
BrowserContext* browser_context,
const GURL& site) {
// Override for |site_to_isolate_|.
if (site == site_to_isolate_) {
return StoragePartitionConfig::Create(
browser_context, "blah_isolated_storage", "blah_isolated_storage",
false /* in_memory */);
}
return StoragePartitionConfig::CreateDefault(browser_context);
}
CommitNavigationPauser::CommitNavigationPauser(RenderFrameHostImpl* rfh) {
rfh->SetCommitCallbackInterceptorForTesting(this);
}
CommitNavigationPauser::~CommitNavigationPauser() = default;
void CommitNavigationPauser::WaitForCommitAndPause() {
loop_.Run();
}
void CommitNavigationPauser::ResumePausedCommit() {
// The caller is responsible for ensuring the paused request is still alive
// and not discarded.
DCHECK(paused_request_);
paused_request_->GetRenderFrameHost()->DidCommitNavigation(
paused_request_.get(), std::move(paused_params_),
std::move(paused_interface_params_));
}
bool CommitNavigationPauser::WillProcessDidCommitNavigation(
NavigationRequest* request,
mojom::DidCommitProvisionalLoadParamsPtr* params,
mojom::DidCommitProvisionalLoadInterfaceParamsPtr* interface_params) {
request->GetRenderFrameHost()->SetCommitCallbackInterceptorForTesting(
nullptr);
paused_request_ = request->GetWeakPtr();
paused_params_ = std::move(*params);
paused_interface_params_ = std::move(*interface_params);
loop_.Quit();
// Ignore the commit message.
return false;
}
namespace {
// Helper to return a 200 OK non-cacheable response for a first request, and
// redirect the second request to the URL indicated in the query param.
std::unique_ptr<net::test_server::HttpResponse>
RedirectToTargetOnSecondNavigation(
unsigned int& navigation_counter,
const net::test_server::HttpRequest& request) {
++navigation_counter;
if (navigation_counter == 1) {
auto http_response =
std::make_unique<net::test_server::BasicHttpResponse>();
http_response->set_code(net::HttpStatusCode::HTTP_OK);
http_response->AddCustomHeader("Cache-Control",
"no-store, must-revalidate");
return http_response;
}
std::string url_from_query =
base::UnescapeBinaryURLComponent(request.GetURL().query_piece());
auto http_response = std::make_unique<net::test_server::BasicHttpResponse>();
http_response->set_code(net::HttpStatusCode::HTTP_FOUND);
http_response->AddCustomHeader("Location", url_from_query);
return http_response;
}
} // namespace
void AddRedirectOnSecondNavigationHandler(net::EmbeddedTestServer* server) {
unsigned int navigation_counter = 0;
server->RegisterDefaultHandler(base::BindRepeating(
&net::test_server::HandlePrefixedRequest,
"/redirect-on-second-navigation",
base::BindRepeating(&RedirectToTargetOnSecondNavigation,
base::OwnedRef(navigation_counter))));
}
LoadingStartObserver::LoadingStartObserver(WebContents* web_contents,
Callback callback)
: WebContentsObserver(web_contents), callback_(std::move(callback)) {}
LoadingStartObserver::~LoadingStartObserver() = default;
void LoadingStartObserver::DidStartLoading() {
callback_.Run();
}
LoadingStopObserver::LoadingStopObserver(WebContents* web_contents,
Callback callback)
: WebContentsObserver(web_contents), callback_(std::move(callback)) {}
LoadingStopObserver::~LoadingStopObserver() = default;
void LoadingStopObserver::DidStopLoading() {
callback_.Run();
}
LoadFinishObserver::LoadFinishObserver(WebContents* web_contents,
Callback callback)
: WebContentsObserver(web_contents), callback_(std::move(callback)) {}
LoadFinishObserver::~LoadFinishObserver() = default;
void LoadFinishObserver::DidFinishLoad(RenderFrameHost* render_frame_host,
const GURL& validated_url) {
callback_.Run(render_frame_host, validated_url);
}
} // namespace content