| // 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. |
| |
| #ifndef CHROME_BROWSER_PICTURE_IN_PICTURE_AUTO_PIP_SETTING_HELPER_H_ |
| #define CHROME_BROWSER_PICTURE_IN_PICTURE_AUTO_PIP_SETTING_HELPER_H_ |
| |
| #include "base/functional/callback.h" |
| #include "base/memory/weak_ptr.h" |
| #if !BUILDFLAG(IS_ANDROID) |
| #include "chrome/browser/picture_in_picture/auto_pip_setting_overlay_view.h" |
| #endif // !BUILDFLAG(IS_ANDROID) |
| #include "components/content_settings/core/common/content_settings.h" |
| #include "content/public/browser/render_frame_host.h" |
| #include "content/public/browser/web_contents.h" |
| #include "media/base/picture_in_picture_events_info.h" |
| #include "url/gurl.h" |
| |
| namespace content { |
| class WebContents; |
| } // namespace content |
| |
| namespace permissions { |
| class PermissionDecisionAutoBlockerBase; |
| } // namespace permissions |
| |
| #if !BUILDFLAG(IS_ANDROID) |
| namespace views { |
| class View; |
| } // namespace views |
| #endif // !BUILDFLAG(IS_ANDROID) |
| |
| class HostContentSettingsMap; |
| |
| // Helper class to manage the content setting for AutoPiP, including the |
| // permissions embargo. It's intended to be kept around for the duration of the |
| // visit to the site, so that 'allow once' can be sticky until navigation. It |
| // does not detect navigation; somebody else should get a new instance. |
| class AutoPipSettingHelper { |
| public: |
| // These values are persisted to logs. Entries should not be renumbered and |
| // numeric values should never be reused. |
| enum class PromptResult { |
| // The user closed the PiP window before selecting a choice. Note that this |
| // will not be recorded when the PiP window is closed automatically by the |
| // user focusing the original tab. |
| kIgnored = 0, |
| |
| // The user chose to block automatic picture-in-picture. |
| kBlock = 1, |
| |
| // The user chose to allow automatic picture-in-picture on every visit. |
| kAllowOnEveryVisit = 2, |
| |
| // The user chose to allow automatic picture-in-picture this time. |
| kAllowOnce = 3, |
| |
| // The prompt was not shown because the user has already allowed automatic |
| // picture-in-picture for every visit. |
| kNotShownAllowedOnEveryVisit = 4, |
| |
| // The prompt was not shown because the user has already allowed automatic |
| // picture-in-picture for this visit. |
| kNotShownAllowedOnce = 5, |
| |
| // The prompt was not shown because the user has already blocked automatic |
| // picture-in-picture (and therefore no picture-in-picture was shown at |
| // all). |
| kNotShownBlocked = 6, |
| |
| // The prompt was not shown because automatic picture-in-picture was blocked |
| // due to the user being in incognito mode. |
| kNotShownIncognito = 7, |
| |
| kMaxValue = kNotShownIncognito, |
| }; |
| |
| #if !BUILDFLAG(IS_ANDROID) |
| using ResultCb = |
| base::OnceCallback<void(AutoPipSettingView::UiResult result)>; |
| #endif // !BUILDFLAG(IS_ANDROID) |
| |
| // Convenience function. |
| static std::unique_ptr<AutoPipSettingHelper> CreateForWebContents( |
| content::WebContents* web_contents, |
| HostContentSettingsMap* settings_map, |
| permissions::PermissionDecisionAutoBlockerBase* auto_blocker); |
| |
| // We'll use `close_pip_cb` to close the pip window as needed. It should be |
| // safe to call at any time. It is up to our caller to make sure that we are |
| // destroyed if `settings_map` is. |
| AutoPipSettingHelper( |
| const GURL& origin, |
| HostContentSettingsMap* settings_map, |
| permissions::PermissionDecisionAutoBlockerBase* auto_blocker); |
| ~AutoPipSettingHelper(); |
| |
| AutoPipSettingHelper(const AutoPipSettingHelper&) = delete; |
| AutoPipSettingHelper(AutoPipSettingHelper&&) = delete; |
| |
| #if !BUILDFLAG(IS_ANDROID) |
| // Notify us that the user has closed the window. This will cause the embargo |
| // to be updated if needed. |
| void OnUserClosedWindow( |
| media::PictureInPictureEventsInfo::AutoPipReason auto_pip_reason, |
| std::optional<ukm::SourceId> source_id); |
| |
| // Create an AutoPipSettingOverlayView that should be used as the overlay view |
| // when the content setting is ASK. This view will call back to us, so we |
| // should outlive it. Will return nullptr if no UI is needed, and will |
| // optionally call `close_pip_cb_` if AutoPiP is blocked. |
| std::unique_ptr<AutoPipSettingOverlayView> 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); |
| #endif // !BUILDFLAG(IS_ANDROID) |
| |
| // Called by the AutoPictureInPictureTabHelper when automatic |
| // picture-in-picture has been preemptively blocked. Used to record various |
| // `PromptResultV2` metrics. |
| void OnAutoPipBlockedByPermission( |
| media::PictureInPictureEventsInfo::AutoPipReason auto_pip_reason, |
| std::optional<ukm::SourceId> source_id); |
| void OnAutoPipBlockedByIncognito( |
| media::PictureInPictureEventsInfo::AutoPipReason auto_pip_reason); |
| |
| private: |
| // Returns the content setting, modified as needed by any embargo. |
| ContentSetting GetEffectiveContentSetting(); |
| |
| // Update the content setting to `new_setting`, and clear any embargo. |
| void UpdateContentSetting(ContentSetting new_setting); |
| |
| #if !BUILDFLAG(IS_ANDROID) |
| // Notify us that the user has interacted with the content settings UI that's |
| // displayed in the pip window. `close_pip_cb` will be called if the result |
| // is 'block'. |
| // |
| // `auto_pip_reason` and `source_id` are used for recording various tab helper |
| // related metrics. |
| void OnUiResult( |
| base::OnceClosure close_pip_cb, |
| media::PictureInPictureEventsInfo::AutoPipReason auto_pip_reason, |
| std::optional<ukm::SourceId> source_id, |
| AutoPipSettingView::UiResult result); |
| |
| // Return a new ResultCb, and invalidate any previous ones. |
| ResultCb CreateResultCb( |
| base::OnceClosure close_pip_cb, |
| media::PictureInPictureEventsInfo::AutoPipReason auto_pip_reason, |
| std::optional<ukm::SourceId> source_id); |
| #endif // !BUILDFLAG(IS_ANDROID) |
| |
| // Record metrics for the result of the prompt. |
| // |
| // Records the various prompt results and the prompt results for each of the |
| // reasons for entering auto picture in picture: video conferencing or media |
| // playback. |
| void RecordResult( |
| PromptResult result, |
| media::PictureInPictureEventsInfo::AutoPipReason auto_pip_reason, |
| std::optional<ukm::SourceId> source_id); |
| void RecordUkms( |
| media::PictureInPictureEventsInfo::AutoPipReason auto_pip_reason, |
| std::optional<ukm::SourceId> source_id, |
| PromptResult result) const; |
| |
| GURL origin_; |
| const raw_ptr<HostContentSettingsMap> settings_map_ = nullptr; |
| const raw_ptr<permissions::PermissionDecisionAutoBlockerBase> auto_blocker_ = |
| nullptr; |
| |
| #if !BUILDFLAG(IS_ANDROID) |
| base::OnceClosure close_pip_cb_; |
| |
| // If true, then we've shown the UI but the user hasn't picked an option yet. |
| bool ui_was_shown_but_not_acknowledged_ = false; |
| |
| // Has the user clicked 'allow once' on any permission UI we've created? |
| bool already_selected_allow_once_ = false; |
| #endif // !BUILDFLAG(IS_ANDROID) |
| |
| base::WeakPtrFactory<AutoPipSettingHelper> weak_factory_{this}; |
| }; |
| |
| #endif // CHROME_BROWSER_PICTURE_IN_PICTURE_AUTO_PIP_SETTING_HELPER_H_ |