blob: 903c402727c8f02675390881f5a0041d0c994741 [file] [log] [blame]
// Copyright 2017 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 "chrome/browser/android/oom_intervention/oom_intervention_tab_helper.h"
#include "base/metrics/histogram_macros.h"
#include "chrome/browser/android/oom_intervention/oom_intervention_config.h"
#include "chrome/browser/android/oom_intervention/oom_intervention_decider.h"
#include "chrome/browser/ui/android/infobars/near_oom_reduction_infobar.h"
#include "content/public/browser/browser_context.h"
#include "content/public/browser/navigation_handle.h"
#include "content/public/browser/render_frame_host.h"
#include "content/public/browser/render_process_host.h"
#include "content/public/browser/web_contents.h"
#include "third_party/blink/public/common/oom_intervention/oom_intervention_types.h"
namespace {
constexpr base::TimeDelta kRendererHighMemoryUsageDetectionWindow =
base::TimeDelta::FromSeconds(60);
content::WebContents* g_last_visible_web_contents = nullptr;
bool IsLastVisibleWebContents(content::WebContents* web_contents) {
return web_contents == g_last_visible_web_contents;
}
void SetLastVisibleWebContents(content::WebContents* web_contents) {
g_last_visible_web_contents = web_contents;
}
// These enums are associated with UMA. Values must be kept in sync with
// enums.xml and must not be renumbered/reused.
enum class NearOomDetectionEndReason {
OOM_PROTECTED_CRASH = 0,
RENDERER_GONE = 1,
NAVIGATION = 2,
COUNT,
};
void RecordNearOomDetectionEndReason(NearOomDetectionEndReason reason) {
UMA_HISTOGRAM_ENUMERATION(
"Memory.Experimental.OomIntervention.NearOomDetectionEndReason", reason,
NearOomDetectionEndReason::COUNT);
}
void RecordInterventionUserDecision(bool accepted) {
UMA_HISTOGRAM_BOOLEAN("Memory.Experimental.OomIntervention.UserDecision",
accepted);
}
void RecordInterventionStateOnCrash(bool accepted) {
UMA_HISTOGRAM_BOOLEAN(
"Memory.Experimental.OomIntervention.InterventionStateOnCrash", accepted);
}
} // namespace
// static
bool OomInterventionTabHelper::IsEnabled() {
return OomInterventionConfig::GetInstance()->is_intervention_enabled();
}
OomInterventionTabHelper::OomInterventionTabHelper(
content::WebContents* web_contents)
: content::WebContentsObserver(web_contents),
decider_(OomInterventionDecider::GetForBrowserContext(
web_contents->GetBrowserContext())),
binding_(this),
scoped_observer_(this),
weak_ptr_factory_(this) {
scoped_observer_.Add(crash_reporter::CrashMetricsReporter::GetInstance());
}
OomInterventionTabHelper::~OomInterventionTabHelper() = default;
void OomInterventionTabHelper::OnHighMemoryUsage() {
auto* config = OomInterventionConfig::GetInstance();
if (config->is_renderer_pause_enabled() ||
config->is_navigate_ads_enabled() ||
config->is_purge_v8_memory_enabled()) {
NearOomReductionInfoBar::Show(web_contents(), this);
intervention_state_ = InterventionState::UI_SHOWN;
if (!last_navigation_timestamp_.is_null()) {
base::TimeDelta time_since_last_navigation =
base::TimeTicks::Now() - last_navigation_timestamp_;
UMA_HISTOGRAM_COUNTS_1M(
"Memory.Experimental.OomIntervention."
"RendererTimeSinceLastNavigationAtIntervention",
time_since_last_navigation.InSeconds());
}
}
near_oom_detected_time_ = base::TimeTicks::Now();
renderer_detection_timer_.AbandonAndStop();
}
void OomInterventionTabHelper::AcceptIntervention() {
RecordInterventionUserDecision(true);
intervention_state_ = InterventionState::ACCEPTED;
}
void OomInterventionTabHelper::DeclineIntervention() {
RecordInterventionUserDecision(false);
ResetInterfaces();
intervention_state_ = InterventionState::DECLINED;
if (decider_) {
DCHECK(!web_contents()->GetBrowserContext()->IsOffTheRecord());
const std::string& host = web_contents()->GetVisibleURL().host();
decider_->OnInterventionDeclined(host);
}
}
void OomInterventionTabHelper::DeclineInterventionWithReload() {
web_contents()->GetController().Reload(content::ReloadType::NORMAL, true);
DeclineIntervention();
}
void OomInterventionTabHelper::DeclineInterventionSticky() {
NOTREACHED();
}
void OomInterventionTabHelper::WebContentsDestroyed() {
StopMonitoring();
}
void OomInterventionTabHelper::RenderProcessGone(
base::TerminationStatus status) {
ResetInterfaces();
// Skip background process termination.
if (!IsLastVisibleWebContents(web_contents())) {
ResetInterventionState();
return;
}
// OOM crash is handled in OnForegroundOOMDetected().
if (status == base::TERMINATION_STATUS_OOM_PROTECTED)
return;
if (near_oom_detected_time_) {
base::TimeDelta elapsed_time =
base::TimeTicks::Now() - near_oom_detected_time_.value();
UMA_HISTOGRAM_MEDIUM_TIMES(
"Memory.Experimental.OomIntervention."
"RendererGoneAfterDetectionTime",
elapsed_time);
ResetInterventionState();
} else {
RecordNearOomDetectionEndReason(NearOomDetectionEndReason::RENDERER_GONE);
}
}
void OomInterventionTabHelper::DidStartNavigation(
content::NavigationHandle* navigation_handle) {
load_finished_ = false;
// Filter out sub-frame's navigation or if the navigation happens without
// changing document.
if (!navigation_handle->IsInMainFrame() ||
navigation_handle->IsSameDocument()) {
return;
}
last_navigation_timestamp_ = base::TimeTicks::Now();
// Filter out the first navigation.
if (!navigation_started_) {
navigation_started_ = true;
return;
}
ResetInterfaces();
// Filter out background navigation.
if (!IsLastVisibleWebContents(navigation_handle->GetWebContents())) {
ResetInterventionState();
return;
}
if (near_oom_detected_time_) {
// near-OOM was detected.
base::TimeDelta elapsed_time =
base::TimeTicks::Now() - near_oom_detected_time_.value();
UMA_HISTOGRAM_MEDIUM_TIMES(
"Memory.Experimental.OomIntervention."
"NavigationAfterDetectionTime",
elapsed_time);
ResetInterventionState();
} else {
// Monitoring but near-OOM hasn't been detected.
RecordNearOomDetectionEndReason(NearOomDetectionEndReason::NAVIGATION);
}
}
void OomInterventionTabHelper::OnVisibilityChanged(
content::Visibility visibility) {
if (visibility == content::Visibility::VISIBLE) {
StartMonitoringIfNeeded();
SetLastVisibleWebContents(web_contents());
} else {
StopMonitoring();
}
}
void OomInterventionTabHelper::DocumentOnLoadCompletedInMainFrame() {
load_finished_ = true;
if (IsLastVisibleWebContents(web_contents()))
StartMonitoringIfNeeded();
}
void OomInterventionTabHelper::OnCrashDumpProcessed(
int rph_id,
const crash_reporter::CrashMetricsReporter::ReportedCrashTypeSet&
reported_counts) {
if (rph_id != web_contents()->GetMainFrame()->GetProcess()->GetID())
return;
if (!reported_counts.count(
crash_reporter::CrashMetricsReporter::ProcessedCrashCounts::
kRendererForegroundVisibleOom)) {
return;
}
DCHECK(IsLastVisibleWebContents(web_contents()));
if (near_oom_detected_time_) {
base::TimeDelta elapsed_time =
base::TimeTicks::Now() - near_oom_detected_time_.value();
UMA_HISTOGRAM_MEDIUM_TIMES(
"Memory.Experimental.OomIntervention."
"OomProtectedCrashAfterDetectionTime",
elapsed_time);
if (intervention_state_ != InterventionState::NOT_TRIGGERED) {
// Consider UI_SHOWN as ACCEPTED because we already triggered the
// intervention and the user didn't decline.
bool accepted = intervention_state_ != InterventionState::DECLINED;
RecordInterventionStateOnCrash(accepted);
}
ResetInterventionState();
} else {
RecordNearOomDetectionEndReason(
NearOomDetectionEndReason::OOM_PROTECTED_CRASH);
}
base::TimeDelta time_since_last_navigation;
if (!last_navigation_timestamp_.is_null()) {
time_since_last_navigation =
base::TimeTicks::Now() - last_navigation_timestamp_;
}
UMA_HISTOGRAM_COUNTS_1M(
"Memory.Experimental.OomIntervention."
"RendererTimeSinceLastNavigationAtOOM",
time_since_last_navigation.InSeconds());
if (decider_) {
DCHECK(!web_contents()->GetBrowserContext()->IsOffTheRecord());
const std::string& host = web_contents()->GetVisibleURL().host();
decider_->OnOomDetected(host);
}
}
void OomInterventionTabHelper::StartMonitoringIfNeeded() {
if (subscription_)
return;
if (intervention_)
return;
if (near_oom_detected_time_)
return;
if (!load_finished_)
return;
auto* config = OomInterventionConfig::GetInstance();
if (config->should_detect_in_renderer()) {
if (binding_.is_bound())
return;
StartDetectionInRenderer();
} else if (config->is_swap_monitor_enabled()) {
subscription_ = NearOomMonitor::GetInstance()->RegisterCallback(
base::BindRepeating(&OomInterventionTabHelper::OnNearOomDetected,
base::Unretained(this)));
}
}
void OomInterventionTabHelper::StopMonitoring() {
if (OomInterventionConfig::GetInstance()->should_detect_in_renderer()) {
ResetInterfaces();
} else {
subscription_.reset();
}
}
void OomInterventionTabHelper::StartDetectionInRenderer() {
auto* config = OomInterventionConfig::GetInstance();
bool renderer_pause_enabled = config->is_renderer_pause_enabled();
bool navigate_ads_enabled = config->is_navigate_ads_enabled();
bool purge_v8_memory_enabled = config->is_purge_v8_memory_enabled();
if ((renderer_pause_enabled || navigate_ads_enabled ||
purge_v8_memory_enabled) &&
decider_) {
DCHECK(!web_contents()->GetBrowserContext()->IsOffTheRecord());
const std::string& host = web_contents()->GetVisibleURL().host();
if (!decider_->CanTriggerIntervention(host)) {
return;
}
}
content::RenderFrameHost* main_frame = web_contents()->GetMainFrame();
DCHECK(main_frame);
content::RenderProcessHost* render_process_host = main_frame->GetProcess();
DCHECK(render_process_host);
content::BindInterface(render_process_host,
mojo::MakeRequest(&intervention_));
DCHECK(!binding_.is_bound());
blink::mojom::OomInterventionHostPtr host;
binding_.Bind(mojo::MakeRequest(&host));
blink::mojom::DetectionArgsPtr detection_args =
config->GetRendererOomDetectionArgs();
intervention_->StartDetection(std::move(host), std::move(detection_args),
renderer_pause_enabled, navigate_ads_enabled,
purge_v8_memory_enabled);
}
void OomInterventionTabHelper::OnNearOomDetected() {
DCHECK(!OomInterventionConfig::GetInstance()->should_detect_in_renderer());
DCHECK_EQ(web_contents()->GetVisibility(), content::Visibility::VISIBLE);
DCHECK(!near_oom_detected_time_);
subscription_.reset();
StartDetectionInRenderer();
DCHECK(!renderer_detection_timer_.IsRunning());
renderer_detection_timer_.Start(
FROM_HERE, kRendererHighMemoryUsageDetectionWindow,
base::BindRepeating(&OomInterventionTabHelper::
OnDetectionWindowElapsedWithoutHighMemoryUsage,
weak_ptr_factory_.GetWeakPtr()));
}
void OomInterventionTabHelper::
OnDetectionWindowElapsedWithoutHighMemoryUsage() {
ResetInterventionState();
ResetInterfaces();
StartMonitoringIfNeeded();
}
void OomInterventionTabHelper::ResetInterventionState() {
near_oom_detected_time_.reset();
intervention_state_ = InterventionState::NOT_TRIGGERED;
renderer_detection_timer_.AbandonAndStop();
}
void OomInterventionTabHelper::ResetInterfaces() {
intervention_.reset();
if (binding_.is_bound())
binding_.Close();
}
WEB_CONTENTS_USER_DATA_KEY_IMPL(OomInterventionTabHelper)