blob: bdda9b19f93f312ff1a4623732f0675261681717 [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/media/media_engagement_score.h"
#include <utility>
#include "base/metrics/field_trial_params.h"
#include "chrome/browser/engagement/site_engagement_metrics.h"
#include "components/content_settings/core/browser/host_content_settings_map.h"
#include "components/content_settings/core/common/content_settings.h"
#include "components/content_settings/core/common/content_settings_types.h"
#include "media/base/media_switches.h"
const char MediaEngagementScore::kVisitsKey[] = "visits";
const char MediaEngagementScore::kMediaPlaybacksKey[] = "mediaPlaybacks";
const char MediaEngagementScore::kLastMediaPlaybackTimeKey[] =
"lastMediaPlaybackTime";
const char MediaEngagementScore::kHasHighScoreKey[] = "hasHighScore";
const char MediaEngagementScore::kAudiblePlaybacksKey[] = "audiblePlaybacks";
const char MediaEngagementScore::kSignificantPlaybacksKey[] =
"significantPlaybacks";
const char MediaEngagementScore::kVisitsWithMediaTagKey[] =
"visitsWithMediaTag";
const char MediaEngagementScore::kHighScoreChanges[] = "highScoreChanges";
const char MediaEngagementScore::kSignificantMediaPlaybacksKey[] =
"mediaElementPlaybacks";
const char MediaEngagementScore::kSignificantAudioContextPlaybacksKey[] =
"audioContextPlaybacks";
const char MediaEngagementScore::kScoreMinVisitsParamName[] = "min_visits";
const char MediaEngagementScore::kHighScoreLowerThresholdParamName[] =
"lower_threshold";
const char MediaEngagementScore::kHighScoreUpperThresholdParamName[] =
"upper_threshold";
namespace {
const int kScoreMinVisitsParamDefault = 20;
const double kHighScoreLowerThresholdParamDefault = 0.2;
const double kHighScoreUpperThresholdParamDefault = 0.3;
std::unique_ptr<base::DictionaryValue> GetMediaEngagementScoreDictForSettings(
const HostContentSettingsMap* settings,
const GURL& origin_url) {
if (!settings)
return std::make_unique<base::DictionaryValue>();
std::unique_ptr<base::DictionaryValue> value =
base::DictionaryValue::From(settings->GetWebsiteSetting(
origin_url, origin_url, CONTENT_SETTINGS_TYPE_MEDIA_ENGAGEMENT,
content_settings::ResourceIdentifier(), nullptr));
if (value.get())
return value;
return std::make_unique<base::DictionaryValue>();
}
} // namespace
// static.
double MediaEngagementScore::GetHighScoreLowerThreshold() {
return base::GetFieldTrialParamByFeatureAsDouble(
media::kMediaEngagementBypassAutoplayPolicies,
kHighScoreLowerThresholdParamName, kHighScoreLowerThresholdParamDefault);
}
// static.
double MediaEngagementScore::GetHighScoreUpperThreshold() {
return base::GetFieldTrialParamByFeatureAsDouble(
media::kMediaEngagementBypassAutoplayPolicies,
kHighScoreUpperThresholdParamName, kHighScoreUpperThresholdParamDefault);
}
// static.
int MediaEngagementScore::GetScoreMinVisits() {
return base::GetFieldTrialParamByFeatureAsInt(
media::kMediaEngagementBypassAutoplayPolicies, kScoreMinVisitsParamName,
kScoreMinVisitsParamDefault);
}
MediaEngagementScore::MediaEngagementScore(base::Clock* clock,
const GURL& origin,
HostContentSettingsMap* settings)
: MediaEngagementScore(
clock,
origin,
GetMediaEngagementScoreDictForSettings(settings, origin),
settings) {}
MediaEngagementScore::MediaEngagementScore(
base::Clock* clock,
const GURL& origin,
std::unique_ptr<base::DictionaryValue> score_dict,
HostContentSettingsMap* settings)
: origin_(origin),
clock_(clock),
score_dict_(score_dict.release()),
settings_map_(settings) {
if (!score_dict_)
return;
score_dict_->GetInteger(kVisitsKey, &visits_);
score_dict_->GetInteger(kMediaPlaybacksKey, &media_playbacks_);
score_dict_->GetBoolean(kHasHighScoreKey, &is_high_);
score_dict_->GetInteger(kAudiblePlaybacksKey, &audible_playbacks_);
score_dict_->GetInteger(kSignificantPlaybacksKey, &significant_playbacks_);
score_dict_->GetInteger(kVisitsWithMediaTagKey, &visits_with_media_tag_);
score_dict_->GetInteger(kHighScoreChanges, &high_score_changes_);
score_dict_->GetInteger(kSignificantMediaPlaybacksKey,
&media_element_playbacks_);
score_dict_->GetInteger(kSignificantAudioContextPlaybacksKey,
&audio_context_playbacks_);
double internal_time;
if (score_dict_->GetDouble(kLastMediaPlaybackTimeKey, &internal_time))
last_media_playback_time_ = base::Time::FromInternalValue(internal_time);
// Recalculate the total score and high bit. If the high bit changed we
// should commit this. This should only happen if we change the threshold
// or if we have data from before the bit was introduced.
bool was_high = is_high_;
Recalculate();
bool needs_commit = is_high_ != was_high;
// If we have a score for media playbacks and we do not have a value for
// both media element playbacks and audio context playbacks then we should
// copy media playbacks into media element playbacks.
bool has_new_playbacks_key_ =
score_dict_->FindKey(kSignificantMediaPlaybacksKey) ||
score_dict_->FindKey(kSignificantAudioContextPlaybacksKey);
if (!has_new_playbacks_key_) {
media_element_playbacks_ = media_playbacks_;
needs_commit = true;
}
// If we need to commit because of a migration and we have the settings map
// then we should commit.
if (needs_commit && settings_map_)
Commit();
}
// TODO(beccahughes): Add typemap.
media::mojom::MediaEngagementScoreDetailsPtr
MediaEngagementScore::GetScoreDetails() const {
return media::mojom::MediaEngagementScoreDetails::New(
origin_, actual_score(), visits(), media_playbacks(),
last_media_playback_time().ToJsTime(), high_score(), audible_playbacks(),
significant_playbacks(), high_score_changes(), audio_context_playbacks(),
media_element_playbacks());
}
MediaEngagementScore::~MediaEngagementScore() = default;
MediaEngagementScore::MediaEngagementScore(MediaEngagementScore&&) = default;
MediaEngagementScore& MediaEngagementScore::operator=(MediaEngagementScore&&) =
default;
void MediaEngagementScore::Commit() {
DCHECK(settings_map_);
if (!UpdateScoreDict())
return;
settings_map_->SetWebsiteSettingDefaultScope(
origin_, GURL(), CONTENT_SETTINGS_TYPE_MEDIA_ENGAGEMENT,
content_settings::ResourceIdentifier(), std::move(score_dict_));
}
void MediaEngagementScore::IncrementMediaPlaybacks() {
SetMediaPlaybacks(media_playbacks() + 1);
last_media_playback_time_ = clock_->Now();
}
bool MediaEngagementScore::UpdateScoreDict() {
int stored_visits = 0;
int stored_media_playbacks = 0;
double stored_last_media_playback_internal = 0;
bool is_high = false;
int stored_audible_playbacks = 0;
int stored_significant_playbacks = 0;
int stored_visits_with_media_tag = 0;
int high_score_changes = 0;
int stored_media_element_playbacks = 0;
int stored_audio_context_playbacks = 0;
score_dict_->GetInteger(kVisitsKey, &stored_visits);
score_dict_->GetInteger(kMediaPlaybacksKey, &stored_media_playbacks);
score_dict_->GetDouble(kLastMediaPlaybackTimeKey,
&stored_last_media_playback_internal);
score_dict_->GetBoolean(kHasHighScoreKey, &is_high);
score_dict_->GetInteger(kAudiblePlaybacksKey, &stored_audible_playbacks);
score_dict_->GetInteger(kSignificantPlaybacksKey,
&stored_significant_playbacks);
score_dict_->GetInteger(kVisitsWithMediaTagKey,
&stored_visits_with_media_tag);
score_dict_->GetInteger(kHighScoreChanges, &high_score_changes);
score_dict_->GetInteger(kSignificantMediaPlaybacksKey,
&stored_media_element_playbacks);
score_dict_->GetInteger(kSignificantAudioContextPlaybacksKey,
&stored_audio_context_playbacks);
bool changed = stored_visits != visits() ||
stored_media_playbacks != media_playbacks() ||
is_high_ != is_high ||
stored_audible_playbacks != audible_playbacks() ||
stored_significant_playbacks != significant_playbacks() ||
stored_visits_with_media_tag != visits_with_media_tag() ||
stored_media_element_playbacks != media_element_playbacks() ||
stored_audio_context_playbacks != audio_context_playbacks() ||
stored_last_media_playback_internal !=
last_media_playback_time_.ToInternalValue();
// Do not check for high_score_changes_ change as it MUST happen only if
// `changed` is true (ie. it can't happen alone).
DCHECK(high_score_changes == high_score_changes_ || changed);
if (!changed)
return false;
if (is_high_ != is_high)
++high_score_changes_;
score_dict_->SetInteger(kVisitsKey, visits_);
score_dict_->SetInteger(kMediaPlaybacksKey, media_playbacks_);
score_dict_->SetDouble(kLastMediaPlaybackTimeKey,
last_media_playback_time_.ToInternalValue());
score_dict_->SetBoolean(kHasHighScoreKey, is_high_);
score_dict_->SetInteger(kAudiblePlaybacksKey, audible_playbacks_);
score_dict_->SetInteger(kSignificantPlaybacksKey, significant_playbacks_);
score_dict_->SetInteger(kVisitsWithMediaTagKey, visits_with_media_tag_);
score_dict_->SetInteger(kHighScoreChanges, high_score_changes_);
score_dict_->SetInteger(kSignificantMediaPlaybacksKey,
media_element_playbacks_);
score_dict_->SetInteger(kSignificantAudioContextPlaybacksKey,
audio_context_playbacks_);
return true;
}
void MediaEngagementScore::Recalculate() {
// Use the minimum visits to compute the score to allow websites that would
// surely have a high MEI to pass the bar early.
double effective_visits = std::max(visits(), GetScoreMinVisits());
actual_score_ = static_cast<double>(media_playbacks()) / effective_visits;
// Recalculate whether the engagement score is considered high.
if (is_high_) {
is_high_ = actual_score_ >= GetHighScoreLowerThreshold();
} else {
is_high_ = actual_score_ >= GetHighScoreUpperThreshold();
}
}
void MediaEngagementScore::SetVisits(int visits) {
visits_ = visits;
Recalculate();
}
void MediaEngagementScore::SetMediaPlaybacks(int media_playbacks) {
media_playbacks_ = media_playbacks;
Recalculate();
}