blob: 7750c37f5f7028bf2565cb3a7fbd7c98b3fdccba [file] [log] [blame]
// 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