blob: e4b69a05a045d9cffa8fb880e5ccfba42d9da1af [file] [log] [blame]
// Copyright 2014 The Chromium Authors. All rights reserved.
// 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 <unordered_map>
#include <utility>
#include <vector>
#include "base/containers/circular_deque.h"
#include "base/gtest_prod_util.h"
#include "base/memory/weak_ptr.h"
#include "base/observer_list.h"
#include "base/time/time.h"
#include "components/permissions/permission_prompt.h"
#include "components/permissions/permission_ui_selector.h"
#include "components/permissions/permission_uma_util.h"
#include "content/public/browser/web_contents_observer.h"
#include "content/public/browser/web_contents_user_data.h"
class GURL;
namespace content {
class RenderFrameHost;
}
namespace test {
class PermissionRequestManagerTestApi;
}
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:
virtual void OnBubbleAdded() {}
virtual void OnBubbleRemoved() {}
// Called when the current batch of requests have been handled and the
// bubble is no longer visible. Note that there might be some queued
// permission requests that will get shown after this. This differs from
// OnBubbleRemoved() in that the bubble may disappear while there are still
// in-flight requests (e.g. when switching tabs while the bubble is still
// visible).
virtual void OnRequestsFinalized() {}
protected:
virtual ~Observer() = default;
};
enum AutoResponseType { NONE, ACCEPT_ONCE, ACCEPT_ALL, DENY_ALL, DISMISS };
using UiDecision = PermissionUiSelector::Decision;
using QuietUiReason = PermissionUiSelector::QuietUiReason;
using WarningReason = PermissionUiSelector::WarningReason;
~PermissionRequestManager() override;
// Adds a new request to the permission bubble. Ownership of the request
// remains with the caller. The caller must arrange for the request to
// outlive the PermissionRequestManager. 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,
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);
// Notification permission requests might use a quiet UI when the
// "quiet-notification-prompts" feature is enabled. This is done either
// directly by the user in notifications settings, or via automatic logic that
// might trigger the current request to use the quiet UI.
bool ShouldCurrentRequestUseQuietUI() const;
// If |ShouldCurrentRequestUseQuietUI| return true, this will provide a reason
// as to why the quiet UI needs to be used. Returns `absl::nullopt` otherwise.
absl::optional<QuietUiReason> ReasonForUsingQuietUi() const;
bool IsRequestInProgress() const;
// If the LocationBar is not visible, there is no place to display a quiet
// permission prompt. Abusive prompts will be ignored.
bool ShouldDropCurrentRequestIfCannotShowQuietly();
// 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 DocumentOnLoadCompletedInMainFrame(
content::RenderFrameHost* render_frame_host) override;
void DOMContentLoaded(content::RenderFrameHost* render_frame_host) override;
void WebContentsDestroyed() override;
void OnVisibilityChanged(content::Visibility visibility) override;
// PermissionPrompt::Delegate:
const std::vector<PermissionRequest*>& Requests() override;
GURL GetRequestingOrigin() const override;
GURL GetEmbeddingOrigin() const override;
void Accept() override;
void AcceptThisTime() override;
void Deny() override;
void Closing() override;
bool WasCurrentRequestAlreadyDisplayed() override;
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();
}
void set_view_factory_for_testing(PermissionPrompt::Factory view_factory) {
view_factory_ = std::move(view_factory);
}
PermissionPrompt* view_for_testing() { return view_.get(); }
void set_current_request_first_display_time_for_testing(base::Time time) {
current_request_first_display_time_ = time;
}
absl::optional<PermissionUmaUtil::PredictionGrantLikelihood>
prediction_grant_likelihood_for_testing() {
return prediction_grant_likelihood_;
}
absl::optional<permissions::PermissionPromptDisposition>
current_request_prompt_disposition_for_testing() {
return current_request_prompt_disposition_;
}
private:
friend class test::PermissionRequestManagerTestApi;
friend class content::WebContentsUserData<PermissionRequestManager>;
explicit PermissionRequestManager(content::WebContents* web_contents);
enum class CurrentRequestFate { KeepCurrent, Preempt, Finalize };
// Returns `CurrentRequestFate` based on what type of UI has been shown for
// `requests_`.
CurrentRequestFate GetCurrentRequestFateInFaceOfNewRequest(
PermissionRequest* request);
// Adds `request` into `queued_requests_`, and request's `source_frame` into
// `request_sources_map_`.
void QueueRequest(content::RenderFrameHost* source_frame,
PermissionRequest* request);
// Because the requests are shown in a different order for Normal and Quiet
// Chip, pending requests are returned back to queued_requests_ to process
// them after the new requests.
void PreemptAndRequeueCurrentRequest();
// Posts a task which will allow the bubble to become visible.
void ScheduleShowBubble();
// If a request isn't already in progress, deque and schedule showing the
// request.
void DequeueRequestIfNeeded();
// Schedule a call to dequeue request. Is needed to ensure requests that can
// be grouped together have time to all be added to the queue.
void ScheduleDequeueRequestIfNeeded();
// Will determine if it's possible and necessary to dequeue a new request.
bool ShouldDequeueNewRequest();
// Shows the bubble for a request that has just been dequeued, or re-show a
// bubble after switching tabs away and back.
void ShowBubble();
// Delete the view object
void DeleteBubble();
// Finalize request.
void ResetViewStateForCurrentRequest();
// Delete the view object, finalize requests, asynchronously show a queued
// request if present.
void FinalizeCurrentRequests(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_|, |queued_requests_| and |queued_frame_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);
// 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 CancelledIncludingDuplicates(PermissionRequest* request);
// Calls RequestFinished on a request and all its duplicates.
void RequestFinishedIncludingDuplicates(PermissionRequest* request);
void NotifyBubbleAdded();
void NotifyBubbleRemoved();
void OnPermissionUiSelectorDone(size_t selector_index,
const UiDecision& decision);
PermissionPromptDisposition DetermineCurrentRequestUIDisposition();
PermissionPromptDispositionReason
DetermineCurrentRequestUIDispositionReasonForUMA();
void LogWarningToConsole(const char* message);
void DoAutoResponseForTesting();
// 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.
absl::optional<permissions::PermissionPromptDisposition>
current_request_prompt_disposition_;
// We only show new prompts when |tab_is_hidden_| is false.
bool tab_is_hidden_;
// 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<PermissionRequest*> requests_;
struct PermissionRequestSource {
int render_process_id;
int render_frame_id;
bool IsSourceFrameInactiveAndDisallowActivation() const;
};
PermissionRequest* PeekNextQueuedRequest();
PermissionRequest* PopNextQueuedRequest();
// Encapsulate enqueuing `request` into `queued_requests_`. Based on the chip
// / quiet chip experiments, the `request` is added into the back or front of
// the queue.
void PushQueuedRequest(PermissionRequest* request);
// TODO(crbug.com/1221150): Create a separate entity to handle Enqueue /
// Dequeue with all edge cases. Expose to `PermissionRequestManager` only a
// clear API like `Peek()` and `Pop()`, etc.
base::circular_deque<PermissionRequest*> queued_requests_;
// Maps from the first request of a kind to subsequent requests that were
// duped against it.
std::unordered_multimap<PermissionRequest*, PermissionRequest*>
duplicate_requests_;
// Maps each PermissionRequest currently in |requests_| or |queued_requests_|
// to which RenderFrameHost it originated from. Note that no date is stored
// for |duplicate_requests_|.
std::map<PermissionRequest*, PermissionRequestSource> request_sources_map_;
base::ObserverList<Observer>::Unchecked observer_list_;
AutoResponseType auto_response_for_test_;
// 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<absl::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_|.
absl::optional<UiDecision> current_request_ui_to_use_;
// The likelihood value returned by the Web Permission Predictions Service,
// to be recoreded in UKM.
absl::optional<PermissionUmaUtil::PredictionGrantLikelihood>
prediction_grant_likelihood_;
// 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;
base::WeakPtrFactory<PermissionRequestManager> weak_factory_{this};
WEB_CONTENTS_USER_DATA_KEY_DECL();
};
} // namespace permissions
#endif // COMPONENTS_PERMISSIONS_PERMISSION_REQUEST_MANAGER_H_