| // Copyright 2021 The Chromium Authors |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "content/browser/renderer_host/page_impl.h" |
| |
| #include "base/barrier_closure.h" |
| #include "base/feature_list.h" |
| #include "base/i18n/character_encoding.h" |
| #include "base/trace_event/optional_trace_event.h" |
| #include "cc/base/features.h" |
| #include "content/browser/manifest/manifest_manager_host.h" |
| #include "content/browser/renderer_host/frame_tree_node.h" |
| #include "content/browser/renderer_host/page_delegate.h" |
| #include "content/browser/renderer_host/render_frame_host_delegate.h" |
| #include "content/browser/renderer_host/render_frame_host_impl.h" |
| #include "content/browser/renderer_host/render_frame_proxy_host.h" |
| #include "content/browser/renderer_host/render_view_host_delegate.h" |
| #include "content/browser/renderer_host/render_view_host_impl.h" |
| #include "content/public/browser/render_view_host.h" |
| #include "third_party/blink/public/common/features.h" |
| #include "third_party/blink/public/common/loader/loader_constants.h" |
| #include "third_party/blink/public/mojom/manifest/manifest.mojom.h" |
| #include "third_party/perfetto/include/perfetto/tracing/traced_value.h" |
| |
| namespace content { |
| |
| PageImpl::PageImpl(RenderFrameHostImpl& rfh, PageDelegate& delegate) |
| : main_document_(rfh), |
| delegate_(delegate), |
| text_autosizer_page_info_({0, 0, 1.f}) { |
| if (base::FeatureList::IsEnabled( |
| blink::features::kSharedStorageSelectURLLimit)) { |
| select_url_overall_budget_ = static_cast<double>( |
| blink::features::kSharedStorageSelectURLBitBudgetPerPageLoad.Get()); |
| select_url_max_bits_per_site_ = static_cast<double>( |
| blink::features::kSharedStorageSelectURLBitBudgetPerSitePerPageLoad |
| .Get()); |
| } |
| } |
| |
| PageImpl::~PageImpl() { |
| // As SupportsUserData is a base class of PageImpl, Page members will be |
| // destroyed before running ~SupportsUserData, which would delete the |
| // associated PageUserData objects. Avoid this by calling ClearAllUserData |
| // explicitly here to ensure that the PageUserData destructors can access |
| // associated Page object. |
| ClearAllUserData(); |
| } |
| |
| const absl::optional<GURL>& PageImpl::GetManifestUrl() const { |
| return manifest_url_; |
| } |
| |
| void PageImpl::GetManifest(GetManifestCallback callback) { |
| ManifestManagerHost* manifest_manager_host = |
| ManifestManagerHost::GetOrCreateForPage(*this); |
| manifest_manager_host->GetManifest(std::move(callback)); |
| } |
| |
| bool PageImpl::IsPrimary() const { |
| // TODO(1244137): Check for portals as well, once they are migrated to MPArch. |
| if (main_document_->IsFencedFrameRoot()) |
| return false; |
| |
| return main_document_->lifecycle_state() == |
| RenderFrameHostImpl::LifecycleStateImpl::kActive; |
| } |
| |
| void PageImpl::UpdateManifestUrl(const GURL& manifest_url) { |
| manifest_url_ = manifest_url; |
| |
| // If |main_document_| is not active, the notification is sent on the page |
| // activation. |
| if (!main_document_->IsActive()) |
| return; |
| |
| main_document_->delegate()->OnManifestUrlChanged(*this); |
| } |
| |
| void PageImpl::WriteIntoTrace(perfetto::TracedValue context) { |
| auto dict = std::move(context).WriteDictionary(); |
| dict.Add("main_document", *main_document_); |
| } |
| |
| base::WeakPtr<Page> PageImpl::GetWeakPtr() { |
| return weak_factory_.GetWeakPtr(); |
| } |
| |
| base::WeakPtr<PageImpl> PageImpl::GetWeakPtrImpl() { |
| return weak_factory_.GetWeakPtr(); |
| } |
| |
| bool PageImpl::IsPageScaleFactorOne() { |
| return GetPageScaleFactor() == 1.f; |
| } |
| |
| const std::string& PageImpl::GetContentsMimeType() const { |
| return contents_mime_type_; |
| } |
| |
| void PageImpl::OnFirstVisuallyNonEmptyPaint() { |
| did_first_visually_non_empty_paint_ = true; |
| delegate_->OnFirstVisuallyNonEmptyPaint(*this); |
| } |
| |
| void PageImpl::OnThemeColorChanged(const absl::optional<SkColor>& theme_color) { |
| main_document_theme_color_ = theme_color; |
| delegate_->OnThemeColorChanged(*this); |
| } |
| |
| void PageImpl::DidChangeBackgroundColor(SkColor4f background_color, |
| bool color_adjust) { |
| // TODO(aaronhk): This should remain an SkColor4f |
| main_document_background_color_ = background_color.toSkColor(); |
| delegate_->OnBackgroundColorChanged(*this); |
| if (color_adjust) { |
| // <meta name="color-scheme" content="dark"> may pass the dark canvas |
| // background before the first paint in order to avoid flashing the white |
| // background in between loading documents. If we perform a navigation |
| // within the same renderer process, we keep the content background from the |
| // previous page while rendering is blocked in the new page, but for cross |
| // process navigations we would paint the default background (typically |
| // white) while the rendering is blocked. |
| main_document_->GetRenderWidgetHost()->GetView()->SetContentBackgroundColor( |
| background_color.toSkColor()); |
| } |
| } |
| |
| void PageImpl::DidInferColorScheme( |
| blink::mojom::PreferredColorScheme color_scheme) { |
| main_document_inferred_color_scheme_ = color_scheme; |
| delegate_->DidInferColorScheme(*this); |
| } |
| |
| void PageImpl::NotifyPageBecameCurrent() { |
| if (!IsPrimary()) |
| return; |
| delegate_->NotifyPageBecamePrimary(*this); |
| } |
| |
| void PageImpl::SetContentsMimeType(std::string mime_type) { |
| contents_mime_type_ = std::move(mime_type); |
| } |
| |
| void PageImpl::OnTextAutosizerPageInfoChanged( |
| blink::mojom::TextAutosizerPageInfoPtr page_info) { |
| OPTIONAL_TRACE_EVENT0("content", "PageImpl::OnTextAutosizerPageInfoChanged"); |
| |
| // Keep a copy of `page_info` in case we create a new `blink::WebView` before |
| // the next update, so that the PageImpl can tell the newly created |
| // `blink::WebView` about the autosizer info. |
| text_autosizer_page_info_.main_frame_width = page_info->main_frame_width; |
| text_autosizer_page_info_.main_frame_layout_width = |
| page_info->main_frame_layout_width; |
| text_autosizer_page_info_.device_scale_adjustment = |
| page_info->device_scale_adjustment; |
| |
| auto remote_frames_broadcast_callback = base::BindRepeating( |
| [](const blink::mojom::TextAutosizerPageInfo& page_info, |
| RenderFrameProxyHost* proxy_host) { |
| DCHECK(proxy_host); |
| proxy_host->GetAssociatedRemoteMainFrame()->UpdateTextAutosizerPageInfo( |
| page_info.Clone()); |
| }, |
| text_autosizer_page_info_); |
| |
| main_document_->frame_tree() |
| ->root() |
| ->render_manager() |
| ->ExecuteRemoteFramesBroadcastMethod( |
| std::move(remote_frames_broadcast_callback), |
| main_document_->GetSiteInstance()->group()); |
| } |
| |
| void PageImpl::SetActivationStartTime(base::TimeTicks activation_start) { |
| DCHECK(!activation_start_time_for_prerendering_); |
| activation_start_time_for_prerendering_ = activation_start; |
| } |
| |
| void PageImpl::ActivateForPrerendering( |
| StoredPage::RenderViewHostImplSafeRefSet& render_view_hosts, |
| absl::optional<blink::ViewTransitionState> view_transition_state) { |
| TRACE_EVENT0("navigation", "PageImpl::ActivateForPrerendering"); |
| |
| base::OnceClosure did_activate_render_views = |
| base::BindOnce(&PageImpl::DidActivateAllRenderViewsForPrerendering, |
| weak_factory_.GetWeakPtr()); |
| |
| base::RepeatingClosure barrier = base::BarrierClosure( |
| render_view_hosts.size(), std::move(did_activate_render_views)); |
| for (const auto& rvh : render_view_hosts) { |
| auto params = blink::mojom::PrerenderPageActivationParams::New(); |
| |
| // Only send navigation_start to the RenderViewHost for the main frame to |
| // avoid sending the info cross-origin. Only this RenderViewHost needs the |
| // info, as we expect the other RenderViewHosts are made for cross-origin |
| // iframes which have not yet loaded their document. To the renderer, it |
| // just looks like an ongoing navigation is happening in the frame and has |
| // not yet committed. These RenderViews still need to know about activation |
| // so their documents are created in the non-prerendered state once their |
| // navigation is committed. |
| if (main_document_->GetRenderViewHost() == &*rvh) { |
| params->activation_start = *activation_start_time_for_prerendering_; |
| params->view_transition_state = std::move(view_transition_state); |
| } |
| |
| params->was_user_activated = |
| main_document_->frame_tree_node() |
| ->has_received_user_gesture_before_nav() |
| ? blink::mojom::WasActivatedOption::kYes |
| : blink::mojom::WasActivatedOption::kNo; |
| rvh->ActivatePrerenderedPage(std::move(params), barrier); |
| } |
| |
| // Prepare each RenderFrameHostImpl in this Page for activation. |
| // TODO(https://crbug.com/1232528): Currently we check GetPage() below because |
| // RenderFrameHostImpls may be in a different Page, if, e.g., they are in an |
| // inner WebContents. These are in a different FrameTree which might not know |
| // it is being prerendered. We should teach these FrameTrees that they are |
| // being prerendered, or ban inner FrameTrees in a prerendering page. |
| main_document_->ForEachRenderFrameHostIncludingSpeculative( |
| [this](RenderFrameHostImpl* rfh) { |
| if (&rfh->GetPage() != this) |
| return; |
| rfh->RendererWillActivateForPrerendering(); |
| }); |
| } |
| |
| void PageImpl::MaybeDispatchLoadEventsOnPrerenderActivation() { |
| DCHECK(IsPrimary()); |
| |
| // Dispatch LoadProgressChanged notification on activation with the |
| // prerender last load progress value if the value is not equal to |
| // blink::kFinalLoadProgress, whose notification is dispatched during call |
| // to DidStopLoading. |
| if (load_progress() != blink::kFinalLoadProgress) |
| main_document_->DidChangeLoadProgress(load_progress()); |
| |
| // Dispatch PrimaryMainDocumentElementAvailable before dispatching following |
| // load complete events. |
| if (is_main_document_element_available()) |
| main_document_->MainDocumentElementAvailable(uses_temporary_zoom_level()); |
| |
| main_document_->ForEachRenderFrameHost( |
| &RenderFrameHostImpl::MaybeDispatchDOMContentLoadedOnPrerenderActivation); |
| |
| if (is_on_load_completed_in_main_document()) |
| main_document_->DocumentOnLoadCompleted(); |
| |
| main_document_->ForEachRenderFrameHost( |
| &RenderFrameHostImpl::MaybeDispatchDidFinishLoadOnPrerenderActivation); |
| } |
| |
| void PageImpl::DidActivateAllRenderViewsForPrerendering() { |
| TRACE_EVENT0("navigation", |
| "PageImpl::DidActivateAllRenderViewsForPrerendering"); |
| |
| // Tell each RenderFrameHostImpl in this Page that activation finished. |
| main_document_->ForEachRenderFrameHostIncludingSpeculative( |
| [this](RenderFrameHostImpl* rfh) { |
| if (&rfh->GetPage() != this) { |
| return; |
| } |
| rfh->RendererDidActivateForPrerendering(); |
| }); |
| } |
| |
| RenderFrameHost& PageImpl::GetMainDocumentHelper() { |
| return *main_document_; |
| } |
| |
| RenderFrameHostImpl& PageImpl::GetMainDocument() const { |
| return *main_document_; |
| } |
| |
| void PageImpl::UpdateBrowserControlsState(cc::BrowserControlsState constraints, |
| cc::BrowserControlsState current, |
| bool animate) { |
| // TODO(https://crbug.com/1154852): Asking for the LocalMainFrame interface |
| // before the RenderFrame is created is racy. |
| if (!GetMainDocument().IsRenderFrameLive()) |
| return; |
| |
| if (base::FeatureList::IsEnabled( |
| features::kUpdateBrowserControlsWithoutProxy)) { |
| GetMainDocument().GetRenderWidgetHost()->UpdateBrowserControlsState( |
| constraints, current, animate); |
| } else { |
| GetMainDocument().GetAssociatedLocalMainFrame()->UpdateBrowserControlsState( |
| constraints, current, animate); |
| } |
| } |
| |
| float PageImpl::GetPageScaleFactor() const { |
| return GetMainDocument().GetPageScaleFactor(); |
| } |
| |
| void PageImpl::UpdateEncoding(const std::string& encoding_name) { |
| if (encoding_name == last_reported_encoding_) |
| return; |
| last_reported_encoding_ = encoding_name; |
| |
| canonical_encoding_ = |
| base::GetCanonicalEncodingNameByAliasName(encoding_name); |
| } |
| |
| void PageImpl::NotifyVirtualKeyboardOverlayRect( |
| const gfx::Rect& keyboard_rect) { |
| // TODO(https://crbug.com/1317002): send notification to outer frames if |
| // needed. |
| DCHECK_EQ(virtual_keyboard_mode(), |
| ui::mojom::VirtualKeyboardMode::kOverlaysContent); |
| GetMainDocument().GetAssociatedLocalFrame()->NotifyVirtualKeyboardOverlayRect( |
| keyboard_rect); |
| } |
| |
| void PageImpl::SetVirtualKeyboardMode(ui::mojom::VirtualKeyboardMode mode) { |
| if (virtual_keyboard_mode_ == mode) |
| return; |
| |
| virtual_keyboard_mode_ = mode; |
| |
| delegate_->OnVirtualKeyboardModeChanged(*this); |
| } |
| |
| base::flat_map<std::string, std::string> PageImpl::GetKeyboardLayoutMap() { |
| return GetMainDocument().GetRenderWidgetHost()->GetKeyboardLayoutMap(); |
| } |
| |
| bool PageImpl::CheckAndMaybeDebitSelectURLBudgets( |
| const net::SchemefulSite& site, |
| double bits_to_charge) { |
| if (!select_url_overall_budget_) { |
| // The limits are not enabled. |
| return true; |
| } |
| |
| // Return false if there is insufficient overall budget. |
| if (bits_to_charge > select_url_overall_budget_.value()) { |
| return false; |
| } |
| |
| DCHECK(select_url_max_bits_per_site_); |
| |
| // Return false if the max bits per site is set to a value smaller than the |
| // current bits to charge. |
| if (bits_to_charge > select_url_max_bits_per_site_.value()) { |
| return false; |
| } |
| |
| // Charge the per-site budget or return false if there is not enough. |
| auto it = select_url_per_site_budget_.find(site); |
| if (it == select_url_per_site_budget_.end()) { |
| select_url_per_site_budget_[site] = |
| select_url_max_bits_per_site_.value() - bits_to_charge; |
| } else if (bits_to_charge > it->second) { |
| // There is insufficient per-site budget remaining. |
| return false; |
| } else { |
| it->second -= bits_to_charge; |
| } |
| |
| // Charge the overall budget. |
| select_url_overall_budget_.value() -= bits_to_charge; |
| return true; |
| } |
| |
| } // namespace content |