blob: bca3f67e65a46ca12753f17a471a900e2fee1aed [file] [log] [blame]
// Copyright 2013 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/browser/renderer_host/navigation_entry_impl.h"
#include <stddef.h>
#include <map>
#include <string>
#include <utility>
#include "base/containers/queue.h"
#include "base/files/file_path.h"
#include "base/i18n/rtl.h"
#include "base/memory/ptr_util.h"
#include "base/ranges/algorithm.h"
#include "base/strings/string_util.h"
#include "base/strings/utf_string_conversions.h"
#include "build/build_config.h"
#include "components/url_formatter/url_formatter.h"
#include "content/browser/child_process_security_policy_impl.h"
#include "content/browser/renderer_host/frame_tree_node.h"
#include "content/browser/renderer_host/navigation_controller_impl.h"
#include "content/browser/renderer_host/navigation_entry_restore_context_impl.h"
#include "content/browser/renderer_host/navigation_request.h"
#include "content/common/content_constants_internal.h"
#include "content/public/browser/reload_type.h"
#include "content/public/common/content_constants.h"
#include "content/public/common/url_constants.h"
#include "services/metrics/public/cpp/ukm_source_id.h"
#include "services/network/public/mojom/fetch_api.mojom.h"
#include "third_party/blink/public/common/navigation/navigation_params.h"
#include "third_party/blink/public/common/page_state/page_state_serialization.h"
#include "third_party/blink/public/mojom/frame/frame.mojom.h"
#include "third_party/blink/public/mojom/navigation/navigation_params.mojom.h"
#include "third_party/blink/public/mojom/navigation/prefetched_signed_exchange_info.mojom.h"
#include "third_party/blink/public/mojom/runtime_feature_state/runtime_feature.mojom.h"
#include "ui/gfx/text_elider.h"
#if BUILDFLAG(IS_ANDROID)
#include "base/android/content_uri_utils.h"
#endif
using base::UTF16ToUTF8;
namespace content {
namespace {
// Use this to get a new unique ID for a NavigationEntry during construction.
// The returned ID is guaranteed to be nonzero (which is the "no ID" indicator).
int CreateUniqueEntryID() {
static int unique_id_counter = 0;
return ++unique_id_counter;
}
void RecursivelyGenerateFrameEntries(
NavigationEntryRestoreContextImpl* context,
const blink::ExplodedFrameState& state,
const std::vector<absl::optional<std::u16string>>& referenced_files,
NavigationEntryImpl::TreeNode* node) {
DCHECK(context);
// Set a single-frame PageState on the entry.
blink::ExplodedPageState page_state;
// Copy the incoming PageState's list of referenced files into the main
// frame's PageState. (We don't pass the list to subframes below.)
// TODO(creis): Grant access to this list for each process that renders
// this page, even for OOPIFs. Eventually keep track of a verified list of
// files per frame, so that we only grant access to processes that need it.
if (referenced_files.size() > 0)
page_state.referenced_files = referenced_files;
page_state.top = state;
std::string data;
blink::EncodePageState(page_state, &data);
DCHECK(!data.empty()) << "Shouldn't generate an empty PageState.";
// Attempt to find an existing FrameNavigationEntry from `context`, only if it
// matches by ISN, URL, and target. Note that it is possible for other values
// to still diverge between `entry` and `state` when those match (see
// https://crbug.com/1354634), but these values are considered sufficient to
// treat the FrameNavigationEntry as shared in future sessions.
GURL state_url(state.url_string.value_or(std::u16string()));
scoped_refptr<FrameNavigationEntry> entry = context->GetFrameNavigationEntry(
state.item_sequence_number,
state.target ? base::UTF16ToUTF8(*state.target) : "", state_url);
if (!entry) {
absl::optional<GURL> initiator_base_url;
if (blink::features::IsNewBaseUrlInheritanceBehaviorEnabled() &&
state.initiator_base_url_string) {
GURL initiator_base_url_from_state =
GURL(UTF16ToUTF8(state.initiator_base_url_string.value()));
// If `state.initiator_base_url_string` has a value, it should be
// non-empty. But it's easiest and safest to just check, as opposed to
// using a CHECK.
if (!initiator_base_url_from_state.is_empty()) {
initiator_base_url = initiator_base_url_from_state;
}
}
entry = base::MakeRefCounted<FrameNavigationEntry>(
UTF16ToUTF8(state.target.value_or(std::u16string())),
state.item_sequence_number, state.document_sequence_number,
UTF16ToUTF8(state.navigation_api_key.value_or(std::u16string())),
nullptr, nullptr, state_url,
// TODO(nasko): Supply valid origin once the value is persisted across
// session restore.
absl::nullopt /* origin */,
Referrer(GURL(state.referrer.value_or(std::u16string())),
state.referrer_policy),
state.initiator_origin, initiator_base_url, std::vector<GURL>(),
blink::PageState::CreateFromEncodedData(data), "GET", -1,
nullptr /* blob_url_loader_factory */,
// TODO(https://crbug.com/1140393): We should restore the policy
// container.
nullptr /* policy_container_policies */,
state.protect_url_in_navigation_api);
context->AddFrameNavigationEntry(entry.get());
}
node->frame_entry = std::move(entry);
// Don't pass the file list to subframes, since that would result in multiple
// copies of it ending up in the combined list in GetPageState (via
// RecursivelyGenerateFrameState).
std::vector<absl::optional<std::u16string>> empty_file_list;
for (const blink::ExplodedFrameState& child_state : state.children) {
node->children.push_back(
std::make_unique<NavigationEntryImpl::TreeNode>(node, nullptr));
RecursivelyGenerateFrameEntries(context, child_state, empty_file_list,
node->children.back().get());
}
}
absl::optional<std::u16string> UrlToOptionalString16(const GURL& url) {
if (!url.is_valid())
return absl::nullopt;
return base::UTF8ToUTF16(url.spec());
}
void RecursivelyGenerateFrameState(
NavigationEntryImpl::TreeNode* node,
blink::ExplodedFrameState* state,
std::vector<absl::optional<std::u16string>>* referenced_files) {
// The FrameNavigationEntry's PageState contains just the ExplodedFrameState
// for that particular frame.
blink::ExplodedPageState exploded_page_state;
if (!blink::DecodePageState(node->frame_entry->page_state().ToEncodedData(),
&exploded_page_state)) {
NOTREACHED();
return;
}
blink::ExplodedFrameState frame_state = exploded_page_state.top;
// Copy the FrameNavigationEntry's frame state into the destination state.
*state = frame_state;
// Some data is stored *both* in 1) PageState/ExplodedFrameState and 2)
// FrameNavigationEntry. We want to treat FrameNavigationEntry as the
// authoritative source of the data, so we clobber the ExplodedFrameState with
// the data taken from FrameNavigationEntry.
//
// The following ExplodedFrameState fields do not have an equivalent
// FrameNavigationEntry field:
// - target
// - state_object
// - document_state
// - scroll_restoration_type
// - did_save_scroll_or_scale_state
// - visual_viewport_scroll_offset
// - scroll_offset
// - page_scale_factor
// - http_body (FrameNavigationEntry::GetPostData extracts the body from
// the ExplodedFrameState)
// - scroll_anchor_selector
// - scroll_anchor_offset
// - scroll_anchor_simhash
state->url_string = UrlToOptionalString16(node->frame_entry->url());
state->referrer = UrlToOptionalString16(node->frame_entry->referrer().url);
state->referrer_policy = node->frame_entry->referrer().policy;
state->item_sequence_number = node->frame_entry->item_sequence_number();
state->document_sequence_number =
node->frame_entry->document_sequence_number();
state->initiator_origin = node->frame_entry->initiator_origin();
// Note: If UrlToOptionalString16 receives an empty GURL, it returns nullopt.
state->initiator_base_url_string = UrlToOptionalString16(
node->frame_entry->initiator_base_url().value_or(GURL::EmptyGURL()));
// protect_url_in_navigation_api is not generated by the renderer, it is only
// stored on FrameNavigationEntry.
state->protect_url_in_navigation_api =
node->frame_entry->protect_url_in_navigation_api();
// Copy the frame's files into the PageState's |referenced_files|.
referenced_files->reserve(referenced_files->size() +
exploded_page_state.referenced_files.size());
for (auto& file : exploded_page_state.referenced_files)
referenced_files->emplace_back(file);
state->children.resize(node->children.size());
for (size_t i = 0; i < node->children.size(); ++i) {
RecursivelyGenerateFrameState(node->children[i].get(), &state->children[i],
referenced_files);
}
}
// Walk the ancestor chain for both the |frame_tree_node| and the |node|.
// Comparing the inputs directly is not performed, as this method assumes they
// already match each other. Returns false if a mismatch in unique name or
// ancestor chain is detected, otherwise true.
bool InSameTreePosition(FrameTreeNode* frame_tree_node,
NavigationEntryImpl::TreeNode* node) {
FrameTreeNode* ftn = FrameTreeNode::From(frame_tree_node->parent());
NavigationEntryImpl::TreeNode* current_node = node->parent;
while (ftn && current_node) {
if (!current_node->MatchesFrame(ftn))
return false;
if ((!current_node->parent && ftn->parent()) ||
(current_node->parent && !ftn->parent())) {
return false;
}
ftn = FrameTreeNode::From(ftn->parent());
current_node = current_node->parent;
}
return true;
}
void RegisterOriginsRecursive(NavigationEntryImpl::TreeNode* node,
const url::Origin& origin) {
if (node->frame_entry->committed_origin().has_value()) {
const url::Origin node_origin =
node->frame_entry->committed_origin().value();
SiteInstanceImpl* site_instance = node->frame_entry->site_instance();
if (site_instance && origin == node_origin)
site_instance->RegisterAsDefaultOriginIsolation(node_origin);
}
for (auto& child : node->children)
RegisterOriginsRecursive(child.get(), origin);
}
} // namespace
void NavigationEntryImpl::RegisterExistingOriginAsHavingDefaultIsolation(
const url::Origin& origin) {
return RegisterOriginsRecursive(root_node(), origin);
}
NavigationEntryImpl::TreeNode::TreeNode(
TreeNode* parent,
scoped_refptr<FrameNavigationEntry> frame_entry)
: parent(parent), frame_entry(std::move(frame_entry)) {}
NavigationEntryImpl::TreeNode::~TreeNode() {}
bool NavigationEntryImpl::TreeNode::MatchesFrame(
FrameTreeNode* frame_tree_node) const {
// The root node is for the main frame whether the unique name matches or not.
if (!parent)
return frame_tree_node->IsMainFrame();
// Otherwise check the unique name for subframes.
return !frame_tree_node->IsMainFrame() &&
frame_tree_node->unique_name() == frame_entry->frame_unique_name();
}
std::unique_ptr<NavigationEntryImpl::TreeNode>
NavigationEntryImpl::TreeNode::CloneAndReplace(
scoped_refptr<FrameNavigationEntry> frame_navigation_entry,
bool clone_children_of_target,
FrameTreeNode* target_frame_tree_node,
FrameTreeNode* current_frame_tree_node,
TreeNode* parent_node,
NavigationEntryRestoreContextImpl* restore_context,
ClonePolicy clone_policy) const {
// |restore_context| should only ever be used when doing a deep clone, and
// when there is no target.
if (restore_context) {
DCHECK(!frame_navigation_entry && !target_frame_tree_node &&
clone_policy == ClonePolicy::kCloneFrameEntries);
}
// Clone this TreeNode, possibly replacing its FrameNavigationEntry.
bool is_target_frame =
target_frame_tree_node && MatchesFrame(target_frame_tree_node);
scoped_refptr<FrameNavigationEntry> new_entry;
if (is_target_frame) {
new_entry = frame_navigation_entry;
} else if (clone_policy == ClonePolicy::kShareFrameEntries) {
new_entry = frame_entry;
} else {
if (restore_context) {
// If |restore_context| is given and already has a FrameNavigationEntry
// for the given item sequence number and URL, share that
// FrameNavigationEntry rather than creating a duplicate.
new_entry = restore_context->GetFrameNavigationEntry(
frame_entry->item_sequence_number(), frame_entry->frame_unique_name(),
frame_entry->url());
}
if (!new_entry) {
new_entry = frame_entry->Clone();
if (restore_context)
restore_context->AddFrameNavigationEntry(new_entry.get());
}
}
auto copy = std::make_unique<NavigationEntryImpl::TreeNode>(
parent_node, std::move(new_entry));
// Recursively clone the children if needed.
if (!is_target_frame || clone_children_of_target) {
for (size_t i = 0; i < children.size(); i++) {
const auto& child = children[i];
// Don't check whether it's still in the tree if |current_frame_tree_node|
// is null.
if (!current_frame_tree_node) {
copy->children.push_back(child->CloneAndReplace(
frame_navigation_entry, clone_children_of_target,
target_frame_tree_node, nullptr, copy.get(), restore_context,
clone_policy));
continue;
}
// Otherwise, make sure the frame is still in the tree before cloning it.
// This is O(N^2) in the worst case because we need to look up each frame
// (and since we want to avoid a map of unique names, which can be very
// long). To partly mitigate this, we add an optimization for the common
// case that the two child lists are the same length and are likely in the
// same order: we pick the starting offset of the inner loop to get O(N).
size_t ftn_child_count = current_frame_tree_node->child_count();
for (size_t j = 0; j < ftn_child_count; j++) {
size_t index = j;
// If the two lists of children are the same length, start looking at
// the same index as |child|.
if (children.size() == ftn_child_count)
index = (i + j) % ftn_child_count;
if (current_frame_tree_node->child_at(index)->unique_name() ==
child->frame_entry->frame_unique_name()) {
// Found |child| in the tree. Clone it and break out of inner loop.
copy->children.push_back(child->CloneAndReplace(
frame_navigation_entry, clone_children_of_target,
target_frame_tree_node, current_frame_tree_node->child_at(index),
copy.get(), restore_context, clone_policy));
break;
}
}
}
}
return copy;
}
std::unique_ptr<NavigationEntry> NavigationEntry::Create() {
return std::make_unique<NavigationEntryImpl>();
}
NavigationEntryImpl* NavigationEntryImpl::FromNavigationEntry(
NavigationEntry* entry) {
return static_cast<NavigationEntryImpl*>(entry);
}
const NavigationEntryImpl* NavigationEntryImpl::FromNavigationEntry(
const NavigationEntry* entry) {
return static_cast<const NavigationEntryImpl*>(entry);
}
std::unique_ptr<NavigationEntryImpl> NavigationEntryImpl::FromNavigationEntry(
std::unique_ptr<NavigationEntry> entry) {
return base::WrapUnique(FromNavigationEntry(entry.release()));
}
NavigationEntryImpl::NavigationEntryImpl()
: NavigationEntryImpl(nullptr,
GURL(),
Referrer(),
/* initiator_origin= */ absl::nullopt,
/* initiator_base_url= */ absl::nullopt,
std::u16string(),
ui::PAGE_TRANSITION_LINK,
false,
nullptr,
/* is_initial_entry = */ false) {}
NavigationEntryImpl::NavigationEntryImpl(
scoped_refptr<SiteInstanceImpl> instance,
const GURL& url,
const Referrer& referrer,
const absl::optional<url::Origin>& initiator_origin,
const absl::optional<GURL>& initiator_base_url,
const std::u16string& title,
ui::PageTransition transition_type,
bool is_renderer_initiated,
scoped_refptr<network::SharedURLLoaderFactory> blob_url_loader_factory,
bool is_initial_entry)
: frame_tree_(std::make_unique<TreeNode>(
nullptr,
base::MakeRefCounted<FrameNavigationEntry>(
"",
-1,
-1,
"",
std::move(instance),
nullptr,
url,
absl::nullopt /* origin */,
referrer,
initiator_origin,
initiator_base_url,
std::vector<GURL>(),
blink::PageState(),
"GET",
-1,
std::move(blob_url_loader_factory),
nullptr /* policy_container_policies */,
false /* protect_url_in_navigation_api */))),
unique_id_(CreateUniqueEntryID()),
page_type_(PAGE_TYPE_NORMAL),
update_virtual_url_with_url_(false),
title_(title),
transition_type_(transition_type),
restore_type_(RestoreType::kNotRestored),
is_overriding_user_agent_(false),
http_status_code_(0),
is_renderer_initiated_(is_renderer_initiated),
should_clear_history_list_(false),
can_load_local_resources_(false),
frame_tree_node_id_(FrameTreeNode::kFrameTreeNodeInvalidId),
has_user_gesture_(false),
reload_type_(ReloadType::NONE),
started_from_context_menu_(false),
ssl_error_(false),
should_skip_on_back_forward_ui_(false),
initial_navigation_entry_state_(
is_initial_entry
? InitialNavigationEntryState::kInitialNotForSynchronousAboutBlank
: InitialNavigationEntryState::kNonInitial) {}
NavigationEntryImpl::~NavigationEntryImpl() {}
int NavigationEntryImpl::GetUniqueID() {
return unique_id_;
}
PageType NavigationEntryImpl::GetPageType() {
return page_type_;
}
void NavigationEntryImpl::SetURL(const GURL& url) {
frame_tree_->frame_entry->set_url(url);
cached_display_title_.clear();
}
const GURL& NavigationEntryImpl::GetURL() {
return frame_tree_->frame_entry->url();
}
void NavigationEntryImpl::SetBaseURLForDataURL(const GURL& url) {
base_url_for_data_url_ = url;
}
const GURL& NavigationEntryImpl::GetBaseURLForDataURL() {
return base_url_for_data_url_;
}
#if BUILDFLAG(IS_ANDROID)
void NavigationEntryImpl::SetDataURLAsString(
scoped_refptr<base::RefCountedString> data_url) {
if (data_url) {
// A quick check that it's actually a data URL.
DCHECK(base::StartsWith(data_url->front_as<char>(), url::kDataScheme,
base::CompareCase::SENSITIVE));
}
data_url_as_string_ = std::move(data_url);
}
const scoped_refptr<const base::RefCountedString>&
NavigationEntryImpl::GetDataURLAsString() {
return data_url_as_string_;
}
#endif
void NavigationEntryImpl::SetReferrer(const Referrer& referrer) {
frame_tree_->frame_entry->set_referrer(referrer);
}
const Referrer& NavigationEntryImpl::GetReferrer() {
return frame_tree_->frame_entry->referrer();
}
void NavigationEntryImpl::SetVirtualURL(const GURL& url) {
virtual_url_ = (url == GetURL()) ? GURL() : url;
cached_display_title_.clear();
}
const GURL& NavigationEntryImpl::GetVirtualURL() {
return virtual_url_.is_empty() ? GetURL() : virtual_url_;
}
void NavigationEntryImpl::SetTitle(const std::u16string& title) {
title_ = title;
cached_display_title_.clear();
}
const std::u16string& NavigationEntryImpl::GetTitle() {
return title_;
}
void NavigationEntryImpl::SetPageState(const blink::PageState& state,
NavigationEntryRestoreContext* context) {
DCHECK(state.IsValid());
DCHECK(context);
// SetPageState should only be called before the NavigationEntry has been
// loaded, such as for restore (when there are no subframe
// FrameNavigationEntries yet). However, some callers expect to call this
// after a Clone but before loading the page. Clone will copy over the
// subframe entries, and we should reset them before setting the state again.
//
// TODO(creis): It would be good to verify that this NavigationEntry hasn't
// been loaded yet in cases that SetPageState is called while subframe
// entries exist, but there's currently no way to check that.
if (!frame_tree_->children.empty())
frame_tree_->children.clear();
// If the PageState can't be parsed, store a clean PageState for the URL
// without recursively creating subframe entries. This ensures that the
// renderer and future sessions will be able to handle the history item, even
// if not all data can be preserved. See https://crbug.com/1196330.
blink::ExplodedPageState exploded_state;
if (!blink::DecodePageState(state.ToEncodedData(), &exploded_state)) {
// Replace frame_entry with a clone to avoid sharing with any other
// NavigationEntries, because the item sequence number will be gone.
frame_tree_->frame_entry = frame_tree_->frame_entry->Clone();
frame_tree_->frame_entry->SetPageState(
blink::PageState::CreateFromURL(GetURL()));
return;
}
RecursivelyGenerateFrameEntries(
static_cast<NavigationEntryRestoreContextImpl*>(context),
exploded_state.top, exploded_state.referenced_files, frame_tree_.get());
}
blink::PageState NavigationEntryImpl::GetPageState() {
// Each FrameNavigationEntry has a frame-specific PageState. We combine these
// into an ExplodedPageState tree and generate a full PageState from it.
blink::ExplodedPageState exploded_state;
RecursivelyGenerateFrameState(frame_tree_.get(), &exploded_state.top,
&exploded_state.referenced_files);
std::string encoded_data;
blink::EncodePageState(exploded_state, &encoded_data);
return blink::PageState::CreateFromEncodedData(encoded_data);
}
const std::u16string& NavigationEntryImpl::GetTitleForDisplay() {
// Most pages have real titles. Don't even bother caching anything if this is
// the case.
if (!title_.empty())
return title_;
// More complicated cases will use the URLs as the title. This result we will
// cache since it's more complicated to compute.
if (!cached_display_title_.empty())
return cached_display_title_;
// Use the virtual URL first if any, and fall back on using the real URL.
std::u16string title;
if (!virtual_url_.is_empty() || !GetURL().is_empty()) {
title = url_formatter::FormatUrl(
virtual_url_.is_empty() ? GetURL() : virtual_url_,
url_formatter::kFormatUrlOmitDefaults |
url_formatter::kFormatUrlOmitTrivialSubdomains |
url_formatter::kFormatUrlOmitHTTPS,
base::UnescapeRule::SPACES, nullptr, nullptr, nullptr);
}
// For file:// URLs use the filename as the title, not the full path.
if (GetURL().SchemeIsFile()) {
// It is necessary to ignore the reference and query parameters or else
// looking for slashes might accidentally return one of those values. See
// https://crbug.com/503003.
std::u16string::size_type refpos = title.find('#');
std::u16string::size_type querypos = title.find('?');
std::u16string::size_type lastpos;
if (refpos == std::u16string::npos)
lastpos = querypos;
else if (querypos == std::u16string::npos)
lastpos = refpos;
else
lastpos = (refpos < querypos) ? refpos : querypos;
std::u16string::size_type slashpos = title.rfind('/', lastpos);
if (slashpos != std::u16string::npos)
title = title.substr(slashpos + 1);
} else if (GetURL().SchemeIs(kChromeUIUntrustedScheme)) {
// For chrome-untrusted:// URLs, leave title blank until the page loads.
title = std::u16string();
} else if (base::i18n::StringContainsStrongRTLChars(title)) {
// Wrap the URL in an LTR embedding for proper handling of RTL characters.
// (RFC 3987 Section 4.1 states that "Bidirectional IRIs MUST be rendered in
// the same way as they would be if they were in a left-to-right
// embedding".)
base::i18n::WrapStringWithLTRFormatting(&title);
}
#if BUILDFLAG(IS_ANDROID)
if (GetURL().SchemeIs(url::kContentScheme)) {
std::u16string file_display_name;
if (base::MaybeGetFileDisplayName(base::FilePath(GetURL().spec()),
&file_display_name)) {
title = file_display_name;
}
}
#endif
gfx::ElideString(title, blink::mojom::kMaxTitleChars, &cached_display_title_);
return cached_display_title_;
}
bool NavigationEntryImpl::IsViewSourceMode() {
return virtual_url_.SchemeIs(kViewSourceScheme);
}
void NavigationEntryImpl::SetTransitionType(
ui::PageTransition transition_type) {
transition_type_ = transition_type;
}
ui::PageTransition NavigationEntryImpl::GetTransitionType() {
return transition_type_;
}
const GURL& NavigationEntryImpl::GetUserTypedURL() {
return user_typed_url_;
}
void NavigationEntryImpl::SetHasPostData(bool has_post_data) {
frame_tree_->frame_entry->set_method(has_post_data ? "POST" : "GET");
}
bool NavigationEntryImpl::GetHasPostData() {
return frame_tree_->frame_entry->method() == "POST";
}
void NavigationEntryImpl::SetPostID(int64_t post_id) {
frame_tree_->frame_entry->set_post_id(post_id);
}
int64_t NavigationEntryImpl::GetPostID() {
return frame_tree_->frame_entry->post_id();
}
void NavigationEntryImpl::SetPostData(
const scoped_refptr<network::ResourceRequestBody>& data) {
post_data_ = static_cast<network::ResourceRequestBody*>(data.get());
}
scoped_refptr<network::ResourceRequestBody> NavigationEntryImpl::GetPostData() {
return post_data_.get();
}
FaviconStatus& NavigationEntryImpl::GetFavicon() {
return favicon_;
}
SSLStatus& NavigationEntryImpl::GetSSL() {
return ssl_;
}
void NavigationEntryImpl::SetOriginalRequestURL(const GURL& original_url) {
original_request_url_ = original_url;
}
const GURL& NavigationEntryImpl::GetOriginalRequestURL() {
return original_request_url_;
}
void NavigationEntryImpl::SetIsOverridingUserAgent(bool override_ua) {
is_overriding_user_agent_ = override_ua;
}
bool NavigationEntryImpl::GetIsOverridingUserAgent() {
return is_overriding_user_agent_;
}
void NavigationEntryImpl::SetTimestamp(base::Time timestamp) {
timestamp_ = timestamp;
}
base::Time NavigationEntryImpl::GetTimestamp() {
return timestamp_;
}
void NavigationEntryImpl::SetHttpStatusCode(int http_status_code) {
http_status_code_ = http_status_code;
}
int NavigationEntryImpl::GetHttpStatusCode() {
return http_status_code_;
}
void NavigationEntryImpl::SetRedirectChain(
const std::vector<GURL>& redirect_chain) {
root_node()->frame_entry->set_redirect_chain(redirect_chain);
}
const std::vector<GURL>& NavigationEntryImpl::GetRedirectChain() {
return root_node()->frame_entry->redirect_chain();
}
const absl::optional<ReplacedNavigationEntryData>&
NavigationEntryImpl::GetReplacedEntryData() {
return replaced_entry_data_;
}
bool NavigationEntryImpl::IsRestored() {
return restore_type_ == RestoreType::kRestored;
}
std::string NavigationEntryImpl::GetExtraHeaders() {
return extra_headers_;
}
void NavigationEntryImpl::AddExtraHeaders(
const std::string& more_extra_headers) {
DCHECK(!more_extra_headers.empty());
if (!extra_headers_.empty())
extra_headers_ += "\r\n";
extra_headers_ += more_extra_headers;
}
int64_t NavigationEntryImpl::GetMainFrameDocumentSequenceNumber() {
return frame_tree_->frame_entry->document_sequence_number();
}
void NavigationEntryImpl::SetCanLoadLocalResources(bool allow) {
can_load_local_resources_ = allow;
}
bool NavigationEntryImpl::GetCanLoadLocalResources() {
return can_load_local_resources_;
}
bool NavigationEntryImpl::IsInitialEntry() {
return initial_navigation_entry_state_ !=
InitialNavigationEntryState::kNonInitial;
}
std::unique_ptr<NavigationEntryImpl> NavigationEntryImpl::Clone() const {
std::unique_ptr<NavigationEntryImpl> entry =
CloneAndReplaceInternal(nullptr, false, nullptr, nullptr, nullptr,
ClonePolicy::kShareFrameEntries);
// This function is only used for creating pending entries, which should not
// carry the "initial" status.
entry->set_initial_navigation_entry_state(
InitialNavigationEntryState::kNonInitial);
return entry;
}
std::unique_ptr<NavigationEntryImpl> NavigationEntryImpl::CloneWithoutSharing(
NavigationEntryRestoreContextImpl* restore_context) const {
DCHECK(restore_context);
return CloneAndReplaceInternal(nullptr, false, nullptr, nullptr,
restore_context,
ClonePolicy::kCloneFrameEntries);
}
std::unique_ptr<NavigationEntryImpl> NavigationEntryImpl::CloneAndReplace(
scoped_refptr<FrameNavigationEntry> frame_navigation_entry,
bool clone_children_of_target,
FrameTreeNode* target_frame_tree_node,
FrameTreeNode* root_frame_tree_node) const {
std::unique_ptr<NavigationEntryImpl> entry = CloneAndReplaceInternal(
frame_navigation_entry, clone_children_of_target, target_frame_tree_node,
root_frame_tree_node, nullptr, ClonePolicy::kShareFrameEntries);
return entry;
}
std::unique_ptr<NavigationEntryImpl>
NavigationEntryImpl::CloneAndReplaceInternal(
scoped_refptr<FrameNavigationEntry> frame_navigation_entry,
bool clone_children_of_target,
FrameTreeNode* target_frame_tree_node,
FrameTreeNode* root_frame_tree_node,
NavigationEntryRestoreContextImpl* restore_context,
ClonePolicy clone_policy) const {
auto copy = std::make_unique<NavigationEntryImpl>();
copy->frame_tree_ = frame_tree_->CloneAndReplace(
std::move(frame_navigation_entry), clone_children_of_target,
target_frame_tree_node, root_frame_tree_node, nullptr, restore_context,
clone_policy);
// Copy most state over, unless cleared in ResetForCommit.
// Don't copy unique_id_, otherwise it won't be unique.
copy->page_type_ = page_type_;
copy->virtual_url_ = virtual_url_;
copy->update_virtual_url_with_url_ = update_virtual_url_with_url_;
copy->title_ = title_;
copy->favicon_ = favicon_;
copy->ssl_ = ssl_;
copy->transition_type_ = transition_type_;
copy->user_typed_url_ = user_typed_url_;
copy->restore_type_ = restore_type_;
copy->original_request_url_ = original_request_url_;
copy->is_overriding_user_agent_ = is_overriding_user_agent_;
copy->timestamp_ = timestamp_;
copy->http_status_code_ = http_status_code_;
// ResetForCommit: post_data_
copy->extra_headers_ = extra_headers_;
copy->base_url_for_data_url_ = base_url_for_data_url_;
#if BUILDFLAG(IS_ANDROID)
copy->data_url_as_string_ = data_url_as_string_;
#endif
// ResetForCommit: is_renderer_initiated_
copy->cached_display_title_ = cached_display_title_;
// ResetForCommit: should_replace_entry_
// ResetForCommit: should_clear_history_list_
// ResetForCommit: frame_tree_node_id_
copy->has_user_gesture_ = has_user_gesture_;
// ResetForCommit: reload_type_
copy->CloneDataFrom(*this);
copy->replaced_entry_data_ = replaced_entry_data_;
copy->should_skip_on_back_forward_ui_ = should_skip_on_back_forward_ui_;
copy->initial_navigation_entry_state_ = initial_navigation_entry_state_;
return copy;
}
blink::mojom::CommonNavigationParamsPtr
NavigationEntryImpl::ConstructCommonNavigationParams(
const FrameNavigationEntry& frame_entry,
const scoped_refptr<network::ResourceRequestBody>& post_body,
const GURL& dest_url,
blink::mojom::ReferrerPtr dest_referrer,
blink::mojom::NavigationType navigation_type,
base::TimeTicks navigation_start,
base::TimeTicks input_start) {
// `base_url_for_data_url` is saved in NavigationEntry but should only be used
// by main frames, because loadData* navigations can only happen on the main
// frame.
bool is_for_main_frame = (root_node()->frame_entry == &frame_entry);
// Even if the frame_entry was originally about:blank or about:srcdoc and had
// an initiator_base_url, there's no guarantee here that `dest_url` will be
// either about:blank or about:srcdoc. In that case make sure we don't
// propagate initiator_base_url.
// TODO(creis): Look into how this case can be avoided so that dest_url
// doesn't diverge from other parameters in frame_entry.
const absl::optional<GURL> initiator_base_url =
(dest_url.IsAboutBlank() || dest_url.IsAboutSrcdoc())
? frame_entry.initiator_base_url()
: absl::nullopt;
return blink::mojom::CommonNavigationParams::New(
dest_url, frame_entry.initiator_origin(), initiator_base_url,
std::move(dest_referrer), GetTransitionType(), navigation_type,
blink::NavigationDownloadPolicy(),
// It's okay to pass false for `should_replace_entry` because we never
// replace an entry on session history / reload / restore navigation. New
// navigation that may use replacement create their CommonNavigationParams
// via NavigationRequest, for example, instead of via NavigationEntry.
false /* should_replace_entry */,
is_for_main_frame ? GetBaseURLForDataURL() : GURL(), navigation_start,
frame_entry.method(), post_body ? post_body : post_data_,
network::mojom::SourceLocation::New(), has_started_from_context_menu(),
has_user_gesture(), false /* has_text_fragment_token */,
network::mojom::CSPDisposition::CHECK, std::vector<int>(), std::string(),
false /* is_history_navigation_in_new_child_frame */, input_start,
network::mojom::RequestDestination::kEmpty);
}
blink::mojom::CommitNavigationParamsPtr
NavigationEntryImpl::ConstructCommitNavigationParams(
const FrameNavigationEntry& frame_entry,
const GURL& original_url,
const std::string& original_method,
const base::flat_map<std::string, bool>& subframe_unique_names,
bool intended_as_new_entry,
int pending_history_list_offset,
int current_history_list_offset,
int current_history_list_length,
const blink::FramePolicy& frame_policy,
bool ancestor_or_self_has_cspee,
blink::mojom::SystemEntropy system_entropy_at_navigation_start,
absl::optional<blink::scheduler::TaskAttributionId>
soft_navigation_heuristics_task_id) {
// Set the redirect chain to the navigation's redirects, unless returning to a
// completed navigation (whose previous redirects don't apply).
// Note that this is actually does not work as intended right now because
// we're only copying the redirect URLs into the new CommitNavigationParams,
// keeping redirect_response and redirect_infos as empty.
// TODO(https://crbug.com/1171225): Save redirect_response & redirect_infos in
// FNE and copy them too?
std::vector<GURL> redirects;
if (ui::PageTransitionIsNewNavigation(GetTransitionType())) {
redirects = frame_entry.redirect_chain();
}
int pending_offset_to_send = pending_history_list_offset;
int current_offset_to_send = current_history_list_offset;
int current_length_to_send = current_history_list_length;
if (should_clear_history_list()) {
// Set the history list related parameters to the same values a
// NavigationController would return before its first navigation. This will
// fully clear the RenderView's view of the session history.
pending_offset_to_send = -1;
current_offset_to_send = -1;
current_length_to_send = 0;
}
blink::mojom::CommitNavigationParamsPtr commit_params =
blink::mojom::CommitNavigationParams::New(
absl::nullopt,
// The correct storage key and session storage key will be computed
// before committing the navigation.
blink::StorageKey(), blink::StorageKey(), GetIsOverridingUserAgent(),
redirects, std::vector<network::mojom::URLResponseHeadPtr>(),
std::vector<net::RedirectInfo>(), std::string(), original_url,
original_method, GetCanLoadLocalResources(),
frame_entry.page_state().ToEncodedData(), GetUniqueID(),
subframe_unique_names, intended_as_new_entry, pending_offset_to_send,
current_offset_to_send, current_length_to_send, false,
IsViewSourceMode(), should_clear_history_list(),
blink::mojom::NavigationTiming::New(),
blink::mojom::WasActivatedOption::kUnknown,
base::UnguessableToken::Create(),
std::vector<blink::mojom::PrefetchedSignedExchangeInfoPtr>(),
#if BUILDFLAG(IS_ANDROID)
std::string(),
#endif
false /* is_browser_initiated */,
ukm::kInvalidSourceId /* document_ukm_source_id */, frame_policy,
std::vector<std::string>() /* force_enabled_origin_trials */,
false /* origin_agent_cluster */,
true /* origin_agent_cluster_left_as_default */,
std::vector<
network::mojom::WebClientHintsType>() /* enabled_client_hints */,
false /* is_cross_browsing_instance */, nullptr /* old_page_info */,
-1 /* http_response_code */,
blink::mojom::NavigationApiHistoryEntryArrays::New(),
std::vector<GURL>() /* early_hints_preloaded_resources */,
// This timestamp will be populated when the commit IPC is sent.
base::TimeTicks() /* commit_sent */, std::string() /* srcdoc_value */,
false /* should_load_data_url */, ancestor_or_self_has_cspee,
std::string() /* reduced_accept_language */,
/*navigation_delivery_type=*/
network::mojom::NavigationDeliveryType::kDefault,
/*view_transition_state=*/absl::nullopt,
soft_navigation_heuristics_task_id,
/*modified_runtime_features=*/
base::flat_map<::blink::mojom::RuntimeFeature, bool>(),
/*fenced_frame_properties=*/absl::nullopt,
/*not_restored_reasons=*/nullptr,
/*load_with_storage_access=*/false,
/*browsing_context_group_info=*/absl::nullopt,
/*lcpp_hint=*/nullptr, blink::CreateDefaultRendererContentSettings(),
/*cookie_deprecation_label=*/absl::nullopt);
#if BUILDFLAG(IS_ANDROID)
// `data_url_as_string` is saved in NavigationEntry but should only be used by
// main frames, because loadData* navigations can only happen on the main
// frame.
bool is_for_main_frame = (root_node()->frame_entry == &frame_entry);
if (is_for_main_frame &&
NavigationControllerImpl::ValidateDataURLAsString(GetDataURLAsString())) {
commit_params->data_url_as_string = GetDataURLAsString()->data();
}
#endif
commit_params->navigation_timing->system_entropy_at_navigation_start =
system_entropy_at_navigation_start;
return commit_params;
}
void NavigationEntryImpl::ResetForCommit(FrameNavigationEntry* frame_entry) {
// Any state that only matters when a navigation entry is pending should be
// cleared here.
// TODO(creis): This state should be moved to NavigationRequest.
SetPostData(nullptr);
set_is_renderer_initiated(false);
set_should_clear_history_list(false);
set_frame_tree_node_id(FrameTreeNode::kFrameTreeNodeInvalidId);
set_reload_type(ReloadType::NONE);
if (frame_entry) {
frame_entry->set_source_site_instance(nullptr);
frame_entry->set_blob_url_loader_factory(nullptr);
}
}
NavigationEntryImpl::TreeNode* NavigationEntryImpl::GetTreeNode(
FrameTreeNode* frame_tree_node) const {
NavigationEntryImpl::TreeNode* node = nullptr;
base::queue<NavigationEntryImpl::TreeNode*> work_queue;
work_queue.push(root_node());
while (!work_queue.empty()) {
node = work_queue.front();
work_queue.pop();
if (node->MatchesFrame(frame_tree_node))
return node;
// Enqueue any children and keep looking.
for (const auto& child : node->children)
work_queue.push(child.get());
}
return nullptr;
}
void NavigationEntryImpl::AddOrUpdateFrameEntry(
FrameTreeNode* frame_tree_node,
UpdatePolicy update_policy,
int64_t item_sequence_number,
int64_t document_sequence_number,
const std::string& navigation_api_key,
SiteInstanceImpl* site_instance,
scoped_refptr<SiteInstanceImpl> source_site_instance,
const GURL& url,
const absl::optional<url::Origin>& origin,
const Referrer& referrer,
const absl::optional<url::Origin>& initiator_origin,
const absl::optional<GURL>& initiator_base_url,
const std::vector<GURL>& redirect_chain,
const blink::PageState& page_state,
const std::string& method,
int64_t post_id,
scoped_refptr<network::SharedURLLoaderFactory> blob_url_loader_factory,
std::unique_ptr<PolicyContainerPolicies> policy_container_policies) {
bool protect_url_in_navigation_api =
policy_container_policies &&
NavigationControllerImpl::ShouldProtectUrlInNavigationApi(
policy_container_policies->referrer_policy);
// If this is called for the main frame, the FrameNavigationEntry is
// guaranteed to exist, so just update it directly and return.
if (frame_tree_node->IsMainFrame()) {
// If the document of the FrameNavigationEntry is changing, we must clear
// any child FrameNavigationEntries.
if (root_node()->frame_entry->document_sequence_number() !=
document_sequence_number) {
root_node()->children.clear();
if (!url.is_empty()) {
// A cross-document navigation committed in the main frame, so the
// NavigationEntry loses its "initial NavigationEntry" status. Note that
// the initial entry creation path also goes through this function, but
// we know not to remove the status in that case because it uses the
// empty URL.
initial_navigation_entry_state_ =
InitialNavigationEntryState::kNonInitial;
}
}
root_node()->frame_entry->UpdateEntry(
frame_tree_node->unique_name(), item_sequence_number,
document_sequence_number, navigation_api_key, site_instance,
std::move(source_site_instance), url, origin, referrer,
initiator_origin, initiator_base_url, redirect_chain, page_state,
method, post_id, std::move(blob_url_loader_factory),
std::move(policy_container_policies), protect_url_in_navigation_api);
return;
}
// We should already have a TreeNode for the parent node by the time this node
// commits. Find it first.
NavigationEntryImpl::TreeNode* parent_node =
GetTreeNode(FrameTreeNode::From(frame_tree_node->parent()));
if (!parent_node) {
// The renderer should not send a commit for a subframe before its parent.
// TODO(creis): Kill the renderer if we get here.
return;
}
// Now check whether we have a TreeNode for the node itself.
const std::string& unique_name = frame_tree_node->unique_name();
for (const auto& child : parent_node->children) {
if (child->frame_entry->frame_unique_name() == unique_name) {
if (update_policy == UpdatePolicy::kReplace) {
RemoveEntryForFrame(frame_tree_node, false);
break;
}
// If the document of the FrameNavigationEntry is changing, we must clear
// any child FrameNavigationEntries.
if (child->frame_entry->document_sequence_number() !=
document_sequence_number)
child->children.clear();
// Update the existing FrameNavigationEntry (e.g., for replaceState).
child->frame_entry->UpdateEntry(
unique_name, item_sequence_number, document_sequence_number,
navigation_api_key, site_instance, std::move(source_site_instance),
url, origin, referrer, initiator_origin, initiator_base_url,
redirect_chain, page_state, method, post_id,
std::move(blob_url_loader_factory),
std::move(policy_container_policies), protect_url_in_navigation_api);
return;
}
}
// No entry exists yet, so create a new one.
// Unordered list, since we expect to look up entries by frame sequence number
// or unique name.
auto frame_entry = base::MakeRefCounted<FrameNavigationEntry>(
unique_name, item_sequence_number, document_sequence_number,
navigation_api_key, site_instance, std::move(source_site_instance), url,
origin, referrer, initiator_origin, initiator_base_url, redirect_chain,
page_state, method, post_id, std::move(blob_url_loader_factory),
std::move(policy_container_policies), protect_url_in_navigation_api);
parent_node->children.push_back(
std::make_unique<NavigationEntryImpl::TreeNode>(parent_node,
std::move(frame_entry)));
}
FrameNavigationEntry* NavigationEntryImpl::GetFrameEntry(
FrameTreeNode* frame_tree_node) const {
NavigationEntryImpl::TreeNode* tree_node = GetTreeNode(frame_tree_node);
return tree_node ? tree_node->frame_entry.get() : nullptr;
}
void NavigationEntryImpl::ForEachFrameEntry(
FrameEntryIterationCallback on_frame_entry) {
NavigationEntryImpl::TreeNode* node = nullptr;
base::queue<NavigationEntryImpl::TreeNode*> work_queue;
work_queue.push(root_node());
while (!work_queue.empty()) {
node = work_queue.front();
work_queue.pop();
on_frame_entry(node->frame_entry.get());
// Enqueue any children.
for (const auto& child : node->children)
work_queue.push(child.get());
}
}
base::flat_map<std::string, bool> NavigationEntryImpl::GetSubframeUniqueNames(
FrameTreeNode* frame_tree_node) const {
base::flat_map<std::string, bool> names;
NavigationEntryImpl::TreeNode* tree_node = GetTreeNode(frame_tree_node);
if (tree_node) {
// Return the names of all immediate children.
for (const auto& child : tree_node->children) {
// Keep track of whether we would be loading about:blank, since the
// renderer should be allowed to just commit the initial blank frame if
// that was the default URL. PageState doesn't matter there, because
// content injected into about:blank frames doesn't use it.
//
// Be careful not to rely on FrameNavigationEntry's URLs in this check,
// because the committed URL in the browser could be rewritten to
// about:blank.
// See RenderProcessHostImpl::FilterURL to know which URLs are rewritten.
// See https://crbug.com/657896 for details.
bool is_about_blank = false;
blink::ExplodedPageState exploded_page_state;
if (blink::DecodePageState(
child->frame_entry->page_state().ToEncodedData(),
&exploded_page_state)) {
blink::ExplodedFrameState frame_state = exploded_page_state.top;
if (UTF16ToUTF8(frame_state.url_string.value_or(std::u16string())) ==
url::kAboutBlankURL)
is_about_blank = true;
}
names[child->frame_entry->frame_unique_name()] = is_about_blank;
}
}
return names;
}
void NavigationEntryImpl::RemoveEntryForFrame(FrameTreeNode* frame_tree_node,
bool only_if_different_position) {
DCHECK(!frame_tree_node->IsMainFrame());
NavigationEntryImpl::TreeNode* node = GetTreeNode(frame_tree_node);
if (!node)
return;
// Remove the |node| from the tree if either 1) |only_if_different_position|
// was not asked for or 2) if it is not in the same position in the tree of
// FrameNavigationEntries and the FrameTree.
if (!only_if_different_position ||
!InSameTreePosition(frame_tree_node, node)) {
auto* frame_entry = node->frame_entry.get();
if (frame_entry && frame_entry->committed_origin()) {
// Normally default-isolated origins are tracked through their presence in
// session history, which is consulted whenever an origin newly requests
// isolation. If we remove a frame_entry, its origin won't be available
// to any future global walk if the same origin later wants to opt-in. So
// we add it to the non-opt-in list here to be spec compliant (unless it's
// currently opted-in, in which case this call will do nothing).
ChildProcessSecurityPolicyImpl::GetInstance()
->AddDefaultIsolatedOriginIfNeeded(
frame_entry->site_instance()->GetIsolationContext(),
frame_entry->committed_origin().value(),
true /* global_ walk_or_frame_removal */);
}
NavigationEntryImpl::TreeNode* parent_node = node->parent;
auto it = base::ranges::find(
parent_node->children, node,
&std::unique_ptr<NavigationEntryImpl::TreeNode>::get);
CHECK(it != parent_node->children.end());
parent_node->children.erase(it);
}
}
void NavigationEntryImpl::UpdateBackForwardCacheNotRestoredReasons(
NavigationRequest* navigation_request) {
DCHECK(BackForwardCacheMetrics::IsCrossDocumentMainFrameHistoryNavigation(
navigation_request));
if (!back_forward_cache_metrics()) {
// Create a metrics object if there is none.
FrameNavigationEntry* frame_navigation_entry =
GetFrameEntry(navigation_request->frame_tree_node());
scoped_refptr<BackForwardCacheMetrics> metrics =
base::WrapRefCounted(new BackForwardCacheMetrics(
frame_navigation_entry->document_sequence_number()));
set_back_forward_cache_metrics(std::move(metrics));
}
// Update NotRestoredReasons to include additional reasons only
// known when we navigate back to the NavigationEntry.
back_forward_cache_metrics()->UpdateNotRestoredReasonsForNavigation(
navigation_request);
}
GURL NavigationEntryImpl::GetHistoryURLForDataURL() {
return GetBaseURLForDataURL().is_empty() ? GURL() : GetVirtualURL();
}
} // namespace content