From: uazo Date: Fri, 8 Apr 2022 11:04:04 +0000 Subject: Add lifetime options for permissions Indicate the session mode for content-settings by using the constraint `content_settings::SessionModel` as UserSession when setting the value, and also make use of an expiration time value. This is used in Chromium for `ClientHints` but it is generally possible to use this functionality when a specific value needs to be persisted by origin. All content settings of this type are not saved on disk (except for the `Forever` option), allowing user to reset the status each time application is restarted. There are 4 main areas affected to introduce the functionality: * components/content_settings A new `content_settings::LifetimeMode` enum value is defined to specify the user's choice (Always, OnlyThisTime, UntilOriginClosed, UntilBrowserClosed). Enumeration is also generated for java by adding it in `content_settings_enums_javagen` (gn). This is mainly used in `content_settings_utils.cc` to create a specialised `content_settings::ContentSettingConstraints` that is then used in `SetContentSettingDefaultScope()` by `PermissionContextBase::UpdateContentSetting`. Existing Chromium data structures do not provide a specific property to define a choice which is instead encoded through the `ContentSettingConstraints`; this approach is already used in other parts of the Chromium codebase so it is not novel here. Therefore, `content_settings::GetConstraintSessionExpiration()` and `content_settings::IsConstraintSessionExpiration()` manage the lifetime modes of the session content-settings. The modificaiton also adds the session pattern to the ContentSettingPatternSource so that it is available for the UI. * components/permissions Lifetime support is added to the permissions; most of the changes are caused by the fact that it is necessary to report the value selected by the user from the Java UI managed by `components/browser_ui` up to `PermissionContextBase::UpdateContentSetting()`, without necessarily having to modify all requests that are not related to geolocation/camera/microphone. The approach used is a new `PermissionRequest::PermissionDecidedCallbackWithLifetime` used by an overload of `PermissionContextBase::CreatePermissionRequest` so that options are present only for the specific content-settings (see `PermissionDialogModel.java`). For other permissions no behaviour is changed (see `PermissionDialogDelegate::Accept`); for geolocation it was necessary to act directly in the specific context, because, unlike microphone/camera, the content-setting value is inserted in its specific method (`FinishNotifyPermissionSet`, that calls the callback), even if the class always derives from `PermissionContextBase`. * components/page_info Some changes needed to see in the summary of the `page_info` the text "(only this session)" (aka `page_info_android_permission_session_permission`) through adding a new property "is_user_session" in `PageInfoPermissionEntry` (Java). * components/browser_ui Changes to the Settings UI to show "(only this session)" in the specific content-setting. The same view is used both in the settings and in the page_info. For the management of `UntilOriginClosed` the logic used by flag `kOneTimeGeolocationPermission` was used; this flag is active only in the desktop (files `last_tab_standing_tracker_*`). It is a class that manages a list of the active origins and allows to perform operations when all the tabs relating to that origin have been closed, in this case deleting the session content settings of `UntilOriginClosed`. See also: https://github.com/bromite/bromite/issues/1549 Original License: GPL-2.0-or-later - https://spdx.org/licenses/GPL-2.0-or-later.html License: GPL-3.0-only - https://spdx.org/licenses/GPL-3.0-only.html --- .../permissions/last_tab_standing_tracker.cc | 32 ++++++++ .../one_time_permissions_tracker.cc | 33 +++++++- .../one_time_permissions_tracker.h | 5 +- .../one_time_permissions_tracker_factory.cc | 2 +- .../permissions/embedded_permission_prompt.cc | 2 +- .../permission_prompt_bubble_base_view.cc | 2 +- .../site_settings/PermissionInfo.java | 1 + .../site_settings/SingleWebsiteSettings.java | 9 ++ .../android/website_preference_bridge.cc | 2 +- .../strings/android/browser_ui_strings.grd | 5 ++ .../core/browser/content_settings_utils.cc | 36 ++++++++ .../core/browser/content_settings_utils.h | 6 ++ .../common/content_settings_constraints.h | 9 ++ .../page_info/PageInfoController.java | 4 +- .../PermissionParamsListBuilder.java | 13 ++- .../android/page_info_controller_android.cc | 10 ++- components/page_info/page_info.cc | 2 + components/page_info/page_info.h | 1 + .../permissions/PermissionDialogDelegate.java | 13 +++ .../PermissionDialogModelFactory.java | 82 ++++++++++++++++++- .../permission_dialog_delegate.cc | 24 +++++- .../permission_dialog_delegate.h | 1 + .../permission_prompt_android.cc | 8 +- .../permission_prompt_android.h | 3 +- .../android/permissions_android_strings.grd | 17 ++++ .../geolocation_permission_context_android.cc | 34 ++++++-- .../geolocation_permission_context_android.h | 13 ++- .../permissions/permission_context_base.cc | 52 ++++++++++-- .../permissions/permission_context_base.h | 23 +++++- components/permissions/permission_prompt.h | 3 +- components/permissions/permission_request.cc | 30 ++++++- components/permissions/permission_request.h | 16 +++- .../permissions/permission_request_manager.cc | 45 ++++++---- .../permissions/permission_request_manager.h | 11 ++- 34 files changed, 488 insertions(+), 61 deletions(-) diff --git a/chrome/browser/permissions/last_tab_standing_tracker.cc b/chrome/browser/permissions/last_tab_standing_tracker.cc --- a/chrome/browser/permissions/last_tab_standing_tracker.cc +++ b/chrome/browser/permissions/last_tab_standing_tracker.cc @@ -7,6 +7,32 @@ #include "base/observer_list.h" #include "url/gurl.h" +#include "components/content_settings/core/browser/host_content_settings_map.h" +#include "components/content_settings/core/common/content_settings_utils.h" +#include "components/permissions/permissions_client.h" + +namespace { + // Remove all sessions content setting by origin and type + void RemoveSessionSettings(HostContentSettingsMap* content_settings, + const url::Origin& origin, + ContentSettingsType type) { + ContentSettingsForOneType session_settings = + content_settings->GetSettingsForOneType( + type, content_settings::SessionModel::UserSession); + + GURL url = origin.GetURL(); + for (ContentSettingPatternSource& entry : session_settings) { + if (content_settings::IsConstraintSessionExpiration(entry, + content_settings::LifetimeMode::UntilOriginClosed) && + entry.primary_pattern.Matches(url)) { + content_settings->SetWebsiteSettingCustomScope( + entry.primary_pattern, entry.secondary_pattern, + type, base::Value()); + } + } + } +} + LastTabStandingTracker::LastTabStandingTracker(content::BrowserContext* context) : context_(context) {} @@ -56,4 +82,10 @@ void LastTabStandingTracker::WebContentsUnloadedOrigin( for (auto& observer : observer_list_) { observer.OnLastPageFromOriginClosed(origin); } + HostContentSettingsMap* content_settings = + permissions::PermissionsClient::Get()->GetSettingsMap(context_); + RemoveSessionSettings(content_settings, origin, ContentSettingsType::GEOLOCATION); + RemoveSessionSettings(content_settings, origin, ContentSettingsType::MEDIASTREAM_MIC); + RemoveSessionSettings(content_settings, origin, ContentSettingsType::MEDIASTREAM_CAMERA); + } } diff --git a/chrome/browser/permissions/one_time_permissions_tracker.cc b/chrome/browser/permissions/one_time_permissions_tracker.cc --- a/chrome/browser/permissions/one_time_permissions_tracker.cc +++ b/chrome/browser/permissions/one_time_permissions_tracker.cc @@ -19,8 +19,34 @@ #include "components/permissions/permission_context_base.h" #include "content/public/browser/visibility.h" #include "url/gurl.h" +#include "components/content_settings/core/browser/host_content_settings_map.h" +#include "components/content_settings/core/common/content_settings_utils.h" +#include "components/permissions/permissions_client.h" + +namespace { + // Remove all sessions content setting by origin and type + void RemoveSessionSettings(HostContentSettingsMap* content_settings, + const url::Origin& origin, + ContentSettingsType type) { + ContentSettingsForOneType session_settings = + content_settings->GetSettingsForOneType( + type, content_settings::SessionModel::UserSession); + + GURL url = origin.GetURL(); + for (ContentSettingPatternSource& entry : session_settings) { + if (content_settings::IsConstraintSessionExpiration(entry, + content_settings::LifetimeMode::UntilOriginClosed) && + entry.primary_pattern.Matches(url)) { + content_settings->SetWebsiteSettingCustomScope( + entry.primary_pattern, entry.secondary_pattern, + type, base::Value()); + } + } + } +} -OneTimePermissionsTracker::OneTimePermissionsTracker() = default; +OneTimePermissionsTracker::OneTimePermissionsTracker(content::BrowserContext* context) + : context_(context) {} OneTimePermissionsTracker::~OneTimePermissionsTracker() = default; OneTimePermissionsTracker::OriginTrackEntry::OriginTrackEntry() = default; @@ -258,6 +284,11 @@ void OneTimePermissionsTracker::NotifyLastPageFromOriginClosed( for (auto& observer : observer_list_) { observer.OnLastPageFromOriginClosed(origin); } + HostContentSettingsMap* content_settings = + permissions::PermissionsClient::Get()->GetSettingsMap(context_); + RemoveSessionSettings(content_settings, origin, ContentSettingsType::GEOLOCATION); + RemoveSessionSettings(content_settings, origin, ContentSettingsType::MEDIASTREAM_MIC); + RemoveSessionSettings(content_settings, origin, ContentSettingsType::MEDIASTREAM_CAMERA); } bool OneTimePermissionsTracker::ShouldIgnoreOrigin(const url::Origin& origin) { diff --git a/chrome/browser/permissions/one_time_permissions_tracker.h b/chrome/browser/permissions/one_time_permissions_tracker.h --- a/chrome/browser/permissions/one_time_permissions_tracker.h +++ b/chrome/browser/permissions/one_time_permissions_tracker.h @@ -12,6 +12,7 @@ #include "base/timer/timer.h" #include "chrome/browser/permissions/one_time_permissions_tracker_observer.h" #include "components/content_settings/core/common/content_settings_pattern.h" +#include "chrome/browser/profiles/profile.h" #include "components/content_settings/core/common/content_settings_types.h" #include "components/keyed_service/core/keyed_service.h" #include "content/public/browser/visibility.h" @@ -24,7 +25,7 @@ class OneTimePermissionsTracker : public KeyedService { void (OneTimePermissionsTracker::*)(const url::Origin&); public: - OneTimePermissionsTracker(); + OneTimePermissionsTracker(content::BrowserContext* context); ~OneTimePermissionsTracker() override; OneTimePermissionsTracker(const OneTimePermissionsTracker&) = delete; @@ -139,7 +140,7 @@ class OneTimePermissionsTracker : public KeyedService { base::ObserverList observer_list_; std::map origin_tracker_; - + raw_ptr context_; base::WeakPtrFactory weak_factory_{this}; }; diff --git a/chrome/browser/permissions/one_time_permissions_tracker_factory.cc b/chrome/browser/permissions/one_time_permissions_tracker_factory.cc --- a/chrome/browser/permissions/one_time_permissions_tracker_factory.cc +++ b/chrome/browser/permissions/one_time_permissions_tracker_factory.cc @@ -41,5 +41,5 @@ bool OneTimePermissionsTrackerFactory::ServiceIsCreatedWithBrowserContext() std::unique_ptr OneTimePermissionsTrackerFactory::BuildServiceInstanceForBrowserContext( content::BrowserContext* context) const { - return std::make_unique(); + return std::make_unique(context); } diff --git a/chrome/browser/ui/views/permissions/embedded_permission_prompt.cc b/chrome/browser/ui/views/permissions/embedded_permission_prompt.cc --- a/chrome/browser/ui/views/permissions/embedded_permission_prompt.cc +++ b/chrome/browser/ui/views/permissions/embedded_permission_prompt.cc @@ -237,7 +237,7 @@ void EmbeddedPermissionPrompt::Allow() { } void EmbeddedPermissionPrompt::AllowThisTime() { - delegate_->AcceptThisTime(); + delegate_->AcceptThisTime(content_settings::LifetimeMode::OnlyThisTime); CloseCurrentViewAndMaybeShowNext(/*first_prompt=*/false); } diff --git a/chrome/browser/ui/views/permissions/permission_prompt_bubble_base_view.cc b/chrome/browser/ui/views/permissions/permission_prompt_bubble_base_view.cc --- a/chrome/browser/ui/views/permissions/permission_prompt_bubble_base_view.cc +++ b/chrome/browser/ui/views/permissions/permission_prompt_bubble_base_view.cc @@ -267,7 +267,7 @@ void PermissionPromptBubbleBaseView::RunButtonCallback(int button_id) { delegate_->Accept(); return; case PermissionDialogButton::kAcceptOnce: - delegate_->AcceptThisTime(); + delegate_->AcceptThisTime(content_settings::LifetimeMode::OnlyThisTime); return; case PermissionDialogButton::kDeny: delegate_->Deny(); diff --git a/components/browser_ui/site_settings/android/java/src/org/chromium/components/browser_ui/site_settings/PermissionInfo.java b/components/browser_ui/site_settings/android/java/src/org/chromium/components/browser_ui/site_settings/PermissionInfo.java --- a/components/browser_ui/site_settings/android/java/src/org/chromium/components/browser_ui/site_settings/PermissionInfo.java +++ b/components/browser_ui/site_settings/android/java/src/org/chromium/components/browser_ui/site_settings/PermissionInfo.java @@ -10,6 +10,7 @@ import org.chromium.components.content_settings.ContentSettingValues; import org.chromium.components.content_settings.ContentSettingsType; import org.chromium.components.content_settings.SessionModel; import org.chromium.content_public.browser.BrowserContextHandle; +import org.chromium.components.content_settings.SessionModel; import java.io.Serializable; diff --git a/components/browser_ui/site_settings/android/java/src/org/chromium/components/browser_ui/site_settings/SingleWebsiteSettings.java b/components/browser_ui/site_settings/android/java/src/org/chromium/components/browser_ui/site_settings/SingleWebsiteSettings.java --- a/components/browser_ui/site_settings/android/java/src/org/chromium/components/browser_ui/site_settings/SingleWebsiteSettings.java +++ b/components/browser_ui/site_settings/android/java/src/org/chromium/components/browser_ui/site_settings/SingleWebsiteSettings.java @@ -559,6 +559,11 @@ public class SingleWebsiteSettings extends BaseSiteSettingsFragment } } + private boolean isSessionPermission(@ContentSettingsType.EnumType int type) { + return mSite.getPermissionInfo(type) != null && + mSite.getPermissionInfo(type).getSessionModel() == SessionModel.USER_SESSION; + } + private void setUpClearDataPreference() { ClearWebsiteStorage preference = findPreference(PREF_CLEAR_DATA); long usage = mSite.getTotalUsage(); @@ -1070,6 +1075,10 @@ public class SingleWebsiteSettings extends BaseSiteSettingsFragment if (contentType == mHighlightedPermission) { switchPreference.setBackgroundColor(mHighlightColor); } + if (isSessionPermission(contentType)) { + switchPreference.setSummary(switchPreference.getSummary() + " " + + getString(R.string.page_info_android_permission_session_permission)); + } } /** diff --git a/components/browser_ui/site_settings/android/website_preference_bridge.cc b/components/browser_ui/site_settings/android/website_preference_bridge.cc --- a/components/browser_ui/site_settings/android/website_preference_bridge.cc +++ b/components/browser_ui/site_settings/android/website_preference_bridge.cc @@ -198,7 +198,7 @@ void GetOrigins(JNIEnv* env, seen_origins.push_back(origin); insertionFunc(env, static_cast(content_type), list, ConvertOriginToJavaString(env, origin), jembedder, - /*is_embargoed=*/true, /*is_one_time=*/false); + /*is_embargoed=*/true, 0); } } } diff --git a/components/browser_ui/strings/android/browser_ui_strings.grd b/components/browser_ui/strings/android/browser_ui_strings.grd --- a/components/browser_ui/strings/android/browser_ui_strings.grd +++ b/components/browser_ui/strings/android/browser_ui_strings.grd @@ -673,6 +673,11 @@ URL truncated + + (only this session) + + Ad privacy diff --git a/components/content_settings/core/browser/content_settings_utils.cc b/components/content_settings/core/browser/content_settings_utils.cc --- a/components/content_settings/core/browser/content_settings_utils.cc +++ b/components/content_settings/core/browser/content_settings_utils.cc @@ -174,6 +174,42 @@ bool IsConstraintPersistent(const ContentSettingConstraints& constraints) { return constraints.session_model() == SessionModel::Durable; } +ContentSettingConstraints GetConstraintSessionExpiration(LifetimeMode lifetime_mode) { + int lifetime; + base::Time now; + if (lifetime_mode == LifetimeMode::OnlyThisTime) { + // note: this content settings will be discarded immediately + // 1h is used as a magic constant to identify the one-time lifetime mode + lifetime = 1; + } else if (lifetime_mode == LifetimeMode::UntilOriginClosed) { + now = base::Time::Now(); + lifetime = 24; + } else { + lifetime = 0; + } + content_settings::ContentSettingConstraints c(now); + c.set_lifetime(base::Hours(lifetime)); + c.set_session_model(content_settings::SessionModel::UserSession); + return c; +} + +bool IsConstraintSessionExpiration(const ContentSettingPatternSource& source, + LifetimeMode lifetime_mode) { + if (source.metadata.session_model() != content_settings::SessionModel::UserSession) + return false; + + LifetimeMode type; + if (source.metadata.lifetime() == base::Hours(24)) { + type = LifetimeMode::UntilOriginClosed; + } else if (source.metadata.expiration() == (base::Time() + base::Hours(1))) { + type = LifetimeMode::OnlyThisTime; + } else { + type = LifetimeMode::UntilBrowserClosed; + } + + return lifetime_mode == type; +} + bool CanTrackLastVisit(ContentSettingsType type) { // Last visit is not tracked for notification permission as it shouldn't be // auto-revoked. diff --git a/components/content_settings/core/browser/content_settings_utils.h b/components/content_settings/core/browser/content_settings_utils.h --- a/components/content_settings/core/browser/content_settings_utils.h +++ b/components/content_settings/core/browser/content_settings_utils.h @@ -73,6 +73,12 @@ bool IsConstraintPersistent(const ContentSettingConstraints& constraints); // Returns whether the given type supports tracking last_visit timestamps. bool CanTrackLastVisit(ContentSettingsType type); +ContentSettingConstraints GetConstraintSessionExpiration(LifetimeMode lifetime_mode); + +bool IsConstraintSessionExpiration( + const ContentSettingPatternSource& source, + LifetimeMode lifetime_mode); + // Get a timestamp with week-precision. base::Time GetCoarseVisitedTime(base::Time time); diff --git a/components/content_settings/core/common/content_settings_constraints.h b/components/content_settings/core/common/content_settings_constraints.h --- a/components/content_settings/core/common/content_settings_constraints.h +++ b/components/content_settings/core/common/content_settings_constraints.h @@ -33,6 +33,15 @@ enum class SessionModel : int32_t { kMaxValue = OneTime, }; +// GENERATED_JAVA_ENUM_PACKAGE: org.chromium.components.content_settings +// GENERATED_JAVA_CLASS_NAME_OVERRIDE: LifetimeMode +enum class LifetimeMode { + Always = 99, + OnlyThisTime = 1, + UntilOriginClosed = 2, + UntilBrowserClosed = 0, +}; + // Constraints to be applied when setting a content setting. class ContentSettingConstraints { public: diff --git a/components/page_info/android/java/src/org/chromium/components/page_info/PageInfoController.java b/components/page_info/android/java/src/org/chromium/components/page_info/PageInfoController.java --- a/components/page_info/android/java/src/org/chromium/components/page_info/PageInfoController.java +++ b/components/page_info/android/java/src/org/chromium/components/page_info/PageInfoController.java @@ -378,9 +378,9 @@ public class PageInfoController String name, String nameMidSentence, int type, - @ContentSettingValues int currentSettingValue) { + @ContentSettingValues int currentSettingValue, boolean is_user_session) { mPermissionParamsListBuilder.addPermissionEntry( - name, nameMidSentence, type, currentSettingValue); + name, nameMidSentence, type, currentSettingValue, is_user_session); } /** Update the permissions view based on the contents of mDisplayedPermissions. */ diff --git a/components/page_info/android/java/src/org/chromium/components/page_info/PermissionParamsListBuilder.java b/components/page_info/android/java/src/org/chromium/components/page_info/PermissionParamsListBuilder.java --- a/components/page_info/android/java/src/org/chromium/components/page_info/PermissionParamsListBuilder.java +++ b/components/page_info/android/java/src/org/chromium/components/page_info/PermissionParamsListBuilder.java @@ -44,8 +44,9 @@ public class PermissionParamsListBuilder { } public void addPermissionEntry( - String name, String nameMidSentence, int type, @ContentSettingValues int value) { - mEntries.add(new PageInfoPermissionEntry(name, nameMidSentence, type, value)); + String name, String nameMidSentence, int type, @ContentSettingValues int value, + boolean is_user_session) { + mEntries.add(new PageInfoPermissionEntry(name, nameMidSentence, type, value, is_user_session)); } public void clearPermissionEntries() { @@ -86,6 +87,10 @@ public class PermissionParamsListBuilder { permissionParams.warningTextResource = R.string.page_info_android_permission_blocked; } + if (permission.is_user_session) { + permissionParams.warningTextResource = + R.string.page_info_android_permission_session_permission; + } } } @@ -126,13 +131,15 @@ public class PermissionParamsListBuilder { public final String nameMidSentence; public final int type; public final @ContentSettingValues int setting; + public final boolean is_user_session; PageInfoPermissionEntry( - String name, String nameMidSentence, int type, @ContentSettingValues int setting) { + String name, String nameMidSentence, int type, @ContentSettingValues int setting, boolean is_user_session) { this.name = name; this.nameMidSentence = nameMidSentence; this.type = type; this.setting = setting; + this.is_user_session = is_user_session; } @Override diff --git a/components/page_info/android/page_info_controller_android.cc b/components/page_info/android/page_info_controller_android.cc --- a/components/page_info/android/page_info_controller_android.cc +++ b/components/page_info/android/page_info_controller_android.cc @@ -154,6 +154,8 @@ void PageInfoControllerAndroid::SetPermissionInfo( std::map user_specified_settings_to_display; + std::map + user_specified_settings_is_user_session; for (const auto& permission : permission_info_list) { if (base::Contains(permissions_to_display, permission.type)) { @@ -162,6 +164,8 @@ void PageInfoControllerAndroid::SetPermissionInfo( if (setting_to_display) { user_specified_settings_to_display[permission.type] = *setting_to_display; + user_specified_settings_is_user_session[permission.type] = + permission.is_user_session; } } } @@ -178,7 +182,8 @@ void PageInfoControllerAndroid::SetPermissionInfo( ConvertUTF16ToJavaString(env, setting_title), ConvertUTF16ToJavaString(env, setting_title_mid_sentence), static_cast(permission), - static_cast(user_specified_settings_to_display[permission])); + static_cast(user_specified_settings_to_display[permission]), + user_specified_settings_is_user_session[permission]); } } @@ -191,7 +196,8 @@ void PageInfoControllerAndroid::SetPermissionInfo( env, controller_jobject_, ConvertUTF16ToJavaString(env, object_title), ConvertUTF16ToJavaString(env, object_title), static_cast(chosen_object->ui_info->content_settings_type), - static_cast(CONTENT_SETTING_ALLOW)); + static_cast(CONTENT_SETTING_ALLOW), + /* is_user_session */ false); } Java_PageInfoController_updatePermissionDisplay(env, controller_jobject_); diff --git a/components/page_info/page_info.cc b/components/page_info/page_info.cc --- a/components/page_info/page_info.cc +++ b/components/page_info/page_info.cc @@ -1163,6 +1163,8 @@ void PageInfo::PopulatePermissionInfo(PermissionInfo& permission_info, permission_info.source = info.source; permission_info.is_one_time = (info.metadata.session_model() == content_settings::SessionModel::OneTime); + permission_info.is_user_session = + (info.metadata.session_model() == content_settings::SessionModel::UserSession); auto* page_specific_content_settings = GetPageSpecificContentSettings(); if (page_specific_content_settings && setting == CONTENT_SETTING_ALLOW) { diff --git a/components/page_info/page_info.h b/components/page_info/page_info.h --- a/components/page_info/page_info.h +++ b/components/page_info/page_info.h @@ -199,6 +199,7 @@ class PageInfo : private content_settings::CookieControlsObserver { content_settings::SETTING_SOURCE_NONE; // Whether the permission is a one-time grant. bool is_one_time = false; + bool is_user_session = false; // Only set for settings that can have multiple permissions for different // embedded origins. std::optional requesting_origin; diff --git a/components/permissions/android/java/src/org/chromium/components/permissions/PermissionDialogDelegate.java b/components/permissions/android/java/src/org/chromium/components/permissions/PermissionDialogDelegate.java --- a/components/permissions/android/java/src/org/chromium/components/permissions/PermissionDialogDelegate.java +++ b/components/permissions/android/java/src/org/chromium/components/permissions/PermissionDialogDelegate.java @@ -13,6 +13,7 @@ import org.jni_zero.JNINamespace; import org.jni_zero.NativeMethods; import org.chromium.ui.base.WindowAndroid; +import org.chromium.components.content_settings.LifetimeMode; import java.util.ArrayList; import java.util.List; @@ -57,6 +58,9 @@ public class PermissionDialogDelegate { /** The {@link ContentSettingsType}s requested in this dialog. */ private int[] mContentSettingsTypes; + /** Lifetime option selected by the user. */ + private int mSelectedLifetimeOption = LifetimeMode.ALWAYS; + /** * Defines a (potentially empty) list of ranges represented as pairs of , * which shall be used by the UI to format the specified ranges as bold text. @@ -110,6 +114,15 @@ public class PermissionDialogDelegate { .acceptThisTime(mNativeDelegatePtr, PermissionDialogDelegate.this); } + public void setSelectedLifetimeOption(int idx) { + mSelectedLifetimeOption = idx; + } + + @CalledByNative + public int getSelectedLifetimeOption() { + return mSelectedLifetimeOption; + } + public void onCancel() { assert mNativeDelegatePtr != 0; PermissionDialogDelegateJni.get().cancel(mNativeDelegatePtr, PermissionDialogDelegate.this); diff --git a/components/permissions/android/java/src/org/chromium/components/permissions/PermissionDialogModelFactory.java b/components/permissions/android/java/src/org/chromium/components/permissions/PermissionDialogModelFactory.java --- a/components/permissions/android/java/src/org/chromium/components/permissions/PermissionDialogModelFactory.java +++ b/components/permissions/android/java/src/org/chromium/components/permissions/PermissionDialogModelFactory.java @@ -12,6 +12,18 @@ import org.chromium.ui.UiUtils; import org.chromium.ui.modaldialog.ModalDialogProperties; import org.chromium.ui.modelutil.PropertyModel; +import java.util.Arrays; +import java.util.List; +import android.view.ViewGroup.LayoutParams; +import android.widget.LinearLayout; +import android.widget.RadioButton; +import android.widget.RadioGroup; +import android.widget.TextView; +import org.chromium.base.ApiCompatibilityUtils; +import org.chromium.ui.base.ViewUtils; +import org.chromium.components.content_settings.ContentSettingsType; +import org.chromium.components.content_settings.LifetimeMode; + /** This class creates the model for the permission dialog. */ class PermissionDialogModelFactory { public static PropertyModel getModel( @@ -60,6 +72,74 @@ class PermissionDialogModelFactory { ModalDialogProperties.NEGATIVE_BUTTON_TEXT, delegate.getNegativeButtonText()); } - return builder.build(); + + PropertyModel pm = builder.build(); + int[] types = delegate.getContentSettingsTypes(); + if (contains(types, ContentSettingsType.GEOLOCATION) || + contains(types, ContentSettingsType.MEDIASTREAM_MIC) || + contains(types, ContentSettingsType.MEDIASTREAM_CAMERA)) + { + LinearLayout layout = (LinearLayout) customView; + + // Create a text label before the lifetime selector. + TextView lifetimeOptionsText = new TextView(context); + lifetimeOptionsText.setText(context.getString( + org.chromium.components.permissions.R.string.session_permissions_title)); + ApiCompatibilityUtils.setTextAppearance( + lifetimeOptionsText, R.style.TextAppearance_TextMedium_Primary); + + LinearLayout.LayoutParams lifetimeOptionsTextLayoutParams = + new LinearLayout.LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT); + lifetimeOptionsTextLayoutParams.setMargins(0, 0, 0, ViewUtils.dpToPx(context, 8)); + lifetimeOptionsText.setLayoutParams(lifetimeOptionsTextLayoutParams); + layout.addView(lifetimeOptionsText); + + // Create radio buttons with lifetime options. + RadioGroup radioGroup = new RadioGroup(context); + + RadioButton radioButton = new RadioButton(context); + radioButton.setText(context.getString( + org.chromium.components.permissions.R.string.session_permissions_only_this_this)); + radioButton.setId(LifetimeMode.ONLY_THIS_TIME); + radioGroup.addView(radioButton); + + radioButton = new RadioButton(context); + radioButton.setText(context.getString( + org.chromium.components.permissions.R.string.session_permissions_until_page_close)); + radioButton.setId(LifetimeMode.UNTIL_ORIGIN_CLOSED); + radioGroup.addView(radioButton); + + radioButton = new RadioButton(context); + radioButton.setText(context.getString( + org.chromium.components.permissions.R.string.session_permissions_until_browser_close)); + radioButton.setId(LifetimeMode.UNTIL_BROWSER_CLOSED); + radioGroup.addView(radioButton); + + radioButton = new RadioButton(context); + radioButton.setText(context.getString( + org.chromium.components.permissions.R.string.session_permissions_forever)); + radioButton.setId(LifetimeMode.ALWAYS); + radioGroup.addView(radioButton); + + radioGroup.setOnCheckedChangeListener(new RadioGroup.OnCheckedChangeListener() { + @Override + public void onCheckedChanged(RadioGroup group, int checkedId) { + delegate.setSelectedLifetimeOption(checkedId); + } + }); + radioGroup.check(1); + layout.addView(radioGroup); + } + + return pm; + } + + private static boolean contains(final int[] array, final int key) { + int length = array.length; + for(int i = 0; i < length; i++) { + if (array[i] == key) + return true; + } + return false; } } diff --git a/components/permissions/android/permission_prompt/permission_dialog_delegate.cc b/components/permissions/android/permission_prompt/permission_dialog_delegate.cc --- a/components/permissions/android/permission_prompt/permission_dialog_delegate.cc +++ b/components/permissions/android/permission_prompt/permission_dialog_delegate.cc @@ -144,6 +144,11 @@ void PermissionDialogJavaDelegate::DismissDialog() { Java_PermissionDialogDelegate_dismissFromNative(env, j_delegate_); } +int PermissionDialogJavaDelegate::GetSelectedLifetimeOption() { + JNIEnv* env = base::android::AttachCurrentThread(); + return Java_PermissionDialogDelegate_getSelectedLifetimeOption(env, j_delegate_); +} + // static void PermissionDialogDelegate::Create( content::WebContents* web_contents, @@ -172,6 +177,13 @@ PermissionDialogDelegate* PermissionDialogDelegate::CreateForTesting( void PermissionDialogDelegate::Accept(JNIEnv* env, const JavaParamRef& obj) { CHECK(permission_prompt_); + content_settings::LifetimeMode lifetimeOption = + static_cast( + java_delegate_->GetSelectedLifetimeOption()); + if (lifetimeOption != content_settings::LifetimeMode::Always) { + permission_prompt_->AcceptThisTime(lifetimeOption); + return; + } permission_prompt_->Accept(); } @@ -179,12 +191,22 @@ void PermissionDialogDelegate::AcceptThisTime( JNIEnv* env, const JavaParamRef& obj) { CHECK(permission_prompt_); - permission_prompt_->AcceptThisTime(); + content_settings::LifetimeMode lifetimeOption = + static_cast( + java_delegate_->GetSelectedLifetimeOption()); + permission_prompt_->AcceptThisTime(lifetimeOption); } void PermissionDialogDelegate::Cancel(JNIEnv* env, const JavaParamRef& obj) { CHECK(permission_prompt_); + content_settings::LifetimeMode lifetimeOption = + static_cast( + java_delegate_->GetSelectedLifetimeOption()); + if (lifetimeOption != content_settings::LifetimeMode::Always) { + permission_prompt_->DenyThisTime(lifetimeOption); + return; + } permission_prompt_->Deny(); } diff --git a/components/permissions/android/permission_prompt/permission_dialog_delegate.h b/components/permissions/android/permission_prompt/permission_dialog_delegate.h --- a/components/permissions/android/permission_prompt/permission_dialog_delegate.h +++ b/components/permissions/android/permission_prompt/permission_dialog_delegate.h @@ -41,6 +41,7 @@ class PermissionDialogJavaDelegate { const favicon_base::FaviconRawBitmapResult& favicon_result); virtual void DismissDialog(); + virtual int GetSelectedLifetimeOption(); private: base::android::ScopedJavaGlobalRef j_delegate_; diff --git a/components/permissions/android/permission_prompt/permission_prompt_android.cc b/components/permissions/android/permission_prompt/permission_prompt_android.cc --- a/components/permissions/android/permission_prompt/permission_prompt_android.cc +++ b/components/permissions/android/permission_prompt/permission_prompt_android.cc @@ -53,8 +53,12 @@ void PermissionPromptAndroid::Accept() { delegate_->Accept(); } -void PermissionPromptAndroid::AcceptThisTime() { - delegate_->AcceptThisTime(); +void PermissionPromptAndroid::AcceptThisTime(content_settings::LifetimeMode lifetimeOption) { + delegate_->AcceptThisTime(lifetimeOption); +} + +void PermissionPromptAndroid::DenyThisTime(content_settings::LifetimeMode lifetimeOption) { + delegate_->DenyThisTime(lifetimeOption); } void PermissionPromptAndroid::Deny() { diff --git a/components/permissions/android/permission_prompt/permission_prompt_android.h b/components/permissions/android/permission_prompt/permission_prompt_android.h --- a/components/permissions/android/permission_prompt/permission_prompt_android.h +++ b/components/permissions/android/permission_prompt/permission_prompt_android.h @@ -44,8 +44,9 @@ class PermissionPromptAndroid : public PermissionPrompt { void Closing(); void Accept(); - void AcceptThisTime(); + void AcceptThisTime(content_settings::LifetimeMode lifetimeOption); void Deny(); + void DenyThisTime(content_settings::LifetimeMode lifetimeOption); void SetManageClicked(); void SetLearnMoreClicked(); bool ShouldCurrentRequestUseQuietUI(); diff --git a/components/permissions/android/permissions_android_strings.grd b/components/permissions/android/permissions_android_strings.grd --- a/components/permissions/android/permissions_android_strings.grd +++ b/components/permissions/android/permissions_android_strings.grd @@ -264,6 +264,23 @@ Unknown or unsupported device (%1$sA1:B2:C3:D4:E5:F6) + + + Remeber my decision + + + Only this time + + + Until all pages of this origin are closed + + + Until Bromite is closed + + + Forever + + %1$sitem_name (%2$sitem id) diff --git a/components/permissions/contexts/geolocation_permission_context_android.cc b/components/permissions/contexts/geolocation_permission_context_android.cc --- a/components/permissions/contexts/geolocation_permission_context_android.cc +++ b/components/permissions/contexts/geolocation_permission_context_android.cc @@ -162,7 +162,20 @@ void GeolocationPermissionContextAndroid::NotifyPermissionSet( bool is_one_time, bool is_final_decision) { DCHECK(is_final_decision); + NotifyPermissionSetWithLifetime(id, requesting_origin, embedding_origin, + std::move(callback), persist, content_setting, is_one_time, is_final_decision, + content_settings::LifetimeMode::Always); +} +void GeolocationPermissionContextAndroid::NotifyPermissionSetWithLifetime( + const PermissionRequestID& id, + const GURL& requesting_origin, + const GURL& embedding_origin, + BrowserPermissionCallback callback, + bool persist, + ContentSetting content_setting, + bool is_one_time, bool is_final_decision, + content_settings::LifetimeMode lifetime_option) { bool is_default_search = IsRequestingOriginDSE(requesting_origin); if (content_setting == CONTENT_SETTING_ALLOW && !location_settings_->IsSystemLocationSettingEnabled()) { @@ -175,7 +188,8 @@ void GeolocationPermissionContextAndroid::NotifyPermissionSet( if (IsInLocationSettingsBackOff(is_default_search)) { FinishNotifyPermissionSet(id, requesting_origin, embedding_origin, std::move(callback), false /* persist */, - CONTENT_SETTING_BLOCK, is_one_time); + CONTENT_SETTING_BLOCK, + is_one_time, lifetime_option); return; } @@ -193,7 +207,8 @@ void GeolocationPermissionContextAndroid::NotifyPermissionSet( !location_settings_dialog_callback_.is_null()) { FinishNotifyPermissionSet(id, requesting_origin, embedding_origin, std::move(callback), false /* persist */, - CONTENT_SETTING_BLOCK, is_one_time); + CONTENT_SETTING_BLOCK, + is_one_time, lifetime_option); return; } @@ -205,13 +220,13 @@ void GeolocationPermissionContextAndroid::NotifyPermissionSet( base::BindOnce( &GeolocationPermissionContextAndroid::OnLocationSettingsDialogShown, weak_factory_.GetWeakPtr(), requesting_origin, embedding_origin, - persist, content_setting, is_one_time)); + persist, content_setting, is_one_time, lifetime_option)); return; } FinishNotifyPermissionSet(id, requesting_origin, embedding_origin, std::move(callback), persist, content_setting, - is_one_time); + is_one_time, lifetime_option); } content::PermissionResult @@ -377,7 +392,7 @@ void GeolocationPermissionContextAndroid::OnLocationSettingsDialogShown( const GURL& embedding_origin, bool persist, ContentSetting content_setting, - bool is_one_time, + bool is_one_time, content_settings::LifetimeMode lifetime_option, LocationSettingsDialogOutcome prompt_outcome) { bool is_default_search = IsRequestingOriginDSE(requesting_origin); if (prompt_outcome == GRANTED) { @@ -396,7 +411,8 @@ void GeolocationPermissionContextAndroid::OnLocationSettingsDialogShown( FinishNotifyPermissionSet(location_settings_dialog_request_id_, requesting_origin, embedding_origin, std::move(location_settings_dialog_callback_), - persist, content_setting, is_one_time); + persist, content_setting, is_one_time, + lifetime_option); location_settings_dialog_request_id_ = PermissionRequestID(content::GlobalRenderFrameHostId(0, 0), @@ -410,10 +426,10 @@ void GeolocationPermissionContextAndroid::FinishNotifyPermissionSet( BrowserPermissionCallback callback, bool persist, ContentSetting content_setting, - bool is_one_time) { - GeolocationPermissionContext::NotifyPermissionSet( + bool is_one_time, content_settings::LifetimeMode lifetime_option) { + GeolocationPermissionContext::NotifyPermissionSetWithLifetime( id, requesting_origin, embedding_origin, std::move(callback), persist, - content_setting, is_one_time, /*is_final_decision=*/true); + content_setting, is_one_time, /*is_final_decision=*/true, lifetime_option); } void GeolocationPermissionContextAndroid::SetLocationSettingsForTesting( diff --git a/components/permissions/contexts/geolocation_permission_context_android.h b/components/permissions/contexts/geolocation_permission_context_android.h --- a/components/permissions/contexts/geolocation_permission_context_android.h +++ b/components/permissions/contexts/geolocation_permission_context_android.h @@ -89,6 +89,15 @@ class GeolocationPermissionContextAndroid ContentSetting content_setting, bool is_one_time, bool is_final_decision) override; + void NotifyPermissionSetWithLifetime(const PermissionRequestID& id, + const GURL& requesting_origin, + const GURL& embedding_origin, + BrowserPermissionCallback callback, + bool persist, + ContentSetting content_setting, + bool is_one_time, + bool is_final_decision, + content_settings::LifetimeMode lifetime_option) override; content::PermissionResult UpdatePermissionStatusWithDeviceStatus( content::PermissionResult result, const GURL& requesting_origin, @@ -132,6 +141,7 @@ class GeolocationPermissionContextAndroid bool persist, ContentSetting content_setting, bool is_one_time, + content_settings::LifetimeMode lifetime_option, LocationSettingsDialogOutcome prompt_outcome); void FinishNotifyPermissionSet(const PermissionRequestID& id, @@ -140,7 +150,8 @@ class GeolocationPermissionContextAndroid BrowserPermissionCallback callback, bool persist, ContentSetting content_setting, - bool is_one_time); + bool is_one_time, + content_settings::LifetimeMode lifetime_option); std::unique_ptr location_settings_; diff --git a/components/permissions/permission_context_base.cc b/components/permissions/permission_context_base.cc --- a/components/permissions/permission_context_base.cc +++ b/components/permissions/permission_context_base.cc @@ -265,6 +265,17 @@ bool PermissionContextBase::UsesAutomaticEmbargo() const { return true; } +std::unique_ptr +PermissionContextBase::CreatePermissionRequest( + content::WebContents* web_contents, + PermissionRequestData request_data, + PermissionRequest::PermissionDecidedCallbackWithLifetime permission_decided_callback, + base::OnceClosure delete_callback) const { + return std::make_unique( + std::move(request_data), std::move(permission_decided_callback), + std::move(delete_callback)); +} + content::PermissionResult PermissionContextBase::GetPermissionStatus( content::RenderFrameHost* render_frame_host, const GURL& requesting_origin, @@ -511,7 +522,8 @@ void PermissionContextBase::PermissionDecided(const PermissionRequestID& id, const GURL& embedding_origin, ContentSetting content_setting, bool is_one_time, - bool is_final_decision) { + bool is_final_decision, + content_settings::LifetimeMode lifetime_option) { DCHECK(content_setting == CONTENT_SETTING_ALLOW || content_setting == CONTENT_SETTING_BLOCK || content_setting == CONTENT_SETTING_DEFAULT); @@ -526,13 +538,14 @@ void PermissionContextBase::PermissionDecided(const PermissionRequestID& id, // missing if a permission prompt was preignored and we already notified an // origin about it. if (request->second.second) { - NotifyPermissionSet(id, requesting_origin, embedding_origin, + NotifyPermissionSetWithLifetime(id, requesting_origin, embedding_origin, std::move(request->second.second), persist, - content_setting, is_one_time, is_final_decision); + content_setting, is_one_time, is_final_decision, + lifetime_option); } else { - NotifyPermissionSet(id, requesting_origin, embedding_origin, + NotifyPermissionSetWithLifetime(id, requesting_origin, embedding_origin, base::DoNothing(), persist, content_setting, - is_one_time, is_final_decision); + is_one_time, is_final_decision, lifetime_option); } } @@ -578,11 +591,27 @@ void PermissionContextBase::NotifyPermissionSet( ContentSetting content_setting, bool is_one_time, bool is_final_decision) { + DCHECK(is_one_time == false); + NotifyPermissionSetWithLifetime(id, requesting_origin, embedding_origin, std::move(callback), + persist, content_setting, is_one_time, is_final_decision, + content_settings::LifetimeMode::Always); +} + +void PermissionContextBase::NotifyPermissionSetWithLifetime( + const PermissionRequestID& id, + const GURL& requesting_origin, + const GURL& embedding_origin, + BrowserPermissionCallback callback, + bool persist, + ContentSetting content_setting, + bool is_one_time, + bool is_final_decision, + content_settings::LifetimeMode lifetime_option) { DCHECK_CURRENTLY_ON(content::BrowserThread::UI); if (persist) { UpdateContentSetting(requesting_origin, embedding_origin, content_setting, - is_one_time); + is_one_time, lifetime_option); } if (is_final_decision) { @@ -612,6 +641,15 @@ void PermissionContextBase::UpdateContentSetting(const GURL& requesting_origin, const GURL& embedding_origin, ContentSetting content_setting, bool is_one_time) { + UpdateContentSetting(requesting_origin, embedding_origin, content_setting, + is_one_time, content_settings::LifetimeMode::Always); +} + +void PermissionContextBase::UpdateContentSetting(const GURL& requesting_origin, + const GURL& embedding_origin, + ContentSetting content_setting, + bool is_one_time, + content_settings::LifetimeMode lifetime_option) { DCHECK_EQ(requesting_origin, requesting_origin.DeprecatedGetOriginAsURL()); DCHECK_EQ(embedding_origin, embedding_origin.DeprecatedGetOriginAsURL()); DCHECK(content_setting == CONTENT_SETTING_ALLOW || @@ -621,6 +659,8 @@ void PermissionContextBase::UpdateContentSetting(const GURL& requesting_origin, constraints.set_session_model(is_one_time ? content_settings::SessionModel::OneTime : content_settings::SessionModel::Durable); + if (is_one_time) + constraints = content_settings::GetConstraintSessionExpiration(lifetime_option); #if !BUILDFLAG(IS_ANDROID) // The Permissions module in Safety check will revoke permissions after diff --git a/components/permissions/permission_context_base.h b/components/permissions/permission_context_base.h --- a/components/permissions/permission_context_base.h +++ b/components/permissions/permission_context_base.h @@ -153,6 +153,15 @@ class PermissionContextBase : public content_settings::Observer { // Updates stored content setting if persist is set, updates tab indicators // and runs the callback to finish the request. + virtual void NotifyPermissionSetWithLifetime(const PermissionRequestID& id, + const GURL& requesting_origin, + const GURL& embedding_origin, + BrowserPermissionCallback callback, + bool persist, + ContentSetting content_setting, + bool is_one_time, + bool is_final_decision, + content_settings::LifetimeMode lifetime_option); virtual void NotifyPermissionSet(const PermissionRequestID& id, const GURL& requesting_origin, const GURL& embedding_origin, @@ -174,6 +183,11 @@ class PermissionContextBase : public content_settings::Observer { // Store the decided permission as a content setting. // virtual since the permission might be stored with different restrictions // (for example for desktop notifications). + void UpdateContentSetting(const GURL& requesting_origin, + const GURL& embedding_origin, + ContentSetting content_setting, + bool is_one_time, + content_settings::LifetimeMode lifetime_option); virtual void UpdateContentSetting(const GURL& requesting_origin, const GURL& embedding_origin, ContentSetting content_setting, @@ -207,6 +221,12 @@ class PermissionContextBase : public content_settings::Observer { // Implementors can override this method to avoid using automatic embargo. virtual bool UsesAutomaticEmbargo() const; + virtual std::unique_ptr CreatePermissionRequest( + content::WebContents* web_contents, + PermissionRequestData request_data, + PermissionRequest::PermissionDecidedCallbackWithLifetime permission_decided_callback, + base::OnceClosure delete_callback) const; + base::ObserverList permission_observers_; // Set by subclasses to inform the base class that they will handle adding @@ -229,7 +249,8 @@ class PermissionContextBase : public content_settings::Observer { const GURL& embedding_origin, ContentSetting content_setting, bool is_one_time, - bool is_final_decision); + bool is_final_decision, + content_settings::LifetimeMode lifetime_option); void NotifyObservers(const ContentSettingsPattern& primary_pattern, const ContentSettingsPattern& secondary_pattern, diff --git a/components/permissions/permission_prompt.h b/components/permissions/permission_prompt.h --- a/components/permissions/permission_prompt.h +++ b/components/permissions/permission_prompt.h @@ -67,8 +67,9 @@ class PermissionPrompt { virtual GURL GetEmbeddingOrigin() const = 0; virtual void Accept() = 0; - virtual void AcceptThisTime() = 0; + virtual void AcceptThisTime(content_settings::LifetimeMode lifetime_option) = 0; virtual void Deny() = 0; + virtual void DenyThisTime(content_settings::LifetimeMode lifetime_option) = 0; virtual void Dismiss() = 0; virtual void Ignore() = 0; diff --git a/components/permissions/permission_request.cc b/components/permissions/permission_request.cc --- a/components/permissions/permission_request.cc +++ b/components/permissions/permission_request.cc @@ -43,6 +43,14 @@ PermissionRequest::PermissionRequest( delete_callback_(std::move(delete_callback)), uses_automatic_embargo_(uses_automatic_embargo) {} +PermissionRequest::PermissionRequest( + PermissionRequestData request_data, + PermissionDecidedCallbackWithLifetime permission_decided_callback, + base::OnceClosure delete_callback) + : data_(std::move(request_data)), + permission_decided_callback_withlifetime_(std::move(permission_decided_callback)), + delete_callback_(std::move(delete_callback)) {} + PermissionRequest::~PermissionRequest() { DCHECK(delete_callback_.is_null()); } @@ -357,19 +365,37 @@ bool PermissionRequest::ShouldUseTwoOriginPrompt() const { permissions::features::kPermissionStorageAccessAPI); } -void PermissionRequest::PermissionGranted(bool is_one_time) { +void PermissionRequest::PermissionGranted(bool is_one_time, + content_settings::LifetimeMode lifetime_option) { + if (permission_decided_callback_withlifetime_) { + std::move(permission_decided_callback_withlifetime_) + .Run(CONTENT_SETTING_ALLOW, is_one_time, /*is_final_decision=*/true, lifetime_option); + return; + } std::move(permission_decided_callback_) .Run(CONTENT_SETTING_ALLOW, is_one_time, /*is_final_decision=*/true); } -void PermissionRequest::PermissionDenied() { +void PermissionRequest::PermissionDenied(bool is_one_time, + content_settings::LifetimeMode lifetime_option) { + if (permission_decided_callback_withlifetime_) { + std::move(permission_decided_callback_withlifetime_) + .Run(CONTENT_SETTING_BLOCK, is_one_time, /*is_final_decision=*/true, lifetime_option); + return; + } std::move(permission_decided_callback_) .Run(CONTENT_SETTING_BLOCK, /*is_one_time=*/false, /*is_final_decision=*/true); } void PermissionRequest::Cancelled(bool is_final_decision) { + if (permission_decided_callback_withlifetime_) { + std::move(permission_decided_callback_withlifetime_) + .Run(CONTENT_SETTING_DEFAULT, false, is_final_decision, + content_settings::LifetimeMode::Always); + return; + } if (permission_decided_callback_) { permission_decided_callback_.Run(CONTENT_SETTING_DEFAULT, /*is_one_time=*/false, is_final_decision); diff --git a/components/permissions/permission_request.h b/components/permissions/permission_request.h --- a/components/permissions/permission_request.h +++ b/components/permissions/permission_request.h @@ -43,6 +43,11 @@ class PermissionRequest { bool /*is_one_time*/, bool /*is_final_decision*/)>; + using PermissionDecidedCallbackWithLifetime = + base::OnceCallback; + // `permission_decided_callback` is called when the permission request is // resolved by the user (see comment on PermissionDecidedCallback above). // `delete_callback` is called when the permission request is no longer needed @@ -63,6 +68,10 @@ class PermissionRequest { base::OnceClosure delete_callback, bool uses_automatic_embargo); + PermissionRequest(PermissionRequestData request_data, + PermissionDecidedCallbackWithLifetime permission_decided_callback, + base::OnceClosure delete_callback); + PermissionRequest(const PermissionRequest&) = delete; PermissionRequest& operator=(const PermissionRequest&) = delete; @@ -159,10 +168,10 @@ class PermissionRequest { // If |is_one_time| is true the permission will last until all tabs of // |origin| are closed or navigated away from, and then the permission will // automatically expire after 1 day. - void PermissionGranted(bool is_one_time); + void PermissionGranted(bool is_one_time, content_settings::LifetimeMode lifetime_option); // Called when the user has denied the requested permission. - void PermissionDenied(); + void PermissionDenied(bool is_one_time, content_settings::LifetimeMode lifetime_option); // Called when the user has cancelled the permission request. This // corresponds to a denial, but is segregated in case the context needs to @@ -216,6 +225,9 @@ class PermissionRequest { // Called once a decision is made about the permission. PermissionDecidedCallback permission_decided_callback_; + // Called once a decision is made about the permission (with lifetime option). + PermissionDecidedCallbackWithLifetime permission_decided_callback_withlifetime_; + // Called when the request is no longer in use so it can be deleted by the // caller. base::OnceClosure delete_callback_; diff --git a/components/permissions/permission_request_manager.cc b/components/permissions/permission_request_manager.cc --- a/components/permissions/permission_request_manager.cc +++ b/components/permissions/permission_request_manager.cc @@ -156,7 +156,7 @@ void PermissionRequestManager::AddRequest( if (base::CommandLine::ForCurrentProcess()->HasSwitch( switches::kDenyPermissionPrompts)) { - request->PermissionDenied(); + request->PermissionDenied(/*is_one_time*/false, content_settings::LifetimeMode::Always); request->RequestFinished(); return; } @@ -232,7 +232,7 @@ void PermissionRequestManager::AddRequest( if (auto_approval_origin) { if (url::Origin::Create(request->requesting_origin()) == auto_approval_origin.value()) { - request->PermissionGranted(/*is_one_time=*/false); + request->PermissionGranted(/*is_one_time=*/false, content_settings::LifetimeMode::Always); } request->RequestFinished(); return; @@ -593,7 +593,8 @@ void PermissionRequestManager::Accept() { (*requests_iter)->request_type(), PermissionAction::GRANTED); PermissionGrantedIncludingDuplicates(*requests_iter, - /*is_one_time=*/false); + /*is_one_time=*/false, + content_settings::LifetimeMode::Always); #if !BUILDFLAG(IS_ANDROID) std::optional content_settings_type = @@ -611,7 +612,7 @@ void PermissionRequestManager::Accept() { CurrentRequestsDecided(PermissionAction::GRANTED); } -void PermissionRequestManager::AcceptThisTime() { +void PermissionRequestManager::AcceptThisTime(content_settings::LifetimeMode mode) { if (ignore_callbacks_from_prompt_) return; DCHECK(view_); @@ -623,7 +624,8 @@ void PermissionRequestManager::AcceptThisTime() { (*requests_iter)->request_type(), PermissionAction::GRANTED_ONCE); PermissionGrantedIncludingDuplicates(*requests_iter, - /*is_one_time=*/true); + /*is_one_time=*/true, + mode); } NotifyRequestDecided(PermissionAction::GRANTED_ONCE); @@ -631,6 +633,15 @@ void PermissionRequestManager::AcceptThisTime() { } void PermissionRequestManager::Deny() { + Deny_(/*is_one_time*/ false, content_settings::LifetimeMode::Always); +} + +void PermissionRequestManager::DenyThisTime(content_settings::LifetimeMode mode) { + Deny_(/*is_one_time*/ true, mode); +} + +void PermissionRequestManager::Deny_(bool is_one_time, + content_settings::LifetimeMode lifetime_option) { if (ignore_callbacks_from_prompt_) return; DCHECK(view_); @@ -654,7 +665,7 @@ void PermissionRequestManager::Deny() { StorePermissionActionForUMA((*requests_iter)->requesting_origin(), (*requests_iter)->request_type(), PermissionAction::DENIED); - PermissionDeniedIncludingDuplicates(*requests_iter); + PermissionDeniedIncludingDuplicates(*requests_iter, is_one_time, lifetime_option); } NotifyRequestDecided(PermissionAction::DENIED); @@ -1213,32 +1224,32 @@ PermissionRequestManager::VisitDuplicateRequests( void PermissionRequestManager::PermissionGrantedIncludingDuplicates( PermissionRequest* request, - bool is_one_time) { + bool is_one_time, content_settings::LifetimeMode lifetime_option) { DCHECK_EQ(1ul, base::ranges::count(requests_, request) + pending_permission_requests_.Count(request)) << "Only requests in [pending_permission_]requests_ can have duplicates"; - request->PermissionGranted(is_one_time); + request->PermissionGranted(is_one_time, lifetime_option); VisitDuplicateRequests( base::BindRepeating( - [](bool is_one_time, + [](bool is_one_time, content_settings::LifetimeMode lifetime_option, const base::WeakPtr& weak_request) { - weak_request->PermissionGranted(is_one_time); + weak_request->PermissionGranted(is_one_time, lifetime_option); }, - is_one_time), + is_one_time, lifetime_option), request); } void PermissionRequestManager::PermissionDeniedIncludingDuplicates( - PermissionRequest* request) { + PermissionRequest* request, bool is_one_time, content_settings::LifetimeMode lifetime_option) { DCHECK_EQ(1ul, base::ranges::count(requests_, request) + pending_permission_requests_.Count(request)) << "Only requests in [pending_permission_]requests_ can have duplicates"; - request->PermissionDenied(); + request->PermissionDenied(is_one_time, lifetime_option); VisitDuplicateRequests( base::BindRepeating( - [](const base::WeakPtr& weak_request) { - weak_request->PermissionDenied(); - }), + [](bool is_one_time, content_settings::LifetimeMode lifetime_option, const base::WeakPtr& weak_request) { + weak_request->PermissionDenied(is_one_time, lifetime_option); + }, is_one_time, lifetime_option), request); } @@ -1495,7 +1506,7 @@ void PermissionRequestManager::DoAutoResponseForTesting() { } switch (auto_response_for_test_) { case ACCEPT_ONCE: - AcceptThisTime(); + AcceptThisTime(content_settings::LifetimeMode::OnlyThisTime); break; case ACCEPT_ALL: Accept(); diff --git a/components/permissions/permission_request_manager.h b/components/permissions/permission_request_manager.h --- a/components/permissions/permission_request_manager.h +++ b/components/permissions/permission_request_manager.h @@ -162,8 +162,10 @@ class PermissionRequestManager GURL GetRequestingOrigin() const override; GURL GetEmbeddingOrigin() const override; void Accept() override; - void AcceptThisTime() override; + void AcceptThisTime(content_settings::LifetimeMode lifetime_option) override; void Deny() override; + void Deny_(bool is_one_time, content_settings::LifetimeMode lifetime_option); + void DenyThisTime(content_settings::LifetimeMode lifetime_option) override; void Dismiss() override; void Ignore() override; void FinalizeCurrentRequests() override; @@ -368,9 +370,12 @@ class PermissionRequestManager // Calls PermissionGranted on a request and all its duplicates. void PermissionGrantedIncludingDuplicates(PermissionRequest* request, - bool is_one_time); + bool is_one_time, + content_settings::LifetimeMode lifetime_option); // Calls PermissionDenied on a request and all its duplicates. - void PermissionDeniedIncludingDuplicates(PermissionRequest* request); + void PermissionDeniedIncludingDuplicates(PermissionRequest* request, + bool is_one_time, + content_settings::LifetimeMode lifetime_option); // Calls Cancelled on a request and all its duplicates. void CancelledIncludingDuplicates(PermissionRequest* request, bool is_final_decision = true); --