From: uazo <uazo@users.noreply.github.com>
Date: Fri, 8 Apr 2022 11:04:04 +0000
Subject: Add lifetime options for permissions

Indicate the session mode for content-settings by using the constraint `content_settings::SessionModel` as
UserSession when setting the value, and also make use of an expiration time value.
This is used in Chromium for `ClientHints` but it is generally possible to use this functionality when a
specific value needs to be persisted by origin.
All content settings of this type are not saved on disk (except for the `Forever` option), allowing user to
reset the status each time application is restarted.

There are 4 main areas affected to introduce the functionality:
* components/content_settings
  A new `content_settings::LifetimeMode` enum value is defined to specify the user's
  choice (Always, OnlyThisTime, UntilOriginClosed, UntilBrowserClosed).
  Enumeration is also generated for java by adding it in `content_settings_enums_javagen` (gn).
  This is mainly used in `content_settings_utils.cc` to create a specialised `content_settings::ContentSettingConstraints`
  that is then used in `SetContentSettingDefaultScope()` by `PermissionContextBase::UpdateContentSetting`.
  Existing Chromium data structures do not provide a specific property to define a choice which is instead encoded through
  the `ContentSettingConstraints`; this approach is already used in other parts of the Chromium codebase so it is not
  novel here.
  Therefore, `content_settings::GetConstraintSessionExpiration()` and `content_settings::IsConstraintSessionExpiration()`
  manage the lifetime modes of the session content-settings.
  The modificaiton also adds the session pattern to the ContentSettingPatternSource so that it is available for the UI.
* components/permissions
  Lifetime support is added to the permissions; most of the changes are caused by the fact that it is necessary to report
  the value selected by the user from the Java UI managed by `components/browser_ui` up to
  `PermissionContextBase::UpdateContentSetting()`,   without necessarily having to modify all requests that are not
  related to geolocation/camera/microphone. The approach used is a new
  `PermissionRequest::PermissionDecidedCallbackWithLifetime` used by an overload of
  `PermissionContextBase::CreatePermissionRequest` so that options are present only for the specific content-settings
  (see `PermissionDialogModel.java`).
  For other permissions no behaviour is changed (see `PermissionDialogDelegate::Accept`); for geolocation it was
  necessary to act directly in the specific context, because, unlike microphone/camera, the content-setting value is
  inserted in its specific method (`FinishNotifyPermissionSet`, that calls the callback), even if the class always
  derives from `PermissionContextBase`.
* components/page_info
  Some changes needed to see in the summary of the `page_info` the text "(only this session)"
  (aka `page_info_android_permission_session_permission`) through adding a new property "is_user_session" in
  `PageInfoPermissionEntry` (Java).
* components/browser_ui
  Changes to the Settings UI to show "(only this session)" in the specific content-setting.
  The same view is used both in the settings and in the page_info.

For the management of `UntilOriginClosed` the logic used by flag `kOneTimeGeolocationPermission` was used; this flag
is active only in the desktop (files `last_tab_standing_tracker_*`). It is a class that manages a list of the active
origins and allows to perform operations when all the tabs relating to that origin have been closed, in this case
deleting the session content settings of `UntilOriginClosed`.

See also: https://github.com/bromite/bromite/issues/1549

Original License: GPL-2.0-or-later - https://spdx.org/licenses/GPL-2.0-or-later.html
License: GPL-3.0-only - https://spdx.org/licenses/GPL-3.0-only.html
---
 .../permissions/last_tab_standing_tracker.cc  | 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 | 35 ++++++--
 .../geolocation_permission_context_android.h  | 13 ++-
 .../permissions/permission_context_base.cc    | 49 ++++++++++-
 .../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 | 37 ++++++---
 .../permissions/permission_request_manager.h  | 11 ++-
 35 files changed, 472 insertions(+), 56 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);
+    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<LastTabStandingTrackerObserver> observer_list_;
   // Tracks how many tabs of a particular origin are open at any given time.
   std::map<url::Origin, int> tab_counter_;
+  raw_ptr<content::BrowserContext> 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
@@ -411,10 +411,7 @@ void ChromeBrowserMainExtraPartsProfiles::
   LacrosFirstRunServiceFactory::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
@@ -491,6 +491,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 (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
@@ -35,6 +35,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();
@@ -963,6 +969,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<PermissionInfo> list, String origin, String embedder, boolean isEmbargoed) {
+            ArrayList<PermissionInfo> 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
@@ -122,7 +122,8 @@ typedef void (*InfoListInsertionFunction)(
     const base::android::JavaRef<jobject>&,
     const base::android::JavaRef<jstring>&,
     const base::android::JavaRef<jstring>&,
-    jboolean);
+    jboolean,
+    JniIntWrapper);
 
 void GetOrigins(JNIEnv* env,
                 const JavaParamRef<jobject>& jbrowser_context_handle,
@@ -164,7 +165,7 @@ void GetOrigins(JNIEnv* env,
     seen_origins.push_back(origin);
     insertionFunc(env, static_cast<int>(content_type), list,
                   ConvertOriginToJavaString(env, origin), jembedder,
-                  /*is_embargoed=*/false);
+                  /*is_embargoed=*/false, static_cast<int>(settings_it.metadata.session_model));
   }
 
   // Add any origins which have a default content setting value (thus skipped
@@ -186,7 +187,7 @@ void GetOrigins(JNIEnv* env,
       seen_origins.push_back(origin);
       insertionFunc(env, static_cast<int>(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
@@ -608,6 +608,11 @@
       <message name="IDS_PAGE_INFO_URL_TRUNCATED" desc="Accessibility announcement when the URL in PageInfo switches from full to truncated display">
         URL truncated
       </message>
+      <message name="IDS_PAGE_INFO_ANDROID_PERMISSION_SESSION_PERMISSION"
+               desc="The label used in the About Page dialog to indicate a session permission">
+        (only this session)
+      </message>
+
       <message name="IDS_PAGE_INFO_AD_PERSONALIZATION_TITLE" desc="The title of the 'Ad personalization' section in Page Info.">
         Ad personalization
       </message>
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
@@ -189,6 +189,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) {
 #if BUILDFLAG(IS_ANDROID)
   // The notification provider on Android does not support last visit tracking.
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 GetCoarseTime(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
@@ -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
@@ -360,9 +360,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
@@ -171,6 +171,8 @@ void PageInfoControllerAndroid::SetPermissionInfo(
 
   std::map<ContentSettingsType, ContentSetting>
       user_specified_settings_to_display;
+  std::map<ContentSettingsType, bool>
+      user_specified_settings_is_user_session;
 
   for (const auto& permission : permission_info_list) {
     if (base::Contains(permissions_to_display, permission.type)) {
@@ -179,6 +181,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;
       }
     }
   }
@@ -195,7 +199,8 @@ void PageInfoControllerAndroid::SetPermissionInfo(
           ConvertUTF16ToJavaString(env, setting_title),
           ConvertUTF16ToJavaString(env, setting_title_mid_sentence),
           static_cast<jint>(permission),
-          static_cast<jint>(user_specified_settings_to_display[permission]));
+          static_cast<jint>(user_specified_settings_to_display[permission]),
+          user_specified_settings_is_user_session[permission]);
     }
   }
 
@@ -208,7 +213,8 @@ void PageInfoControllerAndroid::SetPermissionInfo(
         env, controller_jobject_, ConvertUTF16ToJavaString(env, object_title),
         ConvertUTF16ToJavaString(env, object_title),
         static_cast<jint>(chosen_object->ui_info.content_settings_type),
-        static_cast<jint>(CONTENT_SETTING_ALLOW));
+        static_cast<jint>(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
@@ -1189,6 +1189,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
@@ -180,6 +180,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
@@ -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_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<jobject>& obj) {
   DCHECK(permission_prompt_);
+  content_settings::LifetimeMode lifetimeOption =
+    static_cast<content_settings::LifetimeMode>(
+      java_delegate_->GetSelectedLifetimeOption());
+  if (lifetimeOption != content_settings::LifetimeMode::Always) {
+    permission_prompt_->AcceptThisTime(lifetimeOption);
+    return;
+  }
   permission_prompt_->Accept();
 }
 
 void PermissionDialogDelegate::Cancel(JNIEnv* env,
                                       const JavaParamRef<jobject>& obj) {
   DCHECK(permission_prompt_);
+  content_settings::LifetimeMode lifetimeOption =
+    static_cast<content_settings::LifetimeMode>(
+      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<jobject> 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 (<ph name="DEVICE_ID">%1$s<ex>A1:B2:C3:D4:E5:F6</ex></ph>)
       </message>
 
+      <!-- Session permissions -->
+      <message name="IDS_SESSION_PERMISSIONS_TITLE" desc="Title for the session section in the permission request">
+        Remeber my decision
+      </message>
+      <message name="IDS_SESSION_PERMISSIONS_ONLY_THIS_THIS" desc="Message indicating that the permission is only for this time">
+        Only this time
+      </message>
+      <message name="IDS_SESSION_PERMISSIONS_UNTIL_PAGE_CLOSE" desc="Message indicating that the permission is deleted after navigating away from the page">
+        Until all pages of this origin are closed
+      </message>
+      <message name="IDS_SESSION_PERMISSIONS_UNTIL_BROWSER_CLOSE" desc="Message indicating that the permission is for the session only">
+        Until Bromite is closed
+      </message>
+      <message name="IDS_SESSION_PERMISSIONS_FOREVER" desc="Message indicating that the permission is for all sessions">
+        Forever
+      </message>
+
       <!-- Item Chooser UI strings -->
       <message name="IDS_ITEM_CHOOSER_ITEM_NAME_WITH_ID" desc="To distinguish items with the same name, the item chooser shows the item name with id.">
         <ph name="ITEM_NAME">%1$s<ex>item_name</ex></ph> (<ph name="ITEM_ID">%2$s<ex>item id</ex></ph>)
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
@@ -153,6 +153,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()) {
@@ -165,7 +178,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));
@@ -190,7 +204,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(
@@ -207,12 +222,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
@@ -379,6 +395,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) {
@@ -402,7 +419,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());
@@ -414,10 +432,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
@@ -87,6 +87,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,
@@ -129,6 +137,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,
@@ -136,7 +145,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<LocationSettings> 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
@@ -226,6 +226,20 @@ PermissionContextBase::CreatePermissionRequest(
       std::move(delete_callback));
 }
 
+std::unique_ptr<PermissionRequest>
+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<PermissionRequest>(
+      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,
@@ -405,7 +419,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);
@@ -413,9 +428,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 {
@@ -465,11 +480,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,
@@ -490,6 +520,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 ||
@@ -498,6 +537,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
@@ -139,6 +139,14 @@ 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,
+                                   content_settings::LifetimeMode lifetime_option);
   virtual void NotifyPermissionSet(const PermissionRequestID& id,
                                    const GURL& requesting_origin,
                                    const GURL& embedding_origin,
@@ -159,6 +167,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 +204,14 @@ class PermissionContextBase : public content_settings::Observer {
       PermissionRequest::PermissionDecidedCallback permission_decided_callback,
       base::OnceClosure delete_callback) const;
 
+  virtual std::unique_ptr<PermissionRequest> 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 +238,8 @@ class PermissionContextBase : public content_settings::Observer {
                          const GURL& embedding_origin,
                          BrowserPermissionCallback callback,
                          ContentSetting content_setting,
-                         bool is_one_time);
+                         bool is_one_time,
+                         content_settings::LifetimeMode lifetime_option);
 
   raw_ptr<content::BrowserContext> 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
@@ -31,6 +31,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,17 +247,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<void(ContentSetting /*result*/, bool /*is_one_time*/)>;
 
+  using PermissionDecidedCallbackWithLifetime =
+      base::OnceCallback<void(ContentSetting /*result*/, bool /*is_one_time*/,
+        content_settings::LifetimeMode /*lifetime_option*/)>;
+
   // `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;
 
@@ -106,10 +116,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
@@ -147,6 +157,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
@@ -148,7 +148,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;
   }
@@ -224,7 +224,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;
@@ -541,14 +541,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_);
@@ -559,7 +560,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);
@@ -567,6 +569,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_);
@@ -589,7 +600,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);
@@ -967,25 +978,25 @@ PermissionRequest* PermissionRequestManager::GetExistingRequest(
 
 void PermissionRequestManager::PermissionGrantedIncludingDuplicates(
     PermissionRequest* request,
-    bool is_one_time) {
+    bool is_one_time, content_settings::LifetimeMode lifetime_option) {
   DCHECK_EQ(1ul, base::STLCount(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);
   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(1ul, base::STLCount(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);
   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(
@@ -1187,7 +1198,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
@@ -141,8 +141,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;
@@ -303,9 +305,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.
--
2.25.1
