diff --git a/build/patches/Add-lifetime-options-permissions.patch b/build/patches/Add-lifetime-options-permissions.patch new file mode 100644 index 00000000..b432b55d --- /dev/null +++ b/build/patches/Add-lifetime-options-permissions.patch @@ -0,0 +1,1462 @@ +From: uazo +Date: Fri, 8 Apr 2022 11:04:04 +0000 +Subject: Add lifetime options support for permissions + +this patch takes advantage of the possibility to indicate the session mode for content-settings: +in fact in chromium it is possible to specify the constraint content_settings::SessionModel as +UserSession when setting the value, even choosing a possible expiration time. +The logics I have seen with the ClientHints but it seems that chromium exploits this possibility when +it is necessary to maintain a specific value by origin. +In any case, all content settings of this type are not saved on disk, allowing you to reset the status +each time the application is restarted. + +The patch acts on these points: + +components/content_settings +defines a new content_settings::LifetimeMode enum to specify the user's +choice (Always, OnlyThisTime, UntilOriginClosed, UntilBrowserClosed). +the enumeration is also generated for java by adding in content_settings_enums_javagen (gn). +enumeration is mainly used in content_settings_utils.cc to create a specific content_settings::ContentSettingConstraints +for use in SetContentSettingDefaultScope() by PermissionContextBase::UpdateContentSetting. +in fact the chromium code does not provide a specific property to define a choice, which +must instead be encoded through the ContentSettingConstraints: such mechanism is already in use +in other parts of the code. +Therefore, content_settings::GetConstraintSessionExpiration() and content_settings::IsConstraintSessionExpiration() +manage the lifetime modes of the session content-settings. +The patch also adds the session pattern to the ContentSettingPatternSource, +so that it is mainly available for the ui. + +components/permissions +actually adds lifetime support to 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 the geolocation / camera / microphone. +the mechanism I used was adding a new PermissionRequest::PermissionDecidedCallbackWithLifetime +used by an overload of PermissionContextBase::CreatePermissionRequest so that only the code +that interests me is modified: in fact the options are present only for the specific content-settings (see PermissionDialogModel.java). +for other permissions, the patch behaves as before (see PermissionDialogDelegate::Accept) +for geolocation it was necessary to act directly in the specific context, because, unlike +the microphone / camera, although the class always derives from PermissionContextBase, the value in the +content-setting is inserted in its specific method (FinishNotifyPermissionSet). + +components/page_info +here the changes make it possible 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 +the changes affect the settings ui, showing "(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, I recovered the logics used by the flag +kOneTimeGeolocationPermission active only in the desktop (files last_tab_standing_tracker_ *). +basically that class manages a list of the active origins and allows you to perform +operations when all the tabs relating to that origin are been closed: +at that moment I delete the session content settings of type UntilOriginClosed. + +see also: https://github.com/bromite/bromite/issues/1549 +--- + .../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 ++ + .../core/browser/host_content_settings_map.cc | 2 +- + .../core/common/content_settings.cc | 7 +- + .../core/common/content_settings.h | 4 +- + .../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 ++++++++++++++++++- + .../android/permission_dialog_delegate.cc | 19 +++++ + .../android/permission_dialog_delegate.h | 1 + + .../android/permission_prompt_android.cc | 8 ++ + .../android/permission_prompt_android.h | 2 + + .../android/permissions_android_strings.grd | 17 ++++ + .../geolocation_permission_context_android.cc | 35 ++++++-- + .../geolocation_permission_context_android.h | 13 ++- + .../permissions/permission_context_base.cc | 50 ++++++++++-- + .../permissions/permission_context_base.h | 24 +++++- + components/permissions/permission_prompt.h | 3 +- + components/permissions/permission_request.cc | 34 +++++++- + components/permissions/permission_request.h | 17 +++- + .../permissions/permission_request_manager.cc | 38 ++++++--- + .../permissions/permission_request_manager.h | 11 ++- + media/base/media_switches.cc | 2 +- + 39 files changed, 482 insertions(+), 63 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,9 +7,35 @@ + #include "base/observer_list.h" + #include "chrome/browser/profiles/profile.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() { +@@ -55,5 +81,10 @@ void LastTabStandingTracker::WebContentsUnloadedOrigin( + tab_counter_.erase(origin); + for (auto& observer : observer_list_) + observer.OnLastPageFromOriginClosed(origin); ++ auto 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 +@@ -33,7 +33,7 @@ bool LastTabStandingTrackerFactory::ServiceIsCreatedWithBrowserContext() const { + + KeyedService* LastTabStandingTrackerFactory::BuildServiceInstanceFor( + content::BrowserContext* context) const { +- return new LastTabStandingTracker(); ++ return new LastTabStandingTracker(context); + } + + content::BrowserContext* LastTabStandingTrackerFactory::GetBrowserContextToUse( +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 +@@ -365,10 +365,7 @@ void ChromeBrowserMainExtraPartsProfiles:: + InstantServiceFactory::GetInstance(); + #endif + LanguageModelManagerFactory::GetInstance(); +- if (base::FeatureList::IsEnabled( +- permissions::features::kOneTimeGeolocationPermission)) { +- LastTabStandingTrackerFactory::GetInstance(); +- } ++ LastTabStandingTrackerFactory::GetInstance(); + login_detection::LoginDetectionKeyedServiceFactory::GetInstance(); + #if !BUILDFLAG(IS_ANDROID) + LoginUIServiceFactory::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 +@@ -454,6 +454,7 @@ void TabHelpers::AttachTabHelpers(WebContents* web_contents) { + web_contents); + } + video_tutorials::VideoTutorialTabHelper::CreateForWebContents(web_contents); ++ LastTabStandingTrackerTabHelper::CreateForWebContents(web_contents); + #else + if (accuracy_tips::AccuracyWebContentsObserver::IsEnabled(web_contents)) { + accuracy_tips::AccuracyWebContentsObserver::CreateForWebContents( +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 +@@ -34,6 +34,7 @@ import org.chromium.components.browser_ui.settings.ManagedPreferencesUtils; + import org.chromium.components.browser_ui.settings.SettingsUtils; + 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; + +@@ -527,6 +528,11 @@ public class SingleWebsiteSettings extends SiteSettingsPreferenceFragment + return mSite.getPermissionInfo(type) != null && mSite.getPermissionInfo(type).isEmbargoed(); + } + ++ 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(); +@@ -919,6 +925,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; +@@ -54,7 +55,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) { +@@ -63,7 +65,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 +@@ -112,7 +112,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, +@@ -153,7 +154,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.session_model)); + } + + // Add any origins which have a default content setting value (thus skipped +@@ -176,7 +177,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 +@@ -581,6 +581,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 +@@ -57,6 +57,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,4 +187,33 @@ 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.session_model != content_settings::SessionModel::UserSession) ++ return false; ++ ++ LifetimeMode type; ++ if (source.expiration == base::Time()) { ++ type = LifetimeMode::UntilBrowserClosed; ++ } else if (source.expiration == (base::Time() + base::Hours(1))) { ++ type = LifetimeMode::OnlyThisTime; ++ } else { ++ type = LifetimeMode::UntilOriginClosed; ++ } ++ ++ return lifetime_mode == type; ++} ++ + } // namespace content_settings +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 the expiration time for a supplied |duration|. + base::Time GetConstraintExpiration(const base::TimeDelta duration); + ++ContentSettingConstraints GetConstraintSessionExpiration(LifetimeMode lifetime_mode); ++ ++bool IsConstraintSessionExpiration( ++ const ContentSettingPatternSource& source, ++ LifetimeMode lifetime_mode); ++ + } // namespace content_settings + + #endif // COMPONENTS_CONTENT_SETTINGS_CORE_BROWSER_CONTENT_SETTINGS_UTILS_H_ +diff --git a/components/content_settings/core/browser/host_content_settings_map.cc b/components/content_settings/core/browser/host_content_settings_map.cc +--- a/components/content_settings/core/browser/host_content_settings_map.cc ++++ b/components/content_settings/core/browser/host_content_settings_map.cc +@@ -821,7 +821,7 @@ void HostContentSettingsMap::AddSettingsForOneType( + settings->emplace_back(rule.primary_pattern, rule.secondary_pattern, + std::move(value), + kProviderNamesSourceMap[provider_type].provider_name, +- incognito, rule.expiration); ++ incognito, rule.expiration, rule.session_model); + } + } + +diff --git a/components/content_settings/core/common/content_settings.cc b/components/content_settings/core/common/content_settings.cc +--- a/components/content_settings/core/common/content_settings.cc ++++ b/components/content_settings/core/common/content_settings.cc +@@ -147,13 +147,15 @@ ContentSettingPatternSource::ContentSettingPatternSource( + base::Value setting_value, + const std::string& source, + bool incognito, +- base::Time expiration) ++ base::Time expiration, ++ content_settings::SessionModel session_model) + : primary_pattern(primary_pattern), + secondary_pattern(secondary_pattern), + setting_value(std::move(setting_value)), + expiration(expiration), + source(source), +- incognito(incognito) {} ++ incognito(incognito), ++ session_model(session_model) {} + + ContentSettingPatternSource::ContentSettingPatternSource() : incognito(false) {} + +@@ -170,6 +172,7 @@ ContentSettingPatternSource& ContentSettingPatternSource::operator=( + expiration = other.expiration; + source = other.source; + incognito = other.incognito; ++ session_model = other.session_model; + return *this; + } + +diff --git a/components/content_settings/core/common/content_settings.h b/components/content_settings/core/common/content_settings.h +--- a/components/content_settings/core/common/content_settings.h ++++ b/components/content_settings/core/common/content_settings.h +@@ -49,7 +49,8 @@ struct ContentSettingPatternSource { + base::Value setting_value, + const std::string& source, + bool incognito, +- base::Time expiration = base::Time()); ++ base::Time expiration = base::Time(), ++ content_settings::SessionModel session_model = content_settings::SessionModel::Durable); + ContentSettingPatternSource(const ContentSettingPatternSource& other); + ContentSettingPatternSource(); + ContentSettingPatternSource& operator=( +@@ -64,6 +65,7 @@ struct ContentSettingPatternSource { + base::Time expiration; + std::string source; + bool incognito; ++ content_settings::SessionModel session_model; + }; + + typedef std::vector ContentSettingsForOneType; +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 +@@ -18,6 +18,8 @@ namespace content_settings { + // regardless of expiry date, if set. + // 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, +@@ -25,6 +27,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 +@@ -378,9 +378,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; ++ } + } + } + +@@ -124,13 +129,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 +@@ -166,6 +166,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)) { +@@ -174,6 +176,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; + } + } + } +@@ -190,7 +194,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]); + } + } + +@@ -203,7 +208,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 +@@ -1000,6 +1000,8 @@ void PageInfo::PresentSitePermissions() { + permission_info.source = info.source; + permission_info.is_one_time = + (info.session_model == content_settings::SessionModel::OneTime); ++ permission_info.is_user_session = ++ (info.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 +@@ -173,6 +173,7 @@ class PageInfo { + 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 +@@ -16,6 +16,17 @@ import org.chromium.ui.LayoutInflaterUtils; + 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. + */ +@@ -34,7 +45,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()) +@@ -43,6 +54,74 @@ class PermissionDialogModel { + .with(ModalDialogProperties.FILTER_TOUCH_FOR_SECURITY, true) + .with(ModalDialogProperties.TOUCH_FILTERED_CALLBACK, touchFilteredCallback) + .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_dialog_delegate.cc b/components/permissions/android/permission_dialog_delegate.cc +--- a/components/permissions/android/permission_dialog_delegate.cc ++++ b/components/permissions/android/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_dialog_delegate.h b/components/permissions/android/permission_dialog_delegate.h +--- a/components/permissions/android/permission_dialog_delegate.h ++++ b/components/permissions/android/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_android.cc b/components/permissions/android/permission_prompt_android.cc +--- a/components/permissions/android/permission_prompt_android.cc ++++ b/components/permissions/android/permission_prompt_android.cc +@@ -83,6 +83,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_android.h b/components/permissions/android/permission_prompt_android.h +--- a/components/permissions/android/permission_prompt_android.h ++++ b/components/permissions/android/permission_prompt_android.h +@@ -50,7 +50,9 @@ class PermissionPromptAndroid : public permissions::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 +@@ -260,6 +260,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 +@@ -158,6 +158,19 @@ void GeolocationPermissionContextAndroid::NotifyPermissionSet( + ContentSetting content_setting, + bool is_one_time) { + DCHECK(!is_one_time); ++ NotifyPermissionSetWithLifetime(id, requesting_origin, embedding_origin, ++ std::move(callback), persist, content_setting, is_one_time, ++ 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, content_settings::LifetimeMode lifetime_option) { + bool is_default_search = IsRequestingOriginDSE(requesting_origin); + if (content_setting == CONTENT_SETTING_ALLOW && + !location_settings_->IsSystemLocationSettingEnabled()) { +@@ -170,7 +183,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); + LogLocationSettingsMetric( + kLocationSettingsSuppressMetricBase, is_default_search, + LocationSettingsBackOffLevel(is_default_search)); +@@ -195,7 +209,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); + // This case should be very rare, so just pretend it was a denied prompt + // for metrics purposes. + LogLocationSettingsMetric( +@@ -212,12 +227,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 +@@ -384,6 +400,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) { +@@ -411,7 +428,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(0, 0, PermissionRequestID::RequestLocalId()); +@@ -423,10 +441,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); ++ content_setting, is_one_time, 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,14 @@ class GeolocationPermissionContextAndroid + bool persist, + ContentSetting content_setting, + bool is_one_time) 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, ++ content_settings::LifetimeMode lifetime_option) override; + PermissionResult UpdatePermissionStatusWithDeviceStatus( + PermissionResult result, + const GURL& requesting_origin, +@@ -130,6 +138,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 +146,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 +@@ -242,6 +242,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, +@@ -434,7 +448,8 @@ void PermissionContextBase::PermissionDecided( + const GURL& embedding_origin, + BrowserPermissionCallback callback, + ContentSetting content_setting, +- bool is_one_time) { ++ bool is_one_time, ++ content_settings::LifetimeMode lifetime_option) { + DCHECK(content_setting == CONTENT_SETTING_ALLOW || + content_setting == CONTENT_SETTING_BLOCK || + content_setting == CONTENT_SETTING_DEFAULT); +@@ -442,9 +457,9 @@ void PermissionContextBase::PermissionDecided( + content_setting); + + bool persist = content_setting != CONTENT_SETTING_DEFAULT; +- NotifyPermissionSet(id, requesting_origin, embedding_origin, ++ NotifyPermissionSetWithLifetime(id, requesting_origin, embedding_origin, + std::move(callback), persist, content_setting, +- is_one_time); ++ is_one_time, lifetime_option); + } + + content::BrowserContext* PermissionContextBase::browser_context() const { +@@ -494,11 +509,26 @@ void PermissionContextBase::NotifyPermissionSet( + bool persist, + ContentSetting content_setting, + bool is_one_time) { ++ DCHECK(is_one_time == false); ++ NotifyPermissionSetWithLifetime(id, requesting_origin, embedding_origin, std::move(callback), ++ persist, content_setting, is_one_time, ++ 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, ++ 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); + } + + UpdateTabContext(id, requesting_origin, +@@ -519,6 +549,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 || +@@ -530,8 +569,7 @@ void PermissionContextBase::UpdateContentSetting(const GURL& requesting_origin, + ->SetContentSettingDefaultScope( + requesting_origin, embedding_origin, content_settings_type_, + content_setting, +- is_one_time ? Constraints{base::Time(), +- content_settings::SessionModel::OneTime} ++ is_one_time ? content_settings::GetConstraintSessionExpiration(lifetime_option) + : Constraints()); + } + +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 +@@ -143,6 +143,14 @@ class PermissionContextBase : public KeyedService, + + // 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, ++ content_settings::LifetimeMode lifetime_option); + virtual void NotifyPermissionSet(const PermissionRequestID& id, + const GURL& requesting_origin, + const GURL& embedding_origin, +@@ -163,6 +171,11 @@ class PermissionContextBase : public KeyedService, + // 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, +@@ -195,6 +208,14 @@ class PermissionContextBase : public KeyedService, + 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_; + } +@@ -221,7 +242,8 @@ class PermissionContextBase : public KeyedService, + const GURL& embedding_origin, + BrowserPermissionCallback callback, + ContentSetting content_setting, +- bool is_one_time); ++ bool is_one_time, ++ 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 +@@ -30,6 +30,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()); + } +@@ -237,17 +249,35 @@ 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, lifetime_option); ++ return; ++ } + std::move(permission_decided_callback_) + .Run(CONTENT_SETTING_ALLOW, is_one_time); + } + +-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, lifetime_option); ++ return; ++ } + std::move(permission_decided_callback_) + .Run(CONTENT_SETTING_BLOCK, /*is_one_time=*/false); + } + + void PermissionRequest::Cancelled() { ++ if (permission_decided_callback_withlifetime_) { ++ std::move(permission_decided_callback_withlifetime_) ++ .Run(CONTENT_SETTING_DEFAULT, false, ++ content_settings::LifetimeMode::Always); ++ return; ++ } + std::move(permission_decided_callback_) + .Run(CONTENT_SETTING_DEFAULT, /*is_one_time=*/false); + } +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 +@@ -39,6 +39,10 @@ class PermissionRequest { + using PermissionDecidedCallback = + base::OnceCallback; + ++ 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 +@@ -54,6 +58,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; + +@@ -97,10 +107,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 +@@ -138,6 +148,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; + } +@@ -210,7 +210,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; +@@ -469,12 +469,13 @@ void PermissionRequestManager::Accept() { + for (requests_iter = requests_.begin(); requests_iter != requests_.end(); + requests_iter++) { + PermissionGrantedIncludingDuplicates(*requests_iter, +- /*is_one_time=*/false); ++ /*is_one_time=*/false, ++ content_settings::LifetimeMode::Always); + } + FinalizeCurrentRequests(PermissionAction::GRANTED); + } + +-void PermissionRequestManager::AcceptThisTime() { ++void PermissionRequestManager::AcceptThisTime(content_settings::LifetimeMode mode) { + if (ignore_callbacks_from_prompt_) + return; + DCHECK(view_); +@@ -482,12 +483,22 @@ void PermissionRequestManager::AcceptThisTime() { + for (requests_iter = requests_.begin(); requests_iter != requests_.end(); + requests_iter++) { + PermissionGrantedIncludingDuplicates(*requests_iter, +- /*is_one_time=*/true); ++ /*is_one_time=*/true, ++ mode); + } + FinalizeCurrentRequests(PermissionAction::GRANTED_ONCE); + } + + 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_); +@@ -509,7 +520,7 @@ void PermissionRequestManager::Deny() { + std::vector::iterator requests_iter; + for (requests_iter = requests_.begin(); requests_iter != requests_.end(); + requests_iter++) { +- PermissionDeniedIncludingDuplicates(*requests_iter); ++ PermissionDeniedIncludingDuplicates(*requests_iter, is_one_time, lifetime_option); + } + FinalizeCurrentRequests(PermissionAction::DENIED); + } +@@ -858,25 +869,26 @@ PermissionRequest* PermissionRequestManager::GetExistingRequest( + + void PermissionRequestManager::PermissionGrantedIncludingDuplicates( + PermissionRequest* request, +- bool is_one_time) { ++ bool is_one_time, content_settings::LifetimeMode lifetime_option) { + DCHECK_EQ(1, base::STLCount(requests_, request) + + base::STLCount(queued_requests_, request)) + << "Only requests in [queued_[frame_]]requests_ can have duplicates"; +- request->PermissionGranted(is_one_time); ++ request->PermissionGranted(is_one_time, lifetime_option); + auto range = duplicate_requests_.equal_range(request); + for (auto it = range.first; it != range.second; ++it) +- it->second->PermissionGranted(is_one_time); ++ it->second->PermissionGranted(is_one_time, lifetime_option); + } + + void PermissionRequestManager::PermissionDeniedIncludingDuplicates( +- PermissionRequest* request) { ++ PermissionRequest* request, bool is_one_time, ++ content_settings::LifetimeMode lifetime_option) { + DCHECK_EQ(1, base::STLCount(requests_, request) + + base::STLCount(queued_requests_, request)) + << "Only requests in [queued_]requests_ can have duplicates"; +- request->PermissionDenied(); ++ request->PermissionDenied(is_one_time, lifetime_option); + auto range = duplicate_requests_.equal_range(request); + for (auto it = range.first; it != range.second; ++it) +- it->second->PermissionDenied(); ++ it->second->PermissionDenied(is_one_time, lifetime_option); + } + + void PermissionRequestManager::CancelledIncludingDuplicates( +@@ -1044,7 +1056,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 +@@ -136,8 +136,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; + bool WasCurrentRequestAlreadyDisplayed() override; +@@ -268,9 +270,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); + // Calls RequestFinished on a request and all its duplicates. +diff --git a/media/base/media_switches.cc b/media/base/media_switches.cc +--- a/media/base/media_switches.cc ++++ b/media/base/media_switches.cc +@@ -469,7 +469,7 @@ const base::Feature kUseR16Texture{"use-r16-texture", + // Enables the Unified Autoplay policy by overriding the platform's default + // autoplay policy. + const base::Feature kUnifiedAutoplay{"UnifiedAutoplay", +- base::FEATURE_DISABLED_BY_DEFAULT}; ++ base::FEATURE_ENABLED_BY_DEFAULT}; + + #if BUILDFLAG(IS_LINUX) + // Enable vaapi video decoding on linux. This is already enabled by default on +-- +2.25.1