blob: 6ea342e3c9a341372ae45dc52b49b4394541d677 [file] [log] [blame]
// Copyright 2013 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 "content/browser/renderer_host/render_frame_host_impl.h"
#include <algorithm>
#include <memory>
#include <tuple>
#include <unordered_map>
#include <utility>
#include "base/bind.h"
#include "base/callback_helpers.h"
#include "base/command_line.h"
#include "base/containers/contains.h"
#include "base/containers/cxx20_erase.h"
#include "base/debug/crash_logging.h"
#include "base/debug/dump_without_crashing.h"
#include "base/feature_list.h"
#include "base/lazy_instance.h"
#include "base/memory/memory_pressure_monitor.h"
#include "base/memory/ptr_util.h"
#include "base/memory/raw_ptr.h"
#include "base/metrics/field_trial_params.h"
#include "base/metrics/histogram_functions.h"
#include "base/metrics/histogram_macros.h"
#include "base/metrics/metrics_hashes.h"
#include "base/metrics/user_metrics.h"
#include "base/no_destructor.h"
#include "base/numerics/safe_conversions.h"
#include "base/process/kill.h"
#include "base/ranges/algorithm.h"
#include "base/state_transitions.h"
#include "base/stl_util.h"
#include "base/strings/strcat.h"
#include "base/strings/string_number_conversions.h"
#include "base/strings/string_piece.h"
#include "base/strings/stringprintf.h"
#include "base/syslog_logging.h"
#include "base/system/sys_info.h"
#include "base/task/thread_pool.h"
#include "base/threading/sequence_bound.h"
#include "base/threading/thread_task_runner_handle.h"
#include "base/time/time.h"
#include "base/timer/elapsed_timer.h"
#include "base/trace_event/optional_trace_event.h"
#include "build/build_config.h"
#include "components/download/public/common/download_url_parameters.h"
#include "content/browser/about_url_loader_factory.h"
#include "content/browser/accessibility/browser_accessibility_manager.h"
#include "content/browser/accessibility/render_accessibility_host.h"
#include "content/browser/attribution_reporting/attribution_host.h"
#include "content/browser/bad_message.h"
#include "content/browser/bluetooth/web_bluetooth_service_impl.h"
#include "content/browser/broadcast_channel/broadcast_channel_provider.h"
#include "content/browser/broadcast_channel/broadcast_channel_service.h"
#include "content/browser/browser_main_loop.h"
#include "content/browser/can_commit_status.h"
#include "content/browser/child_process_security_policy_impl.h"
#include "content/browser/code_cache/generated_code_cache_context.h"
#include "content/browser/data_url_loader_factory.h"
#include "content/browser/devtools/devtools_instrumentation.h"
#include "content/browser/dom_storage/dom_storage_context_wrapper.h"
#include "content/browser/download/data_url_blob_reader.h"
#include "content/browser/feature_observer.h"
#include "content/browser/fenced_frame/fenced_frame.h"
#include "content/browser/fenced_frame/fenced_frame_url_mapping.h"
#include "content/browser/file_system/file_system_manager_impl.h"
#include "content/browser/file_system/file_system_url_loader_factory.h"
#include "content/browser/file_system_access/file_system_access_manager_impl.h"
#include "content/browser/font_access/font_access_manager.h"
#include "content/browser/generic_sensor/sensor_provider_proxy_impl.h"
#include "content/browser/geolocation/geolocation_service_impl.h"
#include "content/browser/idle/idle_manager_impl.h"
#include "content/browser/installedapp/installed_app_provider_impl.h"
#include "content/browser/interest_group/ad_auction_document_data.h"
#include "content/browser/loader/file_url_loader_factory.h"
#include "content/browser/loader/navigation_early_hints_manager.h"
#include "content/browser/loader/prefetch_url_loader_service.h"
#include "content/browser/log_console_message.h"
#include "content/browser/manifest/manifest_manager_host.h"
#include "content/browser/media/media_interface_proxy.h"
#include "content/browser/media/webaudio/audio_context_manager_impl.h"
#include "content/browser/navigation_or_document_handle.h"
#include "content/browser/navigation_subresource_loader_params.h"
#include "content/browser/net/cross_origin_embedder_policy_reporter.h"
#include "content/browser/net/cross_origin_opener_policy_reporter.h"
#include "content/browser/permissions/permission_controller_impl.h"
#include "content/browser/permissions/permission_service_context.h"
#include "content/browser/portal/portal.h"
#include "content/browser/preloading/prerender/prerender_host_registry.h"
#include "content/browser/preloading/prerender/prerender_metrics.h"
#include "content/browser/presentation/presentation_service_impl.h"
#include "content/browser/process_lock.h"
#include "content/browser/push_messaging/push_messaging_manager.h"
#include "content/browser/renderer_host/agent_scheduling_group_host.h"
#include "content/browser/renderer_host/back_forward_cache_disable.h"
#include "content/browser/renderer_host/back_forward_cache_impl.h"
#include "content/browser/renderer_host/close_listener_host.h"
#include "content/browser/renderer_host/code_cache_host_impl.h"
#include "content/browser/renderer_host/cookie_utils.h"
#include "content/browser/renderer_host/dip_util.h"
#include "content/browser/renderer_host/frame_tree.h"
#include "content/browser/renderer_host/frame_tree_node.h"
#include "content/browser/renderer_host/input/input_injector_impl.h"
#include "content/browser/renderer_host/input/input_router.h"
#include "content/browser/renderer_host/input/timeout_monitor.h"
#include "content/browser/renderer_host/ipc_utils.h"
#include "content/browser/renderer_host/media/peer_connection_tracker_host.h"
#include "content/browser/renderer_host/navigation_controller_impl.h"
#include "content/browser/renderer_host/navigation_entry_impl.h"
#include "content/browser/renderer_host/navigation_request.h"
#include "content/browser/renderer_host/navigator.h"
#include "content/browser/renderer_host/page_lifecycle_state_manager.h"
#include "content/browser/renderer_host/pending_beacon_host.h"
#include "content/browser/renderer_host/pending_beacon_service.h"
#include "content/browser/renderer_host/private_network_access_util.h"
#include "content/browser/renderer_host/recently_destroyed_hosts.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_process_host_impl.h"
#include "content/browser/renderer_host/render_view_host_delegate.h"
#include "content/browser/renderer_host/render_view_host_impl.h"
#include "content/browser/renderer_host/render_widget_host_factory.h"
#include "content/browser/renderer_host/render_widget_host_impl.h"
#include "content/browser/renderer_host/render_widget_host_view_base.h"
#include "content/browser/renderer_host/render_widget_host_view_child_frame.h"
#include "content/browser/scoped_active_url.h"
#include "content/browser/service_worker/service_worker_container_host.h"
#include "content/browser/service_worker/service_worker_object_host.h"
#include "content/browser/shared_storage/shared_storage_document_service_impl.h"
#include "content/browser/site_info.h"
#include "content/browser/sms/webotp_service.h"
#include "content/browser/speech/speech_synthesis_impl.h"
#include "content/browser/storage_partition_impl.h"
#include "content/browser/url_loader_factory_params_helper.h"
#include "content/browser/web_exposed_isolation_info.h"
#include "content/browser/web_package/prefetched_signed_exchange_cache.h"
#include "content/browser/web_package/subresource_web_bundle_navigation_info.h"
#include "content/browser/web_package/web_bundle_handle.h"
#include "content/browser/web_package/web_bundle_handle_tracker.h"
#include "content/browser/web_package/web_bundle_navigation_info.h"
#include "content/browser/web_package/web_bundle_source.h"
#include "content/browser/webauth/authenticator_environment_impl.h"
#include "content/browser/webauth/authenticator_impl.h"
#include "content/browser/webauth/webauth_request_security_checker.h"
#include "content/browser/webid/federated_auth_request_impl.h"
#include "content/browser/webid/flags.h"
#include "content/browser/websockets/websocket_connector_impl.h"
#include "content/browser/webtransport/web_transport_connector_impl.h"
#include "content/browser/webui/url_data_manager_backend.h"
#include "content/browser/webui/web_ui_controller_factory_registry.h"
#include "content/browser/worker_host/dedicated_worker_host.h"
#include "content/browser/worker_host/dedicated_worker_host_factory_impl.h"
#include "content/browser/worker_host/dedicated_worker_hosts_for_document.h"
#include "content/common/associated_interfaces.mojom.h"
#include "content/common/content_navigation_policy.h"
#include "content/common/debug_utils.h"
#include "content/common/frame.mojom.h"
#include "content/common/frame_messages.mojom.h"
#include "content/common/navigation_client.mojom.h"
#include "content/common/navigation_params_utils.h"
#include "content/public/browser/ax_event_notification_details.h"
#include "content/public/browser/browser_context.h"
#include "content/public/browser/content_browser_client.h"
#include "content/public/browser/context_menu_params.h"
#include "content/public/browser/disallow_activation_reason.h"
#include "content/public/browser/document_ref.h"
#include "content/public/browser/document_service_internal.h"
#include "content/public/browser/download_manager.h"
#include "content/public/browser/global_routing_id.h"
#include "content/public/browser/render_process_host.h"
#include "content/public/browser/render_widget_host_view.h"
#include "content/public/browser/shared_cors_origin_access_list.h"
#include "content/public/browser/site_isolation_policy.h"
#include "content/public/browser/sms_fetcher.h"
#include "content/public/browser/storage_partition.h"
#include "content/public/browser/weak_document_ptr.h"
#include "content/public/browser/web_ui_url_loader_factory.h"
#include "content/public/common/alternative_error_page_override_info.mojom.h"
#include "content/public/common/bindings_policy.h"
#include "content/public/common/content_client.h"
#include "content/public/common/content_features.h"
#include "content/public/common/content_switches.h"
#include "content/public/common/extra_mojo_js_features.mojom.h"
#include "content/public/common/isolated_world_ids.h"
#include "content/public/common/network_service_util.h"
#include "content/public/common/page_visibility_state.h"
#include "content/public/common/referrer.h"
#include "content/public/common/referrer_type_converters.h"
#include "content/public/common/url_constants.h"
#include "content/public/common/url_utils.h"
#include "media/base/media_switches.h"
#include "media/learning/common/value.h"
#include "media/media_buildflags.h"
#include "media/mojo/mojom/remoting.mojom.h"
#include "media/mojo/services/video_decode_perf_history.h"
#include "media/render_frame_audio_output_stream_factory.h"
#include "mojo/public/cpp/bindings/message.h"
#include "mojo/public/cpp/bindings/self_owned_receiver.h"
#include "mojo/public/cpp/bindings/struct_ptr.h"
#include "mojo/public/cpp/system/data_pipe.h"
#include "net/base/schemeful_site.h"
#include "net/net_buildflags.h"
#include "services/device/public/mojom/screen_orientation.mojom.h"
#include "services/metrics/public/cpp/ukm_builders.h"
#include "services/metrics/public/cpp/ukm_source_id.h"
#include "services/network/public/cpp/cors/origin_access_list.h"
#include "services/network/public/cpp/features.h"
#include "services/network/public/cpp/is_potentially_trustworthy.h"
#include "services/network/public/cpp/network_service_buildflags.h"
#include "services/network/public/cpp/not_implemented_url_loader_factory.h"
#include "services/network/public/cpp/trust_token_operation_authorization.h"
#include "services/network/public/cpp/web_sandbox_flags.h"
#include "services/network/public/mojom/url_loader_factory.mojom.h"
#include "services/network/public/mojom/web_sandbox_flags.mojom-shared.h"
#include "services/service_manager/public/cpp/interface_provider.h"
#include "third_party/blink/public/common/associated_interfaces/associated_interface_provider.h"
#include "third_party/blink/public/common/associated_interfaces/associated_interface_registry.h"
#include "third_party/blink/public/common/chrome_debug_urls.h"
#include "third_party/blink/public/common/features.h"
#include "third_party/blink/public/common/fenced_frame/fenced_frame_utils.h"
#include "third_party/blink/public/common/frame/fenced_frame_sandbox_flags.h"
#include "third_party/blink/public/common/frame/frame_owner_element_type.h"
#include "third_party/blink/public/common/frame/frame_policy.h"
#include "third_party/blink/public/common/loader/inter_process_time_ticks_converter.h"
#include "third_party/blink/public/common/loader/resource_type_util.h"
#include "third_party/blink/public/common/messaging/transferable_message.h"
#include "third_party/blink/public/common/navigation/navigation_params_mojom_traits.h"
#include "third_party/blink/public/common/permissions/permission_utils.h"
#include "third_party/blink/public/common/permissions_policy/document_policy.h"
#include "third_party/blink/public/common/permissions_policy/permissions_policy.h"
#include "third_party/blink/public/common/privacy_budget/identifiability_study_document_created.h"
#include "third_party/blink/public/common/privacy_budget/identifiability_study_settings.h"
#include "third_party/blink/public/common/scheduler/web_scheduler_tracked_feature.h"
#include "third_party/blink/public/common/storage_key/storage_key.h"
#include "third_party/blink/public/mojom/broadcastchannel/broadcast_channel.mojom.h"
#include "third_party/blink/public/mojom/devtools/inspector_issue.mojom.h"
#include "third_party/blink/public/mojom/frame/frame.mojom.h"
#include "third_party/blink/public/mojom/frame/frame_owner_properties.mojom.h"
#include "third_party/blink/public/mojom/frame/fullscreen.mojom.h"
#include "third_party/blink/public/mojom/frame/media_player_action.mojom.h"
#include "third_party/blink/public/mojom/frame/text_autosizer_page_info.mojom.h"
#include "third_party/blink/public/mojom/loader/resource_load_info.mojom.h"
#include "third_party/blink/public/mojom/loader/transferrable_url_loader.mojom.h"
#include "third_party/blink/public/mojom/navigation/navigation_params.mojom.h"
#include "third_party/blink/public/mojom/opengraph/metadata.mojom.h"
#include "third_party/blink/public/mojom/page/display_cutout.mojom.h"
#include "third_party/blink/public/mojom/scroll/scroll_into_view_params.mojom.h"
#include "third_party/blink/public/mojom/service_worker/service_worker_object.mojom.h"
#include "third_party/blink/public/mojom/storage_key/ancestor_chain_bit.mojom.h"
#include "third_party/blink/public/mojom/timing/resource_timing.mojom.h"
#include "ui/accessibility/ax_action_handler_registry.h"
#include "ui/accessibility/ax_common.h"
#include "ui/accessibility/ax_tree_update.h"
#include "ui/display/screen.h"
#include "ui/events/event_constants.h"
#include "url/gurl.h"
#include "url/origin.h"
#include "url/url_constants.h"
#if BUILDFLAG(IS_ANDROID)
#include "content/browser/android/content_url_loader_factory.h"
#include "content/browser/android/java_interfaces_impl.h"
#include "content/browser/renderer_host/render_frame_host_android.h"
#include "content/public/browser/android/java_interfaces.h"
#else
#include "content/browser/hid/hid_service.h"
#include "content/browser/host_zoom_map_impl.h"
#include "content/browser/serial/serial_service.h"
#endif
#if BUILDFLAG(IS_MAC)
#include "content/browser/renderer_host/popup_menu_helper_mac.h"
#endif
#if BUILDFLAG(ENABLE_PLUGINS)
#include "content/browser/plugin_service_impl.h"
#include "content/browser/renderer_host/pepper/pepper_renderer_connection.h"
#endif
#if BUILDFLAG(USE_EXTERNAL_POPUP_MENU)
#include "content/browser/renderer_host/render_view_host_delegate_view.h"
#endif
namespace features {
const base::Feature kDisableFrameNameUpdateOnNonCurrentRenderFrameHost{
"DisableFrameNameUpdateOnNonCurrentRenderFrameHost",
base::FEATURE_ENABLED_BY_DEFAULT};
// Evict when accessibility events occur while in back/forward cache. Remove
// once the https://crbug.com/1341507 is resolved. This crash started to happen
// on Android with bfcache experiments, so we're enabling this flag only on
// Android.
const base::Feature kEvictOnAXEvents {
"EvictOnAXEvents",
#if BUILDFLAG(IS_ANDROID)
base::FEATURE_ENABLED_BY_DEFAULT
#else
base::FEATURE_DISABLED_BY_DEFAULT
#endif
};
}
namespace content {
#if defined(AX_FAIL_FAST_BUILD)
// Enable fast fails on clusterfuzz and other builds used to debug Chrome,
// which should help narrow down illegal ax trees more quickly.
// static
int RenderFrameHostImpl::max_accessibility_resets_ = 0;
#else
int RenderFrameHostImpl::max_accessibility_resets_ = 4;
#endif // AX_FAIL_FAST_BUILD
class RenderFrameHostOrProxy {
public:
RenderFrameHostOrProxy(RenderFrameHostImpl* frame,
RenderFrameProxyHost* proxy) {
DCHECK(!frame || !proxy)
<< "Both frame and proxy can't be non-null at the same time";
if (proxy) {
frame_or_proxy_ = proxy;
return;
}
if (frame) {
frame_or_proxy_ = frame;
return;
}
}
explicit operator bool() { return frame_or_proxy_.index() > 0; }
FrameTreeNode* GetFrameTreeNode() {
if (auto* proxy = GetProxy()) {
return proxy->frame_tree_node();
} else if (auto* frame = GetFrame()) {
return frame->frame_tree_node();
}
return nullptr;
}
RenderFrameHostImpl* GetCurrentFrameHost() {
if (auto* proxy = GetProxy()) {
return proxy->frame_tree_node()->current_frame_host();
} else if (auto* frame = GetFrame()) {
return frame;
}
return nullptr;
}
private:
RenderFrameProxyHost* GetProxy() {
if (auto** proxy = absl::get_if<RenderFrameProxyHost*>(&frame_or_proxy_)) {
return *proxy;
}
return nullptr;
}
RenderFrameHostImpl* GetFrame() {
if (auto** frame = absl::get_if<RenderFrameHostImpl*>(&frame_or_proxy_)) {
return *frame;
}
return nullptr;
}
absl::variant<absl::monostate, RenderFrameHostImpl*, RenderFrameProxyHost*>
frame_or_proxy_;
};
namespace {
constexpr int kSubframeProcessShutdownLongDelayInMSec = 8 * 1000;
static_assert(kSubframeProcessShutdownLongDelayInMSec +
RenderViewHostImpl::kUnloadTimeoutInMSec <
RenderProcessHostImpl::kKeepAliveHandleFactoryTimeoutInMSec,
"The maximum process shutdown delay should not exceed the "
"keepalive timeout. This has security implications, see "
"https://crbug.com/1177674.");
#if BUILDFLAG(IS_ANDROID)
const void* const kRenderFrameHostAndroidKey = &kRenderFrameHostAndroidKey;
#endif // BUILDFLAG(IS_ANDROID)
// Causes RenderAccessibilityHost HandleAXEvents messages to be handled with
// minimal copying of the data.
//
// TODO(nuskos): Once we've conducted a retroactive study of chrometto
// improvements clean up this feature.
const base::Feature kRenderAccessibilityHostAvoidCopying{
"RenderAccessibilityHostAvoidCopying", base::FEATURE_ENABLED_BY_DEFAULT};
// The next value to use for the accessibility reset token.
int g_next_accessibility_reset_token = 1;
// Whether to allow injecting javascript into any kind of frame, for Android
// WebView, WebLayer, Fuchsia web.ContextProvider and CastOS content shell.
bool g_allow_injecting_javascript = false;
const char kDotGoogleDotCom[] = ".google.com";
typedef std::unordered_map<GlobalRenderFrameHostId,
RenderFrameHostImpl*,
GlobalRenderFrameHostIdHasher>
RoutingIDFrameMap;
base::LazyInstance<RoutingIDFrameMap>::DestructorAtExit g_routing_id_frame_map =
LAZY_INSTANCE_INITIALIZER;
// A global set of all sandboxed RenderFrameHosts that could be isolated from
// the rest of their SiteInstance.
typedef std::unordered_set<GlobalRenderFrameHostId,
GlobalRenderFrameHostIdHasher>
RoutingIDIsolatableSandboxedIframesSet;
base::LazyInstance<RoutingIDIsolatableSandboxedIframesSet>::DestructorAtExit
g_routing_id_isolatable_sandboxed_iframes_set = LAZY_INSTANCE_INITIALIZER;
using TokenFrameMap = std::unordered_map<blink::LocalFrameToken,
RenderFrameHostImpl*,
blink::LocalFrameToken::Hasher>;
base::LazyInstance<TokenFrameMap>::Leaky g_token_frame_map =
LAZY_INSTANCE_INITIALIZER;
BackForwardCacheMetrics::NotRestoredReason
RendererEvictionReasonToNotRestoredReason(
blink::mojom::RendererEvictionReason reason) {
switch (reason) {
case blink::mojom::RendererEvictionReason::kJavaScriptExecution:
return BackForwardCacheMetrics::NotRestoredReason::kJavaScriptExecution;
case blink::mojom::RendererEvictionReason::
kNetworkRequestDatapipeDrainedAsBytesConsumer:
return BackForwardCacheMetrics::NotRestoredReason::
kNetworkRequestDatapipeDrainedAsBytesConsumer;
case blink::mojom::RendererEvictionReason::kNetworkRequestRedirected:
return BackForwardCacheMetrics::NotRestoredReason::
kNetworkRequestRedirected;
case blink::mojom::RendererEvictionReason::kNetworkRequestTimeout:
return BackForwardCacheMetrics::NotRestoredReason::kNetworkRequestTimeout;
case blink::mojom::RendererEvictionReason::kNetworkExceedsBufferLimit:
return BackForwardCacheMetrics::NotRestoredReason::
kNetworkExceedsBufferLimit;
}
NOTREACHED();
return BackForwardCacheMetrics::NotRestoredReason::kUnknown;
}
// Ensure that we reset nav_entry_id_ in DidCommitProvisionalLoad if any of
// the validations fail and lead to an early return. Call disable() once we
// know the commit will be successful. Resetting nav_entry_id_ avoids acting on
// any UpdateState or UpdateTitle messages after an ignored commit.
class ScopedCommitStateResetter {
public:
explicit ScopedCommitStateResetter(RenderFrameHostImpl* render_frame_host)
: render_frame_host_(render_frame_host), disabled_(false) {}
~ScopedCommitStateResetter() {
if (!disabled_) {
render_frame_host_->set_nav_entry_id(0);
}
}
void disable() { disabled_ = true; }
private:
raw_ptr<RenderFrameHostImpl> render_frame_host_;
bool disabled_;
};
class ActiveURLMessageFilter : public mojo::MessageFilter {
public:
explicit ActiveURLMessageFilter(RenderFrameHostImpl* render_frame_host)
: render_frame_host_(render_frame_host) {}
~ActiveURLMessageFilter() override {
if (debug_url_set_) {
GetContentClient()->SetActiveURL(GURL(), "");
}
}
// mojo::MessageFilter overrides.
bool WillDispatch(mojo::Message* message) override {
debug_url_set_ = true;
GetContentClient()->SetActiveURL(render_frame_host_->GetLastCommittedURL(),
render_frame_host_->GetMainFrame()
->GetLastCommittedOrigin()
.GetDebugString());
return true;
}
void DidDispatchOrReject(mojo::Message* message, bool accepted) override {
GetContentClient()->SetActiveURL(GURL(), "");
debug_url_set_ = false;
}
private:
raw_ptr<RenderFrameHostImpl> render_frame_host_;
bool debug_url_set_ = false;
};
// This class can be added as a MessageFilter to a mojo receiver to detect
// messages received while the the associated frame is in the Back-Forward
// Cache. Documents that are in the bfcache should not be sending mojo messages
// back to the browser.
class BackForwardCacheMessageFilter : public mojo::MessageFilter {
public:
explicit BackForwardCacheMessageFilter(
RenderFrameHostImpl* render_frame_host,
const char* interface_name,
BackForwardCacheImpl::MessageHandlingPolicyWhenCached policy)
: render_frame_host_(render_frame_host),
interface_name_(interface_name),
policy_(policy) {}
~BackForwardCacheMessageFilter() override = default;
private:
// mojo::MessageFilter overrides.
bool WillDispatch(mojo::Message* message) override {
if (!render_frame_host_->render_view_host())
return false;
if (render_frame_host_->render_view_host()
->GetPageLifecycleStateManager()
->RendererExpectedToSendChannelAssociatedIpcs() ||
ProcessHoldsNonCachedPages() ||
policy_ == BackForwardCacheImpl::kMessagePolicyNone) {
return true;
}
DLOG(ERROR) << "Received message " << message->name() << " on interface "
<< interface_name_ << " from frame in bfcache.";
TRACE_EVENT2(
"content", "BackForwardCacheMessageFilter::WillDispatch bad_message",
"interface_name", interface_name_, "message_name", message->name());
base::UmaHistogramSparse(
"BackForwardCache.UnexpectedRendererToBrowserMessage.InterfaceName",
static_cast<int32_t>(base::HashMetricName(interface_name_)));
switch (policy_) {
case BackForwardCacheImpl::kMessagePolicyNone:
case BackForwardCacheImpl::kMessagePolicyLog:
return true;
case BackForwardCacheImpl::kMessagePolicyDump:
base::debug::DumpWithoutCrashing();
return true;
}
}
void DidDispatchOrReject(mojo::Message* message, bool accepted) override {}
// TODO(https://crbug.com/1125996): Remove once a well-behaved frozen
// RenderFrame never send IPCs messages, even if there are active pages in the
// process.
bool ProcessHoldsNonCachedPages() {
return RenderViewHostImpl::HasNonBackForwardCachedInstancesForProcess(
render_frame_host_->GetProcess());
}
const raw_ptr<RenderFrameHostImpl> render_frame_host_;
const char* const interface_name_;
const BackForwardCacheImpl::MessageHandlingPolicyWhenCached policy_;
};
// This class is used to chain multiple mojo::MessageFilter. Messages will be
// processed by the filters in the same order as the filters are added with the
// Add() method. WillDispatch() might not be called for all filters or might see
// a modified message if a filter earlier in the chain discards or modifies it.
// Similarly a given filter instance might not receive a DidDispatchOrReject()
// call even if WillDispatch() was called if a filter further down the chain
// discarded it. Long story short, the order in which filters are added is
// important!
class MessageFilterChain final : public mojo::MessageFilter {
public:
MessageFilterChain() = default;
~MessageFilterChain() final = default;
bool WillDispatch(mojo::Message* message) override {
for (auto& filter : filters_) {
if (!filter->WillDispatch(message))
return false;
}
return true;
}
void DidDispatchOrReject(mojo::Message* message, bool accepted) override {
for (auto& filter : filters_) {
filter->DidDispatchOrReject(message, accepted);
}
}
// Adds a filter to the end of the chain. See class description for ordering
// implications.
void Add(std::unique_ptr<mojo::MessageFilter> filter) {
filters_.push_back(std::move(filter));
}
private:
std::vector<std::unique_ptr<mojo::MessageFilter>> filters_;
};
std::unique_ptr<mojo::MessageFilter>
CreateMessageFilterForAssociatedReceiverImpl(
RenderFrameHostImpl* render_frame_host,
const char* interface_name,
BackForwardCacheImpl::MessageHandlingPolicyWhenCached policy) {
auto filter_chain = std::make_unique<MessageFilterChain>();
filter_chain->Add(std::make_unique<BackForwardCacheMessageFilter>(
render_frame_host, interface_name, policy));
// BackForwardCacheMessageFilter might drop messages so add
// ActiveURLMessageFilter at the end of the chain as we need to make sure that
// the debug url is reset, that is, DidDispatchOrReject() is called if
// WillDispatch().
filter_chain->Add(
std::make_unique<ActiveURLMessageFilter>(render_frame_host));
return filter_chain;
}
void GrantFileAccess(int child_id,
const std::vector<base::FilePath>& file_paths) {
ChildProcessSecurityPolicyImpl* policy =
ChildProcessSecurityPolicyImpl::GetInstance();
for (const auto& file : file_paths) {
if (!policy->CanReadFile(child_id, file))
policy->GrantReadFile(child_id, file);
}
}
#if BUILDFLAG(ENABLE_MEDIA_REMOTING)
// RemoterFactory that delegates Create() calls to the ContentBrowserClient.
//
// Since Create() could be called at any time, perhaps by a stray task being run
// after a RenderFrameHost has been destroyed, the RemoterFactoryImpl uses the
// process/routing IDs as a weak reference to the RenderFrameHostImpl.
class RemoterFactoryImpl final : public media::mojom::RemoterFactory {
public:
RemoterFactoryImpl(int process_id, int routing_id)
: process_id_(process_id), routing_id_(routing_id) {}
RemoterFactoryImpl(const RemoterFactoryImpl&) = delete;
RemoterFactoryImpl& operator=(const RemoterFactoryImpl&) = delete;
private:
void Create(mojo::PendingRemote<media::mojom::RemotingSource> source,
mojo::PendingReceiver<media::mojom::Remoter> receiver) final {
if (auto* host = RenderFrameHostImpl::FromID(process_id_, routing_id_)) {
GetContentClient()->browser()->CreateMediaRemoter(host, std::move(source),
std::move(receiver));
}
}
const int process_id_;
const int routing_id_;
};
#endif // BUILDFLAG(ENABLE_MEDIA_REMOTING)
RenderFrameHostOrProxy LookupRenderFrameHostOrProxy(int process_id,
int routing_id) {
RenderFrameHostImpl* rfh =
RenderFrameHostImpl::FromID(process_id, routing_id);
RenderFrameProxyHost* proxy = nullptr;
if (!rfh)
proxy = RenderFrameProxyHost::FromID(process_id, routing_id);
return RenderFrameHostOrProxy(rfh, proxy);
}
RenderFrameHostOrProxy LookupRenderFrameHostOrProxy(
int process_id,
const blink::FrameToken& frame_token) {
if (frame_token.Is<blink::LocalFrameToken>()) {
auto it = g_token_frame_map.Get().find(
frame_token.GetAs<blink::LocalFrameToken>());
// The check against |process_id| isn't strictly necessary, but represents
// an extra level of protection against a renderer trying to force a frame
// token.
if (it == g_token_frame_map.Get().end() ||
process_id != it->second->GetProcess()->GetID()) {
return RenderFrameHostOrProxy(nullptr, nullptr);
}
return RenderFrameHostOrProxy(it->second, nullptr);
}
DCHECK(frame_token.Is<blink::RemoteFrameToken>());
return RenderFrameHostOrProxy(
nullptr, RenderFrameProxyHost::FromFrameToken(
process_id, frame_token.GetAs<blink::RemoteFrameToken>()));
}
// Set crash keys that will help understand the circumstances of a renderer
// kill. Note that the commit URL is already reported in a crash key, and
// additional keys are logged in RenderProcessHostImpl::ShutdownForBadMessage.
void LogRendererKillCrashKeys(const SiteInfo& site_info) {
static auto* const site_info_key = base::debug::AllocateCrashKeyString(
"current_site_info", base::debug::CrashKeySize::Size256);
base::debug::SetCrashKeyString(site_info_key, site_info.GetDebugString());
}
void LogCanCommitOriginAndUrlFailureReason(const std::string& failure_reason) {
static auto* const failure_reason_key = base::debug::AllocateCrashKeyString(
"rfhi_can_commit_failure_reason", base::debug::CrashKeySize::Size64);
base::debug::SetCrashKeyString(failure_reason_key, failure_reason);
}
std::unique_ptr<blink::PendingURLLoaderFactoryBundle> CloneFactoryBundle(
scoped_refptr<blink::URLLoaderFactoryBundle> bundle) {
return base::WrapUnique(static_cast<blink::PendingURLLoaderFactoryBundle*>(
bundle->Clone().release()));
}
// Helper method to download a URL on UI thread.
void StartDownload(
std::unique_ptr<download::DownloadUrlParameters> parameters,
scoped_refptr<network::SharedURLLoaderFactory> blob_url_loader_factory) {
DCHECK_CURRENTLY_ON(BrowserThread::UI);
RenderProcessHost* render_process_host =
RenderProcessHost::FromID(parameters->render_process_host_id());
if (!render_process_host)
return;
BrowserContext* browser_context = render_process_host->GetBrowserContext();
DownloadManager* download_manager = browser_context->GetDownloadManager();
parameters->set_download_source(download::DownloadSource::FROM_RENDERER);
download_manager->DownloadUrl(std::move(parameters),
std::move(blob_url_loader_factory));
}
// Called on the UI thread when the data URL in the BlobDataHandle
// is read.
void OnDataURLRetrieved(
std::unique_ptr<download::DownloadUrlParameters> parameters,
GURL data_url) {
DCHECK_CURRENTLY_ON(BrowserThread::UI);
if (!data_url.is_valid())
return;
parameters->set_url(std::move(data_url));
StartDownload(std::move(parameters), nullptr);
}
// Subframe navigations can optionally have associated Trust Tokens operations
// (https://github.com/wicg/trust-token-api). If the operation's type is
// "redemption" or "signing" (as opposed to "issuance"), the parent's frame
// needs to have the trust-token-redemption Permissions Policy feature enabled.
bool ParentNeedsTrustTokenPermissionsPolicy(
const blink::mojom::BeginNavigationParams& begin_params) {
if (!begin_params.trust_token_params)
return false;
return network::DoesTrustTokenOperationRequirePermissionsPolicy(
begin_params.trust_token_params->type);
}
// Analyzes trusted sources of a frame's trust-token-redemption Permissions
// Policy feature to see if the feature is definitely disabled or potentially
// enabled.
//
// This information will be bound to a URLLoaderFactory; if the answer is
// "definitely disabled," the network service will report a bad message if it
// receives a request from the renderer to execute a Trust Tokens redemption or
// signing operation in the frame.
//
// A return value of kForbid denotes that the feature is disabled for the
// frame. A return value of kPotentiallyPermit means that all trusted
// information sources say that the policy is enabled.
network::mojom::TrustTokenRedemptionPolicy
DetermineWhetherToForbidTrustTokenRedemption(
const RenderFrameHostImpl* frame,
const blink::mojom::CommitNavigationParams& commit_params,
const url::Origin& subframe_origin) {
std::unique_ptr<blink::PermissionsPolicy> subframe_policy;
if (frame->IsNestedWithinFencedFrame()) {
// In Fenced Frames, all permission policy gated features must be disabled
// for privacy reasons.
subframe_policy =
blink::PermissionsPolicy::CreateForFencedFrame(subframe_origin);
} else {
// For main frame loads, the frame's permissions policy is determined
// entirely by response headers, which are provided by the renderer.
if (!frame->GetParent())
return network::mojom::TrustTokenRedemptionPolicy::kPotentiallyPermit;
const blink::PermissionsPolicy* parent_policy =
frame->GetParent()->permissions_policy();
blink::ParsedPermissionsPolicy container_policy =
commit_params.frame_policy.container_policy;
subframe_policy = blink::PermissionsPolicy::CreateFromParentPolicy(
parent_policy, container_policy, subframe_origin);
}
if (subframe_policy->IsFeatureEnabled(
blink::mojom::PermissionsPolicyFeature::kTrustTokenRedemption)) {
return network::mojom::TrustTokenRedemptionPolicy::kPotentiallyPermit;
}
return network::mojom::TrustTokenRedemptionPolicy::kForbid;
}
// When a frame creates its initial subresource loaders, it needs to know
// whether the trust-token-redemption Permissions Policy feature will be enabled
// after the commit finishes, which is a little involved (see
// DetermineWhetherToForbidTrustTokenRedemption). In contrast, if it needs to
// make this decision once the frame has committted---for instance, to create
// more loaders after the network service crashes---it can directly consult the
// current Permissions Policy state to determine whether the feature is enabled.
network::mojom::TrustTokenRedemptionPolicy
DetermineAfterCommitWhetherToForbidTrustTokenRedemption(
RenderFrameHostImpl& impl) {
return impl.IsFeatureEnabled(
blink::mojom::PermissionsPolicyFeature::kTrustTokenRedemption)
? network::mojom::TrustTokenRedemptionPolicy::kPotentiallyPermit
: network::mojom::TrustTokenRedemptionPolicy::kForbid;
}
// Returns the string corresponding to LifecycleStateImpl, used for logging
// crash keys.
const char* LifecycleStateImplToString(
RenderFrameHostImpl::LifecycleStateImpl state) {
using LifecycleStateImpl = RenderFrameHostImpl::LifecycleStateImpl;
switch (state) {
case LifecycleStateImpl::kSpeculative:
return "Speculative";
case LifecycleStateImpl::kPrerendering:
return "Prerendering";
case LifecycleStateImpl::kPendingCommit:
return "PendingCommit";
case LifecycleStateImpl::kActive:
return "Active";
case LifecycleStateImpl::kInBackForwardCache:
return "InBackForwardCache";
case LifecycleStateImpl::kRunningUnloadHandlers:
return "RunningUnloadHandlers";
case LifecycleStateImpl::kReadyToBeDeleted:
return "ReadyToBeDeleted";
}
}
// Verify that |browser_side_origin| and |renderer_side_origin| match. See also
// https://crbug.com/888079. Returns true if the origins match, and false
// otherwise.
bool VerifyThatBrowserAndRendererCalculatedOriginsToCommitMatch(
NavigationRequest* navigation_request,
const mojom::DidCommitProvisionalLoadParams& params) {
DCHECK(navigation_request);
// This should be called only when a new document is created. Navigations in
// the same document and page activations do not create a new document.
DCHECK(!navigation_request->IsSameDocument());
DCHECK(!navigation_request->IsPageActivation());
// Ignore for now cases where the NavigationRequest is in an unexpectedly
// early state. Triggered by the following tests:
// NavigationBrowserTest.OpenerNavigation_DownloadPolicy,
// WebContentsImplBrowserTest.NewNamedWindow.
if (navigation_request->state() < NavigationRequest::WILL_PROCESS_RESPONSE)
return true;
// Check if both the renderer and browser expect an opaque origin. This
// effectively ignores the following:
// - precursor origins
// - TODO(https://crbug.com/1041376): mismatched nonces (even if precursor
// origins would have matched)
// - blob urls with content scheme are opaque on browser side
// (https://crbug.com/1295268)
const url::Origin& renderer_side_origin = params.origin;
std::pair<url::Origin, std::string> browser_side_origin_and_debug_info =
navigation_request->GetOriginToCommitWithDebugInfo();
if ((renderer_side_origin.opaque() ||
renderer_side_origin.scheme() == url::kContentScheme) &&
browser_side_origin_and_debug_info.first.opaque()) {
return true;
}
// TODO(https://crbug.com/888079): Remove the DumpWithoutCrashing below, once
// we are sure that the `browser_side_origin` is always the same as the
// `renderer_side_origin`.
if (browser_side_origin_and_debug_info.first != renderer_side_origin) {
NavigationRequest::ScopedCrashKeys navigation_request_crash_keys(
*navigation_request);
SCOPED_CRASH_KEY_STRING256(
"", "browser_origin",
browser_side_origin_and_debug_info.first.GetDebugString());
SCOPED_CRASH_KEY_STRING256("", "browser_debug_info",
browser_side_origin_and_debug_info.second);
SCOPED_CRASH_KEY_STRING256("", "renderer_origin",
renderer_side_origin.GetDebugString());
SCOPED_CRASH_KEY_STRING256("", "renderer_debug_info",
params.origin_calculation_debug_info);
CaptureTraceForNavigationDebugScenario(
DebugScenario::kDebugBrowserVsRendererOriginToCommit);
base::debug::DumpWithoutCrashing();
return false;
}
DCHECK_EQ(browser_side_origin_and_debug_info.first, renderer_side_origin)
<< "; navigation_request->GetURL() = " << navigation_request->GetURL();
return true;
}
// Enum used for Navigation.VerifyDidCommitParams histogram, to indicate which
// DidCommitProvisionalLoadParams differ when comparing browser- vs
// renderer-calculated values.
// Do NOT delete or reorder existing entries.
enum class VerifyDidCommitParamsDifference {
kIntendedAsNewEntry = 0,
kMethod = 1,
kURLIsUnreachable = 2,
kBaseURL = 3,
kPostID = 4,
kIsOverridingUserAgent = 5,
kHTTPStatusCode = 6,
kShouldUpdateHistory = 7,
kGesture = 8,
kShouldReplaceCurrentEntry = 9,
kURL = 10,
kDidCreateNewEntry = 11,
kTransition = 12,
kHistoryListWasCleared = 13,
kOrigin = 14,
kMaxValue = kOrigin,
};
// A simplified version of Blink's WebFrameLoadType, used to simulate renderer
// calculations. See CalculateRendererLoadType() further below.
// TODO(https://crbug.com/1131832): This should only be here temporarily.
// Remove this once the renderer behavior at commit time is more consistent with
// what the browser instructed it to do (e.g. reloads will always be classified
// as kReload).
enum class RendererLoadType {
kStandard,
kBackForward,
kReload,
kReplaceCurrentItem,
};
bool ValidateCSPAttribute(const std::string& value) {
static const size_t kMaxLengthCSPAttribute = 4096;
if (!base::IsStringASCII(value))
return false;
if (value.length() > kMaxLengthCSPAttribute ||
value.find('\n') != std::string::npos ||
value.find('\r') != std::string::npos) {
return false;
}
return true;
}
perfetto::protos::pbzero::FrameDeleteIntention FrameDeleteIntentionToProto(
mojom::FrameDeleteIntention intent) {
using ProtoLevel = perfetto::protos::pbzero::FrameDeleteIntention;
switch (intent) {
case mojom::FrameDeleteIntention::kNotMainFrame:
return ProtoLevel::FRAME_DELETE_INTENTION_NOT_MAIN_FRAME;
case mojom::FrameDeleteIntention::kSpeculativeMainFrameForShutdown:
return ProtoLevel::
FRAME_DELETE_INTENTION_SPECULATIVE_MAIN_FRAME_FOR_SHUTDOWN;
case mojom::FrameDeleteIntention::
kSpeculativeMainFrameForNavigationCancelled:
return ProtoLevel::
FRAME_DELETE_INTENTION_SPECULATIVE_MAIN_FRAME_FOR_NAVIGATION_CANCELLED;
}
// All cases should've been handled by the switch case above.
NOTREACHED();
return ProtoLevel::FRAME_DELETE_INTENTION_NOT_MAIN_FRAME;
}
void WriteRenderFrameImplDeletion(perfetto::EventContext& ctx,
RenderFrameHostImpl* rfh,
mojom::FrameDeleteIntention intent) {
auto* event = ctx.event<perfetto::protos::pbzero::ChromeTrackEvent>();
auto* data = event->set_render_frame_impl_deletion();
data->set_has_pending_commit(rfh->HasPendingCommitNavigation());
data->set_has_pending_cross_document_commit(
rfh->HasPendingCommitForCrossDocumentNavigation());
data->set_frame_tree_node_id(rfh->GetFrameTreeNodeId());
data->set_intent(FrameDeleteIntentionToProto(intent));
}
// Returns an experimental process shutdown delay if the SubframeShutdownDelay
// experiment is enabled, 0 if not or if under memory pressure. This experiment
// keeps subframe processes alive for a few seconds in case they can be reused.
base::TimeDelta GetSubframeProcessShutdownDelay(
BrowserContext* browser_context) {
static constexpr base::TimeDelta kZeroDelay;
if (!base::FeatureList::IsEnabled(features::kSubframeShutdownDelay))
return kZeroDelay;
// Don't delay process shutdown under memory pressure. Does not cancel
// existing shutdown delays for processes already in delayed-shutdown state.
const auto* const memory_monitor = base::MemoryPressureMonitor::Get();
if (memory_monitor &&
memory_monitor->GetCurrentPressureLevel() >=
base::MemoryPressureListener::MEMORY_PRESSURE_LEVEL_MODERATE) {
return kZeroDelay;
}
static constexpr base::TimeDelta kShortDelay = base::Seconds(2);
static constexpr base::TimeDelta kLongDelay =
base::Milliseconds(kSubframeProcessShutdownLongDelayInMSec);
// Added to delay if based on recent performance (i.e., |kHistoryBased| and
// |kHistoryBasedLong|) to account for small variations in timing.
static constexpr base::TimeDelta kDelayBuffer = base::Seconds(1);
switch (features::kSubframeShutdownDelayTypeParam.Get()) {
case features::SubframeShutdownDelayType::kConstant: {
return kShortDelay;
}
case features::SubframeShutdownDelayType::kConstantLong: {
return kLongDelay;
}
case features::SubframeShutdownDelayType::kHistoryBased: {
const base::TimeDelta reuse_interval =
RecentlyDestroyedHosts::GetPercentileReuseInterval(50,
browser_context);
// If no subframe reuse has happened recently, don't delay process
// shutdown at all.
if (reuse_interval.is_zero())
return kZeroDelay;
return std::min(reuse_interval + kDelayBuffer, kLongDelay);
}
case features::SubframeShutdownDelayType::kHistoryBasedLong: {
const base::TimeDelta reuse_interval =
RecentlyDestroyedHosts::GetPercentileReuseInterval(75,
browser_context);
// If no subframe reuse has happened recently, don't delay process
// shutdown at all.
if (reuse_interval.is_zero())
return kZeroDelay;
return std::min(reuse_interval + kDelayBuffer, kLongDelay);
}
case features::SubframeShutdownDelayType::kMemoryBased: {
// See subframe-reuse design doc for more detail on these values.
// docs.google.com/document/d/1x_h4Gg4ForILEj8A4rMBX6d84uHWyQ9RSXmGVqMlBTk
static constexpr uint64_t kHighMemoryThreshold = 8'000'000'000;
static constexpr uint64_t kMaxMemoryThreshold = 16'000'000'000;
const uint64_t available_memory =
base::SysInfo::AmountOfAvailablePhysicalMemory();
if (available_memory <= kHighMemoryThreshold)
return kShortDelay;
if (available_memory >= kMaxMemoryThreshold)
return kLongDelay;
// Scale delay linearly based on where |available_memory| lies between
// |kHighMemoryThreshold| and |kMaxMemoryThreshold|.
const uint64_t available_memory_factor =
(available_memory - kHighMemoryThreshold) /
(kMaxMemoryThreshold - kHighMemoryThreshold);
return kShortDelay + (kLongDelay - kShortDelay) * available_memory_factor;
}
}
NOTREACHED();
}
// Returns the "document" URL used for a navigation, which might be different
// than the commit URL (CommonNavigationParam's URL) for certain cases such as
// error page and loadDataWithBaseURL() commits.
GURL GetLastDocumentURL(
NavigationRequest* request,
const mojom::DidCommitProvisionalLoadParams& params,
bool last_document_is_error_page,
const RenderFrameHostImpl::RendererURLInfo& renderer_url_info) {
if (request->DidEncounterError() ||
(request->IsSameDocument() && last_document_is_error_page)) {
// If the navigation happens on an error page, the document URL is set to
// kUnreachableWebDataURL. Note that if a same-document navigation happens
// in an error page it's possible for the document URL to have changed, but
// the browser has no way of knowing that URL since it isn't exposed in any
// way. Additionally, all current known ways to do a same-document
// navigation on an error page (history.pushState/replaceState without
// changing the URL) won't change the URL, so it's probably OK to keep using
// kUnreachableWebDataURL here.
return GURL(kUnreachableWebDataURL);
}
if (request->IsLoadDataWithBaseURL()) {
// loadDataWithBaseURL() navigation can set its own "base URL", which is
// also used by the renderer as the document URL unless the navigation
// failed (which is already accounted for in the error page case above).
return request->common_params().base_url_for_data_url;
}
if (renderer_url_info.was_loaded_from_load_data_with_base_url &&
request->IsSameDocument()) {
// If this is a same-document navigation on a document loaded from
// loadDataWithBaseURL(), it is not currently possible to figure out the
// document URL. This is because the renderer can navigate to any
// same-document URL, but that URL will not be used for
// DidCommitProvisionalLoadParams' `url` if the loading URL for the document
// is set to the data: URL. In this case, just return the last document URL,
// since at least it will have the correct origin.
// This case doesn't matter for `should_replace_current_entry` calculation
// because we always use the renderer's value for renderer-initiated
// same-document navigations (instead of trying to calculate it in in the
// browser). If other use cases of the document URL care about this case, it
// might be worth it to send the document URL on same-document navigations.
return renderer_url_info.last_document_url;
}
// For all other navigations, the document URL should be the same as the URL
// that is used to commit.
return params.url;
}
bool IsAvoidUnnecessaryBeforeUnloadCheckSyncEnabled() {
const bool is_feature_enabled = base::FeatureList::IsEnabled(
features::kAvoidUnnecessaryBeforeUnloadCheckSync);
#if BUILDFLAG(IS_ANDROID)
return is_feature_enabled &&
GetContentClient()
->browser()
->SupportsAvoidUnnecessaryBeforeUnloadCheckSync();
#else
return is_feature_enabled;
#endif
}
bool IsAvoidUnnecessaryBeforeUnloadCheckPostTaskEnabled() {
// Only one of sync or posttask should be used. If both are set, use sync.
return base::FeatureList::IsEnabled(
features::kAvoidUnnecessaryBeforeUnloadCheckPostTask) &&
!IsAvoidUnnecessaryBeforeUnloadCheckSyncEnabled();
}
// Returns true if `host` has the Window Placement permission granted.
bool IsWindowPlacementGranted(RenderFrameHost* host) {
content::PermissionController* permission_controller =
host->GetBrowserContext()->GetPermissionController();
DCHECK(permission_controller);
return permission_controller->GetPermissionStatusForCurrentDocument(
blink::PermissionType::WINDOW_PLACEMENT, host) ==
blink::mojom::PermissionStatus::GRANTED;
}
bool IsOpenGraphMetadataValid(const blink::mojom::OpenGraphMetadata* metadata) {
return !metadata->image || metadata->image->SchemeIsHTTPOrHTTPS();
}
void ForwardOpenGraphMetadataIfValid(
base::OnceCallback<void(blink::mojom::OpenGraphMetadataPtr)> callback,
blink::mojom::OpenGraphMetadataPtr metadata) {
if (IsOpenGraphMetadataValid(metadata.get()))
std::move(callback).Run(std::move(metadata));
else
std::move(callback).Run({});
}
// Creates a JavaScriptExecuteRequestForTestsCallback callback that delegates
// to the given JavaScriptResultCallback.
blink::mojom::LocalFrame::JavaScriptExecuteRequestForTestsCallback
CreateJavaScriptExecuteRequestForTestsCallback(
RenderFrameHost::JavaScriptResultCallback callback) {
if (!callback)
return base::NullCallback();
return base::BindOnce(
[](RenderFrameHost::JavaScriptResultCallback callback,
blink::mojom::JavaScriptExecutionResultType type, base::Value value) {
if (type == blink::mojom::JavaScriptExecutionResultType::kSuccess)
std::move(callback).Run(value.Clone());
else
std::move(callback).Run(base::Value());
},
std::move(callback));
}
bool ValidateUnfencedTopNavigation(
RenderFrameHostImpl* render_frame_host,
GURL& url,
int initiator_process_id,
const scoped_refptr<network::ResourceRequestBody>& post_body,
bool user_gesture) {
// Validate and modify `url` as needed.
render_frame_host->GetSiteInstance()->GetProcess()->FilterURL(
/*empty_allowed=*/false, &url);
// It should only be possible to send this IPC with this flag from an
// opaque-ads fenced frame. Opaque-ads fenced frames should always
// have the sandbox flag `allow-top-navigation-by-user-activation`.
if ((render_frame_host->frame_tree_node()->GetFencedFrameMode() !=
blink::mojom::FencedFrameMode::kOpaqueAds) ||
render_frame_host->IsSandboxed(
network::mojom::WebSandboxFlags::kTopNavigationByUserActivation)) {
// If we get the IPC elsewhere, assume the renderer is compromised.
bad_message::ReceivedBadMessage(
initiator_process_id,
bad_message::RFHI_UNFENCED_TOP_IPC_OUTSIDE_FENCED_FRAME);
return false;
}
// Perform checks that normally would be performed in
// `blink::CanNavigateHelper` but that we skipped because the target
// frame wasn't available in the renderer.
// TODO(crbug.com/1123606): Change these checks to send a BadMessage
// when the renderer-side refactor is complete.
// Javascript URLs are not allowed, because they can be used to
// communicate from the fenced frame to the embedder.
// TODO(crbug.com/1315802): It does not seem possible to reach this code
// with an uncompromised renderer, because javascript URLs don't reach
// the same IPC; instead they run inside the fenced frame as _self.
// It also seems that Javascript URLs would be caught earlier in this
// particular code path by VerifyOpenURLParams().
// In this code's final IPC resting place after the factor, make sure
// to check whether this code is redundant.
if (url.SchemeIs(url::kJavaScriptScheme)) {
render_frame_host->AddMessageToConsole(
blink::mojom::ConsoleMessageLevel::kError,
"The frame attempting navigation must be in the same fenced "
"frame tree as the target if navigating to a javascript: url");
return false;
}
// Blob URLs are not allowed, because you should not be able to exfiltrate
// arbitrary amounts of information from a fenced frame.
if (url.SchemeIs(url::kBlobScheme)) {
render_frame_host->AddMessageToConsole(
blink::mojom::ConsoleMessageLevel::kError,
"_unfencedTop may not be used with a blob: url");
return false;
}
// POST requests are not allowed, because they are asynchronous and more
// difficult to account for in privacy budgets.
if (post_body) {
render_frame_host->AddMessageToConsole(
blink::mojom::ConsoleMessageLevel::kError,
"_unfencedTop may not be used to send POST requests");
return false;
}
// User activation is required, because fenced frames use the sandbox
// flag `allow-top-navigation-by-user-activation`.
// It would be better to instead check
// `render_frame_host->frame_tree_node()->HasTransientUserActivation()`,
// but it has already been consumed at this point.
// TODO(crbug.com/848778): use the browser's source of truth for user
// activation here (and elsewhere in this file) rather than trust the
// renderer.
if (!user_gesture) {
render_frame_host->AddMessageToConsole(
blink::mojom::ConsoleMessageLevel::kError,
"The frame attempting navigation of the top-level window is "
"sandboxed with the 'allow-top-navigation-by-user-activation' "
"flag, but has no user activation (aka gesture). See "
"https://www.chromestatus.com/feature/5629582019395584.");
return false;
}
return true;
}
// Records the identifiable surface metric associated with a document created
// event when the identifiability study is active.
void RecordIdentifiabilityDocumentCreatedMetrics(
const ukm::SourceId document_ukm_source_id,
ukm::UkmRecorder* ukm_recorder,
ukm::SourceId navigation_source_id,
bool is_cross_origin_frame,
bool is_cross_site_frame,
bool is_main_frame) {
if (blink::IdentifiabilityStudySettings::Get()->IsActive()) {
blink::IdentifiabilityStudyDocumentCreated(document_ukm_source_id)
.SetNavigationSourceId(navigation_source_id)
.SetIsMainFrame(is_main_frame)
.SetIsCrossOriginFrame(is_cross_origin_frame)
.SetIsCrossSiteFrame(is_cross_site_frame)
.Record(ukm_recorder);
}
}
} // namespace
class RenderFrameHostImpl::SubresourceLoaderFactoriesConfig {
public:
static SubresourceLoaderFactoriesConfig ForLastCommittedNavigation(
RenderFrameHostImpl& frame) {
SubresourceLoaderFactoriesConfig result;
result.origin_ = frame.GetLastCommittedOrigin();
result.isolation_info_ = frame.GetIsolationInfoForSubresources();
result.client_security_state_ = frame.BuildClientSecurityState();
if (frame.coep_reporter_) {
frame.coep_reporter_->Clone(
result.coep_reporter_.BindNewPipeAndPassReceiver());
}
result.trust_token_redemption_policy_ =
DetermineAfterCommitWhetherToForbidTrustTokenRedemption(frame);
result.ukm_source_id_ =
ukm::SourceIdObj::FromInt64(frame.GetPageUkmSourceId());
return result;
}
static SubresourceLoaderFactoriesConfig ForPendingNavigation(
NavigationRequest& navigation_request) {
SubresourceLoaderFactoriesConfig result;
result.origin_ = navigation_request.GetOriginToCommit();
result.client_security_state_ =
navigation_request.BuildClientSecurityState();
result.ukm_source_id_ = ukm::SourceIdObj::FromInt64(
navigation_request.GetNextPageUkmSourceId());
// TODO(lukasza): Consider pushing the ok-vs-error differentiation into
// NavigationRequest methods (e.g. into |isolation_info_for_subresources|
// and/or |coep_reporter| methods).
if (navigation_request.DidEncounterError()) {
// Error frames gets locked down `isolation_info_` and
// `trust_token_redemption_policy_` plus an empty/uninitialized
// `coep_reporter_`.
result.isolation_info_ = net::IsolationInfo::CreateTransient();
result.trust_token_redemption_policy_ =
network::mojom::TrustTokenRedemptionPolicy::kForbid;
} else {
result.isolation_info_ =
navigation_request.isolation_info_for_subresources();
if (navigation_request.coep_reporter()) {
navigation_request.coep_reporter()->Clone(
result.coep_reporter_.BindNewPipeAndPassReceiver());
}
result.trust_token_redemption_policy_ =
DetermineWhetherToForbidTrustTokenRedemption(
navigation_request.GetRenderFrameHost(),
navigation_request.commit_params(), result.origin());
}
return result;
}
// ForPendingOrLastCommittedNavigation is useful in scenarios where there is
// no coordination between the timing of 1) a navigation commit and 2)
// subresource loader factories bundle creation. For example, using
// ForPendingOrLastCommittedNavigation from UpdateSubresourceLoaderFactories
// leads to using the correct SubresourceLoaderFactoriesConfig regardless of
// the timing of when a NetworkService crash triggers a call to
// UpdateSubresourceLoaderFactories:
// 1. If the crash happens when there is an in-flight Commit IPC to the
// renderer process, then the newly created subresource loader factories
// will arrive at the renderer *after* the Commit IPC and therefore the
// factories need to use the configuration (e.g. the origin) based on the
// pending navigation.
// 2. OTOH, if the crash happens when there is no in-flight Commit IPC then
// the newly created factories should use the configuration based on the
// last committed navigation.
//
// TODO(https://crbug.com/729021): ForPendingOrLastCommittedNavigation might
// not be needed once we have RenderDocumentHost (e.g. we swap on every
// cross-document navigation), because with RenderDocumentHost there is no
// risk of sending last-commited-navigation-based subresource loaders to a
// document different from the last-committed one.
static SubresourceLoaderFactoriesConfig ForPendingOrLastCommittedNavigation(
RenderFrameHostImpl& frame) {
NavigationRequest* navigation_request =
frame.FindLatestNavigationRequestThatIsStillCommitting();
return navigation_request ? ForPendingNavigation(*navigation_request)
: ForLastCommittedNavigation(frame);
}
~SubresourceLoaderFactoriesConfig() = default;
SubresourceLoaderFactoriesConfig(SubresourceLoaderFactoriesConfig&&) =
default;
SubresourceLoaderFactoriesConfig& operator=(
SubresourceLoaderFactoriesConfig&&) = default;
SubresourceLoaderFactoriesConfig(const SubresourceLoaderFactoriesConfig&) =
delete;
SubresourceLoaderFactoriesConfig& operator=(
const SubresourceLoaderFactoriesConfig&) = delete;
const url::Origin& origin() const { return origin_; }
const net::IsolationInfo& isolation_info() const { return isolation_info_; }
network::mojom::ClientSecurityStatePtr GetClientSecurityState() const {
return mojo::Clone(client_security_state_);
}
mojo::PendingRemote<network::mojom::CrossOriginEmbedderPolicyReporter>
GetCoepReporter() const {
mojo::PendingRemote<network::mojom::CrossOriginEmbedderPolicyReporter> p;
if (coep_reporter_) {
coep_reporter_->Clone(p.InitWithNewPipeAndPassReceiver());
}
return p;
}
const network::mojom::TrustTokenRedemptionPolicy&
trust_token_redemption_policy() const {
return trust_token_redemption_policy_;
}
const ukm::SourceIdObj& ukm_source_id() const { return ukm_source_id_; }
private:
// Private constructor - please go through the static For... methods.
SubresourceLoaderFactoriesConfig() = default;
url::Origin origin_;
net::IsolationInfo isolation_info_;
network::mojom::ClientSecurityStatePtr client_security_state_;
mojo::Remote<network::mojom::CrossOriginEmbedderPolicyReporter>
coep_reporter_;
network::mojom::TrustTokenRedemptionPolicy trust_token_redemption_policy_;
ukm::SourceIdObj ukm_source_id_;
};
#if BUILDFLAG(ENABLE_PLUGINS)
class PepperPluginInstanceHost : public mojom::PepperPluginInstanceHost {
public:
PepperPluginInstanceHost(
int32_t instance_id,
RenderFrameHostImpl* frame_host,
mojo::PendingAssociatedRemote<mojom::PepperPluginInstance> instance,
mojo::PendingAssociatedReceiver<mojom::PepperPluginInstanceHost> host)
: instance_id_(instance_id),
frame_host_(frame_host),
receiver_(this, std::move(host)),
remote_(std::move(instance)) {
frame_host_->delegate()->OnPepperInstanceCreated(frame_host_, instance_id);
remote_.set_disconnect_handler(
base::BindOnce(&RenderFrameHostImpl::PepperInstanceClosed,
base::Unretained(frame_host), instance_id_));
}
~PepperPluginInstanceHost() override = default;
// mojom::PepperPluginInstanceHost overrides.
void StartsPlayback() override {
frame_host_->delegate()->OnPepperStartsPlayback(frame_host_, instance_id_);
}
void StopsPlayback() override {
frame_host_->delegate()->OnPepperStopsPlayback(frame_host_, instance_id_);
}
void InstanceCrashed(const base::FilePath& plugin_path,
base::ProcessId plugin_pid) override {
frame_host_->delegate()->OnPepperPluginCrashed(frame_host_, plugin_path,
plugin_pid);
}
void SetVolume(double volume) { remote_->SetVolume(volume); }
private:
int32_t const instance_id_;
const raw_ptr<RenderFrameHostImpl> frame_host_;
mojo::AssociatedReceiver<mojom::PepperPluginInstanceHost> receiver_;
mojo::AssociatedRemote<mojom::PepperPluginInstance> remote_;
};
#endif // BUILDFLAG(ENABLE_PLUGINS)
struct PendingNavigation {
blink::mojom::CommonNavigationParamsPtr common_params;
blink::mojom::BeginNavigationParamsPtr begin_navigation_params;
scoped_refptr<network::SharedURLLoaderFactory> blob_url_loader_factory;
mojo::PendingAssociatedRemote<mojom::NavigationClient> navigation_client;
mojo::PendingReceiver<mojom::NavigationRendererCancellationListener>
renderer_cancellation_listener;
PendingNavigation(
blink::mojom::CommonNavigationParamsPtr common_params,
blink::mojom::BeginNavigationParamsPtr begin_navigation_params,
scoped_refptr<network::SharedURLLoaderFactory> blob_url_loader_factory,
mojo::PendingAssociatedRemote<mojom::NavigationClient> navigation_client,
mojo::PendingReceiver<mojom::NavigationRendererCancellationListener>
renderer_cancellation_listener);
};
PendingNavigation::PendingNavigation(
blink::mojom::CommonNavigationParamsPtr common_params,
blink::mojom::BeginNavigationParamsPtr begin_navigation_params,
scoped_refptr<network::SharedURLLoaderFactory> blob_url_loader_factory,
mojo::PendingAssociatedRemote<mojom::NavigationClient> navigation_client,
mojo::PendingReceiver<mojom::NavigationRendererCancellationListener>
renderer_cancellation_listener)
: common_params(std::move(common_params)),
begin_navigation_params(std::move(begin_navigation_params)),
blob_url_loader_factory(std::move(blob_url_loader_factory)),
navigation_client(std::move(navigation_client)),
renderer_cancellation_listener(
std::move(renderer_cancellation_listener)) {}
// static
RenderFrameHost* RenderFrameHost::FromID(const GlobalRenderFrameHostId& id) {
return RenderFrameHostImpl::FromID(id);
}
// static
RenderFrameHost* RenderFrameHost::FromID(int render_process_id,
int render_frame_id) {
return RenderFrameHostImpl::FromID(
GlobalRenderFrameHostId(render_process_id, render_frame_id));
}
// static
RenderFrameHost* RenderFrameHost::FromFrameToken(
int process_id,
const blink::LocalFrameToken& token) {
return RenderFrameHostImpl::FromFrameToken(process_id, token);
}
// static
void RenderFrameHost::AllowInjectingJavaScript() {
g_allow_injecting_javascript = true;
}
// static
RenderFrameHostImpl* RenderFrameHostImpl::FromID(GlobalRenderFrameHostId id) {
DCHECK_CURRENTLY_ON(BrowserThread::UI);
RoutingIDFrameMap* frames = g_routing_id_frame_map.Pointer();
auto it = frames->find(id);
return it == frames->end() ? nullptr : it->second;
}
// static
RenderFrameHostImpl* RenderFrameHostImpl::FromID(int render_process_id,
int render_frame_id) {
return RenderFrameHostImpl::FromID(
GlobalRenderFrameHostId(render_process_id, render_frame_id));
}
// static
RenderFrameHostImpl* RenderFrameHostImpl::FromFrameToken(
int process_id,
const blink::LocalFrameToken& frame_token,
mojo::ReportBadMessageCallback* process_mismatch_callback) {
DCHECK_CURRENTLY_ON(BrowserThread::UI);
auto it = g_token_frame_map.Get().find(frame_token);
if (it == g_token_frame_map.Get().end())
return nullptr;
if (it->second->GetProcess()->GetID() != process_id) {
if (process_mismatch_callback) {
SYSLOG(WARNING)
<< "Denying illegal RenderFrameHost::FromFrameToken request.";
std::move(*process_mismatch_callback)
.Run(
"Unknown LocalFrame made RenderFrameHost::FromFrameToken "
"request.");
}
return nullptr;
}
return it->second;
}
// static
RenderFrameHost* RenderFrameHost::FromAXTreeID(const ui::AXTreeID& ax_tree_id) {
return RenderFrameHostImpl::FromAXTreeID(ax_tree_id);
}
// static
RenderFrameHostImpl* RenderFrameHostImpl::FromAXTreeID(
ui::AXTreeID ax_tree_id) {
DCHECK_CURRENTLY_ON(BrowserThread::UI);
ui::AXActionHandlerRegistry::FrameID frame_id =
ui::AXActionHandlerRegistry::GetInstance()->GetFrameID(ax_tree_id);
return RenderFrameHostImpl::FromID(frame_id.first, frame_id.second);
}
// static
RenderFrameHostImpl* RenderFrameHostImpl::FromOverlayRoutingToken(
const base::UnguessableToken& token) {
DCHECK_CURRENTLY_ON(BrowserThread::UI);
auto it = g_token_frame_map.Get().find(blink::LocalFrameToken(token));
return it == g_token_frame_map.Get().end() ? nullptr : it->second;
}
// static
void RenderFrameHostImpl::ClearAllPrefetchedSignedExchangeCache() {
DCHECK_CURRENTLY_ON(BrowserThread::UI);
RoutingIDFrameMap* frames = g_routing_id_frame_map.Pointer();
for (auto it : *frames)
it.second->ClearPrefetchedSignedExchangeCache();
}
// TODO(crbug.com/1213818): Get/SetCodeCacheHostReceiverHandler are used only
// for a test in content/browser/service_worker/service_worker_browsertest
// that tests a bad message is returned on an incorrect origin. Try to find a
// way to test this without adding these additional methods.
RenderFrameHostImpl::CodeCacheHostReceiverHandler&
GetCodeCacheHostReceiverHandler() {
static base::NoDestructor<RenderFrameHostImpl::CodeCacheHostReceiverHandler>
instance;
return *instance;
}
// static
void RenderFrameHostImpl::SetCodeCacheHostReceiverHandlerForTesting(
CodeCacheHostReceiverHandler handler) {
GetCodeCacheHostReceiverHandler() = handler;
}
RenderFrameHostImpl::RenderFrameHostImpl(
SiteInstance* site_instance,
scoped_refptr<RenderViewHostImpl> render_view_host,
RenderFrameHostDelegate* delegate,
FrameTree* frame_tree,
FrameTreeNode* frame_tree_node,
int32_t routing_id,
mojo::PendingAssociatedRemote<mojom::Frame> frame_remote,
const blink::LocalFrameToken& frame_token,
bool renderer_initiated_creation_of_main_frame,
LifecycleStateImpl lifecycle_state,
scoped_refptr<BrowsingContextState> browsing_context_state)
: render_view_host_(std::move(render_view_host)),
delegate_(delegate),
site_instance_(static_cast<SiteInstanceImpl*>(site_instance)),
agent_scheduling_group_(
site_instance_->GetOrCreateAgentSchedulingGroup()),
frame_tree_(frame_tree),
frame_tree_node_(frame_tree_node),
browsing_context_state_(std::move(browsing_context_state)),
parent_(frame_tree_node_->parent()),
depth_(parent_ ? parent_->GetFrameDepth() + 1 : 0),
last_committed_site_info_(site_instance_->GetBrowserContext()),
routing_id_(routing_id),
beforeunload_timeout_delay_(RenderViewHostImpl::kUnloadTimeout),
frame_(std::move(frame_remote)),
waiting_for_init_(renderer_initiated_creation_of_main_frame),
frame_token_(frame_token),
keep_alive_handle_factory_(
agent_scheduling_group_.GetProcess(),
RenderProcessHostImpl::kKeepAliveHandleFactoryTimeout),
subframe_unload_timeout_(RenderViewHostImpl::kUnloadTimeout),
media_device_id_salt_base_(
BrowserContext::CreateRandomMediaDeviceIDSalt()),
document_associated_data_(absl::in_place, *this),
lifecycle_state_(lifecycle_state),
inner_tree_main_frame_tree_node_id_(
FrameTreeNode::kFrameTreeNodeInvalidId),
code_cache_host_receivers_(
GetProcess()->GetStoragePartition()->GetGeneratedCodeCacheContext()),
fenced_frame_status_(
frame_tree_node_->IsInFencedFrameTree()
? (frame_tree_node_->IsFencedFrameRoot()
? FencedFrameStatus::kFencedFrameRoot
: FencedFrameStatus::kIframeNestedWithinFencedFrame)
: FencedFrameStatus::kNotNestedInFencedFrame) {
TRACE_EVENT_BEGIN("navigation", "RenderFrameHostImpl",
perfetto::Track::FromPointer(this),
"render_frame_host_when_created", this);
// Update lifecycle state on track of RenderFrameHostImpl.
TRACE_EVENT_BEGIN(
"navigation",
perfetto::StaticString{LifecycleStateImplToString(lifecycle_state_)},
perfetto::Track::FromPointer(this));
DCHECK(delegate_);
DCHECK(lifecycle_state_ == LifecycleStateImpl::kSpeculative ||
lifecycle_state_ == LifecycleStateImpl::kPrerendering ||
lifecycle_state_ == LifecycleStateImpl::kActive);
// Only main frames have `waiting_for_init_` set.
DCHECK(!waiting_for_init_ || !parent_);
GetAgentSchedulingGroup().AddRoute(routing_id_, this);
g_routing_id_frame_map.Get().emplace(
GlobalRenderFrameHostId(GetProcess()->GetID(), routing_id_), this);
g_token_frame_map.Get().insert(std::make_pair(frame_token_, this));
site_instance_->group()->AddObserver(this);
auto* process = GetProcess();
process->RegisterRenderFrameHost(GetGlobalId());
GetSiteInstance()->group()->IncrementActiveFrameCount();
if (parent_) {
// All frames in a frame tree should use the same storage partition.
CHECK_EQ(parent_->GetStoragePartition(), GetStoragePartition());
// New child frames should inherit the nav_entry_id of their parent.
set_nav_entry_id(parent_->nav_entry_id());
}
if (frame_tree_->is_prerendering()) {
// TODO(https://crbug.com/1132752): Check the prerendering page is
// same-origin to the prerender trigger page.
mojo_binder_policy_applier_ =
MojoBinderPolicyApplier::CreateForSameOriginPrerendering(base::BindOnce(
&RenderFrameHostImpl::CancelPrerenderingByMojoBinderPolicy,
base::Unretained(this)));
broker_.ApplyMojoBinderPolicies(mojo_binder_policy_applier_.get());
}
if (lifecycle_state_ != LifecycleStateImpl::kSpeculative) {
// Creating a RFH in kActive state implies that it is the RFH for a
// newly-created FTN, which should still be on its initial empty document.
DCHECK(frame_tree_node_->is_on_initial_empty_document());
}
InitializePolicyContainerHost(renderer_initiated_creation_of_main_frame);
InitializePrivateNetworkRequestPolicy();
unload_event_monitor_timeout_ =
std::make_unique<TimeoutMonitor>(base::BindRepeating(
&RenderFrameHostImpl::OnUnloaded, weak_ptr_factory_.GetWeakPtr()));
beforeunload_timeout_ = std::make_unique<TimeoutMonitor>(
base::BindRepeating(&RenderFrameHostImpl::BeforeUnloadTimeout,
weak_ptr_factory_.GetWeakPtr()));
// Local roots are:
// - main frames; or
// - subframes that use a proxy to talk to their parent.
//
// Local roots require a RenderWidget for input/layout/painting.
// Note: We cannot use is_local_root() here because this block sets up the
// fields that are used by that method.
const bool setup_local_render_widget_host =
is_main_frame() || RequiresProxyToParent();
if (setup_local_render_widget_host) {
if (is_main_frame()) {
// For main frames, the RenderWidgetHost is owned by the RenderViewHost.
// TODO(https://crbug.com/545684): Once RenderViewHostImpl has-a
// RenderWidgetHostImpl, the main render frame should probably start
// owning the RenderWidgetHostImpl itself.
DCHECK(GetLocalRenderWidgetHost());
} else {
// For local child roots, the RenderFrameHost directly creates and owns
// its RenderWidgetHost.
int32_t widget_routing_id =
site_instance->GetProcess()->GetNextRoutingID();
DCHECK_EQ(nullptr, GetLocalRenderWidgetHost());
owned_render_widget_host_ = RenderWidgetHostFactory::Create(
frame_tree_, frame_tree_->render_widget_delegate(),
site_instance_->group()->GetSafeRef(), widget_routing_id,
/*hidden=*/true,
/*renderer_initiated_creation=*/false);
}
if (is_main_frame())
GetLocalRenderWidgetHost()->SetIntersectsViewport(true);
GetLocalRenderWidgetHost()->SetFrameDepth(depth_);
}
// Verify is_local_root() now indicates whether this frame is a local root or
// not. It is safe to use this method anywhere beyond this point.
DCHECK_EQ(setup_local_render_widget_host, is_local_root());
ResetPermissionsPolicy();
// New RenderFrameHostImpl are put in their own virtual browsing context
// group. Then, they can inherit from:
// 1) Their opener in RenderFrameHostImpl::CreateNewWindow().
// 2) Their navigation in RenderFrameHostImpl::DidCommitNavigationInternal().
virtual_browsing_context_group_ = CrossOriginOpenerPolicyAccessReportManager::
NextVirtualBrowsingContextGroup();
soap_by_default_virtual_browsing_context_group_ =
CrossOriginOpenerPolicyAccessReportManager::
NextVirtualBrowsingContextGroup();
// IdleManager should be unique per RenderFrame to provide proper isolation
// of overrides.
idle_manager_ = std::make_unique<IdleManagerImpl>(this);
preferred_color_scheme_ =
ui::NativeTheme::GetInstanceForWeb()->GetPreferredColorScheme() ==
ui::NativeTheme::PreferredColorScheme::kDark
? blink::mojom::PreferredColorScheme::kDark
: blink::mojom::PreferredColorScheme::kLight;
}
RenderFrameHostImpl::~RenderFrameHostImpl() {
TRACE_EVENT("navigation", "~RenderFrameHostImpl()",
ChromeTrackEvent::kRenderFrameHost, this);
// See https://crbug.com/1276535
if (check_deletion_for_bug_1276535_) {
base::debug::DumpWithoutCrashing();
}
// The lifetime of this object has ended, so remove it from the id map before
// calling any delegates/observers, so that any calls to |FromID| no longer
// return |this|.
g_routing_id_frame_map.Get().erase(
GlobalRenderFrameHostId(GetProcess()->GetID(), routing_id_));
// Remove this object from the isolatable sandboxed iframe set as well, if
// necessary.
g_routing_id_isolatable_sandboxed_iframes_set.Get().erase(GetGlobalId());
// When a RenderFrameHostImpl is deleted, it may still contain children. This
// can happen with the unload timer. It causes a RenderFrameHost to delete
// itself even if it is still waiting for its children to complete their
// unload handlers.
//
// Observers expect children to be deleted first. Do it now before notifying
// them.
ResetChildren();
// Destroying NavigationRequests may call into delegates/observers,
// so we do it early while |this| object is still in a sane state.
ResetNavigationRequests();
// Release the WebUI instances before all else as the WebUI may accesses the
// RenderFrameHost during cleanup.
base::WeakPtr<RenderFrameHostImpl> self = GetWeakPtr();
ClearWebUI();
// `ClearWebUI()` may indirectly call content's embedders and delete this.
// There are no known occurrences of it, so we assume this never happen and
// crash immediately if it does, because there are no easy ways to recover.
CHECK(self);
SetLastCommittedSiteInfo(UrlInfo());
g_token_frame_map.Get().erase(frame_token_);
auto* process = GetProcess();
site_instance_->group()->RemoveObserver(this);
process->UnregisterRenderFrameHost(GetGlobalId());
const bool was_created = is_render_frame_created();
render_frame_state_ = RenderFrameState::kDeleted;
if (was_created)
delegate_->RenderFrameDeleted(this);
// Resetting `document_associated_data_` destroys live `DocumentService` and
// `DocumentUserData` instances. It is important for them to be
// destroyed before the body of the `RenderFrameHostImpl` destructor
// completes. Among other things, this ensures that any `SafeRef`s from
// `DocumentService` and `RenderFrameHostUserData` subclasses are still valid
// when their destructors run.
document_associated_data_.reset();
// Ensure that the render process host has been notified that all audio
// streams from this frame have terminated. This is required to ensure the
// process host has the correct media stream count, which affects its
// background priority.
if (is_audible_)
OnAudibleStateChanged(false);
// If this was the last active frame in the SiteInstanceGroup, the
// DecrementActiveFrameCount call will trigger the deletion of the
// SiteInstanceGroup's proxies.
GetSiteInstance()->group()->DecrementActiveFrameCount();
// Once a RenderFrame is created in the renderer, there are three possible
// clean-up paths:
// 1. The RenderFrame can be the main frame. In this case, closing the
// associated RenderView will clean up the resources associated with the
// main RenderFrame.
// 2. The RenderFrame can be unloaded. In this case, the browser sends a
// mojom::FrameNavigationControl::UnloadFrame message for the RenderFrame
// to replace itself with a `blink::RemoteFrame`and release its associated
// resources. |lifecycle_state_| is advanced to
// LifecycleStateImpl::kRunningUnloadHandlers to track that this IPC is in
// flight.
// 3. The RenderFrame can be detached, as part of removing a subtree (due to
// navigation, unload, or DOM mutation). In this case, the browser sends
// a mojom::FrameNavigationControl::Delete message for the RenderFrame
// to detach itself and release its associated resources. If the subframe
// contains an unload handler, |lifecycle_state_| is advanced to
// LifecycleStateImpl::kRunningUnloadHandlers to track that the detach is
// in progress; otherwise, it is advanced directly to
// LifecycleStateImpl::kReadyToBeDeleted.
//
// For BackForwardCache or Prerender case:
//
// Deleting the BackForwardCache::Entry deletes immediately all the
// Render{View,Frame,FrameProxy}Host. This will destroy the main RenderFrame
// eventually as part of path #1 above:
//
// - The RenderFrameHost/RenderFrameProxyHost of the main frame are owned by
// the BackForwardCache::Entry.
// - RenderFrameHost/RenderFrameProxyHost for sub-frames are owned by their
// parent RenderFrameHost.
// - The RenderViewHost(s) are refcounted by the
// RenderFrameHost/RenderFrameProxyHost of the page. They are guaranteed not
// to be referenced by any other pages.
//
// The browser side gives the renderer a small timeout to finish processing
// unload / detach messages. When the timeout expires, the RFH will be
// removed regardless of whether or not the renderer acknowledged that it
// completed the work, to avoid indefinitely leaking browser-side state. To
// avoid leaks, ~RenderFrameHostImpl still validates that the appropriate
// cleanup IPC was sent to the renderer, by checking IsPendingDeletion().
//
// TODO(dcheng): Due to how frame detach is signalled today, there are some
// bugs in this area. In particular, subtree detach is reported from the
// bottom up, so the replicated mojom::FrameNavigationControl::Delete
// messages actually operate on a node-by-node basis rather than detaching an
// entire subtree at once...
//
// Note that this logic is fairly subtle. It needs to include all subframes
// and all speculative frames, but it should exclude case #1 (a main
// RenderFrame owned by the RenderView). It can't simply check
// |frame_tree_node_->render_manager()->speculative_frame_host()| for
// equality against |this|. The speculative frame host is unset before the
// speculative frame host is destroyed, so this condition would never be
// matched for a speculative RFH that needs to be destroyed.
//
// Directly comparing against
// |RenderViewHostImpl::GetMainRenderFrameHost()| still has one
// additional subtlety though: |GetMainRenderFrameHost()| can sometimes
// return a speculative RFH! For subframes, this obviously does not matter: a
// subframe will always pass the condition
// |render_view_host_->GetMainRenderFrameHost() != this|. However, it
// turns out that a speculative main frame being deleted will *always* pass
// this condition as well: a speculative RFH being deleted will *always* first
// be unassociated from its corresponding RFHM. Thus, it follows that
// |GetMainRenderFrameHost()| will never return the speculative main
// frame being deleted, since it must have already been unset.
if (was_created && render_view_host_->GetMainRenderFrameHost() != this) {
CHECK(IsPendingDeletion() || IsInBackForwardCache() ||
lifecycle_state() == LifecycleStateImpl::kPrerendering ||
lifecycle_state() == LifecycleStateImpl::kSpeculative);
}
GetAgentSchedulingGroup().RemoveRoute(routing_id_);
// Null out the unload timer; in crash dumps this member will be null only if
// the dtor has run. (It may also be null in tests.)
unload_event_monitor_timeout_.reset();
// Delete this before destroying the widget, to guard against reentrancy
// by in-process screen readers such as JAWS.
browser_accessibility_manager_.reset();
// Note: The RenderWidgetHost of the main frame is owned by the RenderViewHost
// instead. In this case the RenderViewHost is responsible for shutting down
// its RenderViewHost.
if (owned_render_widget_host_)
owned_render_widget_host_->ShutdownAndDestroyWidget(false);
render_view_host_.reset();
// If another frame is waiting for a beforeunload completion callback from
// this frame, simulate it now.
RenderFrameHostImpl* beforeunload_initiator = GetBeforeUnloadInitiator();
if (beforeunload_initiator && beforeunload_initiator != this) {
base::TimeTicks approx_renderer_start_time = send_before_unload_start_time_;
beforeunload_initiator->ProcessBeforeUnloadCompletedFromFrame(
/*proceed=*/true, /*treat_as_final_completion_callback=*/false, this,
/*is_frame_being_destroyed=*/true, approx_renderer_start_time,
base::TimeTicks::Now(), /*for_legacy=*/false);
}
if (prefetched_signed_exchange_cache_)
prefetched_signed_exchange_cache_->RecordHistograms();
// Matches the pair of TRACE_EVENT_BEGINS in the constructor: one for
// "RenderFrameHostImpl" slice itself, one for the slice with the lifecycle
// state name.
TRACE_EVENT_END("navigation", perfetto::Track::FromPointer(this));
TRACE_EVENT_END("navigation", perfetto::Track::FromPointer(this));
}
int RenderFrameHostImpl::GetRoutingID() const {
return routing_id_;
}
const blink::LocalFrameToken& RenderFrameHostImpl::GetFrameToken() {
return frame_token_;
}
const base::UnguessableToken& RenderFrameHostImpl::GetReportingSource() {
DCHECK(!document_associated_data_->reporting_source.is_empty());
return document_associated_data_->reporting_source;
}
ui::AXTreeID RenderFrameHostImpl::GetAXTreeID() {
return ax_tree_id();
}
const blink::LocalFrameToken& RenderFrameHostImpl::GetTopFrameToken() {
RenderFrameHostImpl* frame = this;
while (frame->parent_) {
frame = frame->parent_;
}
return frame->GetFrameToken();
}
void RenderFrameHostImpl::AudioContextPlaybackStarted(int audio_context_id) {
delegate_->AudioContextPlaybackStarted(this, audio_context_id);
}
void RenderFrameHostImpl::AudioContextPlaybackStopped(int audio_context_id) {
delegate_->AudioContextPlaybackStopped(this, audio_context_id);
}
// The current frame went into the BackForwardCache.
void RenderFrameHostImpl::DidEnterBackForwardCache() {
TRACE_EVENT0("navigation", "RenderFrameHostImpl::EnterBackForwardCache");
DCHECK(IsBackForwardCacheEnabled());
DCHECK_EQ(lifecycle_state(), LifecycleStateImpl::kActive);
bool was_in_primary_main_frame = IsInPrimaryMainFrame();
SetLifecycleState(LifecycleStateImpl::kInBackForwardCache);
// Pages in the back-forward cache are automatically evicted after a certain
// time.
if (was_in_primary_main_frame)
StartBackForwardCacheEvictionTimer();
for (auto& child : children_)
child->current_frame_host()->DidEnterBackForwardCache();
for (auto& entry : service_worker_container_hosts_) {
if (base::WeakPtr<ServiceWorkerContainerHost> host = entry.second)
host->OnEnterBackForwardCache();
}
DedicatedWorkerHostsForDocument::GetOrCreateForCurrentDocument(this)
->OnEnterBackForwardCache();
#if BUILDFLAG(IS_P2P_ENABLED)
GetProcess()->PauseSocketManagerForRenderFrameHost(GetGlobalId());
#endif // BUILDFLAG(IS_P2P_ENABLED)
}
// The frame as been restored from the BackForwardCache.
void RenderFrameHostImpl::WillLeaveBackForwardCache() {
TRACE_EVENT0("navigation", "RenderFrameHostImpl::LeaveBackForwardCache");
DCHECK(IsBackForwardCacheEnabled());
DCHECK_EQ(lifecycle_state(), LifecycleStateImpl::kInBackForwardCache);
if (back_forward_cache_eviction_timer_.IsRunning())
back_forward_cache_eviction_timer_.Stop();
for (auto& child : children_)
child->current_frame_host()->WillLeaveBackForwardCache();
for (auto& entry : service_worker_container_hosts_) {
if (base::WeakPtr<ServiceWorkerContainerHost> host = entry.second)
host->OnRestoreFromBackForwardCache();
}
DedicatedWorkerHostsForDocument::GetOrCreateForCurrentDocument(this)
->OnRestoreFromBackForwardCache();
#if BUILDFLAG(IS_P2P_ENABLED)
GetProcess()->ResumeSocketManagerForRenderFrameHost(GetGlobalId());
#endif // BUILDFLAG(IS_P2P_ENABLED)
}
mojom::DidCommitProvisionalLoadParamsPtr
RenderFrameHostImpl::TakeLastCommitParams() {
return std::move(last_commit_params_);
}
void RenderFrameHostImpl::StartBackForwardCacheEvictionTimer() {
DCHECK(IsInBackForwardCache());
base::TimeDelta evict_after =
BackForwardCacheImpl::GetTimeToLiveInBackForwardCache();
back_forward_cache_eviction_timer_.SetTaskRunner(
frame_tree()->controller().GetBackForwardCache().GetTaskRunner());
back_forward_cache_eviction_timer_.Start(
FROM_HERE, evict_after,
base::BindOnce(&RenderFrameHostImpl::EvictFromBackForwardCacheWithReason,
weak_ptr_factory_.GetWeakPtr(),
BackForwardCacheMetrics::NotRestoredReason::kTimeout));
}
void RenderFrameHostImpl::DisableBackForwardCache(
BackForwardCache::DisabledReason reason) {
back_forward_cache_disabled_reasons_.insert(reason);
MaybeEvictFromBackForwardCache();
}
void RenderFrameHostImpl::ClearDisableBackForwardCache(
BackForwardCache::DisabledReason reason) {
back_forward_cache_disabled_reasons_.erase(reason);
}
void RenderFrameHostImpl::DisableProactiveBrowsingInstanceSwapForTesting() {
// This should only be called on primary main frames.
DCHECK(IsInPrimaryMainFrame());
has_test_disabled_proactive_browsing_instance_swap_ = true;
}
void RenderFrameHostImpl::OnGrantedMediaStreamAccess() {
was_granted_media_access_ = true;
MaybeEvictFromBackForwardCache();
}
void RenderFrameHostImpl::OnPortalActivated(
std::unique_ptr<Portal> predecessor,
mojo::PendingAssociatedRemote<blink::mojom::Portal> pending_portal,
mojo::PendingAssociatedReceiver<blink::mojom::PortalClient> client_receiver,
blink::TransferableMessage data,
uint64_t trace_id,
base::OnceCallback<void(blink::mojom::PortalActivateResult)> callback) {
auto it = portals_.insert(std::move(predecessor)).first;
TRACE_EVENT_WITH_FLOW0("navigation", "RenderFrameHostImpl::OnPortalActivated",
TRACE_ID_GLOBAL(trace_id),
TRACE_EVENT_FLAG_FLOW_IN | TRACE_EVENT_FLAG_FLOW_OUT);
GetAssociatedLocalMainFrame()->OnPortalActivated(
(*it)->portal_token(), std::move(pending_portal),
std::move(client_receiver), std::move(data), trace_id,
base::BindOnce(
[](base::OnceCallback<void(blink::mojom::PortalActivateResult)>
callback,
blink::mojom::PortalActivateResult result) {
switch (result) {
case blink::mojom::PortalActivateResult::kPredecessorWillUnload:
case blink::mojom::PortalActivateResult::kPredecessorWasAdopted:
// These values are acceptable from the renderer.
break;
case blink::mojom::PortalActivateResult::
kRejectedDueToPredecessorNavigation:
case blink::mojom::PortalActivateResult::
kRejectedDueToPortalNotReady:
case blink::mojom::PortalActivateResult::
kRejectedDueToErrorInPortal:
case blink::mojom::PortalActivateResult::kDisconnected:
case blink::mojom::PortalActivateResult::kNotImplemented:
case blink::mojom::PortalActivateResult::kAbortedDueToBug:
// The renderer is misbehaving.
mojo::ReportBadMessage(
"Unexpected PortalActivateResult from renderer");
result = blink::mojom::PortalActivateResult::kAbortedDueToBug;
break;
}
std::move(callback).Run(result);
},
std::move(callback)));
}
void RenderFrameHostImpl::OnPortalCreatedForTesting(
std::unique_ptr<Portal> portal) {
portals_.insert(std::move(portal));
}
Portal* RenderFrameHostImpl::FindPortalByToken(
const blink::PortalToken& portal_token) {
auto it =
std::find_if(portals_.begin(), portals_.end(), [&](const auto& portal) {
return portal->portal_token() == portal_token;
});
return it == portals_.end() ? nullptr : it->get();
}
std::vector<Portal*> RenderFrameHostImpl::GetPortals() const {
std::vector<Portal*> result;
for (const auto& portal : portals_)
result.push_back(portal.get());
return result;
}
void RenderFrameHostImpl::DestroyPortal(Portal* portal) {
auto it = portals_.find(portal);
CHECK(it != portals_.end());
std::unique_ptr<Portal> owned_portal(std::move(*it));
portals_.erase(it);
// |owned_portal| is deleted as it goes out of scope.
}
void RenderFrameHostImpl::ForwardMessageFromHost(
blink::TransferableMessage message,
const url::Origin& source_origin) {
DCHECK_EQ(source_origin, GetLastCommittedOrigin());
GetAssociatedLocalMainFrame()->ForwardMessageFromHost(std::move(message),
source_origin);
}
SiteInstanceImpl* RenderFrameHostImpl::GetSiteInstance() const {
return site_instance_.get();
}
RenderProcessHost* RenderFrameHostImpl::GetProcess() const {
return agent_scheduling_group_.GetProcess();
}
AgentSchedulingGroupHost& RenderFrameHostImpl::GetAgentSchedulingGroup() {
return agent_scheduling_group_;
}
RenderFrameHostImpl* RenderFrameHostImpl::GetParent() const {
return parent_;
}
PageImpl& RenderFrameHostImpl::GetPage() {
return *GetMainFrame()->document_associated_data_->owned_page.get();
}
bool RenderFrameHostImpl::IsDescendantOfWithinFrameTree(
RenderFrameHostImpl* ancestor) {
if (!ancestor || !ancestor->child_count())
return false;
for (RenderFrameHostImpl* current = GetParent(); current;
current = current->GetParent()) {
if (current == ancestor)
return true;
}
return false;
}
bool RenderFrameHostImpl::IsFencedFrameRoot() const {
return fenced_frame_status_ == FencedFrameStatus::kFencedFrameRoot;
}
bool RenderFrameHostImpl::IsNestedWithinFencedFrame() const {
switch (fenced_frame_status_) {
case FencedFrameStatus::kNotNestedInFencedFrame:
return false;
case FencedFrameStatus::kFencedFrameRoot:
return true;
case FencedFrameStatus::kIframeNestedWithinFencedFrame:
return true;
}
}
void RenderFrameHostImpl::ForEachRenderFrameHost(
FrameIterationCallback on_frame) {
ForEachRenderFrameHost(FrameIterationWrapper(on_frame));
}
void RenderFrameHostImpl::ForEachRenderFrameHost(
FrameIterationAlwaysContinueCallback on_frame) {
ForEachRenderFrameHost(FrameIterationWrapper(on_frame));
}
void RenderFrameHostImpl::ForEachRenderFrameHost(
FrameIterationCallbackImpl on_frame) {
ForEachRenderFrameHostImpl(on_frame, /*include_speculative=*/false);
}
void RenderFrameHostImpl::ForEachRenderFrameHost(
FrameIterationAlwaysContinueCallbackImpl on_frame) {
ForEachRenderFrameHost(FrameIterationWrapper(on_frame));
}
void RenderFrameHostImpl::ForEachRenderFrameHostIncludingSpeculative(
FrameIterationCallbackImpl on_frame) {
ForEachRenderFrameHostImpl(on_frame, /*include_speculative=*/true);
}
void RenderFrameHostImpl::ForEachRenderFrameHostIncludingSpeculative(
FrameIterationAlwaysContinueCallbackImpl on_frame) {
ForEachRenderFrameHostIncludingSpeculative(FrameIterationWrapper(on_frame));
}
void RenderFrameHostImpl::ForEachRenderFrameHostImpl(
FrameIterationCallbackImpl on_frame,
bool include_speculative) {
DCHECK_CURRENTLY_ON(BrowserThread::UI);
if (!include_speculative &&
(lifecycle_state() == LifecycleStateImpl::kSpeculative ||
lifecycle_state() == LifecycleStateImpl::kPendingCommit)) {
return;
}
// Since |this| may not be current in its FrameTree, we can't begin iterating
// from |frame_tree_node()|, so we special case the first invocation for
// |this| and then actually start iterating over the subtree starting with
// our children's FrameTreeNodes.
bool skip_children_of_starting_frame = false;
switch (on_frame.Run(this)) {
case FrameIterationAction::kContinue:
break;
case FrameIterationAction::kSkipChildren:
skip_children_of_starting_frame = true;
break;
case FrameIterationAction::kStop:
return;
}
// Potentially include our FrameTreeNode's speculative RenderFrameHost, but
// only if |this| is current in its FrameTree.
// TODO(1264145): Avoid having a RenderFrameHost access its FrameTreeNode's
// speculative RenderFrameHost by moving
// ForEachRenderFrameHostIncludingSpeculative from RenderFrameHostImpl or
// possibly removing it entirely.
if (include_speculative && frame_tree_node()->current_frame_host() == this) {
RenderFrameHostImpl* speculative_frame_host =
frame_tree_node()->render_manager()->speculative_frame_host();
if (speculative_frame_host) {
DCHECK_EQ(speculative_frame_host->child_count(), 0U);
switch (on_frame.Run(speculative_frame_host)) {
case FrameIterationAction::kContinue:
case FrameIterationAction::kSkipChildren:
break;
case FrameIterationAction::kStop:
return;
}
}
}
if (skip_children_of_starting_frame)
return;
FrameTree::NodeRange ftn_range = FrameTree::SubtreeAndInnerTreeNodes(this);
FrameTree::NodeIterator it = ftn_range.begin();
const FrameTree::NodeIterator end = ftn_range.end();
while (it != end) {
FrameTreeNode* node = *it;
RenderFrameHostImpl* frame_host = node->current_frame_host();
if (frame_host) {
switch (on_frame.Run(frame_host)) {
case FrameIterationAction::kContinue:
++it;
break;
case FrameIterationAction::kSkipChildren:
it.AdvanceSkippingChildren();
break;
case FrameIterationAction::kStop:
return;
}
}
if (include_speculative) {
RenderFrameHostImpl* speculative_frame_host =
node->render_manager()->speculative_frame_host();
if (speculative_frame_host) {
DCHECK_EQ(speculative_frame_host->child_count(), 0U);
switch (on_frame.Run(speculative_frame_host)) {
case FrameIterationAction::kContinue:
case FrameIterationAction::kSkipChildren:
break;
case FrameIterationAction::kStop:
return;
}
}
}
}
}
int RenderFrameHostImpl::GetFrameTreeNodeId() const {
return frame_tree_node_->frame_tree_node_id();
}
const base::UnguessableToken& RenderFrameHostImpl::GetDevToolsFrameToken() {
return frame_tree_node_->devtools_frame_token();
}
absl::optional<base::UnguessableToken>
RenderFrameHostImpl::GetEmbeddingToken() {
return embedding_token_;
}
const std::string& RenderFrameHostImpl::GetFrameName() {
return browsing_context_state_->frame_name();
}
bool RenderFrameHostImpl::IsFrameDisplayNone() {
return frame_tree_node()->frame_owner_properties().is_display_none;
}
const absl::optional<gfx::Size>& RenderFrameHostImpl::GetFrameSize() {
return frame_size_;
}
size_t RenderFrameHostImpl::GetFrameDepth() {
return depth_;
}
bool RenderFrameHostImpl::IsCrossProcessSubframe() {
if (is_main_frame() || GetSiteInstance() == parent_->GetSiteInstance())
return false;
return GetSiteInstance()->GetProcess() !=
parent_->GetSiteInstance()->GetProcess();
}
bool RenderFrameHostImpl::RequiresProxyToParent() {
if (is_main_frame())
return false;
return GetSiteInstance() != parent_->GetSiteInstance();
}
RenderFrameHost::WebExposedIsolationLevel
RenderFrameHostImpl::GetWebExposedIsolationLevel() {
WebExposedIsolationInfo info =
GetSiteInstance()->GetSiteInfo().web_exposed_isolation_info();
if (info.is_isolated_application()) {
// TODO(crbug.com/1159832): Check the document policy once it's available to
// find out if this frame is actually isolated.
return RenderFrameHost::WebExposedIsolationLevel::kMaybeIsolatedApplication;
} else if (info.is_isolated()) {
// TODO(crbug.com/1159832): Check the document policy once it's available to
// find out if this frame is actually isolated.
return RenderFrameHost::WebExposedIsolationLevel::kMaybeIsolated;
}
return RenderFrameHost::WebExposedIsolationLevel::kNotIsolated;
}
const GURL& RenderFrameHostImpl::GetLastCommittedURL() const {
return last_committed_url_;
}
const url::Origin& RenderFrameHostImpl::GetLastCommittedOrigin() const {
return last_committed_origin_;
}
const net::NetworkIsolationKey& RenderFrameHostImpl::GetNetworkIsolationKey() {
DCHECK(!isolation_info_.IsEmpty());
return isolation_info_.network_isolation_key();
}
const net::IsolationInfo&
RenderFrameHostImpl::GetIsolationInfoForSubresources() {
DCHECK(!isolation_info_.IsEmpty());
return isolation_info_;
}
net::IsolationInfo
RenderFrameHostImpl::GetPendingIsolationInfoForSubresources() {
// TODO(https://crbug.com/1211126): Figure out if
// ForPendingOrLastCommittedNavigation is correct below (it might not be).
auto config =
SubresourceLoaderFactoriesConfig::ForPendingOrLastCommittedNavigation(
*this);
DCHECK(!config.isolation_info().IsEmpty());
return config.isolation_info();
}
void RenderFrameHostImpl::GetCanonicalUrl(
base::OnceCallback<void(const absl::optional<GURL>&)> callback) {
if (IsRenderFrameLive()) {
// Validate that the URL returned by the renderer is HTTP(S) only. It is
// allowed to be cross-origin.
auto validate_and_forward =
[](base::OnceCallback<void(const absl::optional<GURL>&)> callback,
const absl::optional<GURL>& url) {
if (url && url->is_valid() && url->SchemeIsHTTPOrHTTPS()) {
std::move(callback).Run(url);
} else {
std::move(callback).Run(absl::nullopt);
}
};
GetAssociatedLocalFrame()->GetCanonicalUrlForSharing(
base::BindOnce(validate_and_forward, std::move(callback)));
} else {
std::move(callback).Run(absl::nullopt);
}
}
void RenderFrameHostImpl::GetOpenGraphMetadata(
base::OnceCallback<void(blink::mojom::OpenGraphMetadataPtr)> callback) {
if (IsRenderFrameLive()) {
GetAssociatedLocalFrame()->GetOpenGraphMetadata(
base::BindOnce(&ForwardOpenGraphMetadataIfValid, std::move(callback)));
} else {
std::move(callback).Run({});
}
}
bool RenderFrameHostImpl::IsErrorDocument() {
// This shouldn't be called before committing the document as this value is
// set during call to RenderFrameHostImpl::DidNavigate which happens after
// commit.
DCHECK_NE(lifecycle_state(), LifecycleStateImpl::kSpeculative);
DCHECK_NE(lifecycle_state(), LifecycleStateImpl::kPendingCommit);
return is_error_page_;
}
DocumentRef RenderFrameHostImpl::GetDocumentRef() {
return DocumentRef(document_associated_data_->weak_ptr_factory.GetSafeRef());
}
WeakDocumentPtr RenderFrameHostImpl::GetWeakDocumentPtr() {
return WeakDocumentPtr(
document_associated_data_->weak_ptr_factory.GetWeakPtr());
}
void RenderFrameHostImpl::GetSerializedHtmlWithLocalLinks(
const base::flat_map<GURL, base::FilePath>& url_map,
const base::flat_map<blink::FrameToken, base::FilePath>& frame_token_map,
bool save_with_empty_url,
mojo::PendingRemote<mojom::FrameHTMLSerializerHandler> serializer_handler) {
if (!IsRenderFrameLive())
return;
GetMojomFrameInRenderer()->GetSerializedHtmlWithLocalLinks(
url_map, frame_token_map, save_with_empty_url,
std::move(serializer_handler));
}
void RenderFrameHostImpl::SetWantErrorMessageStackTrace() {
GetMojomFrameInRenderer()->SetWantErrorMessageStackTrace();
}
void RenderFrameHostImpl::ExecuteMediaPlayerActionAtLocation(
const gfx::Point& location,
const blink::mojom::MediaPlayerAction& action) {
auto media_player_action = blink::mojom::MediaPlayerAction::New();
media_player_action->type = action.type;
media_player_action->enable = action.enable;
gfx::PointF point_in_view = GetView()->TransformRootPointToViewCoordSpace(
gfx::PointF(location.x(), location.y()));
GetAssociatedLocalFrame()->MediaPlayerActionAt(
gfx::Point(point_in_view.x(), point_in_view.y()),
std::move(media_player_action));
}
bool RenderFrameHostImpl::CreateNetworkServiceDefaultFactory(
mojo::PendingReceiver<network::mojom::URLLoaderFactory>
default_factory_receiver) {
// Factory config below is based on the last committed navigation, under the
// assumptions that the caller wants a factory that acts on behalf of the
// *currently* committed document. This assumption is typically valid for
// callers that are responding to an IPC coming from the renderer process. If
// the caller wanted a factory associated with a pending navigation, then the
// config won't be correct. For more details, please see the doc comment of
// ForPendingOrLastCommittedNavigation.
auto subresource_loader_factories_config =
SubresourceLoaderFactoriesConfig::ForLastCommittedNavigation(*this);
return CreateNetworkServiceDefaultFactoryAndObserve(
CreateURLLoaderFactoryParamsForMainWorld(
subresource_loader_factories_config,
"RFHI::CreateNetworkServiceDefaultFactory"),
subresource_loader_factories_config.ukm_source_id(),
std::move(default_factory_receiver));
}
std::unique_ptr<blink::PendingURLLoaderFactoryBundle>
RenderFrameHostImpl::CreateSubresourceLoaderFactoriesForInitialEmptyDocument() {
// This method should only be called (by RenderViewHostImpl::CreateRenderView)
// when creating a new local main frame in a Renderer process.
DCHECK(!GetParent());
// Expecting the frame to be at the initial empty document.
// Not DCHECK-ing `last_committed_origin_`, because it is not reset by
// RenderFrameHostImpl::RenderProcessExited for crashed frames.
DCHECK_EQ(GURL(), last_committed_url_);
auto subresource_loader_factories =
std::make_unique<blink::PendingURLLoaderFactoryBundle>();
switch (lifecycle_state()) {
case RenderFrameHostImpl::LifecycleStateImpl::kInBackForwardCache:
case RenderFrameHostImpl::LifecycleStateImpl::kPendingCommit:
case RenderFrameHostImpl::LifecycleStateImpl::kReadyToBeDeleted:
case RenderFrameHostImpl::LifecycleStateImpl::kRunningUnloadHandlers:
// A newly-created frame shouldn't be in any of the states above.
NOTREACHED();
break;
case RenderFrameHostImpl::LifecycleStateImpl::kSpeculative:
// No subresource requests should be initiated in the speculative frame.
// Serving an empty bundle of `subresource_loader_factories` will
// desirably lead to a crash in URLLoaderFactoryBundle::GetFactory (see
// also the DCHECK there) if the speculative frame attempts to start a
// subresource load.
break;
case RenderFrameHostImpl::LifecycleStateImpl::kActive:
case RenderFrameHostImpl::LifecycleStateImpl::kPrerendering:
CreateNetworkServiceDefaultFactory(
subresource_loader_factories->pending_default_factory()
.InitWithNewPipeAndPassReceiver());
// The caller will send the returned factory to the renderer process.
// Therefore, after a NetworkService crash we need to push to the renderer
// an updated factory.
recreate_default_url_loader_factory_after_network_service_crash_ = true;
break;
}
return subresource_loader_factories;
}
void RenderFrameHostImpl::MarkIsolatedWorldsAsRequiringSeparateURLLoaderFactory(
const base::flat_set<url::Origin>& isolated_world_origins,
bool push_to_renderer_now) {
size_t old_size =
isolated_worlds_requiring_separate_url_loader_factory_.size();
isolated_worlds_requiring_separate_url_loader_factory_.insert(
isolated_world_origins.begin(), isolated_world_origins.end());
size_t new_size =
isolated_worlds_requiring_separate_url_loader_factory_.size();
bool insertion_took_place = (old_size != new_size);
// Push the updated set of factories to the renderer, but only if
// 1) the caller requested an immediate push (e.g. for content scripts
// injected programmatically chrome.tabs.executeCode, but not for content
// scripts declared in the manifest - the difference is that the latter
// happen at a commit and the factories can just be send in the commit
// IPC).
// 2) an insertion actually took place / the factories have been modified.
//
// See also the doc comment of `PendingURLLoaderFactoryBundle::OriginMap`
// (the type of `pending_isolated_world_factories` that are set below).
if (push_to_renderer_now && insertion_took_place) {
// The `config` of the new factories might need to depend on the pending
// (rather than the last committed) navigation, because we can't predict if
// an in-flight Commit IPC might be present when an extension injects a
// content script and MarkIsolatedWorlds... is called. See also the doc
// comment for the ForPendingOrLastCommittedNavigation method.
auto config =
SubresourceLoaderFactoriesConfig::ForPendingOrLastCommittedNavigation(
*this);
std::unique_ptr<blink::PendingURLLoaderFactoryBundle>
subresource_loader_factories =
std::make_unique<blink::PendingURLLoaderFactoryBundle>();
subresource_loader_factories->pending_isolated_world_factories() =
CreateURLLoaderFactoriesForIsolatedWorlds(config,
isolated_world_origins);
GetMojomFrameInRenderer()->UpdateSubresourceLoaderFactories(
std::move(subresource_loader_factories));
}
}
bool RenderFrameHostImpl::IsSandboxed(network::mojom::WebSandboxFlags flags) {
return static_cast<int>(active_sandbox_flags()) & static_cast<int>(flags);
}
blink::web_pref::WebPreferences
RenderFrameHostImpl::GetOrCreateWebPreferences() {
return delegate()->GetOrCreateWebPreferences();
}
blink::PendingURLLoaderFactoryBundle::OriginMap
RenderFrameHostImpl::CreateURLLoaderFactoriesForIsolatedWorlds(
const SubresourceLoaderFactoriesConfig& config,
const base::flat_set<url::Origin>& isolated_world_origins) {
blink::PendingURLLoaderFactoryBundle::OriginMap result;
for (const url::Origin& isolated_world_origin : isolated_world_origins) {
network::mojom::URLLoaderFactoryParamsPtr factory_params =
URLLoaderFactoryParamsHelper::CreateForIsolatedWorld(
this, isolated_world_origin, config.origin(),
config.isolation_info(), config.GetClientSecurityState(),
config.trust_token_redemption_policy());
mojo::PendingRemote<network::mojom::URLLoaderFactory> factory_remote;
CreateNetworkServiceDefaultFactoryAndObserve(
std::move(factory_params),
ukm::kInvalidSourceIdObj, /* isolated from page */
factory_remote.InitWithNewPipeAndPassReceiver());
result[isolated_world_origin] = std::move(factory_remote);
}
return result;
}
gfx::NativeView RenderFrameHostImpl::GetNativeView() {
RenderWidgetHostView* view = render_view_host_->GetWidget()->GetView();
if (!view)
return nullptr;
return view->GetNativeView();
}
void RenderFrameHostImpl::AddMessageToConsole(
blink::mojom::ConsoleMessageLevel level,
const std::string& message) {
AddMessageToConsoleImpl(level, message, /*discard_duplicates=*/false);
}
void RenderFrameHostImpl::ExecuteJavaScriptMethod(
const std::u16string& object_name,
const std::u16string& method_name,
base::Value::List arguments,
JavaScriptResultCallback callback) {
DCHECK_CURRENTLY_ON(BrowserThread::UI);
CHECK(CanExecuteJavaScript());
AssertNonSpeculativeFrame();
const bool wants_result = !callback.is_null();
GetAssociatedLocalFrame()->JavaScriptMethodExecuteRequest(
object_name, method_name, std::move(arguments), wants_result,
std::move(callback));
}
void RenderFrameHostImpl::ExecuteJavaScript(const std::u16string& javascript,
JavaScriptResultCallback callback) {
DCHECK_CURRENTLY_ON(BrowserThread::UI);
CHECK(CanExecuteJavaScript());
AssertNonSpeculativeFrame();
const bool wants_result = !callback.is_null();
GetAssociatedLocalFrame()->JavaScriptExecuteRequest(javascript, wants_result,
std::move(callback));
}
void RenderFrameHostImpl::ExecuteJavaScriptInIsolatedWorld(
const std::u16string& javascript,
JavaScriptResultCallback callback,
int32_t world_id) {
DCHECK_CURRENTLY_ON(BrowserThread::UI);
DCHECK_GT(world_id, ISOLATED_WORLD_ID_GLOBAL);
DCHECK_LE(world_id, ISOLATED_WORLD_ID_MAX);
AssertNonSpeculativeFrame();
const bool wants_result = !callback.is_null();
GetAssociatedLocalFrame()->JavaScriptExecuteRequestInIsolatedWorld(
javascript, wants_result, world_id, std::move(callback));
}
void RenderFrameHostImpl::ExecuteJavaScriptForTests(
const std::u16string& javascript,
JavaScriptResultCallback callback,
int32_t world_id) {
ExecuteJavaScriptForTests( // IN-TEST
javascript, /*has_user_gesture=*/false,
/*resolve_promises=*/false, world_id,
CreateJavaScriptExecuteRequestForTestsCallback(std::move(callback)));
}
void RenderFrameHostImpl::ExecuteJavaScriptWithUserGestureForTests(
const std::u16string& javascript,
JavaScriptResultCallback callback,
int32_t world_id) {
ExecuteJavaScriptForTests( // IN-TEST
javascript, /*has_user_gesture=*/true,
/*resolve_promises=*/false, world_id,
CreateJavaScriptExecuteRequestForTestsCallback(std::move(callback)));
}
void RenderFrameHostImpl::ExecuteJavaScriptForTests(
const std::u16string& javascript,
bool has_user_gesture,
bool resolve_promises,
int32_t world_id,
JavaScriptResultAndTypeCallback callback) {
DCHECK_CURRENTLY_ON(BrowserThread::UI);
AssertNonSpeculativeFrame();
if (has_user_gesture) {
// TODO(mustaq): The render-to-browser state update caused by the below
// JavaScriptExecuteRequestsForTests call is redundant with this update. We
// should determine if the redundancy can be removed.
frame_tree_node()->UpdateUserActivationState(
blink::mojom::UserActivationUpdateType::kNotifyActivation,
blink::mojom::UserActivationNotificationType::kTest);
}
GetAssociatedLocalFrame()->JavaScriptExecuteRequestForTests( // IN-TEST
javascript, has_user_gesture, resolve_promises, world_id,
std::move(callback));
}
void RenderFrameHostImpl::ExecutePluginActionAtLocalLocation(
const gfx::Point& local_location,
blink::mojom::PluginActionType plugin_action) {
GetAssociatedLocalFrame()->PluginActionAt(local_location, plugin_action);
}
void RenderFrameHostImpl::CopyImageAt(int x, int y) {
gfx::PointF point_in_view =
GetView()->TransformRootPointToViewCoordSpace(gfx::PointF(x, y));
GetAssociatedLocalFrame()->CopyImageAt(
gfx::Point(point_in_view.x(), point_in_view.y()));
}
void RenderFrameHostImpl::SaveImageAt(int x, int y) {
gfx::PointF point_in_view =
GetView()->TransformRootPointToViewCoordSpace(gfx::PointF(x, y));
GetAssociatedLocalFrame()->SaveImageAt(
gfx::Point(point_in_view.x(), point_in_view.y()));
}
RenderViewHost* RenderFrameHostImpl::GetRenderViewHost() const {
return render_view_host_.get();
}
service_manager::InterfaceProvider* RenderFrameHostImpl::GetRemoteInterfaces() {
DCHECK(IsRenderFrameLive());
return remote_interfaces_.get();
}
blink::AssociatedInterfaceProvider*
RenderFrameHostImpl::GetRemoteAssociatedInterfaces() {
if (!remote_associated_interfaces_) {
mojo::AssociatedRemote<blink::mojom::AssociatedInterfaceProvider>
remote_interfaces;
if (GetAgentSchedulingGroup().GetChannel()) {
GetAgentSchedulingGroup().GetRemoteRouteProvider()->GetRoute(
GetRoutingID(), remote_interfaces.BindNewEndpointAndPassReceiver());
} else {
// The channel may not be initialized in some tests environments. In this
// case we set up a dummy interface provider.
std::ignore = remote_interfaces.BindNewEndpointAndPassDedicatedReceiver();
}
remote_associated_interfaces_ =
std::make_unique<blink::AssociatedInterfaceProvider>(
remote_interfaces.Unbind());
}
return remote_associated_interfaces_.get();
}
PageVisibilityState RenderFrameHostImpl::GetVisibilityState() {
// Works around the crashes seen in https://crbug.com/501863, where the
// active WebContents from a browser iterator may contain a render frame
// detached from the frame tree. This tries to find a RenderWidgetHost
// attached to an ancestor frame, and defaults to visibility hidden if
// it fails.
// TODO(yfriedman, peter): Ideally this would never be called on an
// unattached frame and we could omit this check. See
// https://crbug.com/615867.
RenderFrameHostImpl* frame = this;
while (frame) {
if (frame->GetLocalRenderWidgetHost())
break;
frame = frame->GetParent();
}
if (!frame)
return PageVisibilityState::kHidden;
PageVisibilityState visibility_state = GetRenderWidgetHost()->is_hidden()
? PageVisibilityState::kHidden
: PageVisibilityState::kVisible;
GetContentClient()->browser()->OverridePageVisibilityState(this,
&visibility_state);
return visibility_state;
}
bool RenderFrameHostImpl::Send(IPC::Message* message) {
return GetAgentSchedulingGroup().Send(message);
}
bool RenderFrameHostImpl::OnMessageReceived(const IPC::Message& msg) {
// Only process messages if the RenderFrame is alive.
if (!is_render_frame_created())
return false;
// Crash reports triggered by IPC messages for this frame should be associated
// with its URL.
ScopedActiveURL scoped_active_url(this);
if (delegate_->OnMessageReceived(this, msg))
return true;
return false;
}
void RenderFrameHostImpl::OnAssociatedInterfaceRequest(
const std::string& interface_name,
mojo::ScopedInterfaceEndpointHandle handle) {
// `this` is an `IPC::Listener`, but there is no path by which `this` would
// receive associated interface requests through this method. Associated
// interface requests come in through `GetAssociatedInterface()`.
NOTREACHED();
}
std::string RenderFrameHostImpl::ToDebugString() {
return "RFHI:" +
render_view_host_->GetDelegate()->GetCreatorLocation().ToString();
}
void RenderFrameHostImpl::AccessibilityPerformAction(
const ui::AXActionData& action_data) {
// Don't perform any Accessibility action on an inactive frame.
if (IsInactiveAndDisallowActivation(
DisallowActivationReasonId::kAXPerformAction) ||
!render_accessibility_)
return;
if (action_data.action == ax::mojom::Action::kHitTest) {
AccessibilityHitTest(action_data.target_point,
action_data.hit_test_event_to_fire,
action_data.request_id, {});
return;
}
// Set the input modality in RenderWidgetHostViewAura to touch so the
// VK shows up.
if (action_data.action == ax::mojom::Action::kFocus) {
RenderWidgetHostViewBase* view = static_cast<RenderWidgetHostViewBase*>(
render_view_host_->GetWidget()->GetView());
if (view)
view->SetLastPointerType(ui::EventPointerType::kTouch);
}
render_accessibility_->PerformAction(action_data);
}
bool RenderFrameHostImpl::AccessibilityViewHasFocus() {
RenderWidgetHostView* view = render_view_host_->GetWidget()->GetView();
if (view)
return view->HasFocus();
return false;
}
void RenderFrameHostImpl::AccessibilityViewSetFocus() {
// Don't update Accessibility for inactive frames.
if (IsInactiveAndDisallowActivation(DisallowActivationReasonId::kAXSetFocus))
return;
RenderWidgetHostView* view = render_view_host_->GetWidget()->GetView();
if (view)
view->Focus();
}
gfx::Rect RenderFrameHostImpl::AccessibilityGetViewBounds() {
RenderWidgetHostView* view = render_view_host_->GetWidget()->GetView();
if (view)
return view->GetViewBounds();
return gfx::Rect();
}
float RenderFrameHostImpl::AccessibilityGetDeviceScaleFactor() {
return GetScaleFactorForView(GetView());
}
void RenderFrameHostImpl::AccessibilityReset() {
if (!render_accessibility_)
return;
accessibility_reset_token_ = g_next_accessibility_reset_token++;
render_accessibility_->Reset(accessibility_reset_token_);
}
void RenderFrameHostImpl::AccessibilityFatalError() {
CHECK(!BrowserAccessibilityManager::IsFailFastMode());
browser_accessibility_manager_.reset();
if (accessibility_reset_token_ || !render_accessibility_)
return;
accessibility_fatal_error_count_++;
if (accessibility_fatal_error_count_ > max_accessibility_resets_) {
// This will both create an "Aw Snap..." and generate a second crash report
// in addition to the DumpWithoutCrashing() for the first reset.
render_accessibility_->FatalError();
} else {
// Crash keys set in BrowserAccessibilityManager::Unserialize().
if (accessibility_fatal_error_count_ == 1) {
// Only send crash report first time -- don't skew crash stats too much.
base::debug::DumpWithoutCrashing();
}
AccessibilityReset();
}
}
gfx::AcceleratedWidget
RenderFrameHostImpl::AccessibilityGetAcceleratedWidget() {
// Only the active RenderFrameHost is connected to the native widget tree for
// accessibility, so return null if this is queried on any other frame.
if (!is_main_frame() || !IsActive())
return gfx::kNullAcceleratedWidget;
RenderWidgetHostViewBase* view = static_cast<RenderWidgetHostViewBase*>(
render_view_host_->GetWidget()->GetView());
if (view)
return view->AccessibilityGetAcceleratedWidget();
return gfx::kNullAcceleratedWidget;
}
gfx::NativeViewAccessible
RenderFrameHostImpl::AccessibilityGetNativeViewAccessible() {
if (base::FeatureList::IsEnabled(features::kEvictOnAXEvents) &&
base::FeatureList::IsEnabled(
features::kEnableBackForwardCacheForScreenReader) &&
IsInactiveAndDisallowActivation(
DisallowActivationReasonId::kAXGetNativeView)) {
// |AccessibilityGetNativeViewAccessible()| should be only accessible when
// we process AX events. Otherwise this should not be used while in
// back/forward cache and the document should be evicted.
return nullptr;
}
RenderWidgetHostViewBase* view = static_cast<RenderWidgetHostViewBase*>(
render_view_host_->GetWidget()->GetView());
if (view)
return view->AccessibilityGetNativeViewAccessible();
return nullptr;
}
gfx::NativeViewAccessible
RenderFrameHostImpl::AccessibilityGetNativeViewAccessibleForWindow() {
// If this method is called when the frame is in BackForwardCache, evict
// the frame to avoid ignoring any accessibility related events which are not
// expected.
if (IsInactiveAndDisallowActivation(
DisallowActivationReasonId::kAXGetNativeViewForWindow))
return nullptr;
RenderWidgetHostViewBase* view = static_cast<RenderWidgetHostViewBase*>(
render_view_host_->GetWidget()->GetView());
if (view)
return view->AccessibilityGetNativeViewAccessibleForWindow();
return nullptr;
}
RenderFrameHostImpl* RenderFrameHostImpl::AccessibilityRenderFrameHost() {
return this;
}
void RenderFrameHostImpl::AccessibilityHitTest(
const gfx::Point& point_in_frame_pixels,
ax::mojom::Event opt_event_to_fire,
int opt_request_id,
base::OnceCallback<void(BrowserAccessibilityManager* hit_manager,
int hit_node_id)> opt_callback) {
// This is called by BrowserAccessibilityManager. During teardown it's
// possible that render_accessibility_ is null but the corresponding
// BrowserAccessibilityManager still exists and could call this.
if (IsInactiveAndDisallowActivation(DisallowActivationReasonId::kAXHitTest) ||
!render_accessibility_) {
if (opt_callback)
std::move(opt_callback).Run(nullptr, 0);
return;
}
render_accessibility_->HitTest(
point_in_frame_pixels, opt_event_to_fire, opt_request_id,
base::BindOnce(&RenderFrameHostImpl::AccessibilityHitTestCallback,
weak_ptr_factory_.GetWeakPtr(), opt_request_id,
opt_event_to_fire, std::move(opt_callback)));
}
bool RenderFrameHostImpl::AccessibilityIsMainFrame() {
// Do not use is_main_frame() or IsOutermostMainFrame().
// Frame trees may be nested so it can be the case that is_main_frame() is
// true, but is not the outermost RenderFrameHost (it only checks for nullity
// of |parent_|. In particular, !is_main_frame() cannot be used to check if
// this RenderFrameHost is embedded. In addition, IsOutermostMainFrame()
// does not escape guest views. Therefore, we must check for any kind of
// parent document or embedder.
return !GetParentOrOuterDocumentOrEmbedder();
}
WebContentsAccessibility*
RenderFrameHostImpl::AccessibilityGetWebContentsAccessibility() {
DCHECK(AccessibilityIsMainFrame());
auto* view = static_cast<RenderWidgetHostViewBase*>(GetView());
if (!view)
return nullptr;
return view->GetWebContentsAccessibility();
}
void RenderFrameHostImpl::InitializePolicyContainerHost(
bool renderer_initiated_creation_of_main_frame) {
// No policy container for speculative frames.
if (lifecycle_state_ == LifecycleStateImpl::kSpeculative) {
return;
}
// The initial empty document inherits its policy container from its creator.
// The creator is either its parent for subframes and embedded frames, or its
// opener for new windows.
//
// Note 1: For normal document created from a navigation, the policy container
// is computed from the NavigationRequest and assigned in
// DidCommitNewDocument().
if (parent_) {
SetPolicyContainerHost(parent_->policy_container_host()->Clone());
} else if (GetParentOrOuterDocument()) {
// In the MPArch implementation of FencedFrame, this RenderFrameHost's
// SiteInstance has been adjusted to match its parent. During navigations,
// COOP and COEP are used to determine the SiteInstance. It means that if
// SiteInstance has been inherited, COOP/COEP must also be inherited to
// avoid creating inconsistencies. See:
// https://chromium-review.googlesource.com/c/chromium/src/+/3645368
//
// TODO(https://crbug.com/1338603): What makes sense for GuestView?
// TODO(https://crbug.com/1338601): What makes sense for Portals?
const PolicyContainerPolicies& parent_policies =
GetParentOrOuterDocument()->policy_container_host()->policies();
// Note: the full constructor is used, to force developers to make an
// explicit decision when adding new fields to the PolicyContainer.
SetPolicyContainerHost(
base::MakeRefCounted<PolicyContainerHost>(PolicyContainerPolicies(
network::mojom::ReferrerPolicy::kDefault,
network::mojom::IPAddressSpace::kUnknown,
/*is_web_secure_context=*/false,
std::vector<network::mojom::ContentSecurityPolicyPtr>(),
parent_policies.cross_origin_opener_policy,
parent_policies.cross_origin_embedder_policy,
network::mojom::WebSandboxFlags::kNone,
/*anonymous=*/false)));
} else if (frame_tree_node_->opener()) {
// During a `window.open(...)` without `noopener`, a new popup is created
// and always starts from the initial empty document. The opener has
// synchronous access toward its openee. So they must both share the same
// policies.
SetPolicyContainerHost(frame_tree_node_->opener()
->current_frame_host()
->policy_container_host()
->Clone());
} else {
// In all the other cases, there is no environment to inherit policies
// from. This is "probably" a new top-level about:blank document created by
// the browser directly (omnibox, bookmarks, ...).
PolicyContainerPolicies policies;
// Main frames created by the browser are treated as belonging the `local`
// address space, so that they can make requests to any address space
// unimpeded. The only way to execute code in such a context is to inject it
// via DevTools, WebView APIs, or extensions; it is impossible to do so with
// Web Platform means only.
//
// See also https://crbug.com/1191161.
//
// We also exclude prerendering from this case manually, since prendering
// render frame hosts are unconditionally created with the
// `renderer_initiated_creation_of_main_frame` set to false, even though the
// frames arguably are renderer-created.
//
// TODO(https://crbug.com/1194421): Address the prerendering case.
if (is_main_frame() && !renderer_initiated_creation_of_main_frame &&
lifecycle_state_ != LifecycleStateImpl::kPrerendering) {
policies.ip_address_space = network::mojom::IPAddressSpace::kLocal;
}
SetPolicyContainerHost(
base::MakeRefCounted<PolicyContainerHost>(std::move(policies)));
}
// The initial empty documents sandbox flags is the union from:
// - The parent or opener document's CSP sandbox inherited by policy
// container.
// - The frame's sandbox flags, contained in browsing_context_state. This
// are either:
// 1. For iframe: the parent + iframe.sandbox attribute.
// 2. For popups: the opener if "allow-popups-to-escape-sandbox" isn't
// set.
network::mojom::WebSandboxFlags sandbox_flags_to_commit =
browsing_context_state_->effective_frame_policy().sandbox_flags;
for (const auto& csp :
policy_container_host_->policies().content_security_policies) {
sandbox_flags_to_commit |= csp->sandbox;
}
policy_container_host_->set_sandbox_flags(sandbox_flags_to_commit);
// The initial empty document's anonymous bit was inherited from the parent
// document. The frame's anonymous bit can also turn it one.
if (frame_tree_node_->anonymous()) {
policy_container_host_->SetIsAnonymous();
}
}
void RenderFrameHostImpl::SetPolicyContainerHost(
scoped_refptr<PolicyContainerHost> policy_container_host) {
policy_container_host_ = std::move(policy_container_host);
policy_container_host_->AssociateWithFrameToken(GetFrameToken(),
GetProcess()->GetID());
// Top-level document are never anonymous.
// Note: It is never inherited from the opener, because they are forced to
// open windows using noopener.
CHECK(parent_ || !IsAnonymous());
}
void RenderFrameHostImpl::InitializePrivateNetworkRequestPolicy() {
if (!policy_container_host_) {
// Only speculative RFHs may lack a policy container.
DCHECK_EQ(lifecycle_state_, LifecycleStateImpl::kSpeculative);
return;
}
private_network_request_policy_ =
DerivePrivateNetworkRequestPolicy(policy_container_host_->policies());
}
void RenderFrameHostImpl::RenderProcessGone(
SiteInstanceGroup* site_instance_group,
const ChildProcessTerminationInfo& info) {
DCHECK_EQ(site_instance_->group(), site_instance_group);
if (IsInBackForwardCache()) {
EvictFromBackForwardCacheWithReason(
info.status == base::TERMINATION_STATUS_PROCESS_CRASHED
? BackForwardCacheMetrics::NotRestoredReason::
kRendererProcessCrashed
: BackForwardCacheMetrics::NotRestoredReason::
kRendererProcessKilled);
}
CancelPrerendering(info.status == base::TERMINATION_STATUS_PROCESS_CRASHED
? PrerenderHost::FinalStatus::kRendererProcessCrashed
: PrerenderHost::FinalStatus::kRendererProcessKilled);
if (owned_render_widget_host_)
owned_render_widget_host_->RendererExited();
// The renderer process is gone, so this frame can no longer be loading.
ResetNavigationRequests();
ResetLoadingState();
// Any future UpdateState or UpdateTitle messages from this or a recreated
// process should be ignored until the next commit.
set_nav_entry_id(0);
if (is_audible_)
OnAudibleStateChanged(false);
++renderer_exit_count_;
if (base::FeatureList::IsEnabled(features::kCrashReporting))
MaybeGenerateCrashReport(info.status, info.exit_code);
// Reporting API: Send any queued reports and mark the reporting source as
// expired so that the reporting configuration in the network service can be
// removed. This is done here, rather than in the destructor, as it needs the
// mojo pipe to the network service.
GetProcess()
->GetStoragePartition()
->GetNetworkContext()
->SendReportsAndRemoveSource(GetReportingSource());
// When a frame's process dies, its RenderFrame no longer exists, which means
// that its child frames must be cleaned up as well.
ResetChildren();
// Reset state for the current RenderFrameHost once the FrameTreeNode has been
// reset.
RenderFrameDeleted();
SetLastCommittedUrl(GURL());
renderer_url_info_ = RendererURLInfo();
web_bundle_handle_.reset();
must_be_replaced_ = true;
has_committed_any_navigation_ = false;
#if BUILDFLAG(IS_ANDROID)
// Execute any pending Samsung smart clip callbacks.
for (base::IDMap<std::unique_ptr<ExtractSmartClipDataCallback>>::iterator
iter(&smart_clip_callbacks_);
!iter.IsAtEnd(); iter.Advance()) {
std::move(*iter.GetCurrentValue())
.Run(std::u16string(), std::u16string(), gfx::Rect());
}
smart_clip_callbacks_.Clear();
#endif // BUILDFLAG(IS_ANDROID)
// Ensure that future remote interface requests are associated with the new
// process's channel.
remote_associated_interfaces_.reset();
// Any termination disablers in content loaded by the new process will
// be sent again.
has_before_unload_handler_ = false;
has_unload_handler_ = false;
has_pagehide_handler_ = false;
has_visibilitychange_handler_ = false;
if (IsPendingDeletion()) {
// If the process has died, we don't need to wait for the ACK. Complete the
// deletion immediately.
// Note that it is possible for a frame to already be in kReadyToBeDeleted.
// This happens when this RenderFrameHost is pending deletion and is
// waiting on one of its children to run its unload handler. While running
// it, it can request its parent to detach itself.
if (lifecycle_state() != LifecycleStateImpl::kReadyToBeDeleted)
SetLifecycleState(LifecycleStateImpl::kReadyToBeDeleted);
DCHECK(children_.empty());
PendingDeletionCheckCompleted(); // Can delete |this|.
// |this| is deleted. Don't add any more code at this point in the function.
return;
}
// If this was the current pending or speculative RFH dying, cancel and
// destroy it.
frame_tree_node_->render_manager()->CancelPendingIfNecessary(this);
// Note: don't add any more code at this point in the function because
// |this| may be deleted. Any additional cleanup should happen before
// the last block of code here.
}
void RenderFrameHostImpl::PerformAction(const ui::AXActionData& data) {
AccessibilityPerformAction(data);
}
bool RenderFrameHostImpl::RequiresPerformActionPointInPixels() const {
return true;
}
bool RenderFrameHostImpl::CreateRenderFrame(
const absl::optional<blink::FrameToken>& previous_frame_token,
const absl::optional<blink::FrameToken>& opener_frame_token,
const absl::optional<blink::FrameToken>& parent_frame_token,
const absl::optional<blink::FrameToken>& previous_sibling_frame_token) {
TRACE_EVENT0("navigation", "RenderFrameHostImpl::CreateRenderFrame");
DCHECK(!IsRenderFrameLive()) << "Creating frame twice";
// The process may (if we're sharing a process with another host that already
// initialized it) or may not (we have our own process or the old process
// crashed) have been initialized. Calling Init() multiple times will be
// ignored, so this is safe.
if (!GetAgentSchedulingGroup().Init())
return false;
DCHECK(GetProcess()->IsInitializedAndNotDead());
mojom::CreateFrameParamsPtr params = mojom::CreateFrameParams::New();
BindBrowserInterfaceBrokerReceiver(
params->interface_broker.InitWithNewPipeAndPassReceiver());
params->routing_id = routing_id_;
params->previous_frame_token = previous_frame_token;
params->opener_frame_token = opener_frame_token;
params->parent_frame_token = parent_frame_token;
params->previous_sibling_frame_token = previous_sibling_frame_token;
params->tree_scope_type = frame_tree_node()->tree_scope_type();
params->replication_state =
browsing_context_state_->current_replication_state().Clone();
params->token = frame_token_;
params->devtools_frame_token = frame_tree_node()->devtools_frame_token();
BindAssociatedInterfaceProviderReceiver(
params->associated_interface_provider_remote
.InitWithNewEndpointAndPassReceiver());
// If this is a new RenderFrameHost for a frame that has already committed a
// document, we don't have a policy container yet. Indeed, in that case, this
// RenderFrameHost will not display any document until it commits a
// navigation. The policy container for the navigated document will be sent to
// Blink at CommitNavigation time and then stored in this RenderFrameHost in
// DidCommitNewDocument.
if (policy_container_host())
params->policy_container =
policy_container_host()->CreatePolicyContainerForBlink();
// Normally, the replication state contains effective frame policy, excluding
// sandbox flags and permissions policy attributes that were updated but have
// not taken effect. However, a new RenderFrame should use the pending frame
// policy, since it is being created as part of the navigation that will
// commit it. (I.e., the RenderFrame needs to know the policy to use when
// initializing the new document once it commits).
params->replication_state->frame_policy =
frame_tree_node()->pending_frame_policy();
// If we switched BrowsingInstances because of the COOP header, we should
// clear the frame name. This below informs the renderer at frame creation.
NavigationRequest* navigation_request =
frame_tree_node()->navigation_request();
bool should_clear_browsing_instance_name =
navigation_request &&
(navigation_request->coop_status().require_browsing_instance_swap() ||
(navigation_request->commit_params()
.is_cross_site_cross_browsing_context_group &&
base::FeatureList::IsEnabled(
features::kClearCrossSiteCrossBrowsingContextGroupWindowName)));
if (should_clear_browsing_instance_name) {
params->replication_state->name = "";
// The "swaps" only affect main frames, that have an empty unique name.
DCHECK(params->replication_state->unique_name.empty());
}
params->frame_owner_properties =
frame_tree_node()->frame_owner_properties().Clone();
params->is_on_initial_empty_document =
frame_tree_node()->is_on_initial_empty_document();
// The RenderWidgetHost takes ownership of its view. It is tied to the
// lifetime of the current RenderProcessHost for this RenderFrameHost.
// TODO(avi): This will need to change to initialize a
// RenderWidgetHostViewAura for the main frame once RenderViewHostImpl has-a
// RenderWidgetHostImpl. https://crbug.com/545684
if (owned_render_widget_host_) {
DCHECK(parent_);
DCHECK(parent_frame_token);
RenderWidgetHostView* rwhv = RenderWidgetHostViewChildFrame::Create(
owned_render_widget_host_.get(),
parent_->GetRenderWidgetHost()->GetScreenInfos());
// The child frame should be created hidden.
DCHECK(!rwhv->IsShowing());
}
if (auto* rwh = GetLocalRenderWidgetHost()) {
params->widget_params = rwh->BindAndGenerateCreateFrameWidgetParams();
}
mojo::PendingAssociatedRemote<mojom::Frame> pending_frame_remote;
params->frame = pending_frame_remote.InitWithNewEndpointAndPassReceiver();
SetMojomFrameRemote(std::move(pending_frame_remote));
// https://crbug.com/1006814. The renderer needs at least one of these tokens
// to be able to insert the new frame in the frame tree.
DCHECK(params->previous_frame_token || params->parent_frame_token);
GetAgentSchedulingGroup().CreateFrame(std::move(params));
if (previous_frame_token &&
previous_frame_token->Is<blink::RemoteFrameToken>()) {
RenderFrameProxyHost* proxy = RenderFrameProxyHost::FromFrameToken(
GetProcess()->GetID(),
previous_frame_token->GetAs<blink::RemoteFrameToken>());
// We have also created a `blink::RemoteFrame` in CreateFrame above, so
// remember that.
CHECK(proxy);
proxy->SetRenderFrameProxyCreated(true);
}
// The renderer now has a RenderFrame for this RenderFrameHost. Note that
// this path is only used for out-of-process iframes. Main frame RenderFrames
// are created with their RenderView, and same-site iframes are created at the
// time of OnCreateChildFrame.
RenderFrameCreated();
return true;
}
void RenderFrameHostImpl::SetMojomFrameRemote(
mojo::PendingAssociatedRemote<mojom::Frame> frame_remote) {
DCHECK(!frame_);
frame_.Bind(std::move(frame_remote));
}
void RenderFrameHostImpl::DeleteRenderFrame(
mojom::FrameDeleteIntention intent) {
TRACE_EVENT("navigation", "RenderFrameHostImpl::DeleteRenderFrame",
[&](perfetto::EventContext ctx) {
WriteRenderFrameImplDeletion(ctx, this, intent);
});
if (IsPendingDeletion())
return;
if (IsRenderFrameLive()) {
GetMojomFrameInRenderer()->Delete(intent);
// We change the lifecycle state to kRunningUnloadHandlers at the end of
// this method to wait until OnUnloadACK() is invoked.
// For subframes, process shutdown may be delayed for two reasons:
// (1) to allow the process to be potentially reused by future navigations
// withjin a short time window, and
// (2) to give the subframe unload handlers a chance to execute.
if (!is_main_frame() && IsActive()) {
base::TimeDelta subframe_shutdown_timeout =
frame_tree_->IsBeingDestroyed()
? base::TimeDelta()
: GetSubframeProcessShutdownDelay(
GetSiteInstance()->GetBrowserContext());
// If this document has unload handlers (and is active), ensure that they
// have a chance to execute by delaying process cleanup. This will prevent
// the process from shutting down immediately in the case where this is
// the last active frame in the process. See https://crbug.com/852204.
// Note that in the majority of cases, this is not necessary now that we
// keep track of pending delete RenderFrameHost
// (https://crbug.com/609963), but there are still a few exceptions where
// this is needed (https://crbug.com/1014550).
const base::TimeDelta unload_handler_timeout =
has_unload_handlers() ? subframe_unload_timeout_ : base::TimeDelta();
if (!subframe_shutdown_timeout.is_zero() ||
!unload_handler_timeout.is_zero()) {
RenderProcessHostImpl* process =
static_cast<RenderProcessHostImpl*>(GetProcess());
process->DelayProcessShutdown(subframe_shutdown_timeout,
unload_handler_timeout,
site_instance_->GetSiteInfo());
}
// If the subframe takes too long to unload, force its removal from the
// tree. See https://crbug.com/950625.
subframe_unload_timer_.Start(FROM_HERE, subframe_unload_timeout_, this,
&RenderFrameHostImpl::OnUnloadTimeout);
}
}
// In case of speculative RenderFrameHosts deletion, we don't run any unload
// handlers and RenderFrameHost is deleted directly without any lifecycle
// state transitions.
if (lifecycle_state() == LifecycleStateImpl::kSpeculative) {
DCHECK(!has_unload_handlers());
return;
}
// In case of BackForwardCache, page is evicted directly from the cache and
// deleted immediately, without waiting for unload handlers.
SetLifecycleState(ShouldWaitForUnloadHandlers()
? LifecycleStateImpl::kRunningUnloadHandlers
: LifecycleStateImpl::kReadyToBeDeleted);
}
void RenderFrameHostImpl::RenderFrameCreated() {
// In https://crbug.com/1146573 a WebContentsObserver was causing the frame to
// be reinitialized during deletion. It is not valid to re-enter navigation
// code like that and it led to an invalid state. This is not a DCHECK because
// the corruption will not be visible until later, making the bug very
// difficult to understand.
CHECK_NE(render_frame_state_, RenderFrameState::kDeleting);
// We should not create new RenderFrames while `frame_tree_` is being
// destroyed (e.g., via a WebContentsObserver during WebContents shutdown).
// This seems to have caused crashes in https://crbug.com/717650.
CHECK(!frame_tree_->IsBeingDestroyed());
DCHECK_NE(render_frame_state_, RenderFrameState::kCreated);
const RenderFrameState old_render_frame_state = render_frame_state_;
render_frame_state_ = RenderFrameState::kCreated;
if (old_render_frame_state == RenderFrameState::kDeleted) {
// Clear all the document-associated data for this RenderFrameHost when its
// RenderFrame is recreated after a crash. Note that the user data is
// intentionally not cleared at the time of crash. Please refer to
// https://crbug.com/1099237 for more details.
//
// Clearing of user data should be called before RenderFrameCreated to
// ensure:
// - a) the new state set in RenderFrameCreated doesn't get deleted.
// - b) the old state is not leaked to a new RenderFrameHost.
document_associated_data_.emplace(*this);
// Dispatch update notification when a Page is recreated after a crash.
if (is_main_frame()) {
// Only a current RenderFrameHost should be recreating its RenderFrame
// here, since speculative and pending deletion RenderFrameHosts get
// deleted immediately after crash, whereas prerender gets cancelled and
// bfcache entry gets evicted.
DCHECK_EQ(frame_tree_node_->current_frame_host(), this);
frame_tree_node_->frame_tree()->delegate()->NotifyPageChanged(GetPage());
}
}
// Initialize the RenderWidgetHost which marks it and the RenderViewHost as
// live before calling to the `delegate_`.
if (GetLocalRenderWidgetHost()) {
#if BUILDFLAG(IS_ANDROID)
GetLocalRenderWidgetHost()->SetForceEnableZoom(
delegate_->GetOrCreateWebPreferences().force_enable_zoom);
#endif // BUILDFLAG(IS_ANDROID)
GetLocalRenderWidgetHost()->RendererWidgetCreated(
/*for_frame_widget=*/true);
}
// Set up mojo connections to the renderer from the `frame_` connection before
// notifying the delegate.
SetUpMojoConnection();
delegate_->RenderFrameCreated(this);
if (enabled_bindings_)
GetFrameBindingsControl()->AllowBindings(enabled_bindings_);
if (web_ui_ && enabled_bindings_ & BINDINGS_POLICY_WEB_UI)
web_ui_->SetUpMojoConnection();
}
void RenderFrameHostImpl::RenderFrameDeleted() {
// In https://crbug.com/1146573 a WebContentsObserver was causing the frame to
// be reinitialized during deletion. It is not valid to re-enter navigation
// code like that and it led to an invalid state. This is not a DCHECK because
// the corruption will cause a crash but later, making the bug very
// difficult to understand.
CHECK_NE(render_frame_state_, RenderFrameState::kDeleting);
bool was_created = is_render_frame_created();
render_frame_state_ = RenderFrameState::kDeleting;
render_frame_scoped_weak_ptr_factory_.InvalidateWeakPtrs();
// If the current status is different than the new status, the delegate
// needs to be notified.
if (was_created) {
delegate_->RenderFrameDeleted(this);
}
TearDownMojoConnection();
if (web_ui_) {
web_ui_->RenderFrameDeleted();
web_ui_->TearDownMojoConnection();
}
render_frame_state_ = RenderFrameState::kDeleted;
}
void RenderFrameHostImpl::SwapIn() {
GetAssociatedLocalFrame()->SwapInImmediately();
}
void RenderFrameHostImpl::Init() {
// This is only called on the main frame, for renderer-created windows. These
// windows wait for the renderer to signal that we can show them and begin
// navigations.
DCHECK(is_main_frame());
DCHECK(waiting_for_init_);
waiting_for_init_ = false;
GetLocalRenderWidgetHost()->Init();
// TODO(danakj): We only blocked the main frame, so we should only need to
// resume that?
ForEachRenderFrameHostIncludingSpeculative(base::BindRepeating(
[](RenderFrameHostImpl* main_rfh,
RenderFrameHostImpl* render_frame_host) {
// Inner frame trees shouldn't be possible here.
DCHECK_EQ(render_frame_host->frame_tree(), main_rfh->frame_tree());
if (render_frame_host->IsRenderFrameLive())
render_frame_host->frame_->ResumeBlockedRequests();
},
base::Unretained(this)));
if (pending_navigate_) {
frame_tree_node()->navigator().OnBeginNavigation(
frame_tree_node(), std::move(pending_navigate_->common_params),
std::move(pending_navigate_->begin_navigation_params),
std::move(pending_navigate_->blob_url_loader_factory),
std::move(pending_navigate_->navigation_client),
EnsurePrefetchedSignedExchangeCache(),
MaybeCreateWebBundleHandleTracker(),
std::move(pending_navigate_->renderer_cancellation_listener));
pending_navigate_.reset();
}
}
RenderFrameProxyHost* RenderFrameHostImpl::GetProxyToParent() {
if (is_main_frame())
return nullptr;
return browsing_context_state_->GetRenderFrameProxyHost(
GetParent()->GetSiteInstance()->group());
}
RenderFrameProxyHost* RenderFrameHostImpl::GetProxyToOuterDelegate() {
// Only the main frame should be able to reach the outer WebContents.
DCHECK(is_main_frame());
int outer_contents_frame_tree_node_id =
frame_tree_node_->frame_tree()
->delegate()
->GetOuterDelegateFrameTreeNodeId();
FrameTreeNode* outer_contents_frame_tree_node =
FrameTreeNode::GloballyFindByID(outer_contents_frame_tree_node_id);
if (!outer_contents_frame_tree_node ||
!outer_contents_frame_tree_node->parent()) {
return nullptr;
}
return browsing_context_state_->GetRenderFrameProxyHost(
outer_contents_frame_tree_node->parent()->GetSiteInstance()->group(),
BrowsingContextState::ProxyAccessMode::kAllowOuterDelegate);
}
void RenderFrameHostImpl::DidChangeReferrerPolicy(
network::mojom::ReferrerPolicy referrer_policy) {
if (!IsActive() || !frame_tree_->controller().GetLastCommittedEntry())
return;
// The FrameNavigationEntry may want to change whether to protect its url
// in the navigation API when the referrer policy changes.
if (FrameNavigationEntry* entry =
frame_tree_->controller().GetLastCommittedEntry()->GetFrameEntry(
frame_tree_node_)) {
entry->set_protect_url_in_navigation_api(
NavigationControllerImpl::ShouldProtectUrlInNavigationApi(
referrer_policy));
}
}
void RenderFrameHostImpl::PropagateEmbeddingTokenToParentFrame() {
// Protect against calls from WebContentsImpl::AttachInnerWebContents() that
// happen before RFHI::SetEmbeddingToken() gets called, which is where the
// token gets set. It's safe to early return in those cases since this method
// will get called anyway by RFHI::SetEmbeddingToken(), at which time the
// outer delegate for the inner web contents will have been created already.
if (!embedding_token_)
return;
// We need to propagate the token to the parent frame if it's either remote or
// part of an outer web contents, therefore we need to figure out the right
// proxy to send the token to, if any.
// For local parents the propagation occurs within the renderer process. The
// token is also present on the main frame for generalization when the main
// frame in embedded in another context (e.g. browser UI). The main frame is
// not embedded in the context of the frame tree so it is not propagated here.
// See RenderFrameHost::GetEmbeddingToken for more details.
RenderFrameProxyHost* target_render_frame_proxy = nullptr;
if (RequiresProxyToParent()) {
// This subframe should have a remote parent frame.
target_render_frame_proxy = GetProxyToParent();
DCHECK(target_render_frame_proxy);
} else if (is_main_frame()) {
// The main frame in an inner web contents could have a delegate in the
// outer web contents, so we need to account for that as well.
target_render_frame_proxy = GetProxyToOuterDelegate();
}
// Propagate the token to the right process, if a proxy was found.
if (target_render_frame_proxy &&
target_render_frame_proxy->is_render_frame_proxy_live()) {
target_render_frame_proxy->GetAssociatedRemoteFrame()->SetEmbeddingToken(
embedding_token_.value());
}
}
void RenderFrameHostImpl::OnAudibleStateChanged(bool is_audible) {
DCHECK_NE(is_audible_, is_audible);
if (is_audible) {
DCHECK_NE(lifecycle_state(), LifecycleStateImpl::kPrerendering);
GetProcess()->OnMediaStreamAdded();
} else {
GetProcess()->OnMediaStreamRemoved();
}
is_audible_ = is_audible;
delegate_->OnFrameAudioStateChanged(this, is_audible_);
}
void RenderFrameHostImpl::DidAddMessageToConsole(
blink::mojom::ConsoleMessageLevel log_level,
const std::u16string& message,
uint32_t line_no,
const absl::optional<std::u16string>& source_id,
const absl::optional<std::u16string>& untrusted_stack_trace) {
std::u16string updated_source_id;
if (source_id.has_value())
updated_source_id = *source_id;
if (delegate_->DidAddMessageToConsole(this, log_level, message, line_no,
updated_source_id,
untrusted_stack_trace)) {
return;
}
// Pass through log severity only on builtin components pages to limit console
// spew.
const bool is_web_ui = HasWebUIScheme(GetMainFrame()->GetLastCommittedURL());
if (is_web_ui) {
DCHECK_EQ(GetMainFrame(), GetOutermostMainFrame())
<< "The mainframe and outermost mainframe should be the same in WebUI.";
}
const bool is_builtin_component =
is_web_ui ||
GetContentClient()->browser()->IsBuiltinComponent(
GetProcess()->GetBrowserContext(), GetLastCommittedOrigin());
const bool is_off_the_record =
GetSiteInstance()->GetBrowserContext()->IsOffTheRecord();
LogConsoleMessage(log_level, message, line_no, is_builtin_component,
is_off_the_record, updated_source_id);
}
void RenderFrameHostImpl::FrameSizeChanged(const gfx::Size& frame_size) {
frame_size_ = frame_size;
delegate_->FrameSizeChanged(this, frame_size);
}
void RenderFrameHostImpl::RendererDidActivateForPrerendering() {
DCHECK(blink::features::IsPrerender2Enabled());
// RendererDidActivateForPrerendering() is called after the renderer has
// notified that it fired the prerenderingchange event on the documents. The
// browser now runs any binders that were deferred during prerendering. This
// corresponds to the following steps of the activate algorithm:
//
// https://wicg.github.io/nav-speculation/prerendering.html#prerendering-browsing-context-activate
// Step 8.3.4. "For each steps in doc's post-prerendering activation steps
// list:"
// Step 8.3.4.1. "Run steps."
// Release Mojo capability control to run the binders. The RenderFrameHostImpl
// may have been created after activation started, in which case it already
// does not have Mojo capability control applied.
if (mojo_binder_policy_applier_) {
mojo_binder_policy_applier_->GrantAll();
// As per `ReleaseMojoBinderPolicies` method requirement, the policy applier
// owner should call the method when destroying the object, so we first
// need to call this method before resetting the unique pointer.
broker_.ReleaseMojoBinderPolicies();
mojo_binder_policy_applier_.reset();
}
}
void RenderFrameHostImpl::SetCrossOriginOpenerPolicyReporter(
std::unique_ptr<CrossOriginOpenerPolicyReporter> coop_reporter) {
coop_access_report_manager_.set_coop_reporter(std::move(coop_reporter));
}
bool RenderFrameHostImpl::IsAnonymous() const {
return policy_container_host_->policies().is_anonymous;
}
void RenderFrameHostImpl::OnCreateChildFrame(
int new_routing_id,
mojo::PendingAssociatedRemote<mojom::Frame> frame_remote,
mojo::PendingReceiver<blink::mojom::BrowserInterfaceBroker>
browser_interface_broker_receiver,
blink::mojom::PolicyContainerBindParamsPtr policy_container_bind_params,
mojo::PendingAssociatedReceiver<blink::mojom::AssociatedInterfaceProvider>
associated_interface_provider_receiver,
blink::mojom::TreeScopeType scope,
const std::string& frame_name,
const std::string& frame_unique_name,
bool is_created_by_script,
const blink::LocalFrameToken& frame_token,
const base::UnguessableToken& devtools_frame_token,
const blink::FramePolicy& frame_policy,
const blink::mojom::FrameOwnerProperties& frame_owner_properties,
blink::FrameOwnerElementType owner_type) {
// TODO(lukasza): Call ReceivedBadMessage when |frame_unique_name| is empty.
DCHECK(!frame_unique_name.empty());
DCHECK(browser_interface_broker_receiver.is_valid());
DCHECK(policy_container_bind_params->receiver.is_valid());
DCHECK(associated_interface_provider_receiver.is_valid());
DCHECK(devtools_frame_token);
// The RenderFrame corresponding to this host sent an IPC message to create a
// child, but by the time we get here, it's possible for its process to have
// disconnected (maybe due to browser shutdown). Ignore such messages.
if (!is_render_frame_created())
return;
// Only active and prerendered documents are allowed to create child
// frames.
if (lifecycle_state() != LifecycleStateImpl::kPrerendering) {
// The RenderFrame corresponding to this host sent an IPC message to create
// a child, but by the time we get here, it's possible for the
// RenderFrameHost to become inactive. Ignore such messages.
if (IsInactiveAndDisallowActivation(
DisallowActivationReasonId::kCreateChildFrame))
return;
}
// |new_routing_id|, |browser_interface_broker_receiver| and
// |devtools_frame_token| were generated on the browser's IO thread and not
// taken from the renderer process.
frame_tree_->AddFrame(
this, GetProcess()->GetID(), new_routing_id, std::move(frame_remote),
std::move(browser_interface_broker_receiver),
std::move(policy_container_bind_params),
std::move(associated_interface_provider_receiver), scope, frame_name,
frame_unique_name, is_created_by_script, frame_token,
devtools_frame_token, frame_policy, frame_owner_properties,
was_discarded_, owner_type,
/*is_dummy_frame_for_inner_tree=*/false);
}
void RenderFrameHostImpl::CreateChildFrame(
int new_routing_id,
mojo::PendingAssociatedRemote<mojom::Frame> frame_remote,
mojo::PendingReceiver<blink::mojom::BrowserInterfaceBroker>
browser_interface_broker_receiver,
blink::mojom::PolicyContainerBindParamsPtr policy_container_bind_params,
mojo::PendingAssociatedReceiver<blink::mojom::AssociatedInterfaceProvider>
associated_interface_provider_receiver,
blink::mojom::TreeScopeType scope,
const std::string& frame_name,
const std::string& frame_unique_name,
bool is_created_by_script,
const blink::FramePolicy& frame_policy,
blink::mojom::FrameOwnerPropertiesPtr frame_owner_properties,
blink::FrameOwnerElementType owner_type) {
blink::LocalFrameToken frame_token;
base::UnguessableToken devtools_frame_token;
if (!static_cast<RenderProcessHostImpl*>(GetProcess())
->TakeFrameTokensForFrameRoutingID(new_routing_id, frame_token,
devtools_frame_token)) {
bad_message::ReceivedBadMessage(
GetProcess(), bad_message::RFH_CREATE_CHILD_FRAME_TOKENS_NOT_FOUND);
return;
}
// Documents create iframes, iframes host new documents. Both are associated
// with sandbox flags. They are required to be stricter or equal to their
// owner when they are created, as we go down.
if (frame_policy.sandbox_flags !=
(frame_policy.sandbox_flags | active_sandbox_flags())) {
bad_message::ReceivedBadMessage(
GetProcess(), bad_message::RFH_CREATE_CHILD_FRAME_SANDBOX_FLAGS);
return;
}
// Cannot create a fenced frame in a sandbox iframe which doesn't allow
// features that need to be allowed in the fenced frame. This is for Shadow
// DOM Fenced Frame.
if (IsSandboxed(blink::kFencedFrameMandatoryUnsandboxedFlags) &&
frame_policy.is_fenced) {
bad_message::ReceivedBadMessage(
GetProcess(), bad_message::RFH_CREATE_FENCED_FRAME_IN_SANDBOXED_FRAME);
return;
}
// TODO(crbug.com/1145708). The interface exposed to tests should
// match the mojo interface.
OnCreateChildFrame(new_routing_id, std::move(frame_remote),
std::move(browser_interface_broker_receiver),
std::move(policy_container_bind_params),
std::move(associated_interface_provider_receiver), scope,
frame_name, frame_unique_name, is_created_by_script,
frame_token, devtools_frame_token, frame_policy,
*frame_owner_properties, owner_type);
}
void RenderFrameHostImpl::DidNavigate(
const mojom::DidCommitProvisionalLoadParams& params,
NavigationRequest* navigation_request,
bool was_within_same_document) {
// Keep track of the last committed URL and origin in the RenderFrameHost
// itself. These allow GetLastCommittedURL and GetLastCommittedOrigin to
// stay correct even if the render_frame_host later becomes pending deletion.
// The URL is set regardless of whether it's for a net error or not.
navigation_request->frame_tree_node()->SetCurrentURL(params.url);
SetLastCommittedOrigin(params.origin);
// If the navigation was a cross-document navigation and it's not the
// synchronous about:blank commit, then it committed a document that is not
// the initial empty document.
if (!navigation_request->IsSameDocument() &&
(!navigation_request->is_synchronous_renderer_commit() ||
!navigation_request->GetURL().IsAboutBlank())) {
navigation_request->frame_tree_node()->SetNotOnInitialEmptyDocument();
}
// For uuid-in-package: resources served from WebBundles, use the Bundle's
// origin.
url::Origin origin =
(params.url.SchemeIs(url::kUuidInPackageScheme) &&
navigation_request->GetWebBundleURL().is_valid())
? url::Origin::Create(navigation_request->GetWebBundleURL())
: GetLastCommittedOrigin();
isolation_info_ =
ComputeIsolationInfoInternal(origin, isolation_info_.request_type(),
navigation_request->is_anonymous());
// Separately, update the frame's last successful URL except for net error
// pages, since those do not end up in the correct process after transfers
// (see https://crbug.com/560511). Instead, the next cross-process navigation
// or transfer should decide whether to swap as if the net error had not
// occurred.
// TODO(creis): Remove this block and always set the URL.
// See https://crbug.com/588314.
if (!navigation_request->DidEncounterError())
last_successful_url_ = params.url;
renderer_url_info_.last_document_url = GetLastDocumentURL(
navigation_request, params, is_error_page_, renderer_url_info_);
// Set the last committed HTTP method and POST ID. Note that we're setting
// this here instead of in DidCommitNewDocument because same-document
// navigations triggered by the History API (history.replaceState/pushState)
// will reset the method to "GET" (while fragment navigations won't).
// TODO(arthursonzogni): Stop relying on DidCommitProvisionalLoadParams. Use
// the NavigationRequest instead. The browser process doesn't need to rely on
// the renderer process.
last_http_method_ = params.method;
last_post_id_ = params.post_id;
// TODO(arthursonzogni): Stop relying on DidCommitProvisionalLoadParams. Use
// the NavigationRequest instead. The browser process doesn't need to rely on
// the renderer process.
last_http_status_code_ = params.http_status_code;
// Sets whether the last navigation has user gesture/transient activation or
// not.
last_navigation_started_with_transient_activation_ =
navigation_request->common_params().has_user_gesture;
// Navigations that activate an existing bfcached or prerendered document do
// not create a new document.
bool did_create_new_document =
!navigation_request->IsPageActivation() && !was_within_same_document;
if (did_create_new_document)
DidCommitNewDocument(params, navigation_request);
// When the frame hosts a different document, its state must be replicated
// via its proxies to the other processes where it appears as remote.
//
// This includes new documents. It also includes documents restored from the
// BackForwardCache. This is because the cached state in
// BrowsingContextState::replication_state_ needs to be refreshed with the
// actual values.
if (!navigation_request->IsSameDocument()) {
// Permissions policy's inheritance from parent frame's permissions policy
// is through accessing parent frame's security context(either remote or
// local) when initializing child's security context, so the update to
// proxies is needed.
browsing_context_state_->UpdateFramePolicyHeaders(
active_sandbox_flags(), permissions_policy_header_);
// Document policy's inheritance from parent frame's required document
// policy is done at |HTMLFrameOwnerElement::UpdateRequiredPolicy|. Parent
// frame owns both parent's required document policy and child frame's frame
// owner element which contains child's required document policy, so there
// is no need to store required document policy in proxies.
}
}
void RenderFrameHostImpl::SetLastCommittedOrigin(const url::Origin& origin) {
last_committed_origin_ = origin;
}
void RenderFrameHostImpl::SetLastCommittedOriginForTesting(
const url::Origin& origin) {
SetLastCommittedOrigin(origin);
}
const url::Origin& RenderFrameHostImpl::ComputeTopFrameOrigin(
const url::Origin& frame_origin) const {
if (is_main_frame()) {
return frame_origin;
}
DCHECK(parent_);
// It's important to go through parent_ rather than via
// frame_free_->root() here in case we're in process of being deleted, as the
// latter might point to what our ancestor is being replaced with rather than
// the actual ancestor.
RenderFrameHostImpl* host = parent_;
while (host->parent_) {
host = host->parent_;
}
return host->GetLastCommittedOrigin();
}
void RenderFrameHostImpl::SetStorageKey(const blink::StorageKey& storage_key) {
storage_key_ = storage_key;
}
net::IsolationInfo RenderFrameHostImpl::ComputeIsolationInfoForNavigation(
const GURL& destination) {
return ComputeIsolationInfoForNavigation(destination, IsAnonymous());
}
net::IsolationInfo RenderFrameHostImpl::ComputeIsolationInfoForNavigation(
const GURL& destination,
bool is_anonymous) {
net::IsolationInfo::RequestType request_type =
is_main_frame() ? net::IsolationInfo::RequestType::kMainFrame
: net::IsolationInfo::RequestType::kSubFrame;
return ComputeIsolationInfoInternal(url::Origin::Create(destination),
request_type, is_anonymous);
}
net::IsolationInfo
RenderFrameHostImpl::ComputeIsolationInfoForSubresourcesForPendingCommit(
const url::Origin& main_world_origin,
bool is_anonymous) {
return ComputeIsolationInfoInternal(
main_world_origin, net::IsolationInfo::RequestType::kOther, is_anonymous);
}
net::SiteForCookies RenderFrameHostImpl::ComputeSiteForCookies() {
return isolation_info_.site_for_cookies();
}
net::IsolationInfo RenderFrameHostImpl::ComputeIsolationInfoInternal(
const url::Origin& frame_origin,
net::IsolationInfo::RequestType request_type,
bool is_anonymous) {
url::Origin top_frame_origin = ComputeTopFrameOrigin(frame_origin);
net::SchemefulSite top_frame_site = net::SchemefulSite(top_frame_origin);
net::SiteForCookies candidate_site_for_cookies(top_frame_site);
std::set<net::SchemefulSite> party_context;
// Walk up the frame tree to check SiteForCookies and compute the
// |party_context|.
//
// If |request_type| is kOther, then IsolationInfo is being computed
// for subresource requests. Check/compute starting from the frame itself.
// Otherwise, it's OK to skip checking the frame itself since it's stored in
// IsolationInfo for each request and will be validated later anyway.
//
// If origins are not consistent with the candidate SiteForCookies
// of the main document, SameSite cookies may not be used.
const RenderFrameHostImpl* initial_rfh = this;
if (request_type != net::IsolationInfo::RequestType::kOther)
initial_rfh = this->parent_;
for (const RenderFrameHostImpl* rfh = initial_rfh; rfh; rfh = rfh->parent_) {
const url::Origin& cur_origin =
rfh == this ? frame_origin : rfh->last_committed_origin_;
net::SchemefulSite cur_site = net::SchemefulSite(cur_origin);
if (top_frame_site != cur_site) {
party_context.insert(cur_site);
}
candidate_site_for_cookies.CompareWithFrameTreeSiteAndRevise(cur_site);
}
// Reset the SiteForCookies if the top frame origin is of a scheme that should
// always be treated as the SiteForCookies.
if (GetContentClient()
->browser()
->ShouldTreatURLSchemeAsFirstPartyWhenTopLevel(
top_frame_origin.scheme(),
GURL::SchemeIsCryptographic(frame_origin.scheme()))) {
candidate_site_for_cookies = net::SiteForCookies(top_frame_site);
}
absl::optional<base::UnguessableToken> nonce = ComputeNonce(is_anonymous);
return net::IsolationInfo::Create(
request_type, top_frame_origin, frame_origin, candidate_site_for_cookies,
std::move(party_context), nonce ? &nonce.value() : nullptr);
}
absl::optional<base::UnguessableToken> RenderFrameHostImpl::ComputeNonce(
bool is_anonymous) {
// If it's an anonymous frame tree, use its nonce even if it's within a fenced
// frame tree to maintain the guarantee that an anonymous frame tree has
// a unique nonce. Otherwise, use the fenced frame nonce for fenced frames.
// Note that MPArch will ensure that fenced frame tree within an anonymous
// iframe does not have `is_anonymous` set to true. The ShadowDOM architecture
// cannot make the same guarantee that MPArch will, and therefore the shadow
// DOM version and will lead to the anonymous iframe nonce being used
// (crbug.com/1249865).
// The nonce was moved from PageImpl to RenderFrameHostImpl to fix
// crbug.com/1287458. In the case of an anonymous iframe embedded in a fenced
// frame, we get the anonymous_iframes_nonce of the fenced frame root to
// prevent anonymous iframes embedded inside a fenced frame from sharing nonce
// with anonymous iframes outside the fenced frame.
if (is_anonymous) {
RenderFrameHostImpl* main_rfh = this;
while (main_rfh->parent_ && !main_rfh->IsFencedFrameRoot()) {
main_rfh = main_rfh->parent_;
}
return main_rfh->anonymous_iframes_nonce();
}
return frame_tree_node_->fenced_frame_nonce();
}
blink::StorageKey RenderFrameHostImpl::CalculateStorageKey(
const url::Origin& new_rfh_origin,
const base::UnguessableToken* nonce) {
std::vector<RenderFrameHostImpl*> ancestor_chain;
RenderFrameHostImpl* current = this;
while (current) {
ancestor_chain.push_back(current);
current = current->parent_;
}
// Make sure to always use the |new_rfh_origin| when referring to the current
// RenderFrameHost. The origin might differ when the RenderFrameHost is reused
// when SiteIsolation is off.
auto origin = [&](RenderFrameHostImpl* rfh) {
return rfh == this ? new_rfh_origin : rfh->GetLastCommittedOrigin();
};
// When the top level RenderFrameHost is a Chrome extension, with host
// permissions to its child in the ancestor chain, then behave "as-if" the
// child was the top-level one computing the StorageKey.
//
// Sites with host permissions are saved in
// `browser_context->GetSharedCorsOriginAccessList()` because they are also
// used to bypass CORS restrictions. We can reuse this permissions list here
// because sites that are explicitly granted access permissions should also be
// able to access partitioned storage based not partitioned by the top level
// extension URL. A origin will only have access to another origin via
// OriginAccessList if the origin is an extension.
bool ignore_top_level_extension =
!is_main_frame() &&
GetBrowserContext()
->GetSharedCorsOriginAccessList()
->GetOriginAccessList()
.CheckAccessState(origin(ancestor_chain.end()[-1]),
origin(ancestor_chain.end()[-2]).GetURL()) ==
network::cors::OriginAccessList::AccessState::kAllowed;
if (ignore_top_level_extension) {
ancestor_chain.pop_back();
}
// Compute the AncestorChainBit. It represents whether every ancestors are all
// same-site or not.
auto site_for_cookies = net::SiteForCookies::FromOrigin(new_rfh_origin);
blink::mojom::AncestorChainBit ancestor_chain_bit =
blink::mojom::AncestorChainBit::kSameSite;
for (auto* ancestor : ancestor_chain) {
if (!site_for_cookies.IsFirstParty(origin(ancestor).GetURL()))
ancestor_chain_bit = blink::mojom::AncestorChainBit::kCrossSite;
}
return blink::StorageKey::CreateWithOptionalNonce(
new_rfh_origin, net::SchemefulSite(origin(ancestor_chain.back())), nonce,
ancestor_chain_bit);
}
void RenderFrameHostImpl::SetOriginDependentStateOfNewFrame(
const url::Origin& new_frame_creator) {
// This method should only be called for *new* frames, that haven't committed
// a navigation yet.
DCHECK(!has_committed_any_navigation_);
DCHECK(GetLastCommittedOrigin().opaque());
// Calculate and set |new_frame_origin|.
bool new_frame_should_be_sandboxed =
network::mojom::WebSandboxFlags::kOrigin ==
(browsing_context_state_->active_sandbox_flags() &
network::mojom::WebSandboxFlags::kOrigin);
url::Origin new_frame_origin = new_frame_should_be_sandboxed
? new_frame_creator.DeriveNewOpaqueOrigin()
: new_frame_creator;
isolation_info_ = ComputeIsolationInfoInternal(
new_frame_origin, net::IsolationInfo::RequestType::kOther, IsAnonymous());
SetLastCommittedOrigin(new_frame_origin);
SetStorageKey(CalculateStorageKey(
new_frame_origin, base::OptionalOrNullptr(isolation_info_.nonce())));
// Apply private network request policy according to our new origin.
if (GetContentClient()->browser()->ShouldAllowInsecurePrivateNetworkRequests(
GetBrowserContext(), new_frame_origin)) {
private_network_request_policy_ =
network::mojom::PrivateNetworkRequestPolicy::kAllow;
}
// Construct the frame's permissions policy only once we know its initial
// committed origin. It's necessary to wait for the origin because the
// permissions policy's state depends on the origin, so the PermissionsPolicy
// object could be configured incorrectly if it were initialized before
// knowing the value of |last_committed_origin_|. More at crbug.com/1112959.
ResetPermissionsPolicy();
}
FrameTreeNode* RenderFrameHostImpl::AddChild(
std::unique_ptr<FrameTreeNode> child,
int frame_routing_id,
mojo::PendingAssociatedRemote<mojom::Frame> frame_remote,
const blink::LocalFrameToken& frame_token,
const blink::FramePolicy& frame_policy,
std::string frame_name,
std::string frame_unique_name) {
// Initialize the RenderFrameHost for the new node. We always create child
// frames in the same SiteInstance as the current frame, and they can swap to
// a different one if they navigate away.
child->render_manager()->InitChild(
GetSiteInstance(), frame_routing_id, std::move(frame_remote), frame_token,
frame_policy, frame_name, frame_unique_name);
// Other renderer processes in this BrowsingInstance may need to find out
// about the new frame. Create a proxy for the child frame in all
// SiteInstances that have a proxy for the frame's parent, since all frames
// in a frame tree should have the same set of proxies.
frame_tree_node_->render_manager()->CreateProxiesForChildFrame(child.get());
// When the child is added, it hasn't committed any navigation yet - its
// initial empty document should inherit the origin of its parent (the origin
// may change after the first commit). See also https://crbug.com/932067.
child->current_frame_host()->SetOriginDependentStateOfNewFrame(
GetLastCommittedOrigin());
children_.push_back(std::move(child));
return children_.back().get();
}
void RenderFrameHostImpl::RemoveChild(FrameTreeNode* child) {
for (auto iter = children_.begin(); iter != children_.end(); ++iter) {
if (iter->get() == child) {
// Subtle: we need to make sure the node is gone from the tree before
// observers are notified of its deletion.
std::unique_ptr<FrameTreeNode> node_to_delete(std::move(*iter));
children_.erase(iter);
// TODO(dcheng): Removing a subtree is still very piecemeal and somewhat
// buggy. The entire subtree should be removed as a group, but it can
// actually happen incrementally. For example, given the frame tree:
//
// A1(A2(B3(A4(B5))))
//
//
// Suppose A1 executes:
//
// window.frames[0].frameElement.remove();
//
// What ends up happening is this:
//
// 1. Renderer A detaches the subtree beginning at A2.
// 2. Renderer A starts detaching A2.
// 3. Renderer A starts detaching B3.
// 4. Renderer A starts detaching A4.
// 5. Renderer A starts detaching B5
// 6. Renderer A reports B5 is complete detaching with the Mojo IPC
// `RenderFrameProxyHost::Detach()`, which calls
// `RenderFrameHostImpl::DetachFromProxy()`. `DetachFromProxy()`
// deletes RenderFrame B5 in renderer B, which means that the unload
// handler for B5 runs immediately--before the unload handler for B3.
// However, per the spec, the right order to run unload handlers is
// top-down (e.g. B3's unload handler should run before B5's in this
// scenario).
node_to_delete->current_frame_host()->DeleteRenderFrame(
mojom::FrameDeleteIntention::kNotMainFrame);
RenderFrameHostImpl* speculative_frame_host =
node_to_delete->render_manager()->speculative_frame_host();
if (speculative_frame_host) {
if (speculative_frame_host->lifecycle_state() ==
LifecycleStateImpl::kPendingCommit) {
// A speculative RenderFrameHost that has reached `kPendingCommit` has
// already sent a `CommitNavigation()` to the renderer. Any subsequent
// IPCs will only be processed after the renderer has already swapped
// in the provisional RenderFrame and swapped out the provisional
// frame's reference frame (which is either a RenderFrame or a
// `blink::RemoteFrame`).
//
// Since the swapped out `RenderFrame`/`blink::RemoteFrame` is already
// gone, a `DeleteRenderFrame()` (routed to the RenderFrame) or a
// `DetachAndDispose()` (routed to the `blink::RemoteFrame`) won't do
// anything. The browser must also instruct the already-committed but
// not-yet-acknowledged speculative RFH to detach itself as well.
speculative_frame_host->DeleteRenderFrame(
mojom::FrameDeleteIntention::kNotMainFrame);
} else {
// Otherwise, the provisional RenderFrame has not yet been instructed
// to swap in but is already associated with the RenderFrame or
// `blink::RemoteFrame` it is expected to replace. The associated
// `RenderFrame`/`blink::RemoteFrame` (which is still in the frame
// tree) will be responsible for tearing down any associated
// provisional RenderFrame, so the browser does not need to take any
// explicit cleanup actions.
}
}
// No explicit cleanup is needed here for `RenderFrameProxyHost`s.
// Destroying `FrameTreeNode` destroys the map of `RenderFrameProxyHost`s,
// and `~RenderFrameProxyHost()` sends a Mojo `DetachAndDispose()` IPC for
// child frame proxies.
node_to_delete.reset();
PendingDeletionCheckCompleted(); // Can delete |this|.
// |this| is potentially deleted. Do not add code after this.
return;
}
}
}
void RenderFrameHostImpl::ResetChildren() {
// Remove child nodes from the tree, then delete them. This destruction
// operation will notify observers. See https://crbug.com/612450 for
// explanation why we don't just call the std::vector::clear method.
std::vector<std::unique_ptr<FrameTreeNode>> children;
children.swap(children_);
// TODO(dcheng): Ideally, this would be done by messaging all the proxies of
// this RenderFrameHostImpl to detach the current frame's children, rather
// than messaging each child's current frame host...
for (auto& child : children)
child->current_frame_host()->DeleteRenderFrame(
mojom::FrameDeleteIntention::kNotMainFrame);
}
void RenderFrameHostImpl::SetLastCommittedUrl(const GURL& url) {
last_committed_url_ = url;
}
void RenderFrameHostImpl::Detach() {
// Detach() can be called in both speculative and pending-commit states.
// - a speculative RenderFrameHost as a result of its associated Frame being
// detached (i.e., the Frame in the renderer with a provisional_frame_ field
// that points to `this`'s LocalFrame). We don't expect it to self-detach
// otherwise.
// - a pending commit RenderFrameHost might detach itself due to unload events
// running that remove it from the tree when swapping it in.
//
// In both cases speculative and pending-commit RenderFrameHosts, it's OK to
// early-return. The logical FrameTreeNode is going to be torn down as well,
// and the speculative / pending commit RenderFrameHost (which is still
// strongly owned by the RenderFrameHostManager via unique_ptr) will be torn
// down then. If we do proceed, this ends up with a use-after-free, since
// StartPendingDeletionOnSubtree() will ResetNavigationsForPendingDeletion(),
// which deletes `this`.
if (lifecycle_state() == LifecycleStateImpl::kSpeculative ||
lifecycle_state() == LifecycleStateImpl::kPendingCommit) {
return;
}
if (!parent_) {
bad_message::ReceivedBadMessage(GetProcess(),
bad_message::RFH_DETACH_MAIN_FRAME);
return;
}
// A frame is removed while replacing this document with the new one. When it
// happens, delete the frame and both the new and old documents. Unload
// handlers aren't guaranteed to run here.
if (is_waiting_for_unload_ack_) {
parent_->RemoveChild(frame_tree_node_);
return;
}
// Ignore Detach Mojo API, if the RenderFrameHost should be left in pending
// deletion state.
if (do_not_delete_for_testing_)
return;
if (IsPendingDeletion()) {
// The frame is pending deletion. Detach Mojo API is used to confirm its
// unload handlers ran. Note that it is possible for a frame to already be
// in kReadyToBeDeleted. This happens when this RenderFrameHost is pending
// deletion and is waiting on one of its children to run its unload
// handler. While running it, it can request its parent to detach itself.
// See test: SitePerProcessBrowserTest.PartialUnloadHandler.
if (lifecycle_state() != LifecycleStateImpl::kReadyToBeDeleted)
SetLifecycleState(LifecycleStateImpl::kReadyToBeDeleted);
PendingDeletionCheckCompleted(); // Can delete |this|.
// |this| is potentially deleted. Do not add code after this.
return;
}
// This frame is being removed by the renderer, and it has already executed
// its unload handler.
SetLifecycleState(LifecycleStateImpl::kReadyToBeDeleted);
// Before completing the removal, we still need to wait for all of its
// descendant frames to execute unload handlers. Start executing those
// handlers now.
StartPendingDeletionOnSubtree();
frame_tree()->FrameUnloading(frame_tree_node_);
// Some children with no unload handler may be eligible for immediate
// deletion. Cut the dead branches now. This is a performance optimization.
PendingDeletionCheckCompletedOnSubtree(); // Can delete |this|.
// |this| is potentially deleted. Do not add code after this.
}
void RenderFrameHostImpl::DidFailLoadWithError(const GURL& url,
int32_t error_code) {
TRACE_EVENT("navigation", "RenderFrameHostImpl::DidFailLoadWithError",
ChromeTrackEvent::kRenderFrameHost, *this, "error", error_code);
// Cancel prerendering if DidFailLoadWithError is called during prerendering.
// Don't dispatch the DidFailLoad event in such a case as the embedders are
// unaware of prerender page yet and shouldn't show any user-visible changes
// from an inactive RenderFrameHost.
if (CancelPrerendering(PrerenderHost::FinalStatus::kDidFailLoad)) {
return;
}
GURL validated_url(url);
GetProcess()->FilterURL(false, &validated_url);
frame_tree_node_->navigator().DidFailLoadWithError(this, validated_url,
error_code);
}
void RenderFrameHostImpl::DidFocusFrame() {
TRACE_EVENT("navigation", "RenderFrameHostImpl::DidFocusFrame",
ChromeTrackEvent::kRenderFrameHost, *this,
ChromeTrackEvent::kSiteInstanceGroup,
*GetSiteInstance()->group());
// We don't handle this IPC signal for non-active RenderFrameHost.
if (!IsActive())
return;
delegate_->SetFocusedFrame(frame_tree_node_, GetSiteInstance()->group());
}
void RenderFrameHostImpl::DidCallFocus() {
// This should not occur for prerenders but may occur for pages in
// the BackForwardCache depending on timing.
if (!IsActive())
return;
delegate_->DidCallFocus();
}
void RenderFrameHostImpl::CancelInitialHistoryLoad() {
// A Javascript navigation interrupted the initial history load. Check if an
// initial subframe cross-process navigation needs to be canceled as a result.
// TODO(creis, clamy): Cancel any cross-process navigation.
NOTIMPLEMENTED();
}
void RenderFrameHostImpl::DidChangeBackForwardCacheDisablingFeatures(
uint64_t features_mask) {
renderer_reported_bfcache_disabling_features_ =
BackForwardCacheDisablingFeatures::FromEnumBitmask(features_mask);
MaybeEvictFromBackForwardCache();
if (back_forward_cache_disabling_features_callback_for_testing_) {
back_forward_cache_disabling_features_callback_for_testing_.Run(
renderer_reported_bfcache_disabling_features_);
}
}
using BackForwardCacheDisablingFeatureHandle =
RenderFrameHostImpl::BackForwardCacheDisablingFeatureHandle;
BackForwardCacheDisablingFeatureHandle::
BackForwardCacheDisablingFeatureHandle() {
// |render_frame_host_| will be null, so this value is never used.
feature_ = BackForwardCacheDisablingFeature::kDummy;
}
BackForwardCacheDisablingFeatureHandle::BackForwardCacheDisablingFeatureHandle(
RenderFrameHostImpl* render_frame_host,
BackForwardCacheDisablingFeature feature)
: render_frame_host_(render_frame_host->GetWeakPtr()), feature_(feature) {
CHECK(render_frame_host_);
render_frame_host_->OnBackForwardCacheDisablingFeatureUsed(feature_);
}
void RenderFrameHostImpl::OnBackForwardCacheDisablingFeatureUsed(
BackForwardCacheDisablingFeature feature) {
++browser_reported_bfcache_disabling_features_counts_[feature];
MaybeEvictFromBackForwardCache();
}
void RenderFrameHostImpl::OnBackForwardCacheDisablingStickyFeatureUsed(
BackForwardCacheDisablingFeature feature) {
OnBackForwardCacheDisablingFeatureUsed(feature);
}
void RenderFrameHostImpl::OnBackForwardCacheDisablingFeatureRemoved(
BackForwardCacheDisablingFeature feature) {
auto it = browser_reported_bfcache_disabling_features_counts_.find(feature);
DCHECK(it->second >= 1);
if (it->second == 1) {
browser_reported_bfcache_disabling_features_counts_.erase(it);
} else {
--it->second;
}
}
RenderFrameHostImpl::BackForwardCacheDisablingFeatures
RenderFrameHostImpl::GetBackForwardCacheDisablingFeatures() const {
BackForwardCacheDisablingFeatures features =
renderer_reported_bfcache_disabling_features_;
features.PutAll(
DedicatedWorkerHostsForDocument::GetOrCreateForCurrentDocument(
const_cast<RenderFrameHostImpl*>(this))
->GetBackForwardCacheDisablingFeatures());
for (const auto& it : browser_reported_bfcache_disabling_features_counts_) {
features.Put(it.first);
}
return features;
}
RenderFrameHostImpl::BackForwardCacheDisablingFeatureHandle
RenderFrameHostImpl::RegisterBackForwardCacheDisablingNonStickyFeature(
BackForwardCacheDisablingFeature feature) {
return BackForwardCacheDisablingFeatureHandle(this, feature);
}
bool RenderFrameHostImpl::IsFrozen() {
// TODO(crbug.com/1081920): Account for non-bfcache freezing here as well.
return lifecycle_state() == LifecycleStateImpl::kInBackForwardCache;
}
void RenderFrameHostImpl::DidCommitProvisionalLoad(
mojom::DidCommitProvisionalLoadParamsPtr params,
mojom::DidCommitProvisionalLoadInterfaceParamsPtr interface_params) {
if (!MaybeInterceptCommitCallback(nullptr, &params, &interface_params))
return;
DCHECK(params);
DidCommitNavigation(nullptr, std::move(params), std::move(interface_params));
}
void RenderFrameHostImpl::DidCommitPageActivation(
NavigationRequest* committing_navigation_request,
mojom::DidCommitProvisionalLoadParamsPtr params) {
DCHECK(committing_navigation_request->IsPageActivation());
DCHECK(is_main_frame());
auto request = navigation_requests_.find(committing_navigation_request);
CHECK(request != navigation_requests_.end());
base::TimeTicks navigation_start =
committing_navigation_request->NavigationStart();
bool is_prerender_page_activation =
committing_navigation_request->IsPrerenderedPageActivation();
std::unique_ptr<NavigationRequest> owned_request = std::move(request->second);
navigation_requests_.erase(committing_navigation_request);
// Copy the prerendering frame replication state to have it available after
// the navigation commit to be able to check that it didn't change, as
// NavigationRequest will be destroyed by that point.
blink::mojom::FrameReplicationState prerender_main_frame_replication_state;
// Copy the prerendering trigger type and the embbeder histogram suffix for
// metrics before NavigationRequest is destroyed.
PrerenderTriggerType prerender_trigger_type;
std::string prerender_embedder_histogram_suffix;
if (is_prerender_page_activation) {
prerender_main_frame_replication_state =
owned_request->prerender_main_frame_replication_state();
prerender_trigger_type = owned_request->GetPrerenderTriggerType();
prerender_embedder_histogram_suffix =
owned_request->GetPrerenderEmbedderHistogramSuffix();
}
#if DCHECK_IS_ON()
// We do not support activating a page while subframes have any ongoing
// NavigationRequest with a NavigationEntry (this would happen on a navigation
// to an existing entry; this is called a "history navigation"). This would be
// tricky to support because the NavigationRequest would change its
// NavigationController in the course of the activation, and while that may be
// safe for a normal navigation, it has more implications for a history
// navigation as it already has an associated NavigationEntry and we do not
// transfer pending NavigationEntries during activation. Fortunately, for
// prerendering, we do not expect there to be any ongoing history navigations
// in a subframe, because we maintain a trivial session history, so check that
// nav_entry_id() is 0 here. Reloading subframes are considered
// renderer-initiated navigations and do not create a new navigation entry
// when NavigationRequest is created.
//
// Note that due to PrerenderCommitDeferringCondition, the main frame should
// have no ongoing NavigationRequest at all, so it is not checked here.
ForEachRenderFrameHost(base::BindRepeating([](RenderFrameHostImpl* rfh) {
// Interested only in subframes.
if (rfh->is_main_frame())
return;
for (const auto& pair : rfh->navigation_requests_)
DCHECK_EQ(pair.first->nav_entry_id(), 0);
}));
#endif
DidCommitNavigationInternal(std::move(owned_request), std::move(params),
/*same_document_params=*/nullptr);
// If any load events occurred pre-activation and were deferred until
// activation, dispatch them now. This must happen before DidStopLoading() is
// called because observers expect them to occur before that.
if (is_prerender_page_activation)
GetPage().MaybeDispatchLoadEventsOnPrerenderActivation();
// Try to dispatch DidStopLoading event (note that
// RenderFrameHostImpl::DidStopLoading implementation won't dispatch the event
// if the page is still loading). We dispatch it here as it hasn't been
// dispatched pre-activation because the back-forward cache page is already
// loaded, whereas, for initial prerendering navigation, prerendered page
// might still be loading.
DidStopLoading();
if (is_prerender_page_activation) {
// Record metric to check navigation time with prerender activation.
base::TimeDelta delta = base::TimeTicks::Now() - navigation_start;
RecordPrerenderActivationTime(delta, prerender_trigger_type,
prerender_embedder_histogram_suffix);
// We haven't sent any updates to the proxies during prerendering
// activation, so we need to ensure that the new frame replication being
// stored in the browser is the same as the old and consistent with the
// state we've sent to the renderers.
// TODO - can we check main frame replication state?
DCHECK(prerender_main_frame_replication_state ==
frame_tree()->root()->current_replication_state());
}
}
void RenderFrameHostImpl::DidCommitSameDocumentNavigation(
mojom::DidCommitProvisionalLoadParamsPtr params,
mojom::DidCommitSameDocumentNavigationParamsPtr same_document_params) {
TRACE_EVENT2(
"navigation", "RenderFrameHostImpl::DidCommitSameDocumentNavigation",
"render_frame_host", this, "url", params->url.possibly_invalid_spec());
ScopedActiveURL scoped_active_url(params->url,
GetMainFrame()->GetLastCommittedOrigin());
ScopedCommitStateResetter commit_state_resetter(this);
// When the frame is pending deletion, the browser is waiting for it to unload
// properly. In the meantime, because of race conditions, it might tries to
// commit a same-document navigation before unloading. Similarly to what is
// done with cross-document navigations, such navigation are ignored. The
// browser already committed to destroying this RenderFrameHost.
// See https://crbug.com/805705 and https://crbug.com/930132.
// TODO(ahemery): Investigate to see if this can be removed when the
// NavigationClient interface is implemented.
//
// If this is called when the frame is in BackForwardCache, evict the frame
// to avoid ignoring the renderer-initiated navigation, which the frame
// might not expect.
//
// If this is called when the frame is in Prerendering, do not cancel
// Prerendering as prerendered frames can be navigated, including
// same-document navigations like push/replaceState.
if (lifecycle_state() != LifecycleStateImpl::kPrerendering &&
IsInactiveAndDisallowActivation(
DisallowActivationReasonId::kCommitSameDocumentNavigation)) {
return;
}
// Check if the navigation matches a stored same-document NavigationRequest.
// In that case it is browser-initiated.
auto request_entry =
same_document_navigation_requests_.find(params->navigation_token);
bool is_browser_initiated =
(request_entry != same_document_navigation_requests_.end());
std::unique_ptr<NavigationRequest> request =
is_browser_initiated ? std::move(request_entry->second) : nullptr;
same_document_navigation_requests_.erase(params->navigation_token);
if (!MaybeInterceptCommitCallback(request.get(), &params, nullptr)) {
return;
}
if (!DidCommitNavigationInternal(std::move(request), std::move(params),
std::move(same_document_params))) {
return;
}
// Since we didn't early return, it's safe to keep the commit state.
commit_state_resetter.disable();
}
void RenderFrameHostImpl::DidOpenDocumentInputStream(const GURL& url) {
// Check if the URL can actually be committed to the current origin. When
// checking, the restrictions should be the same as a same-document navigation
// since document.open() can only update the URL to a same-origin URL.
if (!ValidateURLAndOrigin(url, last_committed_origin_,
/*is_same_document_navigation=*/true,
/*navigation_request=*/nullptr)) {
return;
}
// Filter the URL, then update `renderer_url_info_`'s `last_document_url`.
// Note that we won't update `last_committed_url_` because this doesn't really
// count as committing a real navigation and won't update NavigationEntry etc.
// See https://crbug.com/1046898 and https://github.com/whatwg/html/pull/6649
// for more details.
GURL filtered_url(url);
GetProcess()->FilterURL(/*empty_allowed=*/false, &filtered_url);
renderer_url_info_.last_document_url = filtered_url;
frame_tree_node_->DidOpenDocumentInputStream();
}
RenderWidgetHostImpl* RenderFrameHostImpl::GetRenderWidgetHost() {
RenderFrameHostImpl* frame = this;
while (frame) {
if (frame->GetLocalRenderWidgetHost())
return frame->GetLocalRenderWidgetHost();
frame = frame->GetParent();
}
NOTREACHED();
return nullptr;
}
RenderWidgetHostView* RenderFrameHostImpl::GetView() {
return GetRenderWidgetHost()->GetView();
}
GlobalRenderFrameHostId RenderFrameHostImpl::GetGlobalId() const {
return GlobalRenderFrameHostId(GetProcess()->GetID(), GetRoutingID());
}
bool RenderFrameHostImpl::HasPendingCommitNavigation() const {
return HasPendingCommitForCrossDocumentNavigation() ||
!same_document_navigation_requests_.empty();
}
bool RenderFrameHostImpl::HasPendingCommitForCrossDocumentNavigation() const {
return !navigation_requests_.empty();
}
NavigationRequest* RenderFrameHostImpl::GetSameDocumentNavigationRequest(
const base::UnguessableToken& token) {
auto request = same_document_navigation_requests_.find(token);
return (request == same_document_navigation_requests_.end())
? nullptr
: request->second.get();
}
void RenderFrameHostImpl::ResetNavigationRequests() {
// Move the NavigationRequests to new maps first before deleting them. This
// avoids issues if a re-entrant call is made when a NavigationRequest is
// being deleted (e.g., if the process goes away as the tab is closing).
std::map<NavigationRequest*, std::unique_ptr<NavigationRequest>>
navigation_requests;
navigation_requests_.swap(navigation_requests);
base::flat_map<base::UnguessableToken, std::unique_ptr<NavigationRequest>>
same_document_navigation_requests;
same_document_navigation_requests_.swap(same_document_navigation_requests);
}
void RenderFrameHostImpl::SetNavigationRequest(
std::unique_ptr<NavigationRequest> navigation_request) {
DCHECK(navigation_request);
is_loading_ = true;
if (NavigationTypeUtils::IsSameDocument(
navigation_request->common_params().navigation_type)) {
same_document_navigation_requests_[navigation_request->commit_params()
.navigation_token] =
std::move(navigation_request);
return;
}
navigation_requests_[navigation_request.get()] =
std::move(navigation_request);
}
const scoped_refptr<NavigationOrDocumentHandle>&
RenderFrameHostImpl::GetNavigationOrDocumentHandle() {
if (!document_associated_data_->navigation_or_document_handle) {
document_associated_data_->navigation_or_document_handle =
NavigationOrDocumentHandle::CreateForDocument(GetGlobalId());
}
return document_associated_data_->navigation_or_document_handle;
}
void RenderFrameHostImpl::Unload(RenderFrameProxyHost* proxy, bool is_loading) {
// The end of this event is in OnUnloadACK when the RenderFrame has completed
// the operation and sends back an IPC message.
TRACE_EVENT_NESTABLE_ASYNC_BEGIN1("navigation", "RenderFrameHostImpl::Unload",
TRACE_ID_LOCAL(this), "render_frame_host",
this);
// If this RenderFrameHost is already pending deletion, it must have already
// gone through this, therefore just return.
if (IsPendingDeletion()) {
NOTREACHED() << "RFH should be in default state when calling Unload.";
return;
}
if (unload_event_monitor_timeout_ && !do_not_delete_for_testing_) {
unload_event_monitor_timeout_->Start(RenderViewHostImpl::kUnloadTimeout);
}
// TODO(nasko): If the frame is not live, the RFH should just be deleted by
// simulating the receipt of unload ack.
is_waiting_for_unload_ack_ = true;
if (proxy) {
SetLifecycleState(LifecycleStateImpl::kRunningUnloadHandlers);
if (IsRenderFrameLive()) {
GetMojomFrameInRenderer()->Unload(
is_loading,
proxy->frame_tree_node()->current_replication_state().Clone(),
proxy->GetFrameToken(), proxy->CreateAndBindRemoteFrameInterfaces(),
proxy->CreateAndBindRemoteMainFrameInterfaces());
// Remember that a `blink::RemoteFrame` was created as part of processing
// the Unload message above.
proxy->SetRenderFrameProxyCreated(true);
}
} else {
// RenderDocument: After a local<->local swap, this function is called with
// a null |proxy|.
CHECK(ShouldCreateNewHostForSameSiteSubframe());
// The unload handlers already ran for this document during the
// local<->local swap. Hence, there is no need to send
// mojom::FrameNavigationControl::Unload here. It can be marked at
// completed.
SetLifecycleState(LifecycleStateImpl::kReadyToBeDeleted);
}
if (web_ui())
web_ui()->RenderFrameHostUnloading();
StartPendingDeletionOnSubtree();
// Some children with no unload handler may be eligible for deletion. Cut the
// dead branches now. This is a performance optimization.
PendingDeletionCheckCompletedOnSubtree();
// |this| is potentially deleted. Do not add code after this.
}
void RenderFrameHostImpl::UndoCommitNavigation(RenderFrameProxyHost& proxy,
bool is_loading) {
TRACE_EVENT("navigation", "RenderFrameHostImpl::UndoCommitNavigation",
"render_frame_host", this);
DCHECK_EQ(lifecycle_state_, LifecycleStateImpl::kPendingCommit);
if (IsRenderFrameLive()) {
// By definition, the browser process has not received the
// `DidCommitNavgation()`, so the RenderFrameProxyHost endpoints are still
// bound. Resetting now means any queued IPCs that are still in-flight will
// be dropped. This is a bit problematic, but it is still less problematic
// than just crashing the renderer for being in an inconsistent state.
proxy.TearDownMojoConnection();
GetMojomFrameInRenderer()->UndoCommitNavigation(
is_loading,
proxy.frame_tree_node()->current_replication_state().Clone(),
proxy.GetFrameToken(), proxy.CreateAndBindRemoteFrameInterfaces(),
proxy.CreateAndBindRemoteMainFrameInterfaces());
}
SetLifecycleState(LifecycleStateImpl::kReadyToBeDeleted);
}
void RenderFrameHostImpl::MaybeDispatchDidFinishLoadOnPrerenderActivation() {
// Don't dispatch notification if DidFinishLoad has not yet been invoked for
// `rfh` i.e., when the url is nullopt.
if (!document_associated_data_->pending_did_finish_load_url_for_prerendering)
return;
delegate_->OnDidFinishLoad(
this,
*document_associated_data_->pending_did_finish_load_url_for_prerendering);
// Set to nullopt to avoid calling DidFinishLoad twice.
document_associated_data_->pending_did_finish_load_url_for_prerendering
.reset();
}
void RenderFrameHostImpl::MaybeDispatchDOMContentLoadedOnPrerenderActivation() {
// Don't send a notification if DOM content is not yet loaded.
if (!document_associated_data_->dom_content_loaded)
return;
delegate_->DOMContentLoaded(this);
}
void RenderFrameHostImpl::SwapOuterDelegateFrame(RenderFrameProxyHost* proxy) {
GetMojomFrameInRenderer()->Unload(
/*is_loading=*/false,
browsing_context_state_->current_replication_state().Clone(),
proxy->GetFrameToken(), proxy->CreateAndBindRemoteFrameInterfaces(),
proxy->CreateAndBindRemoteMainFrameInterfaces());
}
void RenderFrameHostImpl::DetachFromProxy() {
if (IsPendingDeletion())
return;
// Start pending deletion on this frame and its children.
DeleteRenderFrame(mojom::FrameDeleteIntention::kNotMainFrame);
StartPendingDeletionOnSubtree();
frame_tree()->FrameUnloading(frame_tree_node_);
// Some children with no unload handler may be eligible for immediate
// deletion. Cut the dead branches now. This is a performance optimization.
PendingDeletionCheckCompletedOnSubtree(); // May delete |this|.
// |this| is potentially deleted. Do not add code after this.
}
void RenderFrameHostImpl::ProcessBeforeUnloadCompleted(
bool proceed,
bool treat_as_final_completion_callback,
const base::TimeTicks& renderer_before_unload_start_time,
const base::TimeTicks& renderer_before_unload_end_time,
bool for_legacy) {
TRACE_EVENT_NESTABLE_ASYNC_END1(
"navigation", "RenderFrameHostImpl BeforeUnload", TRACE_ID_LOCAL(this),
"render_frame_host", this);
// If this renderer navigated while the beforeunload request was in flight, we
// may have cleared this state in DidCommitProvisionalLoad, in which case we
// can ignore this message.
// However renderer might also be swapped out but we still want to proceed
// with navigation, otherwise it would block future navigations. This can
// happen when pending cross-site navigation is canceled by a second one just
// before DidCommitProvisionalLoad while current RVH is waiting for commit
// but second navigation is started from the beginning.
RenderFrameHostImpl* initiator = GetBeforeUnloadInitiator();
if (!initiator)
return;
// Continue processing the ACK in the frame that triggered beforeunload in
// this frame. This could be either this frame itself or an ancestor frame.
initiator->ProcessBeforeUnloadCompletedFromFrame(
proceed, treat_as_final_completion_callback, this,
/*is_frame_being_destroyed=*/false, renderer_before_unload_start_time,
renderer_before_unload_end_time, for_legacy);
}
RenderFrameHostImpl* RenderFrameHostImpl::GetBeforeUnloadInitiator() {
for (RenderFrameHostImpl* frame = this; frame; frame = frame->GetParent()) {
if (frame->is_waiting_for_beforeunload_completion_)
return frame;
}
return nullptr;
}
void RenderFrameHostImpl::ProcessBeforeUnloadCompletedFromFrame(
bool proceed,
bool treat_as_final_completion_callback,
RenderFrameHostImpl* frame,
bool is_frame_being_destroyed,
const base::TimeTicks& renderer_before_unload_start_time,
const base::TimeTicks& renderer_before_unload_end_time,
bool for_legacy) {
// Check if we need to wait for more beforeunload completion callbacks. If
// |proceed| is false, we know the navigation or window close will be aborted,
// so we don't need to wait for beforeunload completion callbacks from any
// other frames. |treat_as_final_completion_callback| also indicates that we
// shouldn't wait for any other ACKs (e.g., when a beforeunload timeout
// fires).
if (!proceed || treat_as_final_completion_callback) {
beforeunload_pending_replies_.clear();
} else {
beforeunload_pending_replies_.erase(frame);
if (!beforeunload_pending_replies_.empty())
return;
}
DCHECK(!send_before_unload_start_time_.is_null());
// Sets a default value for before_unload_end_time so that the browser
// survives a hacked renderer.
base::TimeTicks before_unload_end_time = renderer_before_unload_end_time;
base::TimeDelta browser_to_renderer_ipc_time_delta;
if (!renderer_before_unload_start_time.is_null() &&
!renderer_before_unload_end_time.is_null()) {
base::TimeTicks before_unload_completed_time = base::TimeTicks::Now();
if (!base::TimeTicks::IsConsistentAcrossProcesses() && !for_legacy) {
// TimeTicks is not consistent across processes and we are passing
// TimeTicks across process boundaries so we need to compensate for any
// skew between the processes. Here we are converting the renderer's
// notion of before_unload_end_time to TimeTicks in the browser process.
// See comments in inter_process_time_ticks_converter.h for more.
blink::InterProcessTimeTicksConverter converter(
blink::LocalTimeTicks::FromTimeTicks(send_before_unload_start_time_),
blink::LocalTimeTicks::FromTimeTicks(before_unload_completed_time),
blink::RemoteTimeTicks::FromTimeTicks(
renderer_before_unload_start_time),
blink::RemoteTimeTicks::FromTimeTicks(
renderer_before_unload_end_time));
const base::TimeTicks browser_before_unload_start_time =
converter
.ToLocalTimeTicks(blink::RemoteTimeTicks::FromTimeTicks(
renderer_before_unload_start_time))
.ToTimeTicks();
const base::TimeTicks browser_before_unload_end_time =
converter
.ToLocalTimeTicks(blink::RemoteTimeTicks::FromTimeTicks(
renderer_before_unload_end_time))
.ToTimeTicks();
before_unload_end_time = browser_before_unload_end_time;
browser_to_renderer_ipc_time_delta =
browser_before_unload_start_time - send_before_unload_start_time_;
} else {
browser_to_renderer_ipc_time_delta =
(renderer_before_unload_start_time - send_before_unload_start_time_);
}
if (for_legacy) {
base::UmaHistogramTimes(
"Navigation.OnBeforeUnloadLegacyPostTaskTime",
before_unload_completed_time - renderer_before_unload_end_time);
// When `for_legacy` is true callers should supply
// `send_before_unload_start_time_` as the value for
// `renderer_before_unload_start_time`, which means
// `browser_to_renderer_ipc_time_delta` should be 0.
DCHECK(browser_to_renderer_ipc_time_delta.is_zero());
} else {
base::UmaHistogramTimes(
"Navigation.OnBeforeUnloadBrowserToRendererIpcTime",
browser_to_renderer_ipc_time_delta);
}
if (!base::FeatureList::IsEnabled(
features::kIncludeIpcOverheadInNavigationStart)) {
browser_to_renderer_ipc_time_delta = base::TimeDelta();
}
base::TimeDelta on_before_unload_overhead_time =
(before_unload_completed_time - send_before_unload_start_time_) -
(renderer_before_unload_end_time - renderer_before_unload_start_time);
base::UmaHistogramTimes("Navigation.OnBeforeUnloadOverheadTime",
on_before_unload_overhead_time);
frame_tree_node_->navigator().LogBeforeUnloadTime(
renderer_before_unload_start_time, renderer_before_unload_end_time,
send_before_unload_start_time_, for_legacy);
}
// Resets beforeunload waiting state.
is_waiting_for_beforeunload_completion_ = false;
has_shown_beforeunload_dialog_ = false;
if (beforeunload_timeout_)
beforeunload_timeout_->Stop();
send_before_unload_start_time_ = base::TimeTicks();
// We could reach this from a subframe destructor for |frame| while we're in
// the middle of closing the current tab. In that case, dispatch the ACK to
// prevent re-entrancy and a potential nested attempt to free the current
// frame. See https://crbug.com/866382 and https://crbug.com/1147567.
base::OnceClosure task = base::BindOnce(
[](base::WeakPtr<RenderFrameHostImpl> self,
const base::TimeTicks& before_unload_end_time, bool proceed,
bool unload_ack_is_for_navigation) {
if (!self)
return;
FrameTreeNode* frame = self->frame_tree_node();
// If the ACK is for a navigation, send it to the Navigator to have the
// current navigation stop/proceed. Otherwise, send it to the
// RenderFrameHostManager which handles closing.
if (unload_ack_is_for_navigation) {
frame->navigator().BeforeUnloadCompleted(frame, proceed,
before_unload_end_time);
} else {
frame->render_manager()->BeforeUnloadCompleted(
proceed, before_unload_end_time);
}
},
// The overhead of the browser->renderer IPC may be non trivial. Account
// for it here. Ideally this would also include the time to execute the
// JS, but we would need to exclude the time spent waiting for a dialog,
// which is tricky.
weak_ptr_factory_.GetWeakPtr(),
before_unload_end_time - browser_to_renderer_ipc_time_delta, proceed,
unload_ack_is_for_navigation_);
if (is_frame_being_destroyed) {
DCHECK(proceed);
base::ThreadTaskRunnerHandle::Get()->PostTask(FROM_HERE, std::move(task));
} else {
std::move(task).Run();
}
// If canceled, notify the delegate to cancel its pending navigation entry.
// This is usually redundant with the dialog closure code in WebContentsImpl's
// OnDialogClosed, but there may be some cases that Blink returns !proceed
// without showing the dialog. We also update the address bar here to be safe.
if (!proceed)
frame_tree_->DidCancelLoading();
}
bool RenderFrameHostImpl::IsWaitingForUnloadACK() const {
return render_view_host_->is_waiting_for_page_close_completion_ ||
is_waiting_for_unload_ack_;
}
bool RenderFrameHostImpl::BeforeUnloadTimedOut() const {
return beforeunload_timeout_ &&
(send_before_unload_start_time_ != base::TimeTicks()) &&
(base::TimeTicks::Now() - send_before_unload_start_time_) >
beforeunload_timeout_delay_;
}
void RenderFrameHostImpl::OnUnloadACK() {
// Give the tests a chance to override this sequence.
if (unload_ack_callback_ && unload_ack_callback_.Run()) {
return;
}
if (frame_tree_node_->render_manager()->is_attaching_inner_delegate()) {
// This RFH was unloaded while attaching an inner delegate. The RFH
// will stay around but it will no longer be associated with a RenderFrame.
RenderFrameDeleted();
return;
}
// Ignore spurious unload ack.
if (!is_waiting_for_unload_ack_)
return;
// Ignore OnUnloadACK if the RenderFrameHost should be left in pending
// deletion state.
if (do_not_delete_for_testing_)
return;
DCHECK_EQ(LifecycleStateImpl::kRunningUnloadHandlers, lifecycle_state());
SetLifecycleState(LifecycleStateImpl::kReadyToBeDeleted);
PendingDeletionCheckCompleted(); // Can delete |this|.
// |this| is potentially deleted. Do not add code after this.
}
void RenderFrameHostImpl::OnUnloaded() {
DCHECK(is_waiting_for_unload_ack_);
TRACE_EVENT_NESTABLE_ASYNC_END0("navigation", "RenderFrameHostImpl::Unload",
TRACE_ID_LOCAL(this));
if (unload_event_monitor_timeout_)
unload_event_monitor_timeout_->Stop();
base::WeakPtr<RenderFrameHostImpl> self = GetWeakPtr();
ClearWebUI();
// See https://crbug.com/1308391. Calling `ClearWebUI()` indirectly call
// content's embedders via a chain of destructors. Some might destroy the
// whole WebContents.
if (!self) {
return;
}
bool deleted =
frame_tree_node_->render_manager()->DeleteFromPendingList(this);
CHECK(deleted);
}
void RenderFrameHostImpl::DisableUnloadTimerForTesting() {
unload_event_monitor_timeout_.reset();
}
void RenderFrameHostImpl::SetSubframeUnloadTimeoutForTesting(
const base::TimeDelta& timeout) {
subframe_unload_timeout_ = timeout;
}
#if BUILDFLAG(IS_ANDROID)
void RenderFrameHostImpl::RequestSmartClipExtract(
ExtractSmartClipDataCallback callback,
gfx::Rect rect) {
int32_t callback_id = smart_clip_callbacks_.Add(
std::make_unique<ExtractSmartClipDataCallback>(std::move(callback)));
GetAssociatedLocalFrame()->ExtractSmartClipData(
rect, base::BindOnce(&RenderFrameHostImpl::OnSmartClipDataExtracted,
base::Unretained(this), callback_id));
}
void RenderFrameHostImpl::OnSmartClipDataExtracted(int32_t callback_id,
const std::u16string& text,
const std::u16string& html,
const gfx::Rect& clip_rect) {
std::move(*smart_clip_callbacks_.Lookup(callback_id))
.Run(text, html, clip_rect);
smart_clip_callbacks_.Remove(callback_id);
}
#endif // BUILDFLAG(IS_ANDROID)
void RenderFrameHostImpl::RunModalAlertDialog(
const std::u16string& alert_message,
bool disable_third_party_subframe_suppresion,
RunModalAlertDialogCallback response_callback) {
auto dialog_closed_callback = base::BindOnce(
[](RunModalAlertDialogCallback response_callback, bool success,
const std::u16string& response) {
// The response string is unused but we use a generic mechanism for
// closing the javascript dialog that returns two arguments.
std::move(response_callback).Run();
},
std::move(response_callback));
RunJavaScriptDialog(alert_message, std::u16string(),
JAVASCRIPT_DIALOG_TYPE_ALERT,
disable_third_party_subframe_suppresion,
std::move(dialog_closed_callback));
}
void RenderFrameHostImpl::RunModalConfirmDialog(
const std::u16string& alert_message,
bool disable_third_party_subframe_suppresion,
RunModalConfirmDialogCallback response_callback) {
auto dialog_closed_callback = base::BindOnce(
[](RunModalConfirmDialogCallback response_callback, bool success,
const std::u16string& response) {
// The response string is unused but we use a generic mechanism for
// closing the javascript dialog that returns two arguments.
std::move(response_callback).Run(success);
},
std::move(response_callback));
RunJavaScriptDialog(alert_message, std::u16string(),
JAVASCRIPT_DIALOG_TYPE_CONFIRM,
disable_third_party_subframe_suppresion,
std::move(dialog_closed_callback));
}
void RenderFrameHostImpl::RunModalPromptDialog(
const std::u16string& alert_message,
const std::u16string& default_value,
bool disable_third_party_subframe_suppresion,
RunModalPromptDialogCallback response_callback) {
RunJavaScriptDialog(
alert_message, default_value, JAVASCRIPT_DIALOG_TYPE_PROMPT,
disable_third_party_subframe_suppresion, std::move(response_callback));
}
void RenderFrameHostImpl::RunJavaScriptDialog(
const std::u16string& message,
const std::u16string& default_prompt,
JavaScriptDialogType dialog_type,
bool disable_third_party_subframe_suppresion,
JavaScriptDialogCallback ipc_response_callback) {
// Don't show the dialog if it's triggered on a non-active or non-primary
// RenderFrameHost. This happens when the RenderFrameHost is pending deletion,
// or is a non-primary MPArch page (Fenced Frame, in BFCache, etc.)..
// TODO(https://crbug.com/1262022): Have to check fenced frames explicitly
// since they are not yet implemented with MPArch. Once the transition from
// shadow DOM to MPArch is complete, remove the last part of the condition.
// TODO(crbug.com/1244137): We have to check portals explicitly as they are
// considered primary. Remove check after we migrate portals to MPArch.
if (!IsActive() || !GetPage().IsPrimary() ||
frame_tree_node_->IsInFencedFrameTree() ||
frame_tree()->delegate()->IsPortal()) {
std::move(ipc_response_callback).Run(/*success=*/false, std::u16string());
return;
}
// While a JS message dialog is showing, tabs in the same process shouldn't
// process input events.
GetProcess()->SetBlocked(true);
delegate_->RunJavaScriptDialog(
this, message, default_prompt, dialog_type,
disable_third_party_subframe_suppresion,
base::BindOnce(&RenderFrameHostImpl::JavaScriptDialogClosed,
weak_ptr_factory_.GetWeakPtr(),
std::move(ipc_response_callback)));
}
void RenderFrameHostImpl::RunBeforeUnloadConfirm(
bool is_reload,
RunBeforeUnloadConfirmCallback ipc_response_callback) {
TRACE_EVENT1("navigation", "RenderFrameHostImpl::OnRunBeforeUnloadConfirm",
"render_frame_host", this);
// Don't show the dialog if it's triggered on a non-active or non-primary
// RenderFrameHost. This happens when the RenderFrameHost is pending deletion,
// or is a non-primary MPArch page (Fenced Frame, in BFCache, etc.)..
// TODO(https://crbug.com/1262022): Have to check fenced frames explicitly
// since they are not yet implemented with MPArch. Once the transition from
// shadow DOM to MPArch is complete, remove the last part of the condition.
if (!IsActive() || !GetPage().IsPrimary() ||
frame_tree_node_->IsInFencedFrameTree() ||
frame_tree()->delegate()->IsPortal()) {
std::move(ipc_response_callback).Run(/*success=*/false);
return;
}
// Allow at most one attempt to show a beforeunload dialog per navigation.
RenderFrameHostImpl* beforeunload_initiator = GetBeforeUnloadInitiator();
if (beforeunload_initiator) {
// If the running beforeunload handler wants to display a dialog and the
// before-unload type wants to ignore it, then short-circuit the request and
// respond as if the user decided to stay on the page, canceling the unload.
if (beforeunload_initiator->beforeunload_dialog_request_cancels_unload_) {
std::move(ipc_response_callback).Run(/*success=*/false);
return;
}
if (beforeunload_initiator->has_shown_beforeunload_dialog_) {
// TODO(alexmos): Pass enough data back to renderer to record histograms
// for Document.BeforeUnloadDialog and add the intervention console
// message to match renderer-side behavior in
// Document::DispatchBeforeUnloadEvent().
std::move(ipc_response_callback).Run(/*success=*/true);
return;
}
beforeunload_initiator->has_shown_beforeunload_dialog_ = true;
} else {
// TODO(alexmos): If a renderer-initiated beforeunload shows a dialog, it
// won't find a |beforeunload_initiator|. This can happen for a
// renderer-initiated navigation or window.close(). We should ensure that
// when the browser process later kicks off subframe unload handlers (if
// any), they won't be able to show additional dialogs. However, we can't
// just set |has_shown_beforeunload_dialog_| because we don't know which
// frame is navigating/closing here. Plumb enough information here to fix
// this.
}
// While a JS beforeunload dialog is showing, tabs in the same process
// shouldn't process input events.
GetProcess()->SetBlocked(true);
// The beforeunload dialog for this frame may have been triggered by a
// browser-side request to this frame or a frame up in the frame hierarchy.
// Stop any timers that are waiting.
for (RenderFrameHostImpl* frame = this; frame; frame = frame->GetParent()) {
if (frame->beforeunload_timeout_)
frame->beforeunload_timeout_->Stop();
}
auto ipc_callback_wrapper = base::BindOnce(
[](RunBeforeUnloadConfirmCallback response_callback, bool success,
const std::u16string& response) {
// The response string is unused but we use a generic mechanism for
// closing the javascript dialog that returns two arguments.
std::move(response_callback).Run(success);
},
std::move(ipc_response_callback));
auto dialog_closed_callback = base::BindOnce(
&RenderFrameHostImpl::JavaScriptDialogClosed,
weak_ptr_factory_.GetWeakPtr(), std::move(ipc_callback_wrapper));
delegate_->RunBeforeUnloadConfirm(this, is_reload,
std::move(dialog_closed_callback));
}
// TODO(crbug.com/1213863): Move this method to content::PageImpl.
void RenderFrameHostImpl::UpdateFaviconURL(
std::vector<blink::mojom::FaviconURLPtr> favicon_urls) {
DCHECK(!GetParent());
GetPage().set_favicon_urls(std::move(favicon_urls));
delegate_->UpdateFaviconURL(this, GetPage().favicon_urls());
}
float RenderFrameHostImpl::GetPageScaleFactor() const {
DCHECK(!GetParent());
return page_scale_factor_;
}
void RenderFrameHostImpl::ScaleFactorChanged(float scale) {
DCHECK(!GetParent());
page_scale_factor_ = scale;
delegate_->OnPageScaleFactorChanged(GetPage());
}
void RenderFrameHostImpl::ContentsPreferredSizeChanged(
const gfx::Size& pref_size) {
// Do not try to handle the message in inactive RenderFrameHosts for
// simplicity. If this RenderFrameHost belongs to a bfcached or prerendered
// page, the page will be deleted. We predict that it will not significantly
// impact coverage because renderers only send this message when running in
// `PreferredSizeChanged` mode.
if (IsInactiveAndDisallowActivation(
DisallowActivationReasonId::kContentsPreferredSizeChanged)) {
return;
}
// Ignore the request if we are aren't the outermost main frame.
if (GetParentOrOuterDocument())
return;
delegate_->UpdateWindowPreferredSize(pref_size);
}
void RenderFrameHostImpl::TextAutosizerPageInfoChanged(
blink::mojom::TextAutosizerPageInfoPtr page_info) {
GetPage().OnTextAutosizerPageInfoChanged(std::move(page_info));
}
void RenderFrameHostImpl::FocusPage() {
render_view_host_->OnFocus();
}
void RenderFrameHostImpl::TakeFocus(bool reverse) {
// TODO(crbug.com/1225366): Consider moving this to PageImpl.
DCHECK(is_main_frame());
// If we are representing an inner frame tree call advance on our outer
// delegate's parent's RenderFrameHost.
RenderFrameHostImpl* parent_or_outer_document =
GetParentOrOuterDocumentOrEmbedder();
if (parent_or_outer_document) {
RenderFrameProxyHost* proxy_host = GetProxyToOuterDelegate();
DCHECK(proxy_host);
parent_or_outer_document->DidFocusFrame();
parent_or_outer_document->AdvanceFocus(
reverse ? blink::mojom::FocusType::kBackward
: blink::mojom::FocusType::kForward,
proxy_host);
return;
}
render_view_host_->OnTakeFocus(reverse);
}
void RenderFrameHostImpl::UpdateTargetURL(
const GURL& url,
blink::mojom::LocalMainFrameHost::UpdateTargetURLCallback callback) {
delegate_->UpdateTargetURL(this, url);
std::move(callback).Run();
}
void RenderFrameHostImpl::RequestClose() {
// If the renderer is telling us to close, it has already run the unload
// events, and we can take the fast path.
render_view_host_->ClosePageIgnoringUnloadEvents();
}
void RenderFrameHostImpl::ShowCreatedWindow(
const blink::LocalFrameToken& opener_frame_token,
WindowOpenDisposition disposition,
const gfx::Rect& initial_rect,
bool user_gesture,
ShowCreatedWindowCallback callback) {
// This needs to be sent to the opener frame's delegate since it stores
// the handle to this class's associated RenderWidgetHostView.
RenderFrameHostImpl* opener_frame_host =
FromFrameToken(GetProcess()->GetID(), opener_frame_token);
// If |opener_frame_host| has been destroyed just return.
// TODO(crbug.com/1150976): Get rid of having to look up the opener frame
// to find the newly created web contents, because it is actually just
// |delegate_|.
if (!opener_frame_host) {
std::move(callback).Run();
return;
}
opener_frame_host->delegate()->ShowCreatedWindow(
opener_frame_host, GetRenderWidgetHost()->GetRoutingID(), disposition,
initial_rect, user_gesture);
std::move(callback).Run();
}
void RenderFrameHostImpl::SetWindowRect(const gfx::Rect& bounds,
SetWindowRectCallback callback) {
// Prerendering pages should not reach this code.
if (lifecycle_state_ == LifecycleStateImpl::kPrerendering) {
local_main_frame_host_receiver_.ReportBadMessage(
"SetWindowRect called during prerendering.");
return;
}
// Throw out SetWindowRects that are not from the outermost document.
if (GetParentOrOuterDocument()) {
local_main_frame_host_receiver_.ReportBadMessage(
"SetWindowRect called from child frame.");
return;
}
delegate_->SetWindowRect(bounds);
std::move(callback).Run();
}
void RenderFrameHostImpl::DidFirstVisuallyNonEmptyPaint() {
// TODO(crbug.com/1225366): Consider moving this to PageImpl.
DCHECK(is_main_frame());
GetPage().OnFirstVisuallyNonEmptyPaint();
}
void RenderFrameHostImpl::DownloadURL(
blink::mojom::DownloadURLParamsPtr blink_parameters) {
// TODO(crbug.com/1205359): We should defer the download until the
// prerendering page is activated, and it will comply with the prerendering
// spec.
if (CancelPrerendering(PrerenderHost::FinalStatus::kDownload)) {
return;
}
if (!VerifyDownloadUrlParams(GetSiteInstance(), *blink_parameters))
return;
net::NetworkTrafficAnnotationTag traffic_annotation =
net::DefineNetworkTrafficAnnotation("renderer_initiated_download", R"(
semantics {
sender: "Download from Renderer"
description:
"The frame has either navigated to a URL that was determined to be "
"a download via one of the renderer's classification mechanisms, "
"or WebView has requested a <canvas> or <img> element at a "
"specific location be to downloaded."
trigger:
"The user navigated to a destination that was categorized as a "
"download, or WebView triggered saving a <canvas> or <img> tag."
data: "Only the URL we are attempting to download."
destination: WEBSITE
}
policy {
cookies_allowed: YES
cookies_store: "user"
setting: "This feature cannot be disabled by settings."
chrome_policy {
DownloadRestrictions {
DownloadRestrictions: 3
}
}
})");
std::unique_ptr<download::DownloadUrlParameters> parameters(
new download::DownloadUrlParameters(blink_parameters->url,
GetProcess()->GetID(), GetRoutingID(),
traffic_annotation));
parameters->set_content_initiated(!blink_parameters->is_context_menu_save);
parameters->set_has_user_gesture(blink_parameters->has_user_gesture);
parameters->set_suggested_name(
blink_parameters->suggested_name.value_or(std::u16string()));
parameters->set_prompt(blink_parameters->is_context_menu_save);
parameters->set_cross_origin_redirects(
blink_parameters->cross_origin_redirects);
parameters->set_referrer(
blink_parameters->referrer ? blink_parameters->referrer->url : GURL());
parameters->set_referrer_policy(Referrer::ReferrerPolicyForUrlRequest(
blink_parameters->referrer ? blink_parameters->referrer->policy
: network::mojom::ReferrerPolicy::kDefault));
parameters->set_initiator(
blink_parameters->initiator_origin.value_or(url::Origin()));
parameters->set_download_source(download::DownloadSource::FROM_RENDERER);
if (blink_parameters->data_url_blob) {
DataURLBlobReader::ReadDataURLFromBlob(
std::move(blink_parameters->data_url_blob),
base::BindOnce(&OnDataURLRetrieved, std::move(parameters)));
return;
}
scoped_refptr<network::SharedURLLoaderFactory> blob_url_loader_factory;
if (blink_parameters->blob_url_token) {
blob_url_loader_factory =
ChromeBlobStorageContext::URLLoaderFactoryForToken(
GetStoragePartition(), std::move(blink_parameters->blob_url_token));
}
StartDownload(std::move(parameters), std::move(blob_url_loader_factory));
}
void RenderFrameHostImpl::ReportNoBinderForInterface(const std::string& error) {
broker_receiver_.ReportBadMessage(error + " for the frame/document scope");
}
ukm::SourceId RenderFrameHostImpl::GetPageUkmSourceId() {
// This id for all subframes or fenced frames is the same as the id for the
// outermost main frame. For portals, this id for frames inside a portal is
// the same as the id for the main frame for the portal.
RenderFrameHostImpl* main_frame =
IsNestedWithinFencedFrame() ? GetOutermostMainFrame() : GetMainFrame();
int64_t navigation_id =
main_frame->last_committed_cross_document_navigation_id_;
if (navigation_id == -1)
return ukm::kInvalidSourceId;
return ukm::ConvertToSourceId(navigation_id,
ukm::SourceIdType::NAVIGATION_ID);
}
BrowserContext* RenderFrameHostImpl::GetBrowserContext() {
return GetProcess()->GetBrowserContext();
}
// TODO(crbug.com/1091720): Would be better to do this directly in the chrome
// layer. See referenced bug for further details.
void RenderFrameHostImpl::ReportInspectorIssue(
blink::mojom::InspectorIssueInfoPtr info) {
devtools_instrumentation::BuildAndReportBrowserInitiatedIssue(
this, std::move(info));
}
void RenderFrameHostImpl::WriteIntoTrace(
perfetto::TracedProto<TraceProto> proto) const {
proto.Set(TraceProto::kRenderFrameHostId, GetGlobalId());
proto->set_frame_tree_node_id(frame_tree_node_->frame_tree_node_id());
proto->set_lifecycle_state(LifecycleStateToProto());
proto->set_origin(GetLastCommittedOrigin().GetDebugString());
proto->set_url(GetLastCommittedURL().possibly_invalid_spec());
proto.Set(TraceProto::kProcess, GetProcess());
proto.Set(TraceProto::kSiteInstance, GetSiteInstance());
if (auto* parent = GetParent()) {
proto.Set(TraceProto::kParent, parent);
} else if (auto* outer_document = GetParentOrOuterDocument()) {
proto.Set(TraceProto::kOuterDocument, outer_document);
outer_document->WriteIntoTrace(proto.WriteNestedMessage(
perfetto::protos::pbzero::RenderFrameHost::kOuterDocument));
} else if (auto* embedder = GetParentOrOuterDocumentOrEmbedder()) {
proto.Set(TraceProto::kEmbedder, embedder);
embedder->WriteIntoTrace(proto.WriteNestedMessage(
perfetto::protos::pbzero::RenderFrameHost::kEmbedder));
}
proto.Set(TraceProto::kBrowsingContextState, browsing_context_state_);
}
perfetto::protos::pbzero::RenderFrameHost::LifecycleState
RenderFrameHostImpl::LifecycleStateToProto() const {
using RFHProto = perfetto::protos::pbzero::RenderFrameHost;
switch (lifecycle_state()) {
case LifecycleStateImpl::kSpeculative:
return RFHProto::SPECULATIVE;
case LifecycleStateImpl::kPendingCommit:
return RFHProto::PENDING_COMMIT;
case LifecycleStateImpl::kPrerendering:
return RFHProto::PRERENDERING;
case LifecycleStateImpl::kActive:
return RFHProto::ACTIVE;
case LifecycleStateImpl::kInBackForwardCache:
return RFHProto::IN_BACK_FORWARD_CACHE;
case LifecycleStateImpl::kRunningUnloadHandlers:
return RFHProto::RUNNING_UNLOAD_HANDLERS;
case LifecycleStateImpl::kReadyToBeDeleted:
return RFHProto::READY_TO_BE_DELETED;
}
return RFHProto::UNSPECIFIED;
}
StoragePartitionImpl* RenderFrameHostImpl::GetStoragePartition() {
// Both RenderProcessHostImpl and MockRenderProcessHost obtain the
// StoragePartition instance through BrowserContext::GetStoragePartition()
// call. That method does not support creating TestStoragePartition
// instances and always vends StoragePartitionImpl objects. It is therefore
// safe to static cast the result here.
return static_cast<StoragePartitionImpl*>(
GetProcess()->GetStoragePartition());
}
void RenderFrameHostImpl::RequestTextSurroundingSelection(
blink::mojom::LocalFrame::GetTextSurroundingSelectionCallback callback,
int max_length) {
DCHECK(!callback.is_null());
GetAssociatedLocalFrame()->GetTextSurroundingSelection(max_length,
std::move(callback));
}
bool RenderFrameHostImpl::HasCommittingNavigationRequestForOrigin(
const url::Origin& origin,
NavigationRequest* navigation_request_to_exclude) {
for (const auto& it : navigation_requests_) {
NavigationRequest* request = it.first;
if (request != navigation_request_to_exclude &&
request->HasCommittingOrigin(origin)) {
return true;
}
}
// Note: this function excludes |same_document_navigation_requests_|, which
// should be ok since these cannot change the origin.
return false;
}
void RenderFrameHostImpl::SendInterventionReport(const std::string& id,
const std::string& message) {
GetAssociatedLocalFrame()->SendInterventionReport(id, message);
}
WebUI* RenderFrameHostImpl::GetWebUI() {
return web_ui();
}
void RenderFrameHostImpl::AllowBindings(int bindings_flags) {
// Never grant any bindings to browser plugin guests.
if (GetProcess()->IsForGuestsOnly()) {
NOTREACHED() << "Never grant bindings to a guest process.";
return;
}
TRACE_EVENT2("navigation", "RenderFrameHostImpl::AllowBindings",
"render_frame_host", this, "bindings_flags", bindings_flags);
int webui_bindings = bindings_flags & kWebUIBindingsPolicyMask;
// TODO(nasko): Ensure callers that specify non-zero WebUI bindings are
// doing so on a RenderFrameHost that has WebUI associated with it.
// The bindings being granted here should not differ from the bindings that
// the associated WebUI requires.
if (web_ui_)
CHECK_EQ(web_ui_->GetBindings(), webui_bindings);
// Ensure we aren't granting WebUI bindings to a process that has already
// been used for non-privileged views.
if (webui_bindings && GetProcess()->IsInitializedAndNotDead() &&
!ChildProcessSecurityPolicyImpl::GetInstance()->HasWebUIBindings(
GetProcess()->GetID())) {
// This process has no bindings yet. Make sure it does not have more
// than this single active view.
// --single-process only has one renderer.
if (GetProcess()->GetActiveViewCount() > 1 &&
!base::CommandLine::ForCurrentProcess()->HasSwitch(
switches::kSingleProcess))
return;
}
if (webui_bindings) {
ChildProcessSecurityPolicyImpl::GetInstance()->GrantWebUIBindings(
GetProcess()->GetID(), webui_bindings);
}
enabled_bindings_ |= bindings_flags;
if (is_render_frame_created()) {
GetFrameBindingsControl()->AllowBindings(enabled_bindings_);
if (web_ui_ && enabled_bindings_ & BINDINGS_POLICY_WEB_UI)
web_ui_->SetUpMojoConnection();
}
}
int RenderFrameHostImpl::GetEnabledBindings() {
return enabled_bindings_;
}
void RenderFrameHostImpl::SetWebUIProperty(const std::string& name,
const std::string& value) {
// WebUI allows to register SetProperties only for the outermost main frame.
if (GetParentOrOuterDocument())
return;
// This is a sanity check before telling the renderer to enable the property.
// It could lie and send the corresponding IPC messages anyway, but we will
// not act on them if enabled_bindings_ doesn't agree. If we get here without
// WebUI bindings, terminate the renderer process.
if (enabled_bindings_ & BINDINGS_POLICY_WEB_UI)
web_ui_->SetProperty(name, value);
else
ReceivedBadMessage(GetProcess(), bad_message::RVH_WEB_UI_BINDINGS_MISMATCH);
}
void RenderFrameHostImpl::DisableBeforeUnloadHangMonitorForTesting() {
beforeunload_timeout_.reset();
}
bool RenderFrameHostImpl::IsBeforeUnloadHangMonitorDisabledForTesting() {
return !beforeunload_timeout_;
}
void RenderFrameHostImpl::DoNotDeleteForTesting() {
do_not_delete_for_testing_ = true;
}
void RenderFrameHostImpl::ResumeDeletionForTesting() {
do_not_delete_for_testing_ = false;
}
void RenderFrameHostImpl::DetachForTesting() {
do_not_delete_for_testing_ = false;
RenderFrameHostImpl::Detach();
}
bool RenderFrameHostImpl::IsFeatureEnabled(
blink::mojom::PermissionsPolicyFeature feature) {
return permissions_policy_ && permissions_policy_->IsFeatureEnabledForOrigin(
feature, GetLastCommittedOrigin());
}
void RenderFrameHostImpl::ViewSource() {
delegate_->ViewSource(this);
}
void RenderFrameHostImpl::FlushNetworkAndNavigationInterfacesForTesting(
bool do_nothing_if_no_network_service_connection) {
if (do_nothing_if_no_network_service_connection &&
!network_service_disconnect_handler_holder_) {
return;
}
DCHECK(network_service_disconnect_handler_holder_);
network_service_disconnect_handler_holder_.FlushForTesting(); // IN-TEST
DCHECK(IsRenderFrameLive());
DCHECK(frame_);
frame_.FlushForTesting(); // IN-TEST
}
void RenderFrameHostImpl::PrepareForInnerWebContentsAttach(
PrepareForInnerWebContentsAttachCallback callback) {
frame_tree_node_->render_manager()->PrepareForInnerDelegateAttach(
std::move(callback));
}
// UpdateSubresourceLoaderFactories may be called (internally/privately), when
// RenderFrameHostImpl detects a NetworkService crash after pushing a
// NetworkService-based factory to the renderer process. It may also be called
// when DevTools wants to send to the renderer process a fresh factory bundle
// (e.g. after injecting DevToolsURLLoaderInterceptor) - the latter scenario may
// happen even if `this` RenderFrameHostImpl has not pushed any NetworkService
// factories to the renderer process (DevTools is agnostic to this).
void RenderFrameHostImpl::UpdateSubresourceLoaderFactories() {
// Disregard this if frame is being destroyed.
if (!frame_)
return;
// The `subresource_loader_factories_config` of the new factories might need
// to depend on the pending (rather than the last committed) navigation,
// because we can't predict if an in-flight Commit IPC might be present when
// an extension injects a content script and MarkIsolatedWorlds... is called.
// See also the doc comment for the ForPendingOrLastCommittedNavigation
// method.
auto subresource_loader_factories_config =
SubresourceLoaderFactoriesConfig::ForPendingOrLastCommittedNavigation(
*this);
mojo::PendingRemote<network::mojom::URLLoaderFactory> default_factory_remote;
bool bypass_redirect_checks = false;
if (recreate_default_url_loader_factory_after_network_service_crash_) {
DCHECK(!IsOutOfProcessNetworkService() ||
network_service_disconnect_handler_holder_.is_bound());
bypass_redirect_checks = CreateNetworkServiceDefaultFactoryAndObserve(
CreateURLLoaderFactoryParamsForMainWorld(
subresource_loader_factories_config,
"RFHI::UpdateSubresourceLoaderFactories"),
subresource_loader_factories_config.ukm_source_id(),
default_factory_remote.InitWithNewPipeAndPassReceiver());
}
std::unique_ptr<blink::PendingURLLoaderFactoryBundle>
subresource_loader_factories =
std::make_unique<blink::PendingURLLoaderFactoryBundle>(
std::move(default_factory_remote),
blink::PendingURLLoaderFactoryBundle::SchemeMap(),
CreateURLLoaderFactoriesForIsolatedWorlds(
subresource_loader_factories_config,
isolated_worlds_requiring_separate_url_loader_factory_),
bypass_redirect_checks);
GetMojomFrameInRenderer()->UpdateSubresourceLoaderFactories(
std::move(subresource_loader_factories));
}
blink::FrameOwnerElementType RenderFrameHostImpl::GetFrameOwnerElementType() {
return frame_tree_node_->frame_owner_element_type();
}
bool RenderFrameHostImpl::HasTransientUserActivation() {
return frame_tree_node_->HasTransientUserActivation();
}
void RenderFrameHostImpl::NotifyUserActivation(
blink::mojom::UserActivationNotificationType notification_type) {
GetAssociatedLocalFrame()->NotifyUserActivation(notification_type);
}
void RenderFrameHostImpl::DidAccessInitialMainDocument() {
frame_tree_->DidAccessInitialMainDocument();
}
void RenderFrameHostImpl::DidChangeName(const std::string& name,
const std::string& unique_name) {
// Frame name updates used to occur in the FrameTreeNode; however, as they
// now occur in RenderFrameHostImpl (and by extension, BrowsingContextState),
// ensure that invalid updates (i.e. when in the BackForwardCache or in a
// pending deletion state) are not applied.
if ((IsInBackForwardCache() || IsPendingDeletion()) &&
base::FeatureList::IsEnabled(
features::kDisableFrameNameUpdateOnNonCurrentRenderFrameHost)) {
return;
}
if (GetParent() != nullptr) {
// TODO(lukasza): Call ReceivedBadMessage when |unique_name| is empty.
DCHECK(!unique_name.empty());
}
TRACE_EVENT2("navigation", "RenderFrameHostImpl::OnDidChangeName",
"render_frame_host", this, "name", name);
std::string old_name = browsing_context_state_->frame_name();
browsing_context_state_->SetFrameName(name, unique_name);
if (old_name.empty() && !name.empty())
frame_tree_node_->render_manager()->CreateProxiesForNewNamedFrame(
browsing_context_state_);
delegate_->DidChangeName(this, name);
}
void RenderFrameHostImpl::EnforceInsecureRequestPolicy(
blink::mojom::InsecureRequestPolicy policy) {
browsing_context_state_->SetInsecureRequestPolicy(policy);
}
void RenderFrameHostImpl::EnforceInsecureNavigationsSet(
const std::vector<uint32_t>& set) {
browsing_context_state_->SetInsecureNavigationsSet(set);
}
void RenderFrameHostImpl::AddDocumentService(
internal::DocumentServiceBase* document_service,
base::PassKey<internal::DocumentServiceBase>) {
document_associated_data_->services.push_back(document_service);
}
void RenderFrameHostImpl::RemoveDocumentService(
internal::DocumentServiceBase* document_service,
base::PassKey<internal::DocumentServiceBase>) {
if (document_service == last_web_bluetooth_service_for_testing_) {
last_web_bluetooth_service_for_testing_ = nullptr;
}
base::Erase(document_associated_data_->services, document_service);
}
FrameTreeNode* RenderFrameHostImpl::FindAndVerifyChild(
int32_t child_frame_routing_id,
bad_message::BadMessageReason reason) {
auto child_frame_or_proxy = LookupRenderFrameHostOrProxy(
GetProcess()->GetID(), child_frame_routing_id);
return FindAndVerifyChildInternal(child_frame_or_proxy, reason);
}
FrameTreeNode* RenderFrameHostImpl::FindAndVerifyChild(
const blink::FrameToken& child_frame_token,
bad_message::BadMessageReason reason) {
auto child_frame_or_proxy =
LookupRenderFrameHostOrProxy(GetProcess()->GetID(), child_frame_token);
return FindAndVerifyChildInternal(child_frame_or_proxy, reason);
}
FrameTreeNode* RenderFrameHostImpl::FindAndVerifyChildInternal(
RenderFrameHostOrProxy child_frame_or_proxy,
bad_message::BadMessageReason reason) {
// A race can result in |child| to be nullptr. Avoid killing the renderer in
// that case.
if (!child_frame_or_proxy)
return nullptr;
if (child_frame_or_proxy.GetFrameTreeNode()->frame_tree() != frame_tree()) {
// Ignore the cases when the child lives in a different frame tree.
// This is possible when we create a proxy for inner WebContents (e.g.
// for portals) so the |child_frame_or_proxy| points to the root frame
// of the nested WebContents, which is in a different tree.
// TODO(altimin, lfg): Reconsider what the correct behaviour here should be.
return nullptr;
}
if (child_frame_or_proxy.GetFrameTreeNode()->parent() != this) {
bad_message::ReceivedBadMessage(GetProcess(), reason);
return nullptr;
}
return child_frame_or_proxy.GetFrameTreeNode();
}
void RenderFrameHostImpl::UpdateTitle(
const absl::optional<::std::u16string>& title,
base::i18n::TextDirection title_direction) {
// This message should only be sent for top-level frames.
if (!is_main_frame())
return;
std::u16string received_title;
if (title.has_value())
received_title = title.value();
if (received_title.length() > blink::mojom::kMaxTitleChars) {
mojo::ReportBadMessage("Renderer sent too many characters in title.");
return;
}
delegate_->UpdateTitle(this, received_title, title_direction);
}
void RenderFrameHostImpl::DidUpdatePreferredColorScheme(
blink::mojom::PreferredColorScheme preferred_color_scheme) {
preferred_color_scheme_ = preferred_color_scheme;
}
void RenderFrameHostImpl::DidInferColorScheme(
blink::mojom::PreferredColorScheme color_scheme) {
if (is_main_frame()) {
GetPage().DidInferColorScheme(color_scheme);
}
}
void RenderFrameHostImpl::UpdateEncoding(const std::string& encoding_name) {
if (!is_main_frame()) {
mojo::ReportBadMessage("Renderer sent updated encoding for a subframe.");
return;
}
GetPage().UpdateEncoding(encoding_name);
}
void RenderFrameHostImpl::FullscreenStateChanged(
bool is_fullscreen,
blink::mojom::FullscreenOptionsPtr options) {
if (IsInactiveAndDisallowActivation(
DisallowActivationReasonId::kFullScreenStateChanged))
return;
delegate_->FullscreenStateChanged(this, is_fullscreen, std::move(options));
}
void RenderFrameHostImpl::RegisterProtocolHandler(const std::string& scheme,
const GURL& url,
bool user_gesture) {
delegate_->RegisterProtocolHandler(this, scheme, url, user_gesture);
}
void RenderFrameHostImpl::UnregisterProtocolHandler(const std::string& scheme,
const GURL& url,
bool user_gesture) {
delegate_->UnregisterProtocolHandler(this, scheme, url, user_gesture);
}
void RenderFrameHostImpl::DidDisplayInsecureContent() {
frame_tree_->controller().ssl_manager()->DidDisplayMixedContent();
}
void RenderFrameHostImpl::DidContainInsecureFormAction() {
frame_tree_->controller().ssl_manager()->DidContainInsecureFormAction();
}
void RenderFrameHostImpl::MainDocumentElementAvailable(
bool uses_temporary_zoom_level) {
if (!is_main_frame()) {
bad_message::ReceivedBadMessage(
GetProcess(), bad_message::RFH_INVALID_CALL_FROM_NOT_MAIN_FRAME);
return;
}
GetPage().set_is_main_document_element_available(true);
GetPage().set_uses_temporary_zoom_level(uses_temporary_zoom_level);
// Don't dispatch PrimaryMainDocumentElementAvailable for non-primary
// RenderFrameHosts. As most of the observers are interested only in taking
// into account and can interact with or send IPCs to only the current
// document in the primary main frame. Since the WebContents could be hosting
// more than one main frame (e.g., fenced frame, prerender pages or pending
// delete RFHs), return early for other cases.
if (!IsInPrimaryMainFrame())
return;
delegate_->PrimaryMainDocumentElementAvailable();
if (!uses_temporary_zoom_level)
return;
#if !BUILDFLAG(IS_ANDROID)
HostZoomMapImpl* host_zoom_map =
static_cast<HostZoomMapImpl*>(HostZoomMap::Get(GetSiteInstance()));
host_zoom_map->SetTemporaryZoomLevel(GetProcess()->GetID(),
render_view_host()->GetRoutingID(),
host_zoom_map->GetDefaultZoomLevel());
#endif // !BUILDFLAG(IS_ANDROID)
}
void RenderFrameHostImpl::SetNeedsOcclusionTracking(bool needs_tracking) {
// Do not update the parent on behalf of inactive RenderFrameHost. See also
// https://crbug.com/972566.
if (IsInactiveAndDisallowActivation(
DisallowActivationReasonId::kSetNeedsOcclusionTracking))
return;
RenderFrameProxyHost* proxy = GetProxyToParent();
if (!proxy) {
bad_message::ReceivedBadMessage(GetProcess(),
bad_message::RFH_NO_PROXY_TO_PARENT);
return;
}
if (proxy->is_render_frame_proxy_live()) {
proxy->GetAssociatedRemoteFrame()->SetNeedsOcclusionTracking(
needs_tracking);
}
}
void RenderFrameHostImpl::SetVirtualKeyboardOverlayPolicy(
bool vk_overlays_content) {
// TODO(crbug.com/1225366): Consider moving this to PageImpl.
if (GetOutermostMainFrame() != this) {
bad_message::ReceivedBadMessage(
GetProcess(),
bad_message::RFHI_SET_OVERLAYS_CONTENT_NOT_OUTERMOST_FRAME);
return;
}
GetPage().set_virtual_keyboard_overlays_content(vk_overlays_content);
}
#if BUILDFLAG(IS_ANDROID)
void RenderFrameHostImpl::UpdateUserGestureCarryoverInfo() {
// This should not occur for prerenders but may occur for pages in
// the BackForwardCache depending on timing.
if (!IsActive())
return;
delegate_->UpdateUserGestureCarryoverInfo();
}
#endif
void RenderFrameHostImpl::VisibilityChanged(
blink::mojom::FrameVisibility visibility) {
visibility_ = visibility;
}
void RenderFrameHostImpl::DidChangeThemeColor(
absl::optional<SkColor> theme_color) {
// TODO(crbug.com/1225366): Consider moving this to PageImpl.
DCHECK(is_main_frame());
GetPage().OnThemeColorChanged(theme_color);
}
void RenderFrameHostImpl::DidChangeBackgroundColor(SkColor background_color,
bool color_adjust) {
// TODO(crbug.com/1225366): Consider moving this to PageImpl.
DCHECK(is_main_frame());
GetPage().DidChangeBackgroundColor(background_color, color_adjust);
}
void RenderFrameHostImpl::SetCommitCallbackInterceptorForTesting(
CommitCallbackInterceptor* interceptor) {
// This DCHECK's aims to avoid unexpected replacement of an interceptor.
// If this becomes a legitimate use case, feel free to remove.
DCHECK(!commit_callback_interceptor_ || !interceptor);
commit_callback_interceptor_ = interceptor;
}
void RenderFrameHostImpl::SetCreateNewPopupCallbackForTesting(
const CreateNewPopupWidgetCallbackForTesting& callback) {
// This DCHECK aims to avoid unexpected replacement of a callback.
DCHECK(!create_new_popup_widget_callback_ || !callback);
create_new_popup_widget_callback_ = callback;
}
void RenderFrameHostImpl::SetUnloadACKCallbackForTesting(
const UnloadACKCallbackForTesting& callback) {
// This DCHECK aims to avoid unexpected replacement of a callback.
DCHECK(!unload_ack_callback_ || !callback);
unload_ack_callback_ = callback;
}
const net::HttpResponseHeaders* RenderFrameHostImpl::GetLastResponseHeaders() {
// This shouldn't be called before committing the document as this value is
// set during call to RenderFrameHostImpl::DidNavigate which happens after
// commit.
DCHECK_NE(lifecycle_state(), LifecycleStateImpl::kSpeculative);
DCHECK_NE(lifecycle_state(), LifecycleStateImpl::kPendingCommit);
return last_response_head_ ? last_response_head_->headers.get() : nullptr;
}
void RenderFrameHostImpl::DidBlockNavigation(
const GURL& blocked_url,
const GURL& initiator_url,
blink::mojom::NavigationBlockedReason reason) {
delegate_->OnDidBlockNavigation(blocked_url, initiator_url, reason);
}
void RenderFrameHostImpl::DidChangeLoadProgress(double load_progress) {
// We should not be invoking DidChangeLoadProgress for subframes or fenced
// frames as we only update the observers for primary/ prerender main frame
// load progress change.
if (!is_main_frame() || IsFencedFrameRoot())
return;
if (load_progress < GetPage().load_progress())
return;
GetPage().set_load_progress(load_progress);
frame_tree_node_->frame_tree()->delegate()->DidChangeLoadProgress();
}
void RenderFrameHostImpl::DidFinishLoad(const GURL& validated_url) {
// In case of prerendering, we dispatch DidFinishLoad on activation. This is
// done to avoid notifying observers about a load event triggered from a
// inactive RenderFrameHost.
if (lifecycle_state() == LifecycleStateImpl::kPrerendering) {
document_associated_data_->pending_did_finish_load_url_for_prerendering =
validated_url;
return;
}
delegate_->OnDidFinishLoad(this, validated_url);
}
void RenderFrameHostImpl::DispatchLoad() {
TRACE_EVENT1("navigation", "RenderFrameHostImpl::DispatchLoad",
"render_frame_host", this);
// Only active and prerendered documents are allowed to dispatch load events
// to the parent.
if (lifecycle_state() != LifecycleStateImpl::kPrerendering) {
// Don't forward the load event to the parent on behalf of inactive
// RenderFrameHost. This can happen in a race where this inactive
// RenderFrameHost finishes loading just after the frame navigates away.
// See https://crbug.com/626802.
if (IsInactiveAndDisallowActivation(
DisallowActivationReasonId::kDispatchLoad))
return;
}
DCHECK(lifecycle_state() == LifecycleStateImpl::kActive ||
lifecycle_state() == LifecycleStateImpl::kPrerendering);
// Only frames with an out-of-process parent frame should be sending this
// message.
RenderFrameProxyHost* proxy = GetProxyToParent();
if (!proxy) {
bad_message::ReceivedBadMessage(GetProcess(),
bad_message::RFH_NO_PROXY_TO_PARENT);
return;
}
if (proxy->is_render_frame_proxy_live())
proxy->GetAssociatedRemoteFrame()->DispatchLoadEventForFrameOwner();
}
void RenderFrameHostImpl::GoToEntryAtOffset(int32_t offset,
bool has_user_gesture) {
OPTIONAL_TRACE_EVENT2("content", "RenderFrameHostImpl::GoToEntryAtOffset",
"render_frame_host", this, "offset", offset);
// Non-user initiated navigations coming from the renderer should be ignored
// if there is an ongoing browser-initiated navigation.
// See https://crbug.com/879965.
// TODO(arthursonzogni): See if this should check for ongoing navigations in
// the frame(s) affected by the session history navigation, rather than just
// the main frame.
if (Navigator::ShouldIgnoreIncomingRendererRequest(
frame_tree_->root()->navigation_request(), has_user_gesture)) {
return;
}
// All frames are allowed to navigate the global history.
if (delegate_->IsAllowedToGoToEntryAtOffset(offset)) {
if (IsSandboxed(network::mojom::WebSandboxFlags::kTopNavigation)) {
// Keep track of whether this is a session history from a sandboxed iframe
// with top level navigation disallowed.
frame_tree_->controller().GoToOffsetInSandboxedFrame(
offset, GetFrameTreeNodeId());
} else {
frame_tree_->controller().GoToOffsetFromRenderer(offset);
}
}
}
void RenderFrameHostImpl::NavigateToNavigationApiKey(const std::string& key,
bool has_user_gesture) {
// Non-user initiated navigations coming from the renderer should be ignored
// if there is an ongoing browser-initiated navigation.
// See https://crbug.com/879965.
// TODO(arthursonzogni): See if this should check for ongoing navigations in
// the frame(s) affected by the session history navigation, rather than just
// the main frame.
if (Navigator::ShouldIgnoreIncomingRendererRequest(
frame_tree_->root()->navigation_request(), has_user_gesture)) {
return;
}
int sandboxed_source_frame_tree_node_id =
IsSandboxed(network::mojom::WebSandboxFlags::kTopNavigation)
? frame_tree_node()->frame_tree_node_id()
: FrameTreeNode::kFrameTreeNodeInvalidId;
frame_tree_->controller().NavigateToNavigationApiKey(
frame_tree_node(), sandboxed_source_frame_tree_node_id, key);
}
void RenderFrameHostImpl::HandleAccessibilityFindInPageResult(
blink::mojom::FindInPageResultAXParamsPtr params) {
// Only update FindInPageResult on active RenderFrameHost. Note that, it is
// safe to ignore this call for BackForwardCache, as we terminate the
// FindInPage session once the page enters BackForwardCache.
if (lifecycle_state() != LifecycleStateImpl::kActive)
return;
ui::AXMode accessibility_mode = delegate_->GetAccessibilityMode();
if (accessibility_mode.has_mode(ui::AXMode::kNativeAPIs)) {
BrowserAccessibilityManager* manager =
GetOrCreateBrowserAccessibilityManager();
if (manager) {
manager->OnFindInPageResult(params->request_id, params->match_index,
params->start_id, params->start_offset,
params->end_id, params->end_offset);
}
}
}
void RenderFrameHostImpl::HandleAccessibilityFindInPageTermination() {
// Only update FindInPageTermination on active RenderFrameHost. Note that, it
// is safe to ignore this call for BackForwardCache, as we terminate the
// FindInPage session once the page enters BackForwardCache.
if (lifecycle_state() != LifecycleStateImpl::kActive)
return;
ui::AXMode accessibility_mode = delegate_->GetAccessibilityMode();
if (accessibility_mode.has_mode(ui::AXMode::kNativeAPIs)) {
BrowserAccessibilityManager* manager =
GetOrCreateBrowserAccessibilityManager();
if (manager)
manager->OnFindInPageTermination();
}
}
// TODO(crbug.com/1213863): Move this method to content::PageImpl.
void RenderFrameHostImpl::DocumentOnLoadCompleted() {
if (!is_main_frame()) {
bad_message::ReceivedBadMessage(
GetProcess(), bad_message::RFH_INVALID_CALL_FROM_NOT_MAIN_FRAME);
return;
}
GetPage().set_is_on_load_completed_in_main_document(true);
// Don't dispatch DocumentOnLoadCompletedInPrimaryMainFrame for non-primary
// main frames. As most of the observers are interested only in the onload
// completion of the current document in the primary main frame. Since the
// WebContents could be hosting more than one main frame (e.g., fenced frames,
// prerender pages or pending delete RFHs), return early for other cases. In
// case of prerendering, we dispatch DocumentOnLoadCompletedInPrimaryMainFrame
// on activation.
if (!IsInPrimaryMainFrame())
return;
// This message is only sent for top-level frames.
//
// TODO(avi): when frame tree mirroring works correctly, add a check here
// to enforce it.
delegate_->DocumentOnLoadCompleted(this);
}
void RenderFrameHostImpl::ForwardResourceTimingToParent(
blink::mojom::ResourceTimingInfoPtr timing) {
// Only active and prerendered documents are allowed to forward the resource
// timing information to the parent.
if (lifecycle_state() != LifecycleStateImpl::kPrerendering) {
// Don't forward the resource timing of the parent on behalf of inactive
// RenderFrameHost. This can happen in a race where this RenderFrameHost
// finishes loading just after the frame navigates away. See
// https://crbug.com/626802.
if (IsInactiveAndDisallowActivation(
DisallowActivationReasonId::kForwardResourceTimingToParent))
return;
}
DCHECK(lifecycle_state() == LifecycleStateImpl::kActive ||
lifecycle_state() == LifecycleStateImpl::kPrerendering);
RenderFrameProxyHost* proxy = GetProxyToParent();
if (!proxy) {
bad_message::ReceivedBadMessage(GetProcess(),
bad_message::RFH_NO_PROXY_TO_PARENT);
return;
}
if (proxy->is_render_frame_proxy_live()) {
proxy->GetAssociatedRemoteFrame()->AddResourceTimingFromChild(
std::move(timing));
}
}
bool RenderFrameHostImpl::Reload() {
return frame_tree_node_->navigator().controller().ReloadFrame(
frame_tree_node_);
}
void RenderFrameHostImpl::SendAccessibilityEventsToManager(
const AXEventNotificationDetails& details) {
if (browser_accessibility_manager_ &&
!browser_accessibility_manager_->OnAccessibilityEvents(details)) {
// OnAccessibilityEvents returns false in IPC error conditions
AccessibilityFatalError();
}
}
bool RenderFrameHostImpl::IsInactiveAndDisallowActivation(uint64_t reason) {
TRACE_EVENT1("navigation",
"RenderFrameHostImpl::IsInactiveAndDisallowActivation",
"render_frame_host", this);
switch (lifecycle_state_) {
case LifecycleStateImpl::kRunningUnloadHandlers:
case LifecycleStateImpl::kReadyToBeDeleted:
return true;
case LifecycleStateImpl::kInBackForwardCache: {
// This function should not be called with kAXEvent when the page is in
// back/forward cache, because |HandleAXevents()| will continue to process
// accessibility events without evicting unless the kEvictOnAXEvents flag
// is on.
if (!base::FeatureList::IsEnabled(features::kEvictOnAXEvents))
DCHECK_NE(reason, kAXEvent);
BackForwardCacheCanStoreDocumentResult can_store_flat;
can_store_flat.NoDueToDisallowActivation(reason);
EvictFromBackForwardCacheWithFlattenedReasons(can_store_flat);
}
return true;
case LifecycleStateImpl::kPrerendering:
// TODO(https://crbug.com/1126305): Explain why we cancel prerendering
// here, then pass a dedicated FinalStatus.
CancelPrerendering(PrerenderHost::FinalStatus::kDestroyed);
return true;
case LifecycleStateImpl::kSpeculative:
// We do not expect speculative or pending commit RenderFrameHosts to
// generate events that require an active/inactive check. Don't crash the
// browser process in case it comes from a compromised renderer, but kill
// the renderer to avoid further confusion.
bad_message::ReceivedBadMessage(
GetProcess(), bad_message::RFH_INACTIVE_CHECK_FROM_SPECULATIVE_RFH);
return false;
case LifecycleStateImpl::kPendingCommit:
// TODO(https://crbug.com/1191469): Understand the expected behaviour to
// disallow activation for kPendingCommit RenderFrameHosts and update
// accordingly.
bad_message::ReceivedBadMessage(
GetProcess(),
bad_message::RFH_INACTIVE_CHECK_FROM_PENDING_COMMIT_RFH);
return false;
case LifecycleStateImpl::kActive:
return false;
}
}
bool RenderFrameHostImpl::IsInactiveAndDisallowActivationForAXEvents(
const std::vector<ui::AXEvent>& events) {
DCHECK(base::FeatureList::IsEnabled(features::kEvictOnAXEvents));
if (lifecycle_state_ != LifecycleStateImpl::kInBackForwardCache) {
return IsInactiveAndDisallowActivation(
DisallowActivationReasonId::kAXEvent);
}
// If the lifecycle state is |LifecycleStateImpl::kInBackForwardCache|, we
// cannot handle accessibility events any more. We should evict the entry.
BackForwardCacheCanStoreDocumentResult can_store_flat;
can_store_flat.NoDueToAXEvents(events);
EvictFromBackForwardCacheWithFlattenedReasons(can_store_flat);
return true;
}
void RenderFrameHostImpl::EvictFromBackForwardCache(
blink::mojom::RendererEvictionReason reason) {
EvictFromBackForwardCacheWithReason(
RendererEvictionReasonToNotRestoredReason(reason));
}
void RenderFrameHostImpl::EvictFromBackForwardCacheWithReason(
BackForwardCacheMetrics::NotRestoredReason reason) {
// kIgnoreEventAndEvict should never be a reason on its own without further
// details.
DCHECK_NE(reason,
BackForwardCacheMetrics::NotRestoredReason::kIgnoreEventAndEvict);
BackForwardCacheCanStoreDocumentResult flattened_reasons;
flattened_reasons.No(reason);
EvictFromBackForwardCacheWithFlattenedReasons(flattened_reasons);
}
void RenderFrameHostImpl::EvictFromBackForwardCacheWithFlattenedReasons(
BackForwardCacheCanStoreDocumentResult can_store_flat) {
// Create a NotRestoredReasons tree that has |can_store_flat| as a reason
// for |this| RenderFrameHost.
auto can_store =
BackForwardCacheImpl::CreateEvictionBackForwardCacheCanStoreTreeResult(
*this, can_store_flat);
EvictFromBackForwardCacheWithFlattenedAndTreeReasons(can_store);
}
void RenderFrameHostImpl::EvictFromBackForwardCacheWithFlattenedAndTreeReasons(
BackForwardCacheCanStoreDocumentResultWithTree& can_store) {
TRACE_EVENT2("navigation", "RenderFrameHostImpl::EvictFromBackForwardCache",
"can_store", can_store.flattened_reasons.ToString(), "rfh",
static_cast<void*>(this));
TRACE_EVENT("navigation",
"RenderFrameHostImpl::"
"EvictFromBackForwardCacheWithFlattenedAndTreeReasons",
ChromeTrackEvent::kBackForwardCacheCanStoreDocumentResult,
can_store.flattened_reasons);
DCHECK(IsBackForwardCacheEnabled());
if (is_evicted_from_back_forward_cache_)
return;
bool in_back_forward_cache = IsInBackForwardCache();
RenderFrameHostImpl* top_document = this;
while (top_document->parent_) {
top_document = top_document->parent_;
DCHECK_EQ(top_document->IsInBackForwardCache(), in_back_forward_cache);
}
// TODO(hajimehoshi): Record the 'race condition' by JavaScript execution when
// |in_back_forward_cache| is false.
BackForwardCacheMetrics* metrics = top_document->GetBackForwardCacheMetrics();
if (in_back_forward_cache && metrics) {
metrics->SetNotRestoredReasons(can_store);
}
if (!in_back_forward_cache) {
TRACE_EVENT0("navigation", "BackForwardCache_EvictAfterDocumentRestored");
// TODO(crbug.com/1163843): We should no longer get into this branch thanks
// to https://crrev.com/c/2563674 but we do. We should eventually replace
// this with a CHECK.
BackForwardCacheMetrics::RecordEvictedAfterDocumentRestored(
BackForwardCacheMetrics::EvictedAfterDocumentRestoredReason::
kByJavaScript);
CaptureTraceForNavigationDebugScenario(
DebugScenario::kDebugBackForwardCacheEvictRestoreRace);
// A document is evicted from the BackForwardCache, but it has already been
// restored. The current document should be reloaded, because it is not
// salvageable.
frame_tree()->controller().Reload(ReloadType::NORMAL, false);
return;
}
// Check if there is an in-flight navigation restoring the frame that
// is being evicted.
NavigationRequest* in_flight_navigation_request =
top_document->frame_tree_node()->navigation_request();
bool is_navigation_to_evicted_frame_in_flight =
(in_flight_navigation_request &&
in_flight_navigation_request->rfh_restored_from_back_forward_cache() ==
top_document);
if (is_navigation_to_evicted_frame_in_flight) {
// If we are currently navigating to the frame that was just evicted, we
// must restart the navigation. This is important because restarting the
// navigation deletes the NavigationRequest associated with the evicted
// frame (preventing use-after-free).
// This should also happen asynchronously as eviction might happen in the
// middle of another navigation — we should not try to restart the
// navigation in that case.
// NOTE: Here we rely on the PostTask inside this function running before
// the task posted to destroy the evicted frames below.
in_flight_navigation_request->RestartBackForwardCachedNavigation();
}
// Evict the frame and schedule it to be destroyed. Eviction happens
// immediately, but destruction is delayed, so that callers don't have to
// worry about use-after-free of |this|.
top_document->is_evicted_from_back_forward_cache_ = true;
frame_tree()
->controller()
.GetBackForwardCache()
.PostTaskToDestroyEvictedFrames();
}
void RenderFrameHostImpl::
UseDummyStickyBackForwardCacheDisablingFeatureForTesting() {
OnBackForwardCacheDisablingFeatureUsed(
BackForwardCacheDisablingFeature::kDummy);
}
bool RenderFrameHostImpl::HasSeenRecentXrOverlaySetup() {
static constexpr base::TimeDelta kMaxInterval = base::Seconds(1);
base::TimeDelta delta = base::TimeTicks::Now() - last_xr_overlay_setup_time_;
DVLOG(2) << __func__ << ": return " << (delta <= kMaxInterval);
return delta <= kMaxInterval;
}
void RenderFrameHostImpl::SetIsXrOverlaySetup() {
DVLOG(2) << __func__;
last_xr_overlay_setup_time_ = base::TimeTicks::Now();
}
// TODO(alexmos): When the allowFullscreen flag is known in the browser
// process, use it to double-check that fullscreen can be entered here.
void RenderFrameHostImpl::EnterFullscreen(
blink::mojom::FullscreenOptionsPtr options,
EnterFullscreenCallback callback) {
const bool had_fullscreen_token = fullscreen_request_token_.IsActive();
// Entering fullscreen requires a transient user activation, a fullscreen
// capability delegation token, a user-generated screen orientation change, or
// another feature-specific transient allowance.
// CanEnterFullscreenWithoutUserActivation is only ever true in tests, to
// allow fullscreen when mocking screen orientation changes.
if (!delegate_->HasSeenRecentScreenOrientationChange() &&
!WindowPlacementAllowsFullscreen() && !HasSeenRecentXrOverlaySetup() &&
!GetContentClient()
->browser()
->CanEnterFullscreenWithoutUserActivation()) {
// Consume any transient user activation and delegated fullscreen token.
// Reject requests made without transient user activation or a token.
// TODO(lanwei): Investigate whether we can terminate the renderer when
// transient user activation and the delegated token are both inactive.
const bool consumed_activation =
frame_tree_node_->UpdateUserActivationState(
blink::mojom::UserActivationUpdateType::kConsumeTransientActivation,
blink::mojom::UserActivationNotificationType::kNone);
const bool consumed_token = fullscreen_request_token_.ConsumeIfActive();
if (!consumed_activation && !consumed_token) {
DLOG(ERROR) << "Cannot enter fullscreen because there is no transient "
<< "user activation, orientation change, XR overlay, nor "
<< "capability delegation.";
std::move(callback).Run(/*granted=*/false);
return;
}
}
// Frames (possibly a subframe) that are not active nor belonging to a primary
// page should not enter fullscreen.
if (!IsActive() || !GetPage().IsPrimary() ||
!delegate_->CanEnterFullscreenMode(this, *options)) {
std::move(callback).Run(/*granted=*/false);
return;
}
// Allow sites with the window-placement permission to open a popup window
// after requesting fullscreen on a specific screen of a multi-screen device.
// This enables multi-screen content experiences from a single user gesture.
const display::Screen* screen = display::Screen::GetScreen();
display::Display display;
if (base::FeatureList::IsEnabled(
blink::features::kWindowPlacementFullscreenCompanionWindow) &&
screen && screen->GetNumDisplays() > 1 &&
screen->GetDisplayWithDisplayId(options->display_id, &display) &&
IsWindowPlacementGranted(this)) {
transient_allow_popup_.Activate();
}
std::move(callback).Run(/*granted=*/true);
// Entering fullscreen from a cross-process subframe also affects all
// renderers for ancestor frames, which will need to apply fullscreen CSS to
// appropriate ancestor <iframe> elements, fire fullscreenchange events, etc.
// Thus, walk through the ancestor chain of this frame and for each (parent,
// child) pair, send a message about the pending fullscreen change to the
// child's proxy in parent's SiteInstanceGroup. The renderer process will use
// this to find the <iframe> element in the parent frame that will need
// fullscreen styles. This is done at most once per SiteInstanceGroup: for
// example, with a A-B-A-B hierarchy, if the bottom frame goes fullscreen,
// this only needs to notify its parent, and Blink-side logic will take care
// of applying necessary changes to the other two ancestors.
std::set<SiteInstance*> notified_instances;
notified_instances.insert(GetSiteInstance());
for (RenderFrameHostImpl* rfh = this; rfh->GetParent();
rfh = rfh->GetParent()) {
SiteInstance* parent_site_instance = rfh->GetParent()->GetSiteInstance();
if (base::Contains(notified_instances, parent_site_instance))
continue;
RenderFrameProxyHost* child_proxy =
rfh->browsing_context_state()->GetRenderFrameProxyHost(
static_cast<SiteInstanceImpl*>(parent_site_instance)->group());
if (child_proxy->is_render_frame_proxy_live()) {
child_proxy->GetAssociatedRemoteFrame()->WillEnterFullscreen(
options.Clone());
notified_instances.insert(parent_site_instance);
}
}
// Focus the window if another frame may have delegated the capability.
if (had_fullscreen_token && !GetView()->HasFocus())
GetView()->Focus();
delegate_->EnterFullscreenMode(this, *options);
delegate_->FullscreenStateChanged(this, /*is_fullscreen=*/true,
std::move(options));
// The previous call might change the fullscreen state. We need to make sure
// the renderer is aware of that, which is done via the resize message.
// Typically, this will be sent as part of the call on the |delegate_| above
// when resizing the native windows, but sometimes fullscreen can be entered
// without causing a resize, so we need to ensure that the resize message is
// sent in that case. We always send this to the main frame's widget, and if
// there are any OOPIF widgets, this will also trigger them to resize via
// frameRectsChanged.
render_view_host_->GetWidget()->SynchronizeVisualProperties();
}
// TODO(alexmos): When the allowFullscreen flag is known in the browser
// process, use it to double-check that fullscreen can be entered here.
void RenderFrameHostImpl::ExitFullscreen() {
delegate_->ExitFullscreenMode(/*will_cause_resize=*/true);
// The previous call might change the fullscreen state. We need to make sure
// the renderer is aware of that, which is done via the resize message.
// Typically, this will be sent as part of the call on the |delegate_| above
// when resizing the native windows, but sometimes fullscreen can be entered
// without causing a resize, so we need to ensure that the resize message is
// sent in that case. We always send this to the main frame's widget, and if
// there are any OOPIF widgets, this will also trigger them to resize via
// frameRectsChanged.
render_view_host_->GetWidget()->SynchronizeVisualProperties();
}
void RenderFrameHostImpl::SuddenTerminationDisablerChanged(
bool present,
blink::mojom::SuddenTerminationDisablerType disabler_type) {
switch (disabler_type) {
case blink::mojom::SuddenTerminationDisablerType::kBeforeUnloadHandler:
DCHECK_NE(has_before_unload_handler_, present);
if (IsNestedWithinFencedFrame()) {
bad_message::ReceivedBadMessage(
GetProcess(),
bad_message::RFH_BEFOREUNLOAD_HANDLER_NOT_ALLOWED_IN_FENCED_FRAME);
return;
}
has_before_unload_handler_ = present;
break;
case blink::mojom::SuddenTerminationDisablerType::kPageHideHandler:
DCHECK_NE(has_pagehide_handler_, present);
has_pagehide_handler_ = present;
break;
case blink::mojom::SuddenTerminationDisablerType::kUnloadHandler:
DCHECK_NE(has_unload_handler_, present);
if (IsNestedWithinFencedFrame()) {
bad_message::ReceivedBadMessage(
GetProcess(),
bad_message::RFH_UNLOAD_HANDLER_NOT_ALLOWED_IN_FENCED_FRAME);
return;
}
has_unload_handler_ = present;
break;
case blink::mojom::SuddenTerminationDisablerType::kVisibilityChangeHandler:
DCHECK_NE(has_visibilitychange_handler_, present);
has_visibilitychange_handler_ = present;
break;
}
}
bool RenderFrameHostImpl::GetSuddenTerminationDisablerState(
blink::mojom::SuddenTerminationDisablerType disabler_type) {
if (do_not_delete_for_testing_ &&
disabler_type !=
blink::mojom::SuddenTerminationDisablerType::kBeforeUnloadHandler) {
return true;
}
switch (disabler_type) {
case blink::mojom::SuddenTerminationDisablerType::kBeforeUnloadHandler:
return has_before_unload_handler_;
case blink::mojom::SuddenTerminationDisablerType::kPageHideHandler:
return has_pagehide_handler_;
case blink::mojom::SuddenTerminationDisablerType::kUnloadHandler:
return has_unload_handler_;
case blink::mojom::SuddenTerminationDisablerType::kVisibilityChangeHandler:
return has_visibilitychange_handler_;
}
}
bool RenderFrameHostImpl::UnloadHandlerExistsInSameSiteInstanceSubtree() {
DCHECK(!GetParent());
bool result = false;
ForEachRenderFrameHost(base::BindRepeating(
[](const SiteInstanceImpl* main_frame_site_instance,
const PageImpl* main_frame_page, bool* result,
RenderFrameHostImpl* rfhi) {
// If we aren't from the same page ignore unload handlers.
if (&rfhi->GetPage() != main_frame_page)
return FrameIterationAction::kSkipChildren;
if (rfhi->GetSiteInstance() == main_frame_site_instance &&
rfhi->has_unload_handler_) {
*result = true;
return FrameIterationAction::kStop;
}
return FrameIterationAction::kContinue;
},
base::Unretained(GetSiteInstance()), &GetPage(), &result));
return result;
}
void RenderFrameHostImpl::DidDispatchDOMContentLoadedEvent() {
document_associated_data_->dom_content_loaded = true;
// In case of prerendering, we dispatch DOMContentLoaded on activation. This
// is done to avoid notifying observers about a load event triggered from a
// inactive RenderFrameHost.
if (lifecycle_state() == LifecycleStateImpl::kPrerendering)
return;
delegate_->DOMContentLoaded(this);
}
void RenderFrameHostImpl::FocusedElementChanged(
bool is_editable_element,
const gfx::Rect& bounds_in_frame_widget,
blink::mojom::FocusType focus_type) {
if (!GetView())
return;
has_focused_editable_element_ = is_editable_element;
// First convert the bounds to root view.
delegate_->OnFocusedElementChangedInFrame(
this,
gfx::Rect(GetView()->TransformPointToRootCoordSpace(
bounds_in_frame_widget.origin()),
bounds_in_frame_widget.size()),
focus_type);
}
void RenderFrameHostImpl::TextSelectionChanged(const std::u16string& text,
uint32_t offset,
const gfx::Range& range) {
has_selection_ = !text.empty();
GetRenderWidgetHost()->SelectionChanged(text, offset, range);
}
void RenderFrameHostImpl::DidReceiveUserActivation() {
delegate_->DidReceiveUserActivation(this);
}
void RenderFrameHostImpl::MaybeIsolateForUserActivation() {
// If user activation occurs in a frame that previously triggered a site
// isolation hint based on the Cross-Origin-Opener-Policy header, isolate the
// corresponding site for all future BrowsingInstances. We also do this for
// user activation on any same-origin subframe of such COOP main frames.
//
// Note that without user activation, COOP-triggered site isolation is scoped
// only to the current BrowsingInstance. This prevents malicious sites from
// silently loading COOP sites to put them on the isolation list and later
// querying that state.
if (GetMainFrame()
->GetSiteInstance()
->GetSiteInfo()
.does_site_request_dedicated_process_for_coop()) {
// The SiteInfo flag above should guarantee that we've already passed all
// the isolation eligibility checks, such as having the corresponding
// feature enabled or satisfying memory requirements.
DCHECK(base::FeatureList::IsEnabled(
features::kSiteIsolationForCrossOriginOpenerPolicy));
bool is_same_origin_activation =
GetParent() ? GetMainFrame()->GetLastCommittedOrigin().IsSameOriginWith(
GetLastCommittedOrigin())
: true;
if (is_same_origin_activation) {
SiteInstance::StartIsolatingSite(
GetSiteInstance()->GetBrowserContext(),
GetMainFrame()->GetLastCommittedURL(),
ChildProcessSecurityPolicy::IsolatedOriginSource::WEB_TRIGGERED,
SiteIsolationPolicy::ShouldPersistIsolatedCOOPSites());
}
}
}
void RenderFrameHostImpl::UpdateUserActivationState(
blink::mojom::UserActivationUpdateType update_type,
blink::mojom::UserActivationNotificationType notification_type) {
// Don't update UserActivationState for non-active RenderFrameHost. In case
// of BackForwardCache, this is only called for tests and it is safe to ignore
// such requests.
if (lifecycle_state() != LifecycleStateImpl::kActive)
return;
frame_tree_node_->UpdateUserActivationState(update_type, notification_type);
}
void RenderFrameHostImpl::HadStickyUserActivationBeforeNavigationChanged(
bool value) {
browsing_context_state_->OnSetHadStickyUserActivationBeforeNavigation(value);
}
void RenderFrameHostImpl::ScrollRectToVisibleInParentFrame(
const gfx::RectF& rect_to_scroll,
blink::mojom::ScrollIntoViewParamsPtr params) {
RenderFrameProxyHost* proxy = nullptr;
if (IsFencedFrameRoot()) {
// TODO(bokan): This is overly trusting of the renderer. We'll need some way
// to verify that the browser is the one that initiated this
// ScrollFocusedEditable process.
// https://crbug.com/1123606. I&S tracker row 191.
if (!params->for_focused_editable) {
local_frame_host_receiver_.ReportBadMessage(
"ScrollRectToVisibleInParentFrame can only be used for "
"is_for_editable from a fenced frame");
return;
}
proxy = frame_tree_->IsFencedFramesMPArchBased() ? GetProxyToOuterDelegate()
: GetProxyToParent();
} else {
proxy = GetProxyToParent();
}
if (!proxy)
return;
proxy->ScrollRectToVisible(rect_to_scroll, std::move(params));
}
void RenderFrameHostImpl::BubbleLogicalScrollInParentFrame(
blink::mojom::ScrollDirection direction,
ui::ScrollGranularity granularity) {
// Do not update the parent on behalf of inactive RenderFrameHost.
if (IsInactiveAndDisallowActivation(
DisallowActivationReasonId::kDispatchLoad))
return;
// TODO(bokan): This is overly trusting of the renderer. Ideally we'd check
// that a keyboard event was recently sent. https://crbug.com/1123606. I&S
// tracker row 191.
RenderFrameProxyHost* proxy =
(IsFencedFrameRoot() && frame_tree_->IsFencedFramesMPArchBased())
? GetProxyToOuterDelegate()
: GetProxyToParent();
if (!proxy) {
// Only frames with an out-of-process parent frame should be sending this
// message.
bad_message::ReceivedBadMessage(GetProcess(),
bad_message::RFH_NO_PROXY_TO_PARENT);
return;
}
if (proxy->is_render_frame_proxy_live()) {
proxy->GetAssociatedRemoteFrame()->BubbleLogicalScroll(direction,
granularity);
}
}
void RenderFrameHostImpl::ShowPopupMenu(
mojo::PendingRemote<blink::mojom::PopupMenuClient> popup_client,
const gfx::Rect& bounds,
int32_t item_height,
double font_size,
int32_t selected_item,
std::vector<blink::mojom::MenuItemPtr> menu_items,
bool right_aligned,
bool allow_multiple_selection) {
#if BUILDFLAG(USE_EXTERNAL_POPUP_MENU)
// Do not open popups in an inactive document.
if (!IsActive()) {
// Sending this message requires user activation, which is impossible
// for a prerendering document, so the renderer process should be
// terminated. See
// https://html.spec.whatwg.org/multipage/interaction.html#tracking-user-activation.
if (lifecycle_state() == LifecycleStateImpl::kPrerendering) {
bad_message::ReceivedBadMessage(
GetProcess(), bad_message::RFH_POPUP_REQUEST_WHILE_PRERENDERING);
}
return;
}
if (delegate()->ShowPopupMenu(this, &popup_client, bounds, item_height,
font_size, selected_item, &menu_items,
right_aligned, allow_multiple_selection)) {
return;
}
auto* view = render_view_host()->delegate_->GetDelegateView();
if (!view)
return;
gfx::Point original_point(bounds.x(), bounds.y());
gfx::Point transformed_point =
static_cast<RenderWidgetHostViewBase*>(GetView())
->TransformPointToRootCoordSpace(original_point);
gfx::Rect transformed_bounds(transformed_point.x(), transformed_point.y(),
bounds.width(), bounds.height());
view->ShowPopupMenu(this, std::move(popup_client), transformed_bounds,
item_height, font_size, selected_item,
std::move(menu_items), right_aligned,
allow_multiple_selection);
#endif
}
void RenderFrameHostImpl::ShowContextMenu(
mojo::PendingAssociatedRemote<blink::mojom::ContextMenuClient>
context_menu_client,
const blink::UntrustworthyContextMenuParams& params) {
if (IsInactiveAndDisallowActivation(
DisallowActivationReasonId::kShowContextMenu))
return;
// Validate the URLs in |params|. If the renderer can't request the URLs
// directly, don't show them in the context menu.
ContextMenuParams validated_params(params);
// Freshly constructed ContextMenuParams have empty `page_url` and `frame_url`
// - populate them based on trustworthy, browser-side data.
validated_params.page_url = GetOutermostMainFrame()->GetLastCommittedURL();
if (GetParentOrOuterDocument()) {
// Only populate |frame_url| for subframes and fencedframes.
validated_params.frame_url = GetLastCommittedURL();
}
// We don't validate |unfiltered_link_url| so that this field can be used
// when users want to copy the original link URL.
RenderProcessHost* process = GetProcess();
process->FilterURL(true, &validated_params.link_url);
process->FilterURL(true, &validated_params.src_url);
// In theory `page_url` and `frame_url` come from a trustworthy data source
// (from the browser process) and therefore don't need to be validated via
// FilterURL below. In practice, some scenarios depend on clearing of the
// `page_url` - see https://crbug.com/1285312.
process->FilterURL(false, &validated_params.page_url);
process->FilterURL(true, &validated_params.frame_url);
// It is necessary to transform the coordinates to account for nested
// RenderWidgetHosts, such as with out-of-process iframes.
gfx::Point original_point(validated_params.x, validated_params.y);
gfx::Point transformed_point =
static_cast<RenderWidgetHostViewBase*>(GetView())
->TransformPointToRootCoordSpace(original_point);
validated_params.x = transformed_point.x();
validated_params.y = transformed_point.y();
if (validated_params.selection_start_offset < 0) {
bad_message::ReceivedBadMessage(
GetProcess(), bad_message::RFH_NEGATIVE_SELECTION_START_OFFSET);
return;
}
delegate_->ShowContextMenu(*this, std::move(context_menu_client),
validated_params);
}
void RenderFrameHostImpl::DidLoadResourceFromMemoryCache(
const GURL& url,
const std::string& http_method,
const std::string& mime_type,
network::mojom::RequestDestination request_destination,
bool include_credentials) {
delegate_->DidLoadResourceFromMemoryCache(this, url, http_method, mime_type,
request_destination,
include_credentials);
}
void RenderFrameHostImpl::DidChangeFrameOwnerProperties(
const blink::FrameToken& child_frame_token,
blink::mojom::FrameOwnerPropertiesPtr properties) {
auto* child =
FindAndVerifyChild(child_frame_token, bad_message::RFH_OWNER_PROPERTY);
if (!child)
return;
bool has_display_none_property_changed =
properties->is_display_none !=
child->frame_owner_properties().is_display_none;
child->set_frame_owner_properties(*properties);
child->render_manager()->OnDidUpdateFrameOwnerProperties(*properties);
if (has_display_none_property_changed) {
delegate_->DidChangeDisplayState(child->current_frame_host(),
properties->is_display_none);
}
}
void RenderFrameHostImpl::DidChangeOpener(
const absl::optional<blink::LocalFrameToken>& opener_frame_token) {
frame_tree_node_->render_manager()->DidChangeOpener(
opener_frame_token, GetSiteInstance()->group());
}
void RenderFrameHostImpl::DidChangeIframeAttributes(
const blink::FrameToken& child_frame_token,
blink::mojom::IframeAttributesPtr attributes) {
if (attributes->parsed_csp_attribute &&
!ValidateCSPAttribute(
attributes->parsed_csp_attribute->header->header_value)) {
bad_message::ReceivedBadMessage(GetProcess(),
bad_message::RFH_CSP_ATTRIBUTE);
return;
}
auto* child = FindAndVerifyChild(
child_frame_token, bad_message::RFH_DID_CHANGE_IFRAME_ATTRIBUTE);
if (!child)
return;
child->SetAttributes(std::move(attributes));
}
void RenderFrameHostImpl::DidChangeFramePolicy(
const blink::FrameToken& child_frame_token,
const blink::FramePolicy& frame_policy) {
// Ensure that a frame can only update sandbox flags or permissions policy for
// its immediate children. If this is not the case, the renderer is
// considered malicious and is killed.
FrameTreeNode* child = FindAndVerifyChild(
// TODO(iclelland): Rename this message
child_frame_token, bad_message::RFH_SANDBOX_FLAGS);
if (!child)
return;
child->SetPendingFramePolicy(frame_policy);
// Notify the RenderFrame if it lives in a different process from its parent.
// The frame's proxies in other processes also need to learn about the updated
// flags and policy, but these notifications are sent later in
// RenderFrameHostManager::CommitPendingFramePolicy(), when the frame
// navigates and the new policies take effect.
if (child->current_frame_host()->GetSiteInstance() != GetSiteInstance()) {
child->current_frame_host()
->GetAssociatedLocalFrame()
->DidUpdateFramePolicy(frame_policy);
}
}
void RenderFrameHostImpl::CapturePaintPreviewOfSubframe(
const gfx::Rect& clip_rect,
const base::UnguessableToken& guid) {
if (IsInactiveAndDisallowActivation(
DisallowActivationReasonId::kCapturePaintPreview))
return;
// This should only be called on a subframe.
if (is_main_frame()) {
bad_message::ReceivedBadMessage(
GetProcess(), bad_message::RFH_SUBFRAME_CAPTURE_ON_MAIN_FRAME);
return;
}
delegate()->CapturePaintPreviewOfCrossProcessSubframe(clip_rect, guid, this);
}
void RenderFrameHostImpl::SetCloseListener(
mojo::PendingRemote<blink::mojom::CloseListener> listener) {
CloseListenerHost::GetOrCreateForCurrentDocument(this)->SetListener(
std::move(listener));
}
void RenderFrameHostImpl::BindBrowserInterfaceBrokerReceiver(
mojo::PendingReceiver<blink::mojom::BrowserInterfaceBroker> receiver) {
DCHECK(receiver.is_valid());
if (frame_tree()->is_prerendering()) {
// RenderFrameHostImpl will rebind the receiver end of
// BrowserInterfaceBroker if it receives a new one sent from renderer
// processes. It happens when renderer processes navigate to a new document,
// see RenderFrameImpl::DidCommitNavigation() and
// RenderFrameHostImpl::DidCommitNavigation(). So before binding a new
// receiver end of BrowserInterfaceBroker, RenderFrameHostImpl should drop
// all deferred binders to avoid connecting Mojo pipes with old documents.
DCHECK(mojo_binder_policy_applier_)
<< "prerendering pages should have a policy applier";
mojo_binder_policy_applier_->DropDeferredBinders();
}
broker_receiver_.Bind(std::move(receiver));
broker_receiver_.SetFilter(std::make_unique<ActiveURLMessageFilter>(this));
}
void RenderFrameHostImpl::BindAssociatedInterfaceProviderReceiver(
mojo::PendingAssociatedReceiver<blink::mojom::AssociatedInterfaceProvider>
receiver) {
DCHECK(receiver.is_valid());
associated_interface_provider_receiver_.Bind(std::move(receiver));
}
void RenderFrameHostImpl::BindDomOperationControllerHostReceiver(
mojo::PendingAssociatedReceiver<mojom::DomAutomationControllerHost>
receiver) {
DCHECK(receiver.is_valid());
// In the renderer side, the remote is document-associated so the receiver on
// the browser side can be reused after a cross-document navigation.
// TODO(dcheng): Make this document-associated?
dom_automation_controller_receiver_.reset();
dom_automation_controller_receiver_.Bind(std::move(receiver));
dom_automation_controller_receiver_.SetFilter(
CreateMessageFilterForAssociatedReceiver(
mojom::DomAutomationControllerHost::Name_));
}
void RenderFrameHostImpl::SetKeepAliveTimeoutForTesting(
base::TimeDelta timeout) {
keep_alive_handle_factory_.set_timeout(timeout);
}
void RenderFrameHostImpl::UpdateState(const blink::PageState& state) {
OPTIONAL_TRACE_EVENT1("content", "RenderFrameHostImpl::UpdateState",
"render_frame_host", this);
// TODO(creis): Verify the state's ISN matches the last committed FNE.
// Without this check, the renderer can trick the browser into using
// filenames it can't access in a future session restore.
if (!CanAccessFilesOfPageState(state)) {
bad_message::ReceivedBadMessage(
GetProcess(), bad_message::RFH_CAN_ACCESS_FILES_OF_PAGE_STATE);
return;
}
frame_tree_->controller().UpdateStateForFrame(this, state);
}
void RenderFrameHostImpl::OpenURL(blink::mojom::OpenURLParamsPtr params) {
// Verify and unpack the Mojo payload.
GURL validated_url;
scoped_refptr<network::SharedURLLoaderFactory> blob_url_loader_factory;
if (!VerifyOpenURLParams(this, GetSiteInstance(), params, &validated_url,
&blob_url_loader_factory)) {
return;
}
// If the flag `is_unfenced_top_navigation` is set, this is a special code
// path for MPArch fenced frames. The target frame doesn't have a handle
// inside the MPArch renderer process, so we need to set it here.
// TODO(crbug.com/1315802): Refactor _unfencedTop handling.
if (params->is_unfenced_top_navigation) {
GURL validated_params_url = params->url;
// Check that the IPC parameters are valid and that the navigation
// is allowed.
// TODO(crbug.com/1315802): When this handling is refactored into a separate
// IPC, make sure that the checks from VerifyOpenURLParams above are not
// unintentionally weakened.
if (!ValidateUnfencedTopNavigation(this, validated_params_url,
GetProcess()->GetID(), params->post_body,
params->user_gesture)) {
return;
}
TRACE_EVENT1("navigation", "RenderFrameHostImpl::OpenURL(_unfencedTop)",
"url", validated_params_url.possibly_invalid_spec());
// There are some relevant parameter changes to note:
// Change the navigation target to the outermost frame.
// This escapes Portals but not GuestViews.
// - We don't especially care about Portals, because the frame won't be
// user activated until it's no longer in a portal
// - We don't want _unfencedTop navigations to escape a GuestView
// (<webview>) and affect their embedder.
RenderFrameHostImpl* target_frame = GetOutermostMainFrame();
// Change `should_replace_current_entry` to false.
// Fenced frames are enforced to have a history of length 1. Because the
// renderer thinks this navigation is to the fenced frame root, it sets
// `should_replace_current_entry` to true, but we do not want this
// restriction for navigations outside the fenced frame.
// TODO(crbug.com/1315802): Make sure that the browser doesn't rely on
// whether the renderer says we should replace the current entry, i.e.
// make sure there are no situations where we should actually replace the
// current entry but don't, due to this line.
bool should_replace_current_entry = false;
// TODO(crbug.com/1315802): Null out the initiator origin, frame token, and
// site instance.
// We use an opaque `initiator_origin` in order to avoid leaking
// information from the fenced frame to its embedder. (The navigation will
// be treated as cross-origin unconditionally.) We don't need to provide a
// `source_site_instance`.
// url::Origin initiator_origin;
// TODO(crbug.com/1237552): Resolve the discussion of download policy.
blink::NavigationDownloadPolicy download_policy;
target_frame->frame_tree_node()->navigator().NavigateFromFrameProxy(
target_frame, validated_params_url,
base::OptionalOrNullptr(params->initiator_frame_token),
GetProcess()->GetID(), params->initiator_origin, GetSiteInstance(),
content::Referrer(), ui::PAGE_TRANSITION_LINK,
should_replace_current_entry, download_policy, "GET",
/*post_body=*/nullptr, params->extra_headers,
/*blob_url_loader_factory=*/nullptr,
network::mojom::SourceLocation::New(), /*has_user_gesture=*/false,
params->is_form_submission, params->impression, base::TimeTicks::Now(),
/*is_embedder_initiated_fenced_frame_navigation=*/false,
/*is_unfenced_top_navigation=*/true);
return;
}
TRACE_EVENT1("navigation", "RenderFrameHostImpl::OpenURL", "url",
validated_url.possibly_invalid_spec());
frame_tree_node_->navigator().RequestOpenURL(
this, validated_url,
base::OptionalOrNullptr(params->initiator_frame_token),
GetProcess()->GetID(), params->initiator_origin, params->post_body,
params->extra_headers, params->referrer.To<content::Referrer>(),
params->disposition, params->should_replace_current_entry,
params->user_gesture, params->triggering_event_info,
params->href_translate, std::move(blob_url_loader_factory),
params->impression);
}
void RenderFrameHostImpl::GetAssociatedInterface(
const std::string& name,
mojo::PendingAssociatedReceiver<blink::mojom::AssociatedInterface>
receiver) {
// `associated_interface_provider_receiver_` and `associated_registry_` are
// both reset at the same time, so we should never get a request for an
// associated interface when `associated_registry_` is not valid.
DCHECK(associated_registry_);
// Perform Mojo capability control if `mojo_binder_policy_applier_` exists.
if (mojo_binder_policy_applier_ &&
!mojo_binder_policy_applier_->ApplyPolicyToAssociatedBinder(name)) {
return;
}
mojo::ScopedInterfaceEndpointHandle handle = receiver.PassHandle();
if (associated_registry_->TryBindInterface(name, &handle))
return;
}
void RenderFrameHostImpl::DidStopLoading() {
TRACE_EVENT1("navigation", "RenderFrameHostImpl::DidStopLoading",
"render_frame_host", this);
if (did_stop_loading_callback_)
std::move(did_stop_loading_callback_).Run();
// This may be called for newly created frames when the frame is not loading
// that navigate to about:blank, as well as history navigations during
// BeforeUnload or Unload events.
// TODO(fdegans): Change this to a DCHECK after LoadEventProgress has been
// refactored in Blink. See crbug.com/466089
if (!is_loading_)
return;
was_discarded_ = false;
is_loading_ = false;
// If we have a PeakGpuMemoryTrack, close it as loading as stopped. It will
// asynchronously receive the statistics from the GPU process, and update
// UMA stats.
if (loading_mem_tracker_)
loading_mem_tracker_.reset();
// Only inform the FrameTreeNode of a change in load state if the load state
// of this RenderFrameHost is being tracked.
if (!IsPendingDeletion())
frame_tree_node_->DidStopLoading();
}
void RenderFrameHostImpl::GetSavableResourceLinksCallback(
blink::mojom::GetSavableResourceLinksReplyPtr reply) {
if (!reply) {
delegate_->SavableResourceLinksError(this);
return;
}
delegate_->SavableResourceLinksResponse(this, reply->resources_list,
std::move(reply->referrer),
reply->subframes);
}
void RenderFrameHostImpl::DomOperationResponse(const std::string& json_string) {
delegate_->DomOperationResponse(this, json_string);
}
std::unique_ptr<blink::PendingURLLoaderFactoryBundle>
RenderFrameHostImpl::CreateCrossOriginPrefetchLoaderFactoryBundle() {
// IPCs coming from the renderer talk on behalf of the last-committed
// navigation. This also applies to IPCs asking for a prefetch factory
// bundle.
auto subresource_loader_factories_config =
SubresourceLoaderFactoriesConfig::ForLastCommittedNavigation(*this);
network::mojom::URLLoaderFactoryParamsPtr factory_params =
URLLoaderFactoryParamsHelper::CreateForPrefetch(
this, subresource_loader_factories_config.GetClientSecurityState());
mojo::PendingRemote<network::mojom::URLLoaderFactory> pending_default_factory;
bool bypass_redirect_checks = false;
// Passing an empty IsolationInfo ensures the factory is not initialized with
// a IsolationInfo. This is necessary for a cross-origin prefetch factory
// because the factory must use the value provided by requests going through
// it.
bypass_redirect_checks = CreateNetworkServiceDefaultFactoryAndObserve(
std::move(factory_params),
subresource_loader_factories_config.ukm_source_id(),
pending_default_factory.InitWithNewPipeAndPassReceiver());
return std::make_unique<blink::PendingURLLoaderFactoryBundle>(
std::move(pending_default_factory),
blink::PendingURLLoaderFactoryBundle::SchemeMap(),
CreateURLLoaderFactoriesForIsolatedWorlds(
subresource_loader_factories_config,
isolated_worlds_requiring_separate_url_loader_factory_),
bypass_redirect_checks);
}
base::WeakPtr<RenderFrameHostImpl> RenderFrameHostImpl::GetWeakPtr() {
return weak_ptr_factory_.GetWeakPtr();
}
base::SafeRef<RenderFrameHostImpl> RenderFrameHostImpl::GetSafeRef() const {
return weak_ptr_factory_.GetSafeRef();
}
void RenderFrameHostImpl::CreateNewWindow(
mojom::CreateNewWindowParamsPtr params,
CreateNewWindowCallback callback) {
DCHECK_CURRENTLY_ON(BrowserThread::UI);
TRACE_EVENT2("navigation", "RenderFrameHostImpl::CreateNewWindow",
"render_frame_host", this, "url", params->target_url);
bool no_javascript_access = false;
// Filter out URLs to which navigation is disallowed from this context.
//
// Note that currently, "javascript:" URLs and empty strings (both of which
// are legal arguments to window.open) make it here; FilterURL rewrites them
// to "about:blank" -- they shouldn't be cancelled.
GetProcess()->FilterURL(false, &params->target_url);
bool effective_transient_activation_state =
params->allow_popup || frame_tree_node_->HasTransientUserActivation() ||
(transient_allow_popup_.IsActive() &&
params->disposition == WindowOpenDisposition::NEW_POPUP);
// Ignore window creation when sent from a frame that's not active or
// created.
bool can_create_window =
IsActive() && is_render_frame_created() &&
GetContentClient()->browser()->CanCreateWindow(
this, GetLastCommittedURL(),
GetOutermostMainFrame()->GetLastCommittedURL(),
last_committed_origin_, params->window_container_type,
params->target_url, params->referrer.To<Referrer>(),
params->frame_name, params->disposition, *params->features,
effective_transient_activation_state, params->opener_suppressed,
&no_javascript_access);
// If this frame isn't allowed to create a window, return early (before we
// consume transient user activation).
if (!can_create_window) {
std::move(callback).Run(mojom::CreateNewWindowStatus::kBlocked, nullptr);
return;
}
// Otherwise, consume user activation before we proceed. In particular, it is
// important to do this before we return from the |opener_suppressed| case
// below.
// NB: This call will consume activations in the browser and the remote frame
// proxies for this frame. The initiating renderer will consume its view of
// the activations after we return.
bool was_consumed = frame_tree_node_->UpdateUserActivationState(
blink::mojom::UserActivationUpdateType::kConsumeTransientActivation,
blink::mojom::UserActivationNotificationType::kNone);
// For Android WebView, we support a pop-up like behavior for window.open()
// even if the embedding app doesn't support multiple windows. In this case,
// window.open() will return "window" and navigate it to whatever URL was
// passed.
if (!GetOrCreateWebPreferences().supports_multiple_windows) {
// See crbug.com/1083819, we should ignore if the URL is javascript: scheme,
// previously we already filtered out javascript: scheme and replace the
// URL with |kBlockedURL|, so we check against |kBlockedURL| here.
if (params->target_url == GURL(kBlockedURL)) {
std::move(callback).Run(mojom::CreateNewWindowStatus::kIgnore, nullptr);
} else {
std::move(callback).Run(mojom::CreateNewWindowStatus::kReuse, nullptr);
}
return;
}
// This will clone the sessionStorage for namespace_id_to_clone.
StoragePartition* storage_partition = GetStoragePartition();
DOMStorageContextWrapper* dom_storage_context =
static_cast<DOMStorageContextWrapper*>(
storage_partition->GetDOMStorageContext());
scoped_refptr<SessionStorageNamespaceImpl> cloned_namespace;
if (!params->clone_from_session_storage_namespace_id.empty()) {
cloned_namespace = SessionStorageNamespaceImpl::CloneFrom(
dom_storage_context, params->session_storage_namespace_id,
params->clone_from_session_storage_namespace_id);
} else {
cloned_namespace = SessionStorageNamespaceImpl::Create(
dom_storage_context, params->session_storage_namespace_id);
}
RenderFrameHostImpl* top_level_opener = GetMainFrame();
if (IsAnonymous() || IsNestedWithinFencedFrame()) {
params->opener_suppressed = true;
params->frame_name.clear();
} else {
// Check that this RFH and its main document are same origin.
if (!top_level_opener->GetLastCommittedOrigin().IsSameOriginWith(
GetLastCommittedOrigin())) {
// The documents are cross origin, leave COOP of the popup to the default
// unsafe-none.
switch (top_level_opener->cross_origin_opener_policy().value) {
// Those values are explicitly listed here, to force creator of new
// values to make an explicit decision in the future.
// See regression: https://crbug.com/1181673
case network::mojom::CrossOriginOpenerPolicyValue::kUnsafeNone:
case network::mojom::CrossOriginOpenerPolicyValue::
kSameOriginAllowPopups:
case network::mojom::CrossOriginOpenerPolicyValue::kRestrictProperties:
case network::mojom::CrossOriginOpenerPolicyValue::
kRestrictPropertiesPlusCoep:
break;
// See https://html.spec.whatwg.org/C/#browsing-context-names (step 8)
// ```
// If current's top-level browsing context's active document's
// cross-origin opener policy's value is "same-origin" or
// "same-origin-plus-COEP", then [...] set noopener to true, name to
// "_blank", and windowType to "new with no opener".
// ```
case network::mojom::CrossOriginOpenerPolicyValue::kSameOrigin:
case network::mojom::CrossOriginOpenerPolicyValue::kSameOriginPlusCoep:
DCHECK(base::FeatureList::IsEnabled(
network::features::kCrossOriginOpenerPolicy));
params->opener_suppressed = true;
// The frame name should not be forwarded to a noopener popup.
// TODO(https://crbug.com/1060691) This should be applied to all
// popups opened with noopener.
params->frame_name.clear();
}
}
}
int popup_virtual_browsing_context_group =
params->opener_suppressed
? CrossOriginOpenerPolicyAccessReportManager::
NextVirtualBrowsingContextGroup()
: top_level_opener->virtual_browsing_context_group();
int popup_soap_by_default_virtual_browsing_context_group =
params->opener_suppressed
? CrossOriginOpenerPolicyAccessReportManager::
NextVirtualBrowsingContextGroup()
: top_level_opener->soap_by_default_virtual_browsing_context_group();
// If the opener is suppressed or script access is disallowed, we should
// open the window in a new BrowsingInstance, and thus a new process. That
// means the current renderer process will not be able to route messages to
// it. Because of this, we will immediately show and navigate the window
// in OnCreateNewWindowOnUI, using the params provided here.
bool is_new_browsing_instance =
params->opener_suppressed || no_javascript_access;
DCHECK(IsRenderFrameLive());
// The non-owning pointer |new_frame_tree| is valid in this stack frame since
// nothing can delete it until this thread is freed up again.
FrameTree* new_frame_tree =
delegate_->CreateNewWindow(this, *params, is_new_browsing_instance,
was_consumed, cloned_namespace.get());
transient_allow_popup_.Deactivate();
if (is_new_browsing_instance || !new_frame_tree) {
// Opener suppressed, Javascript access disabled, or delegate did not
// provide a handle to any windows it created. In these cases, never tell
// the renderer about the new window.
std::move(callback).Run(mojom::CreateNewWindowStatus::kIgnore, nullptr);
return;
}
RenderFrameHostImpl* new_main_rfh =
new_frame_tree->root()->current_frame_host();
new_main_rfh->virtual_browsing_context_group_ =
popup_virtual_browsing_context_group;
new_main_rfh->soap_by_default_virtual_browsing_context_group_ =
popup_soap_by_default_virtual_browsing_context_group;
// If inheriting coop (checking this via |opener_suppressed|) and the original
// coop page has a reporter we make sure the the newly created popup also has
// a reporter.
if (!params->opener_suppressed &&
GetMainFrame()->coop_access_report_manager()->coop_reporter()) {
new_main_rfh->SetCrossOriginOpenerPolicyReporter(
std::make_unique<CrossOriginOpenerPolicyReporter>(
GetProcess()->GetStoragePartition(), GetLastCommittedURL(),
params->referrer->url, new_main_rfh->cross_origin_opener_policy(),
GetReportingSource(), isolation_info_.network_isolation_key()));
}
mojo::PendingAssociatedRemote<mojom::Frame> pending_frame_remote;
mojo::PendingAssociatedReceiver<mojom::Frame> pending_frame_receiver =
pending_frame_remote.InitWithNewEndpointAndPassReceiver();
new_main_rfh->SetMojomFrameRemote(std::move(pending_frame_remote));
mojo::PendingRemote<blink::mojom::BrowserInterfaceBroker>
browser_interface_broker;
new_main_rfh->BindBrowserInterfaceBrokerReceiver(
browser_interface_broker.InitWithNewPipeAndPassReceiver());
mojo::PendingAssociatedRemote<blink::mojom::AssociatedInterfaceProvider>
pending_associated_interface_provider;
new_main_rfh->BindAssociatedInterfaceProviderReceiver(
pending_associated_interface_provider
.InitWithNewEndpointAndPassReceiver());
// With this path, RenderViewHostImpl::CreateRenderView is never called
// because RenderView is already created on the renderer side. Thus we need to
// establish the connection here.
mojo::PendingAssociatedRemote<blink::mojom::PageBroadcast> page_broadcast;
mojo::PendingAssociatedReceiver<blink::mojom::PageBroadcast>
page_broadcast_receiver =
page_broadcast.InitWithNewEndpointAndPassReceiver();
auto widget_params =
new_main_rfh->GetLocalRenderWidgetHost()
->BindAndGenerateCreateFrameWidgetParamsForNewWindow();
new_main_rfh->render_view_host()->BindPageBroadcast(
std::move(page_broadcast));
bool wait_for_debugger =
devtools_instrumentation::ShouldWaitForDebuggerInWindowOpen();
mojom::CreateNewWindowReplyPtr reply = mojom::CreateNewWindowReply::New(
new_main_rfh->GetFrameToken(), new_main_rfh->GetRoutingID(),
std::move(pending_frame_receiver), std::move(widget_params),
std::move(page_broadcast_receiver), std::move(browser_interface_broker),
std::move(pending_associated_interface_provider), cloned_namespace->id(),
new_main_rfh->GetDevToolsFrameToken(), wait_for_debugger,
new_main_rfh->policy_container_host()->CreatePolicyContainerForBlink());
std::move(callback).Run(mojom::CreateNewWindowStatus::kSuccess,
std::move(reply));
// When `waiting_for_init_` is true, the browser waits for the renderer to
// request to show the window (which becomes a call to Init() on the new
// window's `new_main_rfh`) before servicing subresource requests. We ensure
// this is the first message received by the remote frame (instead of plumbing
// it with the CreateNewWindow IPC).
if (new_main_rfh->waiting_for_init_)
new_main_rfh->GetMojomFrameInRenderer()->BlockRequests();
// The mojom reply callback with kSuccess causes the renderer to create the
// renderer-side objects.
new_main_rfh->render_view_host()->RenderViewCreated(new_main_rfh);
}
void RenderFrameHostImpl::CreatePortal(
mojo::PendingAssociatedReceiver<blink::mojom::Portal> pending_receiver,
mojo::PendingAssociatedRemote<blink::mojom::PortalClient> client,
blink::mojom::RemoteFrameInterfacesFromRendererPtr remote_frame_interfaces,
CreatePortalCallback callback) {
if (!Portal::IsEnabled()) {
frame_host_associated_receiver_.ReportBadMessage(
"blink.mojom.Portal can only be used if the Portals feature is "
"enabled.");
return;
}
// If a Portal is nested inside a prerendered page, its owning RFH is also
// |kPrerendering|. In this case the creation of the Portal is deferred from
// the renderer.
if (GetLifecycleState() == RenderFrameHost::LifecycleState::kPrerendering) {
frame_host_associated_receiver_.ReportBadMessage(
"RenderFrameHostImpl::CreatePortal cannot be called when the RFH is "
"being prerendered.");
return;
}
// We don't support attaching a portal inside a nested browsing context or
// inside a fenced frame.
if (!is_main_frame() || IsFencedFrameRoot()) {
frame_host_associated_receiver_.ReportBadMessage(
"RFHI::CreatePortal called in a nested browsing context");
return;
}
// TODO(crbug.com/1051639): We need to find a long term solution to when/how
// portals should work in sandboxed documents.
if (active_sandbox_flags() != network::mojom::WebSandboxFlags::kNone) {
frame_host_associated_receiver_.ReportBadMessage(
"RFHI::CreatePortal called in a sandboxed browsing context");
return;
}
// Note that we don't check |GetLastCommittedOrigin|, since that is inherited
// by the initial empty document of a new frame.
// TODO(1008989): Once issue 1008989 is fixed we could move this check into
// |Portal::Create|.
if (!GetLastCommittedURL().SchemeIsHTTPOrHTTPS()) {
frame_host_associated_receiver_.ReportBadMessage(
"Portal creation is restricted to the HTTP family.");
return;
}
auto portal = std::make_unique<Portal>(this);
portal->Bind(std::move(pending_receiver), std::move(client));
auto it = portals_.insert(std::move(portal)).first;
RenderFrameProxyHost* proxy_host =
(*it)->CreateProxyAndAttachPortal(std::move(remote_frame_interfaces));
if (!proxy_host) {
frame_host_associated_receiver_.ReportBadMessage(
"Trying to attach a portal that has already been attached.");
return;
}
// Since the portal is newly created and has yet to commit a navigation, this
// state is default-constructed.
const blink::mojom::FrameReplicationState& initial_replicated_state =
proxy_host->frame_tree_node()->current_replication_state();
DCHECK(initial_replicated_state.origin.opaque());
std::move(callback).Run(initial_replicated_state.Clone(),
(*it)->portal_token(), proxy_host->GetFrameToken(),
(*it)->GetDevToolsFrameToken());
}
void RenderFrameHostImpl::AdoptPortal(
const blink::PortalToken& portal_token,
blink::mojom::RemoteFrameInterfacesFromRendererPtr remote_frame_interfaces,
AdoptPortalCallback callback) {
Portal* portal = FindPortalByToken(portal_token);
if (!portal) {
frame_host_associated_receiver_.ReportBadMessage(
"Unknown portal_token when adopting portal.");
return;
}
DCHECK_EQ(portal->owner_render_frame_host(), this);
RenderFrameProxyHost* proxy_host =
portal->CreateProxyAndAttachPortal(std::move(remote_frame_interfaces));
if (!proxy_host) {
frame_host_associated_receiver_.ReportBadMessage(
"Trying to attach a portal that has already been attached.");
return;
}
// |frame_sink_id| should be set to the associated frame. See
// https://crbug.com/966119 for details.
viz::FrameSinkId frame_sink_id = proxy_host->frame_tree_node()
->render_manager()
->GetRenderWidgetHostView()
->GetFrameSinkId();
proxy_host->GetAssociatedRemoteFrame()->SetFrameSinkId(frame_sink_id);
std::move(callback).Run(
proxy_host->frame_tree_node()->current_replication_state().Clone(),
proxy_host->GetFrameToken(), portal->GetDevToolsFrameToken());
}
std::vector<FencedFrame*> RenderFrameHostImpl::GetFencedFrames() const {
std::vector<FencedFrame*> result;
for (const std::unique_ptr<FencedFrame>& fenced_frame : fenced_frames_)
result.push_back(fenced_frame.get());
return result;
}
void RenderFrameHostImpl::DestroyFencedFrame(FencedFrame& fenced_frame) {
auto it = base::ranges::find_if(fenced_frames_,
base::MatchesUniquePtr(&fenced_frame));
CHECK(it != fenced_frames_.end());
fenced_frames_.erase(it);
}
void RenderFrameHostImpl::CreateFencedFrame(
mojo::PendingAssociatedReceiver<blink::mojom::FencedFrameOwnerHost>
pending_receiver,
blink::mojom::FencedFrameMode mode,
blink::mojom::RemoteFrameInterfacesFromRendererPtr remote_frame_interfaces,
const blink::RemoteFrameToken& frame_token,
const base::UnguessableToken& devtools_frame_token) {
// We should defer fenced frame creation during prerendering, so creation at
// this point is an error.
if (GetLifecycleState() == RenderFrameHost::LifecycleState::kPrerendering) {
bad_message::ReceivedBadMessage(GetProcess(),
bad_message::FF_CREATE_WHILE_PRERENDERING);
return;
}
if (!frame_tree_->IsFencedFramesMPArchBased()) {
bad_message::ReceivedBadMessage(
GetProcess(), bad_message::RFH_FENCED_FRAME_MOJO_WHEN_DISABLED);
return;
}
// Cannot create a fenced frame in a sandbox iframe which doesn't allow
// features that need to be allowed in the fenced frame. This check is for
// MPArch Fenced Frame.
if (IsSandboxed(blink::kFencedFrameMandatoryUnsandboxedFlags)) {
bad_message::ReceivedBadMessage(
GetProcess(), bad_message::RFH_CREATE_FENCED_FRAME_IN_SANDBOXED_FRAME);
return;
}
// A fenced frame embedded in another fenced frame cannot have a different
// mode than its embedder. This is checked in the renderer, but needs
// verification in the browser in case the renderer is compromised.
if (GetMainFrame()->IsFencedFrameRoot() &&
GetMainFrame()->frame_tree_node()->GetFencedFrameMode() != mode) {
bad_message::ReceivedBadMessage(
GetProcess(), bad_message::FF_DIFFERENT_MODE_THAN_EMBEDDER);
return;
}
// Check that we have a unique `frame_token`.
if (RenderFrameProxyHost::IsFrameTokenInUse(frame_token)) {
bad_message::ReceivedBadMessage(
GetProcess(), bad_message::RFHI_CREATE_FENCED_FRAME_BAD_FRAME_TOKEN);
return;
}
// Ensure the devtools frame token doesn't exist in the FrameTree for
// this tab.
for (FrameTreeNode* node :
GetOutermostMainFrame()->frame_tree()->NodesIncludingInnerTreeNodes()) {
if (node->devtools_frame_token() == devtools_frame_token) {
bad_message::ReceivedBadMessage(
GetProcess(),
bad_message::RFHI_CREATE_FENCED_FRAME_BAD_DEVTOOLS_FRAME_TOKEN);
return;
}
}
fenced_frames_.push_back(std::make_unique<FencedFrame>(
weak_ptr_factory_.GetSafeRef(), mode, devtools_frame_token));
FencedFrame* fenced_frame = fenced_frames_.back().get();
RenderFrameProxyHost* proxy_host =
fenced_frame->CreateProxyAndAttachToOuterFrameTree(
std::move(remote_frame_interfaces), frame_token);
fenced_frame->Bind(std::move(pending_receiver));
// Since the fenced frame is newly created and has yet to commit a navigation,
// this state is default-constructed.
const blink::mojom::FrameReplicationState& initial_replicated_state =
proxy_host->frame_tree_node()->current_replication_state();
// Note that a default-constructed `FrameReplicationState` always has an
// opaque origin, simply because the frame hasn't had any navigations yet.
// Fenced frames (after their first navigation) do not have opaque origins,
// and this default-constructed FRS does not impact that.
DCHECK(initial_replicated_state.origin.opaque());
}
void RenderFrameHostImpl::CreateNewPopupWidget(
mojo::PendingAssociatedReceiver<blink::mojom::PopupWidgetHost>
blink_popup_widget_host,
mojo::PendingAssociatedReceiver<blink::mojom::WidgetHost> blink_widget_host,
mojo::PendingAssociatedRemote<blink::mojom::Widget> blink_widget) {
// Do not open popups in an inactive document.
if (!IsActive()) {
// Sending this message requires user activation, which is impossible
// for a prerendering document, so the renderer process should be
// terminated. See
// https://html.spec.whatwg.org/multipage/interaction.html#tracking-user-activation.
if (lifecycle_state() == LifecycleStateImpl::kPrerendering) {
bad_message::ReceivedBadMessage(
GetProcess(), bad_message::RFH_POPUP_REQUEST_WHILE_PRERENDERING);
}
return;
}
// We still need to allocate a widget routing id. Even though the renderer
// doesn't receive it, the browser side still uses routing ids to track
// widgets in various global tables.
int32_t widget_route_id = GetProcess()->GetNextRoutingID();
RenderWidgetHostImpl* widget = delegate_->CreateNewPopupWidget(
site_instance_->group()->GetSafeRef(), widget_route_id,
std::move(blink_popup_widget_host), std::move(blink_widget_host),
std::move(blink_widget));
if (!widget)
return;
// The renderer-owned widget was created before sending the IPC received here.
widget->RendererWidgetCreated(/*for_frame_widget=*/false);
if (create_new_popup_widget_callback_)
create_new_popup_widget_callback_.Run(widget);
}
void RenderFrameHostImpl::GetKeepAliveHandleFactory(
mojo::PendingReceiver<blink::mojom::KeepAliveHandleFactory> receiver) {
DCHECK_CURRENTLY_ON(BrowserThread::UI);
if (GetProcess()->AreRefCountsDisabled())
return;
keep_alive_handle_factory_.Bind(std::move(receiver));
}
void RenderFrameHostImpl::DidChangeSrcDoc(
const blink::FrameToken& child_frame_token,
const std::string& srcdoc_value) {
auto* child =
FindAndVerifyChild(child_frame_token, bad_message::RFH_OWNER_PROPERTY);
if (!child)
return;
child->SetSrcdocValue(srcdoc_value);
}
void RenderFrameHostImpl::ReceivedDelegatedCapability(
blink::mojom::DelegatedCapability delegated_capability) {
if (delegated_capability ==
blink::mojom::DelegatedCapability::kFullscreenRequest) {
fullscreen_request_token_.Activate();
}
}
// TODO(ahemery): Move checks to mojo bad message reporting.
void RenderFrameHostImpl::BeginNavigation(
blink::mojom::CommonNavigationParamsPtr common_params,
blink::mojom::BeginNavigationParamsPtr begin_params,
mojo::PendingRemote<blink::mojom::BlobURLToken> blob_url_token,
mojo::PendingAssociatedRemote<mojom::NavigationClient> navigation_client,
mojo::PendingRemote<blink::mojom::PolicyContainerHostKeepAliveHandle>
initiator_policy_container_host_keep_alive_handle,
mojo::PendingReceiver<mojom::NavigationRendererCancellationListener>
renderer_cancellation_listener) {
if (frame_tree_node_->render_manager()->is_attaching_inner_delegate()) {
// Avoid starting any new navigations since this frame is in the process of
// attaching an inner delegate.
return;
}
// Only active and prerendered documents are allowed to start navigation in
// their frame.
if (lifecycle_state() != LifecycleStateImpl::kPrerendering) {
// If this is reached in case the RenderFrameHost is in BackForwardCache
// evict the document from BackForwardCache.
if (IsInactiveAndDisallowActivation(
DisallowActivationReasonId::kBeginNavigation))
return;
}
TRACE_EVENT2("navigation", "RenderFrameHostImpl::BeginNavigation",
"render_frame_host", this, "url",
common_params->url.possibly_invalid_spec());
DCHECK(navigation_client.is_valid());
blink::mojom::CommonNavigationParamsPtr validated_params =
common_params.Clone();
if (!VerifyBeginNavigationCommonParams(GetSiteInstance(), &*validated_params))
return;
// BeginNavigation() should only be triggered when the navigation is
// initiated by a frame in the same process.
int initiator_process_id = GetProcess()->GetID();
if (!VerifyNavigationInitiator(this, begin_params->initiator_frame_token,
initiator_process_id)) {
return;
}
// If the request is bearing Trust Tokens parameters:
// - it must not be a main-frame navigation, and
// - for certain Trust Tokens operations, the frame's parent needs the
// trust-token-redemption Permissions Policy feature.
if (begin_params->trust_token_params) {
// For Fenced Frame trust_token_params shouldn't be populated since that is
// driven by the iframe specific attribute as defined here:
// https://github.com/WICG/trust-token-api#extension-iframe-activation". If
// this changes, the code below using `GetParent()` will need to be changed.
// TODO(crbug.com/1254770): For portals implemented with mparch, we'll have
// a similar problem with `GetParent()` calls as with Fenced Frame.
if (IsFencedFrameRoot()) {
mojo::ReportBadMessage("RFHI: Trust Token params in fenced frame nav");
return;
}
if (!GetParent()) {
mojo::ReportBadMessage("RFHI: Trust Token params in main frame nav");
return;
}
if (ParentNeedsTrustTokenPermissionsPolicy(*begin_params)) {
RenderFrameHostImpl* parent = GetParent();
if (!parent->IsFeatureEnabled(
blink::mojom::PermissionsPolicyFeature::kTrustTokenRedemption)) {
mojo::ReportBadMessage(
"RFHI: Mandatory Trust Tokens Permissions Policy feature is "
"absent");
return;
}
}
}
// Only uuid-in-package: URL are allowed for navigation to a resource in
// Subresource WebBundles.
if (begin_params->web_bundle_token &&
!common_params->url.SchemeIs(url::kUuidInPackageScheme)) {
bad_message::ReceivedBadMessage(
GetProcess(), bad_message::WEB_BUNDLE_INVALID_NAVIGATION_URL);
return;
}
GetProcess()->FilterURL(true, &begin_params->searchable_form_url);
// If the request was for a blob URL, but the validated URL is no longer a
// blob URL, reset the blob_url_token to prevent hitting the ReportBadMessage
// below, and to make sure we don't incorrectly try to use the blob_url_token.
if (common_params->url.SchemeIsBlob() &&
!validated_params->url.SchemeIsBlob()) {
blob_url_token = mojo::NullRemote();
}
if (blob_url_token && !validated_params->url.SchemeIsBlob()) {
mojo::ReportBadMessage("Blob URL Token, but not a blob: URL");
return;
}
scoped_refptr<network::SharedURLLoaderFactory> blob_url_loader_factory;
if (blob_url_token) {
blob_url_loader_factory =
ChromeBlobStorageContext::URLLoaderFactoryForToken(
GetStoragePartition(), std::move(blob_url_token));
}
// If the request was for a blob URL, but no blob_url_token was passed in this
// is not necessarily an error. Renderer initiated reloads for example are one
// situation where a renderer currently doesn't have an easy way of resolving
// the blob URL. For those situations resolve the blob URL here, as we don't
// care about ordering with other blob URL manipulation anyway.
if (validated_params->url.SchemeIsBlob() && !blob_url_loader_factory) {
blob_url_loader_factory = ChromeBlobStorageContext::URLLoaderFactoryForUrl(
GetStoragePartition(), validated_params->url);
}
if (waiting_for_init_) {
pending_navigate_ = std::make_unique<PendingNavigation>(
std::move(validated_params), std::move(begin_params),
std::move(blob_url_loader_factory), std::move(navigation_client),
std::move(renderer_cancellation_listener));
return;
}
// We can discard the parameter
// |initiator_policy_container_host_keep_alive_handle|. This is just needed to
// ensure that the PolicyContainerHost of the initiator RenderFrameHost
// can still be retrieved even if the RenderFrameHost has been deleted in
// between. Since the NavigationRequest will be created synchronously as a
// result of this function's execution, we don't need to pass
// |initiator_policy_container_host_keep_alive_handle| along.
frame_tree_node()->navigator().OnBeginNavigation(
frame_tree_node(), std::move(validated_params), std::move(begin_params),
std::move(blob_url_loader_factory), std::move(navigation_client),
EnsurePrefetchedSignedExchangeCache(),
MaybeCreateWebBundleHandleTracker(),
std::move(renderer_cancellation_listener));
}
void RenderFrameHostImpl::SubresourceResponseStarted(
const url::SchemeHostPort& final_response_url,
net::CertStatus cert_status) {
OPTIONAL_TRACE_EVENT1("content",
"RenderFrameHostImpl::SubresourceResponseStarted",
"url", final_response_url.GetURL());
frame_tree_->controller().ssl_manager()->DidStartResourceResponse(
final_response_url, cert_status);
}
void RenderFrameHostImpl::ResourceLoadComplete(
blink::mojom::ResourceLoadInfoPtr resource_load_info) {
GlobalRequestID global_request_id;
const bool is_frame_request =
blink::IsRequestDestinationFrame(resource_load_info->request_destination);
if (main_frame_request_ids_.first == resource_load_info->request_id) {
global_request_id = main_frame_request_ids_.second;
} else if (is_frame_request) {
// The load complete message for the main resource arrived before
// |DidCommitProvisionalLoad()|. We save the load info so
// |ResourceLoadComplete()| can be called later in
// |DidCommitProvisionalLoad()| when we can map to the global request ID.
deferred_main_frame_load_info_ = std::move(resource_load_info);
return;
}
delegate_->ResourceLoadComplete(this, global_request_id,
std::move(resource_load_info));
}
void RenderFrameHostImpl::HandleAXEvents(
const ui::AXTreeID& tree_id,
blink::mojom::AXUpdatesAndEventsPtr updates_and_events,
int32_t reset_token) {
TRACE_EVENT0("accessibility", "RenderFrameHostImpl::HandleAXEvents");
SCOPED_UMA_HISTOGRAM_TIMER("Accessibility.Performance.HandleAXEvents");
if (tree_id != GetAXTreeID()) {
// The message has arrived after the frame has navigated which means its
// events are no longer relevant and can be discarded.
if (!accessibility_testing_callback_.is_null()) {
// The callback must still run, otherwise dump event tests can hang.
accessibility_testing_callback_.Run(this, ax::mojom::Event::kNone, 0);
}
return;
}
// Don't process this IPC if either we're waiting on a reset and this IPC
// doesn't have the matching token ID, or if we're not waiting on a reset but
// this message includes a reset token.
if (accessibility_reset_token_ != reset_token) {
return;
}
accessibility_reset_token_ = 0;
ui::AXMode accessibility_mode = delegate_->GetAccessibilityMode();
if (accessibility_mode.is_mode_off()) {
return;
}
if (base::FeatureList::IsEnabled(features::kEvictOnAXEvents)) {
// If the flag is on, evict the bfcache entry now that AX events are
// received.
if (IsInactiveAndDisallowActivationForAXEvents(updates_and_events->events))
return;
} else {
// If the page is in back/forward cache, do not return early and continue to
// apply AX tree updates.
// TODO(https://crbug.com/1328126): Define and implement the behavior for
// when the page is prerendering, too.
if (!IsInBackForwardCache() &&
IsInactiveAndDisallowActivation(DisallowActivationReasonId::kAXEvent)) {
return;
}
}
if (accessibility_mode.has_mode(ui::AXMode::kNativeAPIs))
GetOrCreateBrowserAccessibilityManager();
AXEventNotificationDetails details;
details.ax_tree_id = tree_id;
// TODO(1213848): Remove the false path when the experiment is complete.
if (base::FeatureList::IsEnabled(kRenderAccessibilityHostAvoidCopying)) {
details.events = std::move(updates_and_events->events);
details.updates = std::move(updates_and_events->updates);
for (auto& update : details.updates) {
if (update.has_tree_data) {
DCHECK_EQ(tree_id, update.tree_data.tree_id);
ax_tree_data_ = update.tree_data;
update.tree_data = GetAXTreeData();
}
}
} else {
details.events = updates_and_events->events;
details.updates.resize(updates_and_events->updates.size());
for (size_t i = 0; i < updates_and_events->updates.size(); ++i) {
details.updates[i] = updates_and_events->updates[i];
if (updates_and_events->updates[i].has_tree_data) {
DCHECK_EQ(tree_id, updates_and_events->updates[i].tree_data.tree_id);
ax_tree_data_ = updates_and_events->updates[i].tree_data;
details.updates[i].tree_data = GetAXTreeData();
}
}
}
if (needs_ax_root_id_) {
// This is the first update after the tree id changed. AXTree must be sent
// a new root id, otherwise crashes are likely to result.
DCHECK(!details.updates.empty());
DCHECK_NE(ui::kInvalidAXNodeID, details.updates[0].root_id);
needs_ax_root_id_ = false;
}
if (accessibility_mode.has_mode(ui::AXMode::kNativeAPIs))
SendAccessibilityEventsToManager(details);
delegate_->AccessibilityEventReceived(details);
// For testing only.
if (!accessibility_testing_callback_.is_null()) {
if (details.events.empty()) {
// Objects were marked dirty but no events were provided.
// The callback must still run, otherwise dump event tests can hang.
accessibility_testing_callback_.Run(this, ax::mojom::Event::kNone, 0);
} else {
// Call testing callback functions for each event to fire.
for (auto& event : details.events) {
if (static_cast<int>(event.event_type) < 0)
continue;
accessibility_testing_callback_.Run(this, event.event_type, event.id);
}
}
}
}
void RenderFrameHostImpl::HandleAXLocationChanges(
const ui::AXTreeID& tree_id,
std::vector<blink::mojom::LocationChangesPtr> changes) {
if (tree_id != GetAXTreeID()) {
// The message has arrived after the frame has navigated which means its
// changes are no longer relevant and can be discarded.
return;
}
if (accessibility_reset_token_ ||
IsInactiveAndDisallowActivation(
DisallowActivationReasonId::kAXLocationChange))
return;
ui::AXMode accessibility_mode = delegate_->GetAccessibilityMode();
if (accessibility_mode.has_mode(ui::AXMode::kNativeAPIs)) {
BrowserAccessibilityManager* manager =
GetOrCreateBrowserAccessibilityManager();
if (manager)
manager->OnLocationChanges(changes);
}
// Send the updates to the automation extension API.
std::vector<AXLocationChangeNotificationDetails> details;
details.reserve(changes.size());
for (auto& change : changes) {
AXLocationChangeNotificationDetails detail;
detail.id = change->id;
detail.ax_tree_id = GetAXTreeID();
detail.new_location = change->new_location;
details.push_back(detail);
}
delegate_->AccessibilityLocationChangesReceived(details);
}
media::MediaMetricsProvider::RecordAggregateWatchTimeCallback
RenderFrameHostImpl::GetRecordAggregateWatchTimeCallback() {
// The URL used for UKM must always be the top level frame.
return delegate_->GetRecordAggregateWatchTimeCallback(
GetOutermostMainFrame()->GetLastCommittedURL());
}
void RenderFrameHostImpl::ResetWaitingState() {
// We don't allow resetting waiting state when the RenderFrameHost is either
// in BackForwardCache or in pending deletion state, as we don't allow
// navigations from either of these two states.
DCHECK(!IsInBackForwardCache());
DCHECK(!IsPendingDeletion());
// Whenever we reset the RFH state, we should not be waiting for beforeunload
// or close acks. We clear them here to be safe, since they can cause
// navigations to be ignored in DidCommitProvisionalLoad.
if (is_waiting_for_beforeunload_completion_) {
is_waiting_for_beforeunload_completion_ = false;
if (beforeunload_timeout_)
beforeunload_timeout_->Stop();
has_shown_beforeunload_dialog_ = false;
beforeunload_pending_replies_.clear();
}
send_before_unload_start_time_ = base::TimeTicks();
render_view_host_->is_waiting_for_page_close_completion_ = false;
}
CanCommitStatus RenderFrameHostImpl::CanCommitOriginAndUrl(
const url::Origin& origin,
const GURL& url,
bool is_same_document_navigation,
bool is_pdf,
bool is_sandboxed) {
// Note that callers are responsible for avoiding this function in modes that
// can bypass these rules, such as --disable-web-security or certain Android
// WebView features like universal access from file URLs.
// Renderer-debug URLs can never be committed.
if (blink::IsRendererDebugURL(url)) {
LogCanCommitOriginAndUrlFailureReason("is_renderer_debug_url");
return CanCommitStatus::CANNOT_COMMIT_URL;
}
// Verify that if this RenderFrameHost is for a WebUI it is not committing a
// URL which is not allowed in a WebUI process. As we are at the commit stage,
// set OriginIsolationRequest to kNone (this is implicitly done by the
// UrlInfoInit constructor).
if (!Navigator::CheckWebUIRendererDoesNotDisplayNormalURL(
this, UrlInfo(UrlInfoInit(url).WithOrigin(origin).WithIsPdf(is_pdf)),
/*is_renderer_initiated_check=*/true)) {
return CanCommitStatus::CANNOT_COMMIT_URL;
}
// MHTML subframes can supply URLs at commit time that do not match the
// process lock. For example, it can be either "cid:..." or arbitrary URL at
// which the frame was at the time of generating the MHTML
// (e.g. "http://localhost"). In such cases, don't verify the URL, but require
// the URL to commit in the process of the main frame.
if (!is_main_frame()) {
RenderFrameHostImpl* main_frame = GetMainFrame();
if (main_frame->is_mhtml_document()) {
if (IsSameSiteInstance(main_frame))
return CanCommitStatus::CAN_COMMIT_ORIGIN_AND_URL;
// If an MHTML subframe commits in a different process (even one that
// appears correct for the subframe's URL), then we aren't correctly
// loading it from the archive and should kill the renderer.
static auto* const oopif_in_mhtml_page_key =
base::debug::AllocateCrashKeyString(
"oopif_in_mhtml_page", base::debug::CrashKeySize::Size32);
base::debug::SetCrashKeyString(
oopif_in_mhtml_page_key,
is_mhtml_document() ? "is_mhtml_doc" : "not_mhtml_doc");
LogCanCommitOriginAndUrlFailureReason("oopif_in_mhtml_page");
return CanCommitStatus::CANNOT_COMMIT_URL;
}
}
// Same-document navigations cannot change origins, as long as these checks
// aren't being bypassed in unusual modes. This check must be after the MHTML
// check, as shown by NavigationMhtmlBrowserTest.IframeAboutBlankNotFound.
if (is_same_document_navigation && origin != GetLastCommittedOrigin()) {
LogCanCommitOriginAndUrlFailureReason("cross_origin_same_document");
return CanCommitStatus::CANNOT_COMMIT_ORIGIN;
}
// Give the client a chance to disallow URLs from committing.
if (!GetContentClient()->browser()->CanCommitURL(GetProcess(), url)) {
LogCanCommitOriginAndUrlFailureReason(
"content_client_disallowed_commit_url");
return CanCommitStatus::CANNOT_COMMIT_URL;
}
// Check with ChildProcessSecurityPolicy, which enforces Site Isolation, etc.
auto* policy = ChildProcessSecurityPolicyImpl::GetInstance();
const CanCommitStatus can_commit_status = policy->CanCommitOriginAndUrl(
GetProcess()->GetID(), GetSiteInstance()->GetIsolationContext(),
UrlInfo(
UrlInfoInit(url)
.WithOrigin(origin)
.WithStoragePartitionConfig(
GetSiteInstance()->GetSiteInfo().storage_partition_config())
.WithWebExposedIsolationInfo(
GetSiteInstance()->GetWebExposedIsolationInfo())
.WithSandbox(is_sandboxed)));
if (can_commit_status != CanCommitStatus::CAN_COMMIT_ORIGIN_AND_URL) {
LogCanCommitOriginAndUrlFailureReason("cpspi_disallowed_commit");
return can_commit_status;
}
const auto origin_tuple_or_precursor_tuple =
origin.GetTupleOrPrecursorTupleIfOpaque();
if (origin_tuple_or_precursor_tuple.IsValid()) {
// Verify that the origin/precursor is allowed to commit in this process.
// Note: This also handles non-standard cases for |url|, such as
// about:blank, data, and blob URLs.
// Renderer-debug URLs can never be committed.
if (blink::IsRendererDebugURL(origin_tuple_or_precursor_tuple.GetURL())) {
LogCanCommitOriginAndUrlFailureReason(
"origin_or_precursor_is_renderer_debug_url");
return CanCommitStatus::CANNOT_COMMIT_ORIGIN;
}
// Give the client a chance to disallow origin URLs from committing.
// TODO(acolwell): Fix this code to work with opaque origins. Currently
// some opaque origin precursors, like chrome-extension schemes, can trigger
// the commit to fail. These need to be investigated.
if (!origin.opaque() && !GetContentClient()->browser()->CanCommitURL(
GetProcess(), origin.GetURL())) {
LogCanCommitOriginAndUrlFailureReason(
"content_client_disallowed_commit_origin");
return CanCommitStatus::CANNOT_COMMIT_ORIGIN;
}
}
return CanCommitStatus::CAN_COMMIT_ORIGIN_AND_URL;
}
bool RenderFrameHostImpl::CanSubframeCommitOriginAndUrl(
NavigationRequest* navigation_request) {
DCHECK(navigation_request);
DCHECK(!is_main_frame());
const int nav_entry_id = navigation_request->nav_entry_id();
// No validation of the main frame's origin is needed if the subframe
// navigation doesn't target a different existing NavigationEntry. In such
// cases, it's unlikely the wrong main frame origin could be displayed
// afterward.
if (nav_entry_id == 0)
return true;
const int last_nav_entry_index =
frame_tree_node_->navigator().controller().GetLastCommittedEntryIndex();
const int dest_nav_entry_index =
frame_tree_node_->navigator().controller().GetEntryIndexWithUniqueID(
nav_entry_id);
if (dest_nav_entry_index <= 0 || dest_nav_entry_index == last_nav_entry_index)
return true;
NavigationEntryImpl* dest_nav_entry =
frame_tree_node_->navigator().controller().GetEntryAtIndex(
dest_nav_entry_index);
auto dest_main_frame_fne = dest_nav_entry->root_node()->frame_entry;
// A subframe navigation should never lead to a NavigationEntry that looks
// like a cross-document navigation in the main frame, since cross-document
// navigations destroy all subframes.
int64_t dest_main_frame_dsn = dest_main_frame_fne->document_sequence_number();
int64_t actual_main_frame_dsn = frame_tree_node_->navigator()
.controller()
.GetLastCommittedEntry()
->root_node()
->frame_entry->document_sequence_number();
if (dest_main_frame_dsn != actual_main_frame_dsn)
return false;
// At this point in request handling RenderFrameHostImpl may not have enough
// information to derive an accurate value for what would be the new main
// frame origin if this request gets committed. One case this can happen is
// a NavigtionEntry from a restored session. To make the best of this
// situation, skip validation of main frame origin and assume it will be valid
// by assigning a known good value - i.e. the current main frame origin - then
// continue so at least CanCommitOriginAndUrl can perform its checks using
// dest_top_url.
url::Origin dest_top_origin =
dest_main_frame_fne->committed_origin().value_or(
GetMainFrame()->GetLastCommittedOrigin());
const GURL& dest_top_url = dest_nav_entry->GetURL();
// Assume the change in main frame FrameNavigationEntry won't affect whether
// the main frame is showing a PDF or a sandboxed document, since we don't
// track that in FrameNavigationEntry.
const bool is_top_pdf =
GetMainFrame()->GetSiteInstance()->GetSiteInfo().is_pdf();
const bool is_top_sandboxed =
GetMainFrame()->GetSiteInstance()->GetSiteInfo().is_sandboxed();
return GetMainFrame()->CanCommitOriginAndUrl(
dest_top_origin, dest_top_url,
true /* is_same_document_navigation */, is_top_pdf,
is_top_sandboxed) == CanCommitStatus::CAN_COMMIT_ORIGIN_AND_URL;
}
void RenderFrameHostImpl::Stop() {
TRACE_EVENT1("navigation", "RenderFrameHostImpl::Stop", "render_frame_host",
this);
// Don't call GetAssociatedLocalFrame before the RenderFrame is created.
if (!IsRenderFrameLive())
return;
GetAssociatedLocalFrame()->StopLoading();
}
void RenderFrameHostImpl::DispatchBeforeUnload(BeforeUnloadType type,
bool is_reload) {
bool for_navigation =
type == BeforeUnloadType::BROWSER_INITIATED_NAVIGATION ||
type == BeforeUnloadType::RENDERER_INITIATED_NAVIGATION;
bool for_inner_delegate_attach =
type == BeforeUnloadType::INNER_DELEGATE_ATTACH;
DCHECK(for_navigation || for_inner_delegate_attach || !is_reload);
// TAB_CLOSE and DISCARD should only dispatch beforeunload on main frames.
DCHECK(type == BeforeUnloadType::BROWSER_INITIATED_NAVIGATION ||
type == BeforeUnloadType::RENDERER_INITIATED_NAVIGATION ||
type == BeforeUnloadType::INNER_DELEGATE_ATTACH || is_main_frame());
if (!for_navigation) {
// Cancel any pending navigations, to avoid their navigation commit/fail
// event from wiping out the is_waiting_for_beforeunload_completion_ state.
if (frame_tree_node_->navigation_request() &&
frame_tree_node_->navigation_request()->IsNavigationStarted()) {
frame_tree_node_->navigation_request()->set_net_error(net::ERR_ABORTED);
}
frame_tree_node_->ResetNavigationRequest(false);
}
// In renderer-initiated navigations, don't check for beforeunload in the
// navigating frame, as it has already run beforeunload before it sent the
// BeginNavigation IPC.
bool check_subframes_only =
type == BeforeUnloadType::RENDERER_INITIATED_NAVIGATION;
if (!ShouldDispatchBeforeUnload(check_subframes_only)) {
// When running beforeunload for navigations, ShouldDispatchBeforeUnload()
// is checked earlier and we would only get here if it had already returned
// true.
DCHECK(!for_navigation);
// Dispatch the ACK to prevent re-entrancy.
base::OnceClosure task = base::BindOnce(
[](base::WeakPtr<RenderFrameHostImpl> self) {
if (!self)
return;
self->frame_tree_node_->render_manager()->BeforeUnloadCompleted(
true, base::TimeTicks::Now());
},
weak_ptr_factory_.GetWeakPtr());
base::ThreadTaskRunnerHandle::Get()->PostTask(FROM_HERE, std::move(task));
return;
}
TRACE_EVENT_NESTABLE_ASYNC_BEGIN1(
"navigation", "RenderFrameHostImpl BeforeUnload", TRACE_ID_LOCAL(this),
"render_frame_host", this);
// This may be called more than once (if the user clicks the tab close button
// several times, or if they click the tab close button then the browser close
// button), and we only send the message once.
if (is_waiting_for_beforeunload_completion_) {
// Some of our close messages could be for the tab, others for cross-site
// transitions. We always want to think it's for closing the tab if any
// of the messages were, since otherwise it might be impossible to close
// (if there was a cross-site "close" request pending when the user clicked
// the close button). We want to keep the "for cross site" flag only if
// both the old and the new ones are also for cross site.
unload_ack_is_for_navigation_ =
unload_ack_is_for_navigation_ && for_navigation;
} else {
// Start the hang monitor in case the renderer hangs in the beforeunload
// handler.
is_waiting_for_beforeunload_completion_ = true;
beforeunload_dialog_request_cancels_unload_ = false;
unload_ack_is_for_navigation_ = for_navigation;
send_before_unload_start_time_ = base::TimeTicks::Now();
if (render_view_host_->GetDelegate()->IsJavaScriptDialogShowing()) {
// If there is a JavaScript dialog up, don't bother sending the renderer
// the unload event because it is known unresponsive, waiting for the
// reply from the dialog. If this incoming request is for a DISCARD be
// sure to reply with |proceed = false|, because the presence of a dialog
// indicates that the page can't be discarded.
SimulateBeforeUnloadCompleted(type != BeforeUnloadType::DISCARD);
} else {
// Start a timer that will be shared by all frames that need to run
// beforeunload in the current frame's subtree.
if (beforeunload_timeout_)
beforeunload_timeout_->Start(beforeunload_timeout_delay_);
beforeunload_pending_replies_.clear();
beforeunload_dialog_request_cancels_unload_ =
(type == BeforeUnloadType::DISCARD);
// Run beforeunload in this frame and its cross-process descendant
// frames, in parallel.
CheckOrDispatchBeforeUnloadForSubtree(check_subframes_only,
/*send_ipc=*/true, is_reload);
}
}
}
bool RenderFrameHostImpl::CheckOrDispatchBeforeUnloadForSubtree(
bool subframes_only,
bool send_ipc,
bool is_reload) {
bool found_beforeunload = false;
bool run_beforeunload_for_legacy = false;
// Beforeunload is not supported inside fenced frame trees.
if (IsFencedFrameRoot())
return false;
for (FrameTreeNode* node : frame_tree()->SubtreeNodes(frame_tree_node_)) {
RenderFrameHostImpl* rfh = node->current_frame_host();
// If |subframes_only| is true, skip this frame and its same-site
// descendants. This happens for renderer-initiated navigations, where
// these frames have already run beforeunload.
if (subframes_only && rfh->GetSiteInstance() == GetSiteInstance())
continue;
// No need to run beforeunload if the RenderFrame isn't live.
if (!rfh->IsRenderFrameLive())
continue;
// Only run beforeunload in frames that have registered a beforeunload
// handler. See description of SendBeforeUnload() for details on simulating
// beforeunload for legacy reasons. If
// `kAvoidUnnecessaryBeforeUnloadCheckSync` is true and there is no
// beforeunload handler for the navigating frame, then do not simulate a
// beforeunload handler, and navigation can continue.
const bool run_beforeunload_for_legacy_frame =
rfh == this && !rfh->has_before_unload_handler_ &&
!IsAvoidUnnecessaryBeforeUnloadCheckSyncEnabled();
const bool should_run_beforeunload =
rfh->has_before_unload_handler_ || run_beforeunload_for_legacy_frame;
if (!should_run_beforeunload)
continue;
// If we're only checking whether there's at least one frame with
// beforeunload, then we've just found one, so we can return now.
found_beforeunload = true;
if (!send_ipc)
return true;
// Otherwise, figure out whether we need to send the IPC, or whether this
// beforeunload was already triggered by an ancestor frame's IPC.
// Only send beforeunload to local roots, and let Blink handle any
// same-site frames under them. That is, if a frame has a beforeunload
// handler, ask its local root to run it. If we've already sent the message
// to that local root, skip this frame. For example, in A1(A2,A3), if A2
// and A3 contain beforeunload handlers, and all three frames are
// same-site, we ask A1 to run beforeunload for all three frames, and only
// ask it once.
while (!rfh->is_local_root() && rfh != this)
rfh = rfh->GetParent();
if (base::Contains(beforeunload_pending_replies_, rfh))
continue;
// For a case like A(B(A)), it's not necessary to send an IPC for the
// innermost frame, as Blink will walk all same-site (local)
// descendants. Detect cases like this and skip them.
bool has_same_site_ancestor = false;
for (auto* added_rfh : beforeunload_pending_replies_) {
if (rfh->IsDescendantOfWithinFrameTree(added_rfh) &&
rfh->GetSiteInstance() == added_rfh->GetSiteInstance()) {
has_same_site_ancestor = true;
break;
}
}
if (has_same_site_ancestor)
continue;
if (run_beforeunload_for_legacy_frame &&
IsAvoidUnnecessaryBeforeUnloadCheckPostTaskEnabled()) {
// Wait to schedule until all frames have been processed. The legacy
// beforeunload is not needed if another frame has a beforeunload
// handler. Note that for `kAvoidUnnecessaryBeforeUnloadCheckSync`
// `run_beforeunload_for_legacy_frame` is never true.
run_beforeunload_for_legacy = true;
continue;
}
run_beforeunload_for_legacy = false;
// Add |rfh| to the list of frames that need to receive beforeunload
// ACKs.
beforeunload_pending_replies_.insert(rfh);
SendBeforeUnload(is_reload, rfh->GetWeakPtr(), /*for_legacy=*/false);
}
if (run_beforeunload_for_legacy) {
beforeunload_pending_replies_.insert(this);
SendBeforeUnload(is_reload, GetWeakPtr(), /*for_legacy=*/true);
}
return found_beforeunload;
}
void RenderFrameHostImpl::SimulateBeforeUnloadCompleted(bool proceed) {
DCHECK(is_waiting_for_beforeunload_completion_);
base::TimeTicks approx_renderer_start_time = send_before_unload_start_time_;
// Dispatch the ACK to prevent re-entrancy.
base::ThreadTaskRunnerHandle::Get()->PostTask(
FROM_HERE,
base::BindOnce(&RenderFrameHostImpl::ProcessBeforeUnloadCompleted,
weak_ptr_factory_.GetWeakPtr(), proceed,
/*treat_as_final_completion_callback=*/true,
approx_renderer_start_time, base::TimeTicks::Now(),
/*for_legacy=*/false));
}
bool RenderFrameHostImpl::ShouldDispatchBeforeUnload(
bool check_subframes_only) {
return CheckOrDispatchBeforeUnloadForSubtree(
check_subframes_only, /*send_ipc=*/false, /*is_reload=*/false);
}
void RenderFrameHostImpl::SetBeforeUnloadTimeoutDelayForTesting(
const base::TimeDelta& timeout) {
beforeunload_timeout_delay_ = timeout;
}
void RenderFrameHostImpl::StartPendingDeletionOnSubtree() {
DCHECK(IsPendingDeletion());
ResetNavigationsForPendingDeletion();
for (std::unique_ptr<FrameTreeNode>& child_frame : children_) {
for (FrameTreeNode* node : frame_tree()->SubtreeNodes(child_frame.get())) {
RenderFrameHostImpl* child = node->current_frame_host();
if (child->IsPendingDeletion())
continue;
// Blink handles deletion of all same-process descendants, running their
// unload handler if necessary. So delegate sending IPC on the topmost
// ancestor using the same process.
RenderFrameHostImpl* local_ancestor = child;
for (auto* rfh = child->parent_.get(); rfh != parent_;
rfh = rfh->parent_) {
if (rfh->GetSiteInstance() == child->GetSiteInstance())
local_ancestor = rfh;
}
local_ancestor->DeleteRenderFrame(
mojom::FrameDeleteIntention::kNotMainFrame);
if (local_ancestor != child) {
// In case of BackForwardCache, page is evicted directly from the cache
// and deleted immediately, without waiting for unload handlers.
child->SetLifecycleState(
child->ShouldWaitForUnloadHandlers()
? LifecycleStateImpl::kRunningUnloadHandlers
: LifecycleStateImpl::kReadyToBeDeleted);
}
node->frame_tree()->FrameUnloading(node);
}
}
}
void RenderFrameHostImpl::PendingDeletionCheckCompleted() {
if (lifecycle_state() == LifecycleStateImpl::kReadyToBeDeleted &&
children_.empty()) {
check_deletion_for_bug_1276535_ = false;
if (is_waiting_for_unload_ack_) {
OnUnloaded(); // Delete |this|.
// Do not add code after this.
} else {
parent_->RemoveChild(frame_tree_node_);
}
}
}
void RenderFrameHostImpl::PendingDeletionCheckCompletedOnSubtree() {
if (children_.empty()) {
PendingDeletionCheckCompleted(); // This might delete |this|.
// DO NOT add code after this.
return;
}
base::WeakPtr<RenderFrameHostImpl> self = GetWeakPtr();
check_deletion_for_bug_1276535_ = true;
// Collect children first before calling PendingDeletionCheckCompleted(). It
// can delete them and invalidate |children_|. When the last child has been
// deleted, it might also delete |this|.
//
// https://crbug.com/1276535: We collect WeakPtr, because we suspect deleting
// a child might delete the whole WebContent and everything it contained.
std::vector<base::WeakPtr<RenderFrameHostImpl>> weak_children;
for (std::unique_ptr<FrameTreeNode>& child : children_) {
RenderFrameHostImpl* child_rfh = child->current_frame_host();
CHECK(child_rfh);
weak_children.push_back(child_rfh->GetWeakPtr());
}
// DO NOT use |this| after this line.
for (base::WeakPtr<RenderFrameHostImpl>& child_rfh : weak_children) {
if (child_rfh) {
child_rfh->PendingDeletionCheckCompletedOnSubtree();
}
}
if (self) {
check_deletion_for_bug_1276535_ = false;
}
}
void RenderFrameHostImpl::ResetNavigationsForPendingDeletion() {
for (auto& child : children_)
child->current_frame_host()->ResetNavigationsForPendingDeletion();
ResetNavigationRequests();
frame_tree_node_->ResetNavigationRequest(false);
frame_tree_node_->render_manager()->CleanUpNavigation();
}
void RenderFrameHostImpl::OnUnloadTimeout() {
DCHECK(IsPendingDeletion());
parent_->RemoveChild(frame_tree_node_);
}
void RenderFrameHostImpl::UpdateOpener() {
TRACE_EVENT1("navigation", "RenderFrameHostImpl::UpdateOpener",
"render_frame_host", this);
// This frame (the frame whose opener is being updated) might not have had
// proxies for the new opener chain in its SiteInstance. Make sure they
// exist.
if (frame_tree_node_->opener()) {
frame_tree_node_->opener()->render_manager()->CreateOpenerProxies(
GetSiteInstance(), frame_tree_node_, browsing_context_state_);
}
auto opener_frame_token =
frame_tree_node_->render_manager()->GetOpenerFrameToken(
GetSiteInstance()->group());
GetAssociatedLocalFrame()->UpdateOpener(opener_frame_token);
}
void RenderFrameHostImpl::SetFocusedFrame() {
GetAssociatedLocalFrame()->Focus();
}
void RenderFrameHostImpl::AdvanceFocus(blink::mojom::FocusType type,
RenderFrameProxyHost* source_proxy) {
DCHECK(!source_proxy ||
(source_proxy->GetProcess()->GetID() == GetProcess()->GetID()));
absl::optional<blink::RemoteFrameToken> frame_token;
if (source_proxy)
frame_token = source_proxy->GetFrameToken();
GetAssociatedLocalFrame()->AdvanceFocusInFrame(type, frame_token);
}
void RenderFrameHostImpl::JavaScriptDialogClosed(
JavaScriptDialogCallback dialog_closed_callback,
bool success,
const std::u16string& user_input) {
GetProcess()->SetBlocked(false);
std::move(dialog_closed_callback).Run(success, user_input);
// If executing as part of beforeunload event handling, there may have been
// timers stopped in this frame or a frame up in the frame hierarchy. Restart
// any timers that were stopped in OnRunBeforeUnloadConfirm().
for (RenderFrameHostImpl* frame = this; frame; frame = frame->GetParent()) {
if (frame->is_waiting_for_beforeunload_completion_ &&
frame->beforeunload_timeout_) {
frame->beforeunload_timeout_->Start(beforeunload_timeout_delay_);
}
}
}
bool RenderFrameHostImpl::ShouldDispatchPagehideAndVisibilitychangeDuringCommit(
RenderFrameHostImpl* old_frame_host,
const UrlInfo& dest_url_info) {
// Only return true if this is a same-site navigation and we did a proactive
// BrowsingInstance swap but we're reusing the old page's renderer process.
DCHECK(old_frame_host);
if (old_frame_host->GetSiteInstance()->IsRelatedSiteInstance(
GetSiteInstance())) {
return false;
}
if (old_frame_host->GetProcess() != GetProcess()) {
return false;
}
if (!old_frame_host->IsNavigationSameSite(dest_url_info)) {
return false;
}
DCHECK(is_main_frame());
DCHECK_NE(old_frame_host, this);
DCHECK_NE(old_frame_host->GetSiteInstance(), GetSiteInstance());
return true;
}
void RenderFrameHostImpl::CommitNavigation(
NavigationRequest* navigation_request,
blink::mojom::CommonNavigationParamsPtr common_params,
blink::mojom::CommitNavigationParamsPtr commit_params,
network::mojom::URLResponseHeadPtr response_head,
mojo::ScopedDataPipeConsumerHandle response_body,
network::mojom::URLLoaderClientEndpointsPtr url_loader_client_endpoints,
absl::optional<SubresourceLoaderParams> subresource_loader_params,
absl::optional<std::vector<blink::mojom::TransferrableURLLoaderPtr>>
subresource_overrides,
blink::mojom::ServiceWorkerContainerInfoForClientPtr container_info,
const base::UnguessableToken& devtools_navigation_token,
std::unique_ptr<WebBundleHandle> web_bundle_handle) {
TRACE_EVENT2("navigation", "RenderFrameHostImpl::CommitNavigation",
"navigation_request", navigation_request, "url",
common_params->url);
DCHECK(!blink::IsRendererDebugURL(common_params->url));
DCHECK(navigation_request);
DCHECK_EQ(this, navigation_request->GetRenderFrameHost());
bool is_same_document =
NavigationTypeUtils::IsSameDocument(common_params->navigation_type);
bool is_mhtml_subframe = navigation_request->IsForMhtmlSubframe();
// A |response| and a |url_loader_client_endpoints| must always be provided,
// except for edge cases, where another way to load the document exist.
DCHECK((response_head && url_loader_client_endpoints) ||
common_params->url.SchemeIs(url::kDataScheme) || is_same_document ||
!IsURLHandledByNetworkStack(common_params->url) || is_mhtml_subframe);
// All children of MHTML documents must be MHTML documents.
// As a defensive measure, crash the browser if something went wrong.
if (!is_main_frame()) {
RenderFrameHostImpl* root = GetMainFrame();
if (root->is_mhtml_document_) {
bool loaded_from_outside_the_archive =
response_head || url_loader_client_endpoints;
CHECK(!loaded_from_outside_the_archive ||
common_params->url.SchemeIs(url::kDataScheme));
CHECK(navigation_request->IsForMhtmlSubframe());
CHECK_EQ(GetSiteInstance(), root->GetSiteInstance());
CHECK_EQ(GetProcess(), root->GetProcess());
} else {
DCHECK(!navigation_request->IsForMhtmlSubframe());
}
}
bool is_srcdoc = common_params->url.IsAboutSrcdoc();
if (is_srcdoc) {
commit_params->srcdoc_value = frame_tree_node_->srcdoc_value();
// Main frame srcdoc navigation are meaningless. They are blocked whenever a
// navigation attempt is made. It shouldn't reach CommitNavigation.
CHECK(!is_main_frame());
// For a srcdoc iframe, we expect it to either be in its parent's
// SiteInstance (either AreIsolatedSandboxedIframesEnabled is false or
// both parent and child are sandboxed), or that the two are in different
// SiteInstances when only the child is sandboxed.
CHECK(GetSiteInstance() == parent_->GetSiteInstance() ||
!parent_->GetSiteInstance()->GetSiteInfo().is_sandboxed() &&
GetSiteInstance()->GetSiteInfo().is_sandboxed());
}
// TODO(https://crbug.com/888079): Compute the Origin to commit here.
// If this is an attempt to commit a URL in an incompatible process, capture a
// crash dump to diagnose why it is occurring.
// TODO(creis): Remove this check after we've gathered enough information to
// debug issues with browser-side security checks. https://crbug.com/931895.
auto* policy = ChildProcessSecurityPolicyImpl::GetInstance();
const ProcessLock process_lock =
ProcessLock::FromSiteInfo(GetSiteInstance()->GetSiteInfo());
if (!process_lock.is_error_page() && common_params->url.IsStandard() &&
!is_mhtml_subframe &&
// TODO(https://crbug.com/888079): Replace `common_params().url` with
// the origin to commit calculated on the browser side.
!policy->CanAccessDataForOrigin(
GetProcess()->GetID(), url::Origin::Create(common_params->url))) {
SCOPED_CRASH_KEY_STRING64("CommitNavigation", "lock_url",
process_lock.ToString());
SCOPED_CRASH_KEY_STRING64(
"CommitNavigation", "commit_origin",
common_params->url.DeprecatedGetOriginAsURL().spec());
SCOPED_CRASH_KEY_BOOL("CommitNavigation", "is_main_frame", is_main_frame());
NOTREACHED() << "Commiting in incompatible process for URL: "
<< process_lock.lock_url() << " lock vs "
<< common_params->url.DeprecatedGetOriginAsURL();
base::debug::DumpWithoutCrashing();
}
const bool is_first_navigation = !has_committed_any_navigation_;
has_committed_any_navigation_ = true;
if (!is_same_document) {
// If this is NOT for same-document navigation, existing
// |web_bundle_handle_| should be reset to the new one. Otherwise the
// existing one should be kept around so that the subresource requests keep
// being served from the WebBundleURLLoaderFactory held by the handle.
web_bundle_handle_ = std::move(web_bundle_handle);
// Similarly, reset |subresource_web_bundle_navigation_info_| to the new one
// if this is NOT for same-document navigation. For same-document
// navigation, |navigation_request| doesn't have bundle information so
// existing one should be kept around.
subresource_web_bundle_navigation_info_ =
navigation_request->GetSubresourceWebBundleNavigationInfo();
}
UpdatePermissionsForNavigation(navigation_request);
// Get back to a clean state, in case we start a new navigation without
// completing an unload handler.
ResetWaitingState();
// The renderer can exit view source mode when any error or cancellation
// happen. When reusing the same renderer, overwrite to recover the mode.
if (commit_params->is_view_source && IsActive()) {
DCHECK(!GetParentOrOuterDocument());
GetAssociatedLocalFrame()->EnableViewSourceMode();
}
// TODO(lfg): The renderer is not able to handle a null response, so the
// browser provides an empty response instead. See the DCHECK in the beginning
// of this method for the edge cases where a response isn't provided.
network::mojom::URLResponseHeadPtr head =
response_head ? std::move(response_head)
: network::mojom::URLResponseHead::New();
std::unique_ptr<blink::PendingURLLoaderFactoryBundle>
subresource_loader_factories;
if (!is_same_document || is_first_navigation) {
recreate_default_url_loader_factory_after_network_service_crash_ = false;
subresource_loader_factories =
std::make_unique<blink::PendingURLLoaderFactoryBundle>();
BrowserContext* browser_context = GetSiteInstance()->GetBrowserContext();
auto subresource_loader_factories_config =
SubresourceLoaderFactoriesConfig::ForPendingNavigation(
*navigation_request);
// Calculate `effective_scheme` - this will be the main input for deciding
// whether the new document should have access to special URLLoaderFactories
// like FileURLLoaderFactory, ContentURLLoaderFactory,
// WebUIURLLoaderFactory, etc. We look at GetTupleOrPrecursorTupleIfOpaque
// to make sure the old behavior of sandboxed frames is preserved - see also
// the FileURLLoaderFactory...SubresourcesInSandboxedFileFrame test.
const std::string& effective_scheme =
subresource_loader_factories_config.origin()
.GetTupleOrPrecursorTupleIfOpaque()
.scheme();
ContentBrowserClient::NonNetworkURLLoaderFactoryMap non_network_factories;
// Set up the default factory.
mojo::PendingRemote<network::mojom::URLLoaderFactory>
pending_default_factory;
// See if this is for WebUI.
const auto& webui_schemes = URLDataManagerBackend::GetWebUISchemes();
if (base::Contains(webui_schemes, effective_scheme)) {
mojo::PendingRemote<network::mojom::URLLoaderFactory> factory_for_webui;
auto factory_receiver =
factory_for_webui.InitWithNewPipeAndPassReceiver();
GetContentClient()->browser()->WillCreateURLLoaderFactory(
browser_context, this, GetProcess()->GetID(),
ContentBrowserClient::URLLoaderFactoryType::kDocumentSubResource,
subresource_loader_factories_config.origin(),
/*navigation_id=*/absl::nullopt,
subresource_loader_factories_config.ukm_source_id(),
&factory_receiver, /*header_client=*/nullptr,
/*bypass_redirect_checks=*/nullptr,
/*disable_secure_dns=*/nullptr, /*factory_override=*/nullptr);
mojo::Remote<network::mojom::URLLoaderFactory> direct_factory_for_webui(
CreateWebUIURLLoaderFactory(this, effective_scheme, {}));
direct_factory_for_webui->Clone(std::move(factory_receiver));
// If the renderer has webui bindings, then don't give it access to
// network loader for security reasons.
// http://crbug.com/829412: make an exception for a small whitelist
// of WebUIs that need to be fixed to not make network requests in JS.
if ((enabled_bindings_ & kWebUIBindingsPolicyMask) &&
!GetContentClient()->browser()->IsWebUIAllowedToMakeNetworkRequests(
subresource_loader_factories_config.origin())) {
pending_default_factory = std::move(factory_for_webui);
// WebUIURLLoaderFactory will kill the renderer if it sees a request
// with a non-chrome scheme. Register a URLLoaderFactory for the about
// scheme so about:blank doesn't kill the renderer.
non_network_factories[url::kAboutScheme] =
AboutURLLoaderFactory::Create();
} else {
// This is a webui scheme that doesn't have webui bindings. Give it
// access to the network loader as it might require it.
subresource_loader_factories->pending_scheme_specific_factories()
.emplace(effective_scheme, std::move(factory_for_webui));
}
}
if (navigation_request->IsMhtmlOrSubframe()) {
// MHTML frames are not allowed to make any network requests - all their
// subresource requests should be fulfilled from the MHTML archive.
pending_default_factory =
network::NotImplementedURLLoaderFactory::Create();
}
if (!pending_default_factory) {
// Otherwise default to a Network Service-backed loader from the
// appropriate NetworkContext.
recreate_default_url_loader_factory_after_network_service_crash_ = true;
// Otherwise default to a Network Service-backed loader from the
// appropriate NetworkContext.
bool bypass_redirect_checks =
CreateNetworkServiceDefaultFactoryAndObserve(
CreateURLLoaderFactoryParamsForMainWorld(
subresource_loader_factories_config,
"RFHI::CommitNavigation"),
subresource_loader_factories_config.ukm_source_id(),
pending_default_factory.InitWithNewPipeAndPassReceiver());
subresource_loader_factories->set_bypass_redirect_checks(
bypass_redirect_checks);
}
bool navigation_to_web_bundle = false;
if (web_bundle_handle_ && web_bundle_handle_->IsReadyForLoading()) {
navigation_to_web_bundle = true;
mojo::Remote<network::mojom::URLLoaderFactory> fallback_factory(
std::move(pending_default_factory));
web_bundle_handle_->CreateURLLoaderFactory(
pending_default_factory.InitWithNewPipeAndPassReceiver(),
std::move(fallback_factory));
DCHECK(web_bundle_handle_->navigation_info());
commit_params->web_bundle_physical_url =
web_bundle_handle_->navigation_info()->source().url();
if (web_bundle_handle_->claimed_url().is_valid()) {
commit_params->web_bundle_claimed_url =
web_bundle_handle_->claimed_url();
}
}
DCHECK(pending_default_factory);
subresource_loader_factories->pending_default_factory() =
std::move(pending_default_factory);
// Only documents from a file precursor scheme can load file subresoruces.
//
// For loading Web Bundle files, we don't set FileURLLoaderFactory.
// Because loading local files from a Web Bundle file is prohibited.
if (effective_scheme == url::kFileScheme && !navigation_to_web_bundle) {
// USER_BLOCKING because this scenario is exactly one of the examples
// given by the doc comment for USER_BLOCKING: Loading and rendering a web
// page after the user clicks a link.
base::TaskPriority file_factory_priority =
base::TaskPriority::USER_BLOCKING;
non_network_factories.emplace(
url::kFileScheme,
FileURLLoaderFactory::Create(
browser_context->GetPath(),
browser_context->GetSharedCorsOriginAccessList(),
file_factory_priority));
}
#if BUILDFLAG(IS_ANDROID)
if (effective_scheme == url::kContentScheme) {
// Only content:// URLs can load content:// subresources
non_network_factories.emplace(url::kContentScheme,
ContentURLLoaderFactory::Create());
}
#endif
auto* partition = GetStoragePartition();
non_network_factories.emplace(
url::kFileSystemScheme,
CreateFileSystemURLLoaderFactory(
GetProcess()->GetID(), GetFrameTreeNodeId(),
partition->GetFileSystemContext(), partition->GetPartitionDomain(),
commit_params->storage_key));
non_network_factories.emplace(url::kDataScheme,
DataURLLoaderFactory::Create());
GetContentClient()
->browser()
->RegisterNonNetworkSubresourceURLLoaderFactories(
GetProcess()->GetID(), routing_id_,
subresource_loader_factories_config.origin(),
&non_network_factories);
for (auto& factory : non_network_factories) {
mojo::PendingRemote<network::mojom::URLLoaderFactory>
pending_factory_proxy;
mojo::PendingReceiver<network::mojom::URLLoaderFactory> factory_receiver =
pending_factory_proxy.InitWithNewPipeAndPassReceiver();
WillCreateURLLoaderFactory(
subresource_loader_factories_config.origin(), &factory_receiver,
subresource_loader_factories_config.ukm_source_id());
mojo::Remote<network::mojom::URLLoaderFactory> remote(
std::move(factory.second));
remote->Clone(std::move(factory_receiver));
subresource_loader_factories->pending_scheme_specific_factories().emplace(
factory.first, std::move(pending_factory_proxy));
}
subresource_loader_factories->pending_isolated_world_factories() =
CreateURLLoaderFactoriesForIsolatedWorlds(
subresource_loader_factories_config,
isolated_worlds_requiring_separate_url_loader_factory_);
}
// It is imperative that cross-document navigations always provide a set of
// subresource ULFs.
DCHECK(is_same_document || !is_first_navigation || is_srcdoc ||
subresource_loader_factories);
if (is_same_document) {
DCHECK_EQ(navigation_request->frame_tree_node()->current_frame_host(),
this);
const base::UnguessableToken& navigation_token =
commit_params->navigation_token;
DCHECK(GetSameDocumentNavigationRequest(navigation_token));
bool should_replace_current_entry =
common_params->should_replace_current_entry;
GetMojomFrameInRenderer()->CommitSameDocumentNavigation(
std::move(common_params), std::move(commit_params),
base::BindOnce(&RenderFrameHostImpl::OnSameDocumentCommitProcessed,
base::Unretained(this), navigation_token,
should_replace_current_entry));
} else {
// Pass the controller service worker info if we have one.
blink::mojom::ControllerServiceWorkerInfoPtr controller;
mojo::PendingAssociatedRemote<blink::mojom::ServiceWorkerObject>
remote_object;
blink::mojom::ServiceWorkerState sent_state;
if (subresource_loader_params &&
subresource_loader_params->controller_service_worker_info) {
controller =
std::move(subresource_loader_params->controller_service_worker_info);
if (controller->object_info) {
controller->object_info->receiver =
remote_object.InitWithNewEndpointAndPassReceiver();
sent_state = controller->object_info->state;
}
}
std::unique_ptr<blink::PendingURLLoaderFactoryBundle>
factory_bundle_for_prefetch;
mojo::PendingRemote<network::mojom::URLLoaderFactory>
prefetch_loader_factory;
if (subresource_loader_factories) {
// Clone the factory bundle for prefetch.
auto bundle = base::MakeRefCounted<blink::URLLoaderFactoryBundle>(
std::move(subresource_loader_factories));
subresource_loader_factories = CloneFactoryBundle(bundle);
factory_bundle_for_prefetch = CloneFactoryBundle(bundle);
}
if (factory_bundle_for_prefetch) {
if (prefetched_signed_exchange_cache_) {
prefetched_signed_exchange_cache_->RecordHistograms();
// Reset |prefetched_signed_exchange_cache_|, not to reuse the cached
// signed exchange which was prefetched in the previous page.
prefetched_signed_exchange_cache_.reset();
}
// Also set-up URLLoaderFactory for prefetch using the same loader
// factories. TODO(kinuko): Consider setting this up only when prefetch
// is used. Currently we have this here to make sure we have non-racy
// situation (https://crbug.com/849929).
auto* storage_partition = GetStoragePartition();
storage_partition->GetPrefetchURLLoaderService()->GetFactory(
prefetch_loader_factory.InitWithNewPipeAndPassReceiver(),
navigation_request->frame_tree_node()->frame_tree_node_id(),
std::move(factory_bundle_for_prefetch),
weak_ptr_factory_.GetWeakPtr(),
EnsurePrefetchedSignedExchangeCache());
}
mojom::NavigationClient* navigation_client =
navigation_request->GetCommitNavigationClient();
// Record the metrics about the state of the old main frame at the moment
// when we navigate away from it as it matters for whether the page
// is eligible for being put into back-forward cache.
//
// Ideally we would do this when we are just about to swap out the old
// render frame and swap in the new one, but we can't do this for
// same-process navigations yet as we are reusing the RenderFrameHost and
// as the local frame navigates it overrides the values that we are
// interested in. The cross-process navigation case is handled in
// RenderFrameHostManager::UnloadOldFrame.
//
// Here we are recording the metrics for same-process navigations at the
// point just before the navigation commits.
// TODO(altimin, crbug.com/933147): Remove this logic after we are done with
// implementing back-forward cache.
if (!GetParent() &&
navigation_request->frame_tree_node()->current_frame_host() == this) {
if (NavigationEntryImpl* last_committed_entry =
NavigationEntryImpl::FromNavigationEntry(
navigation_request->frame_tree_node()
->frame_tree()
->controller()
.GetLastCommittedEntry())) {
if (last_committed_entry->back_forward_cache_metrics()) {
last_committed_entry->back_forward_cache_metrics()
->RecordFeatureUsage(this);
}
}
}
blink::mojom::PolicyContainerPtr policy_container =
navigation_request->CreatePolicyContainerForBlink();
auto isolation_info = GetSiteInstance()->GetWebExposedIsolationInfo();
RenderFrameHostImpl* parent_frame_host = GetParentOrOuterDocument();
blink::ParsedPermissionsPolicy manifest_policy;
if (!parent_frame_host && isolation_info.is_isolated_application()) {
manifest_policy =
GetContentClient()->browser()->GetPermissionsPolicyForIsolatedApp(
GetBrowserContext(), isolation_info.origin());
}
// TODO(crbug.com/1126305): Once the Prerender2 moves to use the MPArch, we
// need to check the relevant FrameTree to know the precise prerendering
// state to update commit_params.is_prerendering here.
// Current design doesn't capture the cases NavigationRequest is created via
// CreateRendererInitiated or CreateForSynchronousRendererCommit.
SendCommitNavigation(
navigation_client, navigation_request, std::move(common_params),
std::move(commit_params), std::move(head), std::move(response_body),
std::move(url_loader_client_endpoints),
std::move(subresource_loader_factories),
std::move(subresource_overrides), std::move(controller),
std::move(container_info), std::move(prefetch_loader_factory),
manifest_policy, std::move(policy_container),
devtools_navigation_token);
frame_tree_node_->navigator().LogCommitNavigationSent();
// |remote_object| is an associated interface ptr, so calls can't be made on
// it until its request endpoint is sent. Now that the request endpoint was
// sent, it can be used, so add it to ServiceWorkerObjectHost.
if (remote_object.is_valid()) {
subresource_loader_params->controller_service_worker_object_host
->AddRemoteObjectPtrAndUpdateState(std::move(remote_object),
sent_state);
}
}
}
void RenderFrameHostImpl::FailedNavigation(
NavigationRequest* navigation_request,
const blink::mojom::CommonNavigationParams& common_params,
const blink::mojom::CommitNavigationParams& commit_params,
bool has_stale_copy_in_cache,
int error_code,
int extended_error_code,
const absl::optional<std::string>& error_page_content) {
TRACE_EVENT2("navigation", "RenderFrameHostImpl::FailedNavigation",
"navigation_request", navigation_request, "error", error_code);
DCHECK(navigation_request);
// Update renderer permissions even for failed commits, so that for example
// the URL bar correctly displays privileged URLs instead of filtering them.
UpdatePermissionsForNavigation(navigation_request);
// Get back to a clean state, in case a new navigation started without
// completing an unload handler.
ResetWaitingState();
std::unique_ptr<blink::PendingURLLoaderFactoryBundle>
subresource_loader_factories;
mojo::PendingRemote<network::mojom::URLLoaderFactory> default_factory_remote;
bool bypass_redirect_checks = CreateNetworkServiceDefaultFactoryAndObserve(
CreateURLLoaderFactoryParamsForMainWorld(
SubresourceLoaderFactoriesConfig::ForPendingNavigation(
*navigation_request),
"RFHI::FailedNavigation"),
ukm::kInvalidSourceIdObj,
default_factory_remote.InitWithNewPipeAndPassReceiver());
subresource_loader_factories =
std::make_unique<blink::PendingURLLoaderFactoryBundle>(
std::move(default_factory_remote),
blink::PendingURLLoaderFactoryBundle::SchemeMap(),
blink::PendingURLLoaderFactoryBundle::OriginMap(),
bypass_redirect_checks);
mojom::NavigationClient* navigation_client =
navigation_request->GetCommitNavigationClient();
blink::mojom::PolicyContainerPtr policy_container =
navigation_request->CreatePolicyContainerForBlink();
SendCommitFailedNavigation(
navigation_client, navigation_request, common_params.Clone(),
commit_params.Clone(), has_stale_copy_in_cache, error_code,
extended_error_code, error_page_content,
std::move(subresource_loader_factories), std::move(policy_container));
// TODO(crbug/1129537): support UKM source creation for failed navigations
// too.
has_committed_any_navigation_ = true;
DCHECK(navigation_request && navigation_request->IsNavigationStarted() &&
navigation_request->DidEncounterError());
}
void RenderFrameHostImpl::HandleRendererDebugURL(const GURL& url) {
DCHECK(blink::IsRendererDebugURL(url));
// Several tests expect a load of Chrome Debug URLs to send a DidStopLoading
// notification, so set is loading to true here to properly surface it when
// the renderer process is done handling the URL.
// TODO(crbug.com/1254130): Remove the test dependency on this behavior.
if (!url.SchemeIs(url::kJavaScriptScheme)) {
bool was_loading =
frame_tree()->LoadingTree()->IsLoadingIncludingInnerFrameTrees();
is_loading_ = true;
frame_tree_node()->DidStartLoading(true /* should_show_loading_ui */,
was_loading);
}
GetAssociatedLocalFrame()->HandleRendererDebugURL(url);
// Ensure that the renderer process is marked as used after processing a
// renderer debug URL, since this process is now unsafe to be reused by sites
// that require a dedicated process. Usually this happens at ready-to-commit
// (NavigationRequest::OnResponseStarted) time for regular navigations, but
// renderer debug URLs don't go through that path. This matters for initial
// navigations to renderer debug URLs. See https://crbug.com/1074108.
GetProcess()->SetIsUsed();
}
void RenderFrameHostImpl::CreateBroadcastChannelProvider(
mojo::PendingAssociatedReceiver<blink::mojom::BroadcastChannelProvider>
receiver) {
auto* storage_partition_impl =
static_cast<StoragePartitionImpl*>(GetStoragePartition());
auto* broadcast_channel_service =
storage_partition_impl->GetBroadcastChannelService();
broadcast_channel_service->AddAssociatedReceiver(
std::make_unique<BroadcastChannelProvider>(broadcast_channel_service,
storage_key()),
std::move(receiver));
}
void RenderFrameHostImpl::SetUpMojoConnection() {
CHECK(!associated_registry_);
// TODO(https://crbug.com/1265864): Move the registry logic below to another
// file to ensure security review coverage.
associated_registry_ = std::make_unique<blink::AssociatedInterfaceRegistry>();
auto bind_frame_host_receiver =
[](RenderFrameHostImpl* impl,
mojo::PendingAssociatedReceiver<mojom::FrameHost> receiver) {
impl->frame_host_associated_receiver_.Bind(std::move(receiver));
impl->frame_host_associated_receiver_.SetFilter(
impl->CreateMessageFilterForAssociatedReceiver(
mojom::FrameHost::Name_));
};
associated_registry_->AddInterface<mojom::FrameHost>(
base::BindRepeating(bind_frame_host_receiver, base::Unretained(this)));
associated_registry_->AddInterface<
blink::mojom::BackForwardCacheControllerHost>(base::BindRepeating(
[](RenderFrameHostImpl* impl,
mojo::PendingAssociatedReceiver<
blink::mojom::BackForwardCacheControllerHost> receiver) {
impl->back_forward_cache_controller_host_associated_receiver_.Bind(
std::move(receiver));
impl->back_forward_cache_controller_host_associated_receiver_.SetFilter(
CreateMessageFilterForAssociatedReceiverImpl(
impl, blink::mojom::BackForwardCacheControllerHost::Name_,
BackForwardCacheImpl::kMessagePolicyNone));
},
base::Unretained(this)));
associated_registry_->AddInterface<blink::mojom::PortalHost>(
base::BindRepeating(
[](RenderFrameHostImpl* self,
mojo::PendingAssociatedReceiver<blink::mojom::PortalHost>
receiver) {
Portal::BindPortalHostReceiver(self, std::move(receiver));
},
base::Unretained(this)));
associated_registry_->AddInterface<blink::mojom::LocalFrameHost>(
base::BindRepeating(
[](RenderFrameHostImpl* impl,
mojo::PendingAssociatedReceiver<blink::mojom::LocalFrameHost>
receiver) {
impl->local_frame_host_receiver_.Bind(std::move(receiver));
impl->local_frame_host_receiver_.SetFilter(
impl->CreateMessageFilterForAssociatedReceiver(
blink::mojom::LocalFrameHost::Name_));
},
base::Unretained(this)));
if (base::FeatureList::IsEnabled(blink::features::kSharedStorageAPI)) {
associated_registry_->AddInterface<
blink::mojom::SharedStorageDocumentService>(base::BindRepeating(
[](RenderFrameHostImpl* impl,
mojo::PendingAssociatedReceiver<
blink::mojom::SharedStorageDocumentService> receiver) {
if (SharedStorageDocumentServiceImpl::GetForCurrentDocument(impl)) {
// The renderer somehow requested two shared storage worklets
// associated with the same document. This could indicate a
// compromised renderer, so let's terminate it.
mojo::ReportBadMessage(
"Attempted to request two shared storage worklets associated "
"with the same document.");
return;
}
SharedStorageDocumentServiceImpl::GetOrCreateForCurrentDocument(impl)
->Bind(std::move(receiver));
},
base::Unretained(this)));
}
if (is_main_frame()) {
associated_registry_->AddInterface<blink::mojom::LocalMainFrameHost>(
base::BindRepeating(
[](RenderFrameHostImpl* impl,
mojo::PendingAssociatedReceiver<blink::mojom::LocalMainFrameHost>
receiver) {
impl->local_main_frame_host_receiver_.Bind(std::move(receiver));
impl->local_main_frame_host_receiver_.SetFilter(
impl->CreateMessageFilterForAssociatedReceiver(
blink::mojom::LocalMainFrameHost::Name_));
},
base::Unretained(this)));
associated_registry_->AddInterface<blink::mojom::ManifestUrlChangeObserver>(
base::BindRepeating(
[](RenderFrameHostImpl* impl,
mojo::PendingAssociatedReceiver<
blink::mojom::ManifestUrlChangeObserver> receiver) {
ManifestManagerHost::GetOrCreateForPage(impl->GetPage())
->BindObserver(std::move(receiver));
},
base::Unretained(this)));
}
// TODO(crbug.com/1047354): How to avoid binding if the
// BINDINGS_POLICY_DOM_AUTOMATION policy is not set?
associated_registry_->AddInterface<mojom::DomAutomationControllerHost>(
base::BindRepeating(
[](RenderFrameHostImpl* impl,
mojo::PendingAssociatedReceiver<mojom::DomAutomationControllerHost>
receiver) {
impl->BindDomOperationControllerHostReceiver(std::move(receiver));
},
base::Unretained(this)));
file_system_manager_.reset(new FileSystemManagerImpl(
GetProcess()->GetID(),
GetProcess()->GetStoragePartition()->GetFileSystemContext(),
ChromeBlobStorageContext::GetFor(GetProcess()->GetBrowserContext())));
#if BUILDFLAG(ENABLE_PLUGINS)
associated_registry_->AddInterface<mojom::PepperHost>(base::BindRepeating(
[](RenderFrameHostImpl* impl,
mojo::PendingAssociatedReceiver<mojom::PepperHost> receiver) {
impl->pepper_host_receiver_.Bind(std::move(receiver));
impl->pepper_host_receiver_.SetFilter(
impl->CreateMessageFilterForAssociatedReceiver(
mojom::PepperHost::Name_));
},
base::Unretained(this)));
#endif
associated_registry_->AddInterface<media::mojom::MediaPlayerHost>(
base::BindRepeating(
[](RenderFrameHostImpl* impl,
mojo::PendingAssociatedReceiver<media::mojom::MediaPlayerHost>
receiver) {
impl->delegate()->CreateMediaPlayerHostForRenderFrameHost(
impl, std::move(receiver));
},
base::Unretained(this)));
associated_registry_->AddInterface<blink::mojom::DisplayCutoutHost>(
base::BindRepeating(
[](RenderFrameHostImpl* impl,
mojo::PendingAssociatedReceiver<blink::mojom::DisplayCutoutHost>
receiver) {
impl->delegate()->BindDisplayCutoutHost(impl, std::move(receiver));
},
base::Unretained(this)));
associated_registry_->AddInterface<blink::mojom::ConversionHost>(
base::BindRepeating(
[](RenderFrameHostImpl* impl,
mojo::PendingAssociatedReceiver<blink::mojom::ConversionHost>
receiver) {
AttributionHost::BindReceiver(std::move(receiver), impl);
},
base::Unretained(this)));
associated_registry_->AddInterface<device::mojom::ScreenOrientation>(
base::BindRepeating(
[](RenderFrameHostImpl* impl,
mojo::PendingAssociatedReceiver<device::mojom::ScreenOrientation>
receiver) {
impl->delegate()->BindScreenOrientation(impl, std::move(receiver));
},
base::Unretained(this)));
associated_registry_->AddInterface<blink::mojom::BroadcastChannelProvider>(
base::BindRepeating(&RenderFrameHostImpl::CreateBroadcastChannelProvider,
base::Unretained(this)));
// Allow embedders to register their binders.
GetContentClient()
->browser()
->RegisterAssociatedInterfaceBindersForRenderFrameHost(
*this, *associated_registry_);
mojo::PendingRemote<service_manager::mojom::InterfaceProvider>
remote_interfaces;
GetMojomFrameInRenderer()->GetInterfaceProvider(
remote_interfaces.InitWithNewPipeAndPassReceiver());
remote_interfaces_ = std::make_unique<service_manager::InterfaceProvider>(
base::ThreadTaskRunnerHandle::Get());
remote_interfaces_->Bind(std::move(remote_interfaces));
// Called to bind the receiver for this interface to the local frame. We need
// to eagarly bind here because binding happens at normal priority on the main
// thread and future calls to this interface need to be high priority.
GetHighPriorityLocalFrame();
}
void RenderFrameHostImpl::TearDownMojoConnection() {
// While not directly a Mojo endpoint, |geolocation_service_| may attempt to
// cancel permission requests.
geolocation_service_.reset();
associated_registry_.reset();
mojo_image_downloader_.reset();
find_in_page_.reset();
local_frame_.reset();
local_main_frame_.reset();
high_priority_local_frame_.reset();
frame_host_associated_receiver_.reset();
associated_interface_provider_receiver_.reset();
back_forward_cache_controller_host_associated_receiver_.reset();
frame_.reset();
frame_bindings_control_.reset();
local_frame_host_receiver_.reset();
local_main_frame_host_receiver_.reset();
broker_receiver_.reset();
render_accessibility_.reset();
render_accessibility_host_.Reset();
dom_automation_controller_receiver_.reset();
#if BUILDFLAG(ENABLE_PLUGINS)
pepper_host_receiver_.reset();
pepper_instance_map_.clear();
pepper_hung_detectors_.Clear();
#endif // BUILDFLAG(ENABLE_PLUGINS)
// Audio stream factories are tied to a live RenderFrame: see
// //content/browser/media/forwarding_audio_stream_factory.h.
// Eagerly reset now to ensure that it is impossible to create streams
// associated with a RenderFrameHost without a live RenderFrame;
// otherwise, the `RenderFrameDeleted()` signal used to clean up streams
// will never fire.
audio_service_audio_output_stream_factory_.reset();
audio_service_audio_input_stream_factory_.reset();
}
bool RenderFrameHostImpl::IsFocused() {
if (!GetMainFrame()->GetRenderWidgetHost()->is_focused() ||
!frame_tree_->GetFocusedFrame())
return false;
RenderFrameHostImpl* focused_rfh =
frame_tree_->GetFocusedFrame()->current_frame_host();
return focused_rfh == this ||
focused_rfh->IsDescendantOfWithinFrameTree(this);
}
bool RenderFrameHostImpl::CreateWebUI(const GURL& dest_url,
int entry_bindings) {
// Verify expectation that WebUI should not be created for error pages.
DCHECK(!GetSiteInstance()->GetSiteInfo().is_error_page());
WebUI::TypeID new_web_ui_type =
WebUIControllerFactoryRegistry::GetInstance()->GetWebUIType(
GetSiteInstance()->GetBrowserContext(), dest_url);
CHECK_NE(new_web_ui_type, WebUI::kNoWebUI);
// If |web_ui_| already exists, there is no need to create a new one. However,
// it is useful to verify that its type hasn't changed. Site isolation
// guarantees that RenderFrameHostImpl will be changed if the WebUI type
// differs.
if (web_ui_) {
CHECK_EQ(new_web_ui_type, web_ui_type_);
return false;
}
web_ui_ = delegate_->CreateWebUIForRenderFrameHost(this, dest_url);
if (!web_ui_)
return false;
// If we have assigned (zero or more) bindings to the NavigationEntry in
// the past, make sure we're not granting it different bindings than it
// had before. If so, note it and don't give it any bindings, to avoid a
// potential privilege escalation.
if (entry_bindings != FrameNavigationEntry::kInvalidBindings &&
web_ui_->GetBindings() != entry_bindings) {
RecordAction(base::UserMetricsAction("ProcessSwapBindingsMismatch_RVHM"));
base::WeakPtr<RenderFrameHostImpl> self = GetWeakPtr();
ClearWebUI();
// `ClearWebUI()` may indirectly call content's embedders and delete this.
// There are no known occurrences of it, so we assume this never happen and
// crash immediately if it does, because there are no easy ways to recover.
CHECK(self);
return false;
}
// It is not expected for GuestView to be able to navigate to WebUI.
DCHECK(!GetProcess()->IsForGuestsOnly());
web_ui_type_ = new_web_ui_type;
// WebUIs need the ability to request certain schemes.
for (const auto& scheme : web_ui_->GetRequestableSchemes()) {
ChildProcessSecurityPolicyImpl::GetInstance()->GrantRequestScheme(
GetProcess()->GetID(), scheme);
}
// Since this is new WebUI instance, this RenderFrameHostImpl should not
// have had any bindings. Verify that and grant the required bindings.
DCHECK_EQ(BINDINGS_POLICY_NONE, GetEnabledBindings());
AllowBindings(web_ui_->GetBindings());
return true;
}
void RenderFrameHostImpl::ClearWebUI() {
web_ui_type_ = WebUI::kNoWebUI;
web_ui_.reset(); // This might delete `this`.
// DO NOT ADD CODE after this.
}
const mojo::Remote<blink::mojom::ImageDownloader>&
RenderFrameHostImpl::GetMojoImageDownloader() {
// TODO(https://crbug.com/1249933): Call AssertNonSpeculativeFrame() here.
if (!mojo_image_downloader_.is_bound() && GetRemoteInterfaces()) {
GetRemoteInterfaces()->GetInterface(
mojo_image_downloader_.BindNewPipeAndPassReceiver());
}
return mojo_image_downloader_;
}
const mojo::AssociatedRemote<blink::mojom::FindInPage>&
RenderFrameHostImpl::GetFindInPage() {
if (!find_in_page_)
GetRemoteAssociatedInterfaces()->GetInterface(&find_in_page_);
return find_in_page_;
}
const mojo::AssociatedRemote<blink::mojom::LocalFrame>&
RenderFrameHostImpl::GetAssociatedLocalFrame() {
if (!local_frame_)
GetRemoteAssociatedInterfaces()->GetInterface(&local_frame_);
return local_frame_;
}
blink::mojom::LocalMainFrame*
RenderFrameHostImpl::GetAssociatedLocalMainFrame() {
DCHECK(is_main_frame());
if (!local_main_frame_)
GetRemoteAssociatedInterfaces()->GetInterface(&local_main_frame_);
return local_main_frame_.get();
}
const mojo::Remote<blink::mojom::HighPriorityLocalFrame>&
RenderFrameHostImpl::GetHighPriorityLocalFrame() {
if (!high_priority_local_frame_.is_bound()) {
GetRemoteInterfaces()->GetInterface(
high_priority_local_frame_.BindNewPipeAndPassReceiver());
}
return high_priority_local_frame_;
}
const mojo::AssociatedRemote<mojom::FrameBindingsControl>&
RenderFrameHostImpl::GetFrameBindingsControl() {
if (!frame_bindings_control_)
GetRemoteAssociatedInterfaces()->GetInterface(&frame_bindings_control_);
return frame_bindings_control_;
}
void RenderFrameHostImpl::ResetLoadingState() {
if (is_loading()) {
// When pending deletion, just set the loading state to not loading.
// Otherwise, DidStopLoading will take care of that, as well as sending
// notification to the FrameTreeNode about the change in loading state.
if (IsPendingDeletion() || IsInBackForwardCache())
is_loading_ = false;
else
DidStopLoading();
}
}
void RenderFrameHostImpl::ClearFocusedElement() {
has_focused_editable_element_ = false;
GetAssociatedLocalFrame()->ClearFocusedElement();
}
void RenderFrameHostImpl::BindDevToolsAgent(
mojo::PendingAssociatedRemote<blink::mojom::DevToolsAgentHost> host,
mojo::PendingAssociatedReceiver<blink::mojom::DevToolsAgent> receiver) {
GetAssociatedLocalFrame()->BindDevToolsAgent(std::move(host),
std::move(receiver));
}
bool RenderFrameHostImpl::IsSameSiteInstance(
RenderFrameHostImpl* other_render_frame_host) {
// As a sanity check, make sure the frame belongs to the same BrowserContext.
CHECK_EQ(GetSiteInstance()->GetBrowserContext(),
other_render_frame_host->GetSiteInstance()->GetBrowserContext());
return GetSiteInstance() == other_render_frame_host->GetSiteInstance();
}
void RenderFrameHostImpl::UpdateAccessibilityMode() {
// Don't update accessibility mode for a frame that hasn't been created yet.
if (!IsRenderFrameLive())
return;
ui::AXMode ax_mode = delegate_->GetAccessibilityMode();
// Disable BackForwardCache if ScreenReader is on.
// TODO(crbug.com/1271450): Screen readers do not recognize a navigation when
// the page is served from bfcache. Remove the flag and this section once the
// fix is landed.
if (ax_mode.has_mode(ui::AXMode::kScreenReader) &&
!BackForwardCacheImpl::IsScreenReaderAllowed()) {
BackForwardCache::DisableForRenderFrameHost(
this, BackForwardCacheDisable::DisabledReason(
BackForwardCacheDisable::DisabledReasonId::kScreenReader));
}
if (!ax_mode.has_mode(ui::AXMode::kWebContents)) {
// Resetting the Remote signals the renderer to shutdown accessibility
// in the renderer.
render_accessibility_.reset();
if (browser_accessibility_manager_) {
browser_accessibility_manager_->DetachFromParentManager();
browser_accessibility_manager_.reset();
}
return;
}
if (!render_accessibility_) {
// Render accessibility is not enabled yet, so bind the interface first.
GetRemoteAssociatedInterfaces()->GetInterface(&render_accessibility_);
}
render_accessibility_->SetMode(ax_mode.mode());
}
void RenderFrameHostImpl::RequestAXTreeSnapshot(
AXTreeSnapshotCallback callback,
const ui::AXMode& ax_mode,
bool exclude_offscreen,
size_t max_nodes,
const base::TimeDelta& timeout) {
auto params = mojom::SnapshotAccessibilityTreeParams::New();
params->ax_mode = ax_mode.mode();
params->exclude_offscreen = exclude_offscreen;
params->max_nodes = max_nodes;
params->timeout = timeout;
RequestAXTreeSnapshot(std::move(callback), std::move(params));
}
void RenderFrameHostImpl::RequestAXTreeSnapshot(
AXTreeSnapshotCallback callback,
mojom::SnapshotAccessibilityTreeParamsPtr params) {
// TODO(https://crbug.com/859110): Remove once frame_ can no longer be null.
if (!IsRenderFrameLive())
return;
GetMojomFrameInRenderer()->SnapshotAccessibilityTree(
std::move(params),
base::BindOnce(&RenderFrameHostImpl::RequestAXTreeSnapshotCallback,
weak_ptr_factory_.GetWeakPtr(), std::move(callback)));
}
void RenderFrameHostImpl::RequestDistilledAXTree(
AXTreeDistillerCallback callback) {
// TODO(https://crbug.com/859110): Remove once frame_ can no longer be null.
if (!IsRenderFrameLive())
return;
GetMojomFrameInRenderer()->SnapshotAndDistillAXTree(
base::BindOnce(&RenderFrameHostImpl::RequestDistilledAXTreeCallback,
weak_ptr_factory_.GetWeakPtr(), std::move(callback)));
}
void RenderFrameHostImpl::GetSavableResourceLinksFromRenderer() {
if (!IsRenderFrameLive())
return;
GetAssociatedLocalFrame()->GetSavableResourceLinks(
base::BindOnce(&RenderFrameHostImpl::GetSavableResourceLinksCallback,
weak_ptr_factory_.GetWeakPtr()));
}
void RenderFrameHostImpl::SetAccessibilityCallbackForTesting(
const AccessibilityCallbackForTesting& callback) {
accessibility_testing_callback_ = callback;
}
void RenderFrameHostImpl::UpdateAXTreeData() {
ui::AXMode accessibility_mode = delegate_->GetAccessibilityMode();
if (accessibility_mode.is_mode_off() ||
IsInactiveAndDisallowActivation(
DisallowActivationReasonId::kAXUpdateTree)) {
return;
}
// If `needs_ax_root_id_` is true, an AXTreeUpdate has not been sent to
// the renderer with a valid root id. This effectively means the renderer
// does not really know about the tree, and the renderer should not be
// updated. The renderer will be updated once the root id is known.
if (needs_ax_root_id_)
return;
if (ax_defer_scope_count_ > 0) {
ax_update_deferred_ = true;
return;
}
AXEventNotificationDetails detail;
detail.ax_tree_id = GetAXTreeID();
detail.updates.resize(1);
detail.updates[0].has_tree_data = true;
detail.updates[0].tree_data = GetAXTreeData();
SendAccessibilityEventsToManager(detail);
delegate_->AccessibilityEventReceived(detail);
}
RenderFrameHostImpl::UpdateAXFocusDeferScope::UpdateAXFocusDeferScope(
RenderFrameHostImpl& rfh)
: rfh_(rfh.GetSafeRef()) {
++rfh_->ax_defer_scope_count_;
}
RenderFrameHostImpl::UpdateAXFocusDeferScope::~UpdateAXFocusDeferScope() {
DCHECK_GE(rfh_->ax_defer_scope_count_, 1);
--rfh_->ax_defer_scope_count_;
if (!rfh_->ax_defer_scope_count_ && rfh_->ax_update_deferred_) {
rfh_->ax_update_deferred_ = false;
rfh_->UpdateAXTreeData();
}
}
BrowserAccessibilityManager*
RenderFrameHostImpl::GetOrCreateBrowserAccessibilityManager() {
if (browser_accessibility_manager_ ||
no_create_browser_accessibility_manager_for_testing_)
return browser_accessibility_manager_.get();
browser_accessibility_manager_.reset(
BrowserAccessibilityManager::Create(this));
return browser_accessibility_manager_.get();
}
void RenderFrameHostImpl::ActivateFindInPageResultForAccessibility(
int request_id) {
ui::AXMode accessibility_mode = delegate_->GetAccessibilityMode();
if (accessibility_mode.has_mode(ui::AXMode::kNativeAPIs)) {
BrowserAccessibilityManager* manager =
GetOrCreateBrowserAccessibilityManager();
if (manager)
manager->ActivateFindInPageResult(request_id);
}
}
void RenderFrameHostImpl::InsertVisualStateCallback(
VisualStateCallback callback) {
GetRenderWidgetHost()->InsertVisualStateCallback(std::move(callback));
}
bool RenderFrameHostImpl::IsLastCommitIPAddressPubliclyRoutable() const {
net::IPEndPoint ip_end_point =
last_response_head().get() ? last_response_head().get()->remote_endpoint
: net::IPEndPoint();
return ip_end_point.address().IsPubliclyRoutable();
}
bool RenderFrameHostImpl::IsRenderFrameLive() {
bool is_live =
GetProcess()->IsInitializedAndNotDead() && is_render_frame_created();
// Sanity check: the RenderView should always be live if the RenderFrame is.
DCHECK(!is_live || render_view_host_->IsRenderViewLive());
return is_live;
}
RenderFrameHost::LifecycleState RenderFrameHostImpl::GetLifecycleState() {
return GetLifecycleStateFromImpl(lifecycle_state());
}
bool RenderFrameHostImpl::IsInLifecycleState(LifecycleState state) {
if (lifecycle_state() == LifecycleStateImpl::kSpeculative)
return false;
return state == GetLifecycleState();
}
RenderFrameHost::LifecycleState RenderFrameHostImpl::GetLifecycleStateFromImpl(
LifecycleStateImpl state) {
switch (state) {
case LifecycleStateImpl::kSpeculative:
// TODO(https://crbug.com/1183639): Ensure that Speculative
// RenderFrameHosts are not exposed to embedders.
NOTREACHED();
return LifecycleState::kPendingCommit;
case LifecycleStateImpl::kPendingCommit:
return LifecycleState::kPendingCommit;
case LifecycleStateImpl::kPrerendering:
return LifecycleState::kPrerendering;
case LifecycleStateImpl::kActive:
return LifecycleState::kActive;
case LifecycleStateImpl::kInBackForwardCache:
return LifecycleState::kInBackForwardCache;
case LifecycleStateImpl::kRunningUnloadHandlers:
return LifecycleState::kPendingDeletion;
case LifecycleStateImpl::kReadyToBeDeleted:
return LifecycleState::kPendingDeletion;
}
}
bool RenderFrameHostImpl::IsActive() {
// When the document is transitioning away from kActive/kPrerendering to a
// yet-to-be-determined state, the RenderFrameHostManager has already
// updated its active RenderFrameHost, and the old document is no longer
// the active one. In that case, return false.
if (has_pending_lifecycle_state_update_)
return false;
return lifecycle_state() == LifecycleStateImpl::kActive;
}
size_t RenderFrameHostImpl::GetProxyCount() {
if (!IsActive())
return 0;
return browsing_context_state_->GetProxyCount();
}
bool RenderFrameHostImpl::HasSelection() {
return has_selection_;
}
FrameTreeNode* RenderFrameHostImpl::PreviousSibling() const {
return GetSibling(-1);
}
FrameTreeNode* RenderFrameHostImpl::NextSibling() const {
return GetSibling(1);
}
FrameTreeNode* RenderFrameHostImpl::GetSibling(int relative_offset) const {
if (!parent_ || !parent_->child_count())
return nullptr;
for (size_t i = 0; i < parent_->child_count(); ++i) {
// Frame tree node id will only be known for subframes, and will therefore
// be accessible in this iteration, as all children are subframes.
if (parent_->child_at(i)->frame_tree_node_id() != GetFrameTreeNodeId()) {
continue;
}
if (relative_offset < 0 && base::checked_cast<size_t>(-relative_offset) > i)
return nullptr;
if (i + relative_offset >= parent_->child_count())
return nullptr;
return parent_->child_at(i + relative_offset);
}
NOTREACHED() << "FrameTreeNode not found in its parent's children.";
return nullptr;
}
RenderFrameHostImpl* RenderFrameHostImpl::GetMainFrame() {
return const_cast<RenderFrameHostImpl*>(base::as_const(*this).GetMainFrame());
}
const RenderFrameHostImpl* RenderFrameHostImpl::GetMainFrame() const {
// Iteration over the GetParent() chain is used below, because returning
// |frame_tree()->root()->current_frame_host()| might
// give an incorrect result after |this| has been detached from the frame
// tree.
const RenderFrameHostImpl* main_frame = this;
while (main_frame->GetParent())
main_frame = main_frame->GetParent();
return main_frame;
}
bool RenderFrameHostImpl::IsInPrimaryMainFrame() {
return !GetParent() && GetPage().IsPrimary();
}
RenderFrameHostImpl* RenderFrameHostImpl::GetOutermostMainFrame() {
RenderFrameHostImpl* current = this;
while (true) {
RenderFrameHostImpl* parent_or_outer_doc =
current->GetParentOrOuterDocument();
if (!parent_or_outer_doc)
return current;
current = parent_or_outer_doc;
};
}
bool RenderFrameHostImpl::CanAccessFilesOfPageState(
const blink::PageState& state) {
return ChildProcessSecurityPolicyImpl::GetInstance()->CanReadAllFiles(
GetProcess()->GetID(), state.GetReferencedFiles());
}
void RenderFrameHostImpl::GrantFileAccessFromPageState(
const blink::PageState& state) {
GrantFileAccess(GetProcess()->GetID(), state.GetReferencedFiles());
}
void RenderFrameHostImpl::SetHasPendingLifecycleStateUpdate() {
DCHECK(!has_pending_lifecycle_state_update_);
for (auto& child : children_)
child->current_frame_host()->SetHasPendingLifecycleStateUpdate();
has_pending_lifecycle_state_update_ = true;
}
void RenderFrameHostImpl::GrantFileAccessFromResourceRequestBody(
const network::ResourceRequestBody& body) {
GrantFileAccess(GetProcess()->GetID(), body.GetReferencedFiles());
}
void RenderFrameHostImpl::UpdatePermissionsForNavigation(
NavigationRequest* request) {
// Browser plugin guests are not allowed to navigate outside web-safe schemes,
// so do not grant them the ability to commit additional URLs.
if (!GetProcess()->IsForGuestsOnly()) {
ChildProcessSecurityPolicyImpl::GetInstance()->GrantCommitURL(
GetProcess()->GetID(), request->common_params().url);
if (request->IsLoadDataWithBaseURL()) {
// When there's a base URL specified for the data URL, we also need to
// grant access to the base URL. This allows file: and other unexpected
// schemes to be accepted at commit time and during CORS checks (e.g., for
// font requests).
ChildProcessSecurityPolicyImpl::GetInstance()->GrantCommitURL(
GetProcess()->GetID(),
request->common_params().base_url_for_data_url);
}
}
// We may be returning to an existing NavigationEntry that had been granted
// file access. If this is a different process, we will need to grant the
// access again. Abuse is prevented, because the files listed in the page
// state are validated earlier, when they are received from the renderer (in
// RenderFrameHostImpl::CanAccessFilesOfPageState).
blink::PageState page_state = blink::PageState::CreateFromEncodedData(
request->commit_params().page_state);
if (page_state.IsValid())
GrantFileAccessFromPageState(page_state);
// We may be here after transferring navigation to a different renderer
// process. In this case, we need to ensure that the new renderer retains
// ability to access files that the old renderer could access. Abuse is
// prevented, because the files listed in ResourceRequestBody are validated
// earlier, when they are received from the renderer.
if (request->common_params().post_data)
GrantFileAccessFromResourceRequestBody(*request->common_params().post_data);
}
bool RenderFrameHostImpl::WindowPlacementAllowsFullscreen() {
return IsWindowPlacementGranted(this) &&
delegate_->IsTransientAllowFullscreenActive();
}
mojo::AssociatedRemote<mojom::NavigationClient>
RenderFrameHostImpl::GetNavigationClientFromInterfaceProvider() {
mojo::AssociatedRemote<mojom::NavigationClient> navigation_client_remote;
GetRemoteAssociatedInterfaces()->GetInterface(&navigation_client_remote);
return navigation_client_remote;
}
void RenderFrameHostImpl::NavigationRequestCancelled(
NavigationRequest* navigation_request) {
// Remove the requests from the list of NavigationRequests waiting to commit.
// RenderDocument should obsolete the need for this, as always swapping RFHs
// means that it won't be necessary to clean up the list of navigation
// requests when the renderer aborts a navigation--instead, we'll always just
// throw away the entire speculative RFH.
navigation_requests_.erase(navigation_request);
}
NavigationRequest*
RenderFrameHostImpl::FindLatestNavigationRequestThatIsStillCommitting() {
// Find the most recent NavigationRequest that has triggered a Commit IPC to
// the renderer process. Once the renderer process handles the IPC, it may
// possibly change the origin from |last_committed_origin_| to another origin.
NavigationRequest* found_request = nullptr;
for (const auto& it : navigation_requests_) {
NavigationRequest* candidate = it.first;
DCHECK_EQ(candidate, it.second.get());
if (candidate->state() < NavigationRequest::READY_TO_COMMIT)
continue;
if (candidate->state() >= NavigationRequest::DID_COMMIT)
continue;
if (!found_request ||
found_request->NavigationStart() < candidate->NavigationStart()) {
found_request = candidate;
}
}
return found_request;
}
network::mojom::URLLoaderFactoryParamsPtr
RenderFrameHostImpl::CreateURLLoaderFactoryParamsForMainWorld(
const SubresourceLoaderFactoriesConfig& config,
base::StringPiece debug_tag) {
return URLLoaderFactoryParamsHelper::CreateForFrame(
this, config.origin(), config.isolation_info(),
config.GetClientSecurityState(), config.GetCoepReporter(), GetProcess(),
config.trust_token_redemption_policy(), debug_tag);
}
bool RenderFrameHostImpl::CreateNetworkServiceDefaultFactoryAndObserve(
network::mojom::URLLoaderFactoryParamsPtr params,
ukm::SourceIdObj ukm_source_id,
mojo::PendingReceiver<network::mojom::URLLoaderFactory>
default_factory_receiver) {
bool bypass_redirect_checks = CreateNetworkServiceDefaultFactoryInternal(
std::move(params), ukm_source_id, std::move(default_factory_receiver));
// Add a disconnect handler when Network Service is running
// out-of-process.
if (IsOutOfProcessNetworkService() &&
(!network_service_disconnect_handler_holder_ ||
!network_service_disconnect_handler_holder_.is_connected())) {
network_service_disconnect_handler_holder_.reset();
StoragePartition* storage_partition = GetStoragePartition();
network::mojom::URLLoaderFactoryParamsPtr monitoring_factory_params =
network::mojom::URLLoaderFactoryParams::New();
monitoring_factory_params->process_id = GetProcess()->GetID();
monitoring_factory_params->debug_tag = "RFHI - monitoring_factory_params";
// This factory should never be used to issue actual requests (i.e. it
// should only be used to monitor for Network Service crashes). Below is an
// attempt to enforce that the factory cannot be used in practice.
monitoring_factory_params->request_initiator_origin_lock =
url::Origin::Create(
GURL("https://monitoring.url.loader.factory.invalid"));
storage_partition->GetNetworkContext()->CreateURLLoaderFactory(
network_service_disconnect_handler_holder_.BindNewPipeAndPassReceiver(),
std::move(monitoring_factory_params));
network_service_disconnect_handler_holder_.set_disconnect_handler(
base::BindOnce(&RenderFrameHostImpl::UpdateSubresourceLoaderFactories,
weak_ptr_factory_.GetWeakPtr()));
}
return bypass_redirect_checks;
}
bool RenderFrameHostImpl::CreateNetworkServiceDefaultFactoryInternal(
network::mojom::URLLoaderFactoryParamsPtr params,
ukm::SourceIdObj ukm_source_id,
mojo::PendingReceiver<network::mojom::URLLoaderFactory>
default_factory_receiver) {
DCHECK(params->request_initiator_origin_lock.has_value());
const url::Origin& request_initiator =
params->request_initiator_origin_lock.value();
bool bypass_redirect_checks = false;
WillCreateURLLoaderFactory(
request_initiator, &default_factory_receiver, ukm_source_id,
&params->header_client, &bypass_redirect_checks,
&params->disable_secure_dns, &params->factory_override);
GetProcess()->CreateURLLoaderFactory(std::move(default_factory_receiver),
std::move(params));
return bypass_redirect_checks;
}
void RenderFrameHostImpl::WillCreateURLLoaderFactory(
const url::Origin& request_initiator,
mojo::PendingReceiver<network::mojom::URLLoaderFactory>* factory_receiver,
ukm::SourceIdObj ukm_source_id,
mojo::PendingRemote<network::mojom::TrustedURLLoaderHeaderClient>*
header_client,
bool* bypass_redirect_checks,
bool* disable_secure_dns,
network::mojom::URLLoaderFactoryOverridePtr* factory_override) {
GetContentClient()->browser()->WillCreateURLLoaderFactory(
GetBrowserContext(), this, GetProcess()->GetID(),
ContentBrowserClient::URLLoaderFactoryType::kDocumentSubResource,
request_initiator, /*navigation_id=*/absl::nullopt, ukm_source_id,
factory_receiver, header_client, bypass_redirect_checks,
disable_secure_dns, factory_override);
// Keep DevTools proxy last, i.e. closest to the network.
devtools_instrumentation::WillCreateURLLoaderFactory(
this, /*is_navigation=*/false, /*is_download=*/false, factory_receiver,
factory_override);
}
bool RenderFrameHostImpl::CanExecuteJavaScript() {
if (g_allow_injecting_javascript)
return true;
return !GetLastCommittedURL().is_valid() ||
GetLastCommittedURL().SchemeIs(kChromeDevToolsScheme) ||
ChildProcessSecurityPolicyImpl::GetInstance()->HasWebUIBindings(
GetProcess()->GetID()) ||
// It's possible to load about:blank in a Web UI renderer.
// See http://crbug.com/42547
(GetLastCommittedURL().spec() == url::kAboutBlankURL);
}
// static
int RenderFrameHost::GetFrameTreeNodeIdForRoutingId(int process_id,
int routing_id) {
auto frame_or_proxy = LookupRenderFrameHostOrProxy(process_id, routing_id);
if (frame_or_proxy)
return frame_or_proxy.GetFrameTreeNode()->frame_tree_node_id();
return kNoFrameTreeNodeId;
}
// static
int RenderFrameHost::GetFrameTreeNodeIdForFrameToken(
int process_id,
const ::blink::FrameToken& frame_token) {
auto frame_or_proxy = LookupRenderFrameHostOrProxy(process_id, frame_token);
if (frame_or_proxy)
return frame_or_proxy.GetFrameTreeNode()->frame_tree_node_id();
return kNoFrameTreeNodeId;
}
// static
RenderFrameHost* RenderFrameHost::FromPlaceholderToken(
int render_process_id,
const blink::RemoteFrameToken& placeholder_frame_token) {
RenderFrameProxyHost* rfph = RenderFrameProxyHost::FromFrameToken(
render_process_id, placeholder_frame_token);
FrameTreeNode* node = rfph ? rfph->frame_tree_node() : nullptr;
return node ? node->current_frame_host() : nullptr;
}
ui::AXTreeID RenderFrameHostImpl::GetParentAXTreeID() {
auto* parent = GetParentOrOuterDocumentOrEmbedder();
if (!parent) {
DCHECK(AccessibilityIsMainFrame())
<< "Child frame requires a parent, root=" << GetLastCommittedURL();
return ui::AXTreeIDUnknown();
}
// TODO(accessibility) The following check fails when running this test with
// --force-renderer-accessibility:
// http/tests/devtools/resource-tree/resource-tree-frame-in-crafted-frame.js
// It seems that fabricating a frame with document.write() results in a
// frame that has no embedding token.
// DCHECK(parent->GetAXTreeID() != ui::AXTreeIDUnknown())
// << "Parent frame must have an id, child url = " <<
// GetLastCommittedURL()
// << " parent url = " << parent->GetLastCommittedURL();
DCHECK(!AccessibilityIsMainFrame())
<< "Root frame must not have a parent, root=" << GetLastCommittedURL()
<< " parent=" << parent->GetLastCommittedURL();
return parent->GetAXTreeID();
}
ui::AXTreeID RenderFrameHostImpl::GetFocusedAXTreeID() {
// If this is not the root frame tree node, we're done.
if (!is_main_frame())
return ui::AXTreeIDUnknown();
RenderFrameHostImpl* focused_frame = delegate_->GetFocusedFrame();
if (focused_frame)
return focused_frame->GetAXTreeID();
// It's possible for there to be no focused RenderFrameHost (e.g. the frame
// that had focus was destroyed). Note however, that this doesn't mean that
// keyboard events are ignored; they'd be sent by default to the root
// RenderWidgetHost.
return ui::AXTreeIDUnknown();
}
ui::AXTreeData RenderFrameHostImpl::GetAXTreeData() {
// Make sure to update the locally stored |ax_tree_data_| to include
// references to the relevant AXTreeIDs before returning its value.
ax_tree_data_.tree_id = GetAXTreeID();
ax_tree_data_.parent_tree_id = GetParentAXTreeID();
ax_tree_data_.focused_tree_id = GetFocusedAXTreeID();
return ax_tree_data_;
}
void RenderFrameHostImpl::AccessibilityHitTestCallback(
int request_id,
ax::mojom::Event event_to_fire,
base::OnceCallback<void(BrowserAccessibilityManager* hit_manager,
int hit_node_id)> opt_callback,
blink::mojom::HitTestResponsePtr hit_test_response) {
if (!hit_test_response) {
if (opt_callback)
std::move(opt_callback).Run(nullptr, 0);
return;
}
auto frame_or_proxy = LookupRenderFrameHostOrProxy(
GetProcess()->GetID(), hit_test_response->hit_frame_token);
RenderFrameHostImpl* hit_frame = frame_or_proxy.GetCurrentFrameHost();
if (!hit_frame || hit_frame->IsInactiveAndDisallowActivation(
DisallowActivationReasonId::kAXHitTestCallback)) {
if (opt_callback)
std::move(opt_callback).Run(nullptr, 0);
return;
}
// If the hit node's routing ID is the same frame, we're done. If a
// callback was provided, call it with the information about the hit node.
if (hit_frame->GetFrameToken() == frame_token_) {
if (opt_callback) {
std::move(opt_callback)
.Run(hit_frame->browser_accessibility_manager(),
hit_test_response->hit_node_id);
}
return;
}
// The hit node has a child frame. Do a hit test in that frame's renderer.
hit_frame->AccessibilityHitTest(
hit_test_response->hit_frame_transformed_point, event_to_fire, request_id,
std::move(opt_callback));
}
void RenderFrameHostImpl::RequestAXTreeSnapshotCallback(
AXTreeSnapshotCallback callback,
const ui::AXTreeUpdate& snapshot) {
// Since |snapshot| is const, we need to make a copy in order to modify the
// tree data.
ui::AXTreeUpdate dst_snapshot;
CopyAXTreeUpdate(snapshot, &dst_snapshot);
std::move(callback).Run(dst_snapshot);
}
void RenderFrameHostImpl::RequestDistilledAXTreeCallback(
AXTreeDistillerCallback callback,
const ui::AXTreeUpdate& snapshot,
const std::vector<ui::AXNodeID>& content_node_ids) {
// Since |snapshot| is const, we need to make a copy in order to modify the
// tree data.
ui::AXTreeUpdate dst_snapshot;
CopyAXTreeUpdate(snapshot, &dst_snapshot);
std::move(callback).Run(dst_snapshot, content_node_ids);
}
void RenderFrameHostImpl::CopyAXTreeUpdate(const ui::AXTreeUpdate& snapshot,
ui::AXTreeUpdate* snapshot_copy) {
snapshot_copy->root_id = snapshot.root_id;
snapshot_copy->nodes.resize(snapshot.nodes.size());
for (size_t i = 0; i < snapshot.nodes.size(); ++i)
snapshot_copy->nodes[i] = snapshot.nodes[i];
if (snapshot.has_tree_data) {
ax_tree_data_ = snapshot.tree_data;
// Set the AXTreeData to be the last |ax_tree_data_| received from the
// render frame.
snapshot_copy->tree_data = GetAXTreeData();
snapshot_copy->has_tree_data = true;
}
}
void RenderFrameHostImpl::CreatePaymentManager(
mojo::PendingReceiver<payments::mojom::PaymentManager> receiver) {
if (!IsFeatureEnabled(blink::mojom::PermissionsPolicyFeature::kPayment)) {
mojo::ReportBadMessage("Permissions policy blocks Payment");
return;
}
GetProcess()->CreatePaymentManagerForOrigin(GetLastCommittedOrigin(),
std::move(receiver));
// Blocklist PaymentManager from the back-forward cache as at the moment we
// don't cancel pending payment requests when the RenderFrameHost is stored
// in back-forward cache.
OnBackForwardCacheDisablingStickyFeatureUsed(
BackForwardCacheDisablingFeature::kPaymentManager);
}
WebBluetoothServiceImpl*
RenderFrameHostImpl::GetWebBluetoothServiceForTesting() {
if (!document_associated_data_ || !last_web_bluetooth_service_for_testing_)
return nullptr;
return last_web_bluetooth_service_for_testing_;
}
void RenderFrameHostImpl::CreateWebBluetoothService(
mojo::PendingReceiver<blink::mojom::WebBluetoothService> receiver) {
BackForwardCache::DisableForRenderFrameHost(
this, BackForwardCacheDisable::DisabledReason(
BackForwardCacheDisable::DisabledReasonId::kWebBluetooth));
// Although the returned pointer is being stored for test support, this is not
// a test-only function, and the call of WebBluetoothServiceImpl::Create below
// is how the WebBluetoothServiceImpl instance gets created.
last_web_bluetooth_service_for_testing_ =
WebBluetoothServiceImpl::Create(this, std::move(receiver));
}
void RenderFrameHostImpl::CreateWebUsbService(
mojo::PendingReceiver<blink::mojom::WebUsbService> receiver) {
BackForwardCache::DisableForRenderFrameHost(
this, BackForwardCacheDisable::DisabledReason(
BackForwardCacheDisable::DisabledReasonId::kWebUSB));
GetContentClient()->browser()->CreateWebUsbService(this, std::move(receiver));
}
void RenderFrameHostImpl::ResetPermissionsPolicy() {
if (IsNestedWithinFencedFrame()) {
// In Fenced Frames, all permission policy gated features must be disabled
// for privacy reasons.
permissions_policy_ =
blink::PermissionsPolicy::CreateForFencedFrame(last_committed_origin_);
return;
}
RenderFrameHostImpl* parent_frame_host = GetParent();
auto isolation_info = GetSiteInstance()->GetWebExposedIsolationInfo();
if (!parent_frame_host && isolation_info.is_isolated_application()) {
// In Isolated Apps, the top level frame should use the policy declared in
// the Web App Manifest.
blink::ParsedPermissionsPolicy manifest_policy =
GetContentClient()->browser()->GetPermissionsPolicyForIsolatedApp(
GetBrowserContext(), isolation_info.origin());
permissions_policy_ = blink::PermissionsPolicy::CreateFromParsedPolicy(
manifest_policy, last_committed_origin_);
return;
}
const blink::PermissionsPolicy* parent_policy =
parent_frame_host ? parent_frame_host->permissions_policy() : nullptr;
blink::ParsedPermissionsPolicy container_policy =
browsing_context_state_->effective_frame_policy().container_policy;
permissions_policy_ = blink::PermissionsPolicy::CreateFromParentPolicy(
parent_policy, container_policy, last_committed_origin_);
}
void RenderFrameHostImpl::CreateAudioInputStreamFactory(
mojo::PendingReceiver<blink::mojom::RendererAudioInputStreamFactory>
receiver) {
BrowserMainLoop* browser_main_loop = BrowserMainLoop::GetInstance();
DCHECK(browser_main_loop);
MediaStreamManager* msm = browser_main_loop->media_stream_manager();
audio_service_audio_input_stream_factory_.emplace(std::move(receiver), msm,
this);
}
void RenderFrameHostImpl::CreateAudioOutputStreamFactory(
mojo::PendingReceiver<blink::mojom::RendererAudioOutputStreamFactory>
receiver) {
media::AudioSystem* audio_system =
BrowserMainLoop::GetInstance()->audio_system();
MediaStreamManager* media_stream_manager =
BrowserMainLoop::GetInstance()->media_stream_manager();
// This message can be received before navigation commit, because the renderer
// requests this when initializing the main frame of RenderView which happens
// before commit. Use FrameTree::is_prerendering() rather than
// lifecycle_state() because prerendering lifecycle state only is set after
// navigation commit.
bool restricted_mode = frame_tree()->is_prerendering();
if (restricted_mode) {
audio_service_audio_output_stream_factory_.emplace(
this, audio_system, media_stream_manager, std::move(receiver),
base::BindOnce(
base::IgnoreResult(&RenderFrameHostImpl::CancelPrerendering),
base::Unretained(this),
PrerenderHost::FinalStatus::kAudioOutputDeviceRequested));
} else {
audio_service_audio_output_stream_factory_.emplace(
this, audio_system, media_stream_manager, std::move(receiver),
/*restricted_callback=*/absl::nullopt);
}
}
void RenderFrameHostImpl::GetFeatureObserver(
mojo::PendingReceiver<blink::mojom::FeatureObserver> receiver) {
if (!feature_observer_) {
// Lazy initialize because tests sets the overridden content client
// after the RFHI constructor.
auto* client = GetContentClient()->browser()->GetFeatureObserverClient();
if (!client)
return;
feature_observer_ = std::make_unique<FeatureObserver>(
client, GlobalRenderFrameHostId(GetProcess()->GetID(), routing_id_));
}
feature_observer_->GetFeatureObserver(std::move(receiver));
}
void RenderFrameHostImpl::BindRenderAccessibilityHost(
mojo::PendingReceiver<blink::mojom::RenderAccessibilityHost> receiver) {
// There must be an accessibility token as
// RenderAccessibilityImpl::ScheduleSendPendingAccessibilityEvents will only
// attempt to send updates once it has created one, which happens as part of
// the commit which in turns updates the browser's token before this method
// could be called.
DCHECK(GetAXTreeID().token());
// `render_accessibility_host_` is reset in `TearDownMojoConnection()`, but
// this Mojo endpoint lives on another sequence and posts tasks back to this
// `RenderFrameHostImpl` on the UI thread. After the reset, there may still be
// tasks in flight: use `render_frame_scoped_weak_ptr_factory_` to ensure
// those tasks are dropped if they arrive after the reset of their
// corresponding RenderAccessibilityHost.
ui::AXTreeID ax_tree_id = GetAXTreeID();
if (!render_accessibility_host_ ||
ax_tree_id != render_accessibility_host_ax_tree_id_) {
render_accessibility_host_ = base::SequenceBound<RenderAccessibilityHost>(
base::FeatureList::IsEnabled(
features::kRenderAccessibilityHostDeserializationOffMainThread)
? base::ThreadPool::CreateSequencedTaskRunner({})
: base::SequencedTaskRunnerHandle::Get(),
render_frame_scoped_weak_ptr_factory_.GetWeakPtr(), ax_tree_id);
}
render_accessibility_host_ax_tree_id_ = ax_tree_id;
render_accessibility_host_.AsyncCall(&RenderAccessibilityHost::Bind)
.WithArgs(std::move(receiver));
}
bool RenderFrameHostImpl::CancelPrerendering(
PrerenderHost::FinalStatus status) {
if (!blink::features::IsPrerender2Enabled())
return false;
// A prerendered page is identified by its root FrameTreeNode id, so if this
// RenderFrameHost is in any way embedded, we need to iterate up to the
// prerender root.
FrameTreeNode* outermost_frame =
GetOutermostMainFrameOrEmbedder()->frame_tree_node();
// We need to explicitly check that `outermost_frame` is in a prerendering
// frame tree before accessing `GetPrerenderHostRegistry()`. Non-prerendered
// frames may outlive the PrerenderHostRegistry during WebContents
// destruction.
if (outermost_frame->GetFrameType() != FrameType::kPrerenderMainFrame)
return false;
// TODO(https://crbug.com/1126305): Pass a FinalStatus to CancelPrerendering()
// method when MojoInterface control, or IsInactiveAndDisallowActivation are
// called.
return delegate_->GetPrerenderHostRegistry()->CancelHost(
outermost_frame->frame_tree_node_id(), status);
}
void RenderFrameHostImpl::CancelPrerenderingByMojoBinderPolicy(
const std::string& interface_name) {
// A prerendered page is identified by its root FrameTreeNode id, so if this
// RenderFrameHost is in any way embedded, we need to iterate up to the
// prerender root.
FrameTreeNode* outermost_frame =
GetOutermostMainFrameOrEmbedder()->frame_tree_node();
PrerenderHost* prerender_host =
delegate_->GetPrerenderHostRegistry()->FindNonReservedHostById(
outermost_frame->frame_tree_node_id());
if (!prerender_host)
return;
RecordPrerenderCancelledInterface(
interface_name, prerender_host->trigger_type(),
prerender_host->embedder_histogram_suffix());
bool canceled =
CancelPrerendering(PrerenderHost::FinalStatus::kMojoBinderPolicy);
// This function is called from MojoBinderPolicyApplier, which should only be
// active during prerendering. It would be an error to call this while not
// prerendering, as it could mean an interface request is never resolved for
// an active page.
DCHECK(canceled);
}
void RenderFrameHostImpl::RendererWillActivateForPrerendering() {
DCHECK(blink::features::IsPrerender2Enabled());
if (audio_service_audio_output_stream_factory_) {
audio_service_audio_output_stream_factory_->ReleaseRestriction();
}
// Loosen the policies of the Mojo capability control during dispatching the
// prerenderingchange event in Blink, because the page may start legitimately
// using controlled interfaces once prerenderingchange is dispatched. We
// cannot release policies at this point, i.e., we cannot run the deferred
// binders, because the Mojo message pipes are not channel-associated and we
// should ensure that ActivateForPrerendering() arrives on the renderer
// earlier than these deferred messages.
DCHECK(mojo_binder_policy_applier_)
<< "prerendering pages should have a policy applier";
mojo_binder_policy_applier_->PrepareToGrantAll();
}
void RenderFrameHostImpl::BindMediaInterfaceFactoryReceiver(
mojo::PendingReceiver<media::mojom::InterfaceFactory> receiver) {
MediaInterfaceProxy::GetOrCreateForCurrentDocument(this)->Bind(
std::move(receiver));
}
void RenderFrameHostImpl::BindMediaMetricsProviderReceiver(
mojo::PendingReceiver<media::mojom::MediaMetricsProvider> receiver) {
// Only save decode stats when BrowserContext provides a VideoPerfHistory.
// Off-the-record contexts will internally use an ephemeral history DB.
media::VideoDecodePerfHistory::SaveCallback save_stats_cb;
if (GetSiteInstance()->GetBrowserContext()->GetVideoDecodePerfHistory()) {
save_stats_cb = GetSiteInstance()
->GetBrowserContext()
->GetVideoDecodePerfHistory()
->GetSaveCallback();
}
auto is_shutting_down_cb = base::BindRepeating(
[]() { return GetContentClient()->browser()->IsShuttingDown(); });
media::MediaMetricsProvider::Create(
GetProcess()->GetBrowserContext()->IsOffTheRecord()
? media::MediaMetricsProvider::BrowsingMode::kIncognito
: media::MediaMetricsProvider::BrowsingMode::kNormal,
is_main_frame() ? media::MediaMetricsProvider::FrameStatus::kTopFrame
: media::MediaMetricsProvider::FrameStatus::kNotTopFrame,
GetPage().last_main_document_source_id(),
media::learning::FeatureValue(GetLastCommittedOrigin().host()),
std::move(save_stats_cb),
base::BindRepeating(
[](base::WeakPtr<RenderFrameHostImpl> frame)
-> media::learning::LearningSession* {
if (!base::FeatureList::IsEnabled(media::kMediaLearningFramework) ||
!frame) {
return nullptr;
}
return frame->GetProcess()
->GetBrowserContext()
->GetLearningSession();
},
weak_ptr_factory_.GetWeakPtr()),
base::BindRepeating(
&RenderFrameHostImpl::GetRecordAggregateWatchTimeCallback,
base::Unretained(this)),
std::move(is_shutting_down_cb), std::move(receiver));
}
#if BUILDFLAG(ENABLE_MEDIA_REMOTING)
void RenderFrameHostImpl::BindMediaRemoterFactoryReceiver(
mojo::PendingReceiver<media::mojom::RemoterFactory> receiver) {
mojo::MakeSelfOwnedReceiver(
std::make_unique<RemoterFactoryImpl>(GetProcess()->GetID(), routing_id_),
std::move(receiver));
}
#endif
void RenderFrameHostImpl::CreateWebSocketConnector(
mojo::PendingReceiver<blink::mojom::WebSocketConnector> receiver) {
mojo::MakeSelfOwnedReceiver(std::make_unique<WebSocketConnectorImpl>(
GetProcess()->GetID(), routing_id_,
last_committed_origin_, isolation_info_),
std::move(receiver));
}
void RenderFrameHostImpl::CreateWebTransportConnector(
mojo::PendingReceiver<blink::mojom::WebTransportConnector> receiver) {
mojo::MakeSelfOwnedReceiver(
std::make_unique<WebTransportConnectorImpl>(
GetProcess()->GetID(), weak_ptr_factory_.GetWeakPtr(),
last_committed_origin_, isolation_info_.network_isolation_key()),
std::move(receiver));
}
void RenderFrameHostImpl::CreateNotificationService(
mojo::PendingReceiver<blink::mojom::NotificationService> receiver) {
GetProcess()->CreateNotificationService(
GetRoutingID(), GetLastCommittedOrigin(), std::move(receiver));
}
void RenderFrameHostImpl::CreateInstalledAppProvider(
mojo::PendingReceiver<blink::mojom::InstalledAppProvider> receiver) {
InstalledAppProviderImpl::Create(*this, std::move(receiver));
}
void RenderFrameHostImpl::CreateCodeCacheHostWithIsolationKey(
mojo::PendingReceiver<blink::mojom::CodeCacheHost> receiver,
const net::NetworkIsolationKey& nik) {
// Create a new CodeCacheHostImpl and bind it to the given receiver.
code_cache_host_receivers_.Add(GetProcess()->GetID(), nik,
std::move(receiver),
GetCodeCacheHostReceiverHandler());
}
void RenderFrameHostImpl::CreateCodeCacheHost(
mojo::PendingReceiver<blink::mojom::CodeCacheHost> receiver) {
CreateCodeCacheHostWithIsolationKey(std::move(receiver),
GetNetworkIsolationKey());
}
void RenderFrameHostImpl::CreateDedicatedWorkerHostFactory(
mojo::PendingReceiver<blink::mojom::DedicatedWorkerHostFactory> receiver) {
// Allocate the worker in the same process as the creator.
int worker_process_id = GetProcess()->GetID();
base::WeakPtr<CrossOriginEmbedderPolicyReporter> coep_reporter;
if (coep_reporter_) {
coep_reporter = coep_reporter_->GetWeakPtr();
}
// When a dedicated worker is created from the frame script, the frame is both
// the creator and the ancestor.
mojo::MakeSelfOwnedReceiver(
std::make_unique<DedicatedWorkerHostFactoryImpl>(
worker_process_id,
/*creator_render_frame_host_id=*/GetGlobalId(),
/*creator_worker_token=*/absl::nullopt,
/*ancestor_render_frame_host_id=*/GetGlobalId(), storage_key(),
isolation_info_, BuildClientSecurityState(),
/*creator_coep_reporter=*/coep_reporter,
/*ancestor_coep_reporter=*/coep_reporter),
std::move(receiver));
}
#if BUILDFLAG(IS_ANDROID)
void RenderFrameHostImpl::BindNFCReceiver(
mojo::PendingReceiver<device::mojom::NFC> receiver) {
delegate_->GetNFC(this, std::move(receiver));
}
#endif
#if !BUILDFLAG(IS_ANDROID)
void RenderFrameHostImpl::BindSerialService(
mojo::PendingReceiver<blink::mojom::SerialService> receiver) {
if (!IsFeatureEnabled(blink::mojom::PermissionsPolicyFeature::kSerial)) {
mojo::ReportBadMessage("Permissions policy blocks access to Serial.");
return;
}
// Powerful features like Serial API for FencedFrames are blocked by
// PermissionsPolicy. But as the interface is still exposed to the renderer,
// still good to have a secondary check per-API basis to handle compromised
// renderers. Ignore the request and mark it as bad to kill the initiating
// renderer if it happened for some reason.
if (IsNestedWithinFencedFrame()) {
mojo::ReportBadMessage("Web Serial is not allowed in fences frames.");
return;
}
SerialService::GetOrCreateForCurrentDocument(this)->Bind(std::move(receiver));
}
void RenderFrameHostImpl::GetHidService(
mojo::PendingReceiver<blink::mojom::HidService> receiver) {
HidService::Create(this, std::move(receiver));
}
#endif
IdleManagerImpl* RenderFrameHostImpl::GetIdleManager() {
return idle_manager_.get();
}
void RenderFrameHostImpl::BindIdleManager(
mojo::PendingReceiver<blink::mojom::IdleManager> receiver) {
if (!IsFeatureEnabled(
blink::mojom::PermissionsPolicyFeature::kIdleDetection)) {
mojo::ReportBadMessage(
"Permissions policy blocks access to IdleDetection.");
return;
}
idle_manager_->CreateService(std::move(receiver));
OnBackForwardCacheDisablingStickyFeatureUsed(
BackForwardCacheDisablingFeature::kIdleManager);
}
void RenderFrameHostImpl::GetPresentationService(
mojo::PendingReceiver<blink::mojom::PresentationService> receiver) {
if (!presentation_service_)
presentation_service_ = PresentationServiceImpl::Create(this);
presentation_service_->Bind(std::move(receiver));
}
PresentationServiceImpl&
RenderFrameHostImpl::GetPresentationServiceForTesting() {
DCHECK(presentation_service_);
return *presentation_service_.get();
}
void RenderFrameHostImpl::GetSpeechSynthesis(
mojo::PendingReceiver<blink::mojom::SpeechSynthesis> receiver) {
if (!speech_synthesis_impl_) {
speech_synthesis_impl_ = std::make_unique<SpeechSynthesisImpl>(
GetProcess()->GetBrowserContext(), this);
}
speech_synthesis_impl_->AddReceiver(std::move(receiver));
// Blocklist SpeechSynthesis for BackForwardCache, because currently we do not
// handle speech synthesis after placing the page in BackForwardCache.
// TODO(sreejakshetty): Make SpeechSynthesis compatible with BackForwardCache.
OnBackForwardCacheDisablingFeatureUsed(
BackForwardCacheDisablingFeature::kSpeechSynthesis);
}
void RenderFrameHostImpl::GetSensorProvider(
mojo::PendingReceiver<device::mojom::SensorProvider> receiver) {
SensorProviderProxyImpl::GetOrCreateForCurrentDocument(this)->Bind(
std::move(receiver));
}
void RenderFrameHostImpl::BindCacheStorage(
mojo::PendingReceiver<blink::mojom::CacheStorage> receiver) {
mojo::PendingRemote<network::mojom::CrossOriginEmbedderPolicyReporter>
coep_reporter_remote;
if (coep_reporter_) {
coep_reporter_->Clone(
coep_reporter_remote.InitWithNewPipeAndPassReceiver());
}
GetProcess()->BindCacheStorage(cross_origin_embedder_policy(),
std::move(coep_reporter_remote), storage_key(),
std::move(receiver));
}
void RenderFrameHostImpl::BindInputInjectorReceiver(
mojo::PendingReceiver<mojom::InputInjector> receiver) {
InputInjectorImpl::Create(weak_ptr_factory_.GetWeakPtr(),
std::move(receiver));
}
void RenderFrameHostImpl::BindWebOTPServiceReceiver(
mojo::PendingReceiver<blink::mojom::WebOTPService> receiver) {
auto* fetcher = SmsFetcher::Get(GetProcess()->GetBrowserContext());
if (WebOTPService::Create(fetcher, this, std::move(receiver)))
document_used_web_otp_ = true;
}
void RenderFrameHostImpl::BindFederatedAuthRequestReceiver(
mojo::PendingReceiver<blink::mojom::FederatedAuthRequest> receiver) {
FederatedAuthRequestImpl::Create(this, std::move(receiver));
}
void RenderFrameHostImpl::BindRestrictedCookieManager(
mojo::PendingReceiver<network::mojom::RestrictedCookieManager> receiver) {
BindRestrictedCookieManagerWithOrigin(std::move(receiver),
GetIsolationInfoForSubresources(),
GetLastCommittedOrigin());
}
void RenderFrameHostImpl::BindRestrictedCookieManagerWithOrigin(
mojo::PendingReceiver<network::mojom::RestrictedCookieManager> receiver,
const net::IsolationInfo& isolation_info,
const url::Origin& origin) {
GetStoragePartition()->CreateRestrictedCookieManager(
network::mojom::RestrictedCookieManagerRole::SCRIPT, origin,
isolation_info,
/*is_service_worker=*/false, GetProcess()->GetID(), GetRoutingID(),
std::move(receiver), CreateCookieAccessObserver());
}
void RenderFrameHostImpl::BindTrustTokenQueryAnswerer(
mojo::PendingReceiver<network::mojom::TrustTokenQueryAnswerer> receiver) {
auto top_frame_origin = ComputeTopFrameOrigin(GetLastCommittedOrigin());
// A check at the callsite in the renderer ensures a correctly functioning
// renderer will only request this Mojo handle if the top-frame origin is
// potentially trustworthy and has scheme HTTP or HTTPS.
if ((top_frame_origin.scheme() != url::kHttpScheme &&
top_frame_origin.scheme() != url::kHttpsScheme) ||
!network::IsOriginPotentiallyTrustworthy(top_frame_origin)) {
mojo::ReportBadMessage(
"Attempted to get a TrustTokenQueryAnswerer for a non-trustworthy or "
"non-HTTP/HTTPS top-frame origin.");
return;
}
// This is enforced in benign renderers by the RuntimeEnabled=TrustTokens IDL
// attribute (the base::Feature's value is tied to the
// RuntimeEnabledFeature's).
if (!base::FeatureList::IsEnabled(network::features::kTrustTokens)) {
mojo::ReportBadMessage(
"Attempted to get a TrustTokenQueryAnswerer with Trust Tokens "
"disabled.");
return;
}
// TODO(crbug.com/1145346): Document.hasTrustToken is restricted to secure
// contexts, so we could additionally add a check verifying that the bind
// request "is coming from a secure context"---but there's currently no
// direct way to perform such a check in the browser.
GetProcess()->GetStoragePartition()->CreateTrustTokenQueryAnswerer(
std::move(receiver), ComputeTopFrameOrigin(GetLastCommittedOrigin()));
}
void RenderFrameHostImpl::GetAudioContextManager(
mojo::PendingReceiver<blink::mojom::AudioContextManager> receiver) {
AudioContextManagerImpl::Create(this, std::move(receiver));
}
void RenderFrameHostImpl::GetFileSystemManager(
mojo::PendingReceiver<blink::mojom::FileSystemManager> receiver) {
// This is safe because file_system_manager_ is deleted on the IO thread
GetIOThreadTaskRunner({})->PostTask(
FROM_HERE, base::BindOnce(&FileSystemManagerImpl::BindReceiver,
base::Unretained(file_system_manager_.get()),
storage_key(), std::move(receiver)));
}
void RenderFrameHostImpl::GetGeolocationService(
mojo::PendingReceiver<blink::mojom::GeolocationService> receiver) {
if (!geolocation_service_) {
auto* geolocation_context = delegate_->GetGeolocationContext();
if (!geolocation_context)
return;
geolocation_service_ =
std::make_unique<GeolocationServiceImpl>(geolocation_context, this);
}
geolocation_service_->Bind(std::move(receiver));
}
void RenderFrameHostImpl::GetPendingBeaconHost(
mojo::PendingReceiver<blink::mojom::PendingBeaconHost> receiver) {
PendingBeaconHost::CreateForCurrentDocument(
this, GetStoragePartition()->GetURLLoaderFactoryForBrowserProcess(),
PendingBeaconService::GetInstance());
PendingBeaconHost* pbh = PendingBeaconHost::GetForCurrentDocument(this);
pbh->SetReceiver(std::move(receiver));
}
void RenderFrameHostImpl::GetDeviceInfoService(
mojo::PendingReceiver<blink::mojom::DeviceAPIService> receiver) {
GetContentClient()->browser()->CreateDeviceInfoService(this,
std::move(receiver));
}
void RenderFrameHostImpl::GetManagedConfigurationService(
mojo::PendingReceiver<blink::mojom::ManagedConfigurationService> receiver) {
GetContentClient()->browser()->CreateManagedConfigurationService(
this, std::move(receiver));
}
void RenderFrameHostImpl::GetFontAccessManager(
mojo::PendingReceiver<blink::mojom::FontAccessManager> receiver) {
GetStoragePartition()->GetFontAccessManager()->BindReceiver(
GetGlobalId(), std::move(receiver));
}
void RenderFrameHostImpl::GetFileSystemAccessManager(
mojo::PendingReceiver<blink::mojom::FileSystemAccessManager> receiver) {
DCHECK_CURRENTLY_ON(BrowserThread::UI);
auto* manager = GetStoragePartition()->GetFileSystemAccessManager();
manager->BindReceiver(
FileSystemAccessManagerImpl::BindingContext(
storage_key(), GetLastCommittedURL(), GetGlobalId()),
std::move(receiver));
}
void RenderFrameHostImpl::CreateLockManager(
mojo::PendingReceiver<blink::mojom::LockManager> receiver) {
GetProcess()->CreateLockManager(storage_key(), std::move(receiver));
}
void RenderFrameHostImpl::CreateIDBFactory(
mojo::PendingReceiver<blink::mojom::IDBFactory> receiver) {
GetProcess()->BindIndexedDB(storage_key(), std::move(receiver));
}
void RenderFrameHostImpl::CreateBucketManagerHost(
mojo::PendingReceiver<blink::mojom::BucketManagerHost> receiver) {
GetProcess()->BindBucketManagerHostForRenderFrame(GetGlobalId(),
std::move(receiver));
}
void RenderFrameHostImpl::CreatePermissionService(
mojo::PendingReceiver<blink::mojom::PermissionService> receiver) {
PermissionServiceContext::GetForCurrentDocument(this)->CreateService(
std::move(receiver));
}
void RenderFrameHostImpl::GetWebAuthenticationService(
mojo::PendingReceiver<blink::mojom::Authenticator> receiver) {
#if !BUILDFLAG(IS_ANDROID)
AuthenticatorImpl::Create(this, std::move(receiver));
#else
GetJavaInterfaces()->GetInterface(std::move(receiver));
#endif // !BUILDFLAG(IS_ANDROID)
}
void RenderFrameHostImpl::GetPushMessaging(
mojo::PendingReceiver<blink::mojom::PushMessaging> receiver) {
if (!push_messaging_manager_) {
auto* rph = GetProcess();
push_messaging_manager_ = std::make_unique<PushMessagingManager>(
*rph, routing_id_,
base::WrapRefCounted(GetStoragePartition()->GetServiceWorkerContext()));
}
push_messaging_manager_->AddPushMessagingReceiver(std::move(receiver));
}
void RenderFrameHostImpl::GetVirtualAuthenticatorManager(
mojo::PendingReceiver<blink::test::mojom::VirtualAuthenticatorManager>
receiver) {
#if !BUILDFLAG(IS_ANDROID)
if (base::CommandLine::ForCurrentProcess()->HasSwitch(
switches::kEnableWebAuthDeprecatedMojoTestingApi)) {
auto* environment_singleton = AuthenticatorEnvironmentImpl::GetInstance();
environment_singleton->EnableVirtualAuthenticatorFor(frame_tree_node_,
/*enable_ui=*/false);
environment_singleton->AddVirtualAuthenticatorReceiver(frame_tree_node_,
std::move(receiver));
}
#endif // !BUILDFLAG(IS_ANDROID)
}
bool IsInitialSynchronousAboutBlankCommit(const GURL& url,
bool is_on_initial_empty_document) {
return url.SchemeIs(url::kAboutScheme) && url != GURL(url::kAboutSrcdocURL) &&
is_on_initial_empty_document;
}
std::unique_ptr<NavigationRequest>
RenderFrameHostImpl::CreateNavigationRequestForSynchronousRendererCommit(
const GURL& url,
const url::Origin& origin,
blink::mojom::ReferrerPtr referrer,
const ui::PageTransition& transition,
bool should_replace_current_entry,
bool has_user_gesture,
const std::vector<GURL>& redirects,
const GURL& original_request_url,
bool is_same_document,
bool is_same_document_history_api_navigation) {
// This function must only be called when there are no NavigationRequests for
// a navigation can be found at DidCommit time, which can only happen in two
// cases:
// 1) This was a synchronous renderer-initiated navigation to about:blank
// after the initial empty document.
// 2) This was a renderer-initiated same-document navigation.
DCHECK(IsInitialSynchronousAboutBlankCommit(
url, frame_tree_node_->is_on_initial_empty_document()) ||
is_same_document);
DCHECK(!is_same_document_history_api_navigation || is_same_document);
net::IsolationInfo isolation_info = ComputeIsolationInfoInternal(
origin, net::IsolationInfo::RequestType::kOther, IsAnonymous());
std::unique_ptr<CrossOriginEmbedderPolicyReporter> coep_reporter;
// We don't switch the COEP reporter on same-document navigations, so create
// one only for cross-document navigations.
if (!is_same_document) {
auto* storage_partition =
static_cast<StoragePartitionImpl*>(GetProcess()->GetStoragePartition());
coep_reporter = std::make_unique<CrossOriginEmbedderPolicyReporter>(
storage_partition->GetWeakPtr(), url,
cross_origin_embedder_policy().reporting_endpoint,
cross_origin_embedder_policy().report_only_reporting_endpoint,
GetReportingSource(), isolation_info.network_isolation_key());
}
std::unique_ptr<WebBundleNavigationInfo> web_bundle_navigation_info;
if (is_same_document && web_bundle_handle_ &&
web_bundle_handle_->navigation_info()) {
// Need to set |web_bundle_navigation_info| of NavigationRequest. This
// will be passed to FrameNavigationEntry, and will be used for subsequent
// history navigations.
web_bundle_navigation_info = web_bundle_handle_->navigation_info()->Clone();
}
std::unique_ptr<SubresourceWebBundleNavigationInfo>
subresource_web_bundle_navigation_info;
if (is_same_document && subresource_web_bundle_navigation_info_) {
// Propagate |subresource_web_bundle_navigation_info_| to NavigationRequest
// for same-document navigation. This will be passed to
// FrameNavigationEntry, and will be used for subsequent history
// navigations.
subresource_web_bundle_navigation_info =
subresource_web_bundle_navigation_info_->Clone();
}
std::string method = "GET";
if (is_same_document && !is_same_document_history_api_navigation) {
// Preserve the HTTP method used by the last navigation if this is a
// same-document navigation that is not triggered by the history API
// (history.replaceState/pushState). See spec:
// https://html.spec.whatwg.org/multipage/history.html#url-and-history-update-steps
method = last_http_method_;
}
// HTTP status code:
// - For same-document navigations, we should retain the HTTP status code from
// the last committed navigation.
// - For initial about:blank navigation, the HTTP status code is 0.
int http_status_code = is_same_document ? last_http_status_code_ : 0;
// Same-document navigation should retain is_overriding_user_agent from the
// last committed navigation.
bool is_overriding_user_agent = is_same_document && is_overriding_user_agent_;
return NavigationRequest::CreateForSynchronousRendererCommit(
frame_tree_node_, this, is_same_document, url, origin, isolation_info,
std::move(referrer), transition, should_replace_current_entry, method,
has_user_gesture, is_overriding_user_agent, redirects,
original_request_url, std::move(coep_reporter),
std::move(web_bundle_navigation_info),
std::move(subresource_web_bundle_navigation_info), http_status_code);
}
void RenderFrameHostImpl::BeforeUnloadTimeout() {
if (render_view_host_->GetDelegate()->ShouldIgnoreUnresponsiveRenderer())
return;
SimulateBeforeUnloadCompleted(/*proceed=*/true);
}
void RenderFrameHostImpl::SetLastCommittedSiteInfo(const UrlInfo& url_info) {
BrowserContext* browser_context = GetSiteInstance()->GetBrowserContext();
SiteInfo site_info =
url_info.url.is_empty()
? SiteInfo(browser_context)
: SiteInfo::Create(GetSiteInstance()->GetIsolationContext(),
url_info);
if (last_committed_site_info_ == site_info)
return;
if (!last_committed_site_info_.site_url().is_empty()) {
RenderProcessHostImpl::RemoveFrameWithSite(browser_context, GetProcess(),
last_committed_site_info_);
}
last_committed_site_info_ = site_info;
if (!last_committed_site_info_.site_url().is_empty()) {
RenderProcessHostImpl::AddFrameWithSite(browser_context, GetProcess(),
last_committed_site_info_);
}
}
#if BUILDFLAG(IS_ANDROID)
base::android::ScopedJavaLocalRef<jobject>
RenderFrameHostImpl::GetJavaRenderFrameHost() {
RenderFrameHostAndroid* render_frame_host_android =
static_cast<RenderFrameHostAndroid*>(
GetUserData(kRenderFrameHostAndroidKey));
if (!render_frame_host_android) {
render_frame_host_android = new RenderFrameHostAndroid(this);
SetUserData(kRenderFrameHostAndroidKey,
base::WrapUnique(render_frame_host_android));
}
return render_frame_host_android->GetJavaObject();
}
service_manager::InterfaceProvider* RenderFrameHostImpl::GetJavaInterfaces() {
if (!java_interfaces_) {
mojo::PendingRemote<service_manager::mojom::InterfaceProvider> provider;
BindInterfaceRegistryForRenderFrameHost(
provider.InitWithNewPipeAndPassReceiver(), this);
java_interfaces_ = std::make_unique<service_manager::InterfaceProvider>(
base::ThreadTaskRunnerHandle::Get());
java_interfaces_->Bind(std::move(provider));
}
return java_interfaces_.get();
}
#endif
void RenderFrameHostImpl::ForEachImmediateLocalRoot(
const base::RepeatingCallback<void(RenderFrameHostImpl*)>& callback) {
ForEachRenderFrameHost(base::BindRepeating(
[](const base::RepeatingCallback<void(RenderFrameHostImpl*)>& callback,
const RenderFrameHostImpl* starting_rfh, RenderFrameHostImpl* rfh) {
if (rfh->is_local_root() && rfh != starting_rfh) {
callback.Run(rfh);
return FrameIterationAction::kSkipChildren;
}
return FrameIterationAction::kContinue;
},
callback, this));
}
void RenderFrameHostImpl::SetVisibilityForChildViews(bool visible) {
ForEachImmediateLocalRoot(base::BindRepeating(
[](bool is_visible, RenderFrameHostImpl* frame_host) {
if (auto* view = frame_host->GetView())
return is_visible ? view->Show() : view->Hide();
},
visible));
}
mojom::Frame* RenderFrameHostImpl::GetMojomFrameInRenderer() {
DCHECK(frame_);
return frame_.get();
}
bool RenderFrameHostImpl::ShouldBypassSecurityChecksForErrorPage(
NavigationRequest* navigation_request,
bool* should_commit_error_page) {
if (should_commit_error_page)
*should_commit_error_page = false;
if (frame_tree_node_->IsErrorPageIsolationEnabled()) {
if (GetSiteInstance()->GetSiteInfo().is_error_page()) {
if (should_commit_error_page)
*should_commit_error_page = true;
// With error page isolation, any URL can commit in an error page process.
return true;
}
} else {
// Without error page isolation, a blocked navigation is expected to
// commit in the old renderer process. This may be true for subframe
// navigations even when error page isolation is enabled for main frames.
if (navigation_request &&
net::IsRequestBlockedError(navigation_request->GetNetErrorCode())) {
return true;
}
}
return false;
}
void RenderFrameHostImpl::SetAudioOutputDeviceIdForGlobalMediaControls(
std::string hashed_device_id) {
audio_service_audio_output_stream_factory_
->SetAuthorizedDeviceIdForGlobalMediaControls(
std::move(hashed_device_id));
}
std::unique_ptr<mojo::MessageFilter>
RenderFrameHostImpl::CreateMessageFilterForAssociatedReceiver(
const char* interface_name) {
return CreateMessageFilterForAssociatedReceiverImpl(
this, interface_name,
BackForwardCacheImpl::GetChannelAssociatedMessageHandlingPolicy());
}
network::mojom::ClientSecurityStatePtr
RenderFrameHostImpl::BuildClientSecurityState() const {
// TODO(https://crbug.com/1184150) Remove this bandaid.
//
// Due to a race condition, CreateCrossOriginPrefetchLoaderFactoryBundle() is
// sometimes called on the previous document, before the new document is
// committed. In that case, it mistakenly builds a client security state
// based on the policies of the previous document. If no document has ever
// committed, there is no PolicyContainerHost to source policies from. To
// avoid crashes, this returns a maximally-restrictive value instead.
if (!policy_container_host_) {
// Prevent other code paths from depending on this bandaid.
DCHECK_EQ(lifecycle_state_, LifecycleStateImpl::kSpeculative);
// Omitted: reporting endpoint, report-only value and reporting endpoint.
network::CrossOriginEmbedderPolicy coep;
coep.value = network::mojom::CrossOriginEmbedderPolicyValue::kRequireCorp;
return network::mojom::ClientSecurityState::New(
std::move(coep),
/*is_web_secure_context=*/false,
network::mojom::IPAddressSpace::kUnknown,
network::mojom::PrivateNetworkRequestPolicy::kBlock);
}
auto client_security_state = network::mojom::ClientSecurityState::New();
const PolicyContainerPolicies& policies = policy_container_host_->policies();
client_security_state->is_web_secure_context = policies.is_web_secure_context;
client_security_state->ip_address_space = policies.ip_address_space;
client_security_state->private_network_request_policy =
private_network_request_policy_;
client_security_state->cross_origin_embedder_policy =
policies.cross_origin_embedder_policy;
return client_security_state;
}
bool RenderFrameHostImpl::IsNavigationSameSite(const UrlInfo& dest_url_info) {
if (!WebExposedIsolationInfo::AreCompatible(
GetSiteInstance()->GetWebExposedIsolationInfo(),
dest_url_info.web_exposed_isolation_info)) {
return false;
}
return GetSiteInstance()->IsNavigationSameSite(
last_successful_url(), GetLastCommittedOrigin(), is_main_frame(),
dest_url_info);
}
bool RenderFrameHostImpl::ValidateDidCommitParams(
NavigationRequest* navigation_request,
mojom::DidCommitProvisionalLoadParams* params,
bool is_same_document_navigation) {
DCHECK(params);
RenderProcessHost* process = GetProcess();
// Error pages may sometimes commit a URL in the wrong process, which requires
// an exception for the CanCommitOriginAndUrl() checks. This is ok as long
// as the origin is opaque.
bool should_commit_error_page = false;
bool bypass_checks_for_error_page = ShouldBypassSecurityChecksForErrorPage(
navigation_request, &should_commit_error_page);
// Commits in the error page process must only be failures, otherwise
// successful navigations could commit documents from origins different
// than the chrome-error://chromewebdata/ one and violate expectations.
if (should_commit_error_page &&
(navigation_request && !navigation_request->DidEncounterError())) {
DEBUG_ALIAS_FOR_ORIGIN(origin_debug_alias, params->origin);
bad_message::ReceivedBadMessage(
process, bad_message::RFH_ERROR_PROCESS_NON_ERROR_COMMIT);
return false;
}
// Error pages must commit in a opaque origin. Terminate the renderer
// process if this is violated.
if (bypass_checks_for_error_page && !params->origin.opaque()) {
DEBUG_ALIAS_FOR_ORIGIN(origin_debug_alias, params->origin);
bad_message::ReceivedBadMessage(
process, bad_message::RFH_ERROR_PROCESS_NON_UNIQUE_ORIGIN_COMMIT);
return false;
}
if (!bypass_checks_for_error_page &&
!ValidateURLAndOrigin(params->url, params->origin,
is_same_document_navigation, navigation_request)) {
return false;
}
// Without this check, an evil renderer can trick the browser into creating
// a navigation entry for a banned URL. If the user clicks the back button
// followed by the forward button (or clicks reload, or round-trips through
// session restore, etc), we'll think that the browser commanded the
// renderer to load the URL and grant the renderer the privileges to request
// the URL. To prevent this attack, we block the renderer from inserting
// banned URLs into the navigation controller in the first place.
process->FilterURL(false, &params->url);
process->FilterURL(true, &params->referrer->url);
// Without this check, the renderer can trick the browser into using
// filenames it can't access in a future session restore.
if (!CanAccessFilesOfPageState(params->page_state)) {
bad_message::ReceivedBadMessage(
process, bad_message::RFH_CAN_ACCESS_FILES_OF_PAGE_STATE);
return false;
}
// A cross-document navigation requires an embedding token. Navigations
// activating an existing document do not require new embedding tokens as the
// token is already set.
bool is_page_activation =
navigation_request && navigation_request->IsPageActivation();
DCHECK(!is_page_activation || embedding_token_.has_value());
if (!is_page_activation) {
if (!is_same_document_navigation && !params->embedding_token.has_value()) {
bad_message::ReceivedBadMessage(process,
bad_message::RFH_MISSING_EMBEDDING_TOKEN);
return false;
} else if (is_same_document_navigation &&
params->embedding_token.has_value()) {
bad_message::ReceivedBadMessage(
process, bad_message::RFH_UNEXPECTED_EMBEDDING_TOKEN);
return false;
}
}
// Note: document_policy_header is the document policy state used to
// initialize |document_policy_| in SecurityContext on renderer side. It is
// supposed to be compatible with required_document_policy. If not, kill the
// renderer.
if (!blink::DocumentPolicy::IsPolicyCompatible(
browsing_context_state_->effective_frame_policy()
.required_document_policy,
params->document_policy_header)) {
bad_message::ReceivedBadMessage(
GetProcess(), bad_message::RFH_BAD_DOCUMENT_POLICY_HEADER);
return false;
}
// If a frame claims the navigation was same-document, it must be the current
// frame, not a pending one.
base::WeakPtr<RenderFrameHostImpl> old_frame_host =
frame_tree_node_->render_manager()->current_frame_host()->GetWeakPtr();
if (is_same_document_navigation && this != old_frame_host.get()) {
bad_message::ReceivedBadMessage(this->GetProcess(),
bad_message::NI_IN_PAGE_NAVIGATION);
return false;
}
// Same document navigations should not be possible on post-commit error pages
// and would leave the NavigationController in a weird state. Kill the
// renderer before getting to NavigationController::RendererDidNavigate if
// that happens.
if (is_same_document_navigation && frame_tree_node_->navigator()
.controller()
.has_post_commit_error_entry()) {
bad_message::ReceivedBadMessage(
frame_tree_node_->render_manager()->current_frame_host()->GetProcess(),
bad_message::NC_SAME_DOCUMENT_POST_COMMIT_ERROR);
return false;
}
// Check(s) specific to sub-frame navigation.
if (navigation_request && !is_main_frame()) {
if (!CanSubframeCommitOriginAndUrl(navigation_request)) {
// Terminate the renderer if allowing this subframe navigation to commit
// would change the origin of the main frame.
bad_message::ReceivedBadMessage(
GetProcess(),
bad_message::RFHI_SUBFRAME_NAV_WOULD_CHANGE_MAINFRAME_ORIGIN);
return false;
}
}
return true;
}
bool RenderFrameHostImpl::ValidateURLAndOrigin(
const GURL& url,
const url::Origin& origin,
bool is_same_document_navigation,
NavigationRequest* navigation_request) {
// file: URLs can be allowed to access any other origin, based on settings.
if (origin.scheme() == url::kFileScheme) {
auto prefs = GetOrCreateWebPreferences();
if (prefs.allow_universal_access_from_file_urls)
return true;
}
// If the --disable-web-security flag is specified, all bets are off and the
// renderer process can send any origin it wishes.
if (base::CommandLine::ForCurrentProcess()->HasSwitch(
switches::kDisableWebSecurity)) {
return true;
}
// WebView's loadDataWithBaseURL API is allowed to bypass normal commit
// checks because it is allowed to commit anything into its unlocked process
// and its data: URL and non-opaque origin would fail the normal commit
// checks. We should also allow same-document navigations within pages loaded
// with loadDataWithBaseURL. Since renderer-initiated same-document
// navigations won't have a NavigationRequest at this point, we need to check
// |renderer_url_info_.was_loaded_from_load_data_with_base_url|.
DCHECK(navigation_request || is_same_document_navigation ||
frame_tree_node_->is_on_initial_empty_document());
RenderProcessHost* process = GetProcess();
if ((navigation_request && navigation_request->IsLoadDataWithBaseURL()) ||
(is_same_document_navigation &&
renderer_url_info_.was_loaded_from_load_data_with_base_url)) {
// Allow bypass if the process isn't locked. Otherwise run normal checks.
if (!process->GetProcessLock().is_locked_to_site())
return true;
}
// Use the value of `is_pdf` from `navigation_request` (if provided). This may
// be needed to verify the process lock in `CanCommitOriginAndUrl()`, but
// cannot be derived from the URL and origin alone.
bool is_pdf = navigation_request && navigation_request->GetUrlInfo().is_pdf;
bool is_sandboxed =
navigation_request && navigation_request->GetUrlInfo().is_sandboxed;
// Attempts to commit certain off-limits URL should be caught more strictly
// than our FilterURL checks. If a renderer violates this policy, it
// should be killed.
switch (CanCommitOriginAndUrl(origin, url, is_same_document_navigation,
is_pdf, is_sandboxed)) {
case CanCommitStatus::CAN_COMMIT_ORIGIN_AND_URL:
// The origin and URL are safe to commit.
break;
case CanCommitStatus::CANNOT_COMMIT_URL:
DLOG(ERROR) << "CANNOT_COMMIT_URL url '" << url << "'"
<< " origin '" << origin << "'"
<< " lock '" << process->GetProcessLock().ToString() << "'";
VLOG(1) << "Blocked URL " << url.spec();
LogCannotCommitUrlCrashKeys(url, is_same_document_navigation,
navigation_request);
// Kills the process.
bad_message::ReceivedBadMessage(process,
bad_message::RFH_CAN_COMMIT_URL_BLOCKED);
return false;
case CanCommitStatus::CANNOT_COMMIT_ORIGIN:
DLOG(ERROR) << "CANNOT_COMMIT_ORIGIN url '" << url << "'"
<< " origin '" << origin << "'"
<< " lock '" << process->GetProcessLock().ToString() << "'";
DEBUG_ALIAS_FOR_ORIGIN(origin_debug_alias, origin);
LogCannotCommitOriginCrashKeys(url, origin, process->GetProcessLock(),
is_same_document_navigation,
navigation_request);
// Kills the process.
bad_message::ReceivedBadMessage(
process, bad_message::RFH_INVALID_ORIGIN_ON_COMMIT);
return false;
}
return true;
}
// Simulates the calculation for DidCommitProvisionalLoadParams' `referrer`.
// This is written to preserve the behavior of the calculations that happened in
// the renderer before being moved to the browser. In the future, we might want
// to remove this function in favor of using NavigationRequest::GetReferrer()
// or CommonNavigationParam's `referrer` directly.
blink::mojom::ReferrerPtr GetReferrerForDidCommitParams(
NavigationRequest* request) {
if (request->DidEncounterError()) {
// Error pages always use the referrer from CommonNavigationParams, since
// it won't go through its server redirects in the renderer, and won't be
// marked as a client redirect.
// TODO(https://crbug.com/1218786): Maybe make this case just return the
// sanitized referrer below once the client redirect bug is fixed. This
// means GetReferrerForDidCommitParams(), NavigationRequest::GetReferrer()
// (`sanitized_referrer_`), and CommonNavigationParams's `referrer` will all
// return the same value, and GetReferrerForDidCommitParams() and
// `sanitized_referrer_` can be removed, leaving only
// CommonNavigationParams's `referrer`.
return request->common_params().referrer.Clone();
}
// Otherwise, return the sanitized referrer saved in the NavigationRequest.
// - For client redirects, this will be the the URL that initiated the
// navigation. (Note: this will only be sanitized at the start, and not after
// any redirects, including cross-origin ones. See https://crbug.com/1218786)
// - For other navigations, this will be the referrer used after the final
// redirect.
return request->GetReferrer().Clone();
}
// static
// This function logs metrics about potentially isolatable sandboxed iframes
// that are tracked through calls to UpdateIsolatableSandboxedIframeTracking().
// In addition to reporting the number of potential OOPSIFs, it also reports the
// number of unique origins encountered (to give insight into potential
// behavior if a per-origin isolation model was implemented), and it counts the
// actual number of RenderProcessHosts isolating OOPSIFs using the current
// per-site isolation model.
void RenderFrameHost::LogSandboxedIframesIsolationMetrics() {
RoutingIDIsolatableSandboxedIframesSet* oopsifs =
g_routing_id_isolatable_sandboxed_iframes_set.Pointer();
base::UmaHistogramCounts1000("SiteIsolation.IsolatableSandboxedIframes",
oopsifs->size());
// Count the number of unique origins across all the isolatable sandboxed
// iframes. This will give us a sense of the potential process overhead if we
// chose a per-origin process model for isolating these frames instead of the
// per-site model we plan to use. We use the precursor SchemeHostPort rather
// than the url::Origin, which is always opaque in these cases.
{
std::set<SiteInfo> sandboxed_site_infos;
std::set<url::SchemeHostPort> sandboxed_origins;
for (auto rfh_global_id : *oopsifs) {
auto* rfhi = RenderFrameHostImpl::FromID(rfh_global_id);
DCHECK(rfhi->GetLastCommittedOrigin().opaque());
sandboxed_origins.insert(
rfhi->GetLastCommittedOrigin().GetTupleOrPrecursorTupleIfOpaque());
sandboxed_site_infos.insert(rfhi->GetSiteInstance()->GetSiteInfo());
}
base::UmaHistogramCounts1000(
"SiteIsolation.IsolatableSandboxedIframes.UniqueOrigins",
sandboxed_origins.size());
base::UmaHistogramCounts1000(
"SiteIsolation.IsolatableSandboxedIframes.UniqueSites",
sandboxed_site_infos.size());
}
// Walk the set and count the number of unique RenderProcessHosts. Using a set
// allows us to accurately measure process overhead, including cases where
// SiteInstances from multiple BrowsingInstances are coalesced into a single
// RenderProcess.
std::set<RenderProcessHost*> sandboxed_rphs;
for (auto rfh_global_id : *oopsifs) {
auto* rfhi = FromID(rfh_global_id);
DCHECK(rfhi);
auto* site_instance =
static_cast<SiteInstanceImpl*>(rfhi->GetSiteInstance());
DCHECK(site_instance->HasProcess());
if (site_instance->GetSiteInfo().is_sandboxed())
sandboxed_rphs.insert(site_instance->GetProcess());
}
// There should be no sandboxed RPHs if the feature isn't enabled.
DCHECK(SiteIsolationPolicy::AreIsolatedSandboxedIframesEnabled() ||
sandboxed_rphs.size() == 0);
base::UmaHistogramCounts1000(
"Memory.RenderProcessHost.Count.SandboxedIframeOverhead",
sandboxed_rphs.size());
}
void RenderFrameHostImpl::UpdateIsolatableSandboxedIframeTracking() {
RoutingIDIsolatableSandboxedIframesSet* oopsifs =
g_routing_id_isolatable_sandboxed_iframes_set.Pointer();
GlobalRenderFrameHostId global_id = GetGlobalId();
// Check if the flags are correct.
DCHECK(policy_container_host_);
bool frame_is_isolatable =
IsSandboxed(network::mojom::WebSandboxFlags::kOrigin);
if (frame_is_isolatable) {
// Limit the "isolatable" sandboxed frames to those that are either in the
// same SiteInstance as their parent/opener (and thus could be isolated), or
// that are already isolated due to sandbox flags.
GURL url = GetLastCommittedURL();
if (url.IsAboutBlank() || url.is_empty()) {
frame_is_isolatable = false;
} else {
// Since this frame could be a main frame, we need to consider the
// SiteInstance of either the parent or opener (if either exists) of this
// frame, to see if the url can be placed in an OOPSIF, i.e. it's not
// already isolated because of being cross-site.
RenderFrameHost* frame_owner = GetParent();
if (!frame_owner && frame_tree_node_->opener())
frame_owner = frame_tree_node_->opener()->current_frame_host();
if (!frame_owner) {
frame_is_isolatable = false;
} else if (GetSiteInstance()->GetSiteInfo().is_sandboxed()) {
DCHECK(frame_is_isolatable);
} else if (frame_owner->GetSiteInstance() != GetSiteInstance()) {
// If this host's SiteInstance isn't already marked as is_sandboxed
// (with a frame owner), and yet the SiteInstance doesn't match that of
// our parent/opener, then it is already isolated for some other reason
// (cross-site, origin-keyed, etc.).
frame_is_isolatable = false;
}
}
}
if (frame_is_isolatable)
oopsifs->insert(global_id);
else
oopsifs->erase(global_id);
}
bool RenderFrameHostImpl::DidCommitNavigationInternal(
std::unique_ptr<NavigationRequest> navigation_request,
mojom::DidCommitProvisionalLoadParamsPtr params,
mojom::DidCommitSameDocumentNavigationParamsPtr same_document_params) {
const bool is_same_document_navigation = !!same_document_params;
// Sanity-check the page transition for frame type. Fenced Frames
// will set page transition to AUTO_SUBFRAME.
DCHECK_EQ(ui::PageTransitionIsMainFrame(params->transition),
!GetParent() && !IsFencedFrameRoot());
if (navigation_request &&
navigation_request->commit_params().navigation_token !=
params->navigation_token) {
// We should have the same navigation_token in CommitNavigationParams and
// DidCommit's |params| for all navigations, because:
// - Cross-document navigations use NavigationClient.
// - Same-document navigations will have a null |navigation_request|
// here if the navigation_token doesn't match (checked in
// DidCommitSameDocumentNavigation).
// TODO(https://crbug.com/1131832): Make this a CHECK instead once we're
// sure we never hit this case.
LogCannotCommitUrlCrashKeys(params->url, is_same_document_navigation,
navigation_request.get());
base::debug::DumpWithoutCrashing();
}
// A matching NavigationRequest should have been found, unless in a few very
// specific cases:
// 1) This was a synchronous about:blank navigation triggered by browsing
// context creation.
// 2) This was a renderer-initiated same-document navigation.
// In these cases, we will create a NavigationRequest by calling
// CreateNavigationRequestForSynchronousRendererCommit() further down.
// TODO(https://crbug.com/1131832): Make these navigation go through a
// separate path that does not send
// FrameHostMsg_DidCommitProvisionalLoad_Params at all.
// TODO(https://crbug.com/1215096): Tighten the checks for case 1 so that only
// the synchronous about:blank commit can actually go through (e.g. check
// if the URL is exactly "about:blank", currently we allow any "about:" URL
// except for "about:srcdoc").
const bool is_synchronous_about_blank_commit =
IsInitialSynchronousAboutBlankCommit(
params->url, frame_tree_node_->is_on_initial_empty_document());
if (!navigation_request && !is_synchronous_about_blank_commit &&
!is_same_document_navigation) {
LogCannotCommitUrlCrashKeys(params->url, is_same_document_navigation,
navigation_request.get());
bad_message::ReceivedBadMessage(
GetProcess(),
bad_message::RFH_NO_MATCHING_NAVIGATION_REQUEST_ON_COMMIT);
return false;
}
if (!ValidateDidCommitParams(navigation_request.get(), params.get(),
is_same_document_navigation)) {
return false;
}
// TODO(clamy): We should stop having a special case for same-document
// navigation and just put them in the general map of NavigationRequests.
if (navigation_request &&
navigation_request->common_params().url != params->url &&
is_same_document_navigation) {
same_document_navigation_requests_[navigation_request->commit_params()
.navigation_token] =
std::move(navigation_request);
}
// Set is loading to true now if it has not been set yet. This happens for
// renderer-initiated same-document navigations. It can also happen when a
// racy DidStopLoading Mojo method resets the loading state that was set to
// true in CommitNavigation.
if (!is_loading()) {
// In general, loading ui is only shown for cross-document navigations,
// because same-document navigations are already complete by the time the
// renderer notifies the browser process of the navigation.
// The navigation API's transitionWhile(), however, is an asynchronous
// same-document navigation, and should therefore show loading UI until load
// completion.
bool should_show_loading_ui =
!is_same_document_navigation ||
same_document_params->same_document_navigation_type ==
blink::mojom::SameDocumentNavigationType::
kNavigationApiTransitionWhile;
bool was_loading =
frame_tree()->LoadingTree()->IsLoadingIncludingInnerFrameTrees();
is_loading_ = true;
frame_tree_node()->DidStartLoading(should_show_loading_ui, was_loading);
}
if (navigation_request)
was_discarded_ = navigation_request->commit_params().was_discarded;
if (navigation_request) {
// If the navigation went through the browser before committing, it's
// possible to calculate the referrer only using information known by the
// browser.
// TODO(https://crbug.com/1131832): Get rid of params->referrer completely.
params->referrer = GetReferrerForDidCommitParams(navigation_request.get());
} else {
// For renderer-initiated same-document navigations and the initial
// about:blank navigation, the referrer policy shouldn't change.
params->referrer->policy = policy_container_host_->referrer_policy();
}
if (!navigation_request) {
// If there is no valid NavigationRequest corresponding to this commit,
// create one in order to properly issue DidFinishNavigation calls to
// WebContentsObservers.
DCHECK(is_synchronous_about_blank_commit || is_same_document_navigation);
// Fill the redirect chain for the NavigationRequest. Since this is only for
// initial empty commits or same-document navigation, we should just push
// the client-redirect URL (if it is a client redirect) and the final URL.
std::vector<GURL> redirects;
if (is_same_document_navigation &&
same_document_params->is_client_redirect) {
// If it is a same-document navigation, it might be a client redirect, in
// which case we should put the previous URL at the front of the redirect
// chain.
redirects.push_back(GetLastCommittedURL());
}
redirects.push_back(params->url);
// If this is a (renderer-initiated) same-document navigation, it might be
// started by a transient activation. The only way to know this is from the
// `started_with_transient_activation` value of `same_document_params`
// (because we don't know anything about this navigation before DidCommit).
bool started_with_transient_activation =
(is_same_document_navigation &&
same_document_params->started_with_transient_activation);
// TODO(https://crbug.com/1131832): Do not use |params| to get the values,
// depend on values known at commit time instead.
navigation_request = CreateNavigationRequestForSynchronousRendererCommit(
params->url, params->origin, params->referrer.Clone(),
params->transition, params->should_replace_current_entry,
started_with_transient_activation, redirects, params->url,
is_same_document_navigation,
same_document_params &&
same_document_params->same_document_navigation_type ==
blink::mojom::SameDocumentNavigationType::kHistoryApi);
}
DCHECK(navigation_request);
DCHECK(navigation_request->IsNavigationStarted());
VerifyThatBrowserAndRendererCalculatedDidCommitParamsMatch(
navigation_request.get(), *params, same_document_params.Clone());
// Update the page transition. For subframe navigations, the renderer process
// only gives the correct page transition at commit time.
// TODO(clamy): We should get the correct page transition when starting the
// request.
navigation_request->set_transition(params->transition);
SetLastCommittedSiteInfo(navigation_request->DidEncounterError()
? UrlInfo()
: navigation_request->GetUrlInfo());
isolation_info_ = navigation_request->isolation_info_for_subresources();
// Navigations in the same document and page activations do not create a new
// document.
bool created_new_document =
!is_same_document_navigation && !navigation_request->IsPageActivation();
// TODO(crbug.com/936696): Remove this after we have RenderDocument.
// IsWaitingToCommit can be false inside DidCommitNavigationInternal only in
// specific circumstances listed above, and specifically for the fake
// initial navigations triggered by the blank window.open() and creating a
// blank iframe. In that case we do not want to reset the per-document
// states as we are not really creating a new Document and we want to
// preserve the states set by WebContentsCreated delegate notification
// (which among other things create tab helpers) or RenderFrameCreated.
bool navigated_to_new_document =
created_new_document && navigation_request->IsWaitingToCommit();
if (navigated_to_new_document) {
TRACE_EVENT1("content", "DidCommitProvisionalLoad_StateResetForNewDocument",
"render_frame_host", this);
last_committed_cross_document_navigation_id_ =
navigation_request->GetNavigationId();
if (lifecycle_state() != LifecycleStateImpl::kPendingCommit &&
!committed_speculative_rfh_before_navigation_commit_) {
DCHECK_NE(lifecycle_state(), LifecycleStateImpl::kSpeculative);
// The old Reporting API configuration is no longer valid, as a new
// document is being loaded into the frame. Inform the network service
// of this, so that it can send any queued reports and mark the source
// as expired.
GetStoragePartition()->GetNetworkContext()->SendReportsAndRemoveSource(
GetReportingSource());
// Clear all document-associated data for the non-pending commit
// RenderFrameHosts because the navigation has created a new document.
// Make sure the data doesn't get cleared for the cases when the
// RenderFrameHost commits before the navigation commits. This happens
// when the current RenderFrameHost crashes before navigating to a new
// URL.
document_associated_data_.emplace(*this);
}
// This may only be done after creating the DocumentAssociatedData for the
// new document, if appropriate, since `fenced_frame_urls_map` hangs off of
// that.
if (navigation_request->pending_ad_components_map()) {
navigation_request->pending_ad_components_map()->ExportToMapping(
GetPage().fenced_frame_urls_map());
}
if (navigation_request->ad_auction_data()) {
AdAuctionDocumentData::CreateForCurrentDocument(
this, navigation_request->ad_auction_data()->interest_group_owner,
navigation_request->ad_auction_data()->interest_group_name);
}
// Continue observing the events for the committed navigation.
for (auto& receiver : navigation_request->TakeCookieObservers()) {
cookie_observers_.Add(this, std::move(receiver));
}
// Resets when navigating to a new document. This is needed because
// RenderFrameHost might be reused for a new document
document_used_web_otp_ = false;
// Get the UKM source id sent to the renderer.
const ukm::SourceId document_ukm_source_id =
navigation_request->commit_params().document_ukm_source_id;
ukm::UkmRecorder* ukm_recorder = ukm::UkmRecorder::Get();
// Associate the blink::Document source id to the URL. Only URLs on primary
// main frames can be recorded.
// TODO(crbug.com/1245014): For prerendering pages, record the source url
// after activation.
if (frame_tree_node()->GetFrameType() == FrameType::kPrimaryMainFrame &&
document_ukm_source_id != ukm::kInvalidSourceId) {
ukm_recorder->UpdateSourceURL(document_ukm_source_id, params->url);
}
RecordDocumentCreatedUkmEvent(params->origin, document_ukm_source_id,
ukm_recorder);
}
if (!is_same_document_navigation) {
DCHECK_EQ(navigation_request->is_overriding_user_agent() && is_main_frame(),
params->is_overriding_user_agent);
if (navigation_request->IsPrerenderedPageActivation()) {
// Set the NavigationStart time for
// PerformanceNavigationTiming.activationStart.
// https://wicg.github.io/nav-speculation/prerendering.html#performance-navigation-timing-extension
GetPage().SetActivationStartTime(navigation_request->NavigationStart());
}
} else {
DCHECK_EQ(is_overriding_user_agent_, params->is_overriding_user_agent);
}
if (is_main_frame()) {
document_associated_data_->owned_page->set_last_main_document_source_id(
ukm::ConvertToSourceId(navigation_request->GetNavigationId(),
ukm::SourceIdType::NAVIGATION_ID));
}
// TODO(https://crbug.com/1131832): Do not pass |params| to DidNavigate().
frame_tree_node()->navigator().DidNavigate(this, *params,
std::move(navigation_request),
is_same_document_navigation);
// Reset back the state to false after navigation commits.
// TODO(https://crbug.com/1072817): Undo this plumbing after removing the
// early post-crash CommitPending() call.
committed_speculative_rfh_before_navigation_commit_ = false;
// TODO(arthursonzogni): Updating this flag for same-document, bfcache, or
// prerender navigation doesn't seem right. This should likely be executed in
// DidCommitNewDocument().
if (IsBackForwardCacheEnabled() || blink::features::IsPrerender2Enabled()) {
// Store the Commit params so they can be reused if the page is ever
// restored from the BackForwardCache.
last_commit_params_ = std::move(params);
}
return true;
}
// TODO(arthursonzogni): Investigate what must be done when
// navigation_request->IsWaitingToCommit() is false here.
void RenderFrameHostImpl::DidCommitNewDocument(
const mojom::DidCommitProvisionalLoadParams& params,
NavigationRequest* navigation_request) {
// Navigations in the same document and page activations do not create a new
// document.
DCHECK(!navigation_request->IsSameDocument());
DCHECK(!navigation_request->IsPageActivation());
// The nonce to use in anonymous iframe is a page scoped attribute. So it
// needs to change every time the top-level document change.
// TODO(https://crbug.com1287458): Once the ShadowDom implementation of
// FencedFrame is gone, move this attribute back to PageImpl.
anonymous_iframes_nonce_ = base::UnguessableToken::Create();
ResetPermissionsPolicy();
permissions_policy_header_ = params.permissions_policy_header;
auto isolation_info = GetSiteInstance()->GetWebExposedIsolationInfo();
// Isolated Apps start with a base policy as defined by the permissions_policy
// field in its Web App Manifest, which is an allowlist, and then have headers
// further restrict the policy if applicable. This needs to be handled
// differently than the normal permissions policy behavior, which uses a fully
// permissive policy as its base permissions policy and accepts rules
// specifying which permissions policy features should be blocked, aka a
// blocklist.
if (isolation_info.is_isolated_application() && IsOutermostMainFrame()) {
permissions_policy_->SetHeaderPolicyForIsolatedApp(
params.permissions_policy_header);
} else {
permissions_policy_->SetHeaderPolicy(params.permissions_policy_header);
}
document_policy_ = blink::DocumentPolicy::CreateWithHeaderPolicy({
params.document_policy_header, // document_policy_header
{}, // endpoint_map
});
// Since we're changing documents, we should reset the event handler
// trackers.
has_before_unload_handler_ = false;
has_unload_handler_ = false;
has_pagehide_handler_ = false;
has_visibilitychange_handler_ = false;
DCHECK(params.embedding_token.has_value());
SetEmbeddingToken(params.embedding_token.value());
renderer_reported_bfcache_disabling_features_.Clear();
browser_reported_bfcache_disabling_features_counts_.clear();
TakeNewDocumentPropertiesFromNavigation(navigation_request);
// Set embedded documents' cross-origin-opener-policy from their top level:
// - Use top level's policy if they are same-origin.
// - Use the default policy if they are cross-origin.
// This COOP value is not used to enforce anything on this frame, but will be
// inherited to every local-scheme document created from them.
// It will also be inherited by the initial empty document from its opener.
// TODO(https://crbug.com/888079) Computing and assigning the
// cross-origin-opener-policy of an embedded frame should be done in
// |NavigationRequest::ComputePoliciesToCommit| , but this is not currently
// possible because we need the origin for the computation. The linked bug
// moves the origin computation earlier in the navigation request, which will
// enable the move to |NavigationRequest::ComputePoliciesToCommit|.
if (parent_) {
if (GetMainFrame()->GetLastCommittedOrigin().IsSameOriginWith(
params.origin)) {
policy_container_host_->set_cross_origin_opener_policy(
GetMainFrame()->cross_origin_opener_policy());
} else {
policy_container_host_->set_cross_origin_opener_policy(
network::CrossOriginOpenerPolicy());
}
}
CrossOriginOpenerPolicyAccessReportManager::InstallAccessMonitorsIfNeeded(
frame_tree_node_);
// Reset the salt so that media device IDs are reset for the new document
// if necessary.
media_device_id_salt_base_ = BrowserContext::CreateRandomMediaDeviceIDSalt();
accessibility_fatal_error_count_ = 0;
UpdateIsolatableSandboxedIframeTracking();
}
// TODO(arthursonzogni): Below, many NavigationRequest's objects are passed from
// the navigation to the new document. Consider grouping them in a single
// struct.
void RenderFrameHostImpl::TakeNewDocumentPropertiesFromNavigation(
NavigationRequest* navigation_request) {
// It should be kept in sync with the check in
// NavigationRequest::DidCommitNavigation.
is_error_page_ = navigation_request->DidEncounterError();
// Overwrite reporter's reporting source with rfh's reporting source.
std::unique_ptr<CrossOriginOpenerPolicyReporter> coop_reporter =
navigation_request->coop_status().TakeCoopReporter();
if (coop_reporter)
coop_reporter->set_reporting_source(GetReportingSource());
SetCrossOriginOpenerPolicyReporter(std::move(coop_reporter));
virtual_browsing_context_group_ =
navigation_request->coop_status().virtual_browsing_context_group();
soap_by_default_virtual_browsing_context_group_ =
navigation_request->coop_status()
.soap_by_default_virtual_browsing_context_group();
// Store the required CSP (it will be used by the AncestorThrottle if
// this frame embeds a subframe when that subframe navigates).
required_csp_ = navigation_request->TakeRequiredCSP();
is_fenced_frame_root_originating_from_opaque_url_ =
navigation_request
->is_target_fenced_frame_root_originating_from_opaque_url();
// TODO(https://crbug.com/888079): Once we are able to compute the origin to
// commit in the browser, `navigation_request->commit_params().storage_key`
// will contain the correct origin and it won't be necessary to override it
// with `param.origin` anymore.
const blink::StorageKey& provisional_storage_key =
navigation_request->commit_params().storage_key;
url::Origin origin = GetLastCommittedOrigin();
blink::StorageKey storage_key_to_commit = CalculateStorageKey(
origin, base::OptionalOrNullptr(provisional_storage_key.nonce()));
SetStorageKey(storage_key_to_commit);
coep_reporter_ = navigation_request->TakeCoepReporter();
if (coep_reporter_) {
// Set coep reporter to the document reporting source.
coep_reporter_->set_reporting_source(GetReportingSource());
mojo::PendingRemote<blink::mojom::ReportingObserver> remote;
mojo::PendingReceiver<blink::mojom::ReportingObserver> receiver =
remote.InitWithNewPipeAndPassReceiver();
coep_reporter_->BindObserver(std::move(remote));
// As some tests override the associated frame after commit, do not
// call GetAssociatedLocalFrame now.
base::ThreadTaskRunnerHandle::Get()->PostTask(
FROM_HERE,
base::BindOnce(&RenderFrameHostImpl::BindReportingObserver,
weak_ptr_factory_.GetWeakPtr(), std::move(receiver)));
}
// Set the state whether this navigation is to an MHTML document, since there
// are certain security checks that we cannot apply to subframes in MHTML
// documents. Do not trust renderer data when determining that, rather use
// the |navigation_request|, which was generated and stays browser side.
is_mhtml_document_ = navigation_request->IsWaitingToCommit() &&
navigation_request->IsMhtmlOrSubframe();
is_overriding_user_agent_ =
navigation_request->is_overriding_user_agent() && is_main_frame();
// Mark whether then navigation was intended as a loadDataWithBaseURL or not.
// If |renderer_url_info_.was_loaded_from_load_data_with_base_url| is true, we
// will bypass checks in VerifyDidCommitParams for same-document navigations
// in the loaded document.
renderer_url_info_.was_loaded_from_load_data_with_base_url =
navigation_request->IsLoadDataWithBaseURL();
// If we still have a PeakGpuMemoryTracker, then the loading it was observing
// never completed. Cancel it's callback so that we don't report partial
// loads to UMA.
if (loading_mem_tracker_) {
loading_mem_tracker_->Cancel();
}
// Main Frames will create the tracker, which will be triggered after we
// receive DidStopLoading.
loading_mem_tracker_ = navigation_request->TakePeakGpuMemoryTracker();
early_hints_manager_ = navigation_request->TakeEarlyHintsManager();
// Only take some properties if this is not the synchronous initial
// `about:blank` navigation, because the values set at construction time
// should remain unmodified.
if (!navigation_request->IsWaitingToCommit()) {
return;
}
private_network_request_policy_ =
navigation_request->private_network_request_policy();
reporting_endpoints_.clear();
DCHECK(navigation_request);
// Reporting API: If a Reporting-Endpoints header was received with this
// document over secure connection, send it to the network service to
// configure the endpoints in the reporting cache.
if (GURL::SchemeIsCryptographic(origin.scheme()) &&
navigation_request->response() &&
navigation_request->response()->parsed_headers->reporting_endpoints) {
GetStoragePartition()->GetNetworkContext()->SetDocumentReportingEndpoints(
GetReportingSource(), origin, isolation_info_,
*(navigation_request->response()->parsed_headers->reporting_endpoints));
}
// We move the PolicyContainerHost of |navigation_request| into the
// RenderFrameHost unless this is the initial, "fake" navigation to
// about:blank (because otherwise we would overwrite the PolicyContainerHost
// of the new document, inherited at RenderFrameHost creation time, with an
// empty PolicyContainerHost).
SetPolicyContainerHost(navigation_request->TakePolicyContainerHost());
if (navigation_request->response())
last_response_head_ = navigation_request->response()->Clone();
}
void RenderFrameHostImpl::OnSameDocumentCommitProcessed(
const base::UnguessableToken& navigation_token,
bool should_replace_current_entry,
blink::mojom::CommitResult result) {
auto request = same_document_navigation_requests_.find(navigation_token);
if (request == same_document_navigation_requests_.end()) {
// OnSameDocumentCommitProcessed will be called after DidCommitNavigation on
// successfull same-document commits, so |request| should already be deleted
// by the time we got here.
DCHECK_EQ(result, blink::mojom::CommitResult::Ok);
return;
}
if (result == blink::mojom::CommitResult::RestartCrossDocument) {
// The navigation could not be committed as a same-document navigation.
// Restart the navigation cross-document.
frame_tree_node_->navigator().RestartNavigationAsCrossDocument(
std::move(request->second));
return;
}
DCHECK_EQ(result, blink::mojom::CommitResult::Aborted);
// Note: if the commit was successful, the NavigationRequest is moved in
// DidCommitSameDocumentNavigation.
same_document_navigation_requests_.erase(navigation_token);
}
void RenderFrameHostImpl::MaybeGenerateCrashReport(
base::TerminationStatus status,
int exit_code) {
if (!last_committed_url_.SchemeIsHTTPOrHTTPS())
return;
// Only generate reports for local root frames that are in a different
// process than their parent.
if (!is_main_frame() && !IsCrossProcessSubframe())
return;
DCHECK(is_local_root());
// Check the termination status to see if a crash occurred (and potentially
// determine the |reason| for the crash).
std::string reason;
switch (status) {
case base::TERMINATION_STATUS_ABNORMAL_TERMINATION:
break;
case base::TERMINATION_STATUS_PROCESS_CRASHED:
if (exit_code == RESULT_CODE_HUNG)
reason = "unresponsive";
break;
case base::TERMINATION_STATUS_PROCESS_WAS_KILLED:
if (exit_code == RESULT_CODE_HUNG)
reason = "unresponsive";
else
return;
break;
case base::TERMINATION_STATUS_OOM:
#if BUILDFLAG(IS_CHROMEOS)
case base::TERMINATION_STATUS_PROCESS_WAS_KILLED_BY_OOM:
#endif
#if BUILDFLAG(IS_ANDROID)
case base::TERMINATION_STATUS_OOM_PROTECTED:
#endif
reason = "oom";
break;
default:
// Other termination statuses do not indicate a crash.
return;
}
// Construct the crash report.
base::Value::Dict body;
if (!reason.empty())
body.Set("reason", reason);
// Send the crash report to the Reporting API.
GetProcess()->GetStoragePartition()->GetNetworkContext()->QueueReport(
/*type=*/"crash", /*group=*/"default", last_committed_url_,
GetReportingSource(), isolation_info_.network_isolation_key(),
absl::nullopt /* user_agent */, std::move(body));
}
void RenderFrameHostImpl::SendCommitNavigation(
mojom::NavigationClient* navigation_client,
NavigationRequest* navigation_request,
blink::mojom::CommonNavigationParamsPtr common_params,
blink::mojom::CommitNavigationParamsPtr commit_params,
network::mojom::URLResponseHeadPtr response_head,
mojo::ScopedDataPipeConsumerHandle response_body,
network::mojom::URLLoaderClientEndpointsPtr url_loader_client_endpoints,
std::unique_ptr<blink::PendingURLLoaderFactoryBundle>
subresource_loader_factories,
absl::optional<std::vector<blink::mojom::TransferrableURLLoaderPtr>>
subresource_overrides,
blink::mojom::ControllerServiceWorkerInfoPtr controller,
blink::mojom::ServiceWorkerContainerInfoForClientPtr container_info,
mojo::PendingRemote<network::mojom::URLLoaderFactory>
prefetch_loader_factory,
const blink::ParsedPermissionsPolicy& permissions_policy,
blink::mojom::PolicyContainerPtr policy_container,
const base::UnguessableToken& devtools_navigation_token) {
TRACE_EVENT0("navigation", "RenderFrameHostImpl::SendCommitNavigation");
base::ElapsedTimer timer;
DCHECK_EQ(net::OK, navigation_request->GetNetErrorCode());
IncreaseCommitNavigationCounter();
mojo::PendingRemote<blink::mojom::CodeCacheHost> code_cache_host;
mojom::CookieManagerInfoPtr cookie_manager_info;
mojom::StorageInfoPtr storage_info;
if (base::FeatureList::IsEnabled(
features::kNavigationThreadingOptimizations)) {
CreateCodeCacheHostWithIsolationKey(
code_cache_host.InitWithNewPipeAndPassReceiver(),
navigation_request->isolation_info_for_subresources()
.network_isolation_key());
url::Origin origin_to_commit = navigation_request->GetOriginToCommit();
// Make sure the origin of the isolation info and origin to commit match,
// otherwise the cookie manager will crash. Sending the cookie manager here
// is just an optimization, so it is fine for it to be null in the case
// where these don't match.
if (common_params->url.SchemeIsHTTPOrHTTPS() &&
!origin_to_commit.opaque() &&
navigation_request->isolation_info_for_subresources()
.frame_origin()
.value() == origin_to_commit) {
cookie_manager_info = mojom::CookieManagerInfo::New();
cookie_manager_info->origin = origin_to_commit;
BindRestrictedCookieManagerWithOrigin(
cookie_manager_info->cookie_manager.InitWithNewPipeAndPassReceiver(),
navigation_request->isolation_info_for_subresources(),
origin_to_commit);
// Some tests need the StorageArea interfaces to come through DomStorage,
// so ignore the optimizations in those cases.
if (!RenderProcessHostImpl::HasDomStorageBinderForTesting()) {
storage_info = mojom::StorageInfo::New();
// Bind local storage and session storage areas.
auto* partition = GetStoragePartition();
int process_id = GetProcess()->GetID();
partition->OpenLocalStorageForProcess(
process_id, commit_params->storage_key,
storage_info->local_storage_area.InitWithNewPipeAndPassReceiver());
// Session storage must match the default namespace.
const std::string& namespace_id =
navigation_request->frame_tree_node()
->frame_tree()
->controller()
.GetSessionStorageNamespace(
GetSiteInstance()->GetStoragePartitionConfig())
->id();
partition->BindSessionStorageAreaForProcess(
process_id, commit_params->storage_key, namespace_id,
storage_info->session_storage_area
.InitWithNewPipeAndPassReceiver());
}
}
}
commit_params->commit_sent = base::TimeTicks::Now();
navigation_client->CommitNavigation(
std::move(common_params), std::move(commit_params),
std::move(response_head), std::move(response_body),
std::move(url_loader_client_endpoints),
std::move(subresource_loader_factories), std::move(subresource_overrides),
std::move(controller), std::move(container_info),
std::move(prefetch_loader_factory), devtools_navigation_token,
permissions_policy, std::move(policy_container),
std::move(code_cache_host), std::move(cookie_manager_info),
std::move(storage_info),
BuildCommitNavigationCallback(navigation_request));
base::UmaHistogramTimes(
base::StrCat({"Navigation.SendCommitNavigationTime.",
is_main_frame() ? "MainFrame" : "Subframe"}),
timer.Elapsed());
}
void RenderFrameHostImpl::SendCommitFailedNavigation(
mojom::NavigationClient* navigation_client,
NavigationRequest* navigation_request,
blink::mojom::CommonNavigationParamsPtr common_params,
blink::mojom::CommitNavigationParamsPtr commit_params,
bool has_stale_copy_in_cache,
int32_t error_code,
int32_t extended_error_code,
const absl::optional<std::string>& error_page_content,
std::unique_ptr<blink::PendingURLLoaderFactoryBundle>
subresource_loader_factories,
blink::mojom::PolicyContainerPtr policy_container) {
DCHECK(navigation_client && navigation_request);
DCHECK_NE(GURL(), common_params->url);
DCHECK_NE(net::OK, error_code);
IncreaseCommitNavigationCounter();
navigation_client->CommitFailedNavigation(
std::move(common_params), std::move(commit_params),
has_stale_copy_in_cache, error_code, extended_error_code,
navigation_request->GetResolveErrorInfo(), error_page_content,
std::move(subresource_loader_factories), std::move(policy_container),
GetContentClient()->browser()->GetAlternativeErrorPageOverrideInfo(
navigation_request->GetURL(), this, GetBrowserContext(), error_code),
BuildCommitFailedNavigationCallback(navigation_request));
}
// Called when the renderer navigates. For every frame loaded, we'll get this
// notification containing parameters identifying the navigation.
void RenderFrameHostImpl::DidCommitNavigation(
NavigationRequest* committing_navigation_request,
mojom::DidCommitProvisionalLoadParamsPtr params,
mojom::DidCommitProvisionalLoadInterfaceParamsPtr interface_params) {
DCHECK(params);
// BackForwardCacheImpl::CanStoreRenderFrameHost prevents placing the pages
// with in-flight navigation requests in the back-forward cache and it's not
// possible to start/commit a new one after the RenderFrameHost is in the
// BackForwardCache (see the check IsInactiveAndDisallowActivation in
// RFH::DidCommitSameDocumentNavigation() and RFH::BeginNavigation()) so it
// isn't possible to get a DidCommitNavigation IPC from the renderer in
// kInBackForwardCache state.
DCHECK(!IsInBackForwardCache());
std::unique_ptr<NavigationRequest> request;
// TODO(https://crbug.com/778318): a `committing_navigation_request` is not
// present if and only if this is a synchronous re-navigation to about:blank
// initiated by Blink. In all other cases it should be non-null and present in
// the map of NavigationRequests.
if (committing_navigation_request) {
committing_navigation_request->IgnoreCommitInterfaceDisconnection();
if (!MaybeInterceptCommitCallback(committing_navigation_request, &params,
&interface_params)) {
return;
}
auto find_request =
navigation_requests_.find(committing_navigation_request);
CHECK(find_request != navigation_requests_.end());
request = std::move(find_request->second);
navigation_requests_.erase(committing_navigation_request);
if (request->IsNavigationStarted()) {
main_frame_request_ids_ = {params->request_id,
request->GetGlobalRequestID()};
if (deferred_main_frame_load_info_)
ResourceLoadComplete(std::move(deferred_main_frame_load_info_));
}
}
// The commit IPC should be associated with the URL being committed (not with
// the *last* committed URL that most other IPCs are associated with).
// TODO(crbug.com/1179502): Investigate where the origin should come from when
// we remove FrameTree/FrameTreeNode members of this class, and the last
// committed origin may be incorrect.
ScopedActiveURL scoped_active_url(params->url,
frame_tree()->root()->current_origin());
ScopedCommitStateResetter commit_state_resetter(this);
RenderProcessHost* process = GetProcess();
TRACE_EVENT2("navigation", "RenderFrameHostImpl::DidCommitProvisionalLoad",
"rfh", this, "params", params);
// If we're waiting for a cross-site beforeunload completion callback from
// this renderer and we receive a Navigate message from the main frame, then
// the renderer was navigating already and sent it before hearing the
// blink::mojom::LocalFrame::StopLoading() message. Treat this as an implicit
// beforeunload completion callback to allow the pending navigation to
// continue.
if (is_waiting_for_beforeunload_completion_ &&
unload_ack_is_for_navigation_ && !GetParent()) {
base::TimeTicks approx_renderer_start_time = send_before_unload_start_time_;
ProcessBeforeUnloadCompleted(
/*proceed=*/true, /*treat_as_final_completion_callback=*/true,
approx_renderer_start_time, base::TimeTicks::Now(),
/*for_legacy=*/false);
}
// When a frame enters pending deletion, it waits for itself and its children
// to properly unload. Receiving DidCommitProvisionalLoad() here while the
// frame is not active means it comes from a navigation that reached the
// ReadyToCommit stage just before the frame entered pending deletion.
//
// We should ignore this message, because we have already committed to
// destroying this RenderFrameHost. Note that we intentionally do not ignore
// commits that happen while the current tab is being closed - see
// https://crbug.com/805705.
if (IsPendingDeletion())
return;
if (interface_params) {
if (broker_receiver_.is_bound()) {
broker_receiver_.reset();
}
BindBrowserInterfaceBrokerReceiver(
std::move(interface_params->browser_interface_broker_receiver));
} else {
// If the frame is no longer on the initial empty document, and this is not
// a same-document navigation, then both the active document as well as the
// global object was replaced in this browsing context. The RenderFrame
// should have rebound its BrowserInterfaceBroker to a new pipe, but failed
// to do so. Kill the renderer, and reset the old receiver to ensure that
// any pending interface requests originating from the previous document,
// hence possibly from a different security origin, will no longer be
// dispatched.
if (!frame_tree_node_->is_on_initial_empty_document()) {
broker_receiver_.reset();
bad_message::ReceivedBadMessage(
process, bad_message::RFH_INTERFACE_PROVIDER_MISSING);
return;
}
// Otherwise, it is the first real load committed, for which the RenderFrame
// is allowed to, and will re-use the existing BrowserInterfaceBroker
// connection if the new document is same-origin with the initial empty
// document, and therefore the global object is not replaced.
}
if (!DidCommitNavigationInternal(std::move(request), std::move(params),
/*same_document_params=*/nullptr)) {
return;
}
// Since we didn't early return, it's safe to keep the commit state.
commit_state_resetter.disable();
// For a top-level frame, there are potential security concerns associated
// with displaying graphics from a previously loaded page after the URL in
// the omnibar has been changed. It is unappealing to clear the page
// immediately, but if the renderer is taking a long time to issue any
// compositor output (possibly because of script deliberately creating this
// situation) then we clear it after a while anyway.
// See https://crbug.com/497588.
if (is_main_frame() && GetView()) {
RenderWidgetHostImpl::From(GetView()->GetRenderWidgetHost())->DidNavigate();
}
// TODO(arthursonzogni): This can be removed when RenderDocument will be
// implemented. See https://crbug.com/936696.
EnsureDescendantsAreUnloading();
}
mojom::NavigationClient::CommitNavigationCallback
RenderFrameHostImpl::BuildCommitNavigationCallback(
NavigationRequest* navigation_request) {
DCHECK(navigation_request);
return base::BindOnce(&RenderFrameHostImpl::DidCommitNavigation,
base::Unretained(this), navigation_request);
}
mojom::NavigationClient::CommitFailedNavigationCallback
RenderFrameHostImpl::BuildCommitFailedNavigationCallback(
NavigationRequest* navigation_request) {
DCHECK(navigation_request);
return base::BindOnce(&RenderFrameHostImpl::DidCommitNavigation,
base::Unretained(this), navigation_request);
}
void RenderFrameHostImpl::SendBeforeUnload(
bool is_reload,
base::WeakPtr<RenderFrameHostImpl> rfh,
bool for_legacy) {
auto before_unload_closure = base::BindOnce(
[](base::WeakPtr<RenderFrameHostImpl> impl, bool for_legacy, bool proceed,
base::TimeTicks renderer_before_unload_start_time,
base::TimeTicks renderer_before_unload_end_time) {
if (!impl)
return;
impl->ProcessBeforeUnloadCompleted(
proceed, /*treat_as_final_completion_callback=*/false,
renderer_before_unload_start_time, renderer_before_unload_end_time,
for_legacy);
},
rfh, for_legacy);
if (for_legacy) {
base::ThreadTaskRunnerHandle::Get()->PostTask(
FROM_HERE,
base::BindOnce(
[](blink::mojom::LocalFrame::BeforeUnloadCallback callback,
base::TimeTicks start_time, base::TimeTicks end_time) {
std::move(callback).Run(/*proceed=*/true, start_time, end_time);
},
std::move(before_unload_closure), send_before_unload_start_time_,
base::TimeTicks::Now()));
return;
}
// Experiment to run beforeunload handlers at a higher priority in the
// renderer. See crubug.com/1042118.
if (base::FeatureList::IsEnabled(features::kHighPriorityBeforeUnload)) {
rfh->GetHighPriorityLocalFrame()->DispatchBeforeUnload(
is_reload, std::move(before_unload_closure));
} else {
rfh->GetAssociatedLocalFrame()->BeforeUnload(
is_reload, std::move(before_unload_closure));
}
}
void RenderFrameHostImpl::AddServiceWorkerContainerHost(
const std::string& uuid,
base::WeakPtr<content::ServiceWorkerContainerHost> host) {
if (IsInBackForwardCache()) {
// RenderFrameHost entered BackForwardCache before adding
// ServiceWorkerContainerHost. In this case, evict the entry from the cache.
EvictFromBackForwardCacheWithReason(
BackForwardCacheMetrics::NotRestoredReason::
kEnteredBackForwardCacheBeforeServiceWorkerHostAdded);
}
DCHECK(!base::Contains(service_worker_container_hosts_, uuid));
last_committed_service_worker_host_ = host;
service_worker_container_hosts_[uuid] = std::move(host);
}
void RenderFrameHostImpl::RemoveServiceWorkerContainerHost(
const std::string& uuid) {
DCHECK(!service_worker_container_hosts_.empty());
DCHECK(base::Contains(service_worker_container_hosts_, uuid));
service_worker_container_hosts_.erase(uuid);
}
base::WeakPtr<ServiceWorkerContainerHost>
RenderFrameHostImpl::GetLastCommittedServiceWorkerHost() {
DCHECK_CURRENTLY_ON(BrowserThread::UI);
return last_committed_service_worker_host_;
}
bool RenderFrameHostImpl::MaybeInterceptCommitCallback(
NavigationRequest* navigation_request,
mojom::DidCommitProvisionalLoadParamsPtr* params,
mojom::DidCommitProvisionalLoadInterfaceParamsPtr* interface_params) {
if (commit_callback_interceptor_) {
return commit_callback_interceptor_->WillProcessDidCommitNavigation(
navigation_request, params, interface_params);
}
return true;
}
void RenderFrameHostImpl::PostMessageEvent(
const absl::optional<blink::RemoteFrameToken>& source_token,
const std::u16string& source_origin,
const std::u16string& target_origin,
blink::TransferableMessage message) {
DCHECK(is_render_frame_created());
if (message.delegated_capability != blink::mojom::DelegatedCapability::kNone)
ReceivedDelegatedCapability(message.delegated_capability);
GetAssociatedLocalFrame()->PostMessageEvent(
source_token, source_origin, target_origin, std::move(message));
}
bool RenderFrameHostImpl::IsTestRenderFrameHost() const {
return false;
}
scoped_refptr<PrefetchedSignedExchangeCache>
RenderFrameHostImpl::EnsurePrefetchedSignedExchangeCache() {
if (!prefetched_signed_exchange_cache_) {
prefetched_signed_exchange_cache_ =
base::MakeRefCounted<PrefetchedSignedExchangeCache>();
}
return prefetched_signed_exchange_cache_;
}
void RenderFrameHostImpl::ClearPrefetchedSignedExchangeCache() {
if (prefetched_signed_exchange_cache_)
prefetched_signed_exchange_cache_->Clear();
}
std::unique_ptr<WebBundleHandleTracker>
RenderFrameHostImpl::MaybeCreateWebBundleHandleTracker() {
if (web_bundle_handle_)
return web_bundle_handle_->MaybeCreateTracker();
FrameTreeNode* frame_owner =
frame_tree_node_->parent() ? frame_tree_node_->parent()->frame_tree_node()
: frame_tree_node_->opener();
if (!frame_owner)
return nullptr;
RenderFrameHostImpl* frame_owner_host = frame_owner->current_frame_host();
if (!frame_owner_host->web_bundle_handle_)
return nullptr;
return frame_owner_host->web_bundle_handle_->MaybeCreateTracker();
}
RenderWidgetHostImpl* RenderFrameHostImpl::GetLocalRenderWidgetHost() const {
if (is_main_frame())
return render_view_host_->GetWidget();
else
return owned_render_widget_host_.get();
}
void RenderFrameHostImpl::EnsureDescendantsAreUnloading() {
std::vector<RenderFrameHostImpl*> parents_to_be_checked = {this};
std::vector<RenderFrameHostImpl*> rfhs_to_be_checked;
while (!parents_to_be_checked.empty()) {
RenderFrameHostImpl* document = parents_to_be_checked.back();
parents_to_be_checked.pop_back();
for (auto& subframe : document->children_) {
RenderFrameHostImpl* child = subframe->current_frame_host();
// Every child is expected to be pending deletion. If it isn't the
// case, their FrameTreeNode is immediately removed from the tree.
if (!child->IsPendingDeletion())
rfhs_to_be_checked.push_back(child);
else
parents_to_be_checked.push_back(child);
}
}
for (RenderFrameHostImpl* document : rfhs_to_be_checked)
document->parent_->RemoveChild(document->frame_tree_node());
}
void RenderFrameHostImpl::AddMessageToConsoleImpl(
blink::mojom::ConsoleMessageLevel level,
const std::string& message,
bool discard_duplicates) {
GetAssociatedLocalFrame()->AddMessageToConsole(level, message,
discard_duplicates);
}
void RenderFrameHostImpl::LogCannotCommitUrlCrashKeys(
const GURL& url,
bool is_same_document_navigation,
NavigationRequest* navigation_request) {
LogRendererKillCrashKeys(GetSiteInstance()->GetSiteInfo());
// Temporary instrumentation to debug the root cause of renderer process
// terminations. See https://crbug.com/931895.
auto bool_to_crash_key = [](bool b) { return b ? "true" : "false"; };
static auto* const navigation_url_key = base::debug::AllocateCrashKeyString(
"navigation_url", base::debug::CrashKeySize::Size256);
base::debug::SetCrashKeyString(navigation_url_key, url.spec());
static auto* const is_same_document_key = base::debug::AllocateCrashKeyString(
"is_same_document", base::debug::CrashKeySize::Size32);
base::debug::SetCrashKeyString(
is_same_document_key, bool_to_crash_key(is_same_document_navigation));
static auto* const is_main_frame_key = base::debug::AllocateCrashKeyString(
"is_main_frame", base::debug::CrashKeySize::Size32);
base::debug::SetCrashKeyString(is_main_frame_key,
bool_to_crash_key(is_main_frame()));
static auto* const is_cross_process_subframe_key =
base::debug::AllocateCrashKeyString("is_cross_process_subframe",
base::debug::CrashKeySize::Size32);
base::debug::SetCrashKeyString(is_cross_process_subframe_key,
bool_to_crash_key(IsCrossProcessSubframe()));
static auto* const is_local_root_key = base::debug::AllocateCrashKeyString(
"is_local_root", base::debug::CrashKeySize::Size32);
base::debug::SetCrashKeyString(is_local_root_key,
bool_to_crash_key(is_local_root()));
static auto* const site_lock_key = base::debug::AllocateCrashKeyString(
"site_lock", base::debug::CrashKeySize::Size256);
base::debug::SetCrashKeyString(
site_lock_key,
ProcessLock::FromSiteInfo(GetSiteInstance()->GetSiteInfo()).ToString());
static auto* const process_lock_key = base::debug::AllocateCrashKeyString(
"process_lock", base::debug::CrashKeySize::Size256);
base::debug::SetCrashKeyString(process_lock_key,
GetProcess()->GetProcessLock().ToString());
if (!GetSiteInstance()->IsDefaultSiteInstance()) {
static auto* const original_url_origin_key =
base::debug::AllocateCrashKeyString("original_url_origin",
base::debug::CrashKeySize::Size256);
base::debug::SetCrashKeyString(
original_url_origin_key,
GetSiteInstance()->original_url().DeprecatedGetOriginAsURL().spec());
}
static auto* const is_mhtml_document_key =
base::debug::AllocateCrashKeyString("is_mhtml_document",
base::debug::CrashKeySize::Size32);
base::debug::SetCrashKeyString(is_mhtml_document_key,
bool_to_crash_key(is_mhtml_document()));
static auto* const last_committed_url_origin_key =
base::debug::AllocateCrashKeyString("last_committed_url_origin",
base::debug::CrashKeySize::Size256);
base::debug::SetCrashKeyString(
last_committed_url_origin_key,
GetLastCommittedURL().DeprecatedGetOriginAsURL().spec());
static auto* const last_successful_url_origin_key =
base::debug::AllocateCrashKeyString("last_successful_url_origin",
base::debug::CrashKeySize::Size256);
base::debug::SetCrashKeyString(
last_successful_url_origin_key,
last_successful_url().DeprecatedGetOriginAsURL().spec());
if (navigation_request && navigation_request->IsNavigationStarted()) {
static auto* const is_renderer_initiated_key =
base::debug::AllocateCrashKeyString("is_renderer_initiated",
base::debug::CrashKeySize::Size32);
base::debug::SetCrashKeyString(
is_renderer_initiated_key,
bool_to_crash_key(navigation_request->IsRendererInitiated()));
static auto* const is_server_redirect_key =
base::debug::AllocateCrashKeyString("is_server_redirect",
base::debug::CrashKeySize::Size32);
base::debug::SetCrashKeyString(
is_server_redirect_key,
bool_to_crash_key(navigation_request->WasServerRedirect()));
static auto* const is_form_submission_key =
base::debug::AllocateCrashKeyString("is_form_submission",
base::debug::CrashKeySize::Size32);
base::debug::SetCrashKeyString(
is_form_submission_key,
bool_to_crash_key(navigation_request->IsFormSubmission()));
static auto* const is_error_page_key = base::debug::AllocateCrashKeyString(
"is_error_page", base::debug::CrashKeySize::Size32);
base::debug::SetCrashKeyString(
is_error_page_key,
bool_to_crash_key(navigation_request->IsErrorPage()));
static auto* const from_begin_navigation_key =
base::debug::AllocateCrashKeyString("from_begin_navigation",
base::debug::CrashKeySize::Size32);
base::debug::SetCrashKeyString(
from_begin_navigation_key,
bool_to_crash_key(navigation_request->from_begin_navigation()));
static auto* const net_error_key = base::debug::AllocateCrashKeyString(
"net_error", base::debug::CrashKeySize::Size32);
base::debug::SetCrashKeyString(
net_error_key,
base::NumberToString(navigation_request->GetNetErrorCode()));
static auto* const initiator_origin_key =
base::debug::AllocateCrashKeyString("initiator_origin",
base::debug::CrashKeySize::Size64);
base::debug::SetCrashKeyString(
initiator_origin_key,
navigation_request->GetInitiatorOrigin()
? navigation_request->GetInitiatorOrigin()->GetDebugString()
: "none");
static auto* const starting_site_instance_key =
base::debug::AllocateCrashKeyString("starting_site_instance",
base::debug::CrashKeySize::Size256);
base::debug::SetCrashKeyString(starting_site_instance_key,
navigation_request->GetStartingSiteInstance()
->GetSiteInfo()
.GetDebugString());
// Recompute the target SiteInstance to see if it matches the current
// one at commit time.
scoped_refptr<SiteInstance> dest_instance =
navigation_request->frame_tree_node()
->render_manager()
->GetSiteInstanceForNavigationRequest(navigation_request);
static auto* const does_recomputed_site_instance_match_key =
base::debug::AllocateCrashKeyString(
"does_recomputed_site_instance_match",
base::debug::CrashKeySize::Size32);
base::debug::SetCrashKeyString(
does_recomputed_site_instance_match_key,
bool_to_crash_key(dest_instance == GetSiteInstance()));
}
}
int64_t CalculatePostID(
const std::string& method,
const scoped_refptr<network::ResourceRequestBody>& request_body,
int64_t last_post_id,
bool is_same_document) {
if (method != "POST")
return -1;
// On same-document navigations that keep the "POST" method, use the POST ID
// from the last navigation.
if (is_same_document)
return last_post_id;
// Otherwise, this is a cross-document navigation. Use the POST ID from the
// navigation request.
return request_body ? request_body->identifier() : -1;
}
const std::string CalculateMethod(
const std::string& nav_request_method,
const std::string& last_http_method,
bool is_same_document,
bool is_same_document_history_api_navigation) {
DCHECK(is_same_document || !is_same_document_history_api_navigation);
// History API navigations are always "GET" navigations. See spec:
// https://html.spec.whatwg.org/multipage/history.html#url-and-history-update-steps
if (is_same_document_history_api_navigation)
return "GET";
// If this is a same-document navigation that isn't triggered by the history
// API, we should preserve the HTTP method used by the last navigation.
if (is_same_document)
return last_http_method;
// Otherwise, this is a cross-document navigation. Use the method specified in
// the navigation request.
return nav_request_method;
}
int CalculateHTTPStatusCode(NavigationRequest* request,
int last_http_status_code) {
// Same-document navigations should retain the HTTP status code from the last
// committed navigation.
if (request->IsSameDocument())
return last_http_status_code;
// Navigations that are served from the back/forward cache or that are
// prerendered will always have the HTTP status code set to 200.
//
// TODO(https://crbug.com/1199699): Navigations should actually return the
// last HTTP status code of the RenderFrameHost.
if (request->IsServedFromBackForwardCache() ||
request->IsPrerenderedPageActivation()) {
return 200;
}
// The HTTP status code is not set if we never received any HTTP response for
// the navigation.
const int request_response_code = request->commit_params().http_response_code;
if (request_response_code == -1)
return 0;
// Otherwise, return the status code from |request|.
return request_response_code;
}
bool CalculateShouldReplaceCurrentEntry(
NavigationRequest* request,
const mojom::DidCommitProvisionalLoadParams& params) {
// A Renderer-initiated same-document navigation is not known to the browser
// before it committed and whether it does replacement or not is not
// predictable, so we need to use the renderer-supplied value, and in the
// future move |should_replace_current_entry| from
// DidCommitProvisionalLoadParams to DidCommitSameDocumentNavigationParams.
// For other navigations, the CommonNavigationParams' value supplied by the
// browser to the renderer at commit time can be used, as the renderer will
// always follow it. An exception is when on the initial NavigationEntry,
// CommonParams' should_replace_current_entry will always be true on the
// browser side but the renderer might not know about it so DidCommitParams'
// should_replace_current_entry might differ, which is why we "skip" comparing
// for browser vs renderer values in that case, by comparing the renderer
// value against itself (through returning DidCommitParams'
// should_replace_current_entry here).
NavigationEntryImpl* last_entry = request->frame_tree_node()
->navigator()
.controller()
.GetLastCommittedEntry();
return (request->IsSameDocument() ||
(last_entry && last_entry->IsInitialEntry()))
? params.should_replace_current_entry
: request->common_params().should_replace_current_entry;
}
// Tries to simulate WebFrameLoadType in NavigationTypeToLoadType() in
// render_frame_impl.cc and RenderFrameImpl::CommitFailedNavigation().
// TODO(https://crbug.com/1131832): This should only be here temporarily.
// Remove this once the renderer behavior at commit time is more consistent with
// what the browser instructed it to do (e.g. reloads will always be classified
// as kReload).
RendererLoadType CalculateRendererLoadType(NavigationRequest* request,
bool should_replace_current_entry,
const GURL& previous_document_url) {
const bool is_restore =
NavigationTypeUtils::IsRestore(request->common_params().navigation_type);
const bool is_history =
NavigationTypeUtils::IsHistory(request->common_params().navigation_type);
const bool is_reload =
NavigationTypeUtils::IsReload(request->common_params().navigation_type);
const bool has_valid_page_state = blink::PageState::CreateFromEncodedData(
request->commit_params().page_state)
.IsValid();
const bool is_error_page = request->DidEncounterError();
// Predict if the renderer classified the navigation as a "back/forward"
// navigation (WebFrameLoadType::kBackForward).
bool will_be_classified_as_back_forward_navigation = false;
if (is_error_page) {
// For error pages, whenever the navigation has a valid PageState, it will
// be considered as a back/forward navigation. This includes history
// navigations and restores. See RenderFrameImpl's CommitFailedNavigation().
will_be_classified_as_back_forward_navigation = has_valid_page_state;
} else {
// For normal navigations, RenderFrameImpl's NavigationTypeToLoadType()
// will classify a navigation as kBackForward if it's a history navigation,
// or if it's a restore navigation with valid PageState.
will_be_classified_as_back_forward_navigation =
is_history || (is_restore && has_valid_page_state);
}
if (will_be_classified_as_back_forward_navigation) {
// If the navigation is classified as kBackForward, it can't be changed to
// another RendererLoadType below, so we can immediately return here.
return RendererLoadType::kBackForward;
}
if (!is_error_page && is_reload) {
// For non-error pages, if the NavigationType given by the browser is
// a reload, then the navigation will be classified as a reload.
return RendererLoadType::kReload;
}
return should_replace_current_entry ? RendererLoadType::kReplaceCurrentItem
: RendererLoadType::kStandard;
}
bool CalculateDidCreateNewEntry(NavigationRequest* request,
bool should_replace_current_entry,
RendererLoadType renderer_load_type) {
// This function tries to simulate the calculation of |did_create_new_entry|
// in RenderFrameImpl::MakeDidCommitProvisionalLoadParams().
// Standard navigations will always create a new entry.
if (renderer_load_type == RendererLoadType::kStandard)
return true;
// Back/Forward navigations won't create a new entry.
if (renderer_load_type == RendererLoadType::kBackForward)
return false;
// Otherwise, |did_create_new_entry| is true only for main frame
// cross-document replacements.
return request->IsInMainFrame() && should_replace_current_entry &&
!request->IsSameDocument();
}
ui::PageTransition CalculateTransition(
NavigationRequest* request,
RendererLoadType renderer_load_type,
const mojom::DidCommitProvisionalLoadParams& params,
bool is_in_fenced_frame_tree) {
if (is_in_fenced_frame_tree) {
// Navigations inside fenced frame trees do not add session history items
// and must be marked with PAGE_TRANSITION_AUTO_SUBFRAME. This is set
// regardless of the `is_main_frame` value since this is inside a fenced
// frame tree and should behave the same as iframes. Also preserve client
// redirects if they were set.
ui::PageTransition transition = ui::PAGE_TRANSITION_AUTO_SUBFRAME;
if (request->IsInMainFrame() && !request->DidEncounterError() &&
request->common_params().transition &
ui::PAGE_TRANSITION_CLIENT_REDIRECT) {
transition =
ui::PageTransitionFromInt(ui::PAGE_TRANSITION_AUTO_SUBFRAME |
ui::PAGE_TRANSITION_CLIENT_REDIRECT);
}
return transition;
}
if (request->IsSameDocument())
return params.transition;
if (request->IsInMainFrame()) {
// This follows GetTransitionType() in render_frame_impl.cc.
ui::PageTransition supplied_transition =
ui::PageTransitionFromInt(request->common_params().transition);
if (ui::PageTransitionCoreTypeIs(supplied_transition,
ui::PAGE_TRANSITION_LINK) &&
request->common_params().post_data) {
return ui::PAGE_TRANSITION_FORM_SUBMIT;
}
return supplied_transition;
}
// This follows RenderFrameImpl::MakeDidCommitProvisionalLoadParams().
if (renderer_load_type == RendererLoadType::kStandard)
return ui::PAGE_TRANSITION_MANUAL_SUBFRAME;
return ui::PAGE_TRANSITION_AUTO_SUBFRAME;
}
// Calculates the "loading" URL for a given navigation. This tries to replicate
// RenderFrameImpl::GetLoadingUrl() and is used to predict the value of "url" in
// DidCommitProvisionalLoadParams.
GURL CalculateLoadingURL(
NavigationRequest* request,
const mojom::DidCommitProvisionalLoadParams& params,
const RenderFrameHostImpl::RendererURLInfo& last_renderer_url_info,
bool last_document_is_error_page,
const GURL& last_committed_url) {
if (params.url.IsAboutBlank() && params.url.ref_piece() == "blocked") {
// Some navigations can still be blocked by the renderer during the commit,
// changing the URL to "about:blank#blocked". Currently we have no way of
// predicting this in the browser, so just return the URL given by the
// renderer in this case.
// TODO(https://crbug.com/1131832): Block the navigations in the browser
// instead and remove |params| as a parameter to this function.
return params.url;
}
if (!request->common_params().url.is_valid()) {
// Empty URL (and invalid URLs, which are converted to the empty URL due
// to IPC URL reparsing) will be rewritten to "about:blank" in the renderer.
// TODO(https://crbug.com/1131832): Do the rewrite in the browser.
return GURL(url::kAboutBlankURL);
}
if (request->IsSameDocument() &&
(last_document_is_error_page ||
last_renderer_url_info.was_loaded_from_load_data_with_base_url)) {
// Documents that have an "override" URL (loadDataWithBaseURL navigations,
// error pages) will continue using that URL even after same-document
// navigations.
return last_committed_url;
}
// For all other navigations, the returned URL should be the same as the URL
// in CommonNavigationParams.
return request->common_params().url;
}
bool ShouldVerify(const std::string& param) {
#if DCHECK_IS_ON()
return true;
#else
if (param != "should_replace_current_entry" &&
param != "url_is_unreachable") {
// All params except the two above have no known complications and should be
// compared by default.
return true;
}
return GetFieldTrialParamByFeatureAsBool(features::kVerifyDidCommitParams,
param, false);
#endif
}
void LogVerifyDidCommitParamsDifference(
VerifyDidCommitParamsDifference difference) {
UMA_HISTOGRAM_ENUMERATION("Navigation.VerifyDidCommitParams", difference);
}
std::string GetURLTypeForCrashKey(const GURL& url) {
if (url == kUnreachableWebDataURL)
return "error";
if (url == kBlockedURL)
return "blocked";
if (url.IsAboutBlank())
return "about:blank";
if (url.IsAboutSrcdoc())
return "about:srcdoc";
if (url.is_empty())
return "empty";
if (!url.is_valid())
return "invalid";
return url.scheme();
}
std::string GetURLRelationForCrashKey(
const GURL& actual_url,
const GURL& predicted_url,
const blink::mojom::CommonNavigationParams& common_params,
const GURL& last_committed_url,
const RenderFrameHostImpl::RendererURLInfo& renderer_url_info) {
if (actual_url == predicted_url)
return "as predicted";
if (actual_url == last_committed_url)
return "last committed";
if (actual_url == common_params.url)
return "common params URL";
if (actual_url == common_params.base_url_for_data_url)
return "base URL";
if (actual_url == renderer_url_info.last_document_url)
return "last document URL";
return "unknown";
}
void RenderFrameHostImpl::
VerifyThatBrowserAndRendererCalculatedDidCommitParamsMatch(
NavigationRequest* request,
const mojom::DidCommitProvisionalLoadParams& params,
const mojom::DidCommitSameDocumentNavigationParamsPtr
same_document_params) {
#if !DCHECK_IS_ON()
// Only check for the flag if DCHECK is not enabled, so that we will always
// verify the params for tests.
if (!base::FeatureList::IsEnabled(features::kVerifyDidCommitParams))
return;
#endif
// Check if these values from DidCommitProvisionalLoadParams sent by the
// renderer can be calculated entirely in the browser side:
// - method
// - url_is_unreachable
// - post_id
// - is_overriding_user_agent
// - http_status_code
// - should_update_history
// - should_replace_current_entry
// - url
// - did_create_new_entry
// - transition
// - history_list_was_cleared
// - origin
// TODO(crbug.com/1131832): Verify more params.
// We can know if we're going to be in an error page after this navigation
// if the net error code is not net::OK, or if we're doing a same-document
// navigation on an error page (only possible for renderer-initiated
// navigations).
const bool is_error_page = (request->DidEncounterError() ||
(is_error_page_ && request->IsSameDocument()));
const bool browser_url_is_unreachable = is_error_page;
const bool is_same_document_navigation = !!same_document_params;
const bool is_same_document_history_api_navigation =
same_document_params &&
same_document_params->same_document_navigation_type ==
blink::mojom::SameDocumentNavigationType::kHistoryApi;
DCHECK_EQ(is_same_document_navigation, request->IsSameDocument());
const int64_t browser_post_id =
CalculatePostID(params.method, request->common_params().post_data,
last_post_id_, is_same_document_navigation);
const std::string& browser_method = CalculateMethod(
request->common_params().method, last_http_method_,
is_same_document_navigation, is_same_document_history_api_navigation);
const bool browser_is_overriding_user_agent =
is_same_document_navigation
? is_overriding_user_agent_
: (request->commit_params().is_overriding_user_agent &&
request->frame_tree_node()->IsMainFrame());
const int browser_http_status_code =
CalculateHTTPStatusCode(request, last_http_status_code_);
// Note that this follows the calculation of should_update_history in
// RenderFrameImpl::MakeDidCommitProvisionalLoadParams().
// TODO(https://crbug.com/1158101): Reconsider how we calculate
// should_update_history.
const bool browser_should_update_history =
!browser_url_is_unreachable && browser_http_status_code != 404;
const bool browser_should_replace_current_entry =
CalculateShouldReplaceCurrentEntry(request, params);
const GURL browser_url = CalculateLoadingURL(
request, params, renderer_url_info_, is_error_page_, last_committed_url_);
const RendererLoadType renderer_load_type =
CalculateRendererLoadType(request, browser_should_replace_current_entry,
renderer_url_info_.last_document_url);
const bool browser_did_create_new_entry =
request->is_synchronous_renderer_commit()
? params.did_create_new_entry
: CalculateDidCreateNewEntry(request,
browser_should_replace_current_entry,
renderer_load_type);
const ui::PageTransition browser_transition =
CalculateTransition(request, renderer_load_type, params,
frame_tree_node_->IsInFencedFrameTree());
const bool browser_history_list_was_cleared =
request->commit_params().should_clear_history_list;
const bool everything_except_origin_matches =
((!ShouldVerify("method") || browser_method == params.method) &&
(!ShouldVerify("url_is_unreachable") ||
browser_url_is_unreachable == params.url_is_unreachable) &&
(!ShouldVerify("post_id") || browser_post_id == params.post_id) &&
(!ShouldVerify("is_overriding_user_agent") ||
browser_is_overriding_user_agent == params.is_overriding_user_agent) &&
(!ShouldVerify("http_status_code") ||
browser_http_status_code == params.http_status_code) &&
(!ShouldVerify("should_update_history") ||
browser_should_update_history == params.should_update_history) &&
(!ShouldVerify("should_replace_current_entry") ||
browser_should_replace_current_entry ==
params.should_replace_current_entry) &&
(!ShouldVerify("url") || browser_url == params.url) &&
(!ShouldVerify("did_create_new_entry") ||
browser_did_create_new_entry == params.did_create_new_entry) &&
(!ShouldVerify("transition") ||
ui::PageTransitionTypeIncludingQualifiersIs(browser_transition,
params.transition)) &&
(!ShouldVerify("history_list_was_cleared") ||
browser_history_list_was_cleared == params.history_list_was_cleared));
if (everything_except_origin_matches &&
(!ShouldVerify("origin") ||
request->state() < NavigationRequest::WILL_PROCESS_RESPONSE ||
request->GetOriginToCommit() == params.origin)) {
// Don't do a DumpWithoutCrashing if everything matches. Note that we save
// `everything_except_origin_matches` separately so that we can skip doing
// a DumpWithoutCrashing at the end of this function if the origin turns
// out to match (actual origin match checking is done below as it does its
// own DumpWithoutCrashing).
return;
}
SCOPED_CRASH_KEY_BOOL(
"VerifyDidCommit", "prev_ldwb",
renderer_url_info_.was_loaded_from_load_data_with_base_url);
SCOPED_CRASH_KEY_STRING32(
"VerifyDidCommit", "base_url_fdu_type",
GetURLTypeForCrashKey(request->common_params().base_url_for_data_url));
#if BUILDFLAG(IS_ANDROID)
SCOPED_CRASH_KEY_BOOL("VerifyDidCommit", "data_url_empty",
request->commit_params().data_url_as_string.empty());
#endif
SCOPED_CRASH_KEY_BOOL("VerifyDidCommit", "intended_as_new_entry",
request->commit_params().intended_as_new_entry);
SCOPED_CRASH_KEY_STRING32("VerifyDidCommit", "method_browser",
browser_method);
SCOPED_CRASH_KEY_STRING32("VerifyDidCommit", "method_renderer",
params.method);
SCOPED_CRASH_KEY_STRING32("VerifyDidCommit", "original_method",
request->commit_params().original_method);
// For WebView, since we don't want to log potential PIIs.
SCOPED_CRASH_KEY_BOOL("VerifyDidCommit", "method_post_browser",
browser_method == "POST");
SCOPED_CRASH_KEY_BOOL("VerifyDidCommit", "method_post_renderer",
params.method == "POST");
SCOPED_CRASH_KEY_BOOL("VerifyDidCommit", "original_method_post",
request->commit_params().original_method == "POST");
SCOPED_CRASH_KEY_BOOL("VerifyDidCommit", "unreachable_browser",
browser_url_is_unreachable);
SCOPED_CRASH_KEY_BOOL("VerifyDidCommit", "unreachable_renderer",
params.url_is_unreachable);
SCOPED_CRASH_KEY_NUMBER("VerifyDidCommit", "post_id_browser",
browser_post_id);
SCOPED_CRASH_KEY_NUMBER("VerifyDidCommit", "post_id_renderer",
params.post_id);
// For WebView, since we don't want to log IDs.
SCOPED_CRASH_KEY_BOOL("VerifyDidCommit", "post_id_matches",
browser_post_id == params.post_id);
SCOPED_CRASH_KEY_BOOL("VerifyDidCommit", "post_id_-1_browser",
browser_post_id == -1);
SCOPED_CRASH_KEY_BOOL("VerifyDidCommit", "post_id_-1_renderer",
params.post_id == -1);
SCOPED_CRASH_KEY_BOOL("VerifyDidCommit", "override_ua_browser",
browser_is_overriding_user_agent);
SCOPED_CRASH_KEY_BOOL("VerifyDidCommit", "override_ua_renderer",
params.is_overriding_user_agent);
SCOPED_CRASH_KEY_NUMBER("VerifyDidCommit", "code_browser",
browser_http_status_code);
SCOPED_CRASH_KEY_NUMBER("VerifyDidCommit", "code_renderer",
params.http_status_code);
SCOPED_CRASH_KEY_BOOL("VerifyDidCommit", "suh_browser",
browser_should_update_history);
SCOPED_CRASH_KEY_BOOL("VerifyDidCommit", "suh_renderer",
params.should_update_history);
SCOPED_CRASH_KEY_BOOL("VerifyDidCommit", "gesture",
request->common_params().has_user_gesture);
SCOPED_CRASH_KEY_BOOL("VerifyDidCommit", "replace_browser",
browser_should_replace_current_entry);
SCOPED_CRASH_KEY_BOOL("VerifyDidCommit", "replace_renderer",
params.should_replace_current_entry);
SCOPED_CRASH_KEY_BOOL("VerifyDidCommit", "create_browser",
browser_did_create_new_entry);
SCOPED_CRASH_KEY_BOOL("VerifyDidCommit", "create_renderer",
params.did_create_new_entry);
SCOPED_CRASH_KEY_NUMBER("VerifyDidCommit", "transition_browser",
browser_transition);
SCOPED_CRASH_KEY_NUMBER("VerifyDidCommit", "transition_renderer",
params.transition);
SCOPED_CRASH_KEY_BOOL("VerifyDidCommit", "cleared_browser",
browser_history_list_was_cleared);
SCOPED_CRASH_KEY_BOOL("VerifyDidCommit", "cleared_renderer",
params.history_list_was_cleared);
SCOPED_CRASH_KEY_STRING256("VerifyDidCommit", "url_browser",
browser_url.possibly_invalid_spec());
SCOPED_CRASH_KEY_STRING256("VerifyDidCommit", "url_renderer",
params.url.possibly_invalid_spec());
SCOPED_CRASH_KEY_STRING32(
"VerifyDidCommit", "url_relation",
GetURLRelationForCrashKey(params.url, browser_url,
request->common_params(), GetLastCommittedURL(),
renderer_url_info_));
SCOPED_CRASH_KEY_STRING32("VerifyDidCommit", "url_browser_type",
GetURLTypeForCrashKey(browser_url));
SCOPED_CRASH_KEY_STRING32("VerifyDidCommit", "url_renderer_type",
GetURLTypeForCrashKey(params.url));
SCOPED_CRASH_KEY_BOOL("VerifyDidCommit", "is_same_document",
is_same_document_navigation);
SCOPED_CRASH_KEY_BOOL("VerifyDidCommit", "is_history_api",
is_same_document_history_api_navigation);
SCOPED_CRASH_KEY_BOOL("VerifyDidCommit", "renderer_initiated",
request->IsRendererInitiated());
SCOPED_CRASH_KEY_BOOL("VerifyDidCommit", "is_subframe",
!request->frame_tree_node()->IsMainFrame());
SCOPED_CRASH_KEY_BOOL("VerifyDidCommit", "is_form_submission",
request->IsFormSubmission());
SCOPED_CRASH_KEY_BOOL("VerifyDidCommit", "is_error_page", is_error_page);
SCOPED_CRASH_KEY_NUMBER("VerifyDidCommit", "net_error",
request->GetNetErrorCode());
SCOPED_CRASH_KEY_BOOL("VerifyDidCommit", "is_server_redirect",
request->WasServerRedirect());
SCOPED_CRASH_KEY_NUMBER("VerifyDidCommit", "redirects_size",
request->GetRedirectChain().size());
SCOPED_CRASH_KEY_NUMBER("VerifyDidCommit", "entry_offset",
request->GetNavigationEntryOffset());
SCOPED_CRASH_KEY_NUMBER(
"VerifyDidCommit", "entry_count",
request->frame_tree_node()->frame_tree()->controller().GetEntryCount());
SCOPED_CRASH_KEY_NUMBER("VerifyDidCommit", "last_committed_index",
request->frame_tree_node()
->frame_tree()
->controller()
.GetLastCommittedEntryIndex());
SCOPED_CRASH_KEY_BOOL(
"VerifyDidCommit", "is_reload",
NavigationTypeUtils::IsReload(request->common_params().navigation_type));
SCOPED_CRASH_KEY_BOOL(
"VerifyDidCommit", "is_restore",
NavigationTypeUtils::IsRestore(request->common_params().navigation_type));
SCOPED_CRASH_KEY_BOOL(
"VerifyDidCommit", "is_history",
NavigationTypeUtils::IsHistory(request->common_params().navigation_type));
SCOPED_CRASH_KEY_BOOL("VerifyDidCommit", "has_valid_page_state",
blink::PageState::CreateFromEncodedData(
request->commit_params().page_state)
.IsValid());
SCOPED_CRASH_KEY_BOOL("VerifyDidCommit", "has_gesture",
request->HasUserGesture());
SCOPED_CRASH_KEY_BOOL("VerifyDidCommit", "was_click",
request->WasInitiatedByLinkClick());
SCOPED_CRASH_KEY_STRING256("VerifyDidCommit", "original_req_url",
request->commit_params().original_url.spec());
SCOPED_CRASH_KEY_BOOL("VerifyDidCommit", "original_same_doc",
request->commit_params().original_url.EqualsIgnoringRef(
GetLastCommittedURL()));
SCOPED_CRASH_KEY_BOOL(
"VerifyDidCommit", "on_initial_empty_doc",
request->frame_tree_node()->is_on_initial_empty_document());
SCOPED_CRASH_KEY_STRING256("VerifyDidCommit", "last_committed_url",
GetLastCommittedURL().spec());
SCOPED_CRASH_KEY_STRING256("VerifyDidCommit", "last_document_url",
renderer_url_info_.last_document_url.spec());
SCOPED_CRASH_KEY_STRING32("VerifyDidCommit", "last_url_type",
GetURLTypeForCrashKey(GetLastCommittedURL()));
SCOPED_CRASH_KEY_STRING256("VerifyDidCommit", "last_method",
last_http_method_);
SCOPED_CRASH_KEY_NUMBER("VerifyDidCommit", "last_code",
last_http_status_code_);
bool has_original_url =
GetSiteInstance() && !GetSiteInstance()->IsDefaultSiteInstance();
SCOPED_CRASH_KEY_STRING256(
"VerifyDidCommit", "si_url",
has_original_url
? GetSiteInstance()->original_url().possibly_invalid_spec()
: "");
SCOPED_CRASH_KEY_BOOL("VerifyDidCommit", "has_si_url", has_original_url);
// These DCHECKs ensure that tests will fail if we got here, as
// DumpWithoutCrashing won't fail tests.
DCHECK_EQ(browser_method, params.method);
DCHECK_EQ(browser_url_is_unreachable, params.url_is_unreachable);
DCHECK_EQ(browser_post_id, params.post_id);
DCHECK_EQ(browser_is_overriding_user_agent, params.is_overriding_user_agent);
DCHECK_EQ(browser_http_status_code, params.http_status_code);
DCHECK_EQ(browser_should_update_history, params.should_update_history);
DCHECK_EQ(browser_should_replace_current_entry,
params.should_replace_current_entry);
DCHECK_EQ(browser_url, params.url);
DCHECK_EQ(browser_did_create_new_entry, params.did_create_new_entry);
DCHECK(ui::PageTransitionTypeIncludingQualifiersIs(browser_transition,
params.transition));
DCHECK_EQ(browser_history_list_was_cleared, params.history_list_was_cleared);
// Log histograms to trigger Chrometto slow reports, allowing us to see traces
// to analyze what happened in these navigations.
if (browser_method != params.method) {
LogVerifyDidCommitParamsDifference(
VerifyDidCommitParamsDifference::kMethod);
}
if (browser_url_is_unreachable != params.url_is_unreachable) {
LogVerifyDidCommitParamsDifference(
VerifyDidCommitParamsDifference::kURLIsUnreachable);
}
if (browser_post_id != params.post_id) {
LogVerifyDidCommitParamsDifference(
VerifyDidCommitParamsDifference::kPostID);
}
if (browser_is_overriding_user_agent != params.is_overriding_user_agent) {
LogVerifyDidCommitParamsDifference(
VerifyDidCommitParamsDifference::kIsOverridingUserAgent);
}
if (browser_http_status_code != params.http_status_code) {
LogVerifyDidCommitParamsDifference(
VerifyDidCommitParamsDifference::kHTTPStatusCode);
}
if (browser_should_update_history != params.should_update_history) {
LogVerifyDidCommitParamsDifference(
VerifyDidCommitParamsDifference::kShouldUpdateHistory);
}
if (browser_should_replace_current_entry !=
params.should_replace_current_entry) {
LogVerifyDidCommitParamsDifference(
VerifyDidCommitParamsDifference::kShouldReplaceCurrentEntry);
}
if (browser_url != params.url) {
LogVerifyDidCommitParamsDifference(VerifyDidCommitParamsDifference::kURL);
}
if (browser_did_create_new_entry != params.did_create_new_entry) {
LogVerifyDidCommitParamsDifference(
VerifyDidCommitParamsDifference::kDidCreateNewEntry);
}
if (!ui::PageTransitionTypeIncludingQualifiersIs(browser_transition,
params.transition)) {
LogVerifyDidCommitParamsDifference(
VerifyDidCommitParamsDifference::kTransition);
}
if (browser_history_list_was_cleared != params.history_list_was_cleared) {
LogVerifyDidCommitParamsDifference(
VerifyDidCommitParamsDifference::kHistoryListWasCleared);
}
// TODO(https://crbug.com/888079): The origin computed from the browser must
// match the one reported from the renderer process.
if (!VerifyThatBrowserAndRendererCalculatedOriginsToCommitMatch(request,
params)) {
LogVerifyDidCommitParamsDifference(
VerifyDidCommitParamsDifference::kOrigin);
}
if (!everything_except_origin_matches) {
// It's possible to get here when everything except the origin matches.
// If the origin doesn't match, we would do a DumpWithoutCrashing above.
// So, don't do a DumpWithoutCrashing unless there's another param that
// doesn't match.
base::debug::DumpWithoutCrashing();
}
}
void RenderFrameHostImpl::MaybeEvictFromBackForwardCache() {
if (!IsInBackForwardCache())
return;
RenderFrameHostImpl* top_document = this;
while (RenderFrameHostImpl* parent = top_document->GetParent())
top_document = parent;
BackForwardCacheCanStoreDocumentResultWithTree bfcache_eligibility =
frame_tree()
->controller()
.GetBackForwardCache()
.GetCurrentBackForwardCacheEligibility(top_document);
TRACE_EVENT("navigation",
"RenderFrameHostImpl::MaybeEvictFromBackForwardCache",
"render_frame_host", this, "bfcache_eligibility",
bfcache_eligibility.flattened_reasons.ToString());
if (bfcache_eligibility.CanStore())
return;
EvictFromBackForwardCacheWithFlattenedAndTreeReasons(bfcache_eligibility);
}
void RenderFrameHostImpl::LogCannotCommitOriginCrashKeys(
const GURL& url,
const url::Origin& origin,
const ProcessLock& process_lock,
bool is_same_document_navigation,
NavigationRequest* navigation_request) {
LogRendererKillCrashKeys(GetSiteInstance()->GetSiteInfo());
// Temporary instrumentation to debug the root cause of
// https://crbug.com/923144.
auto bool_to_crash_key = [](bool b) { return b ? "true" : "false"; };
static auto* const target_url_key = base::debug::AllocateCrashKeyString(
"target_url", base::debug::CrashKeySize::Size256);
base::debug::SetCrashKeyString(target_url_key, url.spec());
static auto* const target_origin_key = base::debug::AllocateCrashKeyString(
"target_origin", base::debug::CrashKeySize::Size256);
base::debug::SetCrashKeyString(target_origin_key, origin.GetDebugString());
const url::Origin url_origin = url::Origin::Resolve(url, origin);
const auto target_url_origin_tuple_or_precursor_tuple =
url_origin.GetTupleOrPrecursorTupleIfOpaque();
static auto* const target_url_origin_tuple_key =
base::debug::AllocateCrashKeyString("target_url_origin_tuple",
base::debug::CrashKeySize::Size256);
base::debug::SetCrashKeyString(
target_url_origin_tuple_key,
target_url_origin_tuple_or_precursor_tuple.Serialize());
const auto target_origin_tuple_or_precursor_tuple =
origin.GetTupleOrPrecursorTupleIfOpaque();
static auto* const target_origin_tuple_key =
base::debug::AllocateCrashKeyString("target_origin_tuple",
base::debug::CrashKeySize::Size256);
base::debug::SetCrashKeyString(
target_origin_tuple_key,
target_origin_tuple_or_precursor_tuple.Serialize());
static auto* const last_committed_url_key =
base::debug::AllocateCrashKeyString("last_committed_url",
base::debug::CrashKeySize::Size256);
base::debug::SetCrashKeyString(last_committed_url_key,
GetLastCommittedURL().spec());
static auto* const last_committed_origin_key =
base::debug::AllocateCrashKeyString("last_committed_origin",
base::debug::CrashKeySize::Size256);
base::debug::SetCrashKeyString(last_committed_origin_key,
GetLastCommittedOrigin().GetDebugString());
static auto* const process_lock_key = base::debug::AllocateCrashKeyString(
"process_lock", base::debug::CrashKeySize::Size256);
base::debug::SetCrashKeyString(process_lock_key, process_lock.ToString());
static auto* const is_same_document_key = base::debug::AllocateCrashKeyString(
"is_same_document", base::debug::CrashKeySize::Size32);
base::debug::SetCrashKeyString(
is_same_document_key, bool_to_crash_key(is_same_document_navigation));
static auto* const is_subframe_key = base::debug::AllocateCrashKeyString(
"is_subframe", base::debug::CrashKeySize::Size32);
base::debug::SetCrashKeyString(is_subframe_key,
bool_to_crash_key(GetMainFrame() != this));
static auto* const lifecycle_state_key = base::debug::AllocateCrashKeyString(
"lifecycle_state", base::debug::CrashKeySize::Size32);
base::debug::SetCrashKeyString(lifecycle_state_key,
LifecycleStateImplToString(lifecycle_state()));
static auto* const is_active_key = base::debug::AllocateCrashKeyString(
"is_active", base::debug::CrashKeySize::Size32);
base::debug::SetCrashKeyString(is_active_key, bool_to_crash_key(IsActive()));
static auto* const is_cross_process_subframe_key =
base::debug::AllocateCrashKeyString("is_cross_process_subframe",
base::debug::CrashKeySize::Size32);
base::debug::SetCrashKeyString(is_cross_process_subframe_key,
bool_to_crash_key(IsCrossProcessSubframe()));
static auto* const is_local_root_key = base::debug::AllocateCrashKeyString(
"is_local_root", base::debug::CrashKeySize::Size32);
base::debug::SetCrashKeyString(is_local_root_key,
bool_to_crash_key(is_local_root()));
if (navigation_request && navigation_request->IsNavigationStarted()) {
static auto* const is_renderer_initiated_key =
base::debug::AllocateCrashKeyString("is_renderer_initiated",
base::debug::CrashKeySize::Size32);
base::debug::SetCrashKeyString(
is_renderer_initiated_key,
bool_to_crash_key(navigation_request->IsRendererInitiated()));
static auto* const is_server_redirect_key =
base::debug::AllocateCrashKeyString("is_server_redirect",
base::debug::CrashKeySize::Size32);
base::debug::SetCrashKeyString(
is_server_redirect_key,
bool_to_crash_key(navigation_request->WasServerRedirect()));
static auto* const is_form_submission_key =
base::debug::AllocateCrashKeyString("is_form_submission",
base::debug::CrashKeySize::Size32);
base::debug::SetCrashKeyString(
is_form_submission_key,
bool_to_crash_key(navigation_request->IsFormSubmission()));
static auto* const is_error_page_key = base::debug::AllocateCrashKeyString(
"is_error_page", base::debug::CrashKeySize::Size32);
base::debug::SetCrashKeyString(
is_error_page_key,
bool_to_crash_key(navigation_request->IsErrorPage()));
static auto* const initiator_origin_key =
base::debug::AllocateCrashKeyString("initiator_origin",
base::debug::CrashKeySize::Size64);
base::debug::SetCrashKeyString(
initiator_origin_key,
navigation_request->GetInitiatorOrigin()
? navigation_request->GetInitiatorOrigin()->GetDebugString()
: "none");
static auto* const starting_site_instance_key =
base::debug::AllocateCrashKeyString("starting_site_instance",
base::debug::CrashKeySize::Size256);
base::debug::SetCrashKeyString(starting_site_instance_key,
navigation_request->GetStartingSiteInstance()
->GetSiteInfo()
.GetDebugString());
}
}
void RenderFrameHostImpl::EnableMojoJsBindings(
content::mojom::ExtraMojoJsFeaturesPtr features) {
// This method should only be called on RenderFrameHost which is for a WebUI.
DCHECK_NE(WebUI::kNoWebUI,
WebUIControllerFactoryRegistry::GetInstance()->GetWebUIType(
GetSiteInstance()->GetBrowserContext(),
site_instance_->GetSiteInfo().site_url()));
GetFrameBindingsControl()->EnableMojoJsBindings(std::move(features));
}
void RenderFrameHostImpl::EnableMojoJsBindingsWithBroker(
mojo::PendingRemote<blink::mojom::BrowserInterfaceBroker> broker) {
// This method should only be called on RenderFrameHost that has an associated
// WebUI, because it needs to transfer the broker's ownership to its
// WebUIController. EnableMojoJsBindings does this differently and can be
// called before the WebUI object is created.
DCHECK(GetWebUI());
GetFrameBindingsControl()->EnableMojoJsBindingsWithBroker(std::move(broker));
}
bool RenderFrameHostImpl::IsOutermostMainFrame() const {
return !GetParentOrOuterDocument();
}
BackForwardCacheMetrics* RenderFrameHostImpl::GetBackForwardCacheMetrics() {
NavigationEntryImpl* navigation_entry =
frame_tree()->controller().GetEntryWithUniqueID(nav_entry_id());
if (!navigation_entry)
return nullptr;
return navigation_entry->back_forward_cache_metrics();
}
bool RenderFrameHostImpl::IsBackForwardCacheDisabled() const {
return back_forward_cache_disabled_reasons_.size();
}
bool RenderFrameHostImpl::IsDOMContentLoaded() {
return document_associated_data_->dom_content_loaded;
}
void RenderFrameHostImpl::UpdateIsAdFrame(bool is_ad_frame) {
browsing_context_state_->SetIsAdFrame(is_ad_frame);
}
#if BUILDFLAG(IS_ANDROID)
std::pair<blink::mojom::AuthenticatorStatus, bool>
RenderFrameHostImpl::PerformGetAssertionWebAuthSecurityChecks(
const std::string& relying_party_id,
const url::Origin& effective_origin,
bool is_payment_credential_get_assertion,
const blink::mojom::RemoteDesktopClientOverridePtr&
remote_desktop_client_override) {
bool is_cross_origin = true; // Will be reset in ValidateAncestorOrigins().
WebAuthRequestSecurityChecker::RequestType request_type =
is_payment_credential_get_assertion
? WebAuthRequestSecurityChecker::RequestType::
kGetPaymentCredentialAssertion
: WebAuthRequestSecurityChecker::RequestType::kGetAssertion;
blink::mojom::AuthenticatorStatus status =
GetWebAuthRequestSecurityChecker()->ValidateAncestorOrigins(
effective_origin, request_type, &is_cross_origin);
if (status != blink::mojom::AuthenticatorStatus::SUCCESS) {
return std::make_pair(status, is_cross_origin);
}
status = GetWebAuthRequestSecurityChecker()->ValidateDomainAndRelyingPartyID(
effective_origin, relying_party_id, request_type,
remote_desktop_client_override);
return std::make_pair(status, is_cross_origin);
}
blink::mojom::AuthenticatorStatus
RenderFrameHostImpl::PerformMakeCredentialWebAuthSecurityChecks(
const std::string& relying_party_id,
const url::Origin& effective_origin,
bool is_payment_credential_creation,
const blink::mojom::RemoteDesktopClientOverridePtr&
remote_desktop_client_override) {
bool is_cross_origin;
WebAuthRequestSecurityChecker::RequestType request_type =
is_payment_credential_creation
? WebAuthRequestSecurityChecker::RequestType::kMakePaymentCredential
: WebAuthRequestSecurityChecker::RequestType::kMakeCredential;
blink::mojom::AuthenticatorStatus status =
GetWebAuthRequestSecurityChecker()->ValidateAncestorOrigins(
effective_origin, request_type, &is_cross_origin);
if (status != blink::mojom::AuthenticatorStatus::SUCCESS) {
return status;
}
status = GetWebAuthRequestSecurityChecker()->ValidateDomainAndRelyingPartyID(
effective_origin, relying_party_id, request_type,
remote_desktop_client_override);
if (status != blink::mojom::AuthenticatorStatus::SUCCESS) {
return status;
}
return blink::mojom::AuthenticatorStatus::SUCCESS;
}
#endif
void RenderFrameHostImpl::IsClipboardPasteContentAllowed(
const ui::ClipboardFormatType& data_type,
const std::string& data,
IsClipboardPasteContentAllowedCallback callback) {
delegate_->IsClipboardPasteContentAllowed(GetLastCommittedURL(), data_type,
data, std::move(callback));
}
RenderFrameHostImpl* RenderFrameHostImpl::GetParentOrOuterDocument() const {
return frame_tree_node()->GetParentOrOuterDocumentHelper(
/*escape_guest_view=*/false);
}
RenderFrameHostImpl* RenderFrameHostImpl::GetParentOrOuterDocumentOrEmbedder()
const {
return frame_tree_node()->GetParentOrOuterDocumentHelper(
/*escape_guest_view=*/true);
}
RenderFrameHostImpl* RenderFrameHostImpl::GetOutermostMainFrameOrEmbedder() {
RenderFrameHostImpl* current = this;
while (true) {
RenderFrameHostImpl* parent = current->GetParentOrOuterDocumentOrEmbedder();
if (!parent)
return current;
current = parent;
};
}
scoped_refptr<WebAuthRequestSecurityChecker>
RenderFrameHostImpl::GetWebAuthRequestSecurityChecker() {
if (!webauth_request_security_checker_)
webauth_request_security_checker_ =
base::MakeRefCounted<WebAuthRequestSecurityChecker>(this);
return webauth_request_security_checker_;
}
bool RenderFrameHostImpl::IsInBackForwardCache() const {
return lifecycle_state() == LifecycleStateImpl::kInBackForwardCache;
}
bool RenderFrameHostImpl::IsPendingDeletion() const {
return lifecycle_state() == LifecycleStateImpl::kRunningUnloadHandlers ||
lifecycle_state() == LifecycleStateImpl::kReadyToBeDeleted;
}
void RenderFrameHostImpl::SetLifecycleState(LifecycleStateImpl new_state) {
TRACE_EVENT2("content", "RenderFrameHostImpl::SetLifecycleState",
"render_frame_host", this, "new_state",
LifecycleStateImplToString(new_state));
// Finish the slice corresponding to the old lifecycle state and begin a new
// slice for the lifecycle state we are transitioning to.
TRACE_EVENT_END("navigation", perfetto::Track::FromPointer(this));
TRACE_EVENT_BEGIN(
"navigation",
perfetto::StaticString{LifecycleStateImplToString(new_state)},
perfetto::Track::FromPointer(this));
// TODO(crbug.com/1256898): Consider associating expectations with each
// transitions.
#if DCHECK_IS_ON()
static const base::NoDestructor<base::StateTransitions<LifecycleStateImpl>>
allowed_transitions(
// For a graph of state transitions, see
// https://chromium.googlesource.com/chromium/src/+/HEAD/docs/render-frame-host-lifecycle-state.png
// To update the graph, see the corresponding .gv file.
// RenderFrameHost is only set speculative during its creation and no
// transitions happen to this state during its lifetime.
base::StateTransitions<LifecycleStateImpl>({
{LifecycleStateImpl::kSpeculative,
{LifecycleStateImpl::kActive,
LifecycleStateImpl::kPendingCommit}},
{LifecycleStateImpl::kPendingCommit,
{LifecycleStateImpl::kPrerendering, LifecycleStateImpl::kActive,
LifecycleStateImpl::kReadyToBeDeleted}},
{LifecycleStateImpl::kPrerendering,
{LifecycleStateImpl::kActive,
LifecycleStateImpl::kRunningUnloadHandlers,
LifecycleStateImpl::kReadyToBeDeleted}},
{LifecycleStateImpl::kActive,
{LifecycleStateImpl::kInBackForwardCache,
LifecycleStateImpl::kRunningUnloadHandlers,
LifecycleStateImpl::kReadyToBeDeleted}},
{LifecycleStateImpl::kInBackForwardCache,
{LifecycleStateImpl::kActive,
LifecycleStateImpl::kReadyToBeDeleted}},
{LifecycleStateImpl::kRunningUnloadHandlers,
{LifecycleStateImpl::kReadyToBeDeleted}},
{LifecycleStateImpl::kReadyToBeDeleted, {}},
}));
DCHECK_STATE_TRANSITION(allowed_transitions,
/*old_state=*/lifecycle_state_,
/*new_state=*/new_state);
#endif // DCHECK_IS_ON()
// TODO(crbug.com/1256896): Use switch-case to make this more readable.
// If the RenderFrameHost is restored from BackForwardCache or is part of a
// prerender activation, update states of all the children to kActive.
if (new_state == LifecycleStateImpl::kActive) {
if (lifecycle_state_ == LifecycleStateImpl::kPendingCommit ||
lifecycle_state_ == LifecycleStateImpl::kSpeculative) {
// Newly-created documents shouldn't have children, as child creation
// happens after commit.
DCHECK(children_.empty());
}
if (lifecycle_state() == LifecycleStateImpl::kInBackForwardCache ||
lifecycle_state() == LifecycleStateImpl::kPrerendering) {
for (auto& child : children_) {
DCHECK_EQ(child->current_frame_host()->lifecycle_state(),
lifecycle_state_);
child->current_frame_host()->SetLifecycleState(new_state);
}
}
}
if (lifecycle_state() == LifecycleStateImpl::kInBackForwardCache)
was_restored_from_back_forward_cache_for_debugging_ = true;
if (new_state == LifecycleStateImpl::kPendingCommit ||
new_state == LifecycleStateImpl::kPrerendering) {
DCHECK(children_.empty());
}
LifecycleStateImpl old_state = lifecycle_state_;
lifecycle_state_ = new_state;
// Unset the |has_pending_lifecycle_state_update_| value once the
// LifecycleStateImpl is updated.
if (has_pending_lifecycle_state_update_) {
DCHECK(lifecycle_state() == LifecycleStateImpl::kInBackForwardCache ||
IsPendingDeletion() ||
old_state == LifecycleStateImpl::kPrerendering)
<< "Transitioned to unexpected state with resetting "
"|has_pending_lifecycle_state_update_|\n ";
has_pending_lifecycle_state_update_ = false;
}
// As kSpeculative state is not exposed to embedders, we can ignore the
// transitions out of kSpeculative state while notifying delegate.
if (old_state != LifecycleStateImpl::kSpeculative) {
LifecycleState old_lifecycle_state = GetLifecycleStateFromImpl(old_state);
LifecycleState new_lifecycle_state = GetLifecycleState();
// Old and new lifecycle states can be equal due to the same LifecycleState
// representing multiple LifecycleStateImpls, for example the
// kPendingDeletion state. Don't notify the observers in such cases.
if (old_lifecycle_state != new_lifecycle_state) {
delegate_->RenderFrameHostStateChanged(this, old_lifecycle_state,
new_lifecycle_state);
}
}
}
void RenderFrameHostImpl::RecordDocumentCreatedUkmEvent(
const url::Origin& origin,
const ukm::SourceId document_ukm_source_id,
ukm::UkmRecorder* ukm_recorder) {
DCHECK(ukm_recorder);
if (document_ukm_source_id == ukm::kInvalidSourceId)
return;
// Compares the subframe origin with the main frame origin. In the case of
// nested subframes such as A(B(A)), the bottom-most frame A is expected to
// have |is_cross_origin_frame| set to false, even though this frame is cross-
// origin from its parent frame B. This value is only used in manual analysis.
bool is_cross_origin_frame =
!is_main_frame() &&
!GetMainFrame()->GetLastCommittedOrigin().IsSameOriginWith(origin);
// Compares the subframe site with the main frame site. In the case of
// nested subframes such as A(B(A)), the bottom-most frame A is expected to
// have |is_cross_site_frame| set to false, even though this frame is cross-
// site from its parent frame B. This value is only used in manual analysis.
bool is_cross_site_frame =
!is_main_frame() &&
(net::SchemefulSite(origin) !=
net::SchemefulSite(GetMainFrame()->GetLastCommittedOrigin()));
ukm::builders::DocumentCreated(document_ukm_source_id)
.SetNavigationSourceId(GetPageUkmSourceId())
.SetIsMainFrame(is_main_frame())
.SetIsCrossOriginFrame(is_cross_origin_frame)
.SetIsCrossSiteFrame(is_cross_site_frame)
.Record(ukm_recorder);
RecordIdentifiabilityDocumentCreatedMetrics(
document_ukm_source_id, ukm_recorder, GetPageUkmSourceId(),
is_cross_origin_frame, is_cross_site_frame, IsOutermostMainFrame());
}
void RenderFrameHostImpl::BindReportingObserver(
mojo::PendingReceiver<blink::mojom::ReportingObserver> receiver) {
GetAssociatedLocalFrame()->BindReportingObserver(std::move(receiver));
}
mojo::PendingRemote<network::mojom::URLLoaderNetworkServiceObserver>
RenderFrameHostImpl::CreateURLLoaderNetworkObserver() {
return GetStoragePartition()->CreateURLLoaderNetworkObserverForFrame(
GetProcess()->GetID(), GetRoutingID());
}
PeerConnectionTrackerHost& RenderFrameHostImpl::GetPeerConnectionTrackerHost() {
return *PeerConnectionTrackerHost::GetOrCreateForCurrentDocument(this);
}
void RenderFrameHostImpl::BindPeerConnectionTrackerHost(
mojo::PendingReceiver<blink::mojom::PeerConnectionTrackerHost> receiver) {
GetPeerConnectionTrackerHost().BindReceiver(std::move(receiver));
}
void RenderFrameHostImpl::EnableWebRtcEventLogOutput(int lid,
int output_period_ms) {
GetPeerConnectionTrackerHost().StartEventLog(lid, output_period_ms);
}
void RenderFrameHostImpl::DisableWebRtcEventLogOutput(int lid) {
GetPeerConnectionTrackerHost().StopEventLog(lid);
}
bool RenderFrameHostImpl::IsDocumentOnLoadCompletedInMainFrame() {
return GetPage().is_on_load_completed_in_main_document();
}
// TODO(crbug.com/1192003): Move this method to content::Page when available.
const std::vector<blink::mojom::FaviconURLPtr>&
RenderFrameHostImpl::FaviconURLs() {
return GetPage().favicon_urls();
}
mojo::PendingRemote<network::mojom::CookieAccessObserver>
RenderFrameHostImpl::CreateCookieAccessObserver() {
mojo::PendingRemote<network::mojom::CookieAccessObserver> remote;
cookie_observers_.Add(this, remote.InitWithNewPipeAndPassReceiver());
return remote;
}
#if BUILDFLAG(ENABLE_MDNS)
void RenderFrameHostImpl::CreateMdnsResponder(
mojo::PendingReceiver<network::mojom::MdnsResponder> receiver) {
GetStoragePartition()->GetNetworkContext()->CreateMdnsResponder(
std::move(receiver));
}
#endif // BUILDFLAG(ENABLE_MDNS)
void RenderFrameHostImpl::Clone(
mojo::PendingReceiver<network::mojom::CookieAccessObserver> observer) {
cookie_observers_.Add(this, std::move(observer));
}
#if BUILDFLAG(ENABLE_PLUGINS)
void RenderFrameHostImpl::InstanceCreated(
int32_t instance_id,
mojo::PendingAssociatedRemote<mojom::PepperPluginInstance> instance,
mojo::PendingAssociatedReceiver<mojom::PepperPluginInstanceHost> host) {
pepper_instance_map_.emplace(
instance_id,
std::make_unique<PepperPluginInstanceHost>(
instance_id, this, std::move(instance), std::move(host)));
}
void RenderFrameHostImpl::BindHungDetectorHost(
mojo::PendingReceiver<mojom::PepperHungDetectorHost> hung_host,
int32_t plugin_child_id,
const base::FilePath& path) {
pepper_hung_detectors_.Add(this, std::move(hung_host),
{plugin_child_id, path});
}
void RenderFrameHostImpl::GetPluginInfo(const GURL& url,
const std::string& mime_type,
GetPluginInfoCallback callback) {
bool allow_wildcard = true;
WebPluginInfo info;
std::string actual_mime_type;
bool found = PluginServiceImpl::GetInstance()->GetPluginInfo(
GetProcess()->GetID(), url, mime_type, allow_wildcard, nullptr, &info,
&actual_mime_type);
std::move(callback).Run(found, info, actual_mime_type);
}
void RenderFrameHostImpl::DidCreateInProcessInstance(int32_t instance,
int32_t render_frame_id,
const GURL& document_url,
const GURL& plugin_url) {
RenderProcessHostImpl* process =
static_cast<RenderProcessHostImpl*>(GetProcess());
process->pepper_renderer_connection()->DidCreateInProcessInstance(
instance, render_frame_id, document_url, plugin_url);
}
void RenderFrameHostImpl::DidDeleteInProcessInstance(int32_t instance) {
RenderProcessHostImpl* process =
static_cast<RenderProcessHostImpl*>(GetProcess());
process->pepper_renderer_connection()->DidDeleteInProcessInstance(instance);
}
void RenderFrameHostImpl::DidCreateOutOfProcessPepperInstance(
int32_t plugin_child_id,
int32_t pp_instance,
bool is_external,
int32_t render_frame_id,
const GURL& document_url,
const GURL& plugin_url,
bool is_privileged_context,
DidCreateOutOfProcessPepperInstanceCallback callback) {
RenderProcessHostImpl* process =
static_cast<RenderProcessHostImpl*>(GetProcess());
process->pepper_renderer_connection()->DidCreateOutOfProcessPepperInstance(
plugin_child_id, pp_instance, is_external, render_frame_id, document_url,
plugin_url, is_privileged_context, std::move(callback));
}
void RenderFrameHostImpl::DidDeleteOutOfProcessPepperInstance(
int32_t plugin_child_id,
int32_t pp_instance,
bool is_external) {
RenderProcessHostImpl* process =
static_cast<RenderProcessHostImpl*>(GetProcess());
process->pepper_renderer_connection()->DidDeleteOutOfProcessPepperInstance(
plugin_child_id, pp_instance, is_external);
}
void RenderFrameHostImpl::OpenChannelToPepperPlugin(
const url::Origin& embedder_origin,
const base::FilePath& path,
const absl::optional<url::Origin>& origin_lock,
OpenChannelToPepperPluginCallback callback) {
RenderProcessHostImpl* process =
static_cast<RenderProcessHostImpl*>(GetProcess());
process->pepper_renderer_connection()->OpenChannelToPepperPlugin(
embedder_origin, path, origin_lock, std::move(callback));
}
void RenderFrameHostImpl::PluginHung(bool is_hung) {
const HungDetectorContext& context = pepper_hung_detectors_.current_context();
delegate()->OnPepperPluginHung(this, context.plugin_child_id,
context.plugin_path, is_hung);
}
void RenderFrameHostImpl::PepperInstanceClosed(int32_t instance_id) {
delegate()->OnPepperInstanceDeleted(this, instance_id);
pepper_instance_map_.erase(instance_id);
}
void RenderFrameHostImpl::PepperSetVolume(int32_t instance_id, double volume) {
auto plugin_instance_host = pepper_instance_map_.find(instance_id);
DCHECK(plugin_instance_host != pepper_instance_map_.end());
plugin_instance_host->second->SetVolume(volume);
}
#endif // BUILDFLAG(ENABLE_PLUGINS)
void RenderFrameHostImpl::OnCookiesAccessed(
network::mojom::CookieAccessDetailsPtr details) {
EmitCookieWarningsAndMetrics(this, details);
CookieAccessDetails allowed;
CookieAccessDetails blocked;
SplitCookiesIntoAllowedAndBlocked(details, &allowed, &blocked);
if (!allowed.cookie_list.empty())
delegate_->OnCookiesAccessed(this, allowed);
if (!blocked.cookie_list.empty())
delegate_->OnCookiesAccessed(this, blocked);
}
void RenderFrameHostImpl::SetEmbeddingToken(
const base::UnguessableToken& embedding_token) {
// Everything in this method depends on whether the embedding token has
// actually changed, including setting the AXTreeID (backed by the token).
if (embedding_token_ == embedding_token)
return;
embedding_token_ = embedding_token;
// The AXTreeID of a frame is backed by its embedding token, so we need to
// update its AXTreeID, as well as the associated mapping in
// AXActionHandlerRegistry.
const ui::AXTreeID old_id = GetAXTreeID();
ui::AXTreeID ax_tree_id = ui::AXTreeID::FromToken(embedding_token);
DCHECK_NE(old_id, ax_tree_id);
SetAXTreeID(ax_tree_id);
needs_ax_root_id_ = true;
ui::AXActionHandlerRegistry::GetInstance()->SetFrameIDForAXTreeID(
ui::AXActionHandlerRegistry::FrameID(GetProcess()->GetID(), routing_id_),
ax_tree_id);
// Also important to notify the delegate so that the relevant observers can
// adapt to the fact that the AXTreeID has changed for the primary main frame
// (e.g. the WebView needs to update the ID tracking its child accessibility
// tree).
if (IsInPrimaryMainFrame())
delegate_->AXTreeIDForMainFrameHasChanged();
// Propagate the embedding token to the RenderFrameProxyHost representing the
// parent frame if needed, that is, if either this is a cross-process subframe
// or the main frame of an inner web contents (i.e. would need to send it to
// the RenderFrameProxyHost for the outer web contents owning the inner one).
PropagateEmbeddingTokenToParentFrame();
// The accessibility tree for the outermost root frame contains references
// to the focused frame via its AXTreeID, so ensure that we update that.
RenderFrameHostImpl* outermost = GetOutermostMainFrameOrEmbedder();
if (outermost != this)
outermost->UpdateAXTreeData();
}
bool RenderFrameHostImpl::DocumentUsedWebOTP() {
return document_used_web_otp_;
}
void RenderFrameHostImpl::SetFrameTreeNode(FrameTreeNode& frame_tree_node) {
frame_tree_node_ = &frame_tree_node;
SetFrameTree(*frame_tree_node_->frame_tree());
// Setting the FrameTreeNode is only done for FrameTree/FrameTreeNode swaps
// in MPArch (specifically prerender activation). This is to ensure that
// fields such as proxies and ReplicationState are copied over correctly. In
// the new functionality for swapping BrowsingContext on cross
// BrowsingInstance navigations, the BrowsingContextState is the only field
// that will need to be swapped.
switch (features::GetBrowsingContextMode()) {
case (features::BrowsingContextStateImplementationType::
kLegacyOneToOneWithFrameTreeNode):
browsing_context_state_ = frame_tree_node_->render_manager()
->current_frame_host()
->browsing_context_state();
break;
case (features::BrowsingContextStateImplementationType::
kSwapForCrossBrowsingInstanceNavigations):
// TODO(crbug.com/1270671): implement functionality for swapping on cross
// browsing instance navigations as needed. This will likely be removed
// once BrowsingContextState is decoupled from FrameTreeNode.
break;
}
}
void RenderFrameHostImpl::SetFrameTree(FrameTree& frame_tree) {
DCHECK_EQ(frame_tree_node_->frame_tree(), &frame_tree);
frame_tree_ = &frame_tree;
render_view_host()->SetFrameTree(frame_tree);
if (GetRenderWidgetHost()) {
GetRenderWidgetHost()->SetFrameTree(frame_tree);
}
}
void RenderFrameHostImpl::SetPolicyContainerForEarlyCommitAfterCrash(
scoped_refptr<PolicyContainerHost> policy_container_host) {
DCHECK_EQ(lifecycle_state(), LifecycleStateImpl::kSpeculative);
DCHECK(!policy_container_host_);
SetPolicyContainerHost(std::move(policy_container_host));
}
void RenderFrameHostImpl::OnDidRunInsecureContent(const GURL& security_origin,
const GURL& target_url) {
OPTIONAL_TRACE_EVENT2("content", "RenderFrameHostImpl::DidRunInsecureContent",
"security_origin", security_origin, "target_url",
target_url);
// TODO(nick, estark): Should we call FilterURL using this frame's process on
// these parameters? |target_url| seems unused, except for a log message. And
// |security_origin| might be replaceable with the origin of the main frame.
LOG(WARNING) << security_origin << " ran insecure content from "
<< target_url.possibly_invalid_spec();
RecordAction(base::UserMetricsAction("SSL.RanInsecureContent"));
if (base::EndsWith(security_origin.spec(), kDotGoogleDotCom,
base::CompareCase::INSENSITIVE_ASCII)) {
RecordAction(base::UserMetricsAction("SSL.RanInsecureContentGoogle"));
}
frame_tree_->controller().ssl_manager()->DidRunMixedContent(security_origin);
}
void RenderFrameHostImpl::OnDidRunContentWithCertificateErrors() {
OPTIONAL_TRACE_EVENT0(
"content", "RenderFrameHostImpl::OnDidRunContentWithCertificateErrors");
// For RenderFrameHosts that are inactive and going to be discarded, we can
// disregard this message; there's no need to update the UI if the UI will
// never be shown again.
//
// We still process this message for pending-commit RenderFrameHosts. This can
// happen when a subframe's main resource has a certificate error. The
// origin for the last committed navigation entry will get marked as having
// run insecure content and that will carry over to the navigation entry for
// the pending-commit RenderFrameHost when it commits.
//
// Generally our approach for active content with certificate errors follows
// our approach for mixed content (DidRunInsecureContent): when a page loads
// active insecure content, such as a script or iframe, the top-level origin
// gets marked as insecure and that applies to any navigation entry using the
// same renderer process with that same top-level origin.
//
// We shouldn't be receiving this message for speculative RenderFrameHosts
// i.e., before the renderer is told to commit the navigation.
DCHECK_NE(lifecycle_state(), LifecycleStateImpl::kSpeculative);
if (lifecycle_state() != LifecycleStateImpl::kPendingCommit &&
IsInactiveAndDisallowActivation(
DisallowActivationReasonId::kCertificateErrors)) {
return;
}
// To update mixed content status in a fenced frame, we should call
// an outer frame's OnDidRunContentWithCertificateErrors.
// Otherwise, no update can be processed from fenced frames since they have
// their own NavigationController"
if (IsNestedWithinFencedFrame()) {
GetParentOrOuterDocument()->OnDidRunContentWithCertificateErrors();
return;
}
frame_tree_->controller().ssl_manager()->DidRunContentWithCertErrors(
GetMainFrame()->GetLastCommittedOrigin().GetURL());
}
void RenderFrameHostImpl::OnDidDisplayContentWithCertificateErrors() {
OPTIONAL_TRACE_EVENT0(
"content",
"RenderFrameHostImpl::OnDidDisplayContentWithCertificateErrors");
frame_tree_->controller().ssl_manager()->DidDisplayContentWithCertErrors();
}
void RenderFrameHostImpl::IncreaseCommitNavigationCounter() {
if (commit_navigation_sent_counter_ < std::numeric_limits<int>::max())
++commit_navigation_sent_counter_;
else
commit_navigation_sent_counter_ = 0;
}
bool RenderFrameHostImpl::ShouldWaitForUnloadHandlers() const {
return has_unload_handlers() && !IsInBackForwardCache();
}
void RenderFrameHostImpl::AssertNonSpeculativeFrame() const {
if (lifecycle_state() != LifecycleStateImpl::kSpeculative)
return;
NOTREACHED();
base::debug::DumpWithoutCrashing();
}
RenderFrameHostImpl::DocumentAssociatedData::DocumentAssociatedData(
RenderFrameHostImpl& document)
: weak_ptr_factory(&document) {
// Only create page object for the main document as the PageImpl is 1:1 with
// main document.
if (!document.GetParent()) {
PageDelegate* page_delegate = document.frame_tree()->page_delegate();
DCHECK(page_delegate);
owned_page = std::make_unique<PageImpl>(document, *page_delegate);
}
reporting_source = base::UnguessableToken::Create();
}
RenderFrameHostImpl::DocumentAssociatedData::~DocumentAssociatedData() {
while (!services.empty()) {
// DocumentServiceBase unregisters itself at destruction time.
services.back()->WillBeDestroyed(
DocumentServiceDestructionReason::kEndOfDocumentLifetime);
services.back()->ResetAndDeleteThis();
}
// Explicitly clear all user data here, so that the other fields of
// DocumentAssociatedData are still valid while user data is being destroyed.
ClearAllUserData();
}
std::ostream& operator<<(std::ostream& o,
const RenderFrameHostImpl::LifecycleStateImpl& s) {
return o << LifecycleStateImplToString(s);
}
} // namespace content