blob: b33123855350a8d66ccab8c42512fc39cbe63ffa [file] [log] [blame]
// Copyright 2023 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "chrome/browser/picture_in_picture/auto_pip_setting_helper.h"
#include "base/feature_list.h"
#include "base/metrics/histogram_functions.h"
#include "chrome/browser/content_settings/host_content_settings_map_factory.h"
#include "chrome/browser/permissions/permission_decision_auto_blocker_factory.h"
#include "chrome/browser/profiles/profile.h"
#include "components/content_settings/core/browser/content_settings_utils.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_constraints.h"
#include "components/content_settings/core/common/content_settings_types.h"
#include "components/content_settings/core/common/content_settings_utils.h"
#include "components/permissions/features.h"
#include "components/permissions/permission_decision_auto_blocker.h"
#include "content/public/browser/web_contents.h"
#include "services/metrics/public/cpp/ukm_builders.h"
#include "services/metrics/public/cpp/ukm_recorder.h"
// static
std::unique_ptr<AutoPipSettingHelper>
AutoPipSettingHelper::CreateForWebContents(
content::WebContents* web_contents,
HostContentSettingsMap* settings_map,
permissions::PermissionDecisionAutoBlockerBase* auto_blocker) {
return std::make_unique<AutoPipSettingHelper>(
web_contents->GetLastCommittedURL(), settings_map, auto_blocker);
}
AutoPipSettingHelper::AutoPipSettingHelper(
const GURL& origin,
HostContentSettingsMap* settings_map,
permissions::PermissionDecisionAutoBlockerBase* auto_blocker)
: origin_(origin),
settings_map_(settings_map),
auto_blocker_(auto_blocker) {}
AutoPipSettingHelper::~AutoPipSettingHelper() = default;
#if !BUILDFLAG(IS_ANDROID)
void AutoPipSettingHelper::OnUserClosedWindow(
media::PictureInPictureEventsInfo::AutoPipReason auto_pip_reason,
std::optional<ukm::SourceId> source_id) {
if (ui_was_shown_but_not_acknowledged_) {
RecordResult(PromptResult::kIgnored, auto_pip_reason, std::move(source_id));
// Usually, this isn't needed, since any later pip window that reuses us
// will be for the same site and will still be set to 'ASK'. In that case,
// we'll show the permission UI. However, if the permission changes out
// from under us somehow (e.g., the user sets it to allow via the permission
// chip), then future windows might not show the prompt. This ensures that
// closing those windows, which were allowed, don't fiddle with the embargo.
// It doesn't really matter, but for completeness we do it.
ui_was_shown_but_not_acknowledged_ = false;
if (auto_blocker_) {
auto_blocker_->RecordDismissAndEmbargo(
origin_, ContentSettingsType::AUTO_PICTURE_IN_PICTURE,
/*dismissed_prompt_was_quiet=*/false);
}
}
}
#endif // !BUILDFLAG(IS_ANDROID)
ContentSetting AutoPipSettingHelper::GetEffectiveContentSetting() {
auto setting = settings_map_->GetContentSetting(
origin_, /*secondary_url=*/GURL(),
ContentSettingsType::AUTO_PICTURE_IN_PICTURE);
if (setting == CONTENT_SETTING_ASK && auto_blocker_) {
if (auto_blocker_->IsEmbargoed(
origin_, ContentSettingsType::AUTO_PICTURE_IN_PICTURE)) {
return CONTENT_SETTING_BLOCK;
}
}
return setting;
}
void AutoPipSettingHelper::UpdateContentSetting(ContentSetting new_setting) {
content_settings::ContentSettingConstraints constraints;
constraints.set_session_model(content_settings::mojom::SessionModel::DURABLE);
// Enable last-visit tracking for eligible permissions granted from
// Auto PiP bubble. This allows Safety Hub to auto-revoke the permission
// if the site is not visited for a finite amount of time.
if (base::FeatureList::IsEnabled(
permissions::features::
kSafetyHubUnusedPermissionRevocationForAllSurfaces) &&
new_setting &&
content_settings::CanBeAutoRevokedAsUnusedPermission(
ContentSettingsType::AUTO_PICTURE_IN_PICTURE,
content_settings::ContentSettingToValue(new_setting))) {
constraints.set_track_last_visit_for_autoexpiration(true);
}
settings_map_->SetContentSettingDefaultScope(
origin_, /*secondary_url=*/GURL(),
ContentSettingsType::AUTO_PICTURE_IN_PICTURE, new_setting, constraints);
}
#if !BUILDFLAG(IS_ANDROID)
AutoPipSettingHelper::ResultCb AutoPipSettingHelper::CreateResultCb(
base::OnceClosure close_pip_cb,
media::PictureInPictureEventsInfo::AutoPipReason auto_pip_reason,
std::optional<ukm::SourceId> source_id) {
weak_factory_.InvalidateWeakPtrs();
return base::BindOnce(&AutoPipSettingHelper::OnUiResult,
weak_factory_.GetWeakPtr(), std::move(close_pip_cb),
auto_pip_reason, std::move(source_id));
}
#endif // !BUILDFLAG(IS_ANDROID)
#if !BUILDFLAG(IS_ANDROID)
std::unique_ptr<AutoPipSettingOverlayView>
AutoPipSettingHelper::CreateOverlayViewIfNeeded(
base::OnceClosure close_pip_cb,
media::PictureInPictureEventsInfo::AutoPipReason auto_pip_reason,
std::optional<ukm::SourceId> source_id,
views::View* anchor_view,
views::BubbleBorder::Arrow arrow) {
switch (GetEffectiveContentSetting()) {
case CONTENT_SETTING_ASK:
// If the user already said to allow once, then continue allowing. It's
// assumed that we're used for at most one visit to a site.
if (already_selected_allow_once_) {
RecordResult(PromptResult::kNotShownAllowedOnce, auto_pip_reason,
std::move(source_id));
return nullptr;
}
// Create and return the UI to ask the user.
ui_was_shown_but_not_acknowledged_ = true;
return std::make_unique<AutoPipSettingOverlayView>(
CreateResultCb(std::move(close_pip_cb), auto_pip_reason,
std::move(source_id)),
origin_, anchor_view, arrow);
case CONTENT_SETTING_ALLOW:
// Nothing to do -- allow the auto pip to proceed.
RecordResult(PromptResult::kNotShownAllowedOnEveryVisit, auto_pip_reason,
std::move(source_id));
return nullptr;
case CONTENT_SETTING_BLOCK:
// Auto-pip is not allowed. Close the window.
RecordResult(PromptResult::kNotShownBlocked, auto_pip_reason,
std::move(source_id));
std::move(close_pip_cb).Run();
return nullptr;
default:
NOTREACHED() << " AutoPiP unknown effective content setting";
}
}
#endif // !BUILDFLAG(IS_ANDROID)
void AutoPipSettingHelper::OnAutoPipBlockedByPermission(
media::PictureInPictureEventsInfo::AutoPipReason auto_pip_reason,
std::optional<ukm::SourceId> source_id) {
RecordResult(PromptResult::kNotShownBlocked, auto_pip_reason,
std::move(source_id));
}
void AutoPipSettingHelper::OnAutoPipBlockedByIncognito(
media::PictureInPictureEventsInfo::AutoPipReason auto_pip_reason) {
RecordResult(PromptResult::kNotShownIncognito, auto_pip_reason, std::nullopt);
}
#if !BUILDFLAG(IS_ANDROID)
void AutoPipSettingHelper::OnUiResult(
base::OnceClosure close_pip_cb,
media::PictureInPictureEventsInfo::AutoPipReason auto_pip_reason,
std::optional<ukm::SourceId> source_id,
AutoPipSettingView::UiResult result) {
// The UI was both shown and acknowledged, so we don't have to worry about it
// being dismissed without being acted on for the permission embargo.
ui_was_shown_but_not_acknowledged_ = false;
switch (result) {
case AutoPipSettingView::UiResult::kBlock:
RecordResult(PromptResult::kBlock, auto_pip_reason, std::move(source_id));
UpdateContentSetting(CONTENT_SETTING_BLOCK);
// Also close the pip window.
std::move(close_pip_cb).Run();
break;
case AutoPipSettingView::UiResult::kAllowOnEveryVisit:
RecordResult(PromptResult::kAllowOnEveryVisit, auto_pip_reason,
std::move(source_id));
UpdateContentSetting(CONTENT_SETTING_ALLOW);
break;
case AutoPipSettingView::UiResult::kAllowOnce:
already_selected_allow_once_ = true;
RecordResult(PromptResult::kAllowOnce, auto_pip_reason,
std::move(source_id));
// Leave at 'ASK'. Do not update the embargo, since the user allowed the
// feature to continue. If anything, this should vote for 'anti-embargo'.
break;
}
}
#endif // !BUILDFLAG(IS_ANDROID)
void AutoPipSettingHelper::RecordResult(
PromptResult result,
media::PictureInPictureEventsInfo::AutoPipReason auto_pip_reason,
std::optional<ukm::SourceId> source_id) {
base::UmaHistogramEnumeration("Media.AutoPictureInPicture.PromptResultV2",
result);
switch (auto_pip_reason) {
case media::PictureInPictureEventsInfo::AutoPipReason::kUnknown:
break;
case media::PictureInPictureEventsInfo::AutoPipReason::kVideoConferencing:
base::UmaHistogramEnumeration(
"Media.AutoPictureInPicture.EnterPictureInPicture.AutomaticReason."
"VideoConferencing.PromptResultV2",
result);
base::UmaHistogramEnumeration(
"Media.AutoPictureInPicture.EnterPictureInPicture.AutomaticReasonV2."
"VideoConferencing.PromptResultV2",
result);
RecordUkms(auto_pip_reason, source_id, result);
break;
case media::PictureInPictureEventsInfo::AutoPipReason::kMediaPlayback:
base::UmaHistogramEnumeration(
"Media.AutoPictureInPicture.EnterPictureInPicture.AutomaticReason."
"MediaPlayback.PromptResultV2",
result);
base::UmaHistogramEnumeration(
"Media.AutoPictureInPicture.EnterPictureInPicture.AutomaticReasonV2."
"MediaPlayback.PromptResultV2",
result);
RecordUkms(auto_pip_reason, source_id, result);
break;
case media::PictureInPictureEventsInfo::AutoPipReason::kBrowserInitiated:
base::UmaHistogramEnumeration(
"Media.AutoPictureInPicture.EnterPictureInPicture.AutomaticReasonV2."
"BrowserInitiated.PromptResultV2",
result);
RecordUkms(auto_pip_reason, source_id, result);
break;
}
}
void AutoPipSettingHelper::RecordUkms(
media::PictureInPictureEventsInfo::AutoPipReason auto_pip_reason,
std::optional<ukm::SourceId> source_id,
PromptResult result) const {
ukm::UkmRecorder* ukm_recorder = ukm::UkmRecorder::Get();
if (!ukm_recorder || !source_id) {
return;
}
switch (auto_pip_reason) {
case media::PictureInPictureEventsInfo::AutoPipReason::kUnknown:
break;
case media::PictureInPictureEventsInfo::AutoPipReason::kVideoConferencing:
ukm::builders::
Media_AutoPictureInPicture_EnterPictureInPicture_AutomaticReason_PromptResultV2(
source_id.value())
.SetVideoConferencing(static_cast<uintmax_t>(result))
.Record(ukm_recorder);
break;
case media::PictureInPictureEventsInfo::AutoPipReason::kMediaPlayback:
ukm::builders::
Media_AutoPictureInPicture_EnterPictureInPicture_AutomaticReason_PromptResultV2(
source_id.value())
.SetMediaPlayback(static_cast<uintmax_t>(result))
.Record(ukm_recorder);
break;
case media::PictureInPictureEventsInfo::AutoPipReason::kBrowserInitiated:
ukm::builders::
Media_AutoPictureInPicture_EnterPictureInPicture_AutomaticReason_PromptResultV2(
source_id.value())
.SetBrowserInitiated(static_cast<uintmax_t>(result))
.Record(ukm_recorder);
break;
}
}