|  | // 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_origin_ = static_cast<double>( | 
|  | blink::features::kSharedStorageSelectURLBitBudgetPerOriginPerPageLoad | 
|  | .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 url::Origin& origin, | 
|  | 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_origin_); | 
|  |  | 
|  | // Return false if the max bits per origin is set to a value smaller than the | 
|  | // current bits to charge. | 
|  | if (bits_to_charge > select_url_max_bits_per_origin_.value()) { | 
|  | return false; | 
|  | } | 
|  |  | 
|  | // Charge the per-origin budget or return false if there is not enough. | 
|  | auto it = select_url_per_origin_budget_.find(origin); | 
|  | if (it == select_url_per_origin_budget_.end()) { | 
|  | select_url_per_origin_budget_[origin] = | 
|  | select_url_max_bits_per_origin_.value() - bits_to_charge; | 
|  | } else if (bits_to_charge > it->second) { | 
|  | // There is insufficient per-origin 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 |