| // 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 <unordered_map> |
| #include <utility> |
| |
| #include "base/bind.h" |
| #include "base/callback_helpers.h" |
| #include "base/command_line.h" |
| #include "base/compiler_specific.h" |
| #include "base/containers/queue.h" |
| #include "base/debug/alias.h" |
| #include "base/debug/crash_logging.h" |
| #include "base/debug/dump_without_crashing.h" |
| #include "base/i18n/character_encoding.h" |
| #include "base/lazy_instance.h" |
| #include "base/memory/ptr_util.h" |
| #include "base/memory/ref_counted.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/stl_util.h" |
| #include "base/strings/string_number_conversions.h" |
| #include "base/task/current_thread.h" |
| #include "base/task/post_task.h" |
| #include "base/threading/thread_task_runner_handle.h" |
| #include "base/time/time.h" |
| #include "base/trace_event/trace_conversion_helper.h" |
| #include "base/trace_event/traced_value.h" |
| #include "build/build_config.h" |
| #include "build/chromeos_buildflags.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/browser_accessibility_state_impl.h" |
| #include "content/browser/appcache/appcache_navigation_handle.h" |
| #include "content/browser/bad_message.h" |
| #include "content/browser/bluetooth/web_bluetooth_service_impl.h" |
| #include "content/browser/browser_main_loop.h" |
| #include "content/browser/child_process_security_policy_impl.h" |
| #include "content/browser/contacts/contacts_manager_impl.h" |
| #include "content/browser/data_url_loader_factory.h" |
| #include "content/browser/devtools/devtools_instrumentation.h" |
| #include "content/browser/devtools/protocol/audits.h" |
| #include "content/browser/dom_storage/dom_storage_context_wrapper.h" |
| #include "content/browser/download/data_url_blob_reader.h" |
| #include "content/browser/download/mhtml_generation_manager.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/native_file_system_manager_impl.h" |
| #include "content/browser/generic_sensor/sensor_provider_proxy_impl.h" |
| #include "content/browser/geolocation/geolocation_service_impl.h" |
| #include "content/browser/gpu/gpu_data_manager_impl.h" |
| #include "content/browser/idle/idle_manager_impl.h" |
| #include "content/browser/installedapp/installed_app_provider_impl.h" |
| #include "content/browser/loader/file_url_loader_factory.h" |
| #include "content/browser/loader/navigation_url_loader_impl.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/capture/audio_mirroring_manager.h" |
| #include "content/browser/media/media_interface_proxy.h" |
| #include "content/browser/media/webaudio/audio_context_manager_impl.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/payments/payment_app_context_impl.h" |
| #include "content/browser/permissions/permission_controller_impl.h" |
| #include "content/browser/permissions/permission_service_context.h" |
| #include "content/browser/permissions/permission_service_impl.h" |
| #include "content/browser/portal/portal.h" |
| #include "content/browser/prerender/prerender_processor.h" |
| #include "content/browser/presentation/presentation_service_impl.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_impl.h" |
| #include "content/browser/renderer_host/cookie_utils.h" |
| #include "content/browser/renderer_host/cross_process_frame_connector.h" |
| #include "content/browser/renderer_host/debug_urls.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/keep_alive_handle_factory.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/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_delegate_view.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/screen_enumeration/screen_enumeration_impl.h" |
| #include "content/browser/service_worker/service_worker_container_host.h" |
| #include "content/browser/service_worker/service_worker_context_wrapper.h" |
| #include "content/browser/service_worker/service_worker_object_host.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_package/prefetched_signed_exchange_cache.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/websockets/websocket_connector_impl.h" |
| #include "content/browser/webtransport/quic_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_factory_impl.h" |
| #include "content/browser/worker_host/shared_worker_service_impl.h" |
| #include "content/common/associated_interfaces.mojom.h" |
| #include "content/common/content_constants_internal.h" |
| #include "content/common/content_navigation_policy.h" |
| #include "content/common/frame.mojom.h" |
| #include "content/common/frame_messages.h" |
| #include "content/common/navigation_client.mojom.h" |
| #include "content/common/navigation_params.h" |
| #include "content/common/navigation_params_mojom_traits.h" |
| #include "content/common/navigation_params_utils.h" |
| #include "content/common/render_message_filter.mojom.h" |
| #include "content/common/renderer.mojom.h" |
| #include "content/common/state_transitions.h" |
| #include "content/common/unfreezable_frame_messages.h" |
| #include "content/public/browser/ax_event_notification_details.h" |
| #include "content/public/browser/browser_accessibility_state.h" |
| #include "content/public/browser/browser_context.h" |
| #include "content/public/browser/browser_plugin_guest_manager.h" |
| #include "content/public/browser/browser_task_traits.h" |
| #include "content/public/browser/content_browser_client.h" |
| #include "content/public/browser/context_menu_params.h" |
| #include "content/public/browser/download_manager.h" |
| #include "content/public/browser/global_routing_id.h" |
| #include "content/public/browser/idle_manager.h" |
| #include "content/public/browser/media_player_watch_time.h" |
| #include "content/public/browser/network_service_instance.h" |
| #include "content/public/browser/permission_type.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/web_ui_url_loader_factory.h" |
| #include "content/public/common/bindings_policy.h" |
| #include "content/public/common/content_client.h" |
| #include "content/public/common/content_constants.h" |
| #include "content/public/common/content_features.h" |
| #include "content/public/common/content_switches.h" |
| #include "content/public/common/isolated_world_ids.h" |
| #include "content/public/common/navigation_policy.h" |
| #include "content/public/common/network_service_util.h" |
| #include "content/public/common/origin_util.h" |
| #include "content/public/common/page_visibility_state.h" |
| #include "content/public/common/referrer_type_converters.h" |
| #include "content/public/common/three_d_api_types.h" |
| #include "content/public/common/url_constants.h" |
| #include "content/public/common/url_utils.h" |
| #include "device/gamepad/gamepad_monitor.h" |
| #include "media/audio/audio_manager.h" |
| #include "media/base/media_switches.h" |
| #include "media/base/user_input_monitor.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 "mojo/public/cpp/bindings/message.h" |
| #include "mojo/public/cpp/bindings/self_owned_receiver.h" |
| #include "mojo/public/cpp/system/data_pipe.h" |
| #include "net/base/registry_controlled_domains/registry_controlled_domain.h" |
| #include "net/cookies/cookie_constants.h" |
| #include "services/device/public/cpp/device_features.h" |
| #include "services/device/public/mojom/sensor_provider.mojom.h" |
| #include "services/device/public/mojom/wake_lock.mojom.h" |
| #include "services/device/public/mojom/wake_lock_context.mojom.h" |
| #include "services/metrics/public/cpp/ukm_builders.h" |
| #include "services/metrics/public/cpp/ukm_source_id.h" |
| #include "services/network/public/cpp/features.h" |
| #include "services/network/public/cpp/is_potentially_trustworthy.h" |
| #include "services/network/public/cpp/trust_token_operation_authorization.h" |
| #include "services/network/public/cpp/web_sandbox_flags.h" |
| #include "services/network/public/cpp/wrapper_shared_url_loader_factory.h" |
| #include "services/network/public/mojom/cookie_access_observer.mojom.h" |
| #include "services/network/public/mojom/network_service.mojom.h" |
| #include "services/network/public/mojom/url_loader.mojom-shared.h" |
| #include "services/network/public/mojom/web_sandbox_flags.mojom-shared.h" |
| #include "services/service_manager/public/cpp/interface_provider.h" |
| #include "storage/browser/blob/blob_storage_context.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/blob/blob_utils.h" |
| #include "third_party/blink/public/common/feature_policy/document_policy_features.h" |
| #include "third_party/blink/public/common/feature_policy/feature_policy.h" |
| #include "third_party/blink/public/common/features.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/mojom/appcache/appcache.mojom.h" |
| #include "third_party/blink/public/mojom/bluetooth/web_bluetooth.mojom.h" |
| #include "third_party/blink/public/mojom/cache_storage/cache_storage.mojom.h" |
| #include "third_party/blink/public/mojom/choosers/file_chooser.mojom.h" |
| #include "third_party/blink/public/mojom/choosers/popup_menu.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/user_activation_update_types.mojom.h" |
| #include "third_party/blink/public/mojom/geolocation/geolocation_service.mojom.h" |
| #include "third_party/blink/public/mojom/loader/pause_subresource_loading_handle.mojom.h" |
| #include "third_party/blink/public/mojom/loader/resource_load_info.mojom.h" |
| #include "third_party/blink/public/mojom/loader/url_loader_factory_bundle.mojom.h" |
| #include "third_party/blink/public/mojom/security_context/insecure_request_policy.mojom.h" |
| #include "third_party/blink/public/mojom/service_worker/service_worker_object.mojom.h" |
| #include "third_party/blink/public/mojom/sms/webotp_service.mojom.h" |
| #include "third_party/blink/public/mojom/timing/resource_timing.mojom.h" |
| #include "third_party/blink/public/mojom/usb/web_usb_service.mojom.h" |
| #include "third_party/blink/public/mojom/webauthn/virtual_authenticator.mojom.h" |
| #include "ui/accessibility/ax_common.h" |
| #include "ui/accessibility/ax_tree.h" |
| #include "ui/accessibility/ax_tree_id_registry.h" |
| #include "ui/accessibility/ax_tree_update.h" |
| #include "ui/events/event_constants.h" |
| #include "ui/gfx/geometry/quad_f.h" |
| #include "url/gurl.h" |
| #include "url/origin.h" |
| #include "url/url_constants.h" |
| |
| #if defined(OS_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 defined(OS_MAC) |
| #include "content/browser/renderer_host/popup_menu_helper_mac.h" |
| #endif |
| |
| using base::TimeDelta; |
| |
| 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 |
| |
| struct RenderFrameHostOrProxy { |
| RenderFrameHostImpl* const frame; |
| RenderFrameProxyHost* const proxy; |
| |
| RenderFrameHostOrProxy(RenderFrameHostImpl* frame, |
| RenderFrameProxyHost* proxy) |
| : frame(frame), proxy(proxy) { |
| DCHECK(!frame || !proxy) |
| << "Both frame and proxy can't be non-null at the same time"; |
| } |
| |
| explicit operator bool() { return frame || proxy; } |
| |
| FrameTreeNode* GetFrameTreeNode() { |
| if (frame) |
| return frame->frame_tree_node(); |
| if (proxy) |
| return proxy->frame_tree_node(); |
| return nullptr; |
| } |
| }; |
| |
| namespace { |
| |
| #if defined(OS_ANDROID) |
| const void* const kRenderFrameHostAndroidKey = &kRenderFrameHostAndroidKey; |
| #endif // OS_ANDROID |
| |
| // 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; |
| |
| typedef std::unordered_map<GlobalFrameRoutingId, |
| RenderFrameHostImpl*, |
| GlobalFrameRoutingIdHasher> |
| RoutingIDFrameMap; |
| base::LazyInstance<RoutingIDFrameMap>::DestructorAtExit g_routing_id_frame_map = |
| LAZY_INSTANCE_INITIALIZER; |
| |
| using TokenFrameMap = std::unordered_map<base::UnguessableToken, |
| RenderFrameHostImpl*, |
| base::UnguessableTokenHash>; |
| 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::kNetworkRequestDatapipeDrained: |
| return BackForwardCacheMetrics::NotRestoredReason:: |
| kNetworkRequestDatapipeDrained; |
| 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: |
| 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; |
| auto* frame_tree_node = render_frame_host_->frame_tree_node(); |
| GetContentClient()->SetActiveURL(frame_tree_node->current_url(), |
| frame_tree_node->frame_tree() |
| ->root() |
| ->current_origin() |
| .GetDebugString()); |
| return true; |
| } |
| |
| void DidDispatchOrReject(mojo::Message* message, bool accepted) override { |
| GetContentClient()->SetActiveURL(GURL(), ""); |
| debug_url_set_ = false; |
| } |
| |
| private: |
| 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() |
| ->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()); |
| } |
| |
| RenderFrameHostImpl* const 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) {} |
| |
| 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_; |
| |
| DISALLOW_COPY_AND_ASSIGN(RemoterFactoryImpl); |
| }; |
| #endif // BUILDFLAG(ENABLE_MEDIA_REMOTING) |
| |
| using FrameCallback = base::RepeatingCallback<void(RenderFrameHostImpl*)>; |
| void ForEachFrame(RenderFrameHostImpl* root_frame_host, |
| const FrameCallback& frame_callback) { |
| DCHECK_CURRENTLY_ON(BrowserThread::UI); |
| |
| FrameTree* frame_tree = root_frame_host->frame_tree(); |
| DCHECK_EQ(root_frame_host, frame_tree->GetMainFrame()); |
| |
| for (FrameTreeNode* node : frame_tree->Nodes()) { |
| RenderFrameHostImpl* frame_host = node->current_frame_host(); |
| RenderFrameHostImpl* pending_frame_host = |
| node->render_manager()->speculative_frame_host(); |
| if (frame_host) |
| frame_callback.Run(frame_host); |
| if (pending_frame_host) |
| frame_callback.Run(pending_frame_host); |
| } |
| } |
| |
| 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 base::UnguessableToken& frame_token) { |
| auto it = g_token_frame_map.Get().find(frame_token); |
| RenderFrameHostImpl* rfh = nullptr; |
| RenderFrameProxyHost* proxy = nullptr; |
| if (it != g_token_frame_map.Get().end()) { |
| // 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. |
| rfh = |
| process_id == it->second->GetProcess()->GetID() ? it->second : nullptr; |
| } else { |
| proxy = RenderFrameProxyHost::FromFrameToken(process_id, frame_token); |
| } |
| return RenderFrameHostOrProxy(rfh, proxy); |
| } |
| |
| // Takes the lower 31 bits of the metric-name-hash of a Mojo interface |name|. |
| base::Histogram::Sample HashInterfaceNameToHistogramSample( |
| base::StringPiece name) { |
| return base::strict_cast<base::Histogram::Sample>( |
| static_cast<int32_t>(base::HashMetricName(name) & 0x7fffffffull)); |
| } |
| |
| // 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* 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* 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 = |
| BrowserContext::GetDownloadManager(browser_context); |
| 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); |
| } |
| |
| void RecordWebPlatformSecurityMetrics(RenderFrameHostImpl* rfh, |
| NavigationRequest* navigation_request) { |
| ContentBrowserClient* client = GetContentClient()->browser(); |
| if (rfh->cross_origin_opener_policy().value == |
| network::mojom::CrossOriginOpenerPolicyValue::kSameOrigin) { |
| client->LogWebFeatureForCurrentPage( |
| rfh, blink::mojom::WebFeature::kCrossOriginOpenerPolicySameOrigin); |
| } |
| if (rfh->cross_origin_opener_policy().value == |
| network::mojom::CrossOriginOpenerPolicyValue::kSameOriginAllowPopups) { |
| client->LogWebFeatureForCurrentPage( |
| rfh, blink::mojom::WebFeature:: |
| kCrossOriginOpenerPolicySameOriginAllowPopups); |
| } |
| |
| if (rfh->cross_origin_embedder_policy().value == |
| network::mojom::CrossOriginEmbedderPolicyValue::kRequireCorp) { |
| client->LogWebFeatureForCurrentPage( |
| rfh, blink::mojom::WebFeature::kCrossOriginEmbedderPolicyRequireCorp); |
| } |
| |
| if (rfh->cross_origin_opener_policy().value == |
| network::mojom::CrossOriginOpenerPolicyValue::kSameOriginPlusCoep) { |
| client->LogWebFeatureForCurrentPage( |
| rfh, blink::mojom::WebFeature::kCoopAndCoepIsolated); |
| } |
| |
| if (rfh->cross_origin_opener_policy().reporting_endpoint || |
| rfh->cross_origin_opener_policy().report_only_reporting_endpoint) { |
| client->LogWebFeatureForCurrentPage( |
| rfh, blink::mojom::WebFeature::kCrossOriginOpenerPolicyReporting); |
| } |
| |
| // Record iframes embedded in cross-origin contexts without a CSP |
| // frame-ancestor directive. |
| bool is_embedded_in_cross_origin_context = false; |
| RenderFrameHostImpl* parent = rfh->frame_tree_node()->parent(); |
| while (parent) { |
| if (!parent->GetLastCommittedOrigin().IsSameOriginWith( |
| rfh->GetLastCommittedOrigin())) { |
| is_embedded_in_cross_origin_context = true; |
| break; |
| } |
| parent = parent->frame_tree_node()->parent(); |
| } |
| |
| bool has_embedding_control = false; |
| if (!navigation_request->response()) { |
| // This navigation did not result in a network request. The embedding of |
| // the frame is not controlled by network headers. |
| has_embedding_control = true; |
| } else { |
| // Check if the request has a CSP frame-ancestor directive. |
| for (const auto& csp : navigation_request->response() |
| ->parsed_headers->content_security_policy) { |
| if (csp->header->type == |
| network::mojom::ContentSecurityPolicyType::kEnforce && |
| csp->directives.contains( |
| network::mojom::CSPDirectiveName::FrameAncestors)) { |
| has_embedding_control = true; |
| break; |
| } |
| } |
| } |
| |
| if (is_embedded_in_cross_origin_context && !has_embedding_control && |
| !navigation_request->IsErrorPage()) { |
| client->LogWebFeatureForCurrentPage( |
| rfh, |
| blink::mojom::WebFeature::kCrossOriginSubframeWithoutEmbeddingControl); |
| } |
| } |
| |
| // 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 Feature Policy feature enabled. |
| bool ParentNeedsTrustTokenFeaturePolicy( |
| const mojom::BeginNavigationParams& begin_params) { |
| if (!begin_params.trust_token_params) |
| return false; |
| |
| return network::DoesTrustTokenOperationRequireFeaturePolicy( |
| begin_params.trust_token_params->type); |
| } |
| |
| // Analyzes trusted sources of a frame's trust-token-redemption Feature 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* parent, |
| const mojom::CommitNavigationParams& commit_params, |
| const url::Origin& subframe_origin) { |
| // For main frame loads, the frame's feature policy is determined entirely by |
| // response headers, which are provided by the renderer. |
| if (!parent) |
| return network::mojom::TrustTokenRedemptionPolicy::kPotentiallyPermit; |
| |
| const blink::FeaturePolicy* parent_policy = parent->feature_policy(); |
| blink::ParsedFeaturePolicy container_policy = |
| commit_params.frame_policy.container_policy; |
| |
| auto subframe_policy = blink::FeaturePolicy::CreateFromParentPolicy( |
| parent_policy, container_policy, subframe_origin); |
| |
| if (subframe_policy->IsFeatureEnabled( |
| blink::mojom::FeaturePolicyFeature::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 Feature 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 Feature Policy state to determine whether the feature is enabled. |
| network::mojom::TrustTokenRedemptionPolicy |
| DetermineAfterCommitWhetherToForbidTrustTokenRedemption( |
| RenderFrameHostImpl* impl) { |
| return impl->IsFeatureEnabled( |
| blink::mojom::FeaturePolicyFeature::kTrustTokenRedemption) |
| ? network::mojom::TrustTokenRedemptionPolicy::kPotentiallyPermit |
| : network::mojom::TrustTokenRedemptionPolicy::kForbid; |
| } |
| |
| // Returns the string corresponding to LifecycleState, used for logging crash |
| // keys. |
| const char* LifecycleStateToString(RenderFrameHostImpl::LifecycleState state) { |
| using LifecycleState = RenderFrameHostImpl::LifecycleState; |
| switch (state) { |
| case LifecycleState::kSpeculative: |
| return "Speculative"; |
| case LifecycleState::kActive: |
| return "Active"; |
| case LifecycleState::kInBackForwardCache: |
| return "InBackForwardCache"; |
| case LifecycleState::kRunningUnloadHandlers: |
| return "RunningUnloadHandlers"; |
| case LifecycleState::kReadyToBeDeleted: |
| return "ReadyToDeleted"; |
| } |
| } |
| |
| // Verify that |browser_side_origin| and |renderer_side_origin| match. See also |
| // https://crbug.com/888079. |
| void VerifyThatBrowserAndRendererCalculatedOriginsToCommitMatch( |
| NavigationRequest* navigation_request, |
| const mojom::DidCommitProvisionalLoadParams& params) { |
| DCHECK(navigation_request); |
| |
| // Ignore for now cases where the NavigationRequest is in an unexpectedly |
| // early state. See also the NavigationRequestBrowserTest.VerifySameDocument |
| // test. |
| if (navigation_request->state() < NavigationRequest::WILL_PROCESS_RESPONSE) |
| return; |
| |
| // Ignore for now opaque |renderer_side_origin| origins. This effectively |
| // ignores the following scenarios: |
| // - error frames (i.e. navigation_request->GetNetErrorCode() != net::OK; |
| // see also the NavigationBrowserTest.FailedNavigation test) |
| // - sandboxed frames (see also https://crbug.com/1145139#c5) |
| // - comparison of precursor origins |
| // - TODO(https://crbug.com/1041376): mismatched nonces (even if precursor |
| // origins would have matched) |
| const url::Origin& renderer_side_origin = params.origin; |
| if (renderer_side_origin.opaque()) |
| return; |
| |
| // Ignore about:blank navigations, because browser-side calculated the origin |
| // based on the initiator of the navigation, but renderer-side takes the |
| // origin from the frame parent or opener. Example scenario (exercised by |
| // FrameNavigationEntry_RecreatedSubframeToBlank) starts with a.com(data:) and |
| // has the subframe navigate itself to about:blank. |
| if (navigation_request->GetURL().IsAboutBlank()) |
| return; |
| |
| url::Origin browser_side_origin = |
| navigation_request->GetOriginForURLLoaderFactory(); |
| DCHECK_EQ(browser_side_origin, renderer_side_origin); |
| } |
| |
| } // namespace |
| |
| class RenderFrameHostImpl::DroppedInterfaceRequestLogger |
| : public blink::mojom::BrowserInterfaceBroker { |
| public: |
| explicit DroppedInterfaceRequestLogger( |
| mojo::PendingReceiver<blink::mojom::BrowserInterfaceBroker> receiver) { |
| receiver_.Bind(std::move(receiver)); |
| } |
| |
| ~DroppedInterfaceRequestLogger() override { |
| UMA_HISTOGRAM_EXACT_LINEAR("RenderFrameHostImpl.DroppedInterfaceRequests", |
| num_dropped_requests_, 20); |
| } |
| |
| protected: |
| // blink::mojom::BrowserInterfaceBroker |
| void GetInterface(mojo::GenericPendingReceiver receiver) override { |
| ++num_dropped_requests_; |
| auto interface_name = receiver.interface_name().value_or(""); |
| base::UmaHistogramSparse( |
| "RenderFrameHostImpl.DroppedInterfaceRequestName", |
| HashInterfaceNameToHistogramSample(interface_name)); |
| DLOG(WARNING) |
| << "InterfaceRequest was dropped, the document is no longer active: " |
| << interface_name; |
| } |
| |
| private: |
| mojo::Receiver<blink::mojom::BrowserInterfaceBroker> receiver_{this}; |
| int num_dropped_requests_ = 0; |
| |
| DISALLOW_COPY_AND_ASSIGN(DroppedInterfaceRequestLogger); |
| }; |
| |
| struct PendingNavigation { |
| mojom::CommonNavigationParamsPtr common_params; |
| mojom::BeginNavigationParamsPtr begin_navigation_params; |
| scoped_refptr<network::SharedURLLoaderFactory> blob_url_loader_factory; |
| mojo::PendingAssociatedRemote<mojom::NavigationClient> navigation_client; |
| mojo::PendingRemote<blink::mojom::NavigationInitiator> navigation_initiator; |
| |
| PendingNavigation( |
| mojom::CommonNavigationParamsPtr common_params, |
| mojom::BeginNavigationParamsPtr begin_navigation_params, |
| scoped_refptr<network::SharedURLLoaderFactory> blob_url_loader_factory, |
| mojo::PendingAssociatedRemote<mojom::NavigationClient> navigation_client, |
| mojo::PendingRemote<blink::mojom::NavigationInitiator> |
| navigation_initiator); |
| }; |
| |
| PendingNavigation::PendingNavigation( |
| mojom::CommonNavigationParamsPtr common_params, |
| mojom::BeginNavigationParamsPtr begin_navigation_params, |
| scoped_refptr<network::SharedURLLoaderFactory> blob_url_loader_factory, |
| mojo::PendingAssociatedRemote<mojom::NavigationClient> navigation_client, |
| mojo::PendingRemote<blink::mojom::NavigationInitiator> navigation_initiator) |
| : 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)), |
| navigation_initiator(std::move(navigation_initiator)) {} |
| |
| // static |
| RenderFrameHost* RenderFrameHost::FromID(GlobalFrameRoutingId id) { |
| return RenderFrameHostImpl::FromID(id); |
| } |
| |
| // static |
| RenderFrameHost* RenderFrameHost::FromID(int render_process_id, |
| int render_frame_id) { |
| return RenderFrameHostImpl::FromID( |
| GlobalFrameRoutingId(render_process_id, render_frame_id)); |
| } |
| |
| // static |
| RenderFrameHost* RenderFrameHost::FromFrameToken( |
| int process_id, |
| const base::UnguessableToken& token) { |
| return RenderFrameHostImpl::FromFrameToken(process_id, token); |
| } |
| |
| // static |
| void RenderFrameHost::AllowInjectingJavaScript() { |
| g_allow_injecting_javascript = true; |
| } |
| |
| // static |
| RenderFrameHostImpl* RenderFrameHostImpl::FromID(GlobalFrameRoutingId 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( |
| GlobalFrameRoutingId(render_process_id, render_frame_id)); |
| } |
| |
| // static |
| RenderFrameHostImpl* RenderFrameHostImpl::FromFrameToken( |
| int process_id, |
| const base::UnguessableToken& frame_token) { |
| 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; |
| |
| // TODO(tonikitoo): Consider killing the renderer when this happens |
| if (it->second->GetProcess()->GetID() != process_id) |
| return nullptr; |
| |
| return it->second; |
| } |
| |
| // static |
| RenderFrameHost* RenderFrameHost::FromAXTreeID(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::AXTreeIDRegistry::FrameID frame_id = |
| ui::AXTreeIDRegistry::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(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(); |
| } |
| |
| RenderFrameHostImpl::RenderFrameHostImpl( |
| SiteInstance* site_instance, |
| scoped_refptr<RenderViewHostImpl> render_view_host, |
| RenderFrameHostDelegate* delegate, |
| FrameTree* frame_tree, |
| FrameTreeNode* frame_tree_node, |
| int32_t routing_id, |
| const base::UnguessableToken& frame_token, |
| bool renderer_initiated_creation, |
| LifecycleState lifecycle_state) |
| : render_view_host_(std::move(render_view_host)), |
| delegate_(delegate), |
| site_instance_(static_cast<SiteInstanceImpl*>(site_instance)), |
| agent_scheduling_group_(site_instance_->GetAgentSchedulingGroup()), |
| frame_tree_(frame_tree), |
| frame_tree_node_(frame_tree_node), |
| parent_(frame_tree_node_->parent()), |
| routing_id_(routing_id), |
| beforeunload_timeout_delay_(RenderViewHostImpl::kUnloadTimeout), |
| last_navigation_previews_state_( |
| blink::PreviewsTypes::PREVIEWS_UNSPECIFIED), |
| waiting_for_init_(renderer_initiated_creation), |
| push_messaging_manager_( |
| nullptr, |
| base::OnTaskRunnerDeleter(base::CreateSequencedTaskRunner( |
| {ServiceWorkerContext::GetCoreThreadId()}))), |
| frame_token_(frame_token), |
| keep_alive_timeout_(base::TimeDelta::FromSeconds(30)), |
| subframe_unload_timeout_(RenderViewHostImpl::kUnloadTimeout), |
| media_device_id_salt_base_( |
| BrowserContext::CreateRandomMediaDeviceIDSalt()), |
| lifecycle_state_(lifecycle_state) { |
| DCHECK(delegate_); |
| DCHECK(lifecycle_state_ == LifecycleState::kSpeculative || |
| lifecycle_state_ == LifecycleState::kActive); |
| |
| agent_scheduling_group().AddRoute(routing_id_, this); |
| g_routing_id_frame_map.Get().emplace( |
| GlobalFrameRoutingId(GetProcess()->GetID(), routing_id_), this); |
| g_token_frame_map.Get().insert(std::make_pair(frame_token_, this)); |
| site_instance_->AddObserver(this); |
| GetProcess()->AddObserver(this); |
| GetSiteInstance()->IncrementActiveFrameCount(); |
| |
| if (parent_) { |
| cross_origin_embedder_policy_ = parent_->cross_origin_embedder_policy(); |
| |
| // New child frames should inherit the nav_entry_id of their parent. |
| set_nav_entry_id(parent_->nav_entry_id()); |
| } |
| |
| // The initial empty document inherits its policy container from its creator. |
| // The creator is either its parent for iframes 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(). |
| // |
| // Note 2: After creating a new frame, blink emits a second IPC |
| // (DidCommitProvisionalLoad) for committing the initial empty |
| // document. However, since the RenderFrameHost has already been created, we |
| // cannot use DidCommitProvisionalLoad to set the policy container, since we |
| // could run into a race condition. Hence we need to set the policy container |
| // immediately when creating the RenderFrameHost here. |
| if (lifecycle_state_ != LifecycleState::kSpeculative) { |
| // Creating a RFH in kActive state implies that it is the RFH for a |
| // newly-created FTN, which should not have committed a real load yet. |
| DCHECK(!frame_tree_node_->has_committed_real_load()); |
| if (parent_) { |
| policy_container_host_ = parent_->policy_container_host()->Clone(); |
| } else if (frame_tree_node_->opener()) { |
| policy_container_host_ = frame_tree_node_->opener() |
| ->current_frame_host() |
| ->policy_container_host() |
| ->Clone(); |
| } else { |
| policy_container_host_ = std::make_unique<PolicyContainerHost>(); |
| } |
| } |
| |
| SetUpMojoIfNeeded(); |
| |
| 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 in a different SiteInstance from their parent. |
| // |
| // Local roots require a RenderWidget for input/layout/painting. |
| if (!parent_ || IsCrossProcessSubframe()) { |
| if (!parent_) { |
| // 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_->render_widget_delegate(), agent_scheduling_group_, |
| widget_routing_id, /*hidden=*/true); |
| #if defined(OS_ANDROID) |
| owned_render_widget_host_->SetForceEnableZoom( |
| delegate_->GetOrCreateWebPreferences().force_enable_zoom); |
| #endif // defined(OS_ANDROID) |
| } |
| |
| if (is_main_frame()) |
| GetLocalRenderWidgetHost()->SetIntersectsViewport(true); |
| GetLocalRenderWidgetHost()->SetFrameDepth(frame_tree_node_->depth()); |
| GetLocalRenderWidgetHost()->input_router()->SetFrameTreeNodeId( |
| frame_tree_node_->frame_tree_node_id()); |
| } |
| ResetFeaturePolicy(); |
| |
| // 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_ = |
| CrossOriginOpenerPolicyReporter::NextVirtualBrowsingContextGroup(); |
| |
| // IdleManager should be unique per RenderFrame to provide proper isolation |
| // of overrides. |
| idle_manager_ = |
| std::make_unique<IdleManagerImpl>(GetProcess()->GetBrowserContext()); |
| } |
| |
| RenderFrameHostImpl::~RenderFrameHostImpl() { |
| // 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. |
| ClearWebUI(); |
| |
| SetLastCommittedSiteInfo(GURL()); |
| if (last_committed_document_priority_) { |
| GetProcess()->UpdateFrameWithPriority(last_committed_document_priority_, |
| base::nullopt); |
| } |
| |
| g_token_frame_map.Get().erase(frame_token_); |
| |
| site_instance_->RemoveObserver(this); |
| GetProcess()->RemoveObserver(this); |
| |
| const bool was_created = render_frame_created_; |
| render_frame_created_ = false; |
| if (was_created) |
| delegate_->RenderFrameDeleted(this); |
| |
| // 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 SiteInstance, the |
| // DecrementActiveFrameCount call will trigger the deletion of the |
| // SiteInstance's proxies. |
| GetSiteInstance()->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 |
| // UnfreezableFrameMsg_Unload for the RenderFrame to replace itself with a |
| // RenderFrameProxy and release its associated resources. |
| // |lifecycle_state_| is advanced to LifeCycleState::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 UnfreezableFrameMsg_Delete for the RenderFrame to detach itself and |
| // release its associated resources. If the subframe contains an unload |
| // handler, |lifecycle_state_| is advanced to |
| // LifeCycleState::kRunningUnloadHandlers to track that the detach is in |
| // progress; otherwise, it is advanced directly to |
| // LifeCycleState::kReadyToBeDeleted. |
| // |
| // For BackForwardCache 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 UnfreezableFrameMsg_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. |
| // - check |IsCurrent()|, because the RenderFrames in an InterstitialPageImpl |
| // are never considered current. |
| // |
| // Directly comparing against |RenderViewHostImpl::GetMainFrame()| still has |
| // one additional subtlety though: |GetMainFrame()| can sometimes return a |
| // speculative RFH! For subframes, this obviously does not matter: a subframe |
| // will always pass the condition |render_view_host_->GetMainFrame() != 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 |GetMainFrame()| will never return the speculative main frame |
| // being deleted, since it must have already been unset. |
| if (was_created && render_view_host_->GetMainFrame() != this) |
| CHECK(IsPendingDeletion() || IsInBackForwardCache()); |
| |
| agent_scheduling_group().RemoveRoute(routing_id_); |
| g_routing_id_frame_map.Get().erase( |
| GlobalFrameRoutingId(GetProcess()->GetID(), 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( |
| true /* proceed */, false /* treat_as_final_completion_callback */, |
| this, true /* is_frame_being_destroyed */, approx_renderer_start_time, |
| base::TimeTicks::Now()); |
| } |
| |
| if (prefetched_signed_exchange_cache_) |
| prefetched_signed_exchange_cache_->RecordHistograms(); |
| } |
| |
| int RenderFrameHostImpl::GetRoutingID() { |
| return routing_id_; |
| } |
| |
| const base::UnguessableToken& RenderFrameHostImpl::GetFrameToken() { |
| return frame_token_; |
| } |
| |
| ui::AXTreeID RenderFrameHostImpl::GetAXTreeID() { |
| return ax_tree_id(); |
| } |
| |
| const base::UnguessableToken& 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_, LifecycleState::kActive); |
| SetLifecycleState(LifecycleState::kInBackForwardCache); |
| // Pages in the back-forward cache are automatically evicted after a certain |
| // time. |
| if (!GetParent()) |
| StartBackForwardCacheEvictionTimer(); |
| for (auto& child : children_) |
| child->current_frame_host()->DidEnterBackForwardCache(); |
| |
| if (service_worker_container_hosts_.empty()) |
| return; |
| RunOrPostTaskOnThread( |
| FROM_HERE, ServiceWorkerContext::GetCoreThreadId(), |
| base::BindOnce( |
| [](const std::map<std::string, |
| base::WeakPtr<ServiceWorkerContainerHost>>& hosts) { |
| for (auto host : hosts) { |
| if (host.second) { |
| host.second->OnEnterBackForwardCache(); |
| } |
| } |
| }, |
| service_worker_container_hosts_)); |
| } |
| |
| // The frame as been restored from the BackForwardCache. |
| void RenderFrameHostImpl::WillLeaveBackForwardCache() { |
| TRACE_EVENT0("navigation", "RenderFrameHostImpl::LeaveBackForwardCache"); |
| DCHECK(IsBackForwardCacheEnabled()); |
| DCHECK_EQ(lifecycle_state_, LifecycleState::kInBackForwardCache); |
| if (back_forward_cache_eviction_timer_.IsRunning()) |
| back_forward_cache_eviction_timer_.Stop(); |
| for (auto& child : children_) |
| child->current_frame_host()->WillLeaveBackForwardCache(); |
| |
| if (service_worker_container_hosts_.empty()) |
| return; |
| RunOrPostTaskOnThread( |
| FROM_HERE, ServiceWorkerContext::GetCoreThreadId(), |
| base::BindOnce( |
| [](const std::map<std::string, |
| base::WeakPtr<ServiceWorkerContainerHost>>& hosts) { |
| for (auto host : hosts) { |
| if (host.second) |
| host.second->OnRestoreFromBackForwardCache(); |
| } |
| }, |
| service_worker_container_hosts_)); |
| } |
| |
| 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(base::StringPiece reason) { |
| back_forward_cache_disabled_reasons_.insert(reason.as_string()); |
| MaybeEvictFromBackForwardCache(); |
| } |
| |
| void RenderFrameHostImpl::DisableProactiveBrowsingInstanceSwapForTesting() { |
| // This should only be called on main frames. |
| DCHECK(!GetParent()); |
| 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::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() { |
| return site_instance_.get(); |
| } |
| |
| RenderProcessHost* RenderFrameHostImpl::GetProcess() { |
| return agent_scheduling_group_.GetProcess(); |
| } |
| |
| AgentSchedulingGroupHost& RenderFrameHostImpl::agent_scheduling_group() { |
| return agent_scheduling_group_; |
| } |
| |
| RenderFrameHostImpl* RenderFrameHostImpl::GetParent() { |
| return parent_; |
| } |
| |
| std::vector<RenderFrameHost*> RenderFrameHostImpl::GetFramesInSubtree() { |
| std::vector<RenderFrameHost*> frame_hosts; |
| for (FrameTreeNode* node : frame_tree_->SubtreeNodes(frame_tree_node())) |
| frame_hosts.push_back(node->current_frame_host()); |
| return frame_hosts; |
| } |
| |
| bool RenderFrameHostImpl::IsDescendantOf(RenderFrameHost* ancestor) { |
| if (!ancestor || !static_cast<RenderFrameHostImpl*>(ancestor)->child_count()) |
| return false; |
| |
| for (RenderFrameHostImpl* current = GetParent(); current; |
| current = current->GetParent()) { |
| if (current == ancestor) |
| return true; |
| } |
| return false; |
| } |
| |
| int RenderFrameHostImpl::GetFrameTreeNodeId() { |
| return frame_tree_node_->frame_tree_node_id(); |
| } |
| |
| base::UnguessableToken RenderFrameHostImpl::GetDevToolsFrameToken() { |
| return frame_tree_node_->devtools_frame_token(); |
| } |
| |
| base::Optional<base::UnguessableToken> |
| RenderFrameHostImpl::GetEmbeddingToken() { |
| return embedding_token_; |
| } |
| |
| const std::string& RenderFrameHostImpl::GetFrameName() { |
| return frame_tree_node_->frame_name(); |
| } |
| |
| bool RenderFrameHostImpl::IsFrameDisplayNone() { |
| return frame_tree_node()->frame_owner_properties().is_display_none; |
| } |
| |
| const base::Optional<gfx::Size>& RenderFrameHostImpl::GetFrameSize() { |
| return frame_size_; |
| } |
| |
| size_t RenderFrameHostImpl::GetFrameDepth() { |
| return frame_tree_node()->depth(); |
| } |
| |
| bool RenderFrameHostImpl::IsCrossProcessSubframe() { |
| if (!parent_) |
| return false; |
| return GetSiteInstance() != parent_->GetSiteInstance(); |
| } |
| |
| const GURL& RenderFrameHostImpl::GetLastCommittedURL() { |
| return last_committed_url_; |
| } |
| |
| const url::Origin& RenderFrameHostImpl::GetLastCommittedOrigin() { |
| 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() { |
| network::mojom::URLLoaderFactoryParamsPtr factory_params = |
| RenderFrameHostImpl::CreateURLLoaderFactoryParamsForMainWorld( |
| FindLatestNavigationRequestThatIsStillCommitting(), "Unused tag"); |
| DCHECK(!factory_params->isolation_info.IsEmpty()); |
| return factory_params->isolation_info; |
| } |
| |
| void RenderFrameHostImpl::GetCanonicalUrlForSharing( |
| mojom::Frame::GetCanonicalUrlForSharingCallback callback) { |
| // TODO(https://crbug.com/859110): Remove this once frame_ can no longer be |
| // null. |
| if (IsRenderFrameLive()) { |
| frame_->GetCanonicalUrlForSharing(std::move(callback)); |
| } else { |
| std::move(callback).Run(base::nullopt); |
| } |
| } |
| |
| void RenderFrameHostImpl::GetSerializedHtmlWithLocalLinks( |
| const base::flat_map<GURL, base::FilePath>& url_map, |
| const base::flat_map<base::UnguessableToken, base::FilePath>& |
| frame_token_map, |
| bool save_with_empty_url, |
| mojo::PendingRemote<mojom::FrameHTMLSerializerHandler> serializer_handler) { |
| // TODO(https://crbug.com/859110): Remove once frame_ can no longer be null. |
| if (!IsRenderFrameLive()) |
| return; |
| |
| frame_->GetSerializedHtmlWithLocalLinks(url_map, frame_token_map, |
| save_with_empty_url, |
| std::move(serializer_handler)); |
| } |
| |
| void RenderFrameHostImpl::SetWantErrorMessageStackTrace() { |
| GetNavigationControl()->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) { |
| // By providing null |navigation_request| we will always use the last |
| // committed Origin and ClientSecurityState (using GetPageUkmSourceId() |
| // does the same for UKM). If the caller wanted a factory associated to a |
| // navigation about to commit, the params generated won't be correct. There is |
| // no good way of fixing this before RenderDocumentHost (ie swapping |
| // RenderFrameHost on each navigation). |
| NavigationRequest* navigation_request = nullptr; |
| |
| return CreateNetworkServiceDefaultFactoryAndObserve( |
| CreateURLLoaderFactoryParamsForMainWorld( |
| navigation_request, "RFHI::CreateNetworkServiceDefaultFactory"), |
| ukm::SourceIdObj::FromInt64(GetPageUkmSourceId()), |
| std::move(default_factory_receiver)); |
| } |
| |
| void RenderFrameHostImpl::MarkIsolatedWorldsAsRequiringSeparateURLLoaderFactory( |
| 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 |
| // 3) a commit has taken place before (i.e. the frame has received a factory |
| // bundle before). |
| if (push_to_renderer_now && insertion_took_place && |
| has_committed_any_navigation_) { |
| std::unique_ptr<blink::PendingURLLoaderFactoryBundle> |
| subresource_loader_factories = |
| std::make_unique<blink::PendingURLLoaderFactoryBundle>(); |
| subresource_loader_factories->pending_isolated_world_factories() = |
| CreateURLLoaderFactoriesForIsolatedWorlds( |
| FindLatestNavigationRequestThatIsStillCommitting(), |
| isolated_world_origins); |
| GetNavigationControl()->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( |
| NavigationRequest* navigation_request, |
| const base::flat_set<url::Origin>& isolated_world_origins) { |
| url::Origin main_world_origin; |
| net::IsolationInfo isolation_info; |
| network::mojom::ClientSecurityStatePtr client_security_state; |
| network::mojom::TrustTokenRedemptionPolicy trust_token_redemption_policy = |
| network::mojom::TrustTokenRedemptionPolicy::kForbid; |
| ExtractFactoryParamsFromNavigationRequestOrLastCommittedNavigation( |
| navigation_request, &main_world_origin, &isolation_info, |
| &client_security_state, nullptr /* coep_reporter_remote */, |
| &trust_token_redemption_policy); |
| |
| 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, main_world_origin, isolation_info, |
| mojo::Clone(client_security_state), 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, false /* discard_duplicates */); |
| } |
| |
| void RenderFrameHostImpl::ExecuteJavaScriptMethod( |
| const base::string16& object_name, |
| const base::string16& method_name, |
| base::Value arguments, |
| JavaScriptResultCallback callback) { |
| DCHECK_CURRENTLY_ON(BrowserThread::UI); |
| DCHECK(arguments.is_list()); |
| CHECK(CanExecuteJavaScript()); |
| |
| const bool wants_result = !callback.is_null(); |
| GetNavigationControl()->JavaScriptMethodExecuteRequest( |
| object_name, method_name, std::move(arguments), wants_result, |
| std::move(callback)); |
| } |
| |
| void RenderFrameHostImpl::ExecuteJavaScript(const base::string16& javascript, |
| JavaScriptResultCallback callback) { |
| DCHECK_CURRENTLY_ON(BrowserThread::UI); |
| CHECK(CanExecuteJavaScript()); |
| |
| const bool wants_result = !callback.is_null(); |
| GetNavigationControl()->JavaScriptExecuteRequest(javascript, wants_result, |
| std::move(callback)); |
| } |
| |
| void RenderFrameHostImpl::ExecuteJavaScriptInIsolatedWorld( |
| const base::string16& 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); |
| |
| const bool wants_result = !callback.is_null(); |
| GetNavigationControl()->JavaScriptExecuteRequestInIsolatedWorld( |
| javascript, wants_result, world_id, std::move(callback)); |
| } |
| |
| void RenderFrameHostImpl::ExecuteJavaScriptForTests( |
| const base::string16& javascript, |
| JavaScriptResultCallback callback, |
| int32_t world_id) { |
| DCHECK_CURRENTLY_ON(BrowserThread::UI); |
| |
| const bool has_user_gesture = false; |
| const bool wants_result = !callback.is_null(); |
| GetNavigationControl()->JavaScriptExecuteRequestForTests( // IN-TEST |
| javascript, wants_result, has_user_gesture, world_id, |
| std::move(callback)); |
| } |
| |
| void RenderFrameHostImpl::ExecuteJavaScriptWithUserGestureForTests( |
| const base::string16& javascript, |
| int32_t world_id) { |
| DCHECK_CURRENTLY_ON(BrowserThread::UI); |
| |
| // 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); |
| |
| const bool has_user_gesture = true; |
| GetNavigationControl()->JavaScriptExecuteRequestForTests( // IN-TEST |
| javascript, false, has_user_gesture, world_id, base::NullCallback()); |
| } |
| |
| 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() { |
| return render_view_host_.get(); |
| } |
| |
| service_manager::InterfaceProvider* RenderFrameHostImpl::GetRemoteInterfaces() { |
| return remote_interfaces_.get(); |
| } |
| |
| blink::AssociatedInterfaceProvider* |
| RenderFrameHostImpl::GetRemoteAssociatedInterfaces() { |
| if (!remote_associated_interfaces_) { |
| mojo::AssociatedRemote<blink::mojom::AssociatedInterfaceProvider> |
| remote_interfaces; |
| if (agent_scheduling_group().GetChannel()) { |
| agent_scheduling_group().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. |
| ignore_result( |
| 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 agent_scheduling_group().Send(message); |
| } |
| |
| bool RenderFrameHostImpl::OnMessageReceived(const IPC::Message& msg) { |
| // Only process messages if the RenderFrame is alive. |
| if (!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; |
| |
| bool handled = true; |
| IPC_BEGIN_MESSAGE_MAP(RenderFrameHostImpl, msg) |
| IPC_MESSAGE_HANDLER(FrameHostMsg_Unload_ACK, OnUnloadACK) |
| IPC_MESSAGE_HANDLER(FrameHostMsg_ContextMenu, OnContextMenu) |
| IPC_END_MESSAGE_MAP() |
| |
| // No further actions here, since we may have been deleted. |
| return handled; |
| } |
| |
| void RenderFrameHostImpl::OnAssociatedInterfaceRequest( |
| const std::string& interface_name, |
| mojo::ScopedInterfaceEndpointHandle handle) { |
| // TODO(https://crbug.com/1123438) It is not understood why |
| // OnAssociatedInterfaceRequest can be received after resetting |
| // `associated_registry_`. This is reset in InvalidateMojoConnection(), which |
| // means we want to stop receiving messages on behalf of the frame. Ignoring |
| // this request sounded like the right way to handle this. |
| if (!associated_registry_) |
| return; |
| |
| if (associated_registry_->TryBindInterface(interface_name, &handle)) |
| return; |
| |
| if (GetContentClient()->browser()->BindAssociatedReceiverFromFrame( |
| this, interface_name, &handle)) { |
| return; |
| } |
| |
| delegate_->OnAssociatedInterfaceRequest(this, interface_name, |
| std::move(handle)); |
| } |
| |
| void RenderFrameHostImpl::AccessibilityPerformAction( |
| const ui::AXActionData& action_data) { |
| // Don't perform any Accessibility action on an inactive frame. |
| if (IsInactiveAndDisallowReactivation() || !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 (IsInactiveAndDisallowReactivation()) |
| 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() { |
| RenderWidgetHostView* view = render_view_host_->GetWidget()->GetView(); |
| if (view) |
| return GetScaleFactorForView(view); |
| return 1.0f; |
| } |
| |
| void RenderFrameHostImpl::AccessibilityReset() { |
| if (!render_accessibility_) |
| return; |
| |
| accessibility_reset_token_ = g_next_accessibility_reset_token++; |
| render_accessibility_->Reset(accessibility_reset_token_); |
| } |
| |
| void RenderFrameHostImpl::AccessibilityFatalError() { |
| browser_accessibility_manager_.reset(nullptr); |
| 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 main frame's current frame host is connected to the native |
| // widget tree for accessibility, so return null if this is queried on |
| // any other frame. |
| if (!is_main_frame() || !IsCurrent()) |
| 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 this method is called when the document is in BackForwardCache, evict |
| // the document to avoid ignoring any accessibility related events which the |
| // document might not expect. |
| if (IsInactiveAndDisallowReactivation()) |
| 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 (IsInactiveAndDisallowReactivation()) |
| return nullptr; |
| |
| RenderWidgetHostViewBase* view = static_cast<RenderWidgetHostViewBase*>( |
| render_view_host_->GetWidget()->GetView()); |
| if (view) |
| return view->AccessibilityGetNativeViewAccessibleForWindow(); |
| return nullptr; |
| } |
| |
| WebContents* RenderFrameHostImpl::AccessibilityWebContents() { |
| // 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 (IsInactiveAndDisallowReactivation()) |
| return nullptr; |
| return delegate()->GetAsWebContents(); |
| } |
| |
| 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 (IsInactiveAndDisallowReactivation() || !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() { |
| return is_main_frame(); |
| } |
| |
| WebContentsAccessibility* |
| RenderFrameHostImpl::AccessibilityGetWebContentsAccessibility() { |
| RenderWidgetHostViewBase* view = GetViewForAccessibility(); |
| if (!view) |
| return nullptr; |
| return view->GetWebContentsAccessibility(); |
| } |
| |
| void RenderFrameHostImpl::RenderProcessExited( |
| RenderProcessHost* host, |
| const ChildProcessTerminationInfo& info) { |
| if (base::FeatureList::IsEnabled(features::kCrashReporting)) |
| MaybeGenerateCrashReport(info.status, info.exit_code); |
| |
| // 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(); |
| InvalidateMojoConnection(); |
| broker_receiver_.reset(); |
| SetLastCommittedUrl(GURL()); |
| web_bundle_handle_.reset(); |
| |
| must_be_replaced_ = true; |
| has_committed_any_navigation_ = false; |
| |
| #if defined(OS_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(base::string16(), base::string16(), gfx::Rect()); |
| } |
| smart_clip_callbacks_.Clear(); |
| #endif // defined(OS_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. |
| SetLifecycleState(LifecycleState::kReadyToBeDeleted); |
| DCHECK(children_.empty()); |
| PendingDeletionCheckCompleted(); |
| // |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::RenderProcessGone( |
| SiteInstanceImpl* site_instance, |
| const ChildProcessTerminationInfo& info) { |
| DCHECK_EQ(site_instance_.get(), site_instance); |
| |
| if (IsInBackForwardCache()) { |
| EvictFromBackForwardCacheWithReason( |
| info.status == base::TERMINATION_STATUS_PROCESS_CRASHED |
| ? BackForwardCacheMetrics::NotRestoredReason:: |
| kRendererProcessCrashed |
| : BackForwardCacheMetrics::NotRestoredReason:: |
| kRendererProcessKilled); |
| return; |
| } |
| |
| 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); |
| } |
| |
| void RenderFrameHostImpl::ReportContentSecurityPolicyViolation( |
| network::mojom::CSPViolationPtr violation_params) { |
| GetAssociatedLocalFrame()->ReportContentSecurityPolicyViolation( |
| std::move(violation_params)); |
| } |
| |
| void RenderFrameHostImpl::SanitizeDataForUseInCspViolation( |
| bool is_redirect, |
| network::mojom::CSPDirectiveName directive, |
| GURL* blocked_url, |
| network::mojom::SourceLocation* source_location) const { |
| DCHECK(blocked_url); |
| DCHECK(source_location); |
| GURL source_location_url(source_location->url); |
| |
| // The main goal of this is to avoid leaking information between potentially |
| // separate renderers, in the event of one of them being compromised. |
| // See https://crbug.com/633306. |
| bool sanitize_blocked_url = true; |
| bool sanitize_source_location = true; |
| |
| // There is no need to sanitize data when it is same-origin with the current |
| // url of the renderer. |
| if (url::Origin::Create(*blocked_url) |
| .IsSameOriginWith(last_committed_origin_)) |
| sanitize_blocked_url = false; |
| if (url::Origin::Create(source_location_url) |
| .IsSameOriginWith(last_committed_origin_)) |
| sanitize_source_location = false; |
| |
| // When a renderer tries to do a form submission, it already knows the url of |
| // the blocked url, except when it is redirected. |
| if (!is_redirect && directive == network::mojom::CSPDirectiveName::FormAction) |
| sanitize_blocked_url = false; |
| |
| if (sanitize_blocked_url) |
| *blocked_url = blocked_url->GetOrigin(); |
| if (sanitize_source_location) { |
| source_location->url = source_location_url.GetOrigin().spec(); |
| source_location->line = 0u; |
| source_location->column = 0u; |
| } |
| } |
| |
| void RenderFrameHostImpl::PerformAction(const ui::AXActionData& data) { |
| AccessibilityPerformAction(data); |
| } |
| |
| bool RenderFrameHostImpl::RequiresPerformActionPointInPixels() const { |
| return true; |
| } |
| |
| bool RenderFrameHostImpl::SchemeShouldBypassCSP( |
| const base::StringPiece& scheme) { |
| // Blink uses its SchemeRegistry to check if a scheme should be bypassed. |
| // It can't be used on the browser process. It is used for two things: |
| // 1) Bypassing the "chrome-extension" scheme when chrome is built with the |
| // extensions support. |
| // 2) Bypassing arbitrary scheme for testing purpose only in blink and in V8. |
| // TODO(arthursonzogni): url::GetBypassingCSPScheme() is used instead of the |
| // blink::SchemeRegistry. It contains 1) but not 2). |
| const auto& bypassing_schemes = url::GetCSPBypassingSchemes(); |
| return base::Contains(bypassing_schemes, scheme); |
| } |
| |
| bool RenderFrameHostImpl::CreateRenderFrame( |
| int previous_routing_id, |
| const base::Optional<base::UnguessableToken>& opener_frame_token, |
| int parent_routing_id, |
| int previous_sibling_routing_id) { |
| 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 (!agent_scheduling_group().Init()) |
| return false; |
| |
| DCHECK(GetProcess()->IsInitializedAndNotDead()); |
| |
| mojom::CreateFrameParamsPtr params = mojom::CreateFrameParams::New(); |
| BindBrowserInterfaceBrokerReceiver( |
| params->interface_broker.InitWithNewPipeAndPassReceiver()); |
| |
| params->routing_id = routing_id_; |
| params->previous_routing_id = previous_routing_id; |
| params->opener_frame_token = opener_frame_token; |
| params->parent_routing_id = parent_routing_id; |
| params->previous_sibling_routing_id = previous_sibling_routing_id; |
| params->replication_state = frame_tree_node()->current_replication_state(); |
| params->frame_token = frame_token_; |
| params->devtools_frame_token = frame_tree_node()->devtools_frame_token(); |
| |
| // 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 feature 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_browsing_instance && |
| base::FeatureList::IsEnabled( |
| features::kClearCrossBrowsingContextGroupMainFrameName))); |
| |
| 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->has_committed_real_load = |
| frame_tree_node()->has_committed_real_load(); |
| |
| // 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_NE(parent_routing_id, MSG_ROUTING_NONE); |
| blink::ScreenInfo screen_info; |
| parent_->GetRenderWidgetHost()->GetScreenInfo(&screen_info); |
| RenderWidgetHostView* rwhv = RenderWidgetHostViewChildFrame::Create( |
| owned_render_widget_host_.get(), screen_info); |
| // The child frame should be created hidden. |
| DCHECK(!rwhv->IsShowing()); |
| } |
| |
| if (auto* rwh = GetLocalRenderWidgetHost()) { |
| params->widget_params = mojom::CreateFrameWidgetParams::New(); |
| params->widget_params->routing_id = |
| GetLocalRenderWidgetHost()->GetRoutingID(); |
| params->widget_params->visual_properties = |
| GetLocalRenderWidgetHost()->GetInitialVisualProperties(); |
| |
| std::tie(params->widget_params->widget_host, |
| params->widget_params->widget) = rwh->BindNewWidgetInterfaces(); |
| std::tie(params->widget_params->frame_widget_host, |
| params->widget_params->frame_widget) = |
| rwh->BindNewFrameWidgetInterfaces(); |
| } |
| |
| // https://crbug.com/1006814. The renderer needs at least one of these IDs to |
| // be able to insert the new frame in the frame tree. |
| DCHECK(params->previous_routing_id != MSG_ROUTING_NONE || |
| params->parent_routing_id != MSG_ROUTING_NONE); |
| agent_scheduling_group().CreateFrame(std::move(params)); |
| |
| if (previous_routing_id != MSG_ROUTING_NONE) { |
| RenderFrameProxyHost* proxy = RenderFrameProxyHost::FromID( |
| GetProcess()->GetID(), previous_routing_id); |
| // We have also created a RenderFrameProxy in CreateFrame above, so |
| // remember that. |
| // |
| // RenderDocument: |proxy| can be null, when |previous_routing_id| refers to |
| // a RenderFrameHost instead of a RenderFrameProxy. |
| if (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::DeleteRenderFrame(FrameDeleteIntention intent) { |
| if (IsPendingDeletion()) |
| return; |
| |
| // In case of BackForwardCache, page is evicted directly from the cache and |
| // deleted immediately, without waiting for unload handlers. |
| bool wait_for_unload_handlers = |
| has_unload_handlers() && !IsInBackForwardCache(); |
| |
| if (render_frame_created_) { |
| Send(new UnfreezableFrameMsg_Delete(routing_id_, intent)); |
| |
| if (!frame_tree_node_->IsMainFrame() && IsCurrent()) { |
| DCHECK_NE(lifecycle_state(), LifecycleState::kSpeculative); |
| // Documents from the page in the BackForwardCache don't run their unload |
| // handlers, even if they have one. As a result, this should never delay |
| // process shutdown. |
| DCHECK_NE(lifecycle_state(), LifecycleState::kInBackForwardCache); |
| |
| // If this document has unload handlers (and isn't speculative or in the |
| // back-forward cache), 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. |
| if (has_unload_handlers()) { |
| RenderProcessHostImpl* process = |
| static_cast<RenderProcessHostImpl*>(GetProcess()); |
| process->DelayProcessShutdownForUnload(subframe_unload_timeout_); |
| } |
| |
| // 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); |
| } |
| } |
| |
| LifecycleState lifecycle_state = wait_for_unload_handlers |
| ? LifecycleState::kRunningUnloadHandlers |
| : LifecycleState::kReadyToBeDeleted; |
| SetLifecycleState(lifecycle_state); |
| } |
| |
| void RenderFrameHostImpl::RenderFrameCreated() { |
| // We should not create new RenderFrames while our delegate is being destroyed |
| // (e.g., via a WebContentsObserver during WebContents shutdown). This seems |
| // to have caused crashes in https://crbug.com/717650. |
| CHECK(!delegate_->IsBeingDestroyed()); |
| |
| bool was_created = render_frame_created_; |
| render_frame_created_ = true; |
| |
| // Clear all the user data associated with this RenderFrameHost when its |
| // RenderFrame is recreated after a crash. Checking |
| // |was_render_frame_ever_created_| guarantees that the user data isn't |
| // cleared for the initial RenderFrame creation. 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) new new state set in RenderFrameCreated doesn't get deleted. |
| // - b) the old state is not leaked to a new RenderFrameHost. |
| if (!was_created && was_render_frame_ever_created_) |
| document_associated_data_.ClearAllUserData(); |
| |
| was_render_frame_ever_created_ = true; |
| |
| // If the current status is different than the new status, the delegate |
| // needs to be notified. |
| if (!was_created) { |
| SetUpMojoIfNeeded(); |
| delegate_->RenderFrameCreated(this); |
| } |
| // TODO(http://crbug.com/1014212): Change to DCHECK. |
| CHECK(frame_); |
| |
| if (GetLocalRenderWidgetHost()) { |
| GetLocalRenderWidgetHost()->input_router()->SetFrameTreeNodeId( |
| frame_tree_node_->frame_tree_node_id()); |
| GetLocalRenderWidgetHost()->InitForFrame(); |
| } |
| |
| if (enabled_bindings_) |
| GetFrameBindingsControl()->AllowBindings(enabled_bindings_); |
| |
| if (web_ui_ && enabled_bindings_ & BINDINGS_POLICY_WEB_UI) |
| web_ui_->SetupMojoConnection(); |
| } |
| |
| void RenderFrameHostImpl::RenderFrameDeleted() { |
| bool was_created = render_frame_created_; |
| render_frame_created_ = false; |
| |
| // If the current status is different than the new status, the delegate |
| // needs to be notified. |
| if (was_created) { |
| delegate_->RenderFrameDeleted(this); |
| } |
| if (web_ui_) { |
| web_ui_->InvalidateMojoConnection(); |
| } |
| } |
| |
| void RenderFrameHostImpl::SwapIn() { |
| GetNavigationControl()->SwapIn(); |
| } |
| |
| void RenderFrameHostImpl::Init() { |
| ResumeBlockedRequestsForFrame(); |
| if (!waiting_for_init_) |
| return; |
| |
| waiting_for_init_ = false; |
| 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), |
| std::move(pending_navigate_->navigation_initiator), |
| EnsurePrefetchedSignedExchangeCache(), |
| MaybeCreateWebBundleHandleTracker()); |
| pending_navigate_.reset(); |
| } |
| } |
| |
| 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 (IsCrossProcessSubframe()) { |
| // Cross-process subframes should have a remote parent frame. |
| target_render_frame_proxy = |
| frame_tree_node()->render_manager()->GetProxyToParent(); |
| DCHECK(target_render_frame_proxy); |
| } else if (frame_tree_node()->IsMainFrame()) { |
| // 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 = |
| frame_tree_node()->render_manager()->GetProxyToOuterDelegate(); |
| } |
| |
| // Propagate the token to the right process, if a proxy was found. |
| if (target_render_frame_proxy) { |
| target_render_frame_proxy->GetAssociatedRemoteFrame()->SetEmbeddingToken( |
| embedding_token_.value()); |
| } |
| } |
| |
| void RenderFrameHostImpl::OnAudibleStateChanged(bool is_audible) { |
| DCHECK_NE(is_audible_, is_audible); |
| if (is_audible) { |
| GetProcess()->OnMediaStreamAdded(); |
| } else { |
| GetProcess()->OnMediaStreamRemoved(); |
| } |
| is_audible_ = is_audible; |
| delegate_->OnFrameAudioStateChanged(this, is_audible_); |
| } |
| |
| void RenderFrameHostImpl::DidAddMessageToConsole( |
| blink::mojom::ConsoleMessageLevel log_level, |
| const base::string16& message, |
| int32_t line_no, |
| const base::string16& source_id, |
| const base::Optional<base::string16>& untrusted_stack_trace) { |
| if (delegate_->DidAddMessageToConsole(this, log_level, message, line_no, |
| source_id, untrusted_stack_trace)) { |
| return; |
| } |
| |
| // Pass through log severity only on builtin components pages to limit console |
| // spew. |
| const bool is_builtin_component = |
| HasWebUIScheme(delegate_->GetMainFrameLastCommittedURL()) || |
| 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, source_id); |
| } |
| |
| void RenderFrameHostImpl::OnCreateChildFrame( |
| int new_routing_id, |
| mojo::PendingReceiver<blink::mojom::BrowserInterfaceBroker> |
| browser_interface_broker_receiver, |
| mojo::PendingAssociatedReceiver<blink::mojom::PolicyContainerHost> |
| policy_container_host_receiver, |
| blink::mojom::TreeScopeType scope, |
| const std::string& frame_name, |
| const std::string& frame_unique_name, |
| bool is_created_by_script, |
| const base::UnguessableToken& frame_token, |
| const base::UnguessableToken& devtools_frame_token, |
| const blink::FramePolicy& frame_policy, |
| const blink::mojom::FrameOwnerProperties& frame_owner_properties, |
| const blink::mojom::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_host_receiver.is_valid()); |
| if (owner_type == blink::mojom::FrameOwnerElementType::kNone) { |
| // Any child frame must have a HTMLFrameOwnerElement in its parent document |
| // and therefore the corresponding type of kNone (specific to main frames) |
| // is invalid. |
| bad_message::ReceivedBadMessage( |
| GetProcess(), bad_message::RFH_CHILD_FRAME_NEEDS_OWNER_ELEMENT_TYPE); |
| } |
| |
| DCHECK(frame_token); |
| 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 the RenderFrameHost |
| // to become pending deletion, or for its process to have disconnected (maybe |
| // due to browser shutdown). Ignore such messages. |
| if (IsInactiveAndDisallowReactivation() || !render_frame_created_) |
| 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(browser_interface_broker_receiver), |
| std::move(policy_container_host_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); |
| } |
| |
| void RenderFrameHostImpl::CreateChildFrame( |
| int new_routing_id, |
| mojo::PendingReceiver<blink::mojom::BrowserInterfaceBroker> |
| browser_interface_broker_receiver, |
| mojo::PendingAssociatedReceiver<blink::mojom::PolicyContainerHost> |
| policy_container_host_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, |
| const blink::mojom::FrameOwnerElementType owner_type) { |
| base::UnguessableToken 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; |
| } |
| |
| // TODO(crbug.com/1145708). The interface exposed to tests should |
| // match the mojo interface. |
| OnCreateChildFrame( |
| new_routing_id, std::move(browser_interface_broker_receiver), |
| std::move(policy_container_host_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 did_create_new_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. |
| frame_tree_node_->SetCurrentURL(params.url); |
| SetLastCommittedOrigin(params.origin); |
| |
| // 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 (!params.url_is_unreachable) |
| last_successful_url_ = params.url; |
| |
| // 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; |
| |
| 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 |
| // FrameTreeNode::replication_state_ needs to be refreshed with the actual |
| // values. |
| if (!navigation_request->IsSameDocument()) { |
| // Feature policy's inheritance from parent frame's feature 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. |
| frame_tree_node()->UpdateFramePolicyHeaders(active_sandbox_flags_, |
| feature_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 (frame_tree_node_->IsMainFrame()) { |
| 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(); |
| } |
| |
| net::IsolationInfo RenderFrameHostImpl::ComputeIsolationInfoForNavigation( |
| const GURL& destination) const { |
| net::IsolationInfo::RequestType request_type = |
| frame_tree_node_->IsMainFrame() |
| ? net::IsolationInfo::RequestType::kMainFrame |
| : net::IsolationInfo::RequestType::kSubFrame; |
| return ComputeIsolationInfoInternal(url::Origin::Create(destination), |
| request_type); |
| } |
| |
| net::IsolationInfo |
| RenderFrameHostImpl::ComputeIsolationInfoForSubresourcesForPendingCommit( |
| const url::Origin& main_world_origin) const { |
| return ComputeIsolationInfoInternal(main_world_origin, |
| net::IsolationInfo::RequestType::kOther); |
| } |
| |
| net::IsolationInfo RenderFrameHostImpl::GetIsolationInfoForViewSource() const { |
| // Make sure a subframe has the mode set accordingly so that the |
| // cache can match it correctly. |
| net::IsolationInfo::RequestType view_source_request_type = |
| is_main_frame() ? net::IsolationInfo::RequestType::kMainFrame |
| : net::IsolationInfo::RequestType::kSubFrame; |
| |
| // Use fields from isolation_info_ to avoid computing them again. |
| DCHECK(!isolation_info_.IsEmpty()); |
| return net::IsolationInfo::Create(view_source_request_type, |
| isolation_info_.top_frame_origin().value(), |
| isolation_info_.frame_origin().value(), |
| isolation_info_.site_for_cookies()); |
| } |
| |
| net::SiteForCookies RenderFrameHostImpl::ComputeSiteForCookies() { |
| return isolation_info_.site_for_cookies(); |
| } |
| |
| net::IsolationInfo RenderFrameHostImpl::ComputeIsolationInfoInternal( |
| const url::Origin& frame_origin, |
| net::IsolationInfo::RequestType request_type) const { |
| url::Origin top_frame_origin = ComputeTopFrameOrigin(frame_origin); |
| net::SchemefulSite top_frame_site = net::SchemefulSite(top_frame_origin); |
| |
| net::SiteForCookies candidate_site_for_cookies = |
| net::SiteForCookies(top_frame_site); |
| |
| std::set<net::SchemefulSite> party_context; |
| |
| if (GetContentClient() |
| ->browser() |
| ->ShouldTreatURLSchemeAsFirstPartyWhenTopLevel( |
| top_frame_origin.scheme(), |
| GURL::SchemeIsCryptographic(frame_origin.scheme()))) { |
| return net::IsolationInfo::Create(request_type, top_frame_origin, |
| frame_origin, candidate_site_for_cookies, |
| std::move(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); |
| } |
| if (!candidate_site_for_cookies.IsEquivalent(net::SiteForCookies(cur_site))) |
| candidate_site_for_cookies = net::SiteForCookies(); |
| candidate_site_for_cookies.MarkIfCrossScheme(cur_origin); |
| } |
| |
| return net::IsolationInfo::Create(request_type, top_frame_origin, |
| frame_origin, candidate_site_for_cookies, |
| std::move(party_context)); |
| } |
| |
| 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()); |
| DCHECK(isolation_info_.IsEmpty()); |
| |
| // Calculate and set |new_frame_origin|. |
| bool new_frame_should_be_sandboxed = |
| network::mojom::WebSandboxFlags::kOrigin == |
| (frame_tree_node()->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); |
| SetLastCommittedOrigin(new_frame_origin); |
| |
| // Construct the frame's feature policy only once we know its initial |
| // committed origin. It's necessary to wait for the origin because the feature |
| // policy's state depends on the origin, so the FeaturePolicy object could be |
| // configured incorrectly if it were initialized before knowing the value of |
| // |last_committed_origin_|. More at crbug.com/1112959. |
| ResetFeaturePolicy(); |
| } |
| |
| FrameTreeNode* RenderFrameHostImpl::AddChild( |
| std::unique_ptr<FrameTreeNode> child, |
| int process_id, |
| int frame_routing_id, |
| const base::UnguessableToken& frame_token) { |
| // Child frame must always be created in the same process as the parent. |
| CHECK_EQ(process_id, GetProcess()->GetID()); |
| |
| // 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, |
| frame_token); |
| |
| // 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); |
| node_to_delete->current_frame_host()->DeleteRenderFrame( |
| FrameDeleteIntention::kNotMainFrame); |
| // Speculative RenderFrameHosts are deleted by the FrameTreeNode's |
| // RenderFrameHostManager's destructor. RenderFrameProxyHosts send |
| // UnfreezableFrameMsg_Delete automatically in the destructor. |
| // TODO(dcheng): This is horribly confusing. Refactor this logic so it's |
| // more understandable. |
| node_to_delete.reset(); |
| PendingDeletionCheckCompleted(); |
| 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( |
| FrameDeleteIntention::kNotMainFrame); |
| } |
| |
| void RenderFrameHostImpl::SetLastCommittedUrl(const GURL& url) { |
| last_committed_url_ = url; |
| } |
| |
| void RenderFrameHostImpl::UpdateRenderProcessHostFramePriorities() { |
| const auto new_committed_document_priority = |
| delegate_->IsFrameLowPriority(this) |
| ? RenderProcessHostImpl::FramePriority::kLow |
| : RenderProcessHostImpl::FramePriority::kNormal; |
| if (last_committed_document_priority_ != new_committed_document_priority) { |
| GetProcess()->UpdateFrameWithPriority(last_committed_document_priority_, |
| new_committed_document_priority); |
| last_committed_document_priority_ = new_committed_document_priority; |
| } |
| } |
| |
| void RenderFrameHostImpl::Detach() { |
| if (lifecycle_state() == LifecycleState::kSpeculative) |
| 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_ != LifecycleState::kReadyToBeDeleted) |
| SetLifecycleState(LifecycleState::kReadyToBeDeleted); |
| PendingDeletionCheckCompleted(); // Can delete |this|. |
| return; |
| } |
| |
| // This frame is being removed by the renderer, and it has already executed |
| // its unload handler. |
| SetLifecycleState(LifecycleState::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|. |
| } |
| |
| void RenderFrameHostImpl::DidFailLoadWithError(const GURL& url, |
| int32_t error_code) { |
| TRACE_EVENT2("navigation", "RenderFrameHostImpl::DidFailLoadWithError", |
| "frame_tree_node", frame_tree_node_->frame_tree_node_id(), |
| "error", error_code); |
| |
| GURL validated_url(url); |
| GetProcess()->FilterURL(false, &validated_url); |
| |
| frame_tree_node_->navigator().DidFailLoadWithError(this, validated_url, |
| error_code); |
| } |
| |
| void RenderFrameHostImpl::DidFocusFrame() { |
| // We don't handle this IPC signal for non-active RenderFrameHost. |
| // |
| // For RenderFrameHost in BackForwardCache, it is safe to ignore this IPC as |
| // there is a renderer side check (see Document::IsFocusedAllowed) which |
| // returns false. |
| if (lifecycle_state_ != LifecycleState::kActive) |
| return; |
| |
| // We need to handle receiving this IPC from a frame that is inside a portal |
| // despite there being a renderer side check (see Document::IsFocusAllowed). |
| // This is because the IPC to notify a page that it is inside a portal (see |
| // WebContentsImpl::NotifyInsidePortal) may race with portal activation, and |
| // we may run into a situation where a frame inside a portal doesn't know it's |
| // inside a portal yet and allows focus. |
| if (InsidePortal()) |
| return; |
| |
| delegate_->SetFocusedFrame(frame_tree_node_, GetSiteInstance()); |
| } |
| |
| void RenderFrameHostImpl::DidCallFocus() { |
| delegate_->DidCallFocus(); |
| } |
| |
| void RenderFrameHostImpl::DidAddContentSecurityPolicies( |
| std::vector<network::mojom::ContentSecurityPolicyPtr> policies) { |
| TRACE_EVENT1("navigation", |
| "RenderFrameHostImpl::OnDidAddContentSecurityPolicies", |
| "frame_tree_node", frame_tree_node_->frame_tree_node_id()); |
| |
| std::vector<network::mojom::ContentSecurityPolicyHeaderPtr> headers; |
| for (auto& policy : policies) { |
| headers.push_back(policy->header.Clone()); |
| AddContentSecurityPolicy(std::move(policy)); |
| } |
| frame_tree_node()->AddContentSecurityPolicies(std::move(headers)); |
| } |
| |
| 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::DidChangeActiveSchedulerTrackedFeatures( |
| uint64_t features_mask) { |
| renderer_reported_scheduler_tracked_features_ = features_mask; |
| |
| MaybeEvictFromBackForwardCache(); |
| } |
| |
| void RenderFrameHostImpl::OnSchedulerTrackedFeatureUsed( |
| blink::scheduler::WebSchedulerTrackedFeature feature) { |
| browser_reported_scheduler_tracked_features_ |= |
| 1ull << static_cast<uint64_t>(feature); |
| |
| MaybeEvictFromBackForwardCache(); |
| } |
| |
| bool RenderFrameHostImpl::IsFrozen() { |
| // TODO(crbug.com/1081920): Account for non-bfcache freezing here as well. |
| return lifecycle_state_ == LifecycleState::kInBackForwardCache; |
| } |
| |
| void RenderFrameHostImpl::DidCommitProvisionalLoad( |
| mojom::DidCommitProvisionalLoadParamsPtr params, |
| mojom::DidCommitProvisionalLoadInterfaceParamsPtr interface_params) { |
| if (!MaybeInterceptCommitCallback(nullptr, ¶ms, &interface_params)) |
| return; |
| |
| DCHECK(params); |
| DidCommitNavigation(nullptr, std::move(params), std::move(interface_params)); |
| } |
| |
| void RenderFrameHostImpl::DidCommitBackForwardCacheNavigation( |
| NavigationRequest* committing_navigation_request, |
| mojom::DidCommitProvisionalLoadParamsPtr params) { |
| auto request = navigation_requests_.find(committing_navigation_request); |
| CHECK(request != navigation_requests_.end()); |
| |
| std::unique_ptr<NavigationRequest> owned_request = std::move(request->second); |
| navigation_requests_.erase(committing_navigation_request); |
| |
| // During a normal (uncached) navigation, is_loading_ is set to true in |
| // CommitNavigation(). When navigating to a document in the BackForwardCache, |
| // CommitNavigation() is never called, so we have to set is_loading_ to true |
| // ourselves. |
| // |
| // If is_start_loading_ is set to false, DidCommitNavigationInternal will |
| // re-fire the DidStartLoading event, which we don't want since it has already |
| // been fired. |
| is_loading_ = true; |
| |
| DidCommitNavigationInternal(std::move(owned_request), std::move(params), |
| /*same_document_params=*/nullptr); |
| |
| // The page is already loaded since it came from the cache, so fire the stop |
| // loading event. |
| DidStopLoading(); |
| } |
| |
| void RenderFrameHostImpl::DidCommitSameDocumentNavigation( |
| mojom::DidCommitProvisionalLoadParamsPtr params, |
| mojom::DidCommitSameDocumentNavigationParamsPtr same_document_params) { |
| ScopedActiveURL scoped_active_url(params->url, |
| frame_tree()->root()->current_origin()); |
| 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 (IsInactiveAndDisallowReactivation()) |
| return; |
| |
| TRACE_EVENT2("navigation", |
| "RenderFrameHostImpl::DidCommitSameDocumentNavigation", |
| "frame_tree_node", frame_tree_node_->frame_tree_node_id(), "url", |
| params->url.possibly_invalid_spec()); |
| |
| // Check if the navigation matches a stored same-document NavigationRequest. |
| // In that case it is browser-initiated. |
| bool is_browser_initiated = |
| same_document_navigation_request_ && |
| (same_document_navigation_request_->commit_params().navigation_token == |
| params->navigation_token); |
| if (!DidCommitNavigationInternal( |
| is_browser_initiated ? std::move(same_document_navigation_request_) |
| : nullptr, |
| 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(); |
| } |
| |
| 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(); |
| } |
| |
| GlobalFrameRoutingId RenderFrameHostImpl::GetGlobalFrameRoutingId() { |
| return GlobalFrameRoutingId(GetProcess()->GetID(), GetRoutingID()); |
| } |
| |
| bool RenderFrameHostImpl::HasPendingCommitNavigation() const { |
| return HasPendingCommitForCrossDocumentNavigation() || |
| same_document_navigation_request_; |
| } |
| |
| bool RenderFrameHostImpl::HasPendingCommitForCrossDocumentNavigation() const { |
| return !navigation_requests_.empty(); |
| } |
| |
| void RenderFrameHostImpl::ResetNavigationRequests() { |
| same_document_navigation_request_.reset(); |
| navigation_requests_.clear(); |
| } |
| |
| void RenderFrameHostImpl::SetNavigationRequest( |
| std::unique_ptr<NavigationRequest> navigation_request) { |
| DCHECK(navigation_request); |
| if (NavigationTypeUtils::IsSameDocument( |
| navigation_request->common_params().navigation_type)) { |
| same_document_navigation_request_ = std::move(navigation_request); |
| return; |
| } |
| navigation_requests_[navigation_request.get()] = |
| std::move(navigation_request); |
| } |
| |
| 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), "frame_tree_node", |
| frame_tree_node_->frame_tree_node_id()); |
| |
| // 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(LifecycleState::kRunningUnloadHandlers); |
| if (IsRenderFrameLive()) { |
| Send(new UnfreezableFrameMsg_Unload( |
| routing_id_, proxy->GetRoutingID(), is_loading, |
| proxy->frame_tree_node()->current_replication_state(), |
| proxy->GetFrameToken())); |
| // Remember that a RenderFrameProxy 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 |
| // UnfreezableFrameMsg_Unload here. It can be marked at completed. |
| SetLifecycleState(LifecycleState::kReadyToBeDeleted); |
| } |
| |
| if (web_ui()) |
| web_ui()->RenderFrameHostUnloading(); |
| |
| web_bluetooth_services_.clear(); |
| |
| 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::DetachFromProxy() { |
| if (IsPendingDeletion()) |
| return; |
| |
| // Start pending deletion on this frame and its children. |
| DeleteRenderFrame(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|. |
| } |
| |
| 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) { |
| TRACE_EVENT_NESTABLE_ASYNC_END1( |
| "navigation", "RenderFrameHostImpl BeforeUnload", TRACE_ID_LOCAL(this), |
| "FrameTreeNode id", frame_tree_node_->frame_tree_node_id()); |
| // 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, |
| false /* is_frame_being_destroyed */, renderer_before_unload_start_time, |
| renderer_before_unload_end_time); |
| } |
| |
| 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) { |
| // 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; |
| 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()) { |
| // 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)); |
| blink::LocalTimeTicks browser_before_unload_end_time = |
| converter.ToLocalTimeTicks(blink::RemoteTimeTicks::FromTimeTicks( |
| renderer_before_unload_end_time)); |
| before_unload_end_time = browser_before_unload_end_time.ToTimeTicks(); |
| } |
| |
| 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); |
| UMA_HISTOGRAM_TIMES("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_); |
| } |
| |
| // 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); |
| } |
| }, |
| weak_ptr_factory_.GetWeakPtr(), before_unload_end_time, 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) |
| delegate_->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() { |
| 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(LifecycleState::kRunningUnloadHandlers, lifecycle_state_); |
| SetLifecycleState(LifecycleState::kReadyToBeDeleted); |
| PendingDeletionCheckCompleted(); // Can delete |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(); |
| |
| ClearWebUI(); |
| |
| 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; |
| } |
| |
| void RenderFrameHostImpl::OnContextMenu( |
| const UntrustworthyContextMenuParams& params) { |
| if (IsInactiveAndDisallowReactivation()) |
| 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); |
| validated_params.page_url = GetMainFrame()->GetLastCommittedURL(); |
| if (GetParent()) // Only populate |frame_url| for subframes. |
| 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); |
| 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); |
| } |
| |
| delegate_->ShowContextMenu(this, validated_params); |
| } |
| |
| #if defined(OS_ANDROID) |
| void RenderFrameHostImpl::RequestSmartClipExtract( |
| ExtractSmartClipDataCallback callback, |
| gfx::Rect rect) { |
| int32_t callback_id = smart_clip_callbacks_.Add( |
| std::make_unique<ExtractSmartClipDataCallback>(std::move(callback))); |
| frame_->ExtractSmartClipData( |
| rect, base::BindOnce(&RenderFrameHostImpl::OnSmartClipDataExtracted, |
| base::Unretained(this), callback_id)); |
| } |
| |
| void RenderFrameHostImpl::OnSmartClipDataExtracted(int32_t callback_id, |
| const base::string16& text, |
| const base::string16& 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 // defined(OS_ANDROID) |
| |
| void RenderFrameHostImpl::RunModalAlertDialog( |
| const base::string16& alert_message, |
| RunModalAlertDialogCallback response_callback) { |
| auto dialog_closed_callback = base::BindOnce( |
| [](RunModalAlertDialogCallback response_callback, bool success, |
| const base::string16& 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, base::string16(), |
| JAVASCRIPT_DIALOG_TYPE_ALERT, |
| std::move(dialog_closed_callback)); |
| } |
| |
| void RenderFrameHostImpl::RunModalConfirmDialog( |
| const base::string16& alert_message, |
| RunModalConfirmDialogCallback response_callback) { |
| auto dialog_closed_callback = base::BindOnce( |
| [](RunModalConfirmDialogCallback response_callback, bool success, |
| const base::string16& 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, base::string16(), |
| JAVASCRIPT_DIALOG_TYPE_CONFIRM, |
| std::move(dialog_closed_callback)); |
| } |
| |
| void RenderFrameHostImpl::RunModalPromptDialog( |
| const base::string16& alert_message, |
| const base::string16& default_value, |
| RunModalPromptDialogCallback response_callback) { |
| RunJavaScriptDialog(alert_message, default_value, |
| JAVASCRIPT_DIALOG_TYPE_PROMPT, |
| std::move(response_callback)); |
| } |
| |
| void RenderFrameHostImpl::RunJavaScriptDialog( |
| const base::string16& message, |
| const base::string16& default_prompt, |
| JavaScriptDialogType dialog_type, |
| JavaScriptDialogCallback ipc_response_callback) { |
| // Don't show the dialog if it's triggered on a non-active RenderFrameHost. |
| // This happens when the RenderFrameHost is pending deletion or in the |
| // back-forward cache. |
| if (lifecycle_state_ != LifecycleState::kActive) { |
| std::move(ipc_response_callback).Run(true, base::string16()); |
| 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, |
| 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", |
| "frame_tree_node", frame_tree_node_->frame_tree_node_id()); |
| |
| // 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 base::string16& 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)); |
| } |
| |
| void RenderFrameHostImpl::ScaleFactorChanged(float scale) { |
| delegate_->OnPageScaleFactorChanged(this, scale); |
| } |
| |
| void RenderFrameHostImpl::ContentsPreferredSizeChanged( |
| const gfx::Size& pref_size) { |
| render_view_host_->OnDidContentsPreferredSizeChange(pref_size); |
| } |
| |
| void RenderFrameHostImpl::TextAutosizerPageInfoChanged( |
| blink::mojom::TextAutosizerPageInfoPtr page_info) { |
| delegate_->OnTextAutosizerPageInfoChanged(this, std::move(page_info)); |
| } |
| |
| void RenderFrameHostImpl::FocusPage() { |
| render_view_host_->OnFocus(); |
| } |
| |
| void RenderFrameHostImpl::TakeFocus(bool reverse) { |
| 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 base::UnguessableToken& 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) { |
| delegate_->SetWindowRect(bounds); |
| std::move(callback).Run(); |
| } |
| |
| void RenderFrameHostImpl::UpdateFaviconURL( |
| std::vector<blink::mojom::FaviconURLPtr> favicon_urls) { |
| delegate_->UpdateFaviconURL(this, std::move(favicon_urls)); |
| } |
| |
| void RenderFrameHostImpl::DownloadURL( |
| blink::mojom::DownloadURLParamsPtr blink_parameters) { |
| 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(true); |
| parameters->set_suggested_name( |
| blink_parameters->suggested_name.value_or(base::string16())); |
| parameters->set_prompt(false); |
| 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() { |
| int64_t navigation_id = |
| GetMainFrame()->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::ReportHeavyAdIssue( |
| blink::mojom::HeavyAdResolutionStatus resolution, |
| blink::mojom::HeavyAdReason reason) { |
| auto issue = |
| devtools_instrumentation::GetHeavyAdIssue(this, resolution, reason); |
| devtools_instrumentation::ReportBrowserInitiatedIssue(this, issue.get()); |
| } |
| |
| void RenderFrameHostImpl::AsValueInto( |
| base::trace_event::TracedValue* traced_value) { |
| traced_value->SetPointer("this", this); |
| traced_value->SetInteger("process_id", GetProcess()->GetID()); |
| traced_value->SetInteger("render_frame_id", GetRoutingID()); |
| traced_value->SetString("lifecycle_state", |
| LifecycleStateToString(lifecycle_state_)); |
| traced_value->SetString( |
| "origin", base::trace_event::ValueToString(GetLastCommittedOrigin())); |
| traced_value->SetString( |
| "url", base::trace_event::ValueToString(GetLastCommittedURL())); |
| traced_value->SetInteger("frame_tree_node_id", |
| frame_tree_node_->frame_tree_node_id()); |
| traced_value->SetInteger("site_instance_id", GetSiteInstance()->GetId()); |
| traced_value->SetInteger("browsing_instance_id", |
| GetSiteInstance()->GetBrowsingInstanceId()); |
| traced_value->BeginDictionary("parent"); |
| if (GetParent()) { |
| GetParent()->AsValueInto(traced_value); |
| } |
| traced_value->EndDictionary(); |
| } |
| |
| StoragePartition* RenderFrameHostImpl::GetStoragePartition() { |
| return BrowserContext::GetStoragePartition(GetBrowserContext(), |
| GetSiteInstance()); |
| } |
| |
| 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_request_|, 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", |
| "frame_tree_node", frame_tree_node_->frame_tree_node_id(), |
| "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 (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 main frame. |
| if (GetParent()) |
| 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::FeaturePolicyFeature feature) { |
| return feature_policy_ && feature_policy_->IsFeatureEnabledForOrigin( |
| feature, GetLastCommittedOrigin()); |
| } |
| |
| void RenderFrameHostImpl::ViewSource() { |
| delegate_->ViewSource(this); |
| } |
| |
| void RenderFrameHostImpl::FlushNetworkAndNavigationInterfacesForTesting() { |
| DCHECK(network_service_disconnect_handler_holder_); |
| network_service_disconnect_handler_holder_.FlushForTesting(); // IN-TEST |
| |
| if (!navigation_control_) |
| GetNavigationControl(); |
| DCHECK(navigation_control_); |
| navigation_control_.FlushForTesting(); // IN-TEST |
| } |
| |
| void RenderFrameHostImpl::PrepareForInnerWebContentsAttach( |
| PrepareForInnerWebContentsAttachCallback callback) { |
| frame_tree_node_->render_manager()->PrepareForInnerDelegateAttach( |
| std::move(callback)); |
| } |
| |
| void RenderFrameHostImpl::UpdateSubresourceLoaderFactories() { |
| // We only send loader factory bundle upon navigation, so |
| // bail out if the frame hasn't committed any yet. |
| if (!has_committed_any_navigation_) |
| return; |
| DCHECK(!IsOutOfProcessNetworkService() || |
| network_service_disconnect_handler_holder_.is_bound()); |
| |
| NavigationRequest* latest_nav_request_still_committing = |
| FindLatestNavigationRequestThatIsStillCommitting(); |
| mojo::PendingRemote<network::mojom::URLLoaderFactory> default_factory_remote; |
| bool bypass_redirect_checks = false; |
| if (recreate_default_url_loader_factory_after_network_service_crash_) { |
| bypass_redirect_checks = CreateNetworkServiceDefaultFactoryAndObserve( |
| CreateURLLoaderFactoryParamsForMainWorld( |
| latest_nav_request_still_committing, |
| "RFHI::UpdateSubresourceLoaderFactories"), |
| ukm::SourceIdObj::FromInt64( |
| latest_nav_request_still_committing |
| ? latest_nav_request_still_committing->GetNextPageUkmSourceId() |
| : GetPageUkmSourceId()), |
| 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( |
| latest_nav_request_still_committing, |
| isolated_worlds_requiring_separate_url_loader_factory_), |
| bypass_redirect_checks); |
| GetNavigationControl()->UpdateSubresourceLoaderFactories( |
| std::move(subresource_loader_factories)); |
| } |
| |
| blink::mojom::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::DidAccessInitialDocument() { |
| delegate_->DidAccessInitialDocument(); |
| } |
| |
| void RenderFrameHostImpl::DidChangeName(const std::string& name, |
| const std::string& unique_name) { |
| if (GetParent() != nullptr) { |
| // TODO(lukasza): Call ReceivedBadMessage when |unique_name| is empty. |
| DCHECK(!unique_name.empty()); |
| } |
| TRACE_EVENT2("navigation", "RenderFrameHostImpl::OnDidChangeName", |
| "frame_tree_node", frame_tree_node_->frame_tree_node_id(), |
| "name length", name.length()); |
| |
| std::string old_name = frame_tree_node()->frame_name(); |
| frame_tree_node()->SetFrameName(name, unique_name); |
| if (old_name.empty() && !name.empty()) |
| frame_tree_node_->render_manager()->CreateProxiesForNewNamedFrame(); |
| delegate_->DidChangeName(this, name); |
| } |
| |
| void RenderFrameHostImpl::EnforceInsecureRequestPolicy( |
| blink::mojom::InsecureRequestPolicy policy) { |
| frame_tree_node()->SetInsecureRequestPolicy(policy); |
| } |
| |
| void RenderFrameHostImpl::EnforceInsecureNavigationsSet( |
| const std::vector<uint32_t>& set) { |
| frame_tree_node()->SetInsecureNavigationsSet(set); |
| } |
| |
| RenderFrameHostImpl* 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); |
| } |
| |
| RenderFrameHostImpl* RenderFrameHostImpl::FindAndVerifyChild( |
| const base::UnguessableToken& 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); |
| } |
| |
| RenderFrameHostImpl* 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.proxy |
| ? child_frame_or_proxy.proxy->frame_tree_node() |
| ->current_frame_host() |
| : child_frame_or_proxy.frame; |
| } |
| |
| void RenderFrameHostImpl::UpdateTitle( |
| const base::Optional<::base::string16>& title, |
| base::i18n::TextDirection title_direction) { |
| // This message should only be sent for top-level frames. |
| if (!is_main_frame()) |
| return; |
| |
| base::string16 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::UpdateEncoding(const std::string& encoding_name) { |
| // This message is only sent for top-level frames. TODO(avi): when frame tree |
| // mirroring works correctly, add a check here to enforce it. |
| if (encoding_name == last_reported_encoding_) |
| return; |
| last_reported_encoding_ = encoding_name; |
| |
| canonical_encoding_ = |
| base::GetCanonicalEncodingNameByAliasName(encoding_name); |
| } |
| |
| void RenderFrameHostImpl::FrameSizeChanged(const gfx::Size& frame_size) { |
| frame_size_ = frame_size; |
| delegate_->FrameSizeChanged(this, frame_size); |
| } |
| |
| void RenderFrameHostImpl::FullscreenStateChanged( |
| bool is_fullscreen, |
| blink::mojom::FullscreenOptionsPtr options) { |
| if (IsInactiveAndDisallowReactivation()) |
| 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() { |
| delegate_->DidDisplayInsecureContent(); |
| } |
| |
| void RenderFrameHostImpl::DidContainInsecureFormAction() { |
| delegate_->DidContainInsecureFormAction(); |
| } |
| |
| void RenderFrameHostImpl::DocumentAvailableInMainFrame( |
| bool uses_temporary_zoom_level) { |
| if (!frame_tree_node_->IsMainFrame()) { |
| bad_message::ReceivedBadMessage( |
| GetProcess(), bad_message::RFH_INVALID_CALL_FROM_NOT_MAIN_FRAME); |
| return; |
| } |
| delegate_->DocumentAvailableInMainFrame(); |
| |
| if (!uses_temporary_zoom_level) |
| return; |
| |
| #if !defined(OS_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 // !defined(OS_ANDROID) |
| } |
| |
| void RenderFrameHostImpl::SetNeedsOcclusionTracking(bool needs_tracking) { |
| // Do not update the parent on behalf of inactive RenderFrameHost. See also |
| // https://crbug.com/972566. |
| if (IsInactiveAndDisallowReactivation()) |
| return; |
| |
| RenderFrameProxyHost* proxy = |
| frame_tree_node()->render_manager()->GetProxyToParent(); |
| if (!proxy) { |
| bad_message::ReceivedBadMessage(GetProcess(), |
| bad_message::RFH_NO_PROXY_TO_PARENT); |
| return; |
| } |
| |
| proxy->GetAssociatedRemoteFrame()->SetNeedsOcclusionTracking(needs_tracking); |
| } |
| |
| void RenderFrameHostImpl::SetVirtualKeyboardOverlayPolicy( |
| bool vk_overlays_content) { |
| should_virtual_keyboard_overlay_content_ = vk_overlays_content; |
| } |
| |
| bool RenderFrameHostImpl::ShouldVirtualKeyboardOverlayContent() const { |
| RenderFrameHostImpl* root_frame_host = |
| frame_tree_->root()->current_frame_host(); |
| return root_frame_host->should_virtual_keyboard_overlay_content_; |
| } |
| |
| void RenderFrameHostImpl::NotifyVirtualKeyboardOverlayRect( |
| const gfx::Rect& keyboard_rect) { |
| DCHECK(ShouldVirtualKeyboardOverlayContent()); |
| |
| RenderFrameHostImpl* root_frame_host = |
| frame_tree_->root()->current_frame_host(); |
| RenderWidgetHostViewBase* view = static_cast<RenderWidgetHostViewBase*>( |
| root_frame_host->render_view_host_->GetWidget()->GetView()); |
| if (!view) |
| return; |
| |
| gfx::PointF root_widget_origin(0.f, 0.f); |
| view->TransformPointToRootSurface(&root_widget_origin); |
| |
| gfx::Rect root_widget_rect; |
| if (!keyboard_rect.IsEmpty()) { |
| // If the rect is non-empty, we need to transform it to be widget-relative |
| // window (DIP coordinates). The input is client coordinates for the root |
| // window. |
| // Transform the widget rect origin to root relative coords. |
| root_widget_rect = gfx::Rect(root_widget_origin.x(), root_widget_origin.y(), |
| view->GetViewBounds().width(), |
| view->GetViewBounds().height()); |
| |
| // Intersect with the keyboard rect and transform back to widget-relative |
| // coordinates, which will be sent to the renderer. |
| root_widget_rect.Intersect(keyboard_rect); |
| root_widget_rect.Offset(-root_widget_origin.x(), -root_widget_origin.y()); |
| } |
| |
| // Notify each SiteInstance a single time. Renderer will take care of ensuring |
| // the event is dispatched to all relevant listeners in the grouping of frames |
| // for the SiteInstance. |
| // TODO(snianu): Transform from the main frame's coordinates to each |
| // individual frame client coordinates so that these are more usable from |
| // within iframes. |
| std::set<SiteInstance*> notified_instances; |
| for (RenderFrameHostImpl* node = this; node; node = node->GetParent()) { |
| SiteInstance* site_instance = node->GetSiteInstance(); |
| if (base::Contains(notified_instances, site_instance)) |
| continue; |
| |
| node->GetAssociatedLocalFrame()->NotifyVirtualKeyboardOverlayRect( |
| root_widget_rect); |
| notified_instances.insert(site_instance); |
| } |
| } |
| |
| #if defined(OS_ANDROID) |
| void RenderFrameHostImpl::UpdateUserGestureCarryoverInfo() { |
| delegate_->UpdateUserGestureCarryoverInfo(); |
| } |
| #endif |
| |
| void RenderFrameHostImpl::VisibilityChanged( |
| blink::mojom::FrameVisibility visibility) { |
| visibility_ = visibility; |
| } |
| |
| void RenderFrameHostImpl::DidChangeThemeColor( |
| base::Optional<SkColor> theme_color) { |
| render_view_host_->OnThemeColorChanged(this, theme_color); |
| } |
| |
| void RenderFrameHostImpl::DidChangeBackgroundColor(SkColor background_color, |
| bool color_adjust) { |
| render_view_host_->DidChangeBackgroundColor(this, 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::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) { |
| frame_tree_node_->DidChangeLoadProgress(load_progress); |
| } |
| |
| void RenderFrameHostImpl::DidFinishLoad(const GURL& validated_url) { |
| delegate_->OnDidFinishLoad(this, validated_url); |
| } |
| |
| void RenderFrameHostImpl::DispatchLoad() { |
| TRACE_EVENT1("navigation", "RenderFrameHostImpl::DispatchLoad", |
| "frame_tree_node", frame_tree_node_->frame_tree_node_id()); |
| |
| // 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 (IsInactiveAndDisallowReactivation()) |
| return; |
| |
| // We should never be receiving this message from a speculative RFH. |
| DCHECK(IsCurrent()); |
| |
| // Only frames with an out-of-process parent frame should be sending this |
| // message. |
| RenderFrameProxyHost* proxy = |
| frame_tree_node()->render_manager()->GetProxyToParent(); |
| if (!proxy) { |
| bad_message::ReceivedBadMessage(GetProcess(), |
| bad_message::RFH_NO_PROXY_TO_PARENT); |
| return; |
| } |
| |
| proxy->GetAssociatedRemoteFrame()->DispatchLoadEventForFrameOwner(); |
| } |
| |
| void RenderFrameHostImpl::GoToEntryAtOffset(int32_t offset, |
| bool has_user_gesture) { |
| delegate_->OnGoToEntryAtOffset(this, offset, has_user_gesture); |
| } |
| |
| 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_ != LifecycleState::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_ != LifecycleState::kActive) |
| return; |
| |
| ui::AXMode accessibility_mode = delegate_->GetAccessibilityMode(); |
| if (accessibility_mode.has_mode(ui::AXMode::kNativeAPIs)) { |
| BrowserAccessibilityManager* manager = |
| GetOrCreateBrowserAccessibilityManager(); |
| if (manager) |
| manager->OnFindInPageTermination(); |
| } |
| } |
| |
| void RenderFrameHostImpl::DocumentOnLoadCompleted() { |
| // 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) { |
| // 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 (IsInactiveAndDisallowReactivation()) |
| return; |
| |
| // We should never be receiving this message from a speculative RFH. |
| DCHECK(IsCurrent()); |
| |
| RenderFrameProxyHost* proxy = |
| frame_tree_node()->render_manager()->GetProxyToParent(); |
| if (!proxy) { |
| bad_message::ReceivedBadMessage(GetProcess(), |
| bad_message::RFH_NO_PROXY_TO_PARENT); |
| return; |
| } |
| proxy->GetAssociatedRemoteFrame()->AddResourceTimingFromChild( |
| std::move(timing)); |
| } |
| |
| RenderWidgetHostViewBase* RenderFrameHostImpl::GetViewForAccessibility() { |
| return static_cast<RenderWidgetHostViewBase*>( |
| frame_tree_node_->IsMainFrame() |
| ? render_view_host_->GetWidget()->GetView() |
| : GetMainFrame()->render_view_host_->GetWidget()->GetView()); |
| } |
| |
| void RenderFrameHostImpl::UpdateBrowserControlsState( |
| cc::BrowserControlsState constraints, |
| cc::BrowserControlsState current, |
| bool animate) { |
| DCHECK(frame_tree_node_->IsMainFrame()); |
| |
| // TODO(https://crbug.com/1154852): Asking for the LocalMainFrame interface |
| // before the RenderFrame is created is racy. |
| if (!IsRenderFrameCreated()) |
| return; |
| |
| GetAssociatedLocalMainFrame()->UpdateBrowserControlsState(constraints, |
| current, animate); |
| } |
| |
| bool RenderFrameHostImpl::Reload() { |
| NavigationControllerImpl* controller = static_cast<NavigationControllerImpl*>( |
| frame_tree_node_->navigator().GetController()); |
| return 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::IsInactiveAndDisallowReactivation() { |
| switch (lifecycle_state_) { |
| case LifecycleState::kRunningUnloadHandlers: |
| case LifecycleState::kReadyToBeDeleted: |
| return true; |
| case LifecycleState::kInBackForwardCache: |
| EvictFromBackForwardCacheWithReason( |
| BackForwardCacheMetrics::NotRestoredReason::kIgnoreEventAndEvict); |
| return true; |
| case LifecycleState::kSpeculative: |
| // We do not expect speculative 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 LifecycleState::kActive: |
| return false; |
| } |
| } |
| |
| void RenderFrameHostImpl::EvictFromBackForwardCache( |
| blink::mojom::RendererEvictionReason reason) { |
| EvictFromBackForwardCacheWithReason( |
| RendererEvictionReasonToNotRestoredReason(reason)); |
| } |
| |
| void RenderFrameHostImpl::EvictFromBackForwardCacheWithReason( |
| BackForwardCacheMetrics::NotRestoredReason reason) { |
| BackForwardCacheCanStoreDocumentResult can_store; |
| can_store.No(reason); |
| EvictFromBackForwardCacheWithReasons(can_store); |
| } |
| |
| void RenderFrameHostImpl::EvictFromBackForwardCacheWithReasons( |
| const BackForwardCacheCanStoreDocumentResult& can_store) { |
| TRACE_EVENT2("navigation", "RenderFrameHostImpl::EvictFromBackForwardCache", |
| "can_store", can_store.ToString(), "rfh", this); |
| 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->MarkNotRestoredWithReason(can_store); |
| |
| if (!in_back_forward_cache) { |
| TRACE_EVENT0("navigation", "BackForwardCache_EvictAfterDocumentRestored"); |
| // TODO(carlscab): We should no longer get into this branch thanks to |
| // https://crrev.com/c/2352815. Lets keep this old code for now just in case |
| // and replace with a CHECK once we are confident that is the case. |
| base::debug::DumpWithoutCrashing(); |
| BackForwardCacheMetrics::RecordEvictedAfterDocumentRestored( |
| BackForwardCacheMetrics::EvictedAfterDocumentRestoredReason:: |
| kByJavaScript); |
| |
| // 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(); |
| } |
| |
| bool RenderFrameHostImpl::HasSeenRecentXrOverlaySetup() { |
| static constexpr base::TimeDelta kMaxInterval = |
| base::TimeDelta::FromSeconds(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) { |
| // Consume the user activation when entering fullscreen mode in the browser |
| // side when the renderer is compromised and the fullscreen request is denied. |
| // Fullscreen can only be triggered by: a user activation, 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. |
| // TODO(lanwei): Investigate whether we can terminate the renderer when the |
| // user activation has already been consumed. |
| if (!delegate_->HasSeenRecentScreenOrientationChange() && |
| !WindowPlacementAllowsFullscreen() && !HasSeenRecentXrOverlaySetup() && |
| !GetContentClient() |
| ->browser() |
| ->CanEnterFullscreenWithoutUserActivation()) { |
| bool is_consumed = frame_tree_node_->UpdateUserActivationState( |
| blink::mojom::UserActivationUpdateType::kConsumeTransientActivation, |
| blink::mojom::UserActivationNotificationType::kNone); |
| if (!is_consumed) { |
| DLOG(ERROR) << "Cannot enter fullscreen because there is no transient " |
| << "user activation, orientation change, or XR overlay."; |
| std::move(callback).Run(/*granted=*/false); |
| return; |
| } |
| } |
| |
| if (!delegate_->CanEnterFullscreenMode()) { |
| std::move(callback).Run(/*granted=*/false); |
| return; |
| } |
| 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 SiteInstance. 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 SiteInstance: 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->frame_tree_node()->render_manager()->GetRenderFrameProxyHost( |
| parent_site_instance); |
| child_proxy->GetAssociatedRemoteFrame()->WillEnterFullscreen( |
| options.Clone()); |
| notified_instances.insert(parent_site_instance); |
| } |
| |
| delegate_->EnterFullscreenMode(this, *options); |
| delegate_->FullscreenStateChanged(this, true /* is_fullscreen */, |
| 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); |
| 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); |
| 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()); |
| auto* main_frame_site_instance = GetSiteInstance(); |
| for (auto* subframe : GetFramesInSubtree()) { |
| auto* rfhi = static_cast<RenderFrameHostImpl*>(subframe); |
| if (rfhi->GetSiteInstance() == main_frame_site_instance && |
| rfhi->has_unload_handler_) { |
| return true; |
| } |
| } |
| return false; |
| } |
| |
| bool RenderFrameHostImpl::InsidePortal() { |
| return GetRenderViewHost()->GetDelegate()->IsPortal(); |
| } |
| |
| void RenderFrameHostImpl::DidFinishDocumentLoad() { |
| dom_content_loaded_ = true; |
| 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 base::string16& text, |
| uint32_t offset, |
| const gfx::Range& range) { |
| has_selection_ = !text.empty(); |
| GetRenderWidgetHost()->SelectionChanged(text, offset, range); |
| } |
| |
| void RenderFrameHostImpl::DidReceiveFirstUserActivation() { |
| delegate_->DidReceiveFirstUserActivation(this); |
| } |
| |
| 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_ != LifecycleState::kActive) |
| return; |
| |
| frame_tree_node_->UpdateUserActivationState(update_type, notification_type); |
| } |
| |
| void RenderFrameHostImpl::HadStickyUserActivationBeforeNavigationChanged( |
| bool value) { |
| frame_tree_node_->OnSetHadStickyUserActivationBeforeNavigation(value); |
| } |
| |
| void RenderFrameHostImpl::ScrollRectToVisibleInParentFrame( |
| const gfx::Rect& rect_to_scroll, |
| blink::mojom::ScrollIntoViewParamsPtr params) { |
| RenderFrameProxyHost* proxy = |
| frame_tree_node_->render_manager()->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 (IsInactiveAndDisallowReactivation()) |
| return; |
| |
| RenderFrameProxyHost* proxy = |
| frame_tree_node()->render_manager()->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; |
| } |
| |
| proxy->GetAssociatedRemoteFrame()->BubbleLogicalScroll(direction, |
| granularity); |
| } |
| |
| void RenderFrameHostImpl::RenderFallbackContentInParentProcess() { |
| bool is_object_type = |
| frame_tree_node()->current_replication_state().frame_owner_element_type == |
| blink::mojom::FrameOwnerElementType::kObject; |
| if (!is_object_type) { |
| // Only object elements are expected to render their own fallback content |
| // and since the owner type is set at the creation time of the |
| // FrameTreeNode, this received IPC makes no sense here. |
| bad_message::ReceivedBadMessage( |
| GetProcess(), bad_message::RFH_CANNOT_RENDER_FALLBACK_CONTENT); |
| return; |
| } |
| |
| // The ContentFrame() of the owner element in parent process could be either |
| // a frame or a proxy. When navigating cross-site from a frame which is same- |
| // site with its parent, the frame is still local (e.g., about:blank). |
| // However, navigating from an origin which is cross-site with parent, the |
| // frame of the owner is a proxy. |
| auto* rfh = frame_tree_node()->current_frame_host(); |
| if (rfh->GetSiteInstance() == rfh->GetParent()->GetSiteInstance()) { |
| rfh->GetAssociatedLocalFrame()->RenderFallbackContent(); |
| } else if (auto* proxy = |
| frame_tree_node()->render_manager()->GetProxyToParent()) { |
| proxy->GetAssociatedRemoteFrame()->RenderFallbackContent(); |
| } |
| } |
| |
| 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) |
| 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::DidLoadResourceFromMemoryCache( |
| const GURL& url, |
| const std::string& http_method, |
| const std::string& mime_type, |
| network::mojom::RequestDestination request_destination) { |
| delegate_->DidLoadResourceFromMemoryCache(this, url, http_method, mime_type, |
| request_destination); |
| } |
| |
| void RenderFrameHostImpl::DidChangeFrameOwnerProperties( |
| const base::UnguessableToken& 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_tree_node()->frame_owner_properties().is_display_none; |
| |
| child->frame_tree_node()->set_frame_owner_properties(*properties); |
| |
| child->frame_tree_node()->render_manager()->OnDidUpdateFrameOwnerProperties( |
| *properties); |
| if (has_display_none_property_changed) { |
| delegate_->DidChangeDisplayState( |
| child, properties->is_display_none /* is_display_none */); |
| } |
| } |
| |
| void RenderFrameHostImpl::DidChangeOpener( |
| const base::Optional<base::UnguessableToken>& opener_frame_token) { |
| frame_tree_node_->render_manager()->DidChangeOpener(opener_frame_token, |
| GetSiteInstance()); |
| } |
| |
| void RenderFrameHostImpl::DidChangeCSPAttribute( |
| const base::UnguessableToken& child_frame_token, |
| network::mojom::ContentSecurityPolicyPtr parsed_csp_attribute) { |
| auto* child = |
| FindAndVerifyChild(child_frame_token, bad_message::RFH_CSP_ATTRIBUTE); |
| if (!child) |
| return; |
| |
| child->frame_tree_node()->set_csp_attribute(std::move(parsed_csp_attribute)); |
| } |
| |
| void RenderFrameHostImpl::DidChangeFramePolicy( |
| const base::UnguessableToken& child_frame_token, |
| const blink::FramePolicy& frame_policy) { |
| // Ensure that a frame can only update sandbox flags or feature policy for its |
| // immediate children. If this is not the case, the renderer is considered |
| // malicious and is killed. |
| RenderFrameHostImpl* child = FindAndVerifyChild( |
| // TODO(iclelland): Rename this message |
| child_frame_token, bad_message::RFH_SANDBOX_FLAGS); |
| if (!child) |
| return; |
| |
| child->frame_tree_node()->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->GetSiteInstance() != GetSiteInstance()) { |
| child->GetAssociatedLocalFrame()->DidUpdateFramePolicy(frame_policy); |
| } |
| } |
| |
| void RenderFrameHostImpl::CapturePaintPreviewOfSubframe( |
| const gfx::Rect& clip_rect, |
| const base::UnguessableToken& guid) { |
| if (IsInactiveAndDisallowReactivation()) |
| 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::BindBrowserInterfaceBrokerReceiver( |
| mojo::PendingReceiver<blink::mojom::BrowserInterfaceBroker> receiver) { |
| DCHECK(receiver.is_valid()); |
| broker_receiver_.Bind(std::move(receiver)); |
| broker_receiver_.SetFilter(std::make_unique<ActiveURLMessageFilter>(this)); |
| } |
| |
| void RenderFrameHostImpl::BindDomOperationControllerHostReceiver( |
| mojo::PendingAssociatedReceiver<mojom::DomAutomationControllerHost> |
| receiver) { |
| DCHECK(receiver.is_valid()); |
| 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_timeout_ = timeout; |
| if (keep_alive_handle_factory_) |
| keep_alive_handle_factory_->SetTimeout(keep_alive_timeout_); |
| } |
| |
| void RenderFrameHostImpl::UpdateState(const blink::PageState& state) { |
| // 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; |
| } |
| |
| delegate_->UpdateStateForFrame(this, state); |
| } |
| |
| void RenderFrameHostImpl::OpenURL(mojom::OpenURLParamsPtr params) { |
| // Verify and unpack the Mojo payload. |
| GURL validated_url; |
| scoped_refptr<network::SharedURLLoaderFactory> blob_url_loader_factory; |
| if (!VerifyOpenURLParams(GetSiteInstance(), params, &validated_url, |
| &blob_url_loader_factory)) { |
| return; |
| } |
| |
| TRACE_EVENT1("navigation", "RenderFrameHostImpl::OpenURL", "url", |
| validated_url.possibly_invalid_spec()); |
| |
| frame_tree_node_->navigator().RequestOpenURL( |
| this, validated_url, |
| params->initiator_frame_token.has_value() |
| ? &(params->initiator_frame_token.value()) |
| : nullptr, |
| 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::DidStopLoading() { |
| TRACE_EVENT1("navigation", "RenderFrameHostImpl::DidStopLoading", |
| "frame_tree_node", frame_tree_node_->frame_tree_node_id()); |
| |
| // This method should never be called when the frame is not loading. |
| // Unfortunately, it can happen if a history navigation happens during a |
| // BeforeUnload or Unload event. |
| // 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(json_string); |
| } |
| |
| std::unique_ptr<blink::PendingURLLoaderFactoryBundle> |
| RenderFrameHostImpl::CreateCrossOriginPrefetchLoaderFactoryBundle() { |
| network::mojom::URLLoaderFactoryParamsPtr factory_params = |
| URLLoaderFactoryParamsHelper::CreateForPrefetch( |
| this, BuildClientSecurityState()); |
| |
| 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), |
| // This is for renderer prefetches, so it's associated with this page, not |
| // pending navigation. |
| ukm::SourceIdObj::FromInt64(GetPageUkmSourceId()), |
| pending_default_factory.InitWithNewPipeAndPassReceiver()); |
| |
| return std::make_unique<blink::PendingURLLoaderFactoryBundle>( |
| std::move(pending_default_factory), |
| blink::PendingURLLoaderFactoryBundle::SchemeMap(), |
| CreateURLLoaderFactoriesForIsolatedWorlds( |
| FindLatestNavigationRequestThatIsStillCommitting(), |
| isolated_worlds_requiring_separate_url_loader_factory_), |
| bypass_redirect_checks); |
| } |
| |
| base::WeakPtr<RenderFrameHostImpl> RenderFrameHostImpl::GetWeakPtr() { |
| return weak_ptr_factory_.GetWeakPtr(); |
| } |
| |
| void RenderFrameHostImpl::CreateNewWindow( |
| mojom::CreateNewWindowParamsPtr params, |
| CreateNewWindowCallback callback) { |
| DCHECK_CURRENTLY_ON(BrowserThread::UI); |
| TRACE_EVENT2("navigation", "RenderFrameHostImpl::CreateNewWindow", |
| "frame_tree_node", frame_tree_node_->frame_tree_node_id(), "url", |
| params->target_url.possibly_invalid_spec()); |
| |
| 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, ¶ms->target_url); |
| |
| bool effective_transient_activation_state = |
| params->allow_popup || frame_tree_node_->HasTransientUserActivation(); |
| |
| // Ignore window creation when sent from a frame that's not current or |
| // created. |
| bool can_create_window = |
| IsCurrent() && render_frame_created_ && |
| GetContentClient()->browser()->CanCreateWindow( |
| this, GetLastCommittedURL(), GetMainFrame()->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); |
| |
| bool was_consumed = false; |
| if (can_create_window) { |
| // Consume activation even w/o User Activation v2, to sync other renderers |
| // with calling renderer. |
| was_consumed = frame_tree_node_->UpdateUserActivationState( |
| blink::mojom::UserActivationUpdateType::kConsumeTransientActivation, |
| blink::mojom::UserActivationNotificationType::kNone); |
| } else { |
| std::move(callback).Run(mojom::CreateNewWindowStatus::kIgnore, nullptr); |
| return; |
| } |
| |
| // 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 = BrowserContext::GetStoragePartition( |
| GetSiteInstance()->GetBrowserContext(), GetSiteInstance()); |
| 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); |
| } |
| |
| network::CrossOriginOpenerPolicy popup_coop; |
| network::CrossOriginEmbedderPolicy popup_coep; |
| // On popup creation, if the opener and the openers's top-level document |
| // are same origin, then the popup's initial empty document inherits its |
| // COOP policy from the opener's top-level document. See |
| // https://gist.github.com/annevk/6f2dd8c79c77123f39797f6bdac43f3e#model |
| RenderFrameHostImpl* top_level_opener = GetMainFrame(); |
| // Verify that they are same origin. |
| if (top_level_opener->GetLastCommittedOrigin().IsSameOriginWith( |
| GetLastCommittedOrigin())) { |
| popup_coop = top_level_opener->cross_origin_opener_policy(); |
| } else { |
| // The documents are cross origin, leave COOP of the popup to the default |
| // unsafe-none. |
| // Then set the popup to noopener if the top level COOP is same origin. |
| if (top_level_opener->cross_origin_opener_policy().value == |
| network::mojom::CrossOriginOpenerPolicyValue::kSameOrigin) { |
| 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(); |
| } |
| } |
| |
| // The initial empty document in the popup inherits the COEP of its opener (if |
| // any). |
| if (!params->opener_suppressed) |
| popup_coep = cross_origin_embedder_policy(); |
| |
| int popup_virtual_browsing_context_group = |
| params->opener_suppressed |
| ? CrossOriginOpenerPolicyReporter::NextVirtualBrowsingContextGroup() |
| : top_level_opener->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_window| is valid in this stack frame since |
| // nothing can delete it until this thread is freed up again. |
| RenderFrameHostDelegate* new_window = |
| delegate_->CreateNewWindow(this, *params, is_new_browsing_instance, |
| was_consumed, cloned_namespace.get()); |
| |
| if (is_new_browsing_instance || !new_window) { |
| // 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* main_frame = new_window->GetMainFrame(); |
| |
| // When the popup is created, it hasn't committed any navigation yet - its |
| // initial empty document should inherit the origin of its opener (the origin |
| // may change after the first commit). See also https://crbug.com/932067. |
| // |
| // Note that that origin of the new frame might depend on sandbox flags. |
| // Checking sandbox flags of the new frame should be safe at this point, |
| // because the flags should be already inherited by the CreateNewWindow call |
| // above. |
| main_frame->SetOriginDependentStateOfNewFrame(GetLastCommittedOrigin()); |
| main_frame->cross_origin_opener_policy_ = popup_coop; |
| main_frame->cross_origin_embedder_policy_ = popup_coep; |
| main_frame->virtual_browsing_context_group_ = |
| popup_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_reporter()) { |
| main_frame->set_coop_reporter( |
| std::make_unique<CrossOriginOpenerPolicyReporter>( |
| GetProcess()->GetStoragePartition(), GetLastCommittedURL(), |
| params->referrer->url, popup_coop, |
| isolation_info_.network_isolation_key())); |
| } |
| |
| if (main_frame->waiting_for_init_) { |
| // Need to check |waiting_for_init_| as some paths inside CreateNewWindow |
| // call above (eg if WebContentsDelegate::IsWebContentsCreationOverridden() |
| // returns true) will resume requests by calling RenderFrameHostImpl::Init. |
| main_frame->frame_->BlockRequests(); |
| } |
| |
| mojo::PendingRemote<blink::mojom::BrowserInterfaceBroker> |
| browser_interface_broker; |
| main_frame->BindBrowserInterfaceBrokerReceiver( |
| browser_interface_broker.InitWithNewPipeAndPassReceiver()); |
| |
| mojo::PendingAssociatedRemote<blink::mojom::FrameWidget> blink_frame_widget; |
| mojo::PendingAssociatedReceiver<blink::mojom::FrameWidget> |
| blink_frame_widget_receiver = |
| blink_frame_widget.InitWithNewEndpointAndPassReceiver(); |
| |
| mojo::PendingAssociatedRemote<blink::mojom::FrameWidgetHost> |
| blink_frame_widget_host; |
| mojo::PendingAssociatedReceiver<blink::mojom::FrameWidgetHost> |
| blink_frame_widget_host_receiver = |
| blink_frame_widget_host.InitWithNewEndpointAndPassReceiver(); |
| |
| mojo::PendingAssociatedRemote<blink::mojom::Widget> blink_widget; |
| mojo::PendingAssociatedReceiver<blink::mojom::Widget> blink_widget_receiver = |
| blink_widget.InitWithNewEndpointAndPassReceiver(); |
| |
| mojo::PendingAssociatedRemote<blink::mojom::WidgetHost> blink_widget_host; |
| mojo::PendingAssociatedReceiver<blink::mojom::WidgetHost> |
| blink_widget_host_receiver = |
| blink_widget_host.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(); |
| |
| // TODO(danakj): The main frame's RenderWidgetHost has no RenderWidgetHostView |
| // yet here. It seems like it should though? In the meantime we send some |
| // nonsense with a semi-valid but incorrect ScreenInfo (it needs a |
| // RenderWidgetHostView to be correct). An updates VisualProperties will get |
| // to the RenderWidget eventually. |
| blink::VisualProperties visual_properties; |
| main_frame->GetLocalRenderWidgetHost()->GetScreenInfo( |
| &visual_properties.screen_info); |
| main_frame->GetLocalRenderWidgetHost()->BindFrameWidgetInterfaces( |
| std::move(blink_frame_widget_host_receiver), |
| std::move(blink_frame_widget)); |
| main_frame->GetLocalRenderWidgetHost()->BindWidgetInterfaces( |
| std::move(blink_widget_host_receiver), std::move(blink_widget)); |
| main_frame->render_view_host()->BindPageBroadcast(std::move(page_broadcast)); |
| |
| bool wait_for_debugger = |
| devtools_instrumentation::ShouldWaitForDebuggerInWindowOpen(); |
| mojom::CreateNewWindowReplyPtr reply = mojom::CreateNewWindowReply::New( |
| main_frame->GetRenderViewHost()->GetRoutingID(), |
| main_frame->GetRoutingID(), main_frame->GetFrameToken(), |
| main_frame->GetLocalRenderWidgetHost()->GetRoutingID(), visual_properties, |
| std::move(blink_frame_widget_host), |
| std::move(blink_frame_widget_receiver), std::move(blink_widget_host), |
| std::move(blink_widget_receiver), std::move(page_broadcast_receiver), |
| std::move(browser_interface_broker), cloned_namespace->id(), |
| main_frame->GetDevToolsFrameToken(), wait_for_debugger, |
| main_frame->policy_container_host()->CreatePolicyContainerForBlink()); |
| |
| std::move(callback).Run(mojom::CreateNewWindowStatus::kSuccess, |
| std::move(reply)); |
| } |
| |
| void RenderFrameHostImpl::CreatePortal( |
| mojo::PendingAssociatedReceiver<blink::mojom::Portal> pending_receiver, |
| mojo::PendingAssociatedRemote<blink::mojom::PortalClient> client, |
| CreatePortalCallback callback) { |
| if (!Portal::IsEnabled()) { |
| frame_host_associated_receiver_.ReportBadMessage( |
| "blink.mojom.Portal can only be used if the Portals feature is " |
| "enabled."); |
| return; |
| } |
| |
| // We don't support attaching a portal inside a nested browsing context. |
| if (!is_main_frame()) { |
| 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(); |
| |
| // Since the portal is newly created and has yet to commit a navigation, this |
| // state is trivial. |
| const FrameReplicationState& initial_replicated_state = |
| proxy_host->frame_tree_node()->current_replication_state(); |
| DCHECK(initial_replicated_state.origin.opaque()); |
| |
| std::move(callback).Run(proxy_host->GetRoutingID(), initial_replicated_state, |
| (*it)->portal_token(), proxy_host->GetFrameToken(), |
| (*it)->GetDevToolsFrameToken()); |
| } |
| |
| void RenderFrameHostImpl::AdoptPortal(const blink::PortalToken& portal_token, |
| 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(callback).Run( |
| proxy_host->GetRoutingID(), |
| static_cast<RenderWidgetHostViewBase*>(proxy_host->frame_tree_node() |
| ->render_manager() |
| ->GetRenderWidgetHostView()) |
| ->GetFrameSinkId(), |
| proxy_host->frame_tree_node()->current_replication_state(), |
| proxy_host->GetFrameToken(), portal->GetDevToolsFrameToken()); |
| } |
| |
| 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) { |
| // 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( |
| agent_scheduling_group_, widget_route_id, |
| std::move(blink_popup_widget_host), std::move(blink_widget_host), |
| std::move(blink_widget)); |
| if (create_new_popup_widget_callback_) |
| create_new_popup_widget_callback_.Run(widget); |
| } |
| |
| void RenderFrameHostImpl::IssueKeepAliveHandle( |
| mojo::PendingReceiver<blink::mojom::KeepAliveHandle> receiver) { |
| DCHECK_CURRENTLY_ON(BrowserThread::UI); |
| if (GetProcess()->IsKeepAliveRefCountDisabled()) |
| return; |
| |
| if (base::FeatureList::IsEnabled(network::features::kDisableKeepaliveFetch)) { |
| return; |
| } |
| |
| if (!keep_alive_handle_factory_) { |
| keep_alive_handle_factory_ = |
| std::make_unique<KeepAliveHandleFactory>(GetProcess()); |
| keep_alive_handle_factory_->SetTimeout(keep_alive_timeout_); |
| } |
| keep_alive_handle_factory_->Create(std::move(receiver)); |
| } |
| |
| // TODO(ahemery): Move checks to mojo bad message reporting. |
| void RenderFrameHostImpl::BeginNavigation( |
| mojom::CommonNavigationParamsPtr common_params, |
| mojom::BeginNavigationParamsPtr begin_params, |
| mojo::PendingRemote<blink::mojom::BlobURLToken> blob_url_token, |
| mojo::PendingAssociatedRemote<mojom::NavigationClient> navigation_client, |
| mojo::PendingRemote<blink::mojom::NavigationInitiator> |
| navigation_initiator) { |
| 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; |
| } |
| |
| if (IsInactiveAndDisallowReactivation()) |
| return; |
| |
| TRACE_EVENT2("navigation", "RenderFrameHostImpl::BeginNavigation", |
| "frame_tree_node", frame_tree_node_->frame_tree_node_id(), "url", |
| common_params->url.possibly_invalid_spec()); |
| |
| DCHECK(navigation_client.is_valid()); |
| |
| mojom::CommonNavigationParamsPtr validated_params = common_params.Clone(); |
| if (!VerifyBeginNavigationCommonParams(GetSiteInstance(), &*validated_params)) |
| 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 Feature Policy feature. |
| if (begin_params->trust_token_params && !GetParent()) { |
| mojo::ReportBadMessage("RFHI: Trust Token params in main frame nav"); |
| return; |
| } |
| if (begin_params->trust_token_params && |
| ParentNeedsTrustTokenFeaturePolicy(*begin_params)) { |
| RenderFrameHostImpl* parent = GetParent(); |
| if (!parent->IsFeatureEnabled( |
| blink::mojom::FeaturePolicyFeature::kTrustTokenRedemption)) { |
| mojo::ReportBadMessage( |
| "RFHI: Mandatory Trust Tokens Feature Policy feature is absent"); |
| 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(navigation_initiator)); |
| return; |
| } |
| |
| 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), |
| std::move(navigation_initiator), EnsurePrefetchedSignedExchangeCache(), |
| MaybeCreateWebBundleHandleTracker()); |
| } |
| |
| void RenderFrameHostImpl::SubresourceResponseStarted( |
| const GURL& url, |
| net::CertStatus cert_status) { |
| delegate_->SubresourceResponseStarted(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 std::vector<ui::AXTreeUpdate>& updates, |
| const std::vector<ui::AXEvent>& events, |
| int32_t reset_token, |
| HandleAXEventsCallback callback) { |
| // 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) { |
| std::move(callback).Run(); |
| return; |
| } |
| accessibility_reset_token_ = 0; |
| |
| ui::AXMode accessibility_mode = delegate_->GetAccessibilityMode(); |
| if (accessibility_mode.is_mode_off() || IsInactiveAndDisallowReactivation()) { |
| std::move(callback).Run(); |
| return; |
| } |
| |
| if (accessibility_mode.has_mode(ui::AXMode::kNativeAPIs)) |
| GetOrCreateBrowserAccessibilityManager(); |
| |
| AXEventNotificationDetails details; |
| details.ax_tree_id = GetAXTreeID(); |
| details.events = events; |
| |
| details.updates.resize(updates.size()); |
| for (size_t i = 0; i < updates.size(); ++i) { |
| details.updates[i] = updates[i]; |
| if (updates[i].has_tree_data) { |
| ax_tree_data_ = updates[i].tree_data; |
| details.updates[i].tree_data = GetAXTreeData(); |
| } |
| } |
| |
| 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); |
| } |
| } |
| } |
| |
| // Always send an ACK or the renderer can be in a bad state. |
| std::move(callback).Run(); |
| } |
| |
| void RenderFrameHostImpl::HandleAXLocationChanges( |
| std::vector<mojom::LocationChangesPtr> changes) { |
| if (accessibility_reset_token_ || IsInactiveAndDisallowReactivation()) |
| 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() { |
| return delegate_->GetRecordAggregateWatchTimeCallback(); |
| } |
| |
| 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) { |
| // 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 CanCommitStatus::CAN_COMMIT_ORIGIN_AND_URL; |
| } |
| |
| // Renderer-debug URLs can never be committed. |
| if (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 |origin_requests_isolation| = false. |
| if (!Navigator::CheckWebUIRendererDoesNotDisplayNormalURL( |
| this, UrlInfo(url, false /* origin_requests_isolation */), |
| /* 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 (!frame_tree_node()->IsMainFrame()) { |
| 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. |
| base::debug::SetCrashKeyString( |
| base::debug::AllocateCrashKeyString( |
| "oopif_in_mhtml_page", base::debug::CrashKeySize::Size32), |
| is_mhtml_document() ? "is_mhtml_doc" : "not_mhtml_doc"); |
| LogCanCommitOriginAndUrlFailureReason("oopif_in_mhtml_page"); |
| return CanCommitStatus::CANNOT_COMMIT_URL; |
| } |
| } |
| |
| // 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; |
| } |
| |
| auto* policy = ChildProcessSecurityPolicyImpl::GetInstance(); |
| const CanCommitStatus can_commit_status = policy->CanCommitOriginAndUrl( |
| GetProcess()->GetID(), GetSiteInstance()->GetIsolationContext(), origin, |
| UrlInfo(url, false /* origin_requests_isolation */), |
| GetSiteInstance()->GetCoopCoepCrossOriginIsolatedInfo()); |
| 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 (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; |
| } |
| |
| void RenderFrameHostImpl::Stop() { |
| TRACE_EVENT1("navigation", "RenderFrameHostImpl::Stop", "frame_tree_node", |
| frame_tree_node_->frame_tree_node_id()); |
| 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 || |
| frame_tree_node_->IsMainFrame()); |
| |
| 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), |
| "&RenderFrameHostImpl", static_cast<void*>(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, |
| true /* send_ipc */, is_reload); |
| } |
| } |
| } |
| |
| bool RenderFrameHostImpl::CheckOrDispatchBeforeUnloadForSubtree( |
| bool subframes_only, |
| bool send_ipc, |
| bool is_reload) { |
| bool found_beforeunload = 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. |
| bool should_run_beforeunload = rfh->has_before_unload_handler_; |
| // TODO(alexmos): Many tests, as well as some DevTools cases, currently |
| // assume that beforeunload for a navigating/closing frame is always sent |
| // to the renderer. For now, keep this assumption by checking |rfh == |
| // this|. In the future, this condition should be removed, and beforeunload |
| // should only be sent when a handler is registered. For subframes of a |
| // navigating/closing frame, this assumption was never present, so |
| // subframes are included only if they have registered a beforeunload |
| // handler. |
| if (rfh == this) |
| should_run_beforeunload = true; |
| |
| 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->IsDescendantOf(added_rfh) && |
| rfh->GetSiteInstance() == added_rfh->GetSiteInstance()) { |
| has_same_site_ancestor = true; |
| break; |
| } |
| } |
| if (has_same_site_ancestor) |
| continue; |
| |
| // Add |rfh| to the list of frames that need to receive beforeunload |
| // ACKs. |
| beforeunload_pending_replies_.insert(rfh); |
| |
| SendBeforeUnload(is_reload, rfh->GetWeakPtr()); |
| } |
| |
| 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, |
| true /* treat_as_final_completion_callback */, |
| approx_renderer_start_time, base::TimeTicks::Now())); |
| } |
| |
| bool RenderFrameHostImpl::ShouldDispatchBeforeUnload( |
| bool check_subframes_only) { |
| return CheckOrDispatchBeforeUnloadForSubtree( |
| check_subframes_only, false /* send_ipc */, false /* is_reload */); |
| } |
| |
| void RenderFrameHostImpl::SetBeforeUnloadTimeoutDelayForTesting( |
| const base::TimeDelta& timeout) { |
| beforeunload_timeout_delay_ = timeout; |
| } |
| |
| void RenderFrameHostImpl::StartPendingDeletionOnSubtree() { |
| ResetNavigationsForPendingDeletion(); |
| |
| DCHECK(IsPendingDeletion()); |
| 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_; rfh != parent_; rfh = rfh->parent_) { |
| if (rfh->GetSiteInstance() == child->GetSiteInstance()) |
| local_ancestor = rfh; |
| } |
| |
| local_ancestor->DeleteRenderFrame(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. |
| bool wait_for_unload_handlers = |
| child->has_unload_handlers() && !child->IsInBackForwardCache(); |
| LifecycleState child_lifecycle_state = |
| wait_for_unload_handlers ? LifecycleState::kRunningUnloadHandlers |
| : LifecycleState::kReadyToBeDeleted; |
| child->SetLifecycleState(child_lifecycle_state); |
| } |
| |
| node->frame_tree()->FrameUnloading(node); |
| } |
| } |
| } |
| |
| void RenderFrameHostImpl::PendingDeletionCheckCompleted() { |
| if (lifecycle_state_ == LifecycleState::kReadyToBeDeleted && |
| children_.empty()) { |
| if (is_waiting_for_unload_ack_) |
| OnUnloaded(); |
| else |
| parent_->RemoveChild(frame_tree_node_); |
| } |
| } |
| |
| void RenderFrameHostImpl::PendingDeletionCheckCompletedOnSubtree() { |
| if (children_.empty()) { |
| PendingDeletionCheckCompleted(); |
| return; |
| } |
| |
| // Collect children first before calling PendingDeletionCheckCompleted() on |
| // them, because it may delete them. |
| std::vector<RenderFrameHostImpl*> children_rfh; |
| for (std::unique_ptr<FrameTreeNode>& child : children_) |
| children_rfh.push_back(child->current_frame_host()); |
| |
| for (RenderFrameHostImpl* child_rfh : children_rfh) |
| child_rfh->PendingDeletionCheckCompletedOnSubtree(); |
| } |
| |
| 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", |
| "frame_tree_node", frame_tree_node_->frame_tree_node_id()); |
| |
| // 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_); |
| } |
| |
| auto opener_frame_token = |
| frame_tree_node_->render_manager()->GetOpenerFrameToken( |
| GetSiteInstance()); |
| 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())); |
| |
| base::Optional<base::UnguessableToken> 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 base::string16& 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, |
| GetSiteInstance()->GetCoopCoepCrossOriginIsolatedInfo())) { |
| return false; |
| } |
| DCHECK(frame_tree_node_->IsMainFrame()); |
| DCHECK_NE(old_frame_host, this); |
| DCHECK_NE(old_frame_host->GetSiteInstance(), GetSiteInstance()); |
| return true; |
| } |
| |
| void RenderFrameHostImpl::CommitNavigation( |
| NavigationRequest* navigation_request, |
| mojom::CommonNavigationParamsPtr common_params, |
| mojom::CommitNavigationParamsPtr commit_params, |
| network::mojom::URLResponseHeadPtr response_head, |
| mojo::ScopedDataPipeConsumerHandle response_body, |
| network::mojom::URLLoaderClientEndpointsPtr url_loader_client_endpoints, |
| bool is_view_source, |
| base::Optional<SubresourceLoaderParams> subresource_loader_params, |
| base::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", |
| "frame_tree_node", frame_tree_node_->frame_tree_node_id(), "url", |
| common_params->url.possibly_invalid_spec()); |
| DCHECK(!IsRendererDebugURL(common_params->url)); |
| DCHECK(navigation_request); |
| |
| 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 (!frame_tree_node()->IsMainFrame()) { |
| 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) { |
| // Main frame srcdoc navigation are meaningless. They are blocked whenever a |
| // navigation attempt is made. It shouldn't reach CommitNavigation. |
| CHECK(!frame_tree_node_->IsMainFrame()); |
| |
| // An about:srcdoc document is always same SiteInstance with its parent. |
| // Otherwise, it won't be able to load. The parent's document contains the |
| // iframe and its srcdoc attribute. |
| CHECK_EQ(GetSiteInstance(), parent_->GetSiteInstance()); |
| } |
| |
| // 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 = GetSiteInstance()->GetProcessLock(); |
| if (process_lock != ProcessLock::CreateForErrorPage() && |
| common_params->url.IsStandard() && |
| !policy->CanAccessDataForOrigin(GetProcess()->GetID(), |
| common_params->url) && |
| !is_mhtml_subframe) { |
| base::debug::SetCrashKeyString( |
| base::debug::AllocateCrashKeyString("lock_url", |
| base::debug::CrashKeySize::Size64), |
| process_lock.ToString()); |
| base::debug::SetCrashKeyString( |
| base::debug::AllocateCrashKeyString("commit_origin", |
| base::debug::CrashKeySize::Size64), |
| common_params->url.GetOrigin().spec()); |
| base::debug::SetCrashKeyString( |
| base::debug::AllocateCrashKeyString("is_main_frame", |
| base::debug::CrashKeySize::Size32), |
| frame_tree_node_->IsMainFrame() ? "true" : "false"); |
| NOTREACHED() << "Commiting in incompatible process for URL: " |
| << process_lock.lock_url() << " lock vs " |
| << common_params->url.GetOrigin(); |
| base::debug::DumpWithoutCrashing(); |
| } |
| |
| const bool is_first_navigation = !has_committed_any_navigation_; |
| has_committed_any_navigation_ = true; |
| |
| // 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. |
| if (!is_same_document) |
| web_bundle_handle_ = std::move(web_bundle_handle); |
| |
| UpdatePermissionsForNavigation(*common_params, *commit_params); |
| |
| // 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 (is_view_source && IsCurrent()) { |
| DCHECK(!GetParent()); |
| 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(); |
| |
| // TODO(crbug.com/979296): Consider changing this code to copy an origin |
| // instead of creating one from a URL which lacks opacity information. |
| url::Origin main_world_origin_for_url_loader_factory = |
| navigation_request->GetOriginForURLLoaderFactory(); |
| |
| if (navigation_request->appcache_handle()) { |
| // AppCache may create a subresource URLLoaderFactory later, so make sure it |
| // has the correct origin to use when calling |
| // ContentBrowserClient::WillCreateURLLoaderFactory(). |
| navigation_request->appcache_handle() |
| ->host() |
| ->set_origin_for_url_loader_factory( |
| main_world_origin_for_url_loader_factory); |
| } |
| // This needs to ask |navigation_request| for the UKM ID since the commit is |
| // in progress but not yet done. |
| ukm::SourceIdObj next_page_ukm_source_id = |
| ukm::SourceIdObj::FromInt64(navigation_request->GetNextPageUkmSourceId()); |
| std::unique_ptr<blink::PendingURLLoaderFactoryBundle> |
| subresource_loader_factories; |
| if ((!is_same_document || is_first_navigation) && !is_srcdoc) { |
| recreate_default_url_loader_factory_after_network_service_crash_ = false; |
| subresource_loader_factories = |
| std::make_unique<blink::PendingURLLoaderFactoryBundle>(); |
| BrowserContext* browser_context = GetSiteInstance()->GetBrowserContext(); |
| |
| // NOTE: On Network Service navigations, we want to ensure that a frame is |
| // given everything it will need to load any accessible subresources. We |
| // however only do this for cross-document navigations, because the |
| // alternative would be redundant effort. |
| if (subresource_loader_params && |
| subresource_loader_params->pending_appcache_loader_factory.is_valid()) { |
| // If the caller has supplied a factory for AppCache, use it. |
| mojo::Remote<network::mojom::URLLoaderFactory> appcache_remote(std::move( |
| subresource_loader_params->pending_appcache_loader_factory)); |
| |
| mojo::PendingRemote<network::mojom::URLLoaderFactory> |
| appcache_proxied_remote; |
| auto appcache_proxied_receiver = |
| appcache_proxied_remote.InitWithNewPipeAndPassReceiver(); |
| bool use_proxy = |
| GetContentClient()->browser()->WillCreateURLLoaderFactory( |
| browser_context, this, GetProcess()->GetID(), |
| ContentBrowserClient::URLLoaderFactoryType::kDocumentSubResource, |
| main_world_origin_for_url_loader_factory, |
| base::nullopt /* navigation_id */, next_page_ukm_source_id, |
| &appcache_proxied_receiver, nullptr /* header_client */, |
| nullptr /* bypass_redirect_checks */, |
| nullptr /* disable_secure_dns */, nullptr /* factory_override */); |
| if (use_proxy) { |
| appcache_remote->Clone(std::move(appcache_proxied_receiver)); |
| appcache_remote.reset(); |
| appcache_remote.Bind(std::move(appcache_proxied_remote)); |
| } |
| |
| subresource_loader_factories->pending_appcache_factory() = |
| appcache_remote.Unbind(); |
| } |
| |
| ContentBrowserClient::NonNetworkURLLoaderFactoryMap non_network_factories; |
| |
| // Set up the default factory. |
| mojo::PendingRemote<network::mojom::URLLoaderFactory> |
| pending_default_factory; |
| |
| // See if this is for WebUI. |
| std::string scheme = common_params->url.scheme(); |
| const auto& webui_schemes = URLDataManagerBackend::GetWebUISchemes(); |
| if (base::Contains(webui_schemes, 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, |
| main_world_origin_for_url_loader_factory, |
| base::nullopt /* navigation_id */, next_page_ukm_source_id, |
| &factory_receiver, nullptr /* header_client */, |
| nullptr /* bypass_redirect_checks */, |
| nullptr /* disable_secure_dns */, nullptr /* factory_override */); |
| mojo::Remote<network::mojom::URLLoaderFactory> direct_factory_for_webui( |
| CreateWebUIURLLoaderFactory(this, 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( |
| url::Origin::Create(common_params->url.GetOrigin()))) { |
| 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(scheme, std::move(factory_for_webui)); |
| } |
| } |
| |
| 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( |
| navigation_request, "RFHI::CommitNavigation"), |
| next_page_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 file resources and about:blank with an initiator that can load files |
| // can load file subresources. |
| // |
| // Other URLs like about:srcdoc might be able load files, but only because |
| // they will inherit loaders from their parents instead of the ones |
| // provided by the browser process here. |
| // TODO(crbug.com/949510): Make about:srcdoc also use this path instead of |
| // inheriting loaders from the parent. |
| // |
| // For loading Web Bundle files, we don't set FileURLLoaderFactory. |
| // Because loading local files from a Web Bundle file is prohibited. |
| // |
| // TODO(crbug.com/888079): In the future, use |
| // GetOriginForURLLoaderFactory/GetOriginToCommit. |
| if ((common_params->url.SchemeIsFile() || |
| (common_params->url.IsAboutBlank() && |
| common_params->initiator_origin && |
| common_params->initiator_origin->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 defined(OS_ANDROID) |
| if (common_params->url.SchemeIs(url::kContentScheme)) { |
| // Only content:// URLs can load content:// subresources |
| non_network_factories.emplace(url::kContentScheme, |
| ContentURLLoaderFactory::Create()); |
| } |
| #endif |
| |
| auto* partition = |
| static_cast<StoragePartitionImpl*>(BrowserContext::GetStoragePartition( |
| browser_context, GetSiteInstance())); |
| non_network_factories.emplace( |
| url::kFileSystemScheme, CreateFileSystemURLLoaderFactory( |
| GetProcess()->GetID(), GetFrameTreeNodeId(), |
| partition->GetFileSystemContext(), |
| partition->GetPartitionDomain())); |
| |
| non_network_factories.emplace(url::kDataScheme, |
| DataURLLoaderFactory::Create()); |
| |
| GetContentClient() |
| ->browser() |
| ->RegisterNonNetworkSubresourceURLLoaderFactories( |
| GetProcess()->GetID(), routing_id_, &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(main_world_origin_for_url_loader_factory, |
| &factory_receiver, next_page_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( |
| navigation_request, |
| 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(frame_tree_node()->current_frame_host(), this); |
| DCHECK(same_document_navigation_request_); |
| bool should_replace_current_entry = |
| common_params->should_replace_current_entry; |
| GetNavigationControl()->CommitSameDocumentNavigation( |
| std::move(common_params), std::move(commit_params), |
| base::BindOnce(&RenderFrameHostImpl::OnSameDocumentCommitProcessed, |
| base::Unretained(this), |
| same_document_navigation_request_->GetNavigationId(), |
| 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 = static_cast<StoragePartitionImpl*>( |
| BrowserContext::GetStoragePartition( |
| GetSiteInstance()->GetBrowserContext(), GetSiteInstance())); |
| storage_partition->GetPrefetchURLLoaderService()->GetFactory( |
| prefetch_loader_factory.InitWithNewPipeAndPassReceiver(), |
| 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() && frame_tree_node_->current_frame_host() == this) { |
| if (NavigationEntryImpl* last_committed_entry = |
| NavigationEntryImpl::FromNavigationEntry( |
| frame_tree()->controller()->GetLastCommittedEntry())) { |
| if (last_committed_entry->back_forward_cache_metrics()) { |
| last_committed_entry->back_forward_cache_metrics() |
| ->RecordFeatureUsage(this); |
| } |
| } |
| } |
| |
| // about:srcdoc "inherits" loaders from its parent in the renderer process, |
| // There are no need to provide new ones here. |
| // TODO(arthursonzogni): What about about:blank URLs? |
| // TODO(arthursonzogni): What about data-URLs? |
| // |
| // Note: Inheriting loaders could be done in the browser process, but we |
| // aren't confident there are enough reliable information in the |
| // browser process to always make the correct decision. "Inheriting" |
| // in the renderer process is slightly less problematic in that it |
| // guarantees the renderer won't have higher privileges than it |
| // originally had (since it will inherit loader factories it already |
| // had access to). |
| if (is_srcdoc) { |
| DCHECK(!subresource_loader_factories); |
| DCHECK(!subresource_overrides); |
| DCHECK(!prefetch_loader_factory); |
| } |
| |
| // If a network request was made, update the Previews state. |
| if (IsURLHandledByNetworkStack(common_params->url)) |
| last_navigation_previews_state_ = common_params->previews_state; |
| |
| DCHECK(navigation_request->policy_container_host()); |
| blink::mojom::PolicyContainerPtr policy_container = |
| navigation_request->policy_container_host() |
| ->CreatePolicyContainerForBlink(); |
| |
| 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), |
| std::move(policy_container), devtools_navigation_token); |
| |
| // |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()) { |
| RunOrPostTaskOnThread( |
| FROM_HERE, ServiceWorkerContext::GetCoreThreadId(), |
| base::BindOnce( |
| &ServiceWorkerObjectHost::AddRemoteObjectPtrAndUpdateState, |
| subresource_loader_params->controller_service_worker_object_host, |
| std::move(remote_object), sent_state)); |
| } |
| } |
| |
| is_loading_ = true; |
| } |
| |
| void RenderFrameHostImpl::FailedNavigation( |
| NavigationRequest* navigation_request, |
| const mojom::CommonNavigationParams& common_params, |
| const mojom::CommitNavigationParams& commit_params, |
| bool has_stale_copy_in_cache, |
| int error_code, |
| const base::Optional<std::string>& error_page_content) { |
| TRACE_EVENT2("navigation", "RenderFrameHostImpl::FailedNavigation", |
| "frame_tree_node", frame_tree_node_->frame_tree_node_id(), |
| "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(common_params, commit_params); |
| |
| // 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(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(); |
| |
| SendCommitFailedNavigation( |
| navigation_client, navigation_request, common_params.Clone(), |
| commit_params.Clone(), has_stale_copy_in_cache, error_code, |
| error_page_content, std::move(subresource_loader_factories)); |
| |
| // TODO(crbug/1129537): support UKM source creation for failed navigations |
| // too. |
| |
| // An error page is expected to commit, hence why is_loading_ is set to true. |
| is_loading_ = true; |
| dom_content_loaded_ = false; |
| has_committed_any_navigation_ = true; |
| DCHECK(navigation_request && navigation_request->IsNavigationStarted() && |
| navigation_request->GetNetErrorCode() != net::OK); |
| } |
| |
| void RenderFrameHostImpl::HandleRendererDebugURL(const GURL& url) { |
| DCHECK(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(clamy): Remove the test dependency on this behavior. |
| if (!url.SchemeIs(url::kJavaScriptScheme)) { |
| bool was_loading = frame_tree()->IsLoading(); |
| is_loading_ = true; |
| frame_tree_node()->DidStartLoading(true, was_loading); |
| } |
| |
| GetNavigationControl()->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::SetUpMojoIfNeeded() { |
| if (associated_registry_.get()) |
| return; |
| |
| 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( |
| base::BindRepeating(bind_frame_host_receiver, base::Unretained(this))); |
| |
| associated_registry_->AddInterface(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(base::BindRepeating( |
| [](RenderFrameHostImpl* self, |
| mojo::PendingAssociatedReceiver<blink::mojom::PortalHost> receiver) { |
| Portal::BindPortalHostReceiver(self, std::move(receiver)); |
| }, |
| base::Unretained(this))); |
| |
| associated_registry_->AddInterface(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 (frame_tree_node_->IsMainFrame()) { |
| associated_registry_->AddInterface(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(base::BindRepeating( |
| [](RenderFrameHostImpl* impl, |
| mojo::PendingAssociatedReceiver< |
| blink::mojom::ManifestUrlChangeObserver> receiver) { |
| ManifestManagerHost::GetOrCreateForCurrentDocument(impl) |
| ->BindObserver(std::move(receiver)); |
| }, |
| base::Unretained(this))); |
| } |
| |
| associated_registry_->AddInterface(base::BindRepeating( |
| [](RenderFrameHostImpl* impl, |
| mojo::PendingAssociatedReceiver<mojom::RenderAccessibilityHost> |
| receiver) { |
| impl->render_accessibility_host_receiver_.Bind(std::move(receiver)); |
| impl->render_accessibility_host_receiver_.SetFilter( |
| impl->CreateMessageFilterForAssociatedReceiver( |
| mojom::RenderAccessibilityHost::Name_)); |
| }, |
| base::Unretained(this))); |
| |
| // TODO(crbug.com/1047354): How to avoid binding if the |
| // BINDINGS_POLICY_DOM_AUTOMATION policy is not set? |
| associated_registry_->AddInterface(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()))); |
| |
| mojo::PendingRemote<mojom::FrameFactory> frame_factory; |
| GetProcess()->BindReceiver(frame_factory.InitWithNewPipeAndPassReceiver()); |
| mojo::Remote<mojom::FrameFactory>(std::move(frame_factory)) |
| ->CreateFrame(routing_id_, frame_.BindNewPipeAndPassReceiver()); |
| |
| // TODO(http://crbug.com/1014212): Change to DCHECK. |
| CHECK(frame_); |
| |
| mojo::PendingRemote<service_manager::mojom::InterfaceProvider> |
| remote_interfaces; |
| frame_->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::InvalidateMojoConnection() { |
| frame_.reset(); |
| frame_bindings_control_.reset(); |
| frame_host_associated_receiver_.reset(); |
| local_frame_.reset(); |
| local_main_frame_.reset(); |
| high_priority_local_frame_.reset(); |
| navigation_control_.reset(); |
| find_in_page_.reset(); |
| render_accessibility_.reset(); |
| |
| // Disconnect with ImageDownloader Mojo service in Blink. |
| mojo_image_downloader_.reset(); |
| |
| // The geolocation service and sensor provider proxy may attempt to cancel |
| // permission requests so they must be reset before the routing_id mapping is |
| // removed. |
| geolocation_service_.reset(); |
| sensor_provider_proxy_.reset(); |
| |
| render_accessibility_host_receiver_.reset(); |
| local_frame_host_receiver_.reset(); |
| local_main_frame_host_receiver_.reset(); |
| associated_registry_.reset(); |
| } |
| |
| bool RenderFrameHostImpl::IsFocused() { |
| if (!GetRenderWidgetHost()->is_focused() || !frame_tree_->GetFocusedFrame()) |
| return false; |
| |
| RenderFrameHostImpl* focused_rfh = |
| frame_tree_->GetFocusedFrame()->current_frame_host(); |
| return focused_rfh == this || focused_rfh->IsDescendantOf(this); |
| } |
| |
| bool RenderFrameHostImpl::CreateWebUI(const GURL& dest_url, |
| int entry_bindings) { |
| // Verify expectation that WebUI should not be created for error pages. |
| DCHECK_NE(GetSiteInstance()->GetSiteInfo(), SiteInfo::CreateForErrorPage()); |
| |
| 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")); |
| ClearWebUI(); |
| 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(0, GetEnabledBindings()); |
| AllowBindings(web_ui_->GetBindings()); |
| |
| return true; |
| } |
| |
| void RenderFrameHostImpl::ClearWebUI() { |
| web_ui_type_ = WebUI::kNoWebUI; |
| web_ui_.reset(); |
| } |
| |
| const mojo::Remote<blink::mojom::ImageDownloader>& |
| RenderFrameHostImpl::GetMojoImageDownloader() { |
| 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(frame_tree_node_->IsMainFrame()); |
| 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::ResumeBlockedRequestsForFrame() { |
| ForEachFrame(this, |
| base::BindRepeating([](RenderFrameHostImpl* render_frame_host) { |
| if (render_frame_host->frame_) |
| render_frame_host->frame_->ResumeBlockedRequests(); |
| })); |
| } |
| |
| void RenderFrameHostImpl::BindDevToolsAgent( |
| mojo::PendingAssociatedRemote<blink::mojom::DevToolsAgentHost> host, |
| mojo::PendingAssociatedReceiver<blink::mojom::DevToolsAgent> receiver) { |
| GetNavigationControl()->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 (!IsRenderFrameCreated()) |
| return; |
| |
| ui::AXMode ax_mode = delegate_->GetAccessibilityMode(); |
| if (!ax_mode.has_mode(ui::AXMode::kWebContents)) { |
| // Resetting the Remote signals the renderer to shutdown accessibility |
| // in the renderer. |
| render_accessibility_.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, |
| ui::AXMode ax_mode) { |
| // TODO(https://crbug.com/859110): Remove once frame_ can no longer be null. |
| if (!IsRenderFrameLive()) |
| return; |
| |
| frame_->SnapshotAccessibilityTree( |
| ax_mode.mode(), |
| base::BindOnce(&RenderFrameHostImpl::RequestAXTreeSnapshotCallback, |
| 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() || IsInactiveAndDisallowReactivation()) { |
| 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); |
| } |
| |
| 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::IsRenderFrameCreated() { |
| return render_frame_created_; |
| } |
| |
| bool RenderFrameHostImpl::IsRenderFrameLive() { |
| bool is_live = |
| GetProcess()->IsInitializedAndNotDead() && 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; |
| } |
| |
| bool RenderFrameHostImpl::IsCurrent() { |
| RenderFrameHostImpl* rfh = this; |
| // Check this RenderFrameHost and all its ancestors to see if they are the |
| // current ones in their respective FrameTreeNodes. |
| // It is important to check for all ancestors as when navigation commits a new |
| // RenderFrameHost may replace one of the parents, swapping out the old with |
| // its entire subtree but |this| will still be a current one in its |
| // FrameTreeNode. |
| while (rfh) { |
| if (rfh->frame_tree_node()->current_frame_host() != rfh) |
| return false; |
| rfh = rfh->GetParent(); |
| } |
| return true; |
| } |
| |
| size_t RenderFrameHostImpl::GetProxyCount() { |
| if (!IsCurrent()) |
| return 0; |
| return frame_tree_node_->render_manager()->GetProxyCount(); |
| } |
| |
| bool RenderFrameHostImpl::HasSelection() { |
| return has_selection_; |
| } |
| |
| RenderFrameHostImpl* RenderFrameHostImpl::GetMainFrame() { |
| // 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. |
| RenderFrameHostImpl* main_frame = this; |
| while (main_frame->GetParent()) |
| main_frame = main_frame->GetParent(); |
| return main_frame; |
| } |
| |
| 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::GrantFileAccessFromResourceRequestBody( |
| const network::ResourceRequestBody& body) { |
| GrantFileAccess(GetProcess()->GetID(), body.GetReferencedFiles()); |
| } |
| |
| void RenderFrameHostImpl::UpdatePermissionsForNavigation( |
| const mojom::CommonNavigationParams& common_params, |
| const mojom::CommitNavigationParams& commit_params) { |
| // 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(), common_params.url); |
| if (NavigationRequest::IsLoadDataWithBaseURL(common_params)) { |
| // 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(), 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). |
| if (commit_params.page_state.IsValid()) |
| GrantFileAccessFromPageState(commit_params.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 (common_params.post_data) |
| GrantFileAccessFromResourceRequestBody(*common_params.post_data); |
| } |
| |
| bool RenderFrameHostImpl::WindowPlacementAllowsFullscreen() { |
| if (!delegate_->IsTransientAllowFullscreenActive()) |
| return false; |
| auto* controller = |
| PermissionControllerImpl::FromBrowserContext(GetBrowserContext()); |
| return controller && |
| controller->GetPermissionStatusForFrame( |
| PermissionType::WINDOW_PLACEMENT, this, GetLastCommittedURL()) == |
| blink::mojom::PermissionStatus::GRANTED; |
| } |
| |
| 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; |
| } |
| |
| void RenderFrameHostImpl:: |
| ExtractFactoryParamsFromNavigationRequestOrLastCommittedNavigation( |
| NavigationRequest* navigation_request, |
| url::Origin* out_main_world_origin, |
| net::IsolationInfo* out_isolation_info, |
| network::mojom::ClientSecurityStatePtr* out_client_security_state, |
| mojo::PendingRemote<network::mojom::CrossOriginEmbedderPolicyReporter>* |
| coep_reporter_pending_remote, |
| network::mojom::TrustTokenRedemptionPolicy* |
| out_trust_token_redemption_policy) { |
| // |navigation_request| is optional. |
| DCHECK(out_main_world_origin); |
| DCHECK(out_client_security_state); |
| // |coep_reporter_receiver| is optional. |
| DCHECK(out_trust_token_redemption_policy); |
| |
| bool have_successful_request = |
| navigation_request && navigation_request->GetNetErrorCode() == net::OK; |
| bool have_failed_request = |
| navigation_request && navigation_request->GetNetErrorCode() != net::OK; |
| CrossOriginEmbedderPolicyReporter* coep_reporter = nullptr; |
| |
| if (have_successful_request) { |
| // Return values based on the |navigation_request|. |
| *out_main_world_origin = navigation_request->GetOriginForURLLoaderFactory(); |
| *out_isolation_info = navigation_request->isolation_info_for_subresources(); |
| *out_client_security_state = navigation_request->BuildClientSecurityState(); |
| coep_reporter = navigation_request->coep_reporter(); |
| *out_trust_token_redemption_policy = |
| DetermineWhetherToForbidTrustTokenRedemption( |
| GetParent(), navigation_request->commit_params(), |
| *out_main_world_origin); |
| } else if (have_failed_request) { |
| // Some return values should always be the same for error pages. Some |
| // return values may be based on the |navigation_request|. |
| |
| // Error page will commit in an opaque origin. |
| // TODO(lukasza): https://crbug.com/888079: Use this origin when sending the |
| // commit IPC and setting |last_committed_origin_|. |
| url::Origin error_page_origin = url::Origin(); |
| *out_main_world_origin = error_page_origin; |
| *out_isolation_info = net::IsolationInfo::CreateTransient(); |
| *out_client_security_state = navigation_request->BuildClientSecurityState(); |
| coep_reporter = nullptr; |
| *out_trust_token_redemption_policy = |
| network::mojom::TrustTokenRedemptionPolicy::kForbid; |
| } else { |
| // Use properties of the last committed navigation. |
| *out_main_world_origin = last_committed_origin_; |
| *out_isolation_info = isolation_info_; |
| *out_client_security_state = BuildClientSecurityState(); |
| coep_reporter = coep_reporter_.get(); |
| *out_trust_token_redemption_policy = |
| DetermineAfterCommitWhetherToForbidTrustTokenRedemption(this); |
| } |
| |
| if (coep_reporter && coep_reporter_pending_remote) { |
| coep_reporter->Clone( |
| coep_reporter_pending_remote->InitWithNewPipeAndPassReceiver()); |
| } |
| } |
| |
| network::mojom::URLLoaderFactoryParamsPtr |
| RenderFrameHostImpl::CreateURLLoaderFactoryParamsForMainWorld( |
| NavigationRequest* navigation_request, |
| base::StringPiece debug_tag) { |
| url::Origin main_world_origin; |
| net::IsolationInfo isolation_info; |
| network::mojom::ClientSecurityStatePtr client_security_state; |
| mojo::PendingRemote<network::mojom::CrossOriginEmbedderPolicyReporter> |
| coep_reporter_remote; |
| network::mojom::TrustTokenRedemptionPolicy trust_token_redemption_policy = |
| network::mojom::TrustTokenRedemptionPolicy::kForbid; |
| ExtractFactoryParamsFromNavigationRequestOrLastCommittedNavigation( |
| navigation_request, &main_world_origin, &isolation_info, |
| &client_security_state, &coep_reporter_remote, |
| &trust_token_redemption_policy); |
| |
| return URLLoaderFactoryParamsHelper::CreateForFrame( |
| this, main_world_origin, isolation_info, std::move(client_security_state), |
| std::move(coep_reporter_remote), GetProcess(), |
| 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 = BrowserContext::GetStoragePartition( |
| GetSiteInstance()->GetBrowserContext(), GetSiteInstance()); |
| 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, |
| ¶ms->header_client, &bypass_redirect_checks, |
| ¶ms->disable_secure_dns, ¶ms->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, base::nullopt /* navigation_id */, 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, false /* is_navigation */, false /* is_download */, |
| factory_receiver, factory_override); |
| } |
| |
| bool RenderFrameHostImpl::CanExecuteJavaScript() { |
| if (g_allow_injecting_javascript) |
| return true; |
| return !frame_tree_node_->current_url().is_valid() || |
| frame_tree_node_->current_url().SchemeIs(kChromeDevToolsScheme) || |
| ChildProcessSecurityPolicyImpl::GetInstance()->HasWebUIBindings( |
| GetProcess()->GetID()) || |
| // It's possible to load about:blank in a Web UI renderer. |
| // See http://crbug.com/42547 |
| (frame_tree_node_->current_url().spec() == url::kAboutBlankURL) || |
| // InterstitialPageImpl should be the only case matching this. |
| (delegate_->GetAsWebContents() == nullptr); |
| } |
| |
| // 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 |
| RenderFrameHost* RenderFrameHost::FromPlaceholderToken( |
| int render_process_id, |
| const base::UnguessableToken& 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() { |
| if (browser_plugin_embedder_ax_tree_id_ != ui::AXTreeIDUnknown()) |
| return browser_plugin_embedder_ax_tree_id_; |
| |
| if (!frame_tree_node_->IsMainFrame()) |
| return GetParent()->GetAXTreeID(); |
| |
| return ui::AXTreeIDUnknown(); |
| } |
| |
| ui::AXTreeID RenderFrameHostImpl::GetFocusedAXTreeID() { |
| // If this is not the root frame tree node, we're done. |
| if (!is_main_frame()) |
| return ui::AXTreeIDUnknown(); |
| |
| auto* focused_frame = static_cast<RenderFrameHostImpl*>( |
| delegate_->GetFocusedFrameIncludingInnerWebContents()); |
| if (focused_frame) |
| return focused_frame->GetAXTreeID(); |
| |
| 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, |
| 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.proxy |
| ? frame_or_proxy.proxy->frame_tree_node()->current_frame_host() |
| : frame_or_proxy.frame; |
| |
| if (!hit_frame || hit_frame->IsInactiveAndDisallowReactivation()) { |
| 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) { |
| ui::AXTreeUpdate dst_snapshot; |
| dst_snapshot.root_id = snapshot.root_id; |
| dst_snapshot.nodes.resize(snapshot.nodes.size()); |
| for (size_t i = 0; i < snapshot.nodes.size(); ++i) |
| dst_snapshot.nodes[i] = snapshot.nodes[i]; |
| |
| if (snapshot.has_tree_data) { |
| ax_tree_data_ = snapshot.tree_data; |
| dst_snapshot.tree_data = GetAXTreeData(); |
| dst_snapshot.has_tree_data = true; |
| } |
| std::move(callback).Run(dst_snapshot); |
| } |
| |
| void RenderFrameHostImpl::CreatePaymentManager( |
| mojo::PendingReceiver<payments::mojom::PaymentManager> receiver) { |
| if (!IsFeatureEnabled(blink::mojom::FeaturePolicyFeature::kPayment)) { |
| mojo::ReportBadMessage("Feature 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. |
| OnSchedulerTrackedFeatureUsed( |
| blink::scheduler::WebSchedulerTrackedFeature::kPaymentManager); |
| } |
| |
| void RenderFrameHostImpl::CreateWebBluetoothService( |
| mojo::PendingReceiver<blink::mojom::WebBluetoothService> receiver) { |
| BackForwardCache::DisableForRenderFrameHost(this, "WebBluetooth"); |
| // RFHI owns |web_bluetooth_services_| and |web_bluetooth_service| owns the |
| // |receiver_| which may run the error handler. |receiver_| can't run the |
| // error handler after it's destroyed so it can't run after the RFHI is |
| // destroyed. |
| auto web_bluetooth_service = |
| std::make_unique<WebBluetoothServiceImpl>(this, std::move(receiver)); |
| web_bluetooth_service->SetClientConnectionErrorHandler( |
| base::BindOnce(&RenderFrameHostImpl::DeleteWebBluetoothService, |
| base::Unretained(this), web_bluetooth_service.get())); |
| web_bluetooth_services_.push_back(std::move(web_bluetooth_service)); |
| } |
| |
| WebBluetoothServiceImpl* |
| RenderFrameHostImpl::GetWebBluetoothServiceForTesting() { |
| DCHECK(web_bluetooth_services_.back()); |
| return web_bluetooth_services_.back().get(); |
| } |
| |
| void RenderFrameHostImpl::DeleteWebBluetoothService( |
| WebBluetoothServiceImpl* web_bluetooth_service) { |
| auto it = std::find_if( |
| web_bluetooth_services_.begin(), web_bluetooth_services_.end(), |
| [web_bluetooth_service]( |
| const std::unique_ptr<WebBluetoothServiceImpl>& service) { |
| return web_bluetooth_service == service.get(); |
| }); |
| DCHECK(it != web_bluetooth_services_.end()); |
| web_bluetooth_services_.erase(it); |
| } |
| |
| void RenderFrameHostImpl::CreateWebUsbService( |
| mojo::PendingReceiver<blink::mojom::WebUsbService> receiver) { |
| BackForwardCache::DisableForRenderFrameHost(this, "WebUSB"); |
| GetContentClient()->browser()->CreateWebUsbService(this, std::move(receiver)); |
| } |
| |
| void RenderFrameHostImpl::ResetFeaturePolicy() { |
| RenderFrameHostImpl* parent_frame_host = GetParent(); |
| const blink::FeaturePolicy* parent_policy = |
| parent_frame_host ? parent_frame_host->feature_policy() : nullptr; |
| blink::ParsedFeaturePolicy container_policy = |
| frame_tree_node()->effective_frame_policy().container_policy; |
| feature_policy_ = blink::FeaturePolicy::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(); |
| audio_service_audio_output_stream_factory_.emplace( |
| this, audio_system, media_stream_manager, std::move(receiver)); |
| } |
| |
| 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, GlobalFrameRoutingId(GetProcess()->GetID(), routing_id_)); |
| } |
| feature_observer_->GetFeatureObserver(std::move(receiver)); |
| } |
| |
| void RenderFrameHostImpl::BindScreenEnumerationReceiver( |
| mojo::PendingReceiver<blink::mojom::ScreenEnumeration> receiver) { |
| if (!screen_enumeration_impl_) |
| screen_enumeration_impl_ = std::make_unique<ScreenEnumerationImpl>(this); |
| screen_enumeration_impl_->Bind(std::move(receiver)); |
| } |
| |
| void RenderFrameHostImpl::BindPrerenderProcessor( |
| RenderFrameHost* render_frame_host, |
| mojo::PendingReceiver<blink::mojom::PrerenderProcessor> pending_receiver) { |
| DCHECK(base::FeatureList::IsEnabled(blink::features::kPrerender2)); |
| DCHECK_EQ(render_frame_host, this); |
| prerender_processor_receivers_.Add( |
| std::make_unique<PrerenderProcessor>(*this), std::move(pending_receiver)); |
| } |
| |
| void RenderFrameHostImpl::BindMediaInterfaceFactoryReceiver( |
| mojo::PendingReceiver<media::mojom::InterfaceFactory> receiver) { |
| if (!media_interface_proxy_) { |
| media_interface_proxy_ = std::make_unique<MediaInterfaceProxy>(this); |
| } |
| media_interface_proxy_->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(); |
| } |
| |
| media::MediaMetricsProvider::Create( |
| GetProcess()->GetBrowserContext()->IsOffTheRecord() |
| ? media::MediaMetricsProvider::BrowsingMode::kIncognito |
| : media::MediaMetricsProvider::BrowsingMode::kNormal, |
| frame_tree_node_->IsMainFrame() |
| ? media::MediaMetricsProvider::FrameStatus::kTopFrame |
| : media::MediaMetricsProvider::FrameStatus::kNotTopFrame, |
| base::BindRepeating( |
| &RenderFrameHostDelegate:: |
| GetUkmSourceIdForLastCommittedSourceIncludingSameDocument, |
| // This callback is only executed when Create() is called, during |
| // which the lifetime of the |delegate_| is guaranteed. |
| base::Unretained(delegate_)), |
| base::BindRepeating( |
| [](RenderFrameHostImpl* frame) { |
| return ::media::learning::FeatureValue( |
| frame->GetLastCommittedOrigin().host()); |
| }, |
| // Same as above. |
| base::Unretained(this)), |
| 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(receiver)); |
| } |
| |
| void RenderFrameHostImpl::CreateMediaPlayerHost( |
| mojo::PendingReceiver<media::mojom::MediaPlayerHost> receiver) { |
| delegate_->CreateMediaPlayerHostForRenderFrameHost(this, 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::CreateQuicTransportConnector( |
| mojo::PendingReceiver<blink::mojom::QuicTransportConnector> receiver) { |
| mojo::MakeSelfOwnedReceiver( |
| std::make_unique<QuicTransportConnectorImpl>( |
| 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(GetLastCommittedOrigin(), |
| std::move(receiver)); |
| } |
| |
| void RenderFrameHostImpl::CreateInstalledAppProvider( |
| mojo::PendingReceiver<blink::mojom::InstalledAppProvider> receiver) { |
| InstalledAppProviderImpl::Create(this, std::move(receiver)); |
| } |
| |
| 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(); |
| |
| mojo::PendingRemote<network::mojom::CrossOriginEmbedderPolicyReporter> |
| coep_reporter; |
| auto coep_reporter_endpoint = coep_reporter.InitWithNewPipeAndPassReceiver(); |
| if (coep_reporter_) |
| coep_reporter_->Clone(std::move(coep_reporter_endpoint)); |
| |
| // 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=*/GetGlobalFrameRoutingId(), |
| /*ancestor_render_frame_host_id=*/GetGlobalFrameRoutingId(), |
| last_committed_origin_, isolation_info_, |
| cross_origin_embedder_policy_, std::move(coep_reporter)), |
| std::move(receiver)); |
| } |
| |
| #if defined(OS_ANDROID) |
| void RenderFrameHostImpl::BindNFCReceiver( |
| mojo::PendingReceiver<device::mojom::NFC> receiver) { |
| delegate_->GetNFC(this, std::move(receiver)); |
| } |
| #endif |
| |
| #if !defined(OS_ANDROID) |
| void RenderFrameHostImpl::BindSerialService( |
| mojo::PendingReceiver<blink::mojom::SerialService> receiver) { |
| if (!IsFeatureEnabled(blink::mojom::FeaturePolicyFeature::kSerial)) { |
| mojo::ReportBadMessage("Feature policy blocks access to Serial."); |
| return; |
| } |
| |
| SerialService::GetOrCreateForCurrentDocument(this)->Bind(std::move(receiver)); |
| } |
| |
| void RenderFrameHostImpl::BindAuthenticatorReceiver( |
| mojo::PendingReceiver<blink::mojom::Authenticator> receiver) { |
| if (!authenticator_impl_) |
| authenticator_impl_ = std::make_unique<AuthenticatorImpl>(this); |
| |
| authenticator_impl_->Bind(std::move(receiver)); |
| } |
| |
| void RenderFrameHostImpl::GetHidService( |
| mojo::PendingReceiver<blink::mojom::HidService> receiver) { |
| HidService::Create(this, std::move(receiver)); |
| } |
| #endif |
| |
| IdleManager* RenderFrameHostImpl::GetIdleManager() { |
| return idle_manager_.get(); |
| } |
| |
| void RenderFrameHostImpl::BindIdleManager( |
| mojo::PendingReceiver<blink::mojom::IdleManager> receiver) { |
| if (!IsFeatureEnabled(blink::mojom::FeaturePolicyFeature::kIdleDetection)) { |
| mojo::ReportBadMessage("Feature policy blocks access to IdleDetection."); |
| return; |
| } |
| |
| idle_manager_->CreateService(std::move(receiver), |
| GetMainFrame()->GetLastCommittedOrigin()); |
| OnSchedulerTrackedFeatureUsed( |
| blink::scheduler::WebSchedulerTrackedFeature::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(), delegate_->GetAsWebContents()); |
| } |
| 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. |
| OnSchedulerTrackedFeatureUsed( |
| blink::scheduler::WebSchedulerTrackedFeature::kSpeechSynthesis); |
| } |
| |
| void RenderFrameHostImpl::GetSensorProvider( |
| mojo::PendingReceiver<device::mojom::SensorProvider> receiver) { |
| if (!sensor_provider_proxy_) { |
| sensor_provider_proxy_ = std::make_unique<SensorProviderProxyImpl>( |
| PermissionControllerImpl::FromBrowserContext( |
| GetProcess()->GetBrowserContext()), |
| this); |
| } |
| sensor_provider_proxy_->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), |
| GetLastCommittedOrigin(), 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) { |
| DCHECK(base::FeatureList::IsEnabled(features::kWebID)); |
| FederatedAuthRequestImpl::Create(this, std::move(receiver)); |
| } |
| |
| void RenderFrameHostImpl::BindRestrictedCookieManager( |
| mojo::PendingReceiver<network::mojom::RestrictedCookieManager> receiver) { |
| static_cast<StoragePartitionImpl*>(GetProcess()->GetStoragePartition()) |
| ->CreateRestrictedCookieManager( |
| network::mojom::RestrictedCookieManagerRole::SCRIPT, |
| GetLastCommittedOrigin(), isolation_info_.site_for_cookies(), |
| ComputeTopFrameOrigin(GetLastCommittedOrigin()), |
| /* is_service_worker = */ false, GetProcess()->GetID(), routing_id(), |
| std::move(receiver), CreateCookieAccessObserver()); |
| } |
| |
| void RenderFrameHostImpl::BindHasTrustTokensAnswerer( |
| mojo::PendingReceiver<network::mojom::HasTrustTokensAnswerer> 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 HasTrustTokensAnswerer 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 HasTrustTokensAnswerer 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()->CreateHasTrustTokensAnswerer( |
| std::move(receiver), ComputeTopFrameOrigin(GetLastCommittedOrigin())); |
| } |
| |
| void RenderFrameHostImpl::CreateAppCacheBackend( |
| mojo::PendingReceiver<blink::mojom::AppCacheBackend> receiver) { |
| DCHECK_CURRENTLY_ON(BrowserThread::UI); |
| DCHECK(StoragePartition::IsAppCacheEnabled()); |
| static auto* crash_key = base::debug::AllocateCrashKeyString( |
| "CreateAppCacheBackend-data", base::debug::CrashKeySize::Size64); |
| std::string data = base::StringPrintf( |
| "f=%d br=%d irfl=%d iiand=%d fid=%d", frame_.is_bound(), |
| broker_receiver_.is_bound(), IsRenderFrameLive(), |
| GetProcess()->IsInitializedAndNotDead(), |
| RenderProcessHost::FromID(GetProcess()->GetID()) != nullptr); |
| base::debug::ScopedCrashKeyString scoped_crash_key(crash_key, data); |
| |
| auto* storage_partition_impl = |
| static_cast<StoragePartitionImpl*>(GetProcess()->GetStoragePartition()); |
| auto* appcache_service = storage_partition_impl->GetAppCacheService(); |
| // CreateAppCacheBackend should only be called if AppCache is enabled |
| // (which implies the service exists). |
| DCHECK(appcache_service); |
| appcache_service->CreateBackend(GetProcess()->GetID(), routing_id_, |
| std::move(receiver)); |
| } |
| |
| void RenderFrameHostImpl::GetAudioContextManager( |
| mojo::PendingReceiver<blink::mojom::AudioContextManager> receiver) { |
| AudioContextManagerImpl::Create(this, std::move(receiver)); |
| } |
| |
| void RenderFrameHostImpl::GetContactsManager( |
| mojo::PendingReceiver<blink::mojom::ContactsManager> receiver) { |
| ContactsManagerImpl::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()), |
| 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::GetDeviceInfoService( |
| mojo::PendingReceiver<blink::mojom::DeviceAPIService> receiver) { |
| GetContentClient()->browser()->CreateDeviceInfoService(this, |
| std::move(receiver)); |
| } |
| |
| void RenderFrameHostImpl::GetFontAccessManager( |
| mojo::PendingReceiver<blink::mojom::FontAccessManager> receiver) { |
| static_cast<StoragePartitionImpl*>(GetProcess()->GetStoragePartition()) |
| ->GetFontAccessManager() |
| ->BindReceiver(FontAccessManagerImpl::BindingContext( |
| GetLastCommittedOrigin(), GetGlobalFrameRoutingId()), |
| std::move(receiver)); |
| } |
| |
| void RenderFrameHostImpl::GetNativeFileSystemManager( |
| mojo::PendingReceiver<blink::mojom::NativeFileSystemManager> receiver) { |
| DCHECK_CURRENTLY_ON(BrowserThread::UI); |
| auto* storage_partition = |
| static_cast<StoragePartitionImpl*>(GetProcess()->GetStoragePartition()); |
| auto* manager = storage_partition->GetNativeFileSystemManager(); |
| manager->BindReceiver(NativeFileSystemManagerImpl::BindingContext( |
| GetLastCommittedOrigin(), GetLastCommittedURL(), |
| GetGlobalFrameRoutingId()), |
| std::move(receiver)); |
| } |
| |
| void RenderFrameHostImpl::CreateLockManager( |
| mojo::PendingReceiver<blink::mojom::LockManager> receiver) { |
| GetProcess()->CreateLockManager(GetRoutingID(), GetLastCommittedOrigin(), |
| std::move(receiver)); |
| } |
| |
| void RenderFrameHostImpl::CreateIDBFactory( |
| mojo::PendingReceiver<blink::mojom::IDBFactory> receiver) { |
| GetProcess()->BindIndexedDB(GetLastCommittedOrigin(), std::move(receiver)); |
| } |
| |
| void RenderFrameHostImpl::CreatePermissionService( |
| mojo::PendingReceiver<blink::mojom::PermissionService> receiver) { |
| if (!permission_service_context_) |
| permission_service_context_ = |
| std::make_unique<PermissionServiceContext>(this); |
| |
| permission_service_context_->CreateService(std::move(receiver)); |
| } |
| |
| void RenderFrameHostImpl::GetAuthenticator( |
| mojo::PendingReceiver<blink::mojom::Authenticator> receiver) { |
| #if !defined(OS_ANDROID) |
| if (base::FeatureList::IsEnabled(features::kWebAuth)) { |
| BindAuthenticatorReceiver(std::move(receiver)); |
| } |
| #else |
| GetJavaInterfaces()->GetInterface(std::move(receiver)); |
| #endif // !defined(OS_ANDROID) |
| } |
| |
| void RenderFrameHostImpl::GetPushMessaging( |
| mojo::PendingReceiver<blink::mojom::PushMessaging> receiver) { |
| if (!push_messaging_manager_) { |
| push_messaging_manager_.reset(new PushMessagingManager( |
| GetProcess()->GetID(), routing_id_, |
| static_cast<StoragePartitionImpl*>(GetProcess()->GetStoragePartition()) |
| ->GetServiceWorkerContext())); |
| } |
| |
| RunOrPostTaskOnThread( |
| FROM_HERE, ServiceWorkerContext::GetCoreThreadId(), |
| base::BindOnce(&PushMessagingManager::AddPushMessagingReceiver, |
| push_messaging_manager_->AsWeakPtr(), |
| std::move(receiver))); |
| } |
| |
| void RenderFrameHostImpl::GetVirtualAuthenticatorManager( |
| mojo::PendingReceiver<blink::test::mojom::VirtualAuthenticatorManager> |
| receiver) { |
| #if !defined(OS_ANDROID) |
| if (base::FeatureList::IsEnabled(features::kWebAuth) && |
| base::CommandLine::ForCurrentProcess()->HasSwitch( |
| switches::kEnableWebAuthDeprecatedMojoTestingApi)) { |
| auto* environment_singleton = AuthenticatorEnvironmentImpl::GetInstance(); |
| environment_singleton->EnableVirtualAuthenticatorFor(frame_tree_node_); |
| environment_singleton->AddVirtualAuthenticatorReceiver(frame_tree_node_, |
| std::move(receiver)); |
| } |
| #endif // !defined(OS_ANDROID) |
| } |
| |
| bool IsInitialEmptyCommit(const GURL& url, bool has_committed_real_load) { |
| return url.SchemeIs(url::kAboutScheme) && url != GURL(url::kAboutSrcdocURL) && |
| !has_committed_real_load; |
| } |
| |
| std::unique_ptr<NavigationRequest> |
| RenderFrameHostImpl::CreateNavigationRequestForCommit( |
| const GURL& url, |
| const url::Origin& origin, |
| blink::mojom::ReferrerPtr referrer, |
| const ui::PageTransition& transition, |
| bool should_replace_current_entry, |
| const NavigationGesture& gesture, |
| const std::vector<GURL>& redirects, |
| const GURL& original_request_url, |
| const blink::PageState& page_state, |
| 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 renderer-initiated navigation to the initial empty |
| // document. |
| // 2) This was a renderer-initiated same-document navigation. |
| DCHECK( |
| IsInitialEmptyCommit(url, frame_tree_node_->has_committed_real_load()) || |
| is_same_document); |
| DCHECK(!is_same_document_history_api_navigation || is_same_document); |
| |
| net::IsolationInfo isolation_info = ComputeIsolationInfoInternal( |
| origin, net::IsolationInfo::RequestType::kOther); |
| |
| 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) { |
| coep_reporter = std::make_unique<CrossOriginEmbedderPolicyReporter>( |
| GetProcess()->GetStoragePartition(), url, |
| cross_origin_embedder_policy_.reporting_endpoint, |
| cross_origin_embedder_policy_.report_only_reporting_endpoint, |
| 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::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::CreateForCommit( |
| frame_tree_node_, this, is_same_document, url, origin, isolation_info, |
| std::move(referrer), transition, should_replace_current_entry, method, |
| gesture, is_overriding_user_agent, redirects, original_request_url, |
| page_state, std::move(coep_reporter), |
| std::move(web_bundle_navigation_info), http_status_code); |
| } |
| |
| void RenderFrameHostImpl::BeforeUnloadTimeout() { |
| if (render_view_host_->GetDelegate()->ShouldIgnoreUnresponsiveRenderer()) |
| return; |
| |
| SimulateBeforeUnloadCompleted(true /* proceed */); |
| } |
| |
| void RenderFrameHostImpl::SetLastCommittedSiteInfo(const GURL& url) { |
| // Since |url| has already committed, |origin_requests_isolation| below should |
| // be set to false. |
| SiteInfo site_info = |
| url.is_empty() |
| ? SiteInfo() |
| : SiteInstanceImpl::ComputeSiteInfo( |
| GetSiteInstance()->GetIsolationContext(), |
| UrlInfo(url, false /* origin_requests_isolation */), |
| GetSiteInstance()->GetCoopCoepCrossOriginIsolatedInfo()); |
| |
| if (last_committed_site_info_ == site_info) |
| return; |
| |
| if (!last_committed_site_info_.site_url().is_empty()) { |
| RenderProcessHostImpl::RemoveFrameWithSite( |
| frame_tree()->controller()->GetBrowserContext(), GetProcess(), |
| last_committed_site_info_); |
| } |
| |
| last_committed_site_info_ = site_info; |
| |
| if (!last_committed_site_info_.site_url().is_empty()) { |
| RenderProcessHostImpl::AddFrameWithSite( |
| frame_tree()->controller()->GetBrowserContext(), GetProcess(), |
| last_committed_site_info_); |
| } |
| } |
| |
| #if defined(OS_ANDROID) |
| |
| class RenderFrameHostImpl::JavaInterfaceProvider |
| : public service_manager::mojom::InterfaceProvider { |
| public: |
| using BindCallback = |
| base::RepeatingCallback<void(const std::string&, |
| mojo::ScopedMessagePipeHandle)>; |
| |
| JavaInterfaceProvider( |
| const BindCallback& bind_callback, |
| mojo::PendingReceiver<service_manager::mojom::InterfaceProvider> receiver) |
| : bind_callback_(bind_callback), receiver_(this, std::move(receiver)) {} |
| ~JavaInterfaceProvider() override = default; |
| |
| private: |
| // service_manager::mojom::InterfaceProvider: |
| void GetInterface(const std::string& interface_name, |
| mojo::ScopedMessagePipeHandle handle) override { |
| bind_callback_.Run(interface_name, std::move(handle)); |
| } |
| |
| const BindCallback bind_callback_; |
| mojo::Receiver<service_manager::mojom::InterfaceProvider> receiver_; |
| |
| DISALLOW_COPY_AND_ASSIGN(JavaInterfaceProvider); |
| }; |
| |
| base::android::ScopedJavaLocalRef<jobject> |
| RenderFrameHostImpl::GetJavaRenderFrameHost() { |
| RenderFrameHostAndroid* render_frame_host_android = |
| static_cast<RenderFrameHostAndroid*>( |
| GetUserData(kRenderFrameHostAndroidKey)); |
| if (!render_frame_host_android) { |
| mojo::PendingRemote<service_manager::mojom::InterfaceProvider> |
| interface_provider_remote; |
| java_interface_registry_ = std::make_unique<JavaInterfaceProvider>( |
| base::BindRepeating( |
| &RenderFrameHostImpl::ForwardGetInterfaceToRenderFrame, |
| weak_ptr_factory_.GetWeakPtr()), |
| interface_provider_remote.InitWithNewPipeAndPassReceiver()); |
| render_frame_host_android = |
| new RenderFrameHostAndroid(this, std::move(interface_provider_remote)); |
| 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_.reset(new service_manager::InterfaceProvider( |
| base::ThreadTaskRunnerHandle::Get())); |
| java_interfaces_->Bind(std::move(provider)); |
| } |
| return java_interfaces_.get(); |
| } |
| |
| void RenderFrameHostImpl::ForwardGetInterfaceToRenderFrame( |
| const std::string& interface_name, |
| mojo::ScopedMessagePipeHandle pipe) { |
| GetRemoteInterfaces()->GetInterfaceByName(interface_name, std::move(pipe)); |
| } |
| #endif |
| |
| void RenderFrameHostImpl::ForEachImmediateLocalRoot( |
| const base::RepeatingCallback<void(RenderFrameHostImpl*)>& callback) { |
| if (!frame_tree_node_->child_count()) |
| return; |
| |
| base::queue<FrameTreeNode*> queue; |
| for (size_t index = 0; index < frame_tree_node_->child_count(); ++index) |
| queue.push(frame_tree_node_->child_at(index)); |
| while (queue.size()) { |
| FrameTreeNode* current = queue.front(); |
| queue.pop(); |
| if (current->current_frame_host()->is_local_root()) { |
| callback.Run(current->current_frame_host()); |
| } else { |
| for (size_t index = 0; index < current->child_count(); ++index) |
| queue.push(current->child_at(index)); |
| } |
| } |
| } |
| |
| 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::FrameNavigationControl* RenderFrameHostImpl::GetNavigationControl() { |
| if (!navigation_control_) |
| GetRemoteAssociatedInterfaces()->GetInterface(&navigation_control_); |
| return navigation_control_.get(); |
| } |
| |
| bool RenderFrameHostImpl::ShouldBypassSecurityChecksForErrorPage( |
| NavigationRequest* navigation_request, |
| bool* should_commit_unreachable_url) { |
| if (should_commit_unreachable_url) |
| *should_commit_unreachable_url = false; |
| |
| if (SiteIsolationPolicy::IsErrorPageIsolationEnabled(is_main_frame())) { |
| if (GetSiteInstance()->GetSiteInfo() == SiteInfo::CreateForErrorPage()) { |
| if (should_commit_unreachable_url) |
| *should_commit_unreachable_url = 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 { |
| auto client_security_state = network::mojom::ClientSecurityState::New(); |
| client_security_state->is_web_secure_context = is_web_secure_context_; |
| client_security_state->cross_origin_embedder_policy = |
| cross_origin_embedder_policy_; |
| client_security_state->ip_address_space = ip_address_space_; |
| client_security_state->private_network_request_policy = |
| private_network_request_policy_; |
| return client_security_state; |
| } |
| |
| bool RenderFrameHostImpl::IsNavigationSameSite( |
| const UrlInfo& dest_url_info, |
| const CoopCoepCrossOriginIsolatedInfo& cross_origin_isolated_info) { |
| if (GetSiteInstance()->GetCoopCoepCrossOriginIsolatedInfo() != |
| cross_origin_isolated_info) { |
| return false; |
| } |
| return GetSiteInstance()->IsNavigationSameSite( |
| last_successful_url(), GetLastCommittedOrigin(), |
| frame_tree_node()->IsMainFrame(), 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_unreachable_url = false; |
| bool bypass_checks_for_error_page = ShouldBypassSecurityChecksForErrorPage( |
| navigation_request, &should_commit_unreachable_url); |
| |
| // 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_unreachable_url && !params->url_is_unreachable) { |
| 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; |
| } |
| |
| // file: URLs can be allowed to access any other origin, based on settings. |
| bool bypass_checks_for_file_scheme = false; |
| if (params->origin.scheme() == url::kFileScheme) { |
| auto prefs = GetOrCreateWebPreferences(); |
| if (prefs.allow_universal_access_from_file_urls) |
| bypass_checks_for_file_scheme = 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 |
| // |is_loaded_from_load_data_with_base_url_|. |
| DCHECK(navigation_request || is_same_document_navigation || |
| !frame_tree_node_->has_committed_real_load()); |
| bool bypass_checks_for_webview = false; |
| if ((navigation_request && NavigationRequest::IsLoadDataWithBaseURL( |
| navigation_request->common_params())) || |
| (is_same_document_navigation && |
| is_loaded_from_load_data_with_base_url_)) { |
| // Allow bypass if the process isn't locked. Otherwise run normal checks. |
| bypass_checks_for_webview = !ChildProcessSecurityPolicyImpl::GetInstance() |
| ->GetProcessLock(process->GetID()) |
| .is_locked_to_site(); |
| } |
| |
| if (!bypass_checks_for_error_page && !bypass_checks_for_file_scheme && |
| !bypass_checks_for_webview) { |
| // 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(params->origin, params->url)) { |
| 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 '" << params->url << "'" |
| << " origin '" << params->origin << "'" |
| << " lock '" |
| << ChildProcessSecurityPolicyImpl::GetInstance() |
| ->GetProcessLock(process->GetID()) |
| .ToString() |
| << "'"; |
| VLOG(1) << "Blocked URL " << params->url.spec(); |
| LogCannotCommitUrlCrashKeys(params->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 '" << params->url << "'" |
| << " origin '" << params->origin << "'" |
| << " lock '" |
| << ChildProcessSecurityPolicyImpl::GetInstance() |
| ->GetProcessLock(process->GetID()) |
| .ToString() |
| << "'"; |
| DEBUG_ALIAS_FOR_ORIGIN(origin_debug_alias, params->origin); |
| LogCannotCommitOriginCrashKeys(is_same_document_navigation, |
| navigation_request); |
| |
| // Kills the process. |
| bad_message::ReceivedBadMessage( |
| process, bad_message::RFH_INVALID_ORIGIN_ON_COMMIT); |
| 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, ¶ms->url); |
| process->FilterURL(true, ¶ms->referrer->url); |
| for (auto& redirect : params->redirects) { |
| process->FilterURL(false, &redirect); |
| } |
| |
| // 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 |
| // served from the back-forward cache do not require new embedding tokens as |
| // the token is already set. |
| bool is_served_from_bfcache = |
| navigation_request && navigation_request->IsServedFromBackForwardCache(); |
| if (!is_served_from_bfcache) { |
| 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( |
| frame_tree_node()->effective_frame_policy().required_document_policy, |
| params->document_policy_header)) { |
| bad_message::ReceivedBadMessage( |
| GetProcess(), bad_message::RFH_BAD_DOCUMENT_POLICY_HEADER); |
| return false; |
| } |
| |
| return true; |
| } |
| |
| void RenderFrameHostImpl::UpdateSiteURL(const GURL& url, |
| bool url_is_unreachable) { |
| if (url_is_unreachable) { |
| SetLastCommittedSiteInfo(GURL()); |
| } else { |
| SetLastCommittedSiteInfo(url); |
| } |
| } |
| |
| 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. |
| DCHECK_EQ(ui::PageTransitionIsMainFrame(params->transition), !GetParent()); |
| 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 renderer-initiated navigation to the initial empty |
| // document. |
| // 2) This was a renderer-initiated same-document navigation. |
| // In these cases, we will create a NavigationRequest by calling |
| // CreateNavigationRequestForCommit() further down. |
| // TODO(https://crbug.com/1131832): Make these navigation go through a |
| // separate path that does not send |
| // FrameHostMsg_DidCommitProvisionalLoad_Params at all. |
| const bool is_initial_empty_commit = IsInitialEmptyCommit( |
| params->url, frame_tree_node_->has_committed_real_load()); |
| if (!navigation_request && !is_initial_empty_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_request_ = 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()) { |
| bool was_loading = frame_tree()->IsLoading(); |
| is_loading_ = true; |
| frame_tree_node()->DidStartLoading(!is_same_document_navigation, |
| was_loading); |
| } |
| |
| if (navigation_request) |
| was_discarded_ = navigation_request->commit_params().was_discarded; |
| |
| 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_initial_empty_commit || is_same_document_navigation); |
| // TODO(https://crbug.com/1131832): Do not use |params| to get the values, |
| // depend on values known at commit time instead. |
| navigation_request = CreateNavigationRequestForCommit( |
| params->url, params->origin, params->referrer.Clone(), |
| params->transition, params->should_replace_current_entry, |
| params->gesture, params->redirects, params->original_request_url, |
| params->page_state, is_same_document_navigation, |
| same_document_params && |
| same_document_params->is_history_api_navigation); |
| } |
| |
| DCHECK(navigation_request); |
| DCHECK(navigation_request->IsNavigationStarted()); |
| VerifyThatBrowserAndRendererCalculatedOriginsToCommitMatch( |
| navigation_request.get(), *params); |
| 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); |
| |
| navigation_request->set_has_user_gesture(params->gesture == |
| NavigationGestureUser); |
| |
| UpdateSiteURL(params->url, params->url_is_unreachable); |
| if (!is_same_document_navigation) |
| UpdateRenderProcessHostFramePriorities(); |
| |
| // TODO(arthursonzogni): Updating this flag for same-document or bfcache |
| // navigation isn't right. This should be moved to DidCommitNewDocument(). |
| appcache_handle_ = navigation_request->TakeAppCacheHandle(); |
| |
| isolation_info_ = navigation_request->isolation_info_for_subresources(); |
| |
| bool created_new_document = |
| !is_same_document_navigation && |
| !navigation_request->IsServedFromBackForwardCache(); |
| |
| // 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); |
| |
| if (navigation_request->IsInMainFrame()) { |
| render_view_host_->ResetPerPageState(); |
| } |
| last_committed_cross_document_navigation_id_ = |
| navigation_request->GetNavigationId(); |
| |
| if (IsCurrent() && !committed_speculative_rfh_before_navigation_commit_) { |
| // Clear all the user data associated with the non speculative |
| // RenderFrameHost when the navigation is a cross-document navigation not |
| // served from the back-forward cache. 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_.ClearAllUserData(); |
| } |
| |
| // 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 main |
| // frames can be recorded. |
| if (is_main_frame() && 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->IsOverridingUserAgent() && |
| frame_tree_node_->IsMainFrame(), |
| params->is_overriding_user_agent); |
| } else { |
| DCHECK_EQ(is_overriding_user_agent_, params->is_overriding_user_agent); |
| } |
| |
| // 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 or bfcache |
| // navigation doesn't seem right. This should likely be executed in |
| // DidCommitNewDocument(). |
| if (IsBackForwardCacheEnabled()) { |
| // 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): Below, many NavigationRequest's objects are passed from |
| // the navigation to the new document. Consider grouping them in a single |
| // struct. |
| // |
| // TODO(arthursonzogni): Investigate what must be done when |
| // navigation_request->IsWaitingToCommit() is false here. |
| void RenderFrameHostImpl::DidCommitNewDocument( |
| const mojom::DidCommitProvisionalLoadParams& params, |
| NavigationRequest* navigation_request) { |
| // BackForwardCache navigations restore existing document, but never create |
| // new ones. |
| DCHECK(!navigation_request->IsServedFromBackForwardCache()); |
| |
| ResetFeaturePolicy(); |
| active_sandbox_flags_ = params.sandbox_flags; |
| feature_policy_header_ = params.feature_policy_header; |
| feature_policy_->SetHeaderPolicy(params.feature_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; |
| |
| dom_content_loaded_ = false; |
| |
| is_error_page_ = (navigation_request->GetNetErrorCode() != net::OK); |
| |
| DCHECK(params.embedding_token.has_value()); |
| SetEmbeddingToken(params.embedding_token.value()); |
| |
| // 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; |
| |
| renderer_reported_scheduler_tracked_features_ = 0; |
| browser_reported_scheduler_tracked_features_ = 0; |
| |
| cross_origin_opener_policy_ = |
| navigation_request->coop_status().current_coop(); |
| if (navigation_request->IsWaitingToCommit()) { |
| is_web_secure_context_ = navigation_request->is_web_secure_context(); |
| ip_address_space_ = navigation_request->ip_address_space(); |
| private_network_request_policy_ = |
| navigation_request->private_network_request_policy(); |
| cross_origin_embedder_policy_ = |
| navigation_request->cross_origin_embedder_policy(); |
| } |
| |
| coop_reporter_ = navigation_request->coop_status().TakeCoopReporter(); |
| virtual_browsing_context_group_ = |
| navigation_request->coop_status().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(); |
| |
| // TODO(https://crbug.com/1041376): The sandbox flags computed from the |
| // browser must match with the ones computed from the renderer process. |
| // Ultimately, the one from the browser process should supersede the |
| // renderer one. The browser will just "push" the correct value. |
| if (navigation_request->state() >= |
| NavigationRequest::NavigationState::WILL_PROCESS_RESPONSE) { |
| DCHECK_EQ(params.sandbox_flags, navigation_request->SandboxFlagsToCommit()); |
| } |
| |
| coep_reporter_ = navigation_request->TakeCoepReporter(); |
| if (coep_reporter_) { |
| 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))); |
| } |
| |
| // 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). |
| if (navigation_request->IsWaitingToCommit()) { |
| policy_container_host_ = navigation_request->TakePolicyContainerHost(); |
| } |
| |
| // 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->IsOverridingUserAgent() && |
| frame_tree_node_->IsMainFrame(); |
| |
| // Mark whether the document is loaded with loadDataWithBaseURL or not. If |
| // |is_loaded_from_load_data_with_base_url_| is true, we will bypass checks |
| // in VerifyDidCommitParams for same-document navigations in the loaded |
| // document. |
| is_loaded_from_load_data_with_base_url_ = |
| NavigationRequest::IsLoadDataWithBaseURL( |
| navigation_request->common_params()); |
| |
| base::Optional<std::string> data_url_as_string; |
| #if defined(OS_ANDROID) |
| data_url_as_string = navigation_request->commit_params().data_url_as_string; |
| #endif |
| is_loaded_from_load_data_with_base_url_and_unreachable_url_ = |
| NavigationRequest::IsLoadDataWithBaseURLAndUnreachableURL( |
| frame_tree_node_->IsMainFrame(), navigation_request->common_params(), |
| data_url_as_string); |
| |
| RecordWebPlatformSecurityMetrics(this, navigation_request); |
| |
| CrossOriginOpenerPolicyReporter::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(); |
| |
| // 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. |
| // TODO(arthursonzogni): Updating this flag for same-document or bfcache |
| // navigation isn't right. This should be moved to DidCommitNewDocument(). |
| loading_mem_tracker_ = navigation_request->TakePeakGpuMemoryTracker(); |
| |
| accessibility_fatal_error_count_ = 0; |
| } |
| |
| void RenderFrameHostImpl::OnSameDocumentCommitProcessed( |
| int64_t navigation_id, |
| bool should_replace_current_entry, |
| blink::mojom::CommitResult result) { |
| // If the NavigationRequest was deleted, another navigation commit started to |
| // be processed. Let the latest commit go through and stop doing anything. |
| if (!same_document_navigation_request_ || |
| same_document_navigation_request_->GetNavigationId() != navigation_id) { |
| 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(same_document_navigation_request_)); |
| } |
| |
| if (result == blink::mojom::CommitResult::Aborted) |
| same_document_navigation_request_.reset(); |
| } |
| |
| std::unique_ptr<base::trace_event::TracedValue> |
| RenderFrameHostImpl::CommitAsTracedValue( |
| const mojom::DidCommitProvisionalLoadParams& params) const { |
| auto value = std::make_unique<base::trace_event::TracedValue>(); |
| |
| // TODO(nasko): Move the process lock into RenderProcessHost. |
| value->SetString( |
| "process lock", |
| ChildProcessSecurityPolicyImpl::GetInstance() |
| ->GetProcessLock(agent_scheduling_group_.GetProcess()->GetID()) |
| .ToString()); |
| |
| value->SetInteger("item_sequence_number", params.item_sequence_number); |
| value->SetInteger("document_sequence_number", |
| params.document_sequence_number); |
| value->SetString("url", params.url.spec()); |
| if (!params.base_url.is_empty()) { |
| value->SetString("base_url", params.base_url.possibly_invalid_spec()); |
| } |
| value->SetInteger("transition", params.transition); |
| value->BeginDictionary("referrer"); |
| value->SetString("url", params.referrer->url.spec()); |
| value->SetInteger("policy", static_cast<int>(params.referrer->policy)); |
| value->EndDictionary(); |
| value->SetBoolean("should_update_history", params.should_update_history); |
| value->SetString("contents_mime_type", params.contents_mime_type); |
| |
| value->SetBoolean("intended_as_new_entry", params.intended_as_new_entry); |
| value->SetBoolean("did_create_new_entry", params.did_create_new_entry); |
| value->SetBoolean("should_replace_current_entry", |
| params.should_replace_current_entry); |
| value->SetString("method", params.method); |
| value->SetInteger("post_id", params.post_id); |
| value->SetInteger("http_status_code", params.http_status_code); |
| value->SetBoolean("url_is_unreachable", params.url_is_unreachable); |
| value->SetString("original_request_url", params.original_request_url.spec()); |
| value->SetBoolean("is_overriding_user_agent", |
| params.is_overriding_user_agent); |
| value->SetBoolean("history_list_was_cleared", |
| params.history_list_was_cleared); |
| value->SetString("origin", params.origin.GetDebugString()); |
| value->SetBoolean("has_potentially_trustworthy_unique_origin", |
| params.has_potentially_trustworthy_unique_origin); |
| value->SetInteger("request_id", params.request_id); |
| value->SetString("navigation_token", params.navigation_token.ToString()); |
| if (params.embedding_token) |
| value->SetString("embedding_token", params.embedding_token->ToString()); |
| |
| return value; |
| } |
| |
| void RenderFrameHostImpl::MaybeGenerateCrashReport( |
| base::TerminationStatus status, |
| int exit_code) { |
| if (!last_committed_url_.SchemeIsHTTPOrHTTPS()) |
| return; |
| |
| // Only generate reports for local root frames. |
| if (!frame_tree_node_->IsMainFrame() && !IsCrossProcessSubframe()) |
| return; |
| |
| // 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_ASH) |
| case base::TERMINATION_STATUS_PROCESS_WAS_KILLED_BY_OOM: |
| #endif |
| #if defined(OS_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. |
| auto body = base::DictionaryValue(); |
| if (!reason.empty()) |
| body.SetString("reason", reason); |
| |
| // Send the crash report to the Reporting API. |
| GetProcess()->GetStoragePartition()->GetNetworkContext()->QueueReport( |
| "crash" /* type */, "default" /* group */, last_committed_url_, |
| isolation_info_.network_isolation_key(), base::nullopt, std::move(body)); |
| } |
| |
| void RenderFrameHostImpl::SendCommitNavigation( |
| mojom::NavigationClient* navigation_client, |
| NavigationRequest* navigation_request, |
| mojom::CommonNavigationParamsPtr common_params, |
| 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, |
| base::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, |
| blink::mojom::PolicyContainerPtr policy_container, |
| const base::UnguessableToken& devtools_navigation_token) { |
| 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, |
| std::move(policy_container), |
| BuildCommitNavigationCallback(navigation_request)); |
| } |
| |
| void RenderFrameHostImpl::SendCommitFailedNavigation( |
| mojom::NavigationClient* navigation_client, |
| NavigationRequest* navigation_request, |
| mojom::CommonNavigationParamsPtr common_params, |
| mojom::CommitNavigationParamsPtr commit_params, |
| bool has_stale_copy_in_cache, |
| int32_t error_code, |
| const base::Optional<std::string>& error_page_content, |
| std::unique_ptr<blink::PendingURLLoaderFactoryBundle> |
| subresource_loader_factories) { |
| DCHECK(navigation_client && navigation_request); |
| DCHECK_NE(GURL(), common_params->url); |
| navigation_client->CommitFailedNavigation( |
| std::move(common_params), std::move(commit_params), |
| has_stale_copy_in_cache, error_code, |
| navigation_request->GetResolveErrorInfo(), error_page_content, |
| std::move(subresource_loader_factories), |
| 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 IsInactiveAndDisallowReactivation 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; |
| // A `committing_navigation_request` is not present only in the case of |
| // committing an initial empty document in a frame. 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, ¶ms, |
| &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). |
| 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", base::trace_event::ToTracedValue(this), "params", |
| CommitAsTracedValue(*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( |
| true /* proceed */, true /* treat_as_final_completion_callback */, |
| approx_renderer_start_time, base::TimeTicks::Now()); |
| } |
| |
| // 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()) { |
| auto broker_receiver_of_previous_document = broker_receiver_.Unbind(); |
| dropped_interface_request_logger_ = |
| std::make_unique<DroppedInterfaceRequestLogger>( |
| std::move(broker_receiver_of_previous_document)); |
| } |
| BindBrowserInterfaceBrokerReceiver( |
| std::move(interface_params->browser_interface_broker_receiver)); |
| } else { |
| // If there had already been a real load committed in the frame, 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_->has_committed_real_load()) { |
| 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), |
| nullptr /* same_document_params */)) { |
| 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 (frame_tree_node_->IsMainFrame() && 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) { |
| auto before_unload_closure = base::BindOnce( |
| [](base::WeakPtr<RenderFrameHostImpl> impl, bool proceed, |
| base::TimeTicks renderer_before_unload_start_time, |
| base::TimeTicks renderer_before_unload_end_time) { |
| if (!impl) |
| return; |
| impl->ProcessBeforeUnloadCompleted( |
| proceed, false /* treat_as_final_completion_callback */, |
| renderer_before_unload_start_time, renderer_before_unload_end_time); |
| }, |
| rfh); |
| // 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 base::Optional<base::UnguessableToken>& source_token, |
| const base::string16& source_origin, |
| const base::string16& target_origin, |
| blink::TransferableMessage message) { |
| DCHECK(render_frame_created_); |
| |
| 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 (!parent_) |
| 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"; }; |
| |
| base::debug::SetCrashKeyString( |
| base::debug::AllocateCrashKeyString("navigation_url", |
| base::debug::CrashKeySize::Size256), |
| url.spec()); |
| |
| base::debug::SetCrashKeyString( |
| base::debug::AllocateCrashKeyString("is_same_document", |
| base::debug::CrashKeySize::Size32), |
| bool_to_crash_key(is_same_document_navigation)); |
| |
| base::debug::SetCrashKeyString( |
| base::debug::AllocateCrashKeyString("is_main_frame", |
| base::debug::CrashKeySize::Size32), |
| bool_to_crash_key(frame_tree_node_->IsMainFrame())); |
| |
| base::debug::SetCrashKeyString( |
| base::debug::AllocateCrashKeyString("is_cross_process_subframe", |
| base::debug::CrashKeySize::Size32), |
| bool_to_crash_key(IsCrossProcessSubframe())); |
| |
| base::debug::SetCrashKeyString( |
| base::debug::AllocateCrashKeyString("site_lock", |
| base::debug::CrashKeySize::Size256), |
| GetSiteInstance()->GetProcessLock().ToString()); |
| |
| if (!GetSiteInstance()->IsDefaultSiteInstance()) { |
| base::debug::SetCrashKeyString( |
| base::debug::AllocateCrashKeyString("original_url_origin", |
| base::debug::CrashKeySize::Size256), |
| GetSiteInstance()->original_url().GetOrigin().spec()); |
| } |
| |
| base::debug::SetCrashKeyString( |
| base::debug::AllocateCrashKeyString("is_mhtml_document", |
| base::debug::CrashKeySize::Size32), |
| bool_to_crash_key(is_mhtml_document())); |
| |
| base::debug::SetCrashKeyString( |
| base::debug::AllocateCrashKeyString("last_committed_url_origin", |
| base::debug::CrashKeySize::Size256), |
| GetLastCommittedURL().GetOrigin().spec()); |
| |
| base::debug::SetCrashKeyString( |
| base::debug::AllocateCrashKeyString("last_successful_url_origin", |
| base::debug::CrashKeySize::Size256), |
| last_successful_url().GetOrigin().spec()); |
| |
| if (navigation_request && navigation_request->IsNavigationStarted()) { |
| base::debug::SetCrashKeyString( |
| base::debug::AllocateCrashKeyString("is_renderer_initiated", |
| base::debug::CrashKeySize::Size32), |
| bool_to_crash_key(navigation_request->IsRendererInitiated())); |
| |
| base::debug::SetCrashKeyString( |
| base::debug::AllocateCrashKeyString("is_server_redirect", |
| base::debug::CrashKeySize::Size32), |
| bool_to_crash_key(navigation_request->WasServerRedirect())); |
| |
| base::debug::SetCrashKeyString( |
| base::debug::AllocateCrashKeyString("is_form_submission", |
| base::debug::CrashKeySize::Size32), |
| bool_to_crash_key(navigation_request->IsFormSubmission())); |
| |
| base::debug::SetCrashKeyString( |
| base::debug::AllocateCrashKeyString("is_error_page", |
| base::debug::CrashKeySize::Size32), |
| bool_to_crash_key(navigation_request->IsErrorPage())); |
| |
| base::debug::SetCrashKeyString( |
| base::debug::AllocateCrashKeyString("from_begin_navigation", |
| base::debug::CrashKeySize::Size32), |
| bool_to_crash_key(navigation_request->from_begin_navigation())); |
| |
| base::debug::SetCrashKeyString( |
| base::debug::AllocateCrashKeyString("net_error", |
| base::debug::CrashKeySize::Size32), |
| base::NumberToString(navigation_request->GetNetErrorCode())); |
| |
| base::debug::SetCrashKeyString( |
| base::debug::AllocateCrashKeyString("initiator_origin", |
| base::debug::CrashKeySize::Size64), |
| navigation_request->GetInitiatorOrigin() |
| ? navigation_request->GetInitiatorOrigin()->GetDebugString() |
| : "none"); |
| |
| base::debug::SetCrashKeyString( |
| base::debug::AllocateCrashKeyString("starting_site_instance", |
| base::debug::CrashKeySize::Size256), |
| 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 = |
| frame_tree_node_->render_manager()->GetSiteInstanceForNavigationRequest( |
| navigation_request); |
| base::debug::SetCrashKeyString( |
| base::debug::AllocateCrashKeyString( |
| "does_recomputed_site_instance_match", |
| base::debug::CrashKeySize::Size32), |
| 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; |
| } |
| |
| bool DoBaseURLExpectationsMatch(const GURL& renderer_base_url, |
| const net::Error& net_error_code) { |
| // base_url value is currently only used in the browser side to check if a |
| // navigation results in an error page or not in |
| // NavigationRequest::DidCommitNavigation. This can be known by just checking |
| // for the net error code value instead, as all navigations that result in an |
| // error page should be known by the browser side before committing. |
| if (renderer_base_url == kUnreachableWebDataURL && |
| net_error_code == net::OK) { |
| return false; |
| } |
| // Note: base_url might be set in |params| in other cases (e.g. if a page has |
| // the <base> element and it's doing a same document navigation, about:srcdoc, |
| // MHTML), but those cases do not matter because they aren't handled at all |
| // in the browser side. |
| return true; |
| } |
| |
| bool CalculateURLIsUnreachable( |
| NavigationRequest* request, |
| bool is_loaded_from_load_data_with_base_url_and_unreachable_url) { |
| // url_is_unreachable should only be true in two cases: |
| // 1) The navigation is for an error page |
| if (request->GetNetErrorCode() != net::OK) |
| return true; |
| // 2) This is a main frame navigation to a data: URL document with a base_url |
| // (either an initial load or a same-document navigation). |
| // For same-document navigations, we can know this by checking the value |
| // of |is_loaded_from_load_data_with_base_url_and_unreachable_url|, which was |
| // set when the document was initially loaded. |
| if (request->IsSameDocument()) |
| return is_loaded_from_load_data_with_base_url_and_unreachable_url; |
| |
| // For cross-document navigations, we can know by checking |
| // NavigationRequest::IsLoadDataWithBaseURLAndUnreachableURL(). |
| base::Optional<std::string> data_url_as_string; |
| #if defined(OS_ANDROID) |
| data_url_as_string = request->commit_params().data_url_as_string; |
| #endif |
| return NavigationRequest::IsLoadDataWithBaseURLAndUnreachableURL( |
| request->IsInMainFrame(), request->common_params(), data_url_as_string); |
| } |
| |
| 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 will always have |
| // the HTTP status code set to 200. |
| if (request->IsServedFromBackForwardCache()) |
| 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 ShouldVerify(const std::string& param) { |
| #if DCHECK_IS_ON() |
| return true; |
| #else |
| return GetFieldTrialParamByFeatureAsBool(features::kVerifyDidCommitParams, |
| param, false); |
| #endif |
| } |
| |
| 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: |
| // - intended_as_new_entry |
| // - method |
| // - url_is_unreachable |
| // - base_url |
| // - post_id |
| // - is_overriding_user_agent |
| // - http_status_code |
| // - should_update_history |
| // TODO(crbug.com/1131832): Verify more params. |
| const GURL& browser_base_url = request->common_params().base_url_for_data_url; |
| |
| const bool browser_url_is_unreachable = CalculateURLIsUnreachable( |
| request, is_loaded_from_load_data_with_base_url_and_unreachable_url_); |
| |
| const bool base_url_expectations_match = |
| DoBaseURLExpectationsMatch(params.base_url, request->GetNetErrorCode()); |
| |
| const bool is_same_document_navigation = !!same_document_params; |
| const bool is_same_document_history_api_navigation = |
| same_document_params && same_document_params->is_history_api_navigation; |
| 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 && |
| 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; |
| |
| if ((!ShouldVerify("intended_as_new_entry") || |
| request->commit_params().intended_as_new_entry == |
| params.intended_as_new_entry) && |
| (!ShouldVerify("method") || browser_method == params.method) && |
| (!ShouldVerify("url_is_unreachable") || |
| browser_url_is_unreachable == params.url_is_unreachable) && |
| (!ShouldVerify("base_url") || base_url_expectations_match) && |
| (!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)) { |
| return; |
| } |
| |
| SCOPED_CRASH_KEY_BOOL("VerifyDidCommit", "browser_intended", |
| request->commit_params().intended_as_new_entry); |
| SCOPED_CRASH_KEY_BOOL("VerifyDidCommit", "renderer_intended", |
| params.intended_as_new_entry); |
| |
| SCOPED_CRASH_KEY_STRING32("VerifyDidCommit", "browser_method", |
| browser_method); |
| SCOPED_CRASH_KEY_STRING32("VerifyDidCommit", "renderer_method", |
| params.method); |
| SCOPED_CRASH_KEY_STRING32("VerifyDidCommit", "original_method", |
| request->commit_params().original_method); |
| |
| SCOPED_CRASH_KEY_BOOL("VerifyDidCommit", "browser_unreachable", |
| browser_url_is_unreachable); |
| SCOPED_CRASH_KEY_BOOL("VerifyDidCommit", "renderer_unreachable", |
| params.url_is_unreachable); |
| |
| SCOPED_CRASH_KEY_STRING256("VerifyDidCommit", "browser_base_url", |
| browser_base_url.possibly_invalid_spec()); |
| SCOPED_CRASH_KEY_STRING256("VerifyDidCommit", "renderer_base_url", |
| params.base_url.possibly_invalid_spec()); |
| SCOPED_CRASH_KEY_BOOL("VerifyDidCommit", "base_url_exp_match", |
| base_url_expectations_match); |
| |
| SCOPED_CRASH_KEY_BOOL("VerifyDidCommit", "prev_ldwb", |
| is_loaded_from_load_data_with_base_url_); |
| SCOPED_CRASH_KEY_BOOL( |
| "VerifyDidCommit", "prev_ldwbu", |
| is_loaded_from_load_data_with_base_url_and_unreachable_url_); |
| SCOPED_CRASH_KEY_BOOL("VerifyDidCommit", "b_base_url_valid", |
| browser_base_url.is_valid()); |
| SCOPED_CRASH_KEY_BOOL("VerifyDidCommit", "b_base_url_empty", |
| browser_base_url.is_empty()); |
| SCOPED_CRASH_KEY_BOOL( |
| "VerifyDidCommit", "b_hist_url_empty", |
| request->common_params().history_url_for_data_url.is_empty()); |
| #if defined(OS_ANDROID) |
| SCOPED_CRASH_KEY_BOOL("VerifyDidCommit", "b_data_url_empty", |
| request->commit_params().data_url_as_string.empty()); |
| #endif |
| SCOPED_CRASH_KEY_BOOL("VerifyDidCommit", "r_base_url_empty", |
| params.base_url.is_empty()); |
| SCOPED_CRASH_KEY_BOOL("VerifyDidCommit", "r_base_url_is_error", |
| params.base_url == kUnreachableWebDataURL); |
| SCOPED_CRASH_KEY_BOOL( |
| "VerifyDidCommit", "r_history_url_empty", |
| request->common_params().history_url_for_data_url.is_empty()); |
| |
| SCOPED_CRASH_KEY_NUMBER("VerifyDidCommit", "browser_post_id", |
| browser_post_id); |
| SCOPED_CRASH_KEY_NUMBER("VerifyDidCommit", "renderer_post_id", |
| params.post_id); |
| |
| SCOPED_CRASH_KEY_BOOL("VerifyDidCommit", "browser_override_ua", |
| browser_is_overriding_user_agent); |
| SCOPED_CRASH_KEY_BOOL("VerifyDidCommit", "renderer_override_ua", |
| params.is_overriding_user_agent); |
| |
| SCOPED_CRASH_KEY_NUMBER("VerifyDidCommit", "browser_code", |
| browser_http_status_code); |
| SCOPED_CRASH_KEY_NUMBER("VerifyDidCommit", "renderer_code", |
| params.http_status_code); |
| |
| SCOPED_CRASH_KEY_BOOL("VerifyDidCommit", "browser_suh", |
| browser_should_update_history); |
| SCOPED_CRASH_KEY_BOOL("VerifyDidCommit", "renderer_suh", |
| params.should_update_history); |
| |
| 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", |
| !frame_tree_node_->IsMainFrame()); |
| SCOPED_CRASH_KEY_BOOL("VerifyDidCommit", "is_form_submission", |
| request->IsFormSubmission()); |
| 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", |
| params.redirects.size()); |
| |
| SCOPED_CRASH_KEY_NUMBER("VerifyDidCommit", "entry_offset", |
| request->GetNavigationEntryOffset()); |
| SCOPED_CRASH_KEY_BOOL("VerifyDidCommit", "is_reload", |
| request->GetReloadType() != ReloadType::NONE); |
| SCOPED_CRASH_KEY_BOOL("VerifyDidCommit", "is_restore", |
| request->GetRestoreType() == RestoreType::kRestored); |
| SCOPED_CRASH_KEY_BOOL("VerifyDidCommit", "has_gesture", |
| request->HasUserGesture()); |
| SCOPED_CRASH_KEY_BOOL("VerifyDidCommit", "was_click", |
| request->WasInitiatedByLinkClick()); |
| |
| SCOPED_CRASH_KEY_STRING256("VerifyDidCommit", "navigation_url", |
| params.url.possibly_invalid_spec()); |
| SCOPED_CRASH_KEY_BOOL("VerifyDidCommit", "nav_url_blank", |
| params.url.IsAboutBlank()); |
| SCOPED_CRASH_KEY_BOOL("VerifyDidCommit", "nav_url_srcdoc", |
| params.url.IsAboutSrcdoc()); |
| SCOPED_CRASH_KEY_BOOL("VerifyDidCommit", "nav_url_blocked", |
| params.url == kBlockedURL); |
| SCOPED_CRASH_KEY_BOOL("VerifyDidCommit", "nav_url_error", |
| params.url == kUnreachableWebDataURL); |
| |
| SCOPED_CRASH_KEY_STRING256("VerifyDidCommit", "original_req_url", |
| params.original_request_url.spec()); |
| SCOPED_CRASH_KEY_BOOL( |
| "VerifyDidCommit", "original_same_doc", |
| params.original_request_url.EqualsIgnoringRef(GetLastCommittedURL())); |
| |
| SCOPED_CRASH_KEY_STRING256("VerifyDidCommit", "last_committed_url", |
| GetLastCommittedURL().spec()); |
| SCOPED_CRASH_KEY_BOOL("VerifyDidCommit", "last_url_empty", |
| GetLastCommittedURL().is_empty()); |
| SCOPED_CRASH_KEY_BOOL("VerifyDidCommit", "last_url_blank", |
| GetLastCommittedURL().IsAboutBlank()); |
| SCOPED_CRASH_KEY_BOOL("VerifyDidCommit", "last_url_srcdoc", |
| GetLastCommittedURL().IsAboutSrcdoc()); |
| SCOPED_CRASH_KEY_BOOL("VerifyDidCommit", "last_url_error", |
| GetLastCommittedURL() == kUnreachableWebDataURL); |
| |
| 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(request->commit_params().intended_as_new_entry, |
| params.intended_as_new_entry); |
| 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(base_url_expectations_match); |
| base::debug::DumpWithoutCrashing(); |
| } |
| |
| void RenderFrameHostImpl::MaybeEvictFromBackForwardCache() { |
| if (!IsInBackForwardCache()) |
| return; |
| |
| RenderFrameHostImpl* top_document = this; |
| while (RenderFrameHostImpl* parent = top_document->GetParent()) |
| top_document = parent; |
| |
| auto can_store = |
| frame_tree()->controller()->GetBackForwardCache().CanStorePageNow( |
| top_document); |
| TRACE_EVENT1("navigation", |
| "RenderFrameHostImpl::MaybeEvictFromBackForwardCache", |
| "can_store", can_store.ToString()); |
| |
| if (can_store) |
| return; |
| |
| EvictFromBackForwardCacheWithReasons(can_store); |
| } |
| |
| void RenderFrameHostImpl::LogCannotCommitOriginCrashKeys( |
| 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"; }; |
| base::debug::SetCrashKeyString( |
| base::debug::AllocateCrashKeyString("is_same_document", |
| base::debug::CrashKeySize::Size32), |
| bool_to_crash_key(is_same_document_navigation)); |
| |
| base::debug::SetCrashKeyString( |
| base::debug::AllocateCrashKeyString("is_subframe", |
| base::debug::CrashKeySize::Size32), |
| bool_to_crash_key(!frame_tree_node_->IsMainFrame())); |
| |
| base::debug::SetCrashKeyString( |
| base::debug::AllocateCrashKeyString("lifecycle_state", |
| base::debug::CrashKeySize::Size32), |
| LifecycleStateToString(lifecycle_state_)); |
| |
| base::debug::SetCrashKeyString( |
| base::debug::AllocateCrashKeyString("is_current", |
| base::debug::CrashKeySize::Size32), |
| bool_to_crash_key(IsCurrent())); |
| |
| base::debug::SetCrashKeyString( |
| base::debug::AllocateCrashKeyString("is_cross_process_subframe", |
| base::debug::CrashKeySize::Size32), |
| bool_to_crash_key(IsCrossProcessSubframe())); |
| |
| if (navigation_request && navigation_request->IsNavigationStarted()) { |
| base::debug::SetCrashKeyString( |
| base::debug::AllocateCrashKeyString("is_renderer_initiated", |
| base::debug::CrashKeySize::Size32), |
| bool_to_crash_key(navigation_request->IsRendererInitiated())); |
| |
| base::debug::SetCrashKeyString( |
| base::debug::AllocateCrashKeyString("is_server_redirect", |
| base::debug::CrashKeySize::Size32), |
| bool_to_crash_key(navigation_request->WasServerRedirect())); |
| |
| base::debug::SetCrashKeyString( |
| base::debug::AllocateCrashKeyString("is_form_submission", |
| base::debug::CrashKeySize::Size32), |
| bool_to_crash_key(navigation_request->IsFormSubmission())); |
| |
| base::debug::SetCrashKeyString( |
| base::debug::AllocateCrashKeyString("is_error_page", |
| base::debug::CrashKeySize::Size32), |
| bool_to_crash_key(navigation_request->IsErrorPage())); |
| |
| base::debug::SetCrashKeyString( |
| base::debug::AllocateCrashKeyString("initiator_origin", |
| base::debug::CrashKeySize::Size64), |
| navigation_request->GetInitiatorOrigin() |
| ? navigation_request->GetInitiatorOrigin()->GetDebugString() |
| : "none"); |
| |
| base::debug::SetCrashKeyString( |
| base::debug::AllocateCrashKeyString("starting_site_instance", |
| base::debug::CrashKeySize::Size256), |
| navigation_request->GetStartingSiteInstance() |
| ->GetSiteInfo() |
| .GetDebugString()); |
| } |
| } |
| |
| void RenderFrameHostImpl::EnableMojoJsBindings() { |
| // 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(); |
| } |
| |
| 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 dom_content_loaded_; |
| } |
| |
| void RenderFrameHostImpl::UpdateAdFrameType( |
| blink::mojom::AdFrameType ad_frame_type) { |
| frame_tree_node_->SetAdFrameType(ad_frame_type); |
| } |
| |
| blink::mojom::AuthenticatorStatus |
| RenderFrameHostImpl::PerformGetAssertionWebAuthSecurityChecks( |
| const std::string& relying_party_id, |
| const url::Origin& effective_origin) { |
| bool is_cross_origin; |
| blink::mojom::AuthenticatorStatus status = |
| GetWebAuthRequestSecurityChecker()->ValidateAncestorOrigins( |
| effective_origin, |
| WebAuthRequestSecurityChecker::RequestType::kGetAssertion, |
| &is_cross_origin); |
| if (status != blink::mojom::AuthenticatorStatus::SUCCESS) { |
| return status; |
| } |
| |
| status = GetWebAuthRequestSecurityChecker()->ValidateDomainAndRelyingPartyID( |
| effective_origin, relying_party_id); |
| if (status != blink::mojom::AuthenticatorStatus::SUCCESS) { |
| return status; |
| } |
| |
| return blink::mojom::AuthenticatorStatus::SUCCESS; |
| } |
| |
| blink::mojom::AuthenticatorStatus |
| RenderFrameHostImpl::PerformMakeCredentialWebAuthSecurityChecks( |
| const std::string& relying_party_id, |
| const url::Origin& effective_origin) { |
| bool is_cross_origin; |
| blink::mojom::AuthenticatorStatus status = |
| GetWebAuthRequestSecurityChecker()->ValidateAncestorOrigins( |
| effective_origin, |
| WebAuthRequestSecurityChecker::RequestType::kMakeCredential, |
| &is_cross_origin); |
| if (status != blink::mojom::AuthenticatorStatus::SUCCESS) { |
| return status; |
| } |
| |
| status = GetWebAuthRequestSecurityChecker()->ValidateDomainAndRelyingPartyID( |
| effective_origin, relying_party_id); |
| if (status != blink::mojom::AuthenticatorStatus::SUCCESS) { |
| return status; |
| } |
| |
| return blink::mojom::AuthenticatorStatus::SUCCESS; |
| } |
| |
| void RenderFrameHostImpl::IsClipboardPasteAllowed( |
| const ui::ClipboardFormatType& data_type, |
| const std::string& data, |
| IsClipboardPasteAllowedCallback callback) { |
| delegate_->IsClipboardPasteAllowed(GetLastCommittedURL(), data_type, data, |
| std::move(callback)); |
| } |
| |
| RenderFrameHostImpl* RenderFrameHostImpl::ParentOrOuterDelegateFrame() { |
| // Find the parent in the FrameTree (iframe). |
| if (parent_) |
| return parent_; |
| |
| // Find the parent in the WebContentsTree (GuestView or Portal). |
| FrameTreeNode* frame_in_embedder = |
| frame_tree_node()->render_manager()->GetOuterDelegateNode(); |
| if (frame_in_embedder) |
| return frame_in_embedder->current_frame_host()->GetParent(); |
| |
| // No parent found. |
| return nullptr; |
| } |
| |
| RenderFrameHostImpl* RenderFrameHostImpl::GetOutermostMainFrame() { |
| RenderFrameHostImpl* current = this; |
| while (true) { |
| RenderFrameHostImpl* parent = current->ParentOrOuterDelegateFrame(); |
| 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() { |
| return lifecycle_state_ == LifecycleState::kInBackForwardCache; |
| } |
| |
| bool RenderFrameHostImpl::IsPendingDeletion() { |
| return lifecycle_state_ == LifecycleState::kRunningUnloadHandlers || |
| lifecycle_state_ == LifecycleState::kReadyToBeDeleted; |
| } |
| |
| void RenderFrameHostImpl::SetLifecycleStateToActive() { |
| // If the RenderFrameHost is restored from BackForwardCache, update states of |
| // all the children to kActive. This is called from |
| // RenderFrameHostManager::SetRenderFrameHost which happens after commit. |
| if (IsInBackForwardCache()) { |
| for (auto& child : children_) { |
| DCHECK_EQ(child->current_frame_host()->lifecycle_state_, |
| LifecycleState::kInBackForwardCache); |
| child->current_frame_host()->SetLifecycleStateToActive(); |
| } |
| } |
| |
| SetLifecycleState(LifecycleState::kActive); |
| } |
| |
| void RenderFrameHostImpl::SetLifecycleState(LifecycleState state) { |
| TRACE_EVENT2("content", "RenderFrameHostImpl::SetLifecycleState", |
| "render_frame_host", base::trace_event::ToTracedValue(this), |
| "new_state", LifecycleStateToString(state)); |
| #if DCHECK_IS_ON() |
| static const base::NoDestructor<StateTransitions<LifecycleState>> |
| 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. |
| StateTransitions<LifecycleState>({ |
| {LifecycleState::kSpeculative, |
| {LifecycleState::kActive, LifecycleState::kReadyToBeDeleted}}, |
| |
| {LifecycleState::kActive, |
| {LifecycleState::kInBackForwardCache, |
| LifecycleState::kRunningUnloadHandlers, |
| LifecycleState::kReadyToBeDeleted}}, |
| |
| {LifecycleState::kInBackForwardCache, |
| {LifecycleState::kActive, LifecycleState::kReadyToBeDeleted}}, |
| |
| {LifecycleState::kRunningUnloadHandlers, |
| {LifecycleState::kReadyToBeDeleted}}, |
| |
| {LifecycleState::kReadyToBeDeleted, {}}, |
| })); |
| DCHECK_STATE_TRANSITION(allowed_transitions, /*old_state=*/lifecycle_state_, |
| /*new_state*/ state); |
| #endif // DCHECK_IS_ON() |
| |
| LifecycleState old_state = lifecycle_state_; |
| lifecycle_state_ = state; |
| // Notify the delegate about change in |lifecycle_state_|. |
| delegate_->RenderFrameHostStateChanged(this, old_state, 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); |
| |
| ukm::builders::DocumentCreated(document_ukm_source_id) |
| .SetNavigationSourceId(GetPageUkmSourceId()) |
| .SetIsMainFrame(is_main_frame()) |
| .SetIsCrossOriginFrame(is_cross_origin_frame) |
| .Record(ukm_recorder); |
| } |
| |
| void RenderFrameHostImpl::BindReportingObserver( |
| mojo::PendingReceiver<blink::mojom::ReportingObserver> receiver) { |
| GetAssociatedLocalFrame()->BindReportingObserver(std::move(receiver)); |
| } |
| |
| mojo::PendingRemote<network::mojom::CookieAccessObserver> |
| RenderFrameHostImpl::CreateCookieAccessObserver() { |
| mojo::PendingRemote<network::mojom::CookieAccessObserver> remote; |
| cookie_observers_.Add(this, remote.InitWithNewPipeAndPassReceiver()); |
| return remote; |
| } |
| |
| void RenderFrameHostImpl::Clone( |
| mojo::PendingReceiver<network::mojom::CookieAccessObserver> observer) { |
| cookie_observers_.Add(this, std::move(observer)); |
| } |
| |
| void RenderFrameHostImpl::OnCookiesAccessed( |
| network::mojom::CookieAccessDetailsPtr details) { |
| EmitSameSiteCookiesDeprecationWarning(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 AXTreeIDRegistry. |
| ui::AXTreeID ax_tree_id = ui::AXTreeID::FromToken(embedding_token); |
| SetAXTreeID(ax_tree_id); |
| ui::AXTreeIDRegistry::GetInstance()->SetFrameIDForAXTreeID( |
| ui::AXTreeIDRegistry::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 main frame (e.g. |
| // the WebView needs to update the ID tracking its child accessibility tree). |
| if (is_main_frame()) |
| 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(); |
| } |
| |
| bool RenderFrameHostImpl::DocumentUsedWebOTP() { |
| return document_used_web_otp_; |
| } |
| |
| std::ostream& operator<<(std::ostream& o, |
| const RenderFrameHostImpl::LifecycleState& s) { |
| return o << LifecycleStateToString(s); |
| } |
| |
| } // namespace content |