Avi Drissman | 4e1b7bc3 | 2022-09-15 14:03:50 | [diff] [blame] | 1 | // Copyright 2021 The Chromium Authors |
Sreeja Kamishetty | 9e1d0e73 | 2021-05-27 18:20:09 | [diff] [blame] | 2 | // Use of this source code is governed by a BSD-style license that can be |
| 3 | // found in the LICENSE file. |
| 4 | |
| 5 | #include "content/browser/renderer_host/page_impl.h" |
| 6 | |
Matt Falkenhagen | f78c219 | 2021-07-24 02:01:43 | [diff] [blame] | 7 | #include "base/barrier_closure.h" |
Sky Malice | 3774b00 | 2022-10-21 19:18:40 | [diff] [blame] | 8 | #include "base/feature_list.h" |
Dominic Farolino | 5c606c1 | 2021-12-18 09:40:14 | [diff] [blame] | 9 | #include "base/i18n/character_encoding.h" |
Lingqi Chi | dcf72244 | 2021-09-02 01:47:19 | [diff] [blame] | 10 | #include "base/trace_event/optional_trace_event.h" |
Sky Malice | 3774b00 | 2022-10-21 19:18:40 | [diff] [blame] | 11 | #include "cc/base/features.h" |
Peilin Wang | 1187e4e8 | 2025-02-10 08:53:29 | [diff] [blame] | 12 | #include "cc/input/browser_controls_offset_tag_modifications.h" |
Jeremy Roman | 4bd173d | 2021-06-17 00:05:44 | [diff] [blame] | 13 | #include "content/browser/manifest/manifest_manager_host.h" |
Dave Tapuska | 9c9afe8 | 2021-06-22 19:07:45 | [diff] [blame] | 14 | #include "content/browser/renderer_host/frame_tree_node.h" |
Jeremy Roman | 2d8dfe13 | 2021-07-06 20:51:26 | [diff] [blame] | 15 | #include "content/browser/renderer_host/page_delegate.h" |
Julie Jeongeun Kim | 9e20451 | 2021-06-24 07:28:54 | [diff] [blame] | 16 | #include "content/browser/renderer_host/render_frame_host_delegate.h" |
Sreeja Kamishetty | 9e1d0e73 | 2021-05-27 18:20:09 | [diff] [blame] | 17 | #include "content/browser/renderer_host/render_frame_host_impl.h" |
Lingqi Chi | dcf72244 | 2021-09-02 01:47:19 | [diff] [blame] | 18 | #include "content/browser/renderer_host/render_frame_proxy_host.h" |
Jeremy Roman | 2d8dfe13 | 2021-07-06 20:51:26 | [diff] [blame] | 19 | #include "content/browser/renderer_host/render_view_host_delegate.h" |
Matt Falkenhagen | f78c219 | 2021-07-24 02:01:43 | [diff] [blame] | 20 | #include "content/browser/renderer_host/render_view_host_impl.h" |
Takashi Toyoshima | 41afbbc | 2024-11-12 00:31:12 | [diff] [blame] | 21 | #include "content/browser/shared_storage/shared_storage_features.h" |
Camillia Smith Barnes | 385a2e9 | 2024-04-16 17:49:40 | [diff] [blame] | 22 | #include "content/public/browser/content_browser_client.h" |
Aman Verma | 39fe049 | 2024-06-17 14:07:01 | [diff] [blame] | 23 | #include "content/public/browser/peak_gpu_memory_tracker_factory.h" |
Jeremy Roman | 2d8dfe13 | 2021-07-06 20:51:26 | [diff] [blame] | 24 | #include "content/public/browser/render_view_host.h" |
Camillia Smith Barnes | 385a2e9 | 2024-04-16 17:49:40 | [diff] [blame] | 25 | #include "content/public/common/content_client.h" |
Peilin Wang | ae08e26e | 2024-06-10 20:55:30 | [diff] [blame] | 26 | #include "services/viz/public/mojom/compositing/offset_tag.mojom.h" |
Camillia Smith Barnes | ddaf5b1 | 2023-01-24 00:06:32 | [diff] [blame] | 27 | #include "third_party/blink/public/common/features.h" |
Sreeja Kamishetty | 0be3b1b | 2021-08-12 17:04:15 | [diff] [blame] | 28 | #include "third_party/blink/public/common/loader/loader_constants.h" |
Camillia Smith Barnes | 385a2e9 | 2024-04-16 17:49:40 | [diff] [blame] | 29 | #include "third_party/blink/public/common/shared_storage/shared_storage_utils.h" |
Julie Jeongeun Kim | 33ef6a2 | 2022-03-22 09:46:11 | [diff] [blame] | 30 | #include "third_party/blink/public/mojom/manifest/manifest.mojom.h" |
Camillia Smith Barnes | 385a2e9 | 2024-04-16 17:49:40 | [diff] [blame] | 31 | #include "third_party/blink/public/mojom/use_counter/metrics/web_feature.mojom.h" |
Jeremy Roman | 2d8dfe13 | 2021-07-06 20:51:26 | [diff] [blame] | 32 | #include "third_party/perfetto/include/perfetto/tracing/traced_value.h" |
Sreeja Kamishetty | 9e1d0e73 | 2021-05-27 18:20:09 | [diff] [blame] | 33 | |
| 34 | namespace content { |
Jeremy Roman | 2d8dfe13 | 2021-07-06 20:51:26 | [diff] [blame] | 35 | |
| 36 | PageImpl::PageImpl(RenderFrameHostImpl& rfh, PageDelegate& delegate) |
Lingqi Chi | dcf72244 | 2021-09-02 01:47:19 | [diff] [blame] | 37 | : main_document_(rfh), |
| 38 | delegate_(delegate), |
Camillia Smith Barnes | ddaf5b1 | 2023-01-24 00:06:32 | [diff] [blame] | 39 | text_autosizer_page_info_({0, 0, 1.f}) { |
Takashi Toyoshima | 41afbbc | 2024-11-12 00:31:12 | [diff] [blame] | 40 | if (base::FeatureList::IsEnabled(features::kSharedStorageSelectURLLimit)) { |
| 41 | select_url_overall_budget_ = |
| 42 | features::kSharedStorageSelectURLBitBudgetPerPageLoad.Get(); |
| 43 | select_url_max_bits_per_site_ = |
| 44 | features::kSharedStorageSelectURLBitBudgetPerSitePerPageLoad.Get(); |
Camillia Smith Barnes | 477a311 | 2023-02-28 19:06:30 | [diff] [blame] | 45 | } |
Rakina Zata Amni | 65a5dc4 | 2025-03-03 02:51:55 | [diff] [blame] | 46 | |
| 47 | #if BUILDFLAG(IS_ANDROID) |
| 48 | page_proxy_ = std::make_unique<PageProxy>(this); |
| 49 | #endif |
Camillia Smith Barnes | ddaf5b1 | 2023-01-24 00:06:32 | [diff] [blame] | 50 | } |
Sreeja Kamishetty | 9e1d0e73 | 2021-05-27 18:20:09 | [diff] [blame] | 51 | |
Sreeja Kamishetty | 1b5c143 | 2021-06-25 11:32:59 | [diff] [blame] | 52 | PageImpl::~PageImpl() { |
Rakina Zata Amni | 65a5dc4 | 2025-03-03 02:51:55 | [diff] [blame] | 53 | #if BUILDFLAG(IS_ANDROID) |
| 54 | page_proxy_->WillDeletePage(GetMainDocument().IsInLifecycleState( |
| 55 | RenderFrameHost::LifecycleState::kPrerendering)); |
| 56 | #endif |
| 57 | |
Sreeja Kamishetty | 1b5c143 | 2021-06-25 11:32:59 | [diff] [blame] | 58 | // As SupportsUserData is a base class of PageImpl, Page members will be |
| 59 | // destroyed before running ~SupportsUserData, which would delete the |
| 60 | // associated PageUserData objects. Avoid this by calling ClearAllUserData |
| 61 | // explicitly here to ensure that the PageUserData destructors can access |
| 62 | // associated Page object. |
| 63 | ClearAllUserData(); |
Adithya Srinivasan | a5795b9 | 2023-12-20 22:35:22 | [diff] [blame] | 64 | |
| 65 | // If we still have a PeakGpuMemoryTracker, then the loading it was observing |
| 66 | // never completed. Cancel its callback so that we don't report partial |
| 67 | // loads to UMA. |
| 68 | CancelLoadingMemoryTracker(); |
Sreeja Kamishetty | 1b5c143 | 2021-06-25 11:32:59 | [diff] [blame] | 69 | } |
Sreeja Kamishetty | 9e1d0e73 | 2021-05-27 18:20:09 | [diff] [blame] | 70 | |
Arthur Sonzogni | c686e8f | 2024-01-11 08:36:37 | [diff] [blame] | 71 | const std::optional<GURL>& PageImpl::GetManifestUrl() const { |
Sreeja Kamishetty | 7c91ab2 | 2021-06-03 13:29:52 | [diff] [blame] | 72 | return manifest_url_; |
| 73 | } |
| 74 | |
Jeremy Roman | 4bd173d | 2021-06-17 00:05:44 | [diff] [blame] | 75 | void PageImpl::GetManifest(GetManifestCallback callback) { |
| 76 | ManifestManagerHost* manifest_manager_host = |
Julie Jeongeun Kim | 33ef6a2 | 2022-03-22 09:46:11 | [diff] [blame] | 77 | ManifestManagerHost::GetOrCreateForPage(*this); |
Jeremy Roman | 4bd173d | 2021-06-17 00:05:44 | [diff] [blame] | 78 | manifest_manager_host->GetManifest(std::move(callback)); |
| 79 | } |
| 80 | |
Julie Jeongeun Kim | da52992 | 2023-01-13 02:59:59 | [diff] [blame] | 81 | bool PageImpl::IsPrimary() const { |
Kevin McNee | f1b0f0b | 2024-09-17 21:49:41 | [diff] [blame] | 82 | return main_document_->IsInPrimaryMainFrame(); |
Dave Tapuska | 9c9afe8 | 2021-06-22 19:07:45 | [diff] [blame] | 83 | } |
| 84 | |
Julie Jeongeun Kim | 9e20451 | 2021-06-24 07:28:54 | [diff] [blame] | 85 | void PageImpl::UpdateManifestUrl(const GURL& manifest_url) { |
| 86 | manifest_url_ = manifest_url; |
| 87 | |
| 88 | // If |main_document_| is not active, the notification is sent on the page |
| 89 | // activation. |
Takashi Toyoshima | 41afbbc | 2024-11-12 00:31:12 | [diff] [blame] | 90 | if (!main_document_->IsActive()) { |
Julie Jeongeun Kim | 9e20451 | 2021-06-24 07:28:54 | [diff] [blame] | 91 | return; |
Takashi Toyoshima | 41afbbc | 2024-11-12 00:31:12 | [diff] [blame] | 92 | } |
Julie Jeongeun Kim | 9e20451 | 2021-06-24 07:28:54 | [diff] [blame] | 93 | |
Ali Hijazi | d87307d | 2022-11-07 20:15:03 | [diff] [blame] | 94 | main_document_->delegate()->OnManifestUrlChanged(*this); |
Julie Jeongeun Kim | 9e20451 | 2021-06-24 07:28:54 | [diff] [blame] | 95 | } |
| 96 | |
Jeremy Roman | 2d8dfe13 | 2021-07-06 20:51:26 | [diff] [blame] | 97 | void PageImpl::WriteIntoTrace(perfetto::TracedValue context) { |
| 98 | auto dict = std::move(context).WriteDictionary(); |
Ali Hijazi | d87307d | 2022-11-07 20:15:03 | [diff] [blame] | 99 | dict.Add("main_document", *main_document_); |
Jeremy Roman | 2d8dfe13 | 2021-07-06 20:51:26 | [diff] [blame] | 100 | } |
| 101 | |
Miyoung Shin | fa182e47 | 2021-09-03 12:39:32 | [diff] [blame] | 102 | base::WeakPtr<Page> PageImpl::GetWeakPtr() { |
| 103 | return weak_factory_.GetWeakPtr(); |
| 104 | } |
| 105 | |
Yao Xiao | c722436 | 2022-02-16 08:21:40 | [diff] [blame] | 106 | base::WeakPtr<PageImpl> PageImpl::GetWeakPtrImpl() { |
| 107 | return weak_factory_.GetWeakPtr(); |
| 108 | } |
| 109 | |
Kevin McNee | 3183a779 | 2021-11-09 21:03:36 | [diff] [blame] | 110 | bool PageImpl::IsPageScaleFactorOne() { |
Kevin McNee | c4325ba | 2022-04-08 23:18:23 | [diff] [blame] | 111 | return GetPageScaleFactor() == 1.f; |
Kevin McNee | 3183a779 | 2021-11-09 21:03:36 | [diff] [blame] | 112 | } |
| 113 | |
Takashi Toyoshima | 6c58bbd | 2023-05-19 09:41:35 | [diff] [blame] | 114 | const std::string& PageImpl::GetContentsMimeType() const { |
| 115 | return contents_mime_type_; |
| 116 | } |
| 117 | |
Arthur Sonzogni | c686e8f | 2024-01-11 08:36:37 | [diff] [blame] | 118 | void PageImpl::SetResizableForTesting(std::optional<bool> resizable) { |
Sonja | 5f1ab74 | 2023-11-09 14:48:36 | [diff] [blame] | 119 | SetResizable(resizable); |
| 120 | } |
| 121 | |
Arthur Sonzogni | c686e8f | 2024-01-11 08:36:37 | [diff] [blame] | 122 | void PageImpl::SetResizable(std::optional<bool> resizable) { |
Sonja | 5f1ab74 | 2023-11-09 14:48:36 | [diff] [blame] | 123 | resizable_ = resizable; |
Robert Ferens | 9610d728 | 2025-02-17 09:52:32 | [diff] [blame] | 124 | delegate_->OnWebApiWindowResizableChanged(); |
Sonja | 5f1ab74 | 2023-11-09 14:48:36 | [diff] [blame] | 125 | } |
| 126 | |
Arthur Sonzogni | c686e8f | 2024-01-11 08:36:37 | [diff] [blame] | 127 | std::optional<bool> PageImpl::GetResizable() { |
Sonja | 5f1ab74 | 2023-11-09 14:48:36 | [diff] [blame] | 128 | return resizable_; |
| 129 | } |
| 130 | |
Rakina Zata Amni | 65a5dc4 | 2025-03-03 02:51:55 | [diff] [blame] | 131 | #if BUILDFLAG(IS_ANDROID) |
| 132 | const base::android::JavaRef<jobject>& PageImpl::GetJavaPage() { |
| 133 | return page_proxy_->java_page(); |
| 134 | } |
| 135 | #endif |
| 136 | |
Jeremy Roman | 2d8dfe13 | 2021-07-06 20:51:26 | [diff] [blame] | 137 | void PageImpl::OnFirstVisuallyNonEmptyPaint() { |
| 138 | did_first_visually_non_empty_paint_ = true; |
Ali Hijazi | d87307d | 2022-11-07 20:15:03 | [diff] [blame] | 139 | delegate_->OnFirstVisuallyNonEmptyPaint(*this); |
Jeremy Roman | 2d8dfe13 | 2021-07-06 20:51:26 | [diff] [blame] | 140 | } |
| 141 | |
Arthur Sonzogni | c686e8f | 2024-01-11 08:36:37 | [diff] [blame] | 142 | void PageImpl::OnThemeColorChanged(const std::optional<SkColor>& theme_color) { |
Jeremy Roman | 2d8dfe13 | 2021-07-06 20:51:26 | [diff] [blame] | 143 | main_document_theme_color_ = theme_color; |
Ali Hijazi | d87307d | 2022-11-07 20:15:03 | [diff] [blame] | 144 | delegate_->OnThemeColorChanged(*this); |
Jeremy Roman | 2d8dfe13 | 2021-07-06 20:51:26 | [diff] [blame] | 145 | } |
| 146 | |
Aaron Krajeski | 628c58c | 2023-04-04 16:24:12 | [diff] [blame] | 147 | void PageImpl::DidChangeBackgroundColor(SkColor4f background_color, |
Jeremy Roman | 2d8dfe13 | 2021-07-06 20:51:26 | [diff] [blame] | 148 | bool color_adjust) { |
Aaron Krajeski | 628c58c | 2023-04-04 16:24:12 | [diff] [blame] | 149 | // TODO(aaronhk): This should remain an SkColor4f |
| 150 | main_document_background_color_ = background_color.toSkColor(); |
Ali Hijazi | d87307d | 2022-11-07 20:15:03 | [diff] [blame] | 151 | delegate_->OnBackgroundColorChanged(*this); |
Jeremy Roman | 2d8dfe13 | 2021-07-06 20:51:26 | [diff] [blame] | 152 | if (color_adjust) { |
| 153 | // <meta name="color-scheme" content="dark"> may pass the dark canvas |
| 154 | // background before the first paint in order to avoid flashing the white |
| 155 | // background in between loading documents. If we perform a navigation |
| 156 | // within the same renderer process, we keep the content background from the |
| 157 | // previous page while rendering is blocked in the new page, but for cross |
| 158 | // process navigations we would paint the default background (typically |
| 159 | // white) while the rendering is blocked. |
Ali Hijazi | d87307d | 2022-11-07 20:15:03 | [diff] [blame] | 160 | main_document_->GetRenderWidgetHost()->GetView()->SetContentBackgroundColor( |
Aaron Krajeski | 628c58c | 2023-04-04 16:24:12 | [diff] [blame] | 161 | background_color.toSkColor()); |
Jeremy Roman | 2d8dfe13 | 2021-07-06 20:51:26 | [diff] [blame] | 162 | } |
| 163 | } |
| 164 | |
Michael Bai | 19f17a30 | 2021-12-08 04:08:33 | [diff] [blame] | 165 | void PageImpl::DidInferColorScheme( |
| 166 | blink::mojom::PreferredColorScheme color_scheme) { |
| 167 | main_document_inferred_color_scheme_ = color_scheme; |
Ali Hijazi | d87307d | 2022-11-07 20:15:03 | [diff] [blame] | 168 | delegate_->DidInferColorScheme(*this); |
Michael Bai | 19f17a30 | 2021-12-08 04:08:33 | [diff] [blame] | 169 | } |
| 170 | |
Julie Jeongeun Kim | d4597df1 | 2022-11-11 02:44:51 | [diff] [blame] | 171 | void PageImpl::NotifyPageBecameCurrent() { |
Takashi Toyoshima | 41afbbc | 2024-11-12 00:31:12 | [diff] [blame] | 172 | if (!IsPrimary()) { |
Julie Jeongeun Kim | d4597df1 | 2022-11-11 02:44:51 | [diff] [blame] | 173 | return; |
Takashi Toyoshima | 41afbbc | 2024-11-12 00:31:12 | [diff] [blame] | 174 | } |
Julie Jeongeun Kim | d4597df1 | 2022-11-11 02:44:51 | [diff] [blame] | 175 | delegate_->NotifyPageBecamePrimary(*this); |
| 176 | } |
| 177 | |
Jeremy Roman | 2d8dfe13 | 2021-07-06 20:51:26 | [diff] [blame] | 178 | void PageImpl::SetContentsMimeType(std::string mime_type) { |
| 179 | contents_mime_type_ = std::move(mime_type); |
| 180 | } |
| 181 | |
Lingqi Chi | dcf72244 | 2021-09-02 01:47:19 | [diff] [blame] | 182 | void PageImpl::OnTextAutosizerPageInfoChanged( |
| 183 | blink::mojom::TextAutosizerPageInfoPtr page_info) { |
| 184 | OPTIONAL_TRACE_EVENT0("content", "PageImpl::OnTextAutosizerPageInfoChanged"); |
| 185 | |
Dave Tapuska | 2cf1f53 | 2022-08-10 15:30:49 | [diff] [blame] | 186 | // Keep a copy of `page_info` in case we create a new `blink::WebView` before |
| 187 | // the next update, so that the PageImpl can tell the newly created |
| 188 | // `blink::WebView` about the autosizer info. |
Lingqi Chi | dcf72244 | 2021-09-02 01:47:19 | [diff] [blame] | 189 | text_autosizer_page_info_.main_frame_width = page_info->main_frame_width; |
| 190 | text_autosizer_page_info_.main_frame_layout_width = |
| 191 | page_info->main_frame_layout_width; |
| 192 | text_autosizer_page_info_.device_scale_adjustment = |
| 193 | page_info->device_scale_adjustment; |
| 194 | |
Kevin McNee | 7705fe8 | 2024-11-07 18:56:31 | [diff] [blame] | 195 | auto remote_frames_broadcast_callback = |
| 196 | [this](RenderFrameProxyHost* proxy_host) { |
Lingqi Chi | dcf72244 | 2021-09-02 01:47:19 | [diff] [blame] | 197 | DCHECK(proxy_host); |
| 198 | proxy_host->GetAssociatedRemoteMainFrame()->UpdateTextAutosizerPageInfo( |
Kevin McNee | 7705fe8 | 2024-11-07 18:56:31 | [diff] [blame] | 199 | text_autosizer_page_info_.Clone()); |
| 200 | }; |
Lingqi Chi | dcf72244 | 2021-09-02 01:47:19 | [diff] [blame] | 201 | |
Charlie Reis | 0650ad0d | 2024-12-06 18:12:35 | [diff] [blame] | 202 | { |
| 203 | TRACE_EVENT("navigation", |
| 204 | "PageImpl::OnTextAutosizerPageInfoChanged broadcast"); |
| 205 | main_document_->frame_tree() |
| 206 | ->root() |
| 207 | ->render_manager() |
| 208 | ->ExecuteRemoteFramesBroadcastMethod( |
| 209 | std::move(remote_frames_broadcast_callback), |
| 210 | main_document_->GetSiteInstance()->group()); |
| 211 | } |
Lingqi Chi | dcf72244 | 2021-09-02 01:47:19 | [diff] [blame] | 212 | } |
| 213 | |
Matt Falkenhagen | f78c219 | 2021-07-24 02:01:43 | [diff] [blame] | 214 | void PageImpl::SetActivationStartTime(base::TimeTicks activation_start) { |
Takashi Toyoshima | 881f4d7 | 2023-11-09 05:07:43 | [diff] [blame] | 215 | CHECK(!activation_start_time_); |
| 216 | activation_start_time_ = activation_start; |
Matt Falkenhagen | f78c219 | 2021-07-24 02:01:43 | [diff] [blame] | 217 | } |
| 218 | |
Takashi Toyoshima | 881f4d7 | 2023-11-09 05:07:43 | [diff] [blame] | 219 | void PageImpl::Activate( |
| 220 | ActivationType type, |
Vladimir Levin | 48d5100 | 2023-02-27 17:23:27 | [diff] [blame] | 221 | StoredPage::RenderViewHostImplSafeRefSet& render_view_hosts, |
Arthur Sonzogni | c686e8f | 2024-01-11 08:36:37 | [diff] [blame] | 222 | std::optional<blink::ViewTransitionState> view_transition_state, |
Takashi Toyoshima | 881f4d7 | 2023-11-09 05:07:43 | [diff] [blame] | 223 | base::OnceCallback<void(base::TimeTicks)> completion_callback) { |
| 224 | TRACE_EVENT1("navigation", "PageImpl::Activate", "activation_type", type); |
Lingqi Chi | ef8657c | 2023-06-08 08:40:31 | [diff] [blame] | 225 | |
Takashi Toyoshima | 881f4d7 | 2023-11-09 05:07:43 | [diff] [blame] | 226 | // SetActivationStartTime() should be called first as the value is used in |
| 227 | // the callback below. |
| 228 | CHECK(activation_start_time_.has_value()); |
| 229 | |
| 230 | base::OnceClosure did_activate_render_views = base::BindOnce( |
| 231 | &PageImpl::DidActivateAllRenderViewsForPrerenderingOrPreview, |
| 232 | weak_factory_.GetWeakPtr(), std::move(completion_callback)); |
Matt Falkenhagen | f78c219 | 2021-07-24 02:01:43 | [diff] [blame] | 233 | |
| 234 | base::RepeatingClosure barrier = base::BarrierClosure( |
| 235 | render_view_hosts.size(), std::move(did_activate_render_views)); |
Takashi Toyoshima | 881f4d7 | 2023-11-09 05:07:43 | [diff] [blame] | 236 | bool view_transition_state_consumed = false; |
Dave Tapuska | c3e5835 | 2022-09-28 19:05:27 | [diff] [blame] | 237 | for (const auto& rvh : render_view_hosts) { |
Vladimir Levin | 48d5100 | 2023-02-27 17:23:27 | [diff] [blame] | 238 | auto params = blink::mojom::PrerenderPageActivationParams::New(); |
| 239 | |
Vladimir Levin | 48d5100 | 2023-02-27 17:23:27 | [diff] [blame] | 240 | if (main_document_->GetRenderViewHost() == &*rvh) { |
Takashi Toyoshima | 881f4d7 | 2023-11-09 05:07:43 | [diff] [blame] | 241 | // For prerendering activation, send activation_start only to the |
| 242 | // RenderViewHost for the main frame to avoid sending the info |
| 243 | // cross-origin. Only this RenderViewHost needs the info, as we expect the |
| 244 | // other RenderViewHosts are made for cross-origin iframes which have not |
| 245 | // yet loaded their document. To the renderer, it just looks like an |
| 246 | // ongoing navigation is happening in the frame and has not yet committed. |
| 247 | // These RenderViews still need to know about activation so their |
| 248 | // documents are created in the non-prerendered state once their |
| 249 | // navigation is committed. |
| 250 | params->activation_start = *activation_start_time_; |
| 251 | // Note that there cannot be a use-after-move since the if condition |
| 252 | // should be true at most once. |
| 253 | CHECK(!view_transition_state_consumed); |
Vladimir Levin | 48d5100 | 2023-02-27 17:23:27 | [diff] [blame] | 254 | params->view_transition_state = std::move(view_transition_state); |
Takashi Toyoshima | 881f4d7 | 2023-11-09 05:07:43 | [diff] [blame] | 255 | view_transition_state_consumed = true; |
| 256 | } else if (type == ActivationType::kPreview) { |
| 257 | // For preview activation, send activation_start to all RenderViewHosts |
| 258 | // as preview loads cross-origin subframes under the capability control, |
| 259 | // and activation_start time is meaningful there. |
| 260 | params->activation_start = *activation_start_time_; |
Vladimir Levin | 48d5100 | 2023-02-27 17:23:27 | [diff] [blame] | 261 | } |
Matt Falkenhagen | f78c219 | 2021-07-24 02:01:43 | [diff] [blame] | 262 | |
Takashi Toyoshima | 881f4d7 | 2023-11-09 05:07:43 | [diff] [blame] | 263 | // For preview activation, there is no way to activate the previewed page |
| 264 | // other than with a user action, or testing only methods. |
Hiroki Nakagawa | ab53cd2 | 2022-04-13 19:18:02 | [diff] [blame] | 265 | params->was_user_activated = |
Takashi Toyoshima | 881f4d7 | 2023-11-09 05:07:43 | [diff] [blame] | 266 | (main_document_->frame_tree_node() |
| 267 | ->has_received_user_gesture_before_nav() || |
| 268 | type == ActivationType::kPreview) |
Hiroki Nakagawa | ab53cd2 | 2022-04-13 19:18:02 | [diff] [blame] | 269 | ? blink::mojom::WasActivatedOption::kYes |
| 270 | : blink::mojom::WasActivatedOption::kNo; |
Hiroki Nakagawa | ab53cd2 | 2022-04-13 19:18:02 | [diff] [blame] | 271 | rvh->ActivatePrerenderedPage(std::move(params), barrier); |
Matt Falkenhagen | f78c219 | 2021-07-24 02:01:43 | [diff] [blame] | 272 | } |
| 273 | |
| 274 | // Prepare each RenderFrameHostImpl in this Page for activation. |
Peter Kasting | d2876a2 | 2024-12-03 01:12:55 | [diff] [blame] | 275 | main_document_->ForEachRenderFrameHostImplIncludingSpeculative( |
Kevin McNee | 4d35f5a | 2024-07-09 16:39:38 | [diff] [blame] | 276 | [](RenderFrameHostImpl* rfh) { |
Takashi Toyoshima | 881f4d7 | 2023-11-09 05:07:43 | [diff] [blame] | 277 | rfh->RendererWillActivateForPrerenderingOrPreview(); |
Daniel Cheng | 982f2b2 | 2022-08-25 23:46:16 | [diff] [blame] | 278 | }); |
Matt Falkenhagen | f78c219 | 2021-07-24 02:01:43 | [diff] [blame] | 279 | } |
| 280 | |
Sreeja Kamishetty | 81fbeefb | 2021-08-12 07:21:41 | [diff] [blame] | 281 | void PageImpl::MaybeDispatchLoadEventsOnPrerenderActivation() { |
| 282 | DCHECK(IsPrimary()); |
| 283 | |
Sreeja Kamishetty | 0be3b1b | 2021-08-12 17:04:15 | [diff] [blame] | 284 | // Dispatch LoadProgressChanged notification on activation with the |
| 285 | // prerender last load progress value if the value is not equal to |
| 286 | // blink::kFinalLoadProgress, whose notification is dispatched during call |
| 287 | // to DidStopLoading. |
Takashi Toyoshima | 41afbbc | 2024-11-12 00:31:12 | [diff] [blame] | 288 | if (load_progress() != blink::kFinalLoadProgress) { |
Ali Hijazi | d87307d | 2022-11-07 20:15:03 | [diff] [blame] | 289 | main_document_->DidChangeLoadProgress(load_progress()); |
Takashi Toyoshima | 41afbbc | 2024-11-12 00:31:12 | [diff] [blame] | 290 | } |
Sreeja Kamishetty | 0be3b1b | 2021-08-12 17:04:15 | [diff] [blame] | 291 | |
Sreeja Kamishetty | 4978330 | 2022-01-28 17:52:25 | [diff] [blame] | 292 | // Dispatch PrimaryMainDocumentElementAvailable before dispatching following |
| 293 | // load complete events. |
Takashi Toyoshima | 41afbbc | 2024-11-12 00:31:12 | [diff] [blame] | 294 | if (is_main_document_element_available()) { |
Ali Hijazi | d87307d | 2022-11-07 20:15:03 | [diff] [blame] | 295 | main_document_->MainDocumentElementAvailable(uses_temporary_zoom_level()); |
Takashi Toyoshima | 41afbbc | 2024-11-12 00:31:12 | [diff] [blame] | 296 | } |
Sreeja Kamishetty | cd556091 | 2021-11-22 11:54:53 | [diff] [blame] | 297 | |
Peter Kasting | d2876a2 | 2024-12-03 01:12:55 | [diff] [blame] | 298 | main_document_->ForEachRenderFrameHostImpl( |
Daniel Cheng | 982f2b2 | 2022-08-25 23:46:16 | [diff] [blame] | 299 | &RenderFrameHostImpl::MaybeDispatchDOMContentLoadedOnPrerenderActivation); |
Sreeja Kamishetty | 81fbeefb | 2021-08-12 07:21:41 | [diff] [blame] | 300 | |
Takashi Toyoshima | 41afbbc | 2024-11-12 00:31:12 | [diff] [blame] | 301 | if (is_on_load_completed_in_main_document()) { |
Ali Hijazi | d87307d | 2022-11-07 20:15:03 | [diff] [blame] | 302 | main_document_->DocumentOnLoadCompleted(); |
Takashi Toyoshima | 41afbbc | 2024-11-12 00:31:12 | [diff] [blame] | 303 | } |
Sreeja Kamishetty | 81fbeefb | 2021-08-12 07:21:41 | [diff] [blame] | 304 | |
Rakina Zata Amni | 2620dcb | 2024-11-13 01:41:58 | [diff] [blame] | 305 | if (did_first_contentful_paint_in_main_document()) { |
| 306 | main_document_->OnFirstContentfulPaint(); |
| 307 | } |
| 308 | |
Peter Kasting | d2876a2 | 2024-12-03 01:12:55 | [diff] [blame] | 309 | main_document_->ForEachRenderFrameHostImpl( |
Daniel Cheng | 982f2b2 | 2022-08-25 23:46:16 | [diff] [blame] | 310 | &RenderFrameHostImpl::MaybeDispatchDidFinishLoadOnPrerenderActivation); |
Sreeja Kamishetty | 81fbeefb | 2021-08-12 07:21:41 | [diff] [blame] | 311 | } |
| 312 | |
Takashi Toyoshima | 881f4d7 | 2023-11-09 05:07:43 | [diff] [blame] | 313 | void PageImpl::DidActivateAllRenderViewsForPrerenderingOrPreview( |
| 314 | base::OnceCallback<void(base::TimeTicks)> completion_callback) { |
Lingqi Chi | ef8657c | 2023-06-08 08:40:31 | [diff] [blame] | 315 | TRACE_EVENT0("navigation", |
| 316 | "PageImpl::DidActivateAllRenderViewsForPrerendering"); |
| 317 | |
Matt Falkenhagen | f78c219 | 2021-07-24 02:01:43 | [diff] [blame] | 318 | // Tell each RenderFrameHostImpl in this Page that activation finished. |
Peter Kasting | d2876a2 | 2024-12-03 01:12:55 | [diff] [blame] | 319 | main_document_->ForEachRenderFrameHostImplIncludingSpeculative( |
Hiroki Nakagawa | f98d009a | 2023-03-16 02:26:10 | [diff] [blame] | 320 | [this](RenderFrameHostImpl* rfh) { |
| 321 | if (&rfh->GetPage() != this) { |
| 322 | return; |
| 323 | } |
| 324 | rfh->RendererDidActivateForPrerendering(); |
| 325 | }); |
Takashi Toyoshima | 881f4d7 | 2023-11-09 05:07:43 | [diff] [blame] | 326 | CHECK(activation_start_time_.has_value()); |
| 327 | std::move(completion_callback).Run(*activation_start_time_); |
Matt Falkenhagen | f78c219 | 2021-07-24 02:01:43 | [diff] [blame] | 328 | } |
| 329 | |
Sreeja Kamishetty | 1b5c143 | 2021-06-25 11:32:59 | [diff] [blame] | 330 | RenderFrameHost& PageImpl::GetMainDocumentHelper() { |
Ali Hijazi | d87307d | 2022-11-07 20:15:03 | [diff] [blame] | 331 | return *main_document_; |
Sreeja Kamishetty | 1b5c143 | 2021-06-25 11:32:59 | [diff] [blame] | 332 | } |
| 333 | |
| 334 | RenderFrameHostImpl& PageImpl::GetMainDocument() const { |
Ali Hijazi | d87307d | 2022-11-07 20:15:03 | [diff] [blame] | 335 | return *main_document_; |
Sreeja Kamishetty | 1b5c143 | 2021-06-25 11:32:59 | [diff] [blame] | 336 | } |
| 337 | |
Peilin Wang | ae08e26e | 2024-06-10 20:55:30 | [diff] [blame] | 338 | void PageImpl::UpdateBrowserControlsState( |
| 339 | cc::BrowserControlsState constraints, |
| 340 | cc::BrowserControlsState current, |
| 341 | bool animate, |
Peilin Wang | 1187e4e8 | 2025-02-10 08:53:29 | [diff] [blame] | 342 | const std::optional<cc::BrowserControlsOffsetTagModifications>& |
| 343 | offset_tag_modifications) { |
Alison Gale | 47d1537d | 2024-04-19 21:31:46 | [diff] [blame] | 344 | // TODO(crbug.com/40159655): Asking for the LocalMainFrame interface |
Yoshisato Yanagisawa | d016d62d3 | 2021-10-15 04:38:55 | [diff] [blame] | 345 | // before the RenderFrame is created is racy. |
Takashi Toyoshima | 41afbbc | 2024-11-12 00:31:12 | [diff] [blame] | 346 | if (!GetMainDocument().IsRenderFrameLive()) { |
Yoshisato Yanagisawa | d016d62d3 | 2021-10-15 04:38:55 | [diff] [blame] | 347 | return; |
Takashi Toyoshima | 41afbbc | 2024-11-12 00:31:12 | [diff] [blame] | 348 | } |
Yoshisato Yanagisawa | d016d62d3 | 2021-10-15 04:38:55 | [diff] [blame] | 349 | |
Morten Stenshorne | 457cad1 | 2024-11-06 07:37:09 | [diff] [blame] | 350 | GetMainDocument().GetRenderWidgetHost()->UpdateBrowserControlsState( |
Peilin Wang | 1187e4e8 | 2025-02-10 08:53:29 | [diff] [blame] | 351 | constraints, current, animate, offset_tag_modifications); |
Yoshisato Yanagisawa | d016d62d3 | 2021-10-15 04:38:55 | [diff] [blame] | 352 | } |
| 353 | |
Kevin McNee | c4325ba | 2022-04-08 23:18:23 | [diff] [blame] | 354 | float PageImpl::GetPageScaleFactor() const { |
| 355 | return GetMainDocument().GetPageScaleFactor(); |
| 356 | } |
| 357 | |
Dominic Farolino | 5c606c1 | 2021-12-18 09:40:14 | [diff] [blame] | 358 | void PageImpl::UpdateEncoding(const std::string& encoding_name) { |
Takashi Toyoshima | 41afbbc | 2024-11-12 00:31:12 | [diff] [blame] | 359 | if (encoding_name == last_reported_encoding_) { |
Dominic Farolino | 5c606c1 | 2021-12-18 09:40:14 | [diff] [blame] | 360 | return; |
Takashi Toyoshima | 41afbbc | 2024-11-12 00:31:12 | [diff] [blame] | 361 | } |
Dominic Farolino | 5c606c1 | 2021-12-18 09:40:14 | [diff] [blame] | 362 | last_reported_encoding_ = encoding_name; |
| 363 | |
| 364 | canonical_encoding_ = |
| 365 | base::GetCanonicalEncodingNameByAliasName(encoding_name); |
| 366 | } |
| 367 | |
Yoshisato Yanagisawa | 8ae0d11 | 2022-04-20 01:49:30 | [diff] [blame] | 368 | void PageImpl::NotifyVirtualKeyboardOverlayRect( |
| 369 | const gfx::Rect& keyboard_rect) { |
Alison Gale | 81f4f2c7 | 2024-04-22 19:33:31 | [diff] [blame] | 370 | // TODO(crbug.com/40222405): send notification to outer frames if |
Yoshisato Yanagisawa | 8ae0d11 | 2022-04-20 01:49:30 | [diff] [blame] | 371 | // needed. |
David Bokan | d6e44055b | 2022-09-21 03:58:08 | [diff] [blame] | 372 | DCHECK_EQ(virtual_keyboard_mode(), |
| 373 | ui::mojom::VirtualKeyboardMode::kOverlaysContent); |
Yoshisato Yanagisawa | 8ae0d11 | 2022-04-20 01:49:30 | [diff] [blame] | 374 | GetMainDocument().GetAssociatedLocalFrame()->NotifyVirtualKeyboardOverlayRect( |
| 375 | keyboard_rect); |
| 376 | } |
| 377 | |
Mason Freed | e478e493 | 2025-03-31 23:52:15 | [diff] [blame] | 378 | void PageImpl::NotifyContextMenuInsetsObservers(const gfx::Rect& safe_area) { |
| 379 | GetMainDocument().GetAssociatedLocalFrame()->NotifyContextMenuInsetsObservers( |
| 380 | safe_area); |
| 381 | } |
| 382 | |
Mason Freed | 098db03 | 2025-05-06 11:52:11 | [diff] [blame] | 383 | void PageImpl::ShowInterestInElement(int nodeID) { |
| 384 | GetMainDocument().GetAssociatedLocalFrame()->ShowInterestInElement(nodeID); |
| 385 | } |
| 386 | |
David Bokan | d6e44055b | 2022-09-21 03:58:08 | [diff] [blame] | 387 | void PageImpl::SetVirtualKeyboardMode(ui::mojom::VirtualKeyboardMode mode) { |
Takashi Toyoshima | 41afbbc | 2024-11-12 00:31:12 | [diff] [blame] | 388 | if (virtual_keyboard_mode_ == mode) { |
David Bokan | d6e44055b | 2022-09-21 03:58:08 | [diff] [blame] | 389 | return; |
Takashi Toyoshima | 41afbbc | 2024-11-12 00:31:12 | [diff] [blame] | 390 | } |
David Bokan | d6e44055b | 2022-09-21 03:58:08 | [diff] [blame] | 391 | |
| 392 | virtual_keyboard_mode_ = mode; |
| 393 | |
Ali Hijazi | d87307d | 2022-11-07 20:15:03 | [diff] [blame] | 394 | delegate_->OnVirtualKeyboardModeChanged(*this); |
David Bokan | d6e44055b | 2022-09-21 03:58:08 | [diff] [blame] | 395 | } |
| 396 | |
Yoshisato Yanagisawa | 668f844 | 2022-04-20 04:45:58 | [diff] [blame] | 397 | base::flat_map<std::string, std::string> PageImpl::GetKeyboardLayoutMap() { |
| 398 | return GetMainDocument().GetRenderWidgetHost()->GetKeyboardLayoutMap(); |
| 399 | } |
| 400 | |
Camillia Smith Barnes | 397c965 | 2024-10-29 21:27:16 | [diff] [blame] | 401 | int32_t PageImpl::GetSavedQueryResultIndexOrStoreCallback( |
| 402 | const url::Origin& origin, |
| 403 | const GURL& script_url, |
| 404 | const std::string& operation_name, |
| 405 | const std::u16string& query_name, |
| 406 | base::OnceCallback<void(uint32_t)> callback) { |
| 407 | auto key = std::make_tuple(origin, script_url, operation_name, query_name); |
| 408 | auto it = select_url_saved_query_index_results_.find(key); |
| 409 | if (it == select_url_saved_query_index_results_.end()) { |
| 410 | select_url_saved_query_index_results_[key] = SharedStorageSavedQueryData(); |
| 411 | // The result index will be determined by running the registered worklet |
| 412 | // operation upon return to the SHaredStorageWorkletHost. |
| 413 | return -2; |
| 414 | } |
| 415 | if (it->second.index == -1) { |
| 416 | // The result index will be determined when a previously initiated worklet |
| 417 | // operation finishes running. We save a callback that will notify us of the |
| 418 | // result. |
| 419 | it->second.callbacks.push(std::move(callback)); |
| 420 | return -1; |
| 421 | } |
| 422 | // The result index has been stored from a previously resolved worklet |
| 423 | // operation. |
| 424 | return it->second.index; |
| 425 | } |
| 426 | |
| 427 | void PageImpl::SetSavedQueryResultIndexAndRunCallbacks( |
| 428 | const url::Origin& origin, |
| 429 | const GURL& script_url, |
| 430 | const std::string& operation_name, |
| 431 | const std::u16string& query_name, |
| 432 | uint32_t index) { |
| 433 | auto key = std::make_tuple(origin, script_url, operation_name, query_name); |
| 434 | auto it = select_url_saved_query_index_results_.find(key); |
| 435 | CHECK(it != select_url_saved_query_index_results_.end()); |
| 436 | CHECK_EQ(it->second.index, -1L); |
| 437 | it->second.index = index; |
| 438 | while (!it->second.callbacks.empty()) { |
| 439 | std::move(it->second.callbacks.front()).Run(index); |
| 440 | it->second.callbacks.pop(); |
| 441 | } |
| 442 | } |
| 443 | |
Camillia Smith Barnes | 385a2e9 | 2024-04-16 17:49:40 | [diff] [blame] | 444 | blink::SharedStorageSelectUrlBudgetStatus |
| 445 | PageImpl::CheckAndMaybeDebitSelectURLBudgets(const net::SchemefulSite& site, |
| 446 | double bits_to_charge) { |
Camillia Smith Barnes | 477a311 | 2023-02-28 19:06:30 | [diff] [blame] | 447 | if (!select_url_overall_budget_) { |
| 448 | // The limits are not enabled. |
Camillia Smith Barnes | 385a2e9 | 2024-04-16 17:49:40 | [diff] [blame] | 449 | return blink::SharedStorageSelectUrlBudgetStatus::kSufficientBudget; |
Camillia Smith Barnes | 9d70e5ae8 | 2023-01-18 19:25:24 | [diff] [blame] | 450 | } |
| 451 | |
Camillia Smith Barnes | 385a2e9 | 2024-04-16 17:49:40 | [diff] [blame] | 452 | // Return insufficient if there is insufficient overall budget. |
Camillia Smith Barnes | 477a311 | 2023-02-28 19:06:30 | [diff] [blame] | 453 | if (bits_to_charge > select_url_overall_budget_.value()) { |
Camillia Smith Barnes | 385a2e9 | 2024-04-16 17:49:40 | [diff] [blame] | 454 | GetContentClient()->browser()->LogWebFeatureForCurrentPage( |
| 455 | &GetMainDocument(), |
| 456 | blink::mojom::WebFeature:: |
| 457 | kSharedStorageAPI_SelectURLOverallPageloadBudgetInsufficient); |
| 458 | return blink::SharedStorageSelectUrlBudgetStatus:: |
| 459 | kInsufficientOverallPageloadBudget; |
Camillia Smith Barnes | 574d4d4 | 2023-01-10 18:57:47 | [diff] [blame] | 460 | } |
| 461 | |
Camillia Smith Barnes | 7455223 | 2023-10-02 18:50:07 | [diff] [blame] | 462 | DCHECK(select_url_max_bits_per_site_); |
Camillia Smith Barnes | 477a311 | 2023-02-28 19:06:30 | [diff] [blame] | 463 | |
Camillia Smith Barnes | 7455223 | 2023-10-02 18:50:07 | [diff] [blame] | 464 | // Return false if the max bits per site is set to a value smaller than the |
Camillia Smith Barnes | 477a311 | 2023-02-28 19:06:30 | [diff] [blame] | 465 | // current bits to charge. |
Camillia Smith Barnes | 7455223 | 2023-10-02 18:50:07 | [diff] [blame] | 466 | if (bits_to_charge > select_url_max_bits_per_site_.value()) { |
Camillia Smith Barnes | 385a2e9 | 2024-04-16 17:49:40 | [diff] [blame] | 467 | return blink::SharedStorageSelectUrlBudgetStatus:: |
| 468 | kInsufficientSitePageloadBudget; |
Camillia Smith Barnes | 477a311 | 2023-02-28 19:06:30 | [diff] [blame] | 469 | } |
| 470 | |
Camillia Smith Barnes | 385a2e9 | 2024-04-16 17:49:40 | [diff] [blame] | 471 | // Charge the per-site budget or return insufficient if there is not enough. |
Camillia Smith Barnes | 7455223 | 2023-10-02 18:50:07 | [diff] [blame] | 472 | auto it = select_url_per_site_budget_.find(site); |
| 473 | if (it == select_url_per_site_budget_.end()) { |
| 474 | select_url_per_site_budget_[site] = |
| 475 | select_url_max_bits_per_site_.value() - bits_to_charge; |
Camillia Smith Barnes | 477a311 | 2023-02-28 19:06:30 | [diff] [blame] | 476 | } else if (bits_to_charge > it->second) { |
Camillia Smith Barnes | 7455223 | 2023-10-02 18:50:07 | [diff] [blame] | 477 | // There is insufficient per-site budget remaining. |
Camillia Smith Barnes | 385a2e9 | 2024-04-16 17:49:40 | [diff] [blame] | 478 | return blink::SharedStorageSelectUrlBudgetStatus:: |
| 479 | kInsufficientSitePageloadBudget; |
Camillia Smith Barnes | 477a311 | 2023-02-28 19:06:30 | [diff] [blame] | 480 | } else { |
| 481 | it->second -= bits_to_charge; |
| 482 | } |
| 483 | |
| 484 | // Charge the overall budget. |
| 485 | select_url_overall_budget_.value() -= bits_to_charge; |
Camillia Smith Barnes | 385a2e9 | 2024-04-16 17:49:40 | [diff] [blame] | 486 | return blink::SharedStorageSelectUrlBudgetStatus::kSufficientBudget; |
Camillia Smith Barnes | 574d4d4 | 2023-01-10 18:57:47 | [diff] [blame] | 487 | } |
| 488 | |
Adithya Srinivasan | a5795b9 | 2023-12-20 22:35:22 | [diff] [blame] | 489 | void PageImpl::TakeLoadingMemoryTracker(NavigationRequest* request) { |
| 490 | CHECK(IsPrimary()); |
| 491 | loading_memory_tracker_ = request->TakePeakGpuMemoryTracker(); |
| 492 | } |
| 493 | |
| 494 | void PageImpl::ResetLoadingMemoryTracker() { |
| 495 | CHECK(IsPrimary()); |
| 496 | if (loading_memory_tracker_) { |
| 497 | loading_memory_tracker_.reset(); |
| 498 | } |
| 499 | } |
| 500 | |
| 501 | void PageImpl::CancelLoadingMemoryTracker() { |
| 502 | if (loading_memory_tracker_) { |
| 503 | loading_memory_tracker_->Cancel(); |
| 504 | loading_memory_tracker_.reset(); |
| 505 | } |
| 506 | } |
| 507 | |
Adithya Srinivasan | ec6b3f9 | 2024-01-09 16:31:01 | [diff] [blame] | 508 | void PageImpl::SetLastCommitParams( |
| 509 | mojom::DidCommitProvisionalLoadParamsPtr commit_params) { |
| 510 | CHECK(GetMainDocument().IsOutermostMainFrame()); |
| 511 | last_commit_params_ = std::move(commit_params); |
| 512 | } |
| 513 | |
| 514 | mojom::DidCommitProvisionalLoadParamsPtr PageImpl::TakeLastCommitParams() { |
| 515 | CHECK(GetMainDocument().IsOutermostMainFrame()); |
| 516 | return std::move(last_commit_params_); |
| 517 | } |
| 518 | |
Sreeja Kamishetty | 9e1d0e73 | 2021-05-27 18:20:09 | [diff] [blame] | 519 | } // namespace content |