blob: 3a10d90ab344fa42cb028dfde27071ff2a7870f7 [file] [log] [blame]
// Copyright 2014 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef COMPONENTS_PERMISSIONS_PERMISSION_REQUEST_MANAGER_H_
#define COMPONENTS_PERMISSIONS_PERMISSION_REQUEST_MANAGER_H_
#include <algorithm>
#include <memory>
#include <optional>
#include <unordered_map>
#include <utility>
#include <vector>
#include "base/callback_list.h"
#include "base/check_is_test.h"
#include "base/gtest_prod_util.h"
#include "base/memory/raw_ptr.h"
#include "base/memory/weak_ptr.h"
#include "base/observer_list.h"
#include "base/time/time.h"
#include "base/timer/timer.h"
#include "components/content_settings/browser/page_specific_content_settings.h"
#include "components/content_settings/core/common/content_settings.h"
#include "components/permissions/features.h"
#include "components/permissions/permission_prompt.h"
#include "components/permissions/permission_request_queue.h"
#include "components/permissions/permission_uma_util.h"
#include "components/permissions/prediction_service/permission_ui_selector.h"
#include "components/permissions/request_type.h"
#include "components/tabs/public/tab_interface.h"
#include "content/public/browser/global_routing_id.h"
#include "content/public/browser/web_contents.h"
#include "content/public/browser/web_contents_observer.h"
#include "content/public/browser/web_contents_user_data.h"
#include "ui/gfx/geometry/rect.h"
class GURL;
namespace content {
class RenderFrameHost;
}
namespace test {
class PermissionRequestManagerTestApi;
class MockPermissionRequestManager;
} // namespace test
namespace permissions {
class PermissionRequest;
enum class PermissionAction;
enum class PermissionPromptDisposition;
enum class PermissionPromptDispositionReason;
// The message to be printed in the Developer Tools console when the quiet
// notification permission prompt UI is shown on sites with abusive permission
// request flows.
extern const char kAbusiveNotificationRequestsEnforcementMessage[];
// The message to be printed in the Developer Tools console when the site is on
// the warning list for abusive permission request flows.
extern const char kAbusiveNotificationRequestsWarningMessage[];
// The message to be printed in the Developer Tools console when the site is on
// the blocking list for showing abusive notification content.
extern const char kAbusiveNotificationContentEnforcementMessage[];
// The message to be printed in the Developer Tools console when the site is on
// the warning list for showing abusive notification content.
extern const char kAbusiveNotificationContentWarningMessage[];
// Provides access to permissions bubbles. Allows clients to add a request
// callback interface to the existing permission bubble configuration.
// Depending on the situation and policy, that may add new UI to an existing
// permission bubble, create and show a new permission bubble, or provide no
// visible UI action at all. (In that case, the request will be immediately
// informed that the permission request failed.)
//
// A PermissionRequestManager is associated with a particular WebContents.
// Requests attached to a particular WebContents' PBM must outlive it.
//
// The PermissionRequestManager should be addressed on the UI thread.
class PermissionRequestManager
: public content::WebContentsObserver,
public content::WebContentsUserData<PermissionRequestManager>,
public PermissionPrompt::Delegate {
public:
class Observer : public base::CheckedObserver {
public:
virtual void OnTabActiveChanged(bool is_active) {}
virtual void OnPromptAdded() {}
virtual void OnPromptRemoved() {}
// Called when recreation of the permission prompt is not possible. It means
// that `PermissionRequestManager` is ready to display a prompt but the UI
// layer was not able to display it.
virtual void OnPromptRecreateViewFailed() {}
// Called when permission prompt creation was aborted because the current
// tab is no longer visible, hance it is not possible to display a prompt.
virtual void OnPromptCreationFailedHiddenTab() {}
// Called when the current batch of requests have been handled and the
// prompt is no longer visible. Note that there might be some queued
// permission requests that will get shown after this. This differs from
// OnPromptRemoved() in that the prompt may disappear while there are
// still in-flight requests (e.g. when switching tabs while the prompt is
// still visible).
virtual void OnRequestsFinalized() {}
virtual void OnPermissionRequestManagerDestructed() {}
virtual void OnNavigation(content::NavigationHandle* navigation_handle) {}
virtual void OnRequestDecided(permissions::PermissionAction action) {}
};
enum AutoResponseType { NONE, ACCEPT_ONCE, ACCEPT_ALL, DENY_ALL, DISMISS };
using UiDecision = PermissionUiSelector::Decision;
using QuietUiReason = PermissionUiSelector::QuietUiReason;
using WarningReason = PermissionUiSelector::WarningReason;
~PermissionRequestManager() override;
// Takes ownership of a new request and adds it to the permission bubble. The
// PermissionRequestManager will destroy the request after handling it.
// If a bubble is visible when this call is made, the request will be queued
// up and shown after the current bubble closes. A request with message text
// identical to an outstanding request will be merged with the outstanding
// request, and will have the same callbacks called as the outstanding
// request.
void AddRequest(content::RenderFrameHost* source_frame,
std::unique_ptr<PermissionRequest> request);
// Will reposition the bubble (may change parent if necessary).
void UpdateAnchor();
// For observing the status of the permission bubble manager.
void AddObserver(Observer* observer);
void RemoveObserver(Observer* observer);
bool IsRequestInProgress() const;
// Returns `true` if a permission request is in progress but a prompt view is
// nullptr.
bool CanRestorePrompt();
// Recreates a permission prompt.
void RestorePrompt();
// Do NOT use this methods in production code. Use this methods in browser
// tests that need to accept or deny permissions when requested in
// JavaScript. Your test needs to set this appropriately, and then the bubble
// will proceed as desired as soon as Show() is called.
void set_auto_response_for_test(AutoResponseType response) {
auto_response_for_test_ = response;
}
// WebContentsObserver:
void DidStartNavigation(
content::NavigationHandle* navigation_handle) override;
void DidFinishNavigation(
content::NavigationHandle* navigation_handle) override;
void DocumentOnLoadCompletedInPrimaryMainFrame() override;
void DOMContentLoaded(content::RenderFrameHost* render_frame_host) override;
void WebContentsDestroyed() override;
void OnVisibilityChanged(content::Visibility visibility) override;
// PermissionPrompt::Delegate:
const std::vector<std::unique_ptr<PermissionRequest>>& Requests() override;
GURL GetRequestingOrigin() const override;
GURL GetEmbeddingOrigin() const override;
void Accept() override;
void AcceptThisTime() override;
void Deny() override;
void Dismiss() override;
void Ignore() override;
void FinalizeCurrentRequests() override;
void OpenHelpCenterLink(const ui::Event& event) override;
void PreIgnoreQuietPrompt() override;
bool WasCurrentRequestAlreadyDisplayed() override;
bool ShouldDropCurrentRequestIfCannotShowQuietly() const override;
bool ShouldCurrentRequestUseQuietUI() const override;
std::optional<PermissionUiSelector::QuietUiReason> ReasonForUsingQuietUi()
const override;
void SetDismissOnTabClose() override;
void SetPromptShown() override;
void SetDecisionTime() override;
void SetManageClicked() override;
void SetLearnMoreClicked() override;
base::WeakPtr<PermissionPrompt::Delegate> GetWeakPtr() override;
content::WebContents* GetAssociatedWebContents() override;
bool RecreateView() override;
const PermissionPrompt* GetCurrentPrompt() const override;
// Returns true if there is a request in progress that is initiated by an
// embedded permission element.
bool IsCurrentRequestEmbeddedPermissionElementInitiated() const;
// Returns the bounds of the active permission prompt view if we're
// displaying one.
std::optional<gfx::Rect> GetPromptBubbleViewBoundsInScreen() const;
void set_manage_clicked() { did_click_manage_ = true; }
void set_learn_more_clicked() { did_click_learn_more_ = true; }
void set_web_contents_supports_permission_requests(
bool web_contents_supports_permission_requests) {
web_contents_supports_permission_requests_ =
web_contents_supports_permission_requests;
}
// For testing only, used to override the default UI selectors and instead use
// a new one.
void set_permission_ui_selector_for_testing(
std::unique_ptr<PermissionUiSelector> selector) {
clear_permission_ui_selector_for_testing();
add_permission_ui_selector_for_testing(std::move(selector));
}
// For testing only, used to add a new selector without overriding the
// existing ones.
void add_permission_ui_selector_for_testing(
std::unique_ptr<PermissionUiSelector> selector) {
permission_ui_selectors_.emplace_back(std::move(selector));
}
// For testing only, clear the existing ui selectors.
void clear_permission_ui_selector_for_testing() {
permission_ui_selectors_.clear();
}
// Getter for testing.
const std::vector<std::unique_ptr<PermissionUiSelector>>&
get_permission_ui_selectors_for_testing() {
return permission_ui_selectors_;
}
void set_view_factory_for_testing(PermissionPrompt::Factory view_factory) {
view_factory_ = std::move(view_factory);
}
void set_current_request_first_display_time_for_testing(base::Time time) {
current_request_first_display_time_ = time;
}
std::optional<PermissionUiSelector::PredictionGrantLikelihood>
prediction_grant_likelihood_for_testing() const {
return prediction_grant_likelihood_;
}
std::optional<bool> was_decision_held_back_for_testing() const {
return was_decision_held_back_;
}
std::optional<PermissionRequestRelevance>
permission_request_relevance_for_testing() const {
return permission_request_relevance_;
}
std::optional<permissions::PermissionPromptDisposition>
current_request_prompt_disposition_for_testing() const {
return current_request_prompt_disposition_;
}
void set_time_to_decision_for_test(base::TimeDelta time_to_decision) {
time_to_decision_for_test_ = time_to_decision;
}
void set_enabled_app_level_notification_permission_for_testing(bool enabled) {
enabled_app_level_notification_permission_for_testing_ = enabled;
}
void set_embedding_origin_for_testing(const GURL& embedding_origin) {
embedding_origin_for_testing_ = embedding_origin;
}
base::ObserverList<Observer>* get_observer_list_for_testing() {
CHECK_IS_TEST();
return &observer_list_;
}
bool has_pending_requests() {
return !pending_permission_requests_.IsEmpty();
}
void SetHatsShownCallback(base::OnceCallback<void()> callback) override;
// For permissions that have visible views, we should only record
// PromptResolved metrics, for ask prompts.
bool ShouldRecordUmaForCurrentPrompt() const;
void SetPromptOptions(PromptOptions prompt_options) override;
private:
friend class test::PermissionRequestManagerTestApi;
friend class test::MockPermissionRequestManager;
friend class content::WebContentsUserData<PermissionRequestManager>;
FRIEND_TEST_ALL_PREFIXES(PermissionRequestManagerTest, WeakDuplicateRequests);
FRIEND_TEST_ALL_PREFIXES(PermissionRequestManagerTest,
WeakDuplicateRequestsAccept);
// TODO(crbug.com/443780638): Remove this once the TabInterface can be fetched
// from WebContents.
PermissionRequestManager(content::WebContents* web_contents,
tabs::TabInterface* tab_interface);
explicit PermissionRequestManager(content::WebContents* web_contents);
// Defines how to handle the current request, when new requests arrive
enum class CurrentRequestFate {
// Keep showing the current request. The incoming requests should not take
// priority over the current request and will be pushed to pending requests
// queue.
kKeepCurrent,
// Put the current request back to the pending requests queue for displaying
// later when it returns to the front of the queue.
kPreempt,
// Finalize/ignore the current request and show the new request.
kFinalize
};
struct lex_compare {
bool operator()(const base::WeakPtr<PermissionRequest>& lhs,
const base::WeakPtr<PermissionRequest>& rhs) const {
CHECK(lhs);
CHECK(rhs);
return lhs.get() < rhs.get();
}
};
// Reprioritize the current requests (preempting, finalizing) based on what
// type of UI has been shown for `requests_` and current pending requests
// queue.
// Determine the next request candidate would be prompted later and push the
// candidate to front of the pending queue.
// Return true if we keep showing the current request, otherwise return false
bool ReprioritizeCurrentRequestIfNeeded();
// Returns true if the request's source frame can be found and is active.
// This means, its render frame host is committed and lives inside a page
// presented to the user, i.e. its not a prerendered or back-forward cached
// page. Side effect: In the case of the RenderFrameHost is inactive, this
// ensures it will be never activated through the following:
//
// - For BackForwardCache: it evicts the document from the cache and
// triggers deletion.
// - For Prerendering: it cancels prerendering and triggers deletion.
//
// For more information see
// RenderFrameHost::IsInactiveAndDisallowActivation()
bool HasActiveSourceFrameOrDisallowActivationOtherwise(
PermissionRequest* request) const;
// Cancels a request and removes it from |request_sources_map_| and
// |validated_requests_|.
void FinalizeAndCancelRequest(PermissionRequest* request);
// Adds `request` into `pending_permission_requests_`, and request's
// `source_frame` into `request_sources_map_`.
void QueueRequest(content::RenderFrameHost* source_frame,
std::unique_ptr<PermissionRequest> request);
// Because the requests are shown in a different order for Normal and Quiet
// Chip, pending requests are returned back to pending_permission_requests_ to
// process them after the new requests.
void PreemptAndRequeueCurrentRequest();
// If a request isn't already in progress, dequeue and show the request
// prompt.
void DequeueRequestIfNeeded();
// Schedule a call to |DequeueRequestIfNeeded|. Is needed to ensure requests
// that can be grouped together have time to all be added to the queue.
void ScheduleDequeueRequestIfNeeded();
// Shows the prompt for a request that has just been dequeued, or re-show a
// prompt after switching tabs away and back.
void ShowPrompt();
// Delete the view object
void DeletePrompt();
// Finalize request.
void ResetViewStateForCurrentRequest();
// Records metrics and informs embargo and autoblocker about the requests
// being decided. Based on |view_->ShouldFinalizeRequestAfterDecided()| it
// will also call |FinalizeCurrentRequests()|. Otherwise a separate
// |FinalizeCurrentRequests()| call must be made to release the |view_|.
void CurrentRequestsDecided(PermissionAction permission_action);
// Cancel all pending or active requests and destroy the PermissionPrompt if
// one exists. This is called if the WebContents is destroyed or navigates its
// main frame.
void CleanUpRequests();
// Searches |requests_| and |pending_permission_requests_| - but *not*
// |duplicate_requests_| - for a request matching |request|, and returns the
// matching request, or |nullptr| if no match. Note that the matching request
// may or may not be the same object as |request|.
PermissionRequest* GetExistingRequest(PermissionRequest* request) const;
// Returns an iterator into |duplicate_requests_|, points the matching list,
// or duplicate_requests_.end() if no match. The matching list contains all
// the weak requests which are duplicate of the given |request| (see
// |IsDuplicateOf|)
using PermissionRequestList =
std::list<std::list<std::unique_ptr<PermissionRequest>>>;
PermissionRequestList::iterator FindDuplicateRequestList(
PermissionRequest* request);
// Trigger |visitor| for each live weak request which matches the given
// request (see |IsDuplicateOf|) in the |duplicate_requests_|. Returns an
// iterator into |duplicate_requests_|, points the matching list, or
// duplicate_requests_.end() if no match.
using DuplicateRequestVisitor =
base::RepeatingCallback<void(const std::unique_ptr<PermissionRequest>&)>;
PermissionRequestList::iterator VisitDuplicateRequests(
DuplicateRequestVisitor visitor,
PermissionRequest* request);
// Calls PermissionGranted on a request and all its duplicates.
void PermissionGrantedIncludingDuplicates(PermissionRequest* request,
bool is_one_time);
// Calls PermissionDenied on a request and all its duplicates.
void PermissionDeniedIncludingDuplicates(PermissionRequest* request);
// Calls Cancelled on a request and all its duplicates.
void CancelRequestIncludingDuplicates(PermissionRequest* request,
bool is_final_decision = true);
// Erases a request and all its duplicates.
void FinishRequestIncludingDuplicates(PermissionRequest* request);
void NotifyTabActiveChanged(bool is_active);
void NotifyPromptAdded();
void NotifyPromptRemoved();
void NotifyPromptRecreateFailed();
void NotifyPromptCreationFailedHiddenTab();
void NotifyRequestDecided(permissions::PermissionAction permission_action);
void StorePermissionActionForUMA(const GURL& origin,
RequestType request_type,
PermissionAction permission_action);
void OnPermissionUiSelectorDone(size_t selector_index,
const UiDecision& decision);
PermissionPromptDisposition DetermineCurrentRequestUIDisposition();
PermissionPromptDispositionReason
DetermineCurrentRequestUIDispositionReasonForUMA();
void LogWarningToConsole(const char* message);
void DoAutoResponseForTesting();
void PreIgnoreQuietPromptInternal();
// Returns true if there is a request in progress that is for exclusive
// access.
bool IsCurrentRequestExclusiveAccess() const;
// Returns true when the current request should be finalized together with the
// permission decision.
bool ShouldFinalizeRequestAfterDecided(PermissionAction action) const;
// Calculate and record the PermissionEmbargoStatus.
PermissionEmbargoStatus RecordActionAndGetEmbargoStatus(
content::BrowserContext* browser_context,
PermissionRequest* request,
PermissionAction permission_action);
// Take a snapshot of the content setting status for the current requests,
// which can change based on the user's decision. Used in HaTS as a filter.
// This defaults to "DEFAULT" if there's no ContentSettingsType associated
// with the PermissionType.
void SetCurrentRequestsInitialStatuses();
ContentSetting GetRequestInitialStatus(PermissionRequest* request);
void RegisterTabSubscriptions(tabs::TabInterface* tab_interface);
void OnTabActiveStatusChanged(bool is_active,
tabs::TabInterface* tab_interface);
void OnTabVisibleStatusChanged(bool is_visible,
tabs::TabInterface* tab_interface);
void OnTabDetached(tabs::TabInterface* tab_interface,
tabs::TabInterface::DetachReason reason);
void OnTabAttached(tabs::TabInterface* tab_interface);
void OnTabActiveChanged();
// Factory to be used to create views when needed.
PermissionPrompt::Factory view_factory_;
// The UI surface for an active permission prompt if we're displaying one.
// On Desktop, we destroy this upon tab switching, while on Android we keep
// the object alive. The infobar system hides the actual infobar UI and modals
// prevent tab switching.
std::unique_ptr<PermissionPrompt> view_;
// The disposition for the currently active permission prompt, if any.
// Recorded separately because the `view_` might not be available at prompt
// resolution in order to determine the disposition.
std::optional<permissions::PermissionPromptDisposition>
current_request_prompt_disposition_;
// We only show new prompts when |tab_is_active_| is true.
bool tab_is_active_;
// Holds subscriptions for TabInterface active and visible callbacks.
std::vector<base::CallbackListSubscription> tab_subscriptions_;
// Holds subscriptions for TabInterface attach and detach callbacks.
base::CallbackListSubscription tab_insert_subscription_;
// The request (or requests) that the user is currently being prompted for.
// When this is non-empty, the |view_| is generally non-null as long as the
// tab is visible.
std::vector<std::unique_ptr<PermissionRequest>> requests_;
struct PermissionRequestSource {
content::GlobalRenderFrameHostId requesting_frame_id;
bool IsSourceFrameInactiveAndDisallowActivation() const;
};
PermissionRequestQueue pending_permission_requests_;
// Stores the unique pointers of duplicated requests in a 2D list.
PermissionRequestList duplicate_requests_;
// Maps each PermissionRequest currently in |requests_| or
// |pending_permission_requests_| to which RenderFrameHost it originated from.
// Note that no date is stored for |duplicate_requests_|.
std::map<PermissionRequest*, PermissionRequestSource> request_sources_map_;
// Sequence of requests from pending queue will be marked as validated, when
// we are extracting a group of requests from the queue to show to user. This
// is an immature solution to avoid an infinitive loop of preempting, we would
// not prempt a request if the incoming request is already validated.
std::vector<base::WeakPtr<PermissionRequest>> validated_requests_;
base::ObserverList<Observer> observer_list_;
AutoResponseType auto_response_for_test_ = NONE;
// Suppress notification permission prompts in this tab, regardless of the
// origin requesting the permission.
bool is_notification_prompt_cooldown_active_ = false;
// A vector of selectors which decide if the quiet prompt UI should be used
// to display permission requests. Sorted from the highest priority to the
// lowest priority selector.
std::vector<std::unique_ptr<PermissionUiSelector>> permission_ui_selectors_;
// Holds the decisions returned by selectors. Needed in case a lower priority
// selector returns a decision first and we need to wait for the decisions of
// higher priority selectors before making use of it.
std::vector<std::optional<PermissionUiSelector::Decision>>
selector_decisions_;
// Whether the view for the current |requests_| has been shown to the user at
// least once.
bool current_request_already_displayed_ = false;
// When the view for the current |requests_| has been first shown to the user,
// or zero if not at all.
base::Time current_request_first_display_time_;
// Whether to use the normal or quiet UI to display the current permission
// |requests_|, and whether to show warnings. This will be nullopt if we are
// still waiting on the result from |permission_ui_selectors_|.
std::optional<UiDecision> current_request_ui_to_use_;
// The likelihood value returned by the Web Permission Predictions Service,
// to be recorded in UKM.
std::optional<PermissionUiSelector::PredictionGrantLikelihood>
prediction_grant_likelihood_;
// The permission request relevance returned by an on-device ML model,
// to be recorded in UKM.
std::optional<PermissionRequestRelevance> permission_request_relevance_;
// Status of the decision made by the Web Permission Prediction Service, if
// it was held back or not.
std::optional<bool> was_decision_held_back_;
// True when the prompt is being temporary destroyed to be recreated for the
// correct browser or when the tab is hidden. In those cases, callbacks from
// the bubble itself should be ignored.
bool ignore_callbacks_from_prompt_ = false;
// Whether the web contents associated with this request manager supports
// permission prompts.
bool web_contents_supports_permission_requests_ = true;
// Whether the current request should be dismissed if the current tab is
// closed.
bool should_dismiss_current_request_ = false;
// Whether the permission prompt was shown for the current request.
bool did_show_prompt_ = false;
// When the user made any decision for the current |requests_|, or zero if not
// at all.
base::Time current_request_decision_time_;
bool did_click_manage_ = false;
bool did_click_learn_more_ = false;
// Whether the current request can be preempted or not. This is set when
// callbacks are being issued to prevent potential re-entrant behavior of
// those callbacks requesting a permission that would preempt the current one
// and thus invalidate the iterator being used to issue the callback.
bool can_preempt_current_request_ = true;
std::optional<base::TimeDelta> time_to_decision_for_test_;
std::optional<bool> enabled_app_level_notification_permission_for_testing_;
std::optional<GURL> embedding_origin_for_testing_;
// A timer is used to pre-ignore the permission request if it's been displayed
// as a quiet chip.
base::OneShotTimer preignore_timer_;
std::optional<base::OnceCallback<void()>> hats_shown_callback_;
// Holds the position of the current prompt, only relevant for permission
// element prompts.
std::optional<feature_params::PermissionElementPromptPosition>
current_request_pepc_prompt_position_;
// Holds the initial statuses of the current requests, one for each request in
// |requests_|.
std::map<PermissionRequest*, ContentSetting>
current_requests_initial_statuses_;
base::WeakPtrFactory<PermissionRequestManager> weak_factory_{this};
WEB_CONTENTS_USER_DATA_KEY_DECL();
};
} // namespace permissions
#endif // COMPONENTS_PERMISSIONS_PERMISSION_REQUEST_MANAGER_H_