| // 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(); |
| } |