1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314
|
// Copyright 2020 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_lifecycle_state_manager.h"
#include "base/feature_list.h"
#include "base/functional/callback_helpers.h"
#include "base/metrics/histogram_macros.h"
#include "base/task/single_thread_task_runner.h"
#include "base/time/time.h"
#include "content/browser/renderer_host/render_view_host_impl.h"
#include "content/common/content_navigation_policy.h"
#include "content/public/browser/render_process_host.h"
#include "services/service_manager/public/cpp/interface_provider.h"
namespace {
// ASAN builds are slow and we see flakes caused by reaching this timeout. 6s
// was not enough to stop the flakes. Trying 12s just to ensure that there isn't
// something else going on that we don't understand.
// See https://crbug.com/1224355.
#if defined(ADDRESS_SANITIZER)
constexpr base::TimeDelta kBackForwardCacheTimeout = base::Seconds(12);
#else
constexpr base::TimeDelta kBackForwardCacheTimeout = base::Seconds(3);
#endif
base::TimeDelta GetBackForwardCacheEntryTimeout() {
if (base::FeatureList::IsEnabled(features::kBackForwardCacheEntryTimeout)) {
return kBackForwardCacheTimeout;
} else {
return base::TimeDelta::Max();
}
}
}
namespace content {
PageLifecycleStateManager::TestDelegate::TestDelegate() = default;
PageLifecycleStateManager::TestDelegate::~TestDelegate() = default;
void PageLifecycleStateManager::TestDelegate::OnLastAcknowledgedStateChanged(
const blink::mojom::PageLifecycleState& old_state,
const blink::mojom::PageLifecycleState& new_state) {}
void PageLifecycleStateManager::TestDelegate::OnUpdateSentToRenderer(
const blink::mojom::PageLifecycleState& new_state) {}
void PageLifecycleStateManager::TestDelegate::OnDeleted() {}
PageLifecycleStateManager::PageLifecycleStateManager(
RenderViewHostImpl* render_view_host_impl,
blink::mojom::PageVisibilityState frame_tree_visibility)
: frame_tree_visibility_(frame_tree_visibility),
render_view_host_impl_(render_view_host_impl) {
last_acknowledged_state_ = CalculatePageLifecycleState();
last_state_sent_to_renderer_ = last_acknowledged_state_.Clone();
}
PageLifecycleStateManager::~PageLifecycleStateManager() {
if (test_delegate_)
test_delegate_->OnDeleted();
}
void PageLifecycleStateManager::SetIsFrozen(bool frozen) {
if (frozen_explicitly_ == frozen) {
return;
}
frozen_explicitly_ = frozen;
SendUpdatesToRendererIfNeeded(
/*page_restore_params=*/nullptr, base::NullCallback());
}
void PageLifecycleStateManager::SetFrameTreeVisibility(
blink::mojom::PageVisibilityState visibility) {
if (frame_tree_visibility_ == visibility)
return;
frame_tree_visibility_ = visibility;
if (visibility == blink::mojom::PageVisibilityState::kVisible) {
// Unset `frozen_explicitly_` when the page is shown, to reflect that the
// Blink page scheduler unfreezes the page in that situation. This ensures
// that the page is frozen if SetIsFrozen(true) is called while the page is
// hidden in the future (SetIsFrozen(true) no-ops if `frozen_explicitly_` is
// true).
frozen_explicitly_ = false;
}
SendUpdatesToRendererIfNeeded(
/*page_restore_params=*/nullptr, base::NullCallback());
// TODO(yuzus): When a page is frozen and made visible, the page should
// automatically resume.
}
BASE_FEATURE(kBackForwardCacheNonStickyDoubleFix,
"BackForwardCacheNonStickyDoubleFix",
base::FEATURE_ENABLED_BY_DEFAULT);
void PageLifecycleStateManager::SetIsInBackForwardCache(
bool is_in_back_forward_cache,
blink::mojom::PageRestoreParamsPtr page_restore_params) {
if (is_in_back_forward_cache_ == is_in_back_forward_cache)
return;
// Prevent races by waiting for confirmation that the renderer will no longer
// evict the page before allowing it to exit the back-forward cache
DCHECK(is_in_back_forward_cache ||
!last_acknowledged_state_->eviction_enabled);
is_in_back_forward_cache_ = is_in_back_forward_cache;
eviction_enabled_ = is_in_back_forward_cache;
if (is_in_back_forward_cache) {
// When a page is put into BackForwardCache, the page can run a busy loop.
// Set a timeout monitor to check that the transition finishes within the
// time limit.
back_forward_cache_timeout_monitor_.Start(
FROM_HERE, GetBackForwardCacheEntryTimeout(),
base::BindOnce(&PageLifecycleStateManager::OnBackForwardCacheTimeout,
weak_ptr_factory_.GetWeakPtr()));
pagehide_dispatch_ = blink::mojom::PagehideDispatch::kDispatchedPersisted;
} else {
DCHECK(page_restore_params);
// When a page is restored from the back-forward cache, we should reset this
// state so that it behaves correctly next time navigation occurs.
pagehide_dispatch_ = blink::mojom::PagehideDispatch::kNotDispatched;
// TODO(https://crbug.com/360183659): Make this unconditional after
// measuring the impact.
if (base::FeatureList::IsEnabled(kBackForwardCacheNonStickyDoubleFix)) {
did_receive_back_forward_cache_ack_ = false;
}
}
SendUpdatesToRendererIfNeeded(std::move(page_restore_params),
base::NullCallback());
}
blink::mojom::PageLifecycleStatePtr
PageLifecycleStateManager::SetPagehideDispatchDuringNewPageCommit(
bool persisted) {
pagehide_dispatch_ =
persisted ? blink::mojom::PagehideDispatch::kDispatchedPersisted
: blink::mojom::PagehideDispatch::kDispatchedNotPersisted;
// We've only modified |pagehide_dispatch_| here, but the "visibility"
// property of |last_state_sent_to_renderer_| calculated from
// CalculatePageLifecycleState() below will be set to kHidden because it
// depends on the value of |pagehide_dispatch_|.
last_state_sent_to_renderer_ = CalculatePageLifecycleState();
DCHECK_EQ(last_state_sent_to_renderer_->visibility,
blink::mojom::PageVisibilityState::kHidden);
// We don't need to call SendUpdatesToRendererIfNeeded() because the update
// will be sent through an OldPageInfo parameter in the CommitNavigation IPC.
return last_state_sent_to_renderer_.Clone();
}
void PageLifecycleStateManager::DidSetPagehideDispatchDuringNewPageCommit(
blink::mojom::PageLifecycleStatePtr acknowledged_state) {
DCHECK_EQ(acknowledged_state->visibility,
blink::mojom::PageVisibilityState::kHidden);
DCHECK_NE(acknowledged_state->pagehide_dispatch,
blink::mojom::PagehideDispatch::kNotDispatched);
OnPageLifecycleChangedAck(std::move(acknowledged_state),
base::NullCallback());
}
void PageLifecycleStateManager::SetIsLeavingBackForwardCache(
base::OnceClosure done_cb) {
DCHECK(is_in_back_forward_cache_);
eviction_enabled_ = false;
SendUpdatesToRendererIfNeeded(nullptr, std::move(done_cb));
}
bool PageLifecycleStateManager::RendererExpectedToSendChannelAssociatedIpcs()
const {
// eviction_enabled_ => is_in_back_forward_cache_
DCHECK(!eviction_enabled_ || is_in_back_forward_cache_);
return !eviction_enabled_ || !last_acknowledged_state_->eviction_enabled;
}
void PageLifecycleStateManager::SendUpdatesToRendererIfNeeded(
blink::mojom::PageRestoreParamsPtr page_restore_params,
base::OnceClosure done_cb) {
if (!render_view_host_impl_->GetAssociatedPageBroadcast()) {
// TODO(crbug.com/40158974): For some tests, |render_view_host_impl_|
// does not have the associated page.
if (done_cb) {
base::SingleThreadTaskRunner::GetCurrentDefault()->PostTask(
FROM_HERE, std::move(done_cb));
}
return;
}
auto new_state = CalculatePageLifecycleState();
if (last_state_sent_to_renderer_ &&
last_state_sent_to_renderer_.Equals(new_state)) {
// TODO(yuzus): Send updates to renderer only when the effective state (per
// page lifecycle state) has changed since last sent to renderer. It is
// possible that the web contents state has changed but the effective state
// has not.
}
last_state_sent_to_renderer_ = new_state.Clone();
auto state = new_state.Clone();
if (test_delegate_)
test_delegate_->OnUpdateSentToRenderer(*last_state_sent_to_renderer_);
render_view_host_impl_->GetAssociatedPageBroadcast()->SetPageLifecycleState(
std::move(state), std::move(page_restore_params),
base::BindOnce(&PageLifecycleStateManager::OnPageLifecycleChangedAck,
weak_ptr_factory_.GetWeakPtr(), std::move(new_state),
std::move(done_cb)));
}
blink::mojom::PageLifecycleStatePtr
PageLifecycleStateManager::CalculatePageLifecycleState() {
auto state = blink::mojom::PageLifecycleState::New();
state->is_in_back_forward_cache = is_in_back_forward_cache_;
state->is_frozen = is_in_back_forward_cache_ || frozen_explicitly_;
state->pagehide_dispatch = pagehide_dispatch_;
// If a page is stored in the back-forward cache, or we have already
// dispatched/are dispatching pagehide for the page, it should be treated as
// "hidden" regardless of what |frame_tree_visibility_| is set to.
state->visibility =
(is_in_back_forward_cache_ ||
pagehide_dispatch_ != blink::mojom::PagehideDispatch::kNotDispatched)
? blink::mojom::PageVisibilityState::kHidden
: frame_tree_visibility_;
state->eviction_enabled = eviction_enabled_;
return state;
}
void PageLifecycleStateManager::OnPageLifecycleChangedAck(
blink::mojom::PageLifecycleStatePtr acknowledged_state,
base::OnceClosure done_cb) {
blink::mojom::PageLifecycleStatePtr old_state =
std::move(last_acknowledged_state_);
last_acknowledged_state_ = std::move(acknowledged_state);
if (last_acknowledged_state_->is_in_back_forward_cache) {
did_receive_back_forward_cache_ack_ = true;
// TODO(crbug.com/41494183): currently after the navigation, the old
// RenderViewHost is marked as inactive.
// `RenderViewHostImpl::GetMainRenderFrameHost()` will return nullptr. This
// prevents us from getting the RenderFrameHost even if the main frame of
// this RenderViewHost is stored in BFCache. Now we are getting the
// RenderFrameHost from the BackForwardCacheImpl as a workaround, but
// eventually we might allow getting the RenderFrameHost from a
// RenderViewHost that's in BFCache.
for (auto* entry :
render_view_host_impl_->frame_tree()
->controller()
.GetBackForwardCache()
.GetEntriesForRenderViewHostImpl(render_view_host_impl_)) {
if (entry->render_frame_host()->LoadedWithCacheControlNoStoreHeader()) {
// If the BFCached document was loaded with "Cache-control: no-store"
// header, we clear the fallback surface and force the browser to embed
// a completely new surface when this page is activated from BFCache.
// This avoids displaying sensitive information between it's restored
// and the `pageshow` handler completes.
RenderWidgetHostViewBase* rwhv =
render_view_host_impl_->GetWidget()->GetRenderWidgetHostViewBase();
if (rwhv) {
rwhv->InvalidateLocalSurfaceIdAndAllocationGroup();
rwhv->ClearFallbackSurfaceForCommitPending();
}
}
}
}
// Call |MaybeEvictFromBackForwardCache| after setting
// |last_acknowledged_state_|.
// Features which can be cleaned by the page are taken into account only
// after the 'pagehide' handlers have run. As we might have just received
// an acknowledgement from the renderer that these handlers have run, call
// |MaybeEvictFromBackForwardCache| in case we need to start taking these
// features into account.
render_view_host_impl_->MaybeEvictFromBackForwardCache();
// A page that has not yet received an acknowledgement from renderer is not
// counted against the cache size limit because it might still be ineligible
// for caching after the ack, i.e., after running handlers. After it receives
// the ack and we call |MaybeEvictFromBackForwardCache()|, we know whether it
// is eligible for caching and we should reconsider the cache size limits
// again.
render_view_host_impl_->EnforceBackForwardCacheSizeLimit();
if (last_acknowledged_state_->is_in_back_forward_cache) {
back_forward_cache_timeout_monitor_.Stop();
}
if (test_delegate_) {
test_delegate_->OnLastAcknowledgedStateChanged(*old_state,
*last_acknowledged_state_);
}
if (done_cb)
std::move(done_cb).Run();
}
void PageLifecycleStateManager::OnBackForwardCacheTimeout() {
DCHECK(!last_acknowledged_state_->is_in_back_forward_cache);
render_view_host_impl_->OnBackForwardCacheTimeout();
back_forward_cache_timeout_monitor_.Stop();
}
void PageLifecycleStateManager::SetDelegateForTesting(
PageLifecycleStateManager::TestDelegate* test_delegate) {
DCHECK(!test_delegate_ || !test_delegate);
test_delegate_ = test_delegate;
}
} // namespace content
|