1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114 |
- // 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.
- #include "components/permissions/permission_request_manager.h"
- #include <algorithm>
- #include <string>
- #include "base/auto_reset.h"
- #include "base/bind.h"
- #include "base/command_line.h"
- #include "base/metrics/histogram_functions.h"
- #include "base/metrics/user_metrics.h"
- #include "base/metrics/user_metrics_action.h"
- #include "base/observer_list.h"
- #include "base/stl_util.h"
- #include "base/threading/sequenced_task_runner_handle.h"
- #include "components/autofill_assistant/browser/public/runtime_manager.h"
- #include "components/back_forward_cache/back_forward_cache_disable.h"
- #include "components/permissions/features.h"
- #include "components/permissions/permission_decision_auto_blocker.h"
- #include "components/permissions/permission_prompt.h"
- #include "components/permissions/permission_request.h"
- #include "components/permissions/permissions_client.h"
- #include "components/permissions/request_type.h"
- #include "components/permissions/switches.h"
- #include "content/public/browser/back_forward_cache.h"
- #include "content/public/browser/browser_task_traits.h"
- #include "content/public/browser/browser_thread.h"
- #include "content/public/browser/disallow_activation_reason.h"
- #include "content/public/browser/navigation_handle.h"
- #include "content/public/browser/render_frame_host.h"
- #include "content/public/browser/render_process_host.h"
- #include "content/public/browser/web_contents.h"
- #include "url/gurl.h"
- #include "url/origin.h"
- #if BUILDFLAG(IS_ANDROID)
- #include "components/permissions/android/android_permission_util.h"
- #endif
- namespace permissions {
- const char kAbusiveNotificationRequestsEnforcementMessage[] =
- "Chrome is blocking notification permission requests on this site because "
- "the site tends to show permission requests that mislead, trick, or force "
- "users into allowing notifications. You should fix the issues as soon as "
- "possible and submit your site for another review. Learn more at "
- "https://support.google.com/webtools/answer/9799048.";
- const char kAbusiveNotificationRequestsWarningMessage[] =
- "Chrome might start blocking notification permission requests on this site "
- "in the future because the site tends to show permission requests that "
- "mislead, trick, or force users into allowing notifications. You should "
- "fix the issues as soon as possible and submit your site for another "
- "review. Learn more at https://support.google.com/webtools/answer/9799048.";
- constexpr char kAbusiveNotificationContentEnforcementMessage[] =
- "Chrome is blocking notification permission requests on this site because "
- "the site tends to show notifications with content that mislead or trick "
- "users. You should fix the issues as soon as possible and submit your site "
- "for another review. Learn more at "
- "https://support.google.com/webtools/answer/9799048";
- constexpr char kAbusiveNotificationContentWarningMessage[] =
- "Chrome might start blocking notification permission requests on this site "
- "in the future because the site tends to show notifications with content "
- "that mislead or trick users. You should fix the issues as soon as "
- "possible and submit your site for another review. Learn more at "
- "https://support.google.com/webtools/answer/9799048";
- constexpr char kDisruptiveNotificationBehaviorEnforcementMessage[] =
- "Chrome is blocking notification permission requests on this site because "
- "the site exhibits behaviors that may be disruptive to users.";
- namespace {
- // When there are multiple permissions requests in
- // `pending_permission_requests_`, we try to prioritize them based on the
- // acceptance rates. Notifications and Geolocations have one of the lowest
- // acceptance, hence they have the lowest priority and will be shown the last.
- bool IsLowPriorityRequest(PermissionRequest* request) {
- return request->request_type() == RequestType::kNotifications ||
- request->request_type() == RequestType::kGeolocation;
- }
- // In case of multiple permission requests that use chip UI, a newly added
- // request will preempt the currently showing request, which is put back to the
- // queue, and will be shown later. To reduce user annoyance, if a quiet chip
- // permission prompt was displayed longer than `kQuietChipIgnoreTimeout`, we
- // consider it as shown long enough and it will not be shown again after it is
- // preempted.
- // TODO(crbug.com/1221083): If a user switched tabs, do not include that time as
- // "shown".
- bool ShouldShowQuietRequestAgainIfPreempted(
- absl::optional<base::Time> request_display_start_time) {
- if (request_display_start_time->is_null()) {
- return true;
- }
- static constexpr base::TimeDelta kQuietChipIgnoreTimeout = base::Seconds(8.5);
- return base::Time::Now() - request_display_start_time.value() <
- kQuietChipIgnoreTimeout;
- }
- bool IsMediaRequest(RequestType type) {
- #if !BUILDFLAG(IS_ANDROID)
- if (type == RequestType::kCameraPanTiltZoom)
- return true;
- #endif
- return type == RequestType::kMicStream || type == RequestType::kCameraStream;
- }
- bool ShouldGroupRequests(PermissionRequest* a, PermissionRequest* b) {
- if (a->requesting_origin() != b->requesting_origin())
- return false;
- // Group if both requests are media requests.
- if (IsMediaRequest(a->request_type()) && IsMediaRequest(b->request_type())) {
- return true;
- }
- return false;
- }
- } // namespace
- // PermissionRequestManager ----------------------------------------------------
- bool PermissionRequestManager::PermissionRequestSource::
- IsSourceFrameInactiveAndDisallowActivation() const {
- content::RenderFrameHost* rfh =
- content::RenderFrameHost::FromID(render_process_id, render_frame_id);
- return !rfh ||
- rfh->IsInactiveAndDisallowActivation(
- content::DisallowActivationReasonId::kPermissionRequestSource);
- }
- PermissionRequestManager::~PermissionRequestManager() {
- DCHECK(!IsRequestInProgress());
- DCHECK(duplicate_requests_.empty());
- DCHECK(pending_permission_requests_.IsEmpty());
- }
- void PermissionRequestManager::AddRequest(
- content::RenderFrameHost* source_frame,
- PermissionRequest* request) {
- DCHECK(source_frame);
- DCHECK_EQ(content::WebContents::FromRenderFrameHost(source_frame),
- web_contents());
- if (base::CommandLine::ForCurrentProcess()->HasSwitch(
- switches::kDenyPermissionPrompts)) {
- request->PermissionDenied();
- request->RequestFinished();
- return;
- }
- if (source_frame->IsInactiveAndDisallowActivation(
- content::DisallowActivationReasonId::kPermissionAddRequest)) {
- request->Cancelled();
- request->RequestFinished();
- return;
- }
- if (source_frame->IsNestedWithinFencedFrame()) {
- request->Cancelled();
- request->RequestFinished();
- return;
- }
- #if BUILDFLAG(IS_ANDROID)
- if (request->GetContentSettingsType() == ContentSettingsType::NOTIFICATIONS) {
- bool app_level_settings_allow_site_notifications =
- enabled_app_level_notification_permission_for_testing_.has_value()
- ? enabled_app_level_notification_permission_for_testing_.value()
- : DoesAppLevelSettingsAllowSiteNotifications();
- base::UmaHistogramBoolean(
- "Permissions.Prompt.Notifications.EnabledAppLevel",
- app_level_settings_allow_site_notifications);
- if (!app_level_settings_allow_site_notifications &&
- base::FeatureList::IsEnabled(
- features::kBlockNotificationPromptsIfDisabledOnAppLevel)) {
- // Automatically cancel site Notification requests when Chrome is not able
- // to send notifications in an app level.
- request->Cancelled();
- request->RequestFinished();
- return;
- }
- }
- #endif // BUILDFLAG(IS_ANDROID)
- if (is_notification_prompt_cooldown_active_ &&
- request->GetContentSettingsType() == ContentSettingsType::NOTIFICATIONS) {
- // Short-circuit by canceling rather than denying to avoid creating a large
- // number of content setting exceptions on Desktop / disabled notification
- // channels on Android.
- request->Cancelled();
- request->RequestFinished();
- return;
- }
- if (!web_contents_supports_permission_requests_) {
- request->Cancelled();
- request->RequestFinished();
- return;
- }
- // TODO(tsergeant): change the UMA to no longer mention bubbles.
- base::RecordAction(base::UserMetricsAction("PermissionBubbleRequest"));
- // TODO(gbillock): is there a race between an early request on a
- // newly-navigated page and the to-be-cleaned-up requests on the previous
- // page? We should maybe listen to DidStartNavigationToPendingEntry (and
- // any other renderer-side nav initiations?). Double-check this for
- // correct behavior on interstitials -- we probably want to basically queue
- // any request for which GetVisibleURL != GetLastCommittedURL.
- CHECK_EQ(source_frame->GetMainFrame(), web_contents()->GetPrimaryMainFrame());
- const GURL main_frame_origin =
- PermissionUtil::GetLastCommittedOriginAsURL(source_frame->GetMainFrame());
- bool is_main_frame =
- url::IsSameOriginWith(main_frame_origin, request->requesting_origin());
- absl::optional<url::Origin> auto_approval_origin =
- PermissionsClient::Get()->GetAutoApprovalOrigin();
- if (auto_approval_origin) {
- if (url::Origin::Create(request->requesting_origin()) ==
- auto_approval_origin.value()) {
- request->PermissionGranted(/*is_one_time=*/false);
- }
- request->RequestFinished();
- return;
- }
- // Cancel permission requests wile Autofill Assistant's UI is shown.
- auto* assistant_runtime_manager =
- autofill_assistant::RuntimeManager::GetForWebContents(web_contents());
- if (assistant_runtime_manager && assistant_runtime_manager->GetState() ==
- autofill_assistant::UIState::kShown) {
- request->Cancelled();
- request->RequestFinished();
- return;
- }
- // Don't re-add an existing request or one with a duplicate text request.
- PermissionRequest* existing_request = GetExistingRequest(request);
- if (existing_request) {
- // |request| is a duplicate. Add it to |duplicate_requests_| unless it's the
- // same object as |existing_request| or an existing duplicate.
- if (request == existing_request)
- return;
- auto range = duplicate_requests_.equal_range(existing_request);
- for (auto it = range.first; it != range.second; ++it) {
- if (request == it->second)
- return;
- }
- duplicate_requests_.insert(std::make_pair(existing_request, request));
- return;
- }
- if (is_main_frame) {
- if (IsRequestInProgress()) {
- base::RecordAction(
- base::UserMetricsAction("PermissionBubbleRequestQueued"));
- }
- } else {
- base::RecordAction(
- base::UserMetricsAction("PermissionBubbleIFrameRequestQueued"));
- }
- CurrentRequestFate current_request_fate =
- GetCurrentRequestFateInFaceOfNewRequest(request);
- if (current_request_fate == CurrentRequestFate::Preempt) {
- PreemptAndRequeueCurrentRequest();
- }
- QueueRequest(source_frame, request);
- if (current_request_fate == CurrentRequestFate::Finalize) {
- // FinalizeCurrentRequests will call ScheduleDequeueRequest on its own.
- FinalizeCurrentRequests(PermissionAction::IGNORED);
- } else {
- ScheduleDequeueRequestIfNeeded();
- }
- }
- PermissionRequestManager::CurrentRequestFate
- PermissionRequestManager::GetCurrentRequestFateInFaceOfNewRequest(
- PermissionRequest* new_request) {
- if (base::FeatureList::IsEnabled(features::kPermissionQuietChip)) {
- if (ShouldCurrentRequestUseQuietUI() &&
- !ShouldShowQuietRequestAgainIfPreempted(
- current_request_first_display_time_)) {
- return CurrentRequestFate::Finalize;
- }
- if (base::FeatureList::IsEnabled(features::kPermissionChip)) {
- // Preempt current request if it is a quiet UI request or it is not
- // Notifications or Geolocation.
- if (ShouldCurrentRequestUseQuietUI() ||
- !IsLowPriorityRequest(new_request)) {
- return CurrentRequestFate::Preempt;
- }
- } else {
- if (ShouldCurrentRequestUseQuietUI()) {
- return CurrentRequestFate::Preempt;
- }
- }
- } else {
- if (base::FeatureList::IsEnabled(features::kPermissionChip)) {
- return CurrentRequestFate::Preempt;
- } else if (ShouldCurrentRequestUseQuietUI()) {
- // If we're displaying a quiet permission request, ignore it in favor of a
- // new permission request.
- return CurrentRequestFate::Finalize;
- }
- }
- return CurrentRequestFate::KeepCurrent;
- }
- void PermissionRequestManager::QueueRequest(
- content::RenderFrameHost* source_frame,
- PermissionRequest* request) {
- pending_permission_requests_.Push(request);
- request_sources_map_.emplace(
- request, PermissionRequestSource({source_frame->GetProcess()->GetID(),
- source_frame->GetRoutingID()}));
- }
- void PermissionRequestManager::PreemptAndRequeueCurrentRequest() {
- ResetViewStateForCurrentRequest();
- for (auto* current_request : requests_) {
- pending_permission_requests_.Push(current_request);
- }
- requests_.clear();
- }
- void PermissionRequestManager::UpdateAnchor() {
- if (view_) {
- // When the prompt's anchor is being updated, the prompt view can be
- // recreated for the new browser. Because of that, ignore prompt callbacks
- // while doing that.
- base::AutoReset<bool> ignore(&ignore_callbacks_from_prompt_, true);
- view_->UpdateAnchor();
- }
- }
- void PermissionRequestManager::DidStartNavigation(
- content::NavigationHandle* navigation_handle) {
- if (!navigation_handle->IsInPrimaryMainFrame() ||
- navigation_handle->IsSameDocument()) {
- return;
- }
- // Cooldown lasts until the next user-initiated navigation, which is defined
- // as either a renderer-initiated navigation with a user gesture, or a
- // browser-initiated navigation.
- //
- // TODO(crbug.com/952347): This check has to be done at DidStartNavigation
- // time, the HasUserGesture state is lost by the time the navigation
- // commits.
- if (!navigation_handle->IsRendererInitiated() ||
- navigation_handle->HasUserGesture()) {
- is_notification_prompt_cooldown_active_ = false;
- }
- }
- void PermissionRequestManager::DidFinishNavigation(
- content::NavigationHandle* navigation_handle) {
- if (!navigation_handle->IsInPrimaryMainFrame() ||
- !navigation_handle->HasCommitted() ||
- navigation_handle->IsSameDocument()) {
- return;
- }
- if (!pending_permission_requests_.IsEmpty() || IsRequestInProgress()) {
- // |pending_permission_requests_| and |requests_| will be deleted below,
- // which might be a problem for back-forward cache — the page might be
- // restored later, but the requests won't be. Disable bfcache here if we
- // have any requests here to prevent this from happening.
- content::BackForwardCache::DisableForRenderFrameHost(
- navigation_handle->GetPreviousRenderFrameHostId(),
- back_forward_cache::DisabledReason(
- back_forward_cache::DisabledReasonId::kPermissionRequestManager));
- }
- CleanUpRequests();
- }
- void PermissionRequestManager::DocumentOnLoadCompletedInPrimaryMainFrame() {
- // This is scheduled because while all calls to the browser have been
- // issued at DOMContentLoaded, they may be bouncing around in scheduled
- // callbacks finding the UI thread still. This makes sure we allow those
- // scheduled calls to AddRequest to complete before we show the page-load
- // permissions bubble.
- ScheduleDequeueRequestIfNeeded();
- }
- void PermissionRequestManager::DOMContentLoaded(
- content::RenderFrameHost* render_frame_host) {
- ScheduleDequeueRequestIfNeeded();
- }
- void PermissionRequestManager::WebContentsDestroyed() {
- // If the web contents has been destroyed, treat the bubble as cancelled.
- CleanUpRequests();
- // The WebContents is going away; be aggressively paranoid and delete
- // ourselves lest other parts of the system attempt to add permission
- // bubbles or use us otherwise during the destruction.
- web_contents()->RemoveUserData(UserDataKey());
- // That was the equivalent of "delete this". This object is now destroyed;
- // returning from this function is the only safe thing to do.
- }
- void PermissionRequestManager::OnVisibilityChanged(
- content::Visibility visibility) {
- bool tab_was_hidden = tab_is_hidden_;
- tab_is_hidden_ = visibility == content::Visibility::HIDDEN;
- if (tab_was_hidden == tab_is_hidden_)
- return;
- if (tab_is_hidden_) {
- if (view_) {
- switch (view_->GetTabSwitchingBehavior()) {
- case PermissionPrompt::TabSwitchingBehavior::
- kDestroyPromptButKeepRequestPending:
- DeleteBubble();
- break;
- case PermissionPrompt::TabSwitchingBehavior::
- kDestroyPromptAndIgnoreRequest:
- FinalizeCurrentRequests(PermissionAction::IGNORED);
- break;
- case PermissionPrompt::TabSwitchingBehavior::kKeepPromptAlive:
- break;
- }
- }
- return;
- }
- if (!web_contents()->IsDocumentOnLoadCompletedInPrimaryMainFrame())
- return;
- if (!IsRequestInProgress()) {
- ScheduleDequeueRequestIfNeeded();
- return;
- }
- if (view_) {
- // We switched tabs away and back while a prompt was active.
- DCHECK_EQ(view_->GetTabSwitchingBehavior(),
- PermissionPrompt::TabSwitchingBehavior::kKeepPromptAlive);
- } else if (current_request_ui_to_use_.has_value()) {
- ShowBubble();
- }
- }
- const std::vector<PermissionRequest*>& PermissionRequestManager::Requests() {
- return requests_;
- }
- GURL PermissionRequestManager::GetRequestingOrigin() const {
- CHECK(!requests_.empty());
- GURL origin = requests_.front()->requesting_origin();
- if (DCHECK_IS_ON()) {
- for (auto* request : requests_)
- DCHECK_EQ(origin, request->requesting_origin());
- }
- return origin;
- }
- GURL PermissionRequestManager::GetEmbeddingOrigin() const {
- return PermissionUtil::GetLastCommittedOriginAsURL(
- web_contents()->GetPrimaryMainFrame());
- }
- void PermissionRequestManager::Accept() {
- if (ignore_callbacks_from_prompt_)
- return;
- DCHECK(view_);
- std::vector<PermissionRequest*>::iterator requests_iter;
- for (requests_iter = requests_.begin(); requests_iter != requests_.end();
- requests_iter++) {
- PermissionGrantedIncludingDuplicates(*requests_iter,
- /*is_one_time=*/false);
- }
- FinalizeCurrentRequests(PermissionAction::GRANTED);
- }
- void PermissionRequestManager::AcceptThisTime() {
- if (ignore_callbacks_from_prompt_)
- return;
- DCHECK(view_);
- std::vector<PermissionRequest*>::iterator requests_iter;
- for (requests_iter = requests_.begin(); requests_iter != requests_.end();
- requests_iter++) {
- PermissionGrantedIncludingDuplicates(*requests_iter,
- /*is_one_time=*/true);
- }
- FinalizeCurrentRequests(PermissionAction::GRANTED_ONCE);
- }
- void PermissionRequestManager::Deny() {
- if (ignore_callbacks_from_prompt_)
- return;
- DCHECK(view_);
- // Suppress any further prompts in this WebContents, from any origin, until
- // there is a user-initiated navigation. This stops users from getting
- // trapped in request loops where the website automatically navigates
- // cross-origin (e.g. to another subdomain) to be able to prompt again after
- // a rejection.
- if (base::FeatureList::IsEnabled(
- features::kBlockRepeatedNotificationPermissionPrompts) &&
- std::any_of(requests_.begin(), requests_.end(), [](const auto* request) {
- return request->GetContentSettingsType() ==
- ContentSettingsType::NOTIFICATIONS;
- })) {
- is_notification_prompt_cooldown_active_ = true;
- }
- std::vector<PermissionRequest*>::iterator requests_iter;
- for (requests_iter = requests_.begin(); requests_iter != requests_.end();
- requests_iter++) {
- PermissionDeniedIncludingDuplicates(*requests_iter);
- }
- FinalizeCurrentRequests(PermissionAction::DENIED);
- }
- void PermissionRequestManager::Dismiss() {
- if (ignore_callbacks_from_prompt_)
- return;
- DCHECK(view_);
- std::vector<PermissionRequest*>::iterator requests_iter;
- for (requests_iter = requests_.begin(); requests_iter != requests_.end();
- requests_iter++) {
- CancelledIncludingDuplicates(*requests_iter);
- }
- FinalizeCurrentRequests(PermissionAction::DISMISSED);
- }
- void PermissionRequestManager::Ignore() {
- if (ignore_callbacks_from_prompt_)
- return;
- DCHECK(view_);
- std::vector<PermissionRequest*>::iterator requests_iter;
- for (requests_iter = requests_.begin(); requests_iter != requests_.end();
- requests_iter++) {
- CancelledIncludingDuplicates(*requests_iter);
- }
- FinalizeCurrentRequests(PermissionAction::IGNORED);
- }
- bool PermissionRequestManager::WasCurrentRequestAlreadyDisplayed() {
- return current_request_already_displayed_;
- }
- void PermissionRequestManager::SetDismissOnTabClose() {
- should_dismiss_current_request_ = true;
- }
- void PermissionRequestManager::SetBubbleShown() {
- did_show_bubble_ = true;
- }
- void PermissionRequestManager::SetDecisionTime() {
- current_request_decision_time_ = base::Time::Now();
- }
- void PermissionRequestManager::SetManageClicked() {
- set_manage_clicked();
- }
- void PermissionRequestManager::SetLearnMoreClicked() {
- set_learn_more_clicked();
- }
- base::WeakPtr<PermissionPrompt::Delegate>
- PermissionRequestManager::GetWeakPtr() {
- return weak_factory_.GetWeakPtr();
- }
- bool PermissionRequestManager::RecreateView() {
- view_ = view_factory_.Run(web_contents(), this);
- if (!view_) {
- current_request_prompt_disposition_ =
- PermissionPromptDisposition::NONE_VISIBLE;
- FinalizeCurrentRequests(PermissionAction::IGNORED);
- return false;
- }
- current_request_prompt_disposition_ = view_->GetPromptDisposition();
- return true;
- }
- PermissionRequestManager::PermissionRequestManager(
- content::WebContents* web_contents)
- : content::WebContentsObserver(web_contents),
- content::WebContentsUserData<PermissionRequestManager>(*web_contents),
- view_factory_(base::BindRepeating(&PermissionPrompt::Create)),
- view_(nullptr),
- tab_is_hidden_(web_contents->GetVisibility() ==
- content::Visibility::HIDDEN),
- auto_response_for_test_(NONE),
- permission_ui_selectors_(
- PermissionsClient::Get()->CreatePermissionUiSelectors(
- web_contents->GetBrowserContext())) {}
- void PermissionRequestManager::ScheduleShowBubble() {
- base::RecordAction(base::UserMetricsAction("PermissionBubbleRequest"));
- content::GetUIThreadTaskRunner({})->PostTask(
- FROM_HERE, base::BindOnce(&PermissionRequestManager::ShowBubble,
- weak_factory_.GetWeakPtr()));
- }
- void PermissionRequestManager::DequeueRequestIfNeeded() {
- // TODO(olesiamarukhno): Media requests block other media requests from
- // pre-empting them. For example, when a camera request is pending and mic
- // is requested, the camera request remains pending and mic request appears
- // only after the camera request is resolved. This is caused by code in
- // PermissionBubbleMediaAccessHandler and UserMediaClient. We probably don't
- // need two permission queues, so resolve the duplication.
- if (!web_contents()->IsDocumentOnLoadCompletedInPrimaryMainFrame() || view_ ||
- IsRequestInProgress()) {
- return;
- }
- // Find first valid request.
- while (!pending_permission_requests_.IsEmpty()) {
- PermissionRequest* next = pending_permission_requests_.Pop();
- PermissionRequestSource& source = request_sources_map_.find(next)->second;
- if (!source.IsSourceFrameInactiveAndDisallowActivation()) {
- requests_.push_back(next);
- break;
- }
- next->Cancelled();
- next->RequestFinished();
- request_sources_map_.erase(request_sources_map_.find(next));
- }
- if (requests_.empty()) {
- return;
- }
- // Find additional requests that can be grouped with the first one.
- for (; !pending_permission_requests_.IsEmpty();
- pending_permission_requests_.Pop()) {
- PermissionRequest* front = pending_permission_requests_.Peek();
- PermissionRequestSource& source = request_sources_map_.find(front)->second;
- if (source.IsSourceFrameInactiveAndDisallowActivation()) {
- front->Cancelled();
- front->RequestFinished();
- request_sources_map_.erase(request_sources_map_.find(front));
- } else if (ShouldGroupRequests(requests_.front(), front)) {
- requests_.push_back(front);
- } else {
- break;
- }
- }
- if (!permission_ui_selectors_.empty()) {
- DCHECK(!current_request_ui_to_use_.has_value());
- // Initialize the selector decisions vector.
- DCHECK(selector_decisions_.empty());
- selector_decisions_.resize(permission_ui_selectors_.size());
- for (size_t selector_index = 0;
- selector_index < permission_ui_selectors_.size(); ++selector_index) {
- if (permission_ui_selectors_[selector_index]
- ->IsPermissionRequestSupported(
- requests_.front()->request_type())) {
- permission_ui_selectors_[selector_index]->SelectUiToUse(
- requests_.front(),
- base::BindOnce(
- &PermissionRequestManager::OnPermissionUiSelectorDone,
- weak_factory_.GetWeakPtr(), selector_index));
- } else {
- OnPermissionUiSelectorDone(
- selector_index,
- PermissionUiSelector::Decision::UseNormalUiAndShowNoWarning());
- }
- }
- } else {
- current_request_ui_to_use_ =
- UiDecision(UiDecision::UseNormalUi(), UiDecision::ShowNoWarning());
- ScheduleShowBubble();
- }
- }
- void PermissionRequestManager::ScheduleDequeueRequestIfNeeded() {
- base::SequencedTaskRunnerHandle::Get()->PostTask(
- FROM_HERE,
- base::BindOnce(&PermissionRequestManager::DequeueRequestIfNeeded,
- weak_factory_.GetWeakPtr()));
- }
- void PermissionRequestManager::ShowBubble() {
- // There is a race condition where the request might have been removed
- // already so double-checking that there is a request in progress.
- //
- // There is no need to show a new bubble if the previous one still exists.
- if (!IsRequestInProgress() || view_)
- return;
- DCHECK(web_contents()->IsDocumentOnLoadCompletedInPrimaryMainFrame());
- DCHECK(current_request_ui_to_use_);
- if (tab_is_hidden_)
- return;
- if (!RecreateView())
- return;
- if (!current_request_already_displayed_) {
- PermissionUmaUtil::PermissionPromptShown(requests_);
- auto quiet_ui_reason = ReasonForUsingQuietUi();
- if (quiet_ui_reason) {
- switch (*quiet_ui_reason) {
- case QuietUiReason::kEnabledInPrefs:
- case QuietUiReason::kTriggeredByCrowdDeny:
- case QuietUiReason::kServicePredictedVeryUnlikelyGrant:
- case QuietUiReason::kOnDevicePredictedVeryUnlikelyGrant:
- break;
- case QuietUiReason::kTriggeredDueToAbusiveRequests:
- LogWarningToConsole(kAbusiveNotificationRequestsEnforcementMessage);
- break;
- case QuietUiReason::kTriggeredDueToAbusiveContent:
- LogWarningToConsole(kAbusiveNotificationContentEnforcementMessage);
- break;
- case QuietUiReason::kTriggeredDueToDisruptiveBehavior:
- LogWarningToConsole(
- kDisruptiveNotificationBehaviorEnforcementMessage);
- break;
- }
- base::RecordAction(base::UserMetricsAction(
- "Notifications.Quiet.PermissionRequestShown"));
- }
- }
- current_request_already_displayed_ = true;
- current_request_first_display_time_ = base::Time::Now();
- NotifyBubbleAdded();
- // If in testing mode, automatically respond to the bubble that was shown.
- if (auto_response_for_test_ != NONE)
- DoAutoResponseForTesting();
- }
- void PermissionRequestManager::DeleteBubble() {
- DCHECK(view_);
- {
- base::AutoReset<bool> deleting(&ignore_callbacks_from_prompt_, true);
- view_.reset();
- }
- NotifyBubbleRemoved();
- }
- void PermissionRequestManager::ResetViewStateForCurrentRequest() {
- for (const auto& selector : permission_ui_selectors_)
- selector->Cancel();
- current_request_already_displayed_ = false;
- current_request_first_display_time_ = base::Time();
- current_request_decision_time_ = base::Time();
- current_request_prompt_disposition_.reset();
- prediction_grant_likelihood_.reset();
- current_request_ui_to_use_.reset();
- was_decision_held_back_.reset();
- selector_decisions_.clear();
- should_dismiss_current_request_ = false;
- did_show_bubble_ = false;
- did_click_manage_ = false;
- did_click_learn_more_ = false;
- if (view_)
- DeleteBubble();
- }
- void PermissionRequestManager::FinalizeCurrentRequests(
- PermissionAction permission_action) {
- DCHECK(IsRequestInProgress());
- base::TimeDelta time_to_decision;
- if (!current_request_first_display_time_.is_null() &&
- permission_action != PermissionAction::IGNORED) {
- if (current_request_decision_time_.is_null()) {
- current_request_decision_time_ = base::Time::Now();
- }
- time_to_decision =
- current_request_decision_time_ - current_request_first_display_time_;
- }
- if (time_to_decision_for_test_.has_value()) {
- time_to_decision = time_to_decision_for_test_.value();
- time_to_decision_for_test_.reset();
- }
- PermissionUmaUtil::PermissionPromptResolved(
- requests_, web_contents(), permission_action, time_to_decision,
- DetermineCurrentRequestUIDisposition(),
- DetermineCurrentRequestUIDispositionReasonForUMA(),
- prediction_grant_likelihood_, was_decision_held_back_, did_show_bubble_,
- did_click_manage_, did_click_learn_more_);
- content::BrowserContext* browser_context =
- web_contents()->GetBrowserContext();
- PermissionDecisionAutoBlocker* autoblocker =
- PermissionsClient::Get()->GetPermissionDecisionAutoBlocker(
- browser_context);
- absl::optional<QuietUiReason> quiet_ui_reason;
- if (ShouldCurrentRequestUseQuietUI())
- quiet_ui_reason = ReasonForUsingQuietUi();
- for (PermissionRequest* request : requests_) {
- // TODO(timloh): We only support dismiss and ignore embargo for
- // permissions which use PermissionRequestImpl as the other subclasses
- // don't support GetContentSettingsType.
- if (request->GetContentSettingsType() == ContentSettingsType::DEFAULT)
- continue;
- PermissionsClient::Get()->OnPromptResolved(
- browser_context, request->request_type(), permission_action,
- request->requesting_origin(), DetermineCurrentRequestUIDisposition(),
- DetermineCurrentRequestUIDispositionReasonForUMA(),
- request->GetGestureType(), quiet_ui_reason);
- PermissionEmbargoStatus embargo_status =
- PermissionEmbargoStatus::NOT_EMBARGOED;
- if (permission_action == PermissionAction::DISMISSED) {
- if (autoblocker->RecordDismissAndEmbargo(
- request->requesting_origin(), request->GetContentSettingsType(),
- ShouldCurrentRequestUseQuietUI())) {
- embargo_status = PermissionEmbargoStatus::REPEATED_DISMISSALS;
- }
- } else if (permission_action == PermissionAction::IGNORED) {
- if (autoblocker->RecordIgnoreAndEmbargo(
- request->requesting_origin(), request->GetContentSettingsType(),
- ShouldCurrentRequestUseQuietUI())) {
- embargo_status = PermissionEmbargoStatus::REPEATED_IGNORES;
- }
- }
- PermissionUmaUtil::RecordEmbargoStatus(embargo_status);
- }
- ResetViewStateForCurrentRequest();
- std::vector<PermissionRequest*>::iterator requests_iter;
- for (requests_iter = requests_.begin(); requests_iter != requests_.end();
- requests_iter++) {
- RequestFinishedIncludingDuplicates(*requests_iter);
- request_sources_map_.erase(request_sources_map_.find(*requests_iter));
- }
- requests_.clear();
- for (Observer& observer : observer_list_)
- observer.OnRequestsFinalized();
- ScheduleDequeueRequestIfNeeded();
- }
- void PermissionRequestManager::CleanUpRequests() {
- for (; !pending_permission_requests_.IsEmpty();
- pending_permission_requests_.Pop()) {
- auto* pending_request = pending_permission_requests_.Peek();
- CancelledIncludingDuplicates(pending_request);
- RequestFinishedIncludingDuplicates(pending_request);
- request_sources_map_.erase(request_sources_map_.find(pending_request));
- }
- if (IsRequestInProgress()) {
- std::vector<PermissionRequest*>::iterator requests_iter;
- for (requests_iter = requests_.begin(); requests_iter != requests_.end();
- requests_iter++) {
- CancelledIncludingDuplicates(*requests_iter);
- }
- FinalizeCurrentRequests(should_dismiss_current_request_
- ? PermissionAction::DISMISSED
- : PermissionAction::IGNORED);
- should_dismiss_current_request_ = false;
- }
- }
- PermissionRequest* PermissionRequestManager::GetExistingRequest(
- PermissionRequest* request) {
- for (PermissionRequest* existing_request : requests_) {
- if (request->IsDuplicateOf(existing_request))
- return existing_request;
- }
- return pending_permission_requests_.FindDuplicate(request);
- }
- void PermissionRequestManager::PermissionGrantedIncludingDuplicates(
- PermissionRequest* request,
- bool is_one_time) {
- DCHECK_EQ(1ul, base::STLCount(requests_, request) +
- pending_permission_requests_.Count(request))
- << "Only requests in [pending_permission_]requests_ can have duplicates";
- request->PermissionGranted(is_one_time);
- auto range = duplicate_requests_.equal_range(request);
- for (auto it = range.first; it != range.second; ++it)
- it->second->PermissionGranted(is_one_time);
- }
- void PermissionRequestManager::PermissionDeniedIncludingDuplicates(
- PermissionRequest* request) {
- DCHECK_EQ(1ul, base::STLCount(requests_, request) +
- pending_permission_requests_.Count(request))
- << "Only requests in [pending_permission_]requests_ can have duplicates";
- request->PermissionDenied();
- auto range = duplicate_requests_.equal_range(request);
- for (auto it = range.first; it != range.second; ++it)
- it->second->PermissionDenied();
- }
- void PermissionRequestManager::CancelledIncludingDuplicates(
- PermissionRequest* request) {
- DCHECK_EQ(1ul, base::STLCount(requests_, request) +
- pending_permission_requests_.Count(request))
- << "Only requests in [pending_permission_]requests_ can have duplicates";
- request->Cancelled();
- auto range = duplicate_requests_.equal_range(request);
- for (auto it = range.first; it != range.second; ++it)
- it->second->Cancelled();
- }
- void PermissionRequestManager::RequestFinishedIncludingDuplicates(
- PermissionRequest* request) {
- DCHECK_EQ(1ul, base::STLCount(requests_, request) +
- pending_permission_requests_.Count(request))
- << "Only requests in [pending_permission_]requests_ can have duplicates";
- request->RequestFinished();
- // Beyond this point, |request| has probably been deleted.
- auto range = duplicate_requests_.equal_range(request);
- for (auto it = range.first; it != range.second; ++it)
- it->second->RequestFinished();
- // Additionally, we can now remove the duplicates.
- duplicate_requests_.erase(request);
- }
- void PermissionRequestManager::AddObserver(Observer* observer) {
- observer_list_.AddObserver(observer);
- }
- void PermissionRequestManager::RemoveObserver(Observer* observer) {
- observer_list_.RemoveObserver(observer);
- }
- bool PermissionRequestManager::ShouldCurrentRequestUseQuietUI() const {
- // ContentSettingImageModel might call into this method if the user switches
- // between tabs while the |notification_permission_ui_selectors_| are
- // pending.
- return ReasonForUsingQuietUi() != absl::nullopt;
- }
- absl::optional<PermissionRequestManager::QuietUiReason>
- PermissionRequestManager::ReasonForUsingQuietUi() const {
- if (!IsRequestInProgress() || !current_request_ui_to_use_ ||
- !current_request_ui_to_use_->quiet_ui_reason)
- return absl::nullopt;
- return *(current_request_ui_to_use_->quiet_ui_reason);
- }
- bool PermissionRequestManager::IsRequestInProgress() const {
- return !requests_.empty();
- }
- bool PermissionRequestManager::ShouldDropCurrentRequestIfCannotShowQuietly()
- const {
- absl::optional<QuietUiReason> quiet_ui_reason = ReasonForUsingQuietUi();
- if (quiet_ui_reason.has_value()) {
- switch (quiet_ui_reason.value()) {
- case QuietUiReason::kEnabledInPrefs:
- case QuietUiReason::kServicePredictedVeryUnlikelyGrant:
- case QuietUiReason::kOnDevicePredictedVeryUnlikelyGrant:
- case QuietUiReason::kTriggeredByCrowdDeny:
- return false;
- case QuietUiReason::kTriggeredDueToAbusiveRequests:
- case QuietUiReason::kTriggeredDueToAbusiveContent:
- case QuietUiReason::kTriggeredDueToDisruptiveBehavior:
- return true;
- }
- }
- return false;
- }
- void PermissionRequestManager::NotifyBubbleAdded() {
- for (Observer& observer : observer_list_)
- observer.OnBubbleAdded();
- }
- void PermissionRequestManager::NotifyBubbleRemoved() {
- for (Observer& observer : observer_list_)
- observer.OnBubbleRemoved();
- }
- void PermissionRequestManager::OnPermissionUiSelectorDone(
- size_t selector_index,
- const UiDecision& decision) {
- if (decision.warning_reason) {
- switch (*(decision.warning_reason)) {
- case WarningReason::kAbusiveRequests:
- LogWarningToConsole(kAbusiveNotificationRequestsWarningMessage);
- break;
- case WarningReason::kAbusiveContent:
- LogWarningToConsole(kAbusiveNotificationContentWarningMessage);
- break;
- case WarningReason::kDisruptiveBehavior:
- break;
- }
- }
- // We have already made a decision because of a higher priority selector
- // therefore this selector's decision can be discarded.
- if (current_request_ui_to_use_.has_value())
- return;
- CHECK_LT(selector_index, selector_decisions_.size());
- selector_decisions_[selector_index] = decision;
- size_t decision_index = 0;
- while (decision_index < selector_decisions_.size() &&
- selector_decisions_[decision_index].has_value()) {
- const UiDecision& current_decision =
- selector_decisions_[decision_index].value();
- if (!prediction_grant_likelihood_.has_value()) {
- prediction_grant_likelihood_ = permission_ui_selectors_[decision_index]
- ->PredictedGrantLikelihoodForUKM();
- }
- if (!was_decision_held_back_.has_value()) {
- was_decision_held_back_ = permission_ui_selectors_[decision_index]
- ->WasSelectorDecisionHeldback();
- }
- if (current_decision.quiet_ui_reason.has_value()) {
- current_request_ui_to_use_ = current_decision;
- break;
- }
- ++decision_index;
- }
- // All decisions have been considered and none was conclusive.
- if (decision_index == selector_decisions_.size() &&
- !current_request_ui_to_use_.has_value()) {
- current_request_ui_to_use_ = UiDecision::UseNormalUiAndShowNoWarning();
- }
- if (current_request_ui_to_use_.has_value()) {
- ScheduleShowBubble();
- }
- }
- PermissionPromptDisposition
- PermissionRequestManager::DetermineCurrentRequestUIDisposition() {
- if (current_request_prompt_disposition_.has_value())
- return current_request_prompt_disposition_.value();
- return PermissionPromptDisposition::NONE_VISIBLE;
- }
- PermissionPromptDispositionReason
- PermissionRequestManager::DetermineCurrentRequestUIDispositionReasonForUMA() {
- auto quiet_ui_reason = ReasonForUsingQuietUi();
- if (!quiet_ui_reason)
- return PermissionPromptDispositionReason::DEFAULT_FALLBACK;
- switch (*quiet_ui_reason) {
- case QuietUiReason::kEnabledInPrefs:
- return PermissionPromptDispositionReason::USER_PREFERENCE_IN_SETTINGS;
- case QuietUiReason::kTriggeredByCrowdDeny:
- case QuietUiReason::kTriggeredDueToAbusiveRequests:
- case QuietUiReason::kTriggeredDueToAbusiveContent:
- case QuietUiReason::kTriggeredDueToDisruptiveBehavior:
- return PermissionPromptDispositionReason::SAFE_BROWSING_VERDICT;
- case QuietUiReason::kServicePredictedVeryUnlikelyGrant:
- return PermissionPromptDispositionReason::PREDICTION_SERVICE;
- case QuietUiReason::kOnDevicePredictedVeryUnlikelyGrant:
- return PermissionPromptDispositionReason::ON_DEVICE_PREDICTION_MODEL;
- }
- }
- void PermissionRequestManager::LogWarningToConsole(const char* message) {
- web_contents()->GetPrimaryMainFrame()->AddMessageToConsole(
- blink::mojom::ConsoleMessageLevel::kWarning, message);
- }
- void PermissionRequestManager::DoAutoResponseForTesting() {
- switch (auto_response_for_test_) {
- case ACCEPT_ONCE:
- AcceptThisTime();
- break;
- case ACCEPT_ALL:
- Accept();
- break;
- case DENY_ALL:
- Deny();
- break;
- case DISMISS:
- Dismiss();
- break;
- case NONE:
- NOTREACHED();
- }
- }
- WEB_CONTENTS_USER_DATA_KEY_IMPL(PermissionRequestManager);
- } // namespace permissions
|