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 Change-Id: Ic8a115d5b45b5768bec1f2fda45a8bf19da96e48 --- .../permissions/last_tab_standing_tracker.cc | 33 +++++++- .../permissions/last_tab_standing_tracker.h | 4 +- .../last_tab_standing_tracker_factory.cc | 2 +- ...hrome_browser_main_extra_parts_profiles.cc | 5 +- chrome/browser/ui/tab_helpers.cc | 1 + .../site_settings/PermissionInfo.java | 14 +++- .../site_settings/SingleWebsiteSettings.java | 10 +++ .../WebsitePreferenceBridge.java | 6 +- .../android/website_preference_bridge.cc | 7 +- .../strings/android/browser_ui_strings.grd | 5 ++ components/content_settings/android/BUILD.gn | 1 + .../core/browser/content_settings_utils.cc | 29 +++++++ .../core/browser/content_settings_utils.h | 6 ++ .../common/content_settings_constraints.h | 11 +++ .../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 +++ .../permissions/PermissionDialogModel.java | 81 ++++++++++++++++++- .../permission_dialog_delegate.cc | 19 +++++ .../permission_dialog_delegate.h | 1 + .../permission_prompt_android.cc | 8 ++ .../permission_prompt_android.h | 2 + .../android/permissions_android_strings.grd | 17 ++++ .../geolocation_permission_context_android.cc | 36 +++++++-- .../geolocation_permission_context_android.h | 14 +++- .../permissions/permission_context_base.cc | 54 +++++++++++-- .../permissions/permission_context_base.h | 25 +++++- components/permissions/permission_prompt.h | 3 +- components/permissions/permission_request.cc | 36 ++++++++- components/permissions/permission_request.h | 18 ++++- .../permissions/permission_request_manager.cc | 47 +++++++---- .../permissions/permission_request_manager.h | 11 ++- 35 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 @@ -6,9 +6,35 @@ #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" -LastTabStandingTracker::LastTabStandingTracker() = default; +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, &session_settings, + 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) {} LastTabStandingTracker::~LastTabStandingTracker() = default; void LastTabStandingTracker::Shutdown() { @@ -54,5 +80,10 @@ void LastTabStandingTracker::WebContentsUnloadedOrigin( tab_counter_.erase(origin); 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/last_tab_standing_tracker.h b/chrome/browser/permissions/last_tab_standing_tracker.h --- a/chrome/browser/permissions/last_tab_standing_tracker.h +++ b/chrome/browser/permissions/last_tab_standing_tracker.h @@ -9,6 +9,7 @@ #include "base/observer_list.h" #include "chrome/browser/permissions/last_tab_standing_tracker_observer.h" +#include "chrome/browser/profiles/profile.h" #include "components/keyed_service/core/keyed_service.h" #include "url/origin.h" @@ -16,7 +17,7 @@ // all tabs of a particular origin have been closed or navigated away from. class LastTabStandingTracker : public KeyedService { public: - LastTabStandingTracker(); + explicit LastTabStandingTracker(content::BrowserContext* context); ~LastTabStandingTracker() override; LastTabStandingTracker(const LastTabStandingTracker&) = delete; @@ -33,6 +34,7 @@ class LastTabStandingTracker : public KeyedService { base::ObserverList observer_list_; // Tracks how many tabs of a particular origin are open at any given time. std::map tab_counter_; + raw_ptr context_; }; #endif // CHROME_BROWSER_PERMISSIONS_LAST_TAB_STANDING_TRACKER_H_ diff --git a/chrome/browser/permissions/last_tab_standing_tracker_factory.cc b/chrome/browser/permissions/last_tab_standing_tracker_factory.cc --- a/chrome/browser/permissions/last_tab_standing_tracker_factory.cc +++ b/chrome/browser/permissions/last_tab_standing_tracker_factory.cc @@ -31,5 +31,5 @@ bool LastTabStandingTrackerFactory::ServiceIsCreatedWithBrowserContext() const { KeyedService* LastTabStandingTrackerFactory::BuildServiceInstanceFor( content::BrowserContext* context) const { - return new LastTabStandingTracker(); + return new LastTabStandingTracker(context); } diff --git a/chrome/browser/profiles/chrome_browser_main_extra_parts_profiles.cc b/chrome/browser/profiles/chrome_browser_main_extra_parts_profiles.cc --- a/chrome/browser/profiles/chrome_browser_main_extra_parts_profiles.cc +++ b/chrome/browser/profiles/chrome_browser_main_extra_parts_profiles.cc @@ -673,10 +673,7 @@ void ChromeBrowserMainExtraPartsProfiles:: #endif KAnonymityServiceFactory::GetInstance(); LanguageModelManagerFactory::GetInstance(); - if (base::FeatureList::IsEnabled( - permissions::features::kOneTimeGeolocationPermission)) { - LastTabStandingTrackerFactory::GetInstance(); - } + LastTabStandingTrackerFactory::GetInstance(); #if !BUILDFLAG(IS_ANDROID) #if !BUILDFLAG(IS_CHROMEOS_LACROS) captions::LiveCaptionControllerFactory::GetInstance(); diff --git a/chrome/browser/ui/tab_helpers.cc b/chrome/browser/ui/tab_helpers.cc --- a/chrome/browser/ui/tab_helpers.cc +++ b/chrome/browser/ui/tab_helpers.cc @@ -463,6 +463,7 @@ void TabHelpers::AttachTabHelpers(WebContents* web_contents) { PolicyAuditorBridge::CreateForWebContents(web_contents); PluginObserverAndroid::CreateForWebContents(web_contents); video_tutorials::VideoTutorialTabHelper::CreateForWebContents(web_contents); + LastTabStandingTrackerTabHelper::CreateForWebContents(web_contents); #else if (web_app::AreWebAppsUserInstallable(profile)) webapps::AppBannerManagerDesktop::CreateForWebContents(web_contents); 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 @@ -9,6 +9,7 @@ import androidx.annotation.Nullable; import org.chromium.components.content_settings.ContentSettingValues; import org.chromium.components.content_settings.ContentSettingsType; import org.chromium.content_public.browser.BrowserContextHandle; +import org.chromium.components.content_settings.SessionModel; import java.io.Serializable; @@ -20,19 +21,30 @@ public class PermissionInfo implements Serializable { private final String mEmbedder; private final String mOrigin; private final @ContentSettingsType int mContentSettingsType; + private final @SessionModel int mSessionModel; public PermissionInfo(@ContentSettingsType int type, String origin, String embedder) { this(type, origin, embedder, false); } + public PermissionInfo(@ContentSettingsType int type, String origin, String embedder, boolean isEmbargoed) { + this(type, origin, embedder, isEmbargoed, 0); + } + public PermissionInfo( - @ContentSettingsType int type, String origin, String embedder, boolean isEmbargoed) { + @ContentSettingsType int type, String origin, String embedder, boolean isEmbargoed, + @SessionModel int sessionModel) { assert WebsitePermissionsFetcher.getPermissionsType(type) == WebsitePermissionsFetcher.WebsitePermissionsType.PERMISSION_INFO; mOrigin = origin; mEmbedder = embedder; mContentSettingsType = type; mIsEmbargoed = isEmbargoed; + mSessionModel = sessionModel; + } + + public @SessionModel int getSessionModel() { + return mSessionModel; } public @ContentSettingsType int getContentSettingsType() { 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 @@ -37,6 +37,7 @@ import org.chromium.components.browser_ui.settings.SettingsUtils; import org.chromium.components.browser_ui.settings.TextMessagePreference; import org.chromium.components.content_settings.ContentSettingValues; import org.chromium.components.content_settings.ContentSettingsType; +import org.chromium.components.content_settings.SessionModel; import org.chromium.components.embedder_support.util.Origin; import org.chromium.content_public.browser.BrowserContextHandle; import org.chromium.content_public.browser.ContentFeatureList; @@ -539,6 +540,11 @@ public class SingleWebsiteSettings extends SiteSettingsPreferenceFragment } } + private boolean isSessionPermission(@ContentSettingsType 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(); @@ -961,6 +967,10 @@ public class SingleWebsiteSettings extends SiteSettingsPreferenceFragment 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/java/src/org/chromium/components/browser_ui/site_settings/WebsitePreferenceBridge.java b/components/browser_ui/site_settings/android/java/src/org/chromium/components/browser_ui/site_settings/WebsitePreferenceBridge.java --- a/components/browser_ui/site_settings/android/java/src/org/chromium/components/browser_ui/site_settings/WebsitePreferenceBridge.java +++ b/components/browser_ui/site_settings/android/java/src/org/chromium/components/browser_ui/site_settings/WebsitePreferenceBridge.java @@ -9,6 +9,7 @@ import org.chromium.base.annotations.CalledByNative; import org.chromium.base.annotations.NativeMethods; import org.chromium.components.content_settings.ContentSettingValues; import org.chromium.components.content_settings.ContentSettingsType; +import org.chromium.components.content_settings.SessionModel; import org.chromium.components.location.LocationUtils; import org.chromium.content_public.browser.BrowserContextHandle; import org.chromium.url.GURL; @@ -55,7 +56,8 @@ public class WebsitePreferenceBridge { @CalledByNative private static void insertPermissionInfoIntoList(@ContentSettingsType int type, - ArrayList list, String origin, String embedder, boolean isEmbargoed) { + ArrayList list, String origin, String embedder, boolean isEmbargoed, + @SessionModel int sessionModel) { if (type == ContentSettingsType.MEDIASTREAM_CAMERA || type == ContentSettingsType.MEDIASTREAM_MIC) { for (PermissionInfo info : list) { @@ -64,7 +66,7 @@ public class WebsitePreferenceBridge { } } } - list.add(new PermissionInfo(type, origin, embedder, isEmbargoed)); + list.add(new PermissionInfo(type, origin, embedder, isEmbargoed, sessionModel)); } @CalledByNative 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 @@ -125,7 +125,8 @@ typedef void (*InfoListInsertionFunction)( const base::android::JavaRef&, const base::android::JavaRef&, const base::android::JavaRef&, - jboolean); + jboolean, + JniIntWrapper); void GetOrigins(JNIEnv* env, const JavaParamRef& jbrowser_context_handle, @@ -167,7 +168,7 @@ void GetOrigins(JNIEnv* env, seen_origins.push_back(origin); insertionFunc(env, static_cast(content_type), list, ConvertOriginToJavaString(env, origin), jembedder, - /*is_embargoed=*/false); + /*is_embargoed=*/false, static_cast(settings_it.metadata.session_model)); } // Add any origins which have a default content setting value (thus skipped @@ -189,7 +190,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_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 @@ -606,6 +606,11 @@ URL truncated + + (only this session) + + Ad personalization diff --git a/components/content_settings/android/BUILD.gn b/components/content_settings/android/BUILD.gn --- a/components/content_settings/android/BUILD.gn +++ b/components/content_settings/android/BUILD.gn @@ -58,6 +58,7 @@ java_cpp_enum("content_settings_enums_javagen") { "../core/common/content_settings_types.h", "../core/common/cookie_controls_enforcement.h", "../core/common/cookie_controls_status.h", + "../core/common/content_settings_constraints.h", ] visibility = [ ":*" ] # Depend on through :content_settings_enums_java } 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 @@ -187,6 +187,35 @@ base::Time GetConstraintExpiration(const base::TimeDelta duration) { return base::Time::Now() + duration; } +ContentSettingConstraints GetConstraintSessionExpiration(LifetimeMode lifetime_mode) { + 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 + return {base::Time() + base::Hours(1), content_settings::SessionModel::UserSession}; + } else if (lifetime_mode == LifetimeMode::UntilOriginClosed) { + return {base::Time::Now() + base::Hours(24), content_settings::SessionModel::UserSession}; + } else { + return {base::Time(), content_settings::SessionModel::UserSession}; + } +} + +bool IsConstraintSessionExpiration(const ContentSettingPatternSource& source, + LifetimeMode lifetime_mode) { + if (source.metadata.session_model != content_settings::SessionModel::UserSession) + return false; + + LifetimeMode type; + if (source.metadata.expiration == base::Time()) { + type = LifetimeMode::UntilBrowserClosed; + } else if (source.metadata.expiration == (base::Time() + base::Hours(1))) { + type = LifetimeMode::OnlyThisTime; + } else { + type = LifetimeMode::UntilOriginClosed; + } + + 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 @@ -76,6 +76,12 @@ base::Time GetConstraintExpiration(const base::TimeDelta duration); // 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 @@ -23,6 +23,8 @@ namespace content_settings { // a crash or update related restart. // OneTime: Settings will persist for the current "tab session", meaning // until the last tab from the origin is closed. +// GENERATED_JAVA_ENUM_PACKAGE: org.chromium.components.content_settings +// GENERATED_JAVA_CLASS_NAME_OVERRIDE: SessionModel enum class SessionModel { Durable = 0, UserSession = 1, @@ -31,6 +33,15 @@ enum class SessionModel { 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. struct ContentSettingConstraints { // Specification of an |expiration| provides an upper bound on the time a 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 @@ -340,9 +340,9 @@ public class PageInfoController implements PageInfoMainController, ModalDialogPr */ @CalledByNative private void addPermissionSection(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); } /** 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; + } } } @@ -123,13 +128,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 @@ -1210,6 +1210,8 @@ void PageInfo::PresentSitePermissions() { 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); if (info.primary_pattern == ContentSettingsPattern::Wildcard() && info.secondary_pattern == ContentSettingsPattern::Wildcard()) { 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 @@ -191,6 +191,7 @@ class PageInfo : private content_settings::CookieControlsView { content_settings::SettingSource source = content_settings::SETTING_SOURCE_NONE; bool is_one_time = false; + bool is_user_session = false; }; // Creates a PageInfo for the passed |url| using the given |ssl| status 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 @@ -8,6 +8,7 @@ import org.chromium.base.annotations.CalledByNative; import org.chromium.base.annotations.JNINamespace; import org.chromium.base.annotations.NativeMethods; import org.chromium.ui.base.WindowAndroid; +import org.chromium.components.content_settings.LifetimeMode; /** * Delegate class for modal permission dialogs. Contains all of the data displayed in a prompt, @@ -43,6 +44,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; + public WindowAndroid getWindow() { return mWindow; } @@ -72,6 +76,15 @@ public class PermissionDialogDelegate { PermissionDialogDelegateJni.get().accept(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/PermissionDialogModel.java b/components/permissions/android/java/src/org/chromium/components/permissions/PermissionDialogModel.java --- a/components/permissions/android/java/src/org/chromium/components/permissions/PermissionDialogModel.java +++ b/components/permissions/android/java/src/org/chromium/components/permissions/PermissionDialogModel.java @@ -17,6 +17,17 @@ 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 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 permission dialog. */ @@ -35,7 +46,7 @@ class PermissionDialogModel { TextViewCompat.setCompoundDrawablesRelativeWithIntrinsicBounds( messageTextView, delegate.getDrawableId(), 0, 0, 0); - return new PropertyModel.Builder(ModalDialogProperties.ALL_KEYS) + PropertyModel pm = new PropertyModel.Builder(ModalDialogProperties.ALL_KEYS) .with(ModalDialogProperties.CONTROLLER, controller) .with(ModalDialogProperties.CUSTOM_VIEW, customView) .with(ModalDialogProperties.POSITIVE_BUTTON_TEXT, delegate.getPrimaryButtonText()) @@ -46,6 +57,74 @@ class PermissionDialogModel { .with(ModalDialogProperties.BUTTON_TAP_PROTECTION_PERIOD_MS, UiUtils.PROMPT_INPUT_PROTECTION_SHORT_DELAY_MS) .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 radioButon = new RadioButton(context); + radioButon.setText(context.getString( + org.chromium.components.permissions.R.string.session_permissions_only_this_this)); + radioButon.setId(LifetimeMode.ONLY_THIS_TIME); + radioGroup.addView(radioButon); + + radioButon = new RadioButton(context); + radioButon.setText(context.getString( + org.chromium.components.permissions.R.string.session_permissions_until_page_close)); + radioButon.setId(LifetimeMode.UNTIL_ORIGIN_CLOSED); + radioGroup.addView(radioButon); + + radioButon = new RadioButton(context); + radioButon.setText(context.getString( + org.chromium.components.permissions.R.string.session_permissions_until_browser_close)); + radioButon.setId(LifetimeMode.UNTIL_BROWSER_CLOSED); + radioGroup.addView(radioButon); + + radioButon = new RadioButton(context); + radioButon.setText(context.getString( + org.chromium.components.permissions.R.string.session_permissions_forever)); + radioButon.setId(LifetimeMode.ALWAYS); + radioGroup.addView(radioButon); + + 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; } private static View loadDialogView(Context context) { 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 @@ -67,6 +67,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, @@ -95,12 +100,26 @@ PermissionDialogDelegate* PermissionDialogDelegate::CreateForTesting( void PermissionDialogDelegate::Accept(JNIEnv* env, const JavaParamRef& obj) { DCHECK(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(); } void PermissionDialogDelegate::Cancel(JNIEnv* env, const JavaParamRef& obj) { DCHECK(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 @@ -35,6 +35,7 @@ class PermissionDialogJavaDelegate { PermissionDialogDelegate* owner); virtual void CreateDialog(); 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 @@ -41,6 +41,14 @@ void PermissionPromptAndroid::Accept() { delegate_->Accept(); } +void PermissionPromptAndroid::AcceptThisTime(content_settings::LifetimeMode lifetimeOption) { + delegate_->AcceptThisTime(lifetimeOption); +} + +void PermissionPromptAndroid::DenyThisTime(content_settings::LifetimeMode lifetimeOption) { + delegate_->DenyThisTime(lifetimeOption); +} + void PermissionPromptAndroid::Deny() { delegate_->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 @@ -43,7 +43,9 @@ class PermissionPromptAndroid : public PermissionPrompt { void Closing(); void Accept(); + 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 @@ -130,7 +130,21 @@ void GeolocationPermissionContextAndroid::NotifyPermissionSet( bool is_final_decision) { DCHECK(!is_one_time); 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()) { @@ -143,7 +157,8 @@ void GeolocationPermissionContextAndroid::NotifyPermissionSet( if (IsInLocationSettingsBackOff(is_default_search)) { FinishNotifyPermissionSet(id, requesting_origin, embedding_origin, std::move(callback), false /* persist */, - CONTENT_SETTING_BLOCK); + CONTENT_SETTING_BLOCK, + is_one_time, lifetime_option); return; } @@ -161,7 +176,8 @@ void GeolocationPermissionContextAndroid::NotifyPermissionSet( !location_settings_dialog_callback_.is_null()) { FinishNotifyPermissionSet(id, requesting_origin, embedding_origin, std::move(callback), false /* persist */, - CONTENT_SETTING_BLOCK); + CONTENT_SETTING_BLOCK, + is_one_time, lifetime_option); return; } @@ -173,12 +189,13 @@ void GeolocationPermissionContextAndroid::NotifyPermissionSet( base::BindOnce( &GeolocationPermissionContextAndroid::OnLocationSettingsDialogShown, weak_factory_.GetWeakPtr(), requesting_origin, embedding_origin, - persist, content_setting)); + persist, content_setting, is_one_time, lifetime_option)); return; } FinishNotifyPermissionSet(id, requesting_origin, embedding_origin, - std::move(callback), persist, content_setting); + std::move(callback), persist, content_setting, + is_one_time, lifetime_option); } PermissionResult @@ -345,6 +362,7 @@ void GeolocationPermissionContextAndroid::OnLocationSettingsDialogShown( const GURL& embedding_origin, bool persist, ContentSetting content_setting, + bool is_one_time, content_settings::LifetimeMode lifetime_option, LocationSettingsDialogOutcome prompt_outcome) { bool is_default_search = IsRequestingOriginDSE(requesting_origin); if (prompt_outcome == GRANTED) { @@ -362,7 +380,8 @@ void GeolocationPermissionContextAndroid::OnLocationSettingsDialogShown( FinishNotifyPermissionSet( location_settings_dialog_request_id_, requesting_origin, embedding_origin, - std::move(location_settings_dialog_callback_), persist, content_setting); + std::move(location_settings_dialog_callback_), persist, content_setting, + is_one_time, lifetime_option); location_settings_dialog_request_id_ = PermissionRequestID(content::GlobalRenderFrameHostId(0, 0), @@ -375,10 +394,11 @@ void GeolocationPermissionContextAndroid::FinishNotifyPermissionSet( const GURL& embedding_origin, BrowserPermissionCallback callback, bool persist, - ContentSetting content_setting) { - GeolocationPermissionContext::NotifyPermissionSet( + ContentSetting content_setting, + 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=*/false, /*is_final_decision=*/true); + content_setting, /*is_one_time=*/false, /*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 @@ -88,6 +88,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; PermissionResult UpdatePermissionStatusWithDeviceStatus( PermissionResult result, const GURL& requesting_origin, @@ -130,6 +139,7 @@ class GeolocationPermissionContextAndroid const GURL& embedding_origin, bool persist, ContentSetting content_setting, + bool is_one_time, content_settings::LifetimeMode lifetime_option, LocationSettingsDialogOutcome prompt_outcome); void FinishNotifyPermissionSet(const PermissionRequestID& id, @@ -137,7 +147,9 @@ class GeolocationPermissionContextAndroid const GURL& embedding_origin, BrowserPermissionCallback callback, bool persist, - ContentSetting content_setting); + ContentSetting content_setting, + 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 @@ -248,6 +248,20 @@ PermissionContextBase::CreatePermissionRequest( std::move(delete_callback)); } +std::unique_ptr +PermissionContextBase::CreatePermissionRequest( + const GURL& request_origin, + ContentSettingsType content_settings_type, + bool has_gesture, + content::WebContents* web_contents, + PermissionRequest::PermissionDecidedCallbackWithLifetime permission_decided_callback, + base::OnceClosure delete_callback) const { + return std::make_unique( + request_origin, ContentSettingsTypeToRequestType(content_settings_type), + has_gesture, std::move(permission_decided_callback), + std::move(delete_callback)); +} + PermissionResult PermissionContextBase::GetPermissionStatus( content::RenderFrameHost* render_frame_host, const GURL& requesting_origin, @@ -427,7 +441,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); @@ -442,13 +457,13 @@ 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); } } @@ -500,11 +515,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) { @@ -534,6 +565,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 || @@ -542,6 +582,8 @@ void PermissionContextBase::UpdateContentSetting(const GURL& requesting_origin, content_settings::ContentSettingConstraints constraints = { base::Time(), 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) if (base::FeatureList::IsEnabled( 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 @@ -138,6 +138,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, @@ -159,6 +168,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, @@ -191,6 +205,14 @@ class PermissionContextBase : public content_settings::Observer { PermissionRequest::PermissionDecidedCallback permission_decided_callback, base::OnceClosure delete_callback) const; + virtual std::unique_ptr CreatePermissionRequest( + const GURL& request_origin, + ContentSettingsType content_settings_type, + bool has_gesture, + content::WebContents* web_contents, + PermissionRequest::PermissionDecidedCallbackWithLifetime permission_decided_callback, + base::OnceClosure delete_callback) const; + ContentSettingsType content_settings_type() const { return content_settings_type_; } @@ -217,7 +239,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); raw_ptr browser_context_; const ContentSettingsType content_settings_type_; 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 @@ -59,8 +59,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 @@ -27,6 +27,18 @@ PermissionRequest::PermissionRequest( permission_decided_callback_(std::move(permission_decided_callback)), delete_callback_(std::move(delete_callback)) {} +PermissionRequest::PermissionRequest( + const GURL& requesting_origin, + RequestType request_type, + bool has_gesture, + PermissionDecidedCallbackWithLifetime permission_decided_callback, + base::OnceClosure delete_callback) + : requesting_origin_(requesting_origin), + request_type_(request_type), + has_gesture_(has_gesture), + permission_decided_callback_withlifetime_(std::move(permission_decided_callback)), + delete_callback_(std::move(delete_callback)) {} + PermissionRequest::~PermissionRequest() { DCHECK(delete_callback_.is_null()); } @@ -235,19 +247,39 @@ std::u16string PermissionRequest::GetMessageTextFragment() const { } #endif -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=*/false, + /*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; + } 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::RepeatingCallback; // `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 @@ -58,6 +63,12 @@ class PermissionRequest { PermissionDecidedCallback permission_decided_callback, base::OnceClosure delete_callback); + PermissionRequest(const GURL& requesting_origin, + RequestType request_type, + bool has_gesture, + PermissionDecidedCallbackWithLifetime permission_decided_callback, + base::OnceClosure delete_callback); + PermissionRequest(const PermissionRequest&) = delete; PermissionRequest& operator=(const PermissionRequest&) = delete; @@ -113,10 +124,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 @@ -164,6 +175,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 @@ -150,7 +150,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; } @@ -226,7 +226,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; @@ -551,14 +551,15 @@ void PermissionRequestManager::Accept() { (*requests_iter)->request_type(), PermissionAction::GRANTED); PermissionGrantedIncludingDuplicates(*requests_iter, - /*is_one_time=*/false); + /*is_one_time=*/false, + content_settings::LifetimeMode::Always); } NotifyRequestDecided(PermissionAction::GRANTED); FinalizeCurrentRequests(PermissionAction::GRANTED); } -void PermissionRequestManager::AcceptThisTime() { +void PermissionRequestManager::AcceptThisTime(content_settings::LifetimeMode mode) { if (ignore_callbacks_from_prompt_) return; DCHECK(view_); @@ -569,7 +570,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); @@ -577,6 +579,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_); @@ -599,7 +610,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); @@ -1113,32 +1124,36 @@ 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, + 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); } @@ -1379,7 +1394,7 @@ void PermissionRequestManager::LogWarningToConsole(const char* message) { void PermissionRequestManager::DoAutoResponseForTesting() { switch (auto_response_for_test_) { case ACCEPT_ONCE: - AcceptThisTime(); + AcceptThisTime(content_settings::LifetimeMode::Always); 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 @@ -158,8 +158,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 PreIgnoreQuietPrompt() override; @@ -352,9 +354,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); -- 2.40.1