From: uazo Date: Fri, 8 Apr 2022 11:04:04 +0000 Subject: Add lifetime options for permissions Indicate the session mode for content-settings by using the constraint `content_settings::SessionModel` as UserSession when setting the value, and also make use of an expiration time value. This is used in Chromium for `ClientHints` but it is generally possible to use this functionality when a specific value needs to be persisted by origin. All content settings of this type are not saved on disk (except for the `Forever` option), allowing user to reset the status each time application is restarted. There are 4 main areas affected to introduce the functionality: * components/content_settings A new `content_settings::LifetimeMode` enum value is defined to specify the user's choice (Always, OnlyThisTime, UntilOriginClosed, UntilBrowserClosed). Enumeration is also generated for java by adding it in `content_settings_enums_javagen` (gn). This is mainly used in `content_settings_utils.cc` to create a specialised `content_settings::ContentSettingConstraints` that is then used in `SetContentSettingDefaultScope()` by `PermissionContextBase::UpdateContentSetting`. Existing Chromium data structures do not provide a specific property to define a choice which is instead encoded through the `ContentSettingConstraints`; this approach is already used in other parts of the Chromium codebase so it is not novel here. Therefore, `content_settings::GetConstraintSessionExpiration()` and `content_settings::IsConstraintSessionExpiration()` manage the lifetime modes of the session content-settings. The modificaiton also adds the session pattern to the ContentSettingPatternSource so that it is available for the UI. * components/permissions Lifetime support is added to the permissions; most of the changes are caused by the fact that it is necessary to report the value selected by the user from the Java UI managed by `components/browser_ui` up to `PermissionContextBase::UpdateContentSetting()`, without necessarily having to modify all requests that are not related to geolocation/camera/microphone. The approach used is a new `PermissionRequest::PermissionDecidedCallbackWithLifetime` used by an overload of `PermissionContextBase::CreatePermissionRequest` so that options are present only for the specific content-settings (see `PermissionDialogModel.java`). For other permissions no behaviour is changed (see `PermissionDialogDelegate::Accept`); for geolocation it was necessary to act directly in the specific context, because, unlike microphone/camera, the content-setting value is inserted in its specific method (`FinishNotifyPermissionSet`, that calls the callback), even if the class always derives from `PermissionContextBase`. * components/page_info Some changes needed to see in the summary of the `page_info` the text "(only this session)" (aka `page_info_android_permission_session_permission`) through adding a new property "is_user_session" in `PageInfoPermissionEntry` (Java). * components/browser_ui Changes to the Settings UI to show "(only this session)" in the specific content-setting. The same view is used both in the settings and in the page_info. For the management of `UntilOriginClosed` the logic used by flag `kOneTimeGeolocationPermission` was used; this flag is active only in the desktop (files `last_tab_standing_tracker_*`). It is a class that manages a list of the active origins and allows to perform operations when all the tabs relating to that origin have been closed, in this case deleting the session content settings of `UntilOriginClosed`. See also: https://github.com/bromite/bromite/issues/1549 Original License: GPL-2.0-or-later - https://spdx.org/licenses/GPL-2.0-or-later.html License: GPL-3.0-only - https://spdx.org/licenses/GPL-3.0-only.html --- .../permissions/last_tab_standing_tracker.cc | 32 ++++++++ .../one_time_permissions_tracker.cc | 33 +++++++- .../one_time_permissions_tracker.h | 5 +- .../one_time_permissions_tracker_factory.cc | 2 +- .../embedded_permission_prompt_ask_view.cc | 2 +- .../permission_prompt_bubble_base_view.cc | 2 +- .../browser/content_autofill_driver.cc | 64 ++++++++------- .../content/browser/content_autofill_driver.h | 2 +- .../autofill/core/browser/autofill_driver.h | 2 + .../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 | 36 +++++++++ .../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 | 14 +++- .../permissions/permission_context_base.cc | 51 ++++++++++-- .../permissions/permission_context_base.h | 23 +++++- components/permissions/permission_prompt.h | 3 +- components/permissions/permission_request.cc | 30 ++++++- components/permissions/permission_request.h | 16 +++- .../permissions/permission_request_manager.cc | 45 +++++++---- .../permissions/permission_request_manager.h | 11 ++- 39 files changed, 547 insertions(+), 92 deletions(-) diff --git a/chrome/browser/permissions/last_tab_standing_tracker.cc b/chrome/browser/permissions/last_tab_standing_tracker.cc --- a/chrome/browser/permissions/last_tab_standing_tracker.cc +++ b/chrome/browser/permissions/last_tab_standing_tracker.cc @@ -7,6 +7,32 @@ #include "base/observer_list.h" #include "url/gurl.h" +#include "components/content_settings/core/browser/host_content_settings_map.h" +#include "components/content_settings/core/common/content_settings_utils.h" +#include "components/permissions/permissions_client.h" + +namespace { + // Remove all sessions content setting by origin and type + void RemoveSessionSettings(HostContentSettingsMap* content_settings, + const url::Origin& origin, + ContentSettingsType type) { + ContentSettingsForOneType session_settings = + content_settings->GetSettingsForOneType( + type, content_settings::SessionModel::UserSession); + + GURL url = origin.GetURL(); + for (ContentSettingPatternSource& entry : session_settings) { + if (content_settings::IsConstraintSessionExpiration(entry, + content_settings::LifetimeMode::UntilOriginClosed) && + entry.primary_pattern.Matches(url)) { + content_settings->SetWebsiteSettingCustomScope( + entry.primary_pattern, entry.secondary_pattern, + type, base::Value()); + } + } + } +} + LastTabStandingTracker::LastTabStandingTracker(content::BrowserContext* context) : context_(context) {} @@ -56,4 +82,10 @@ void LastTabStandingTracker::WebContentsUnloadedOrigin( for (auto& observer : observer_list_) { observer.OnLastPageFromOriginClosed(origin); } + HostContentSettingsMap* content_settings = + permissions::PermissionsClient::Get()->GetSettingsMap(context_); + RemoveSessionSettings(content_settings, origin, ContentSettingsType::GEOLOCATION); + RemoveSessionSettings(content_settings, origin, ContentSettingsType::MEDIASTREAM_MIC); + RemoveSessionSettings(content_settings, origin, ContentSettingsType::MEDIASTREAM_CAMERA); + } } diff --git a/chrome/browser/permissions/one_time_permissions_tracker.cc b/chrome/browser/permissions/one_time_permissions_tracker.cc --- a/chrome/browser/permissions/one_time_permissions_tracker.cc +++ b/chrome/browser/permissions/one_time_permissions_tracker.cc @@ -19,8 +19,34 @@ #include "components/permissions/permission_context_base.h" #include "content/public/browser/visibility.h" #include "url/gurl.h" +#include "components/content_settings/core/browser/host_content_settings_map.h" +#include "components/content_settings/core/common/content_settings_utils.h" +#include "components/permissions/permissions_client.h" + +namespace { + // Remove all sessions content setting by origin and type + void RemoveSessionSettings(HostContentSettingsMap* content_settings, + const url::Origin& origin, + ContentSettingsType type) { + ContentSettingsForOneType session_settings = + content_settings->GetSettingsForOneType( + type, content_settings::SessionModel::UserSession); + + GURL url = origin.GetURL(); + for (ContentSettingPatternSource& entry : session_settings) { + if (content_settings::IsConstraintSessionExpiration(entry, + content_settings::LifetimeMode::UntilOriginClosed) && + entry.primary_pattern.Matches(url)) { + content_settings->SetWebsiteSettingCustomScope( + entry.primary_pattern, entry.secondary_pattern, + type, base::Value()); + } + } + } +} -OneTimePermissionsTracker::OneTimePermissionsTracker() = default; +OneTimePermissionsTracker::OneTimePermissionsTracker(content::BrowserContext* context) + : context_(context) {} OneTimePermissionsTracker::~OneTimePermissionsTracker() = default; OneTimePermissionsTracker::OriginTrackEntry::OriginTrackEntry() = default; @@ -108,6 +134,11 @@ void OneTimePermissionsTracker::WebContentsUnloadedOrigin( observer.OnLastPageFromOriginClosed(origin); } } + HostContentSettingsMap* content_settings = + permissions::PermissionsClient::Get()->GetSettingsMap(context_); + RemoveSessionSettings(content_settings, origin, ContentSettingsType::GEOLOCATION); + RemoveSessionSettings(content_settings, origin, ContentSettingsType::MEDIASTREAM_MIC); + RemoveSessionSettings(content_settings, origin, ContentSettingsType::MEDIASTREAM_CAMERA); } } diff --git a/chrome/browser/permissions/one_time_permissions_tracker.h b/chrome/browser/permissions/one_time_permissions_tracker.h --- a/chrome/browser/permissions/one_time_permissions_tracker.h +++ b/chrome/browser/permissions/one_time_permissions_tracker.h @@ -12,6 +12,7 @@ #include "base/timer/timer.h" #include "chrome/browser/permissions/one_time_permissions_tracker_observer.h" #include "components/content_settings/core/common/content_settings_pattern.h" +#include "chrome/browser/profiles/profile.h" #include "components/content_settings/core/common/content_settings_types.h" #include "components/keyed_service/core/keyed_service.h" #include "content/public/browser/visibility.h" @@ -24,7 +25,7 @@ class OneTimePermissionsTracker : public KeyedService { void (OneTimePermissionsTracker::*)(const url::Origin&); public: - OneTimePermissionsTracker(); + OneTimePermissionsTracker(content::BrowserContext* context); ~OneTimePermissionsTracker() override; OneTimePermissionsTracker(const OneTimePermissionsTracker&) = delete; @@ -136,7 +137,7 @@ class OneTimePermissionsTracker : public KeyedService { base::ObserverList observer_list_; std::map origin_tracker_; - + raw_ptr context_; base::WeakPtrFactory weak_factory_{this}; }; diff --git a/chrome/browser/permissions/one_time_permissions_tracker_factory.cc b/chrome/browser/permissions/one_time_permissions_tracker_factory.cc --- a/chrome/browser/permissions/one_time_permissions_tracker_factory.cc +++ b/chrome/browser/permissions/one_time_permissions_tracker_factory.cc @@ -41,5 +41,5 @@ bool OneTimePermissionsTrackerFactory::ServiceIsCreatedWithBrowserContext() std::unique_ptr OneTimePermissionsTrackerFactory::BuildServiceInstanceForBrowserContext( content::BrowserContext* context) const { - return std::make_unique(); + return std::make_unique(context); } diff --git a/chrome/browser/ui/views/permissions/embedded_permission_prompt_ask_view.cc b/chrome/browser/ui/views/permissions/embedded_permission_prompt_ask_view.cc --- a/chrome/browser/ui/views/permissions/embedded_permission_prompt_ask_view.cc +++ b/chrome/browser/ui/views/permissions/embedded_permission_prompt_ask_view.cc @@ -34,7 +34,7 @@ void EmbeddedPermissionPromptAskView::RunButtonCallback(int button_id) { if (delegate()) { if (button == ButtonType::kAllowThisTime) { - delegate()->AcceptThisTime(); + delegate()->AcceptThisTime(content_settings::LifetimeMode::OnlyThisTime); } else if (button == ButtonType::kAllow) { delegate()->Accept(); } diff --git a/chrome/browser/ui/views/permissions/permission_prompt_bubble_base_view.cc b/chrome/browser/ui/views/permissions/permission_prompt_bubble_base_view.cc --- a/chrome/browser/ui/views/permissions/permission_prompt_bubble_base_view.cc +++ b/chrome/browser/ui/views/permissions/permission_prompt_bubble_base_view.cc @@ -263,7 +263,7 @@ void PermissionPromptBubbleBaseView::RunButtonCallback(int button_id) { return; case PermissionDialogButton::kAcceptOnce: RecordDecision(permissions::PermissionAction::GRANTED_ONCE); - delegate_->AcceptThisTime(); + delegate_->AcceptThisTime(content_settings::LifetimeMode::OnlyThisTime); return; case PermissionDialogButton::kDeny: RecordDecision(permissions::PermissionAction::DENIED); diff --git a/components/autofill/content/browser/content_autofill_driver.cc b/components/autofill/content/browser/content_autofill_driver.cc --- a/components/autofill/content/browser/content_autofill_driver.cc +++ b/components/autofill/content/browser/content_autofill_driver.cc @@ -412,8 +412,8 @@ void ContentAutofillDriver::FormsSeen( const std::vector& removed_forms) { target->GetAutofillManager().OnFormsSeen( WithNewVersion(updated_forms), removed_forms); - if (target->secondary_autofill_manager_) { - target->secondary_autofill_manager_->OnFormsSeen( + if (target->secondary_autofill_manager()) { + target->secondary_autofill_manager()->OnFormsSeen( WithNewVersion(updated_forms), removed_forms); } }); @@ -443,8 +443,8 @@ void ContentAutofillDriver::FormSubmitted( } target->GetAutofillManager().OnFormSubmitted( WithNewVersion(form), known_success, submission_source); - if (target->secondary_autofill_manager_) { - target->secondary_autofill_manager_->OnFormSubmitted( + if (target->secondary_autofill_manager()) { + target->secondary_autofill_manager()->OnFormSubmitted( WithNewVersion(form), known_success, submission_source); } }); @@ -468,8 +468,8 @@ void ContentAutofillDriver::TextFieldDidChange(const FormData& raw_form, base::TimeTicks timestamp) { target->GetAutofillManager().OnTextFieldDidChange( WithNewVersion(form), field, bounding_box, timestamp); - if (target->secondary_autofill_manager_) { - target->secondary_autofill_manager_->OnTextFieldDidChange( + if (target->secondary_autofill_manager()) { + target->secondary_autofill_manager()->OnTextFieldDidChange( WithNewVersion(form), field, bounding_box, timestamp); } }); @@ -491,8 +491,8 @@ void ContentAutofillDriver::TextFieldDidScroll(const FormData& raw_form, const FormFieldData& field, const gfx::RectF& bounding_box) { target->GetAutofillManager().OnTextFieldDidScroll(WithNewVersion(form), field, bounding_box); - if (target->secondary_autofill_manager_) { - target->secondary_autofill_manager_->OnTextFieldDidScroll( + if (target->secondary_autofill_manager()) { + target->secondary_autofill_manager()->OnTextFieldDidScroll( WithNewVersion(form), field, bounding_box); } }); @@ -515,8 +515,8 @@ void ContentAutofillDriver::SelectControlDidChange( const FormFieldData& field, const gfx::RectF& bounding_box) { target->GetAutofillManager().OnSelectControlDidChange( WithNewVersion(form), field, bounding_box); - if (target->secondary_autofill_manager_) { - target->secondary_autofill_manager_->OnSelectControlDidChange( + if (target->secondary_autofill_manager()) { + target->secondary_autofill_manager()->OnSelectControlDidChange( WithNewVersion(form), field, bounding_box); } }); @@ -541,8 +541,8 @@ void ContentAutofillDriver::AskForValuesToFill( AutofillSuggestionTriggerSource trigger_source) { target->GetAutofillManager().OnAskForValuesToFill( WithNewVersion(form), field, bounding_box, trigger_source); - if (target->secondary_autofill_manager_) { - target->secondary_autofill_manager_->OnAskForValuesToFill( + if (target->secondary_autofill_manager()) { + target->secondary_autofill_manager()->OnAskForValuesToFill( WithNewVersion(form), field, bounding_box, trigger_source); } }); @@ -556,8 +556,8 @@ void ContentAutofillDriver::HidePopup() { DCHECK(!target->IsPrerendering()) << "We should never affect UI while prerendering"; target->GetAutofillManager().OnHidePopup(); - if (target->secondary_autofill_manager_) - target->secondary_autofill_manager_->OnHidePopup(); + if (target->secondary_autofill_manager()) + target->secondary_autofill_manager()->OnHidePopup(); }); } @@ -569,8 +569,8 @@ void ContentAutofillDriver::FocusNoLongerOnForm(bool had_interacted_form) { this, had_interacted_form, [](autofill::AutofillDriver* target, bool had_interacted_form) { target->GetAutofillManager().OnFocusNoLongerOnForm(had_interacted_form); - if (target->secondary_autofill_manager_) - target->secondary_autofill_manager_->OnFocusNoLongerOnForm(had_interacted_form); + if (target->secondary_autofill_manager()) + target->secondary_autofill_manager()->OnFocusNoLongerOnForm(had_interacted_form); }); } @@ -590,15 +590,15 @@ void ContentAutofillDriver::FocusOnFormField(const FormData& raw_form, const FormFieldData& field, const gfx::RectF& bounding_box) { target->GetAutofillManager().OnFocusOnFormField(WithNewVersion(form), field, bounding_box); - if (target->secondary_autofill_manager_) { - target->secondary_autofill_manager_->OnFocusOnFormField( + if (target->secondary_autofill_manager()) { + target->secondary_autofill_manager()->OnFocusOnFormField( WithNewVersion(form), field, bounding_box); } }, [](autofill::AutofillDriver* target) { target->GetAutofillManager().OnFocusNoLongerOnForm(true); - if (target->secondary_autofill_manager_) { - target->secondary_autofill_manager_->OnFocusNoLongerOnForm(true); + if (target->secondary_autofill_manager()) { + target->secondary_autofill_manager()->OnFocusNoLongerOnForm(true); } }); } @@ -614,8 +614,8 @@ void ContentAutofillDriver::DidFillAutofillFormData(const FormData& raw_form, base::TimeTicks timestamp) { target->GetAutofillManager().OnDidFillAutofillFormData( WithNewVersion(form), timestamp); - if (target->secondary_autofill_manager_) { - target->secondary_autofill_manager_->OnDidFillAutofillFormData( + if (target->secondary_autofill_manager()) { + target->secondary_autofill_manager()->OnDidFillAutofillFormData( WithNewVersion(form), timestamp); } }); @@ -627,8 +627,8 @@ void ContentAutofillDriver::DidEndTextFieldEditing() { } router().DidEndTextFieldEditing(this, [](autofill::AutofillDriver* target) { target->GetAutofillManager().OnDidEndTextFieldEditing(); - if (target->secondary_autofill_manager_) - target->secondary_autofill_manager_->OnDidEndTextFieldEditing(); + if (target->secondary_autofill_manager()) + target->secondary_autofill_manager()->OnDidEndTextFieldEditing(); }); } @@ -643,8 +643,8 @@ void ContentAutofillDriver::SelectOrSelectListFieldOptionsDidChange( cast(target) ->GetAutofillManager() .OnSelectOrSelectListFieldOptionsDidChange(WithNewVersion(form)); - if (target->secondary_autofill_manager_) { - target->secondary_autofill_manager_->OnSelectOrSelectMenuFieldOptionsDidChange( + if (target->secondary_autofill_manager()) { + target->secondary_autofill_manager()->OnSelectOrSelectListFieldOptionsDidChange( WithNewVersion(form)); } }); @@ -666,8 +666,8 @@ void ContentAutofillDriver::JavaScriptChangedAutofilledValue( const FormFieldData& field, const std::u16string& old_value) { target->GetAutofillManager().OnJavaScriptChangedAutofilledValue( WithNewVersion(form), field, old_value); - if (target->secondary_autofill_manager_) { - target->secondary_autofill_manager_->OnJavaScriptChangedAutofilledValue( + if (target->secondary_autofill_manager()) { + target->secondary_autofill_manager()->OnJavaScriptChangedAutofilledValue( WithNewVersion(form), field, old_value); } }); @@ -682,8 +682,8 @@ void ContentAutofillDriver::OnContextMenuShownInField( const FieldGlobalId& field_global_id) { target->GetAutofillManager().OnContextMenuShownInField(form_global_id, field_global_id); - if (target->secondary_autofill_manager_) - target->secondary_autofill_manager_->OnContextMenuShownInField(form_global_id, field_global_id); + if (target->secondary_autofill_manager()) + target->secondary_autofill_manager()->OnContextMenuShownInField(form_global_id, field_global_id); }); } @@ -708,6 +708,10 @@ ContentAutofillDriver::GetAutofillAgent() { return autofill_agent_; } +raw_ptr ContentAutofillDriver::secondary_autofill_manager() { + return secondary_autofill_manager_.get(); +} + void ContentAutofillDriver::SetFrameAndFormMetaData( FormData& form, FormFieldData* optional_field) const { diff --git a/components/autofill/content/browser/content_autofill_driver.h b/components/autofill/content/browser/content_autofill_driver.h --- a/components/autofill/content/browser/content_autofill_driver.h +++ b/components/autofill/content/browser/content_autofill_driver.h @@ -132,7 +132,7 @@ class ContentAutofillDriver : public AutofillDriver, autofill_manager_ = std::move(autofill_manager); secondary_autofill_manager_ = std::move(secondary_autofill_manager); } - AutofillManager* secondary_autofill_manager() { return secondary_autofill_manager_.get(); } + raw_ptr secondary_autofill_manager() override; content::RenderFrameHost* render_frame_host() { return &*render_frame_host_; } const content::RenderFrameHost* render_frame_host() const { diff --git a/components/autofill/core/browser/autofill_driver.h b/components/autofill/core/browser/autofill_driver.h --- a/components/autofill/core/browser/autofill_driver.h +++ b/components/autofill/core/browser/autofill_driver.h @@ -69,6 +69,8 @@ class AutofillDriver { // Returns the AutofillManager owned by the AutofillDriver. virtual AutofillManager& GetAutofillManager() = 0; + virtual raw_ptr secondary_autofill_manager() = 0; + // Returns whether the AutofillDriver instance is associated with an active // frame in the MPArch sense. virtual bool IsInActiveFrame() const = 0; 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,9 +21,15 @@ 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, 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 : "invalid type: " @@ -31,6 +38,11 @@ public class PermissionInfo implements Serializable { 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 @@ -38,6 +38,7 @@ import org.chromium.components.browser_ui.settings.TextMessagePreference; import org.chromium.components.browsing_data.DeleteBrowsingDataAction; 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; @@ -558,6 +559,11 @@ public class SingleWebsiteSettings extends BaseSiteSettingsFragment } } + 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(); @@ -1024,6 +1030,10 @@ public class SingleWebsiteSettings extends BaseSiteSettingsFragment if (contentType == mHighlightedPermission) { switchPreference.setBackgroundColor(mHighlightColor); } + if (isSessionPermission(contentType)) { + switchPreference.setSummary(switchPreference.getSummary() + " " + + getString(R.string.page_info_android_permission_session_permission)); + } } /** diff --git a/components/browser_ui/site_settings/android/java/src/org/chromium/components/browser_ui/site_settings/WebsitePreferenceBridge.java b/components/browser_ui/site_settings/android/java/src/org/chromium/components/browser_ui/site_settings/WebsitePreferenceBridge.java --- a/components/browser_ui/site_settings/android/java/src/org/chromium/components/browser_ui/site_settings/WebsitePreferenceBridge.java +++ b/components/browser_ui/site_settings/android/java/src/org/chromium/components/browser_ui/site_settings/WebsitePreferenceBridge.java @@ -9,6 +9,7 @@ import org.chromium.base.annotations.CalledByNative; import org.chromium.base.annotations.NativeMethods; import org.chromium.components.content_settings.ContentSettingValues; import org.chromium.components.content_settings.ContentSettingsType; +import org.chromium.components.content_settings.SessionModel; import org.chromium.components.location.LocationUtils; import org.chromium.content_public.browser.BrowserContextHandle; import org.chromium.url.GURL; @@ -55,7 +56,8 @@ public class WebsitePreferenceBridge { @CalledByNative private static void insertPermissionInfoIntoList(@ContentSettingsType int type, - ArrayList list, String origin, String embedder, boolean isEmbargoed) { + ArrayList list, String origin, String embedder, boolean isEmbargoed, + @SessionModel int sessionModel) { if (type == ContentSettingsType.MEDIASTREAM_CAMERA || type == ContentSettingsType.MEDIASTREAM_MIC) { for (PermissionInfo info : list) { @@ -64,7 +66,7 @@ public class WebsitePreferenceBridge { } } } - list.add(new PermissionInfo(type, origin, embedder, isEmbargoed)); + list.add(new PermissionInfo(type, origin, embedder, isEmbargoed, sessionModel)); } @CalledByNative diff --git a/components/browser_ui/site_settings/android/website_preference_bridge.cc b/components/browser_ui/site_settings/android/website_preference_bridge.cc --- a/components/browser_ui/site_settings/android/website_preference_bridge.cc +++ b/components/browser_ui/site_settings/android/website_preference_bridge.cc @@ -131,7 +131,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, @@ -173,7 +174,7 @@ void GetOrigins(JNIEnv* env, seen_origins.push_back(origin); insertionFunc(env, static_cast(content_type), list, ConvertOriginToJavaString(env, origin), jembedder, - /*is_embargoed=*/false); + /*is_embargoed=*/false, static_cast(settings_it.metadata.session_model())); } // Add any origins which have a default content setting value (thus skipped @@ -195,7 +196,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 @@ -642,6 +642,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 @@ -60,6 +60,7 @@ java_cpp_enum("content_settings_enums_javagen") { "../core/common/cookie_controls_breakage_confidence_level.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 @@ -174,6 +174,42 @@ bool IsConstraintPersistent(const ContentSettingConstraints& constraints) { return constraints.session_model() == SessionModel::Durable; } +ContentSettingConstraints GetConstraintSessionExpiration(LifetimeMode lifetime_mode) { + int lifetime; + base::Time now; + if (lifetime_mode == LifetimeMode::OnlyThisTime) { + // note: this content settings will be discarded immediately + // 1h is used as a magic constant to identify the one-time lifetime mode + lifetime = 1; + } else if (lifetime_mode == LifetimeMode::UntilOriginClosed) { + now = base::Time::Now(); + lifetime = 24; + } else { + lifetime = 0; + } + content_settings::ContentSettingConstraints c(now); + c.set_lifetime(base::Hours(lifetime)); + c.set_session_model(content_settings::SessionModel::UserSession); + return c; +} + +bool IsConstraintSessionExpiration(const ContentSettingPatternSource& source, + LifetimeMode lifetime_mode) { + if (source.metadata.session_model() != content_settings::SessionModel::UserSession) + return false; + + LifetimeMode type; + if (source.metadata.lifetime() == base::Hours(24)) { + type = LifetimeMode::UntilOriginClosed; + } else if (source.metadata.expiration() == (base::Time() + base::Hours(1))) { + type = LifetimeMode::OnlyThisTime; + } else { + type = LifetimeMode::UntilBrowserClosed; + } + + return lifetime_mode == type; +} + bool CanTrackLastVisit(ContentSettingsType type) { // Last visit is not tracked for notification permission as it shouldn't be // auto-revoked. diff --git a/components/content_settings/core/browser/content_settings_utils.h b/components/content_settings/core/browser/content_settings_utils.h --- a/components/content_settings/core/browser/content_settings_utils.h +++ b/components/content_settings/core/browser/content_settings_utils.h @@ -73,6 +73,12 @@ bool IsConstraintPersistent(const ContentSettingConstraints& constraints); // Returns whether the given type supports tracking last_visit timestamps. bool CanTrackLastVisit(ContentSettingsType type); +ContentSettingConstraints GetConstraintSessionExpiration(LifetimeMode lifetime_mode); + +bool IsConstraintSessionExpiration( + const ContentSettingPatternSource& source, + LifetimeMode lifetime_mode); + // Get a timestamp with week-precision. base::Time GetCoarseVisitedTime(base::Time time); diff --git a/components/content_settings/core/common/content_settings_constraints.h b/components/content_settings/core/common/content_settings_constraints.h --- a/components/content_settings/core/common/content_settings_constraints.h +++ b/components/content_settings/core/common/content_settings_constraints.h @@ -23,6 +23,8 @@ namespace content_settings { // a crash or update related restart. // OneTime: Settings will persist for the current "tab session", meaning // until the last tab from the origin is closed. +// GENERATED_JAVA_ENUM_PACKAGE: org.chromium.components.content_settings +// GENERATED_JAVA_CLASS_NAME_OVERRIDE: SessionModel enum class SessionModel { Durable = 0, UserSession = 1, @@ -31,6 +33,15 @@ enum class SessionModel { kMaxValue = OneTime, }; +// GENERATED_JAVA_ENUM_PACKAGE: org.chromium.components.content_settings +// GENERATED_JAVA_CLASS_NAME_OVERRIDE: LifetimeMode +enum class LifetimeMode { + Always = 99, + OnlyThisTime = 1, + UntilOriginClosed = 2, + UntilBrowserClosed = 0, +}; + // Constraints to be applied when setting a content setting. class ContentSettingConstraints { public: diff --git a/components/page_info/android/java/src/org/chromium/components/page_info/PageInfoController.java b/components/page_info/android/java/src/org/chromium/components/page_info/PageInfoController.java --- a/components/page_info/android/java/src/org/chromium/components/page_info/PageInfoController.java +++ b/components/page_info/android/java/src/org/chromium/components/page_info/PageInfoController.java @@ -336,9 +336,9 @@ public class PageInfoController implements PageInfoMainController, ModalDialogPr */ @CalledByNative private void addPermissionSection(String name, String nameMidSentence, int type, - @ContentSettingValues int currentSettingValue) { + @ContentSettingValues int currentSettingValue, boolean is_user_session) { mPermissionParamsListBuilder.addPermissionEntry( - name, nameMidSentence, type, currentSettingValue); + name, nameMidSentence, type, currentSettingValue, is_user_session); } /** diff --git a/components/page_info/android/java/src/org/chromium/components/page_info/PermissionParamsListBuilder.java b/components/page_info/android/java/src/org/chromium/components/page_info/PermissionParamsListBuilder.java --- a/components/page_info/android/java/src/org/chromium/components/page_info/PermissionParamsListBuilder.java +++ b/components/page_info/android/java/src/org/chromium/components/page_info/PermissionParamsListBuilder.java @@ -44,8 +44,9 @@ public class PermissionParamsListBuilder { } public void addPermissionEntry( - String name, String nameMidSentence, int type, @ContentSettingValues int value) { - mEntries.add(new PageInfoPermissionEntry(name, nameMidSentence, type, value)); + String name, String nameMidSentence, int type, @ContentSettingValues int value, + boolean is_user_session) { + mEntries.add(new PageInfoPermissionEntry(name, nameMidSentence, type, value, is_user_session)); } public void clearPermissionEntries() { @@ -86,6 +87,10 @@ public class PermissionParamsListBuilder { permissionParams.warningTextResource = R.string.page_info_android_permission_blocked; } + if (permission.is_user_session) { + permissionParams.warningTextResource = + R.string.page_info_android_permission_session_permission; + } } } @@ -123,13 +128,15 @@ public class PermissionParamsListBuilder { public final String nameMidSentence; public final int type; public final @ContentSettingValues int setting; + public final boolean is_user_session; PageInfoPermissionEntry( - String name, String nameMidSentence, int type, @ContentSettingValues int setting) { + String name, String nameMidSentence, int type, @ContentSettingValues int setting, boolean is_user_session) { this.name = name; this.nameMidSentence = nameMidSentence; this.type = type; this.setting = setting; + this.is_user_session = is_user_session; } @Override diff --git a/components/page_info/android/page_info_controller_android.cc b/components/page_info/android/page_info_controller_android.cc --- a/components/page_info/android/page_info_controller_android.cc +++ b/components/page_info/android/page_info_controller_android.cc @@ -154,6 +154,8 @@ void PageInfoControllerAndroid::SetPermissionInfo( std::map user_specified_settings_to_display; + std::map + user_specified_settings_is_user_session; for (const auto& permission : permission_info_list) { if (base::Contains(permissions_to_display, permission.type)) { @@ -162,6 +164,8 @@ void PageInfoControllerAndroid::SetPermissionInfo( if (setting_to_display) { user_specified_settings_to_display[permission.type] = *setting_to_display; + user_specified_settings_is_user_session[permission.type] = + permission.is_user_session; } } } @@ -178,7 +182,8 @@ void PageInfoControllerAndroid::SetPermissionInfo( ConvertUTF16ToJavaString(env, setting_title), ConvertUTF16ToJavaString(env, setting_title_mid_sentence), static_cast(permission), - static_cast(user_specified_settings_to_display[permission])); + static_cast(user_specified_settings_to_display[permission]), + user_specified_settings_is_user_session[permission]); } } @@ -191,7 +196,8 @@ void PageInfoControllerAndroid::SetPermissionInfo( env, controller_jobject_, ConvertUTF16ToJavaString(env, object_title), ConvertUTF16ToJavaString(env, object_title), static_cast(chosen_object->ui_info->content_settings_type), - static_cast(CONTENT_SETTING_ALLOW)); + static_cast(CONTENT_SETTING_ALLOW), + /* is_user_session */ false); } Java_PageInfoController_updatePermissionDisplay(env, controller_jobject_); diff --git a/components/page_info/page_info.cc b/components/page_info/page_info.cc --- a/components/page_info/page_info.cc +++ b/components/page_info/page_info.cc @@ -1184,6 +1184,8 @@ void PageInfo::PopulatePermissionInfo(PermissionInfo& permission_info, permission_info.source = info.source; permission_info.is_one_time = (info.metadata.session_model() == content_settings::SessionModel::OneTime); + permission_info.is_user_session = + (info.metadata.session_model() == content_settings::SessionModel::UserSession); auto* page_specific_content_settings = GetPageSpecificContentSettings(); if (page_specific_content_settings && setting == CONTENT_SETTING_ALLOW) { diff --git a/components/page_info/page_info.h b/components/page_info/page_info.h --- a/components/page_info/page_info.h +++ b/components/page_info/page_info.h @@ -199,6 +199,7 @@ class PageInfo : private content_settings::CookieControlsObserver, content_settings::SETTING_SOURCE_NONE; // Whether the permission is a one-time grant. bool is_one_time = false; + bool is_user_session = false; // Only set for settings that can have multiple permissions for different // embedded origins. absl::optional requesting_origin; diff --git a/components/permissions/android/java/src/org/chromium/components/permissions/PermissionDialogDelegate.java b/components/permissions/android/java/src/org/chromium/components/permissions/PermissionDialogDelegate.java --- a/components/permissions/android/java/src/org/chromium/components/permissions/PermissionDialogDelegate.java +++ b/components/permissions/android/java/src/org/chromium/components/permissions/PermissionDialogDelegate.java @@ -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, @@ -46,6 +47,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; } @@ -79,6 +83,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.UiUtils; import org.chromium.ui.modaldialog.ModalDialogProperties; import org.chromium.ui.modelutil.PropertyModel; +import java.util.Arrays; +import java.util.List; +import android.view.ViewGroup.LayoutParams; +import android.widget.LinearLayout; +import android.widget.RadioButton; +import android.widget.RadioGroup; +import org.chromium.base.ApiCompatibilityUtils; +import org.chromium.ui.base.ViewUtils; +import org.chromium.components.content_settings.ContentSettingsType; +import org.chromium.components.content_settings.LifetimeMode; + /** * This class creates the model for permission dialog. */ @@ -41,7 +52,7 @@ class PermissionDialogModel { secondaryTextView.setVisibility(View.VISIBLE); } - return new PropertyModel.Builder(ModalDialogProperties.ALL_KEYS) + PropertyModel pm = new PropertyModel.Builder(ModalDialogProperties.ALL_KEYS) .with(ModalDialogProperties.CONTROLLER, controller) .with(ModalDialogProperties.FOCUS_DIALOG, true) .with(ModalDialogProperties.CUSTOM_VIEW, customView) @@ -53,6 +64,74 @@ class PermissionDialogModel { .with(ModalDialogProperties.BUTTON_TAP_PROTECTION_PERIOD_MS, UiUtils.PROMPT_INPUT_PROTECTION_SHORT_DELAY_MS) .build(); + + int[] types = delegate.getContentSettingsTypes(); + if (contains(types, ContentSettingsType.GEOLOCATION) || + contains(types, ContentSettingsType.MEDIASTREAM_MIC) || + contains(types, ContentSettingsType.MEDIASTREAM_CAMERA)) + { + LinearLayout layout = (LinearLayout) customView; + + // Create a text label before the lifetime selector. + TextView lifetimeOptionsText = new TextView(context); + lifetimeOptionsText.setText(context.getString( + org.chromium.components.permissions.R.string.session_permissions_title)); + ApiCompatibilityUtils.setTextAppearance( + lifetimeOptionsText, R.style.TextAppearance_TextMedium_Primary); + + LinearLayout.LayoutParams lifetimeOptionsTextLayoutParams = + new LinearLayout.LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT); + lifetimeOptionsTextLayoutParams.setMargins(0, 0, 0, ViewUtils.dpToPx(context, 8)); + lifetimeOptionsText.setLayoutParams(lifetimeOptionsTextLayoutParams); + layout.addView(lifetimeOptionsText); + + // Create radio buttons with lifetime options. + RadioGroup radioGroup = new RadioGroup(context); + + RadioButton radioButon = new RadioButton(context); + radioButon.setText(context.getString( + org.chromium.components.permissions.R.string.session_permissions_only_this_this)); + radioButon.setId(LifetimeMode.ONLY_THIS_TIME); + radioGroup.addView(radioButon); + + radioButon = new RadioButton(context); + radioButon.setText(context.getString( + org.chromium.components.permissions.R.string.session_permissions_until_page_close)); + radioButon.setId(LifetimeMode.UNTIL_ORIGIN_CLOSED); + radioGroup.addView(radioButon); + + radioButon = new RadioButton(context); + radioButon.setText(context.getString( + org.chromium.components.permissions.R.string.session_permissions_until_browser_close)); + radioButon.setId(LifetimeMode.UNTIL_BROWSER_CLOSED); + radioGroup.addView(radioButon); + + radioButon = new RadioButton(context); + radioButon.setText(context.getString( + org.chromium.components.permissions.R.string.session_permissions_forever)); + radioButon.setId(LifetimeMode.ALWAYS); + radioGroup.addView(radioButon); + + radioGroup.setOnCheckedChangeListener(new RadioGroup.OnCheckedChangeListener() { + @Override + public void onCheckedChanged(RadioGroup group, int checkedId) { + delegate.setSelectedLifetimeOption(checkedId); + } + }); + radioGroup.check(1); + layout.addView(radioGroup); + } + + return pm; + } + + private static boolean contains(final int[] array, final int key) { + int length = array.length; + for(int i = 0; i < length; i++) { + if (array[i] == key) + return true; + } + return false; } private static View loadDialogView(Context context) { diff --git a/components/permissions/android/permission_prompt/permission_dialog_delegate.cc b/components/permissions/android/permission_prompt/permission_dialog_delegate.cc --- a/components/permissions/android/permission_prompt/permission_dialog_delegate.cc +++ b/components/permissions/android/permission_prompt/permission_dialog_delegate.cc @@ -68,6 +68,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, @@ -96,12 +101,26 @@ PermissionDialogDelegate* PermissionDialogDelegate::CreateForTesting( void PermissionDialogDelegate::Accept(JNIEnv* env, const JavaParamRef& obj) { DCHECK(permission_prompt_); + content_settings::LifetimeMode lifetimeOption = + static_cast( + java_delegate_->GetSelectedLifetimeOption()); + if (lifetimeOption != content_settings::LifetimeMode::Always) { + permission_prompt_->AcceptThisTime(lifetimeOption); + return; + } permission_prompt_->Accept(); } void PermissionDialogDelegate::Cancel(JNIEnv* env, const JavaParamRef& obj) { DCHECK(permission_prompt_); + content_settings::LifetimeMode lifetimeOption = + static_cast( + java_delegate_->GetSelectedLifetimeOption()); + if (lifetimeOption != content_settings::LifetimeMode::Always) { + permission_prompt_->DenyThisTime(lifetimeOption); + return; + } permission_prompt_->Deny(); } diff --git a/components/permissions/android/permission_prompt/permission_dialog_delegate.h b/components/permissions/android/permission_prompt/permission_dialog_delegate.h --- a/components/permissions/android/permission_prompt/permission_dialog_delegate.h +++ b/components/permissions/android/permission_prompt/permission_dialog_delegate.h @@ -35,6 +35,7 @@ class PermissionDialogJavaDelegate { PermissionDialogDelegate* owner); virtual void CreateDialog(); virtual void DismissDialog(); + virtual int GetSelectedLifetimeOption(); private: base::android::ScopedJavaGlobalRef j_delegate_; diff --git a/components/permissions/android/permission_prompt/permission_prompt_android.cc b/components/permissions/android/permission_prompt/permission_prompt_android.cc --- a/components/permissions/android/permission_prompt/permission_prompt_android.cc +++ b/components/permissions/android/permission_prompt/permission_prompt_android.cc @@ -47,6 +47,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 @@ -46,7 +46,9 @@ class PermissionPromptAndroid : public PermissionPrompt { void Closing(); void Accept(); + void AcceptThisTime(content_settings::LifetimeMode lifetimeOption); void Deny(); + void DenyThisTime(content_settings::LifetimeMode lifetimeOption); void SetManageClicked(); void SetLearnMoreClicked(); bool ShouldCurrentRequestUseQuietUI(); diff --git a/components/permissions/android/permissions_android_strings.grd b/components/permissions/android/permissions_android_strings.grd --- a/components/permissions/android/permissions_android_strings.grd +++ b/components/permissions/android/permissions_android_strings.grd @@ -264,6 +264,23 @@ Unknown or unsupported device (%1$sA1:B2:C3:D4:E5:F6) + + + Remeber my decision + + + Only this time + + + Until all pages of this origin are closed + + + Until Bromite is closed + + + Forever + + %1$sitem_name (%2$sitem id) diff --git a/components/permissions/contexts/geolocation_permission_context_android.cc b/components/permissions/contexts/geolocation_permission_context_android.cc --- a/components/permissions/contexts/geolocation_permission_context_android.cc +++ b/components/permissions/contexts/geolocation_permission_context_android.cc @@ -163,7 +163,20 @@ void GeolocationPermissionContextAndroid::NotifyPermissionSet( bool is_final_decision) { DCHECK(!is_one_time); DCHECK(is_final_decision); + NotifyPermissionSetWithLifetime(id, requesting_origin, embedding_origin, + std::move(callback), persist, content_setting, is_one_time, is_final_decision, + content_settings::LifetimeMode::Always); +} +void GeolocationPermissionContextAndroid::NotifyPermissionSetWithLifetime( + const PermissionRequestID& id, + const GURL& requesting_origin, + const GURL& embedding_origin, + BrowserPermissionCallback callback, + bool persist, + ContentSetting content_setting, + bool is_one_time, bool is_final_decision, + content_settings::LifetimeMode lifetime_option) { bool is_default_search = IsRequestingOriginDSE(requesting_origin); if (content_setting == CONTENT_SETTING_ALLOW && !location_settings_->IsSystemLocationSettingEnabled()) { @@ -176,7 +189,8 @@ void GeolocationPermissionContextAndroid::NotifyPermissionSet( if (IsInLocationSettingsBackOff(is_default_search)) { FinishNotifyPermissionSet(id, requesting_origin, embedding_origin, std::move(callback), false /* persist */, - CONTENT_SETTING_BLOCK); + CONTENT_SETTING_BLOCK, + is_one_time, lifetime_option); return; } @@ -194,7 +208,8 @@ void GeolocationPermissionContextAndroid::NotifyPermissionSet( !location_settings_dialog_callback_.is_null()) { FinishNotifyPermissionSet(id, requesting_origin, embedding_origin, std::move(callback), false /* persist */, - CONTENT_SETTING_BLOCK); + CONTENT_SETTING_BLOCK, + is_one_time, lifetime_option); return; } @@ -206,12 +221,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); } content::PermissionResult @@ -377,6 +393,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) { @@ -394,7 +411,8 @@ void GeolocationPermissionContextAndroid::OnLocationSettingsDialogShown( FinishNotifyPermissionSet( location_settings_dialog_request_id_, requesting_origin, embedding_origin, - std::move(location_settings_dialog_callback_), persist, content_setting); + std::move(location_settings_dialog_callback_), persist, content_setting, + is_one_time, lifetime_option); location_settings_dialog_request_id_ = PermissionRequestID(content::GlobalRenderFrameHostId(0, 0), @@ -407,10 +425,11 @@ void GeolocationPermissionContextAndroid::FinishNotifyPermissionSet( const GURL& embedding_origin, BrowserPermissionCallback callback, bool persist, - ContentSetting content_setting) { - GeolocationPermissionContext::NotifyPermissionSet( + ContentSetting content_setting, + bool is_one_time, content_settings::LifetimeMode lifetime_option) { + GeolocationPermissionContext::NotifyPermissionSetWithLifetime( id, requesting_origin, embedding_origin, std::move(callback), persist, - content_setting, /*is_one_time=*/false, /*is_final_decision=*/true); + content_setting, is_one_time, /*is_final_decision=*/true, lifetime_option); } void GeolocationPermissionContextAndroid::SetLocationSettingsForTesting( diff --git a/components/permissions/contexts/geolocation_permission_context_android.h b/components/permissions/contexts/geolocation_permission_context_android.h --- a/components/permissions/contexts/geolocation_permission_context_android.h +++ b/components/permissions/contexts/geolocation_permission_context_android.h @@ -89,6 +89,15 @@ class GeolocationPermissionContextAndroid ContentSetting content_setting, bool is_one_time, bool is_final_decision) override; + void NotifyPermissionSetWithLifetime(const PermissionRequestID& id, + const GURL& requesting_origin, + const GURL& embedding_origin, + BrowserPermissionCallback callback, + bool persist, + ContentSetting content_setting, + bool is_one_time, + bool is_final_decision, + content_settings::LifetimeMode lifetime_option) override; content::PermissionResult UpdatePermissionStatusWithDeviceStatus( content::PermissionResult result, const GURL& requesting_origin, @@ -131,6 +140,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, @@ -138,7 +148,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 @@ -262,6 +262,16 @@ PermissionContextBase::CreatePermissionRequest( std::move(delete_callback)); } +std::unique_ptr PermissionContextBase::CreatePermissionRequest( + content::WebContents* web_contents, + PermissionRequestData request_data, + PermissionRequest::PermissionDecidedCallbackWithLifetime permission_decided_callback, + base::OnceClosure delete_callback) const { + return std::make_unique( + std::move(request_data), std::move(permission_decided_callback), + std::move(delete_callback)); +} + content::PermissionResult PermissionContextBase::GetPermissionStatus( content::RenderFrameHost* render_frame_host, const GURL& requesting_origin, @@ -506,7 +516,8 @@ void PermissionContextBase::PermissionDecided(const PermissionRequestID& id, const GURL& embedding_origin, ContentSetting content_setting, bool is_one_time, - bool is_final_decision) { + bool is_final_decision, + content_settings::LifetimeMode lifetime_option) { DCHECK(content_setting == CONTENT_SETTING_ALLOW || content_setting == CONTENT_SETTING_BLOCK || content_setting == CONTENT_SETTING_DEFAULT); @@ -521,13 +532,14 @@ void PermissionContextBase::PermissionDecided(const PermissionRequestID& id, // missing if a permission prompt was preignored and we already notified an // origin about it. if (request->second.second) { - NotifyPermissionSet(id, requesting_origin, embedding_origin, + NotifyPermissionSetWithLifetime(id, requesting_origin, embedding_origin, std::move(request->second.second), persist, - content_setting, is_one_time, is_final_decision); + content_setting, is_one_time, is_final_decision, + lifetime_option); } else { - NotifyPermissionSet(id, requesting_origin, embedding_origin, + NotifyPermissionSetWithLifetime(id, requesting_origin, embedding_origin, base::DoNothing(), persist, content_setting, - is_one_time, is_final_decision); + is_one_time, is_final_decision, lifetime_option); } } @@ -573,11 +585,27 @@ void PermissionContextBase::NotifyPermissionSet( ContentSetting content_setting, bool is_one_time, bool is_final_decision) { + DCHECK(is_one_time == false); + NotifyPermissionSetWithLifetime(id, requesting_origin, embedding_origin, std::move(callback), + persist, content_setting, is_one_time, is_final_decision, + content_settings::LifetimeMode::Always); +} + +void PermissionContextBase::NotifyPermissionSetWithLifetime( + const PermissionRequestID& id, + const GURL& requesting_origin, + const GURL& embedding_origin, + BrowserPermissionCallback callback, + bool persist, + ContentSetting content_setting, + bool is_one_time, + bool is_final_decision, + content_settings::LifetimeMode lifetime_option) { DCHECK_CURRENTLY_ON(content::BrowserThread::UI); if (persist) { UpdateContentSetting(requesting_origin, embedding_origin, content_setting, - is_one_time); + is_one_time, lifetime_option); } if (is_final_decision) { @@ -607,6 +635,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 || @@ -616,6 +653,8 @@ void PermissionContextBase::UpdateContentSetting(const GURL& requesting_origin, constraints.set_session_model(is_one_time ? content_settings::SessionModel::OneTime : content_settings::SessionModel::Durable); + if (is_one_time) + constraints = content_settings::GetConstraintSessionExpiration(lifetime_option); #if !BUILDFLAG(IS_ANDROID) 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 @@ -149,6 +149,15 @@ class PermissionContextBase : public content_settings::Observer { // Updates stored content setting if persist is set, updates tab indicators // and runs the callback to finish the request. + virtual void NotifyPermissionSetWithLifetime(const PermissionRequestID& id, + const GURL& requesting_origin, + const GURL& embedding_origin, + BrowserPermissionCallback callback, + bool persist, + ContentSetting content_setting, + bool is_one_time, + bool is_final_decision, + content_settings::LifetimeMode lifetime_option); virtual void NotifyPermissionSet(const PermissionRequestID& id, const GURL& requesting_origin, const GURL& embedding_origin, @@ -170,6 +179,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, @@ -200,6 +214,12 @@ class PermissionContextBase : public content_settings::Observer { PermissionRequest::PermissionDecidedCallback permission_decided_callback, base::OnceClosure delete_callback) const; + virtual std::unique_ptr CreatePermissionRequest( + content::WebContents* web_contents, + PermissionRequestData request_data, + PermissionRequest::PermissionDecidedCallbackWithLifetime permission_decided_callback, + base::OnceClosure delete_callback) const; + base::ObserverList permission_observers_; // Set by subclasses to inform the base class that they will handle adding @@ -222,7 +242,8 @@ class PermissionContextBase : public content_settings::Observer { const GURL& embedding_origin, ContentSetting content_setting, bool is_one_time, - bool is_final_decision); + bool is_final_decision, + content_settings::LifetimeMode lifetime_option); void NotifyObservers(const ContentSettingsPattern& primary_pattern, const ContentSettingsPattern& secondary_pattern, diff --git a/components/permissions/permission_prompt.h b/components/permissions/permission_prompt.h --- a/components/permissions/permission_prompt.h +++ b/components/permissions/permission_prompt.h @@ -65,8 +65,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 @@ -37,6 +37,14 @@ PermissionRequest::PermissionRequest( permission_decided_callback_(std::move(permission_decided_callback)), delete_callback_(std::move(delete_callback)) {} +PermissionRequest::PermissionRequest( + PermissionRequestData request_data, + PermissionDecidedCallbackWithLifetime permission_decided_callback, + base::OnceClosure delete_callback) + : data_(std::move(request_data)), + permission_decided_callback_withlifetime_(std::move(permission_decided_callback)), + delete_callback_(std::move(delete_callback)) {} + PermissionRequest::~PermissionRequest() { DCHECK(delete_callback_.is_null()); } @@ -276,19 +284,37 @@ bool PermissionRequest::ShouldUseTwoOriginPrompt() const { permissions::features::kPermissionStorageAccessAPI); } -void PermissionRequest::PermissionGranted(bool is_one_time) { +void PermissionRequest::PermissionGranted(bool is_one_time, + content_settings::LifetimeMode lifetime_option) { + if (permission_decided_callback_withlifetime_) { + std::move(permission_decided_callback_withlifetime_) + .Run(CONTENT_SETTING_ALLOW, is_one_time, /*is_final_decision=*/true, lifetime_option); + return; + } std::move(permission_decided_callback_) .Run(CONTENT_SETTING_ALLOW, is_one_time, /*is_final_decision=*/true); } -void PermissionRequest::PermissionDenied() { +void PermissionRequest::PermissionDenied(bool is_one_time, + content_settings::LifetimeMode lifetime_option) { + if (permission_decided_callback_withlifetime_) { + std::move(permission_decided_callback_withlifetime_) + .Run(CONTENT_SETTING_BLOCK, is_one_time, /*is_final_decision=*/true, lifetime_option); + return; + } std::move(permission_decided_callback_) .Run(CONTENT_SETTING_BLOCK, /*is_one_time=*/false, /*is_final_decision=*/true); } void PermissionRequest::Cancelled(bool is_final_decision) { + if (permission_decided_callback_withlifetime_) { + std::move(permission_decided_callback_withlifetime_) + .Run(CONTENT_SETTING_DEFAULT, false, is_final_decision, + content_settings::LifetimeMode::Always); + return; + } permission_decided_callback_.Run(CONTENT_SETTING_DEFAULT, /*is_one_time=*/false, is_final_decision); } diff --git a/components/permissions/permission_request.h b/components/permissions/permission_request.h --- a/components/permissions/permission_request.h +++ b/components/permissions/permission_request.h @@ -44,6 +44,11 @@ class PermissionRequest { bool /*is_one_time*/, bool /*is_final_decision*/)>; + using PermissionDecidedCallbackWithLifetime = + base::OnceCallback; + // `permission_decided_callback` is called when the permission request is // resolved by the user (see comment on PermissionDecidedCallback above). // `delete_callback` is called when the permission request is no longer needed @@ -63,6 +68,10 @@ class PermissionRequest { PermissionDecidedCallback permission_decided_callback, base::OnceClosure delete_callback); + PermissionRequest(PermissionRequestData request_data, + PermissionDecidedCallbackWithLifetime permission_decided_callback, + base::OnceClosure delete_callback); + PermissionRequest(const PermissionRequest&) = delete; PermissionRequest& operator=(const PermissionRequest&) = delete; @@ -128,10 +137,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 @@ -174,6 +183,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 @@ -155,7 +155,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; } @@ -231,7 +231,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; @@ -568,7 +568,8 @@ void PermissionRequestManager::Accept() { (*requests_iter)->request_type(), PermissionAction::GRANTED); PermissionGrantedIncludingDuplicates(*requests_iter, - /*is_one_time=*/false); + /*is_one_time=*/false, + content_settings::LifetimeMode::Always); #if !BUILDFLAG(IS_ANDROID) absl::optional content_settings_type = @@ -586,7 +587,7 @@ void PermissionRequestManager::Accept() { FinalizeCurrentRequests(PermissionAction::GRANTED); } -void PermissionRequestManager::AcceptThisTime() { +void PermissionRequestManager::AcceptThisTime(content_settings::LifetimeMode mode) { if (ignore_callbacks_from_prompt_) return; DCHECK(view_); @@ -597,7 +598,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); @@ -605,6 +607,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_); @@ -627,7 +638,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); @@ -1165,32 +1176,32 @@ PermissionRequestManager::VisitDuplicateRequests( void PermissionRequestManager::PermissionGrantedIncludingDuplicates( PermissionRequest* request, - bool is_one_time) { + bool is_one_time, content_settings::LifetimeMode lifetime_option) { DCHECK_EQ(1ul, base::ranges::count(requests_, request) + pending_permission_requests_.Count(request)) << "Only requests in [pending_permission_]requests_ can have duplicates"; - request->PermissionGranted(is_one_time); + request->PermissionGranted(is_one_time, lifetime_option); VisitDuplicateRequests( base::BindRepeating( - [](bool is_one_time, + [](bool is_one_time, content_settings::LifetimeMode lifetime_option, const base::WeakPtr& weak_request) { - weak_request->PermissionGranted(is_one_time); + weak_request->PermissionGranted(is_one_time, lifetime_option); }, - is_one_time), + is_one_time, lifetime_option), request); } void PermissionRequestManager::PermissionDeniedIncludingDuplicates( - PermissionRequest* request) { + PermissionRequest* request, bool is_one_time, content_settings::LifetimeMode lifetime_option) { DCHECK_EQ(1ul, base::ranges::count(requests_, request) + pending_permission_requests_.Count(request)) << "Only requests in [pending_permission_]requests_ can have duplicates"; - request->PermissionDenied(); + request->PermissionDenied(is_one_time, lifetime_option); VisitDuplicateRequests( base::BindRepeating( - [](const base::WeakPtr& weak_request) { - weak_request->PermissionDenied(); - }), + [](bool is_one_time, content_settings::LifetimeMode lifetime_option, const base::WeakPtr& weak_request) { + weak_request->PermissionDenied(is_one_time, lifetime_option); + }, is_one_time, lifetime_option), request); } @@ -1439,7 +1450,7 @@ void PermissionRequestManager::LogWarningToConsole(const char* message) { void PermissionRequestManager::DoAutoResponseForTesting() { switch (auto_response_for_test_) { case ACCEPT_ONCE: - AcceptThisTime(); + AcceptThisTime(content_settings::LifetimeMode::OnlyThisTime); break; case ACCEPT_ALL: Accept(); diff --git a/components/permissions/permission_request_manager.h b/components/permissions/permission_request_manager.h --- a/components/permissions/permission_request_manager.h +++ b/components/permissions/permission_request_manager.h @@ -159,8 +159,10 @@ class PermissionRequestManager GURL GetRequestingOrigin() const override; GURL GetEmbeddingOrigin() const override; void Accept() override; - void AcceptThisTime() override; + void AcceptThisTime(content_settings::LifetimeMode lifetime_option) override; void Deny() override; + void Deny_(bool is_one_time, content_settings::LifetimeMode lifetime_option); + void DenyThisTime(content_settings::LifetimeMode lifetime_option) override; void Dismiss() override; void Ignore() override; void OpenHelpCenterLink(const ui::Event& event) override; @@ -362,9 +364,12 @@ class PermissionRequestManager // Calls PermissionGranted on a request and all its duplicates. void PermissionGrantedIncludingDuplicates(PermissionRequest* request, - bool is_one_time); + bool is_one_time, + content_settings::LifetimeMode lifetime_option); // Calls PermissionDenied on a request and all its duplicates. - void PermissionDeniedIncludingDuplicates(PermissionRequest* request); + void PermissionDeniedIncludingDuplicates(PermissionRequest* request, + bool is_one_time, + content_settings::LifetimeMode lifetime_option); // Calls Cancelled on a request and all its duplicates. void CancelledIncludingDuplicates(PermissionRequest* request, bool is_final_decision = true); -- 2.25.1