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
content / browser / renderer_host / page_lifecycle_state_manager.cc [blame]
// 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.
}
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 the
// |pagehide_dispatch_| state so that we'd dispatch the
// events again the next time we navigate away from the page.
pagehide_dispatch_ = blink::mojom::PagehideDispatch::kNotDispatched;
}
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