blob: c5ce49222e74ced74b6b2e701f90e63db3b150c6 [file] [log] [blame]
// Copyright 2020 The Chromium Authors. All rights reserved.
// 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/callback_helpers.h"
#include "base/metrics/histogram_macros.h"
#include "base/threading/thread_task_runner_handle.h"
#include "content/browser/renderer_host/render_view_host_impl.h"
#include "content/public/browser/render_process_host.h"
#include "services/service_manager/public/cpp/interface_provider.h"
#include "third_party/blink/public/common/frame/event_page_show_persisted.h"
#include "third_party/blink/public/common/page/page_lifecycle_state_updater.h"
namespace {
constexpr base::TimeDelta kBackForwardCacheTimeoutInSeconds = base::Seconds(3);
}
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 (is_set_frozen_called_ == frozen)
return;
is_set_frozen_called_ = frozen;
SendUpdatesToRendererIfNeeded(
/*page_restore_params=*/nullptr, base::NullCallback(),
/*restoring_main_frame_from_back_forward_cache=*/false);
}
void PageLifecycleStateManager::SetFrameTreeVisibility(
blink::mojom::PageVisibilityState visibility) {
if (frame_tree_visibility_ == visibility)
return;
frame_tree_visibility_ = visibility;
SendUpdatesToRendererIfNeeded(
/*page_restore_params=*/nullptr, base::NullCallback(),
/*restoring_main_frame_from_back_forward_cache=*/false);
// 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,
bool restoring_main_frame_from_back_forward_cache) {
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, kBackForwardCacheTimeoutInSeconds,
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(),
restoring_main_frame_from_back_forward_cache);
}
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),
/*restoring_main_frame_from_back_forward_cache=*/false);
}
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,
bool restoring_main_frame_from_back_forward_cache) {
if (!render_view_host_impl_->GetAssociatedPageBroadcast()) {
// TODO(https://crbug.com/1153155): For some tests, |render_view_host_impl_|
// does not have the associated page.
if (done_cb) {
base::ThreadTaskRunnerHandle::Get()->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.
}
// TODO(https://crbug.com/1234634): Remove this |if|.
if (restoring_main_frame_from_back_forward_cache) {
DCHECK(last_state_sent_to_renderer_);
if (blink::IsRestoredFromBackForwardCache(last_state_sent_to_renderer_,
new_state)) {
// We see that IPCs are not received by the renderer. Check that we are
// about to send an IPC to a live RVH.
if (!render_view_host_impl_->IsRenderViewLive()) {
blink::RecordUMAEventPageShowPersisted(
blink::EventPageShowPersisted::kYesInBrowserRenderViewNotLive);
NOTREACHED();
}
// And that the mojo interface is connected.
if (!render_view_host_impl_->GetAssociatedPageBroadcast()
.is_connected()) {
blink::RecordUMAEventPageShowPersisted(
blink::EventPageShowPersisted::kYesInBrowserDisconnected);
NOTREACHED();
} else {
blink::RecordUMAEventPageShowPersisted(
blink::EventPageShowPersisted::kYesInBrowser);
}
new_state->should_dispatch_pageshow_for_debugging = true;
} else {
NOTREACHED();
}
}
last_state_sent_to_renderer_ = new_state.Clone();
auto state = new_state.Clone();
if (test_delegate_)
test_delegate_->OnUpdateSentToRenderer(*last_state_sent_to_renderer_);
// TODO(https://crbug.com/1234634): Remove this.
// We record the time that we sent it so that if the process exits
// unexpectedly, we can know how long that took.
if (new_state->should_dispatch_pageshow_for_debugging) {
// We could send a second one of these before before
// receiving the first ack. This would require something more complicated to
// handle perfectly but it should be rare or even impossible, so instead we
// just record that it happened and clear the timestamp without setting a
// new one
persisted_pageshow_timestamp_bug_1234634_ = base::Time::Now();
}
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_ ? true : is_set_frozen_called_;
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_;
// TODO(https://crbug.com/1234634): Remove this. It's for temporary
// debugging.
// This may become true later.
state->should_dispatch_pageshow_for_debugging = false;
return state;
}
void PageLifecycleStateManager::OnPageLifecycleChangedAck(
blink::mojom::PageLifecycleStatePtr acknowledged_state,
base::OnceClosure done_cb) {
blink::mojom::PageLifecycleStatePtr old_state =
std::move(last_acknowledged_state_);
// TODO(https://crbug.com/1234634): Remove this.
if (acknowledged_state->should_dispatch_pageshow_for_debugging) {
blink::RecordUMAEventPageShowPersisted(
blink::EventPageShowPersisted::kYesInBrowserAck);
// We have received the ack, no need to track info for failures.
persisted_pageshow_timestamp_bug_1234634_.reset();
}
last_acknowledged_state_ = std::move(acknowledged_state);
if (last_acknowledged_state_->is_in_back_forward_cache)
did_receive_back_forward_cache_ack_ = true;
// 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