// Copyright 2020 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "chrome/browser/permissions/chrome_permissions_client.h"

#include <algorithm>
#include <optional>
#include <vector>

#include "base/check_deref.h"
#include "base/feature_list.h"
#include "base/functional/callback_helpers.h"
#include "base/no_destructor.h"
#include "base/strings/string_util.h"
#include "base/time/time.h"
#include "build/build_config.h"
#include "chrome/browser/actor/actor_keyed_service.h"
#include "chrome/browser/app_mode/app_mode_utils.h"
#include "chrome/browser/ash/shimless_rma/chrome_shimless_rma_delegate.h"
#include "chrome/browser/bluetooth/bluetooth_chooser_context_factory.h"
#include "chrome/browser/browser_process.h"
#include "chrome/browser/content_settings/cookie_settings_factory.h"
#include "chrome/browser/content_settings/host_content_settings_map_factory.h"
#include "chrome/browser/engagement/important_sites_util.h"
#include "chrome/browser/hid/hid_chooser_context.h"
#include "chrome/browser/hid/hid_chooser_context_factory.h"
#include "chrome/browser/infobars/browser_infobar_manager.h"
#include "chrome/browser/infobars/infobar_features.h"
#include "chrome/browser/media/webrtc/media_stream_device_permissions.h"
#include "chrome/browser/metrics/ukm_background_recorder_service.h"
#include "chrome/browser/permissions/origin_keyed_permission_action_service_factory.h"
#include "chrome/browser/permissions/permission_actions_history_factory.h"
#include "chrome/browser/permissions/permission_decision_auto_blocker_factory.h"
#include "chrome/browser/permissions/permission_revocation_request.h"
#include "chrome/browser/permissions/prediction_service/permissions_ai_ui_selector.h"
#include "chrome/browser/permissions/pref_based_quiet_permission_ui_selector.h"
#include "chrome/browser/permissions/quiet_notification_permission_ui_config.h"
#include "chrome/browser/permissions/system/system_permission_settings.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/browser/profiles/profiles_state.h"
#include "chrome/browser/search_engines/ui_thread_search_terms_data.h"
#include "chrome/browser/serial/serial_chooser_context.h"
#include "chrome/browser/serial/serial_chooser_context_factory.h"
#include "chrome/browser/subresource_filter/subresource_filter_profile_context_factory.h"
#include "chrome/browser/ui/browser_window/public/profile_browser_collection.h"
#include "chrome/browser/ui/hats/hats_service.h"
#include "chrome/browser/ui/hats/hats_service_factory.h"
#include "chrome/browser/ui/hats/survey_config.h"
#include "chrome/browser/ui/page_info/page_info_infobar_delegate.h"
#include "chrome/browser/usb/usb_chooser_context.h"
#include "chrome/browser/usb/usb_chooser_context_factory.h"
#include "chrome/common/channel_info.h"
#include "chrome/common/pref_names.h"
#include "chrome/common/url_constants.h"
#include "chrome/common/webui_url_constants.h"
#include "chrome/grit/branded_strings.h"
#include "components/content_settings/core/browser/content_settings_type_set.h"
#include "components/content_settings/core/browser/cookie_settings.h"
#include "components/content_settings/core/browser/permission_settings_info.h"
#include "components/content_settings/core/browser/permission_settings_registry.h"
#include "components/content_settings/core/common/content_settings.h"
#include "components/google/core/common/google_util.h"
#include "components/infobars/content/content_infobar_manager.h"
#include "components/infobars/core/infobar_manager.h"
#include "components/permissions/constants.h"
#include "components/permissions/contexts/bluetooth_chooser_context.h"
#include "components/permissions/features.h"
#include "components/permissions/permission_hats_trigger_helper.h"
#include "components/permissions/permission_request.h"
#include "components/permissions/permission_uma_util.h"
#include "components/permissions/permission_util.h"
#include "components/permissions/permissions_client.h"
#include "components/permissions/request_type.h"
#include "components/permissions/resolvers/permission_prompt_options.h"
#include "components/prefs/pref_service.h"
#include "components/site_engagement/content/site_engagement_service.h"
#include "components/subresource_filter/content/browser/subresource_filter_content_settings_manager.h"
#include "components/subresource_filter/content/browser/subresource_filter_profile_context.h"
#include "components/tabs/public/tab_interface.h"
#include "components/unified_consent/pref_names.h"
#include "components/version_info/version_info.h"
#include "content/public/browser/navigation_controller.h"
#include "content/public/browser/navigation_entry.h"
#include "content/public/browser/render_frame_host.h"
#include "content/public/browser/web_contents.h"
#include "content/public/common/url_constants.h"
#include "extensions/buildflags/buildflags.h"
#include "net/base/registry_controlled_domains/registry_controlled_domain.h"
#include "services/metrics/public/cpp/ukm_recorder.h"
#include "ui/base/l10n/l10n_util.h"
#include "url/origin.h"

#if BUILDFLAG(IS_ANDROID)
#include "chrome/browser/android/resource_mapper.h"
#include "chrome/browser/android/search_permissions/search_permissions_service.h"
#include "chrome/browser/favicon/favicon_service_factory.h"
#include "chrome/browser/permissions/permission_blocked_message_delegate_android.h"
#include "chrome/browser/permissions/permission_update_message_controller_android.h"
#include "components/permissions/android/permissions_android_feature_map.h"
#include "components/permissions/permission_request_manager.h"
#else
#include "chrome/browser/ui/browser_window/public/browser_window_interface.h"
#include "chrome/browser/ui/permission_bubble/permission_prompt.h"
#include "chrome/browser/ui/tabs/tab_strip_model.h"
#include "components/vector_icons/vector_icons.h"
#endif

#if BUILDFLAG(IS_CHROMEOS)
#include "ash/constants/ash_features.h"
#include "chrome/browser/ash/app_mode/isolated_web_app/kiosk_iwa_data.h"
#include "chrome/browser/ash/app_mode/isolated_web_app/kiosk_iwa_manager.h"
#include "chrome/browser/ash/app_mode/web_app/kiosk_web_app_data.h"
#include "chrome/browser/ash/app_mode/web_app/kiosk_web_app_manager.h"
#include "chromeos/ash/components/browser_context_helper/browser_context_types.h"
#include "chromeos/components/kiosk/kiosk_utils.h"
#include "components/user_manager/user.h"
#include "components/user_manager/user_manager.h"
#endif

#if BUILDFLAG(ENABLE_EXTENSIONS_CORE)
#include "extensions/common/constants.h"
#endif

#if BUILDFLAG(ENABLE_EXTENSIONS) && !BUILDFLAG(IS_ANDROID)
#include "extensions/browser/mime_handler/mime_handler_stream_manager.h"
#endif  // BUILDFLAG(ENABLE_EXTENSIONS) && !BUILDFLAG(IS_ANDROID)

#if BUILDFLAG(SAFE_BROWSING_AVAILABLE)
#include "chrome/browser/permissions/prediction_service/contextual_notification_permission_ui_selector.h"
#include "chrome/browser/safe_browsing/safe_browsing_service.h"
#endif

namespace {

using permissions::PermissionPromptDisposition;
using permissions::PermissionPromptDispositionReason;
using permissions::PermissionRequest;
using permissions::PermissionRequestGestureType;

const url::Origin& GetNewTabOrigin() {
  static const base::NoDestructor<url::Origin> origin(
      url::Origin::Create(chrome::ChromeUINewTabURLAsGURL()));
  return *origin;
}

const url::Origin& GetNtpOrigin() {
  static const base::NoDestructor<url::Origin> origin(
      url::Origin::Create(chrome::ChromeUINewTabPageURLAsGURL()));
  return *origin;
}

const url::Origin& GetOmniboxPopupOrigin() {
  static const base::NoDestructor<url::Origin> origin(
      url::Origin::Create(GURL(chrome::kChromeUIOmniboxPopupURL)));
  return *origin;
}

const url::Origin& GetOmniboxEverywhereOrigin() {
  static const base::NoDestructor<url::Origin> origin(
      url::Origin::Create(GURL(chrome::kChromeUIOmniboxEverywhereURL)));
  return *origin;
}

const url::Origin& GetContextualTasksOrigin() {
  static const base::NoDestructor<url::Origin> origin(
      url::Origin::Create(GURL(chrome::kChromeUIContextualTasksURL)));
  return *origin;
}

#if BUILDFLAG(IS_ANDROID)
bool ShouldUseQuietUI(content::WebContents* web_contents,
                      const permissions::PermissionRequest& request) {
  if (request.GetGeolocationPromptType() ==
      permissions::GeolocationPromptType::kUpgradeToPrecise) {
    // We don't support quiet UI for upgrade prompts from approximate location
    // to precise location.
    return false;
  }
  ContentSettingsType type = request.GetContentSettingsType();
  auto* manager =
      permissions::PermissionRequestManager::FromWebContents(web_contents);
  if (type != ContentSettingsType::NOTIFICATIONS &&
      type != permissions::PermissionUtil::GetGeolocationType()) {
    return false;
  }
  return manager->ShouldCurrentRequestUseQuietUI();
}
#endif

#if BUILDFLAG(IS_CHROMEOS)

std::optional<url::Origin> GetCurrentKioskOrigin() {
  if (chromeos::IsWebKioskSession()) {
    const AccountId& account_id =
        user_manager::UserManager::Get()->GetPrimaryUser()->GetAccountId();
    DCHECK(ash::KioskWebAppManager::IsInitialized());
    const ash::KioskWebAppData* app_data =
        ash::KioskWebAppManager::Get()->GetAppByAccountId(account_id);
    DCHECK(app_data);
    return url::Origin::Create(app_data->install_url());
  }

  if (chromeos::IsIwaKioskSession()) {
    const AccountId& account_id =
        user_manager::UserManager::Get()->GetPrimaryUser()->GetAccountId();
    const ash::KioskIwaData* iwa_data =
        CHECK_DEREF(ash::KioskIwaManager::Get()).GetApp(account_id);
    return CHECK_DEREF(iwa_data).origin();
  }

  return std::nullopt;
}

#endif

bool IsPermissionSetByAdministator(
    PermissionSetting setting,
    const content_settings::PermissionSettingsInfo* permission_info,
    const content_settings::SettingInfo& info) {
  return !permission_info->delegate().IsUndecided(setting) &&
         (info.source == content_settings::SettingSource::kPolicy ||
          info.source == content_settings::SettingSource::kSupervised);
}

#if !BUILDFLAG(IS_ANDROID)
// Infobar exists only on Desktop platforms.
bool ShouldShowInfobarOnPromptResolved(
    content::WebContents* web_contents,
    const PermissionRequest* request,
    std::optional<permissions::PermissionsClient::QuietUiReason>
        quiet_ui_reason,
    permissions::PermissionAction action) {
  if (!quiet_ui_reason ||
      (action != permissions::PermissionAction::GRANTED &&
       action != permissions::PermissionAction::GRANTED_ONCE) ||
      (request->request_type() != permissions::RequestType::kNotifications &&
       request->request_type() != permissions::RequestType::kGeolocation)) {
    return false;
  }
  content::PermissionController* permission_controller =
      web_contents->GetBrowserContext()->GetPermissionController();
  if (!permission_controller) {
    return false;
  }

  if (request->IsSourceSubscribedToPermissionChangeEvent(
          permission_controller)) {
    return false;
  }
  return true;
}

void ShowInfobar(content::WebContents* web_contents) {
  if (infobars::IsInfoBarMigrated(
          infobars::InfoBarDelegate::PAGE_INFO_INFOBAR_DELEGATE)) {
    auto* browser_infobar_manager =
        infobars::BrowserInfoBarManager::From(g_browser_process);
    if (browser_infobar_manager) {
      auto* tab = tabs::TabInterface::MaybeGetFromContents(web_contents);
      if (tab) {
        browser_infobar_manager->Show(
            tab, infobars::InfoBarDelegate::PAGE_INFO_INFOBAR_DELEGATE);
      }
    }
  } else {
    infobars::ContentInfoBarManager* infobar_manager =
        infobars::ContentInfoBarManager::FromWebContents(web_contents);
    if (infobar_manager) {
      PageInfoInfoBarDelegate::Create(infobar_manager);
    }
  }
}
#endif
}  // namespace

// static
ChromePermissionsClient* ChromePermissionsClient::GetInstance() {
  static base::NoDestructor<ChromePermissionsClient> instance;
  return instance.get();
}

HostContentSettingsMap* ChromePermissionsClient::GetSettingsMap(
    content::BrowserContext* browser_context) {
  return HostContentSettingsMapFactory::GetForProfile(
      Profile::FromBrowserContext(browser_context));
}

scoped_refptr<content_settings::CookieSettings>
ChromePermissionsClient::GetCookieSettings(
    content::BrowserContext* browser_context) {
  return CookieSettingsFactory::GetForProfile(
      Profile::FromBrowserContext(browser_context));
}

bool ChromePermissionsClient::IsSubresourceFilterActivated(
    content::BrowserContext* browser_context,
    const GURL& url) {
  return SubresourceFilterProfileContextFactory::GetForProfile(
             Profile::FromBrowserContext(browser_context))
      ->settings_manager()
      ->GetSiteActivationFromMetadata(url);
}

permissions::ObjectPermissionContextBase*
ChromePermissionsClient::GetChooserContext(
    content::BrowserContext* browser_context,
    ContentSettingsType type) {
  switch (type) {
    case ContentSettingsType::USB_CHOOSER_DATA:
      return UsbChooserContextFactory::GetForProfile(
          Profile::FromBrowserContext(browser_context));
    case ContentSettingsType::BLUETOOTH_CHOOSER_DATA:
      return BluetoothChooserContextFactory::GetForProfile(
          Profile::FromBrowserContext(browser_context));
    case ContentSettingsType::SERIAL_CHOOSER_DATA:
      return SerialChooserContextFactory::GetForProfile(
          Profile::FromBrowserContext(browser_context));
    case ContentSettingsType::HID_CHOOSER_DATA:
      return HidChooserContextFactory::GetForProfile(
          Profile::FromBrowserContext(browser_context));
    default:
      NOTREACHED();
  }
}

permissions::OriginKeyedPermissionActionService*
ChromePermissionsClient::GetOriginKeyedPermissionActionService(
    content::BrowserContext* browser_context) {
  return OriginKeyedPermissionActionServiceFactory::GetForProfile(
      Profile::FromBrowserContext(browser_context));
}

permissions::PermissionActionsHistory*
ChromePermissionsClient::GetPermissionActionsHistory(
    content::BrowserContext* browser_context) {
  return PermissionActionsHistoryFactory::GetForProfile(
      Profile::FromBrowserContext(browser_context));
}

permissions::PermissionDecisionAutoBlocker*
ChromePermissionsClient::GetPermissionDecisionAutoBlocker(
    content::BrowserContext* browser_context) {
  return PermissionDecisionAutoBlockerFactory::GetForProfile(
      Profile::FromBrowserContext(browser_context));
}

double ChromePermissionsClient::GetSiteEngagementScore(
    content::BrowserContext* browser_context,
    const GURL& origin) {
  return site_engagement::SiteEngagementService::Get(
             Profile::FromBrowserContext(browser_context))
      ->GetScore(origin);
}

void ChromePermissionsClient::AreSitesImportant(
    content::BrowserContext* browser_context,
    std::vector<std::pair<url::Origin, bool>>* origins) {
  // We need to limit our size due to the algorithm in ImportantSiteUtil,
  // but we want to be more on the liberal side here as we're not exposing
  // these sites to the user, we're just using them for our 'clear
  // unimportant' feature in ManageSpaceActivity.java.
  const int kMaxImportantSites = 10;
  std::vector<site_engagement::ImportantSitesUtil::ImportantDomainInfo>
      important_domains =
          site_engagement::ImportantSitesUtil::GetImportantRegisterableDomains(
              Profile::FromBrowserContext(browser_context), kMaxImportantSites);

  for (auto& entry : *origins) {
    const url::Origin& origin = entry.first;
    const std::string& host = origin.host();
    std::string registerable_domain =
        net::registry_controlled_domains::GetDomainAndRegistry(
            origin,
            net::registry_controlled_domains::INCLUDE_PRIVATE_REGISTRIES);
    if (registerable_domain.empty()) {
      registerable_domain = host;  // IP address or internal hostname.
    }
    entry.second =
        std::ranges::contains(important_domains, registerable_domain,
                              &site_engagement::ImportantSitesUtil::
                                  ImportantDomainInfo::registerable_domain);
  }
}

// Some Google-affiliated domains are not allowed to delete cookies for
// supervised accounts. This restriction can also be applied to enterprise
// users via policy.
bool ChromePermissionsClient::IsCookieDeletionDisabled(
    content::BrowserContext* browser_context,
    const GURL& origin) {
  Profile* profile = Profile::FromBrowserContext(browser_context);
  bool is_youtube =
      google_util::IsYoutubeDomainUrl(origin, google_util::ALLOW_SUBDOMAIN,
                                      google_util::ALLOW_NON_STANDARD_PORTS);
  if (!is_youtube) {
    return false;
  }

  if (profile->IsChild()) {
    return true;
  }

  const PrefService::Preference* pref = profile->GetPrefs()->FindPreference(
      prefs::kRestrictYouTubeCookiesDeletion);
  if (pref && pref->IsManaged()) {
    return pref->GetValue()->GetBool();
  }

  return false;
}

void ChromePermissionsClient::GetUkmSourceId(
    ContentSettingsType permission_type,
    content::BrowserContext* browser_context,
    content::RenderFrameHost* render_frame_host,
    const GURL& requesting_origin,
    GetUkmSourceIdCallback callback) {
  if (render_frame_host) {
    ukm::SourceId source_id = render_frame_host->GetPageUkmSourceId();
    std::move(callback).Run(source_id);
  } else if (permission_type == ContentSettingsType::NOTIFICATIONS) {
    ukm::SourceId source_id =
        ukm::UkmRecorder::GetSourceIdForNotificationPermission(
            base::PassKey<ChromePermissionsClient>(), requesting_origin);
    std::move(callback).Run(source_id);
  } else {
    // We only record a permission change if the origin is in the user's
    // history.
    ukm::UkmBackgroundRecorderFactory::GetForProfile(
        Profile::FromBrowserContext(browser_context))
        ->GetBackgroundSourceIdIfAllowed(url::Origin::Create(requesting_origin),
                                         std::move(callback));
  }
}

permissions::IconId ChromePermissionsClient::GetOverrideIconId(
    permissions::RequestType request_type) {
#if BUILDFLAG(IS_CHROMEOS)
  // TODO(xhwang): fix this icon, see crbug.com/40399970.
  if (request_type == permissions::RequestType::kProtectedMediaIdentifier) {
    return vector_icons::kProductIcon;
  }
#endif
  return PermissionsClient::GetOverrideIconId(request_type);
}

// Triggers the prompt HaTS survey if enabled by field trials for this
// combination of prompt parameters.
void ChromePermissionsClient::TriggerPromptHatsSurveyIfEnabled(
    content::WebContents* web_contents,
    permissions::RequestType request_type,
    std::optional<permissions::PermissionAction> action,
    PermissionPromptDisposition prompt_disposition,
    PermissionPromptDispositionReason prompt_disposition_reason,
    PermissionRequestGestureType gesture_type,
    std::optional<base::TimeDelta> prompt_display_duration,
    bool is_post_prompt,
    const GURL& gurl,
    std::optional<permissions::feature_params::PermissionElementPromptPosition>
        pepc_prompt_position,
    ContentSetting initial_permission_status,
    base::OnceCallback<void()> hats_shown_callback,
    PromptOptions prompt_options) {
  Profile* profile =
      Profile::FromBrowserContext(web_contents->GetBrowserContext());
  std::optional<GURL> recorded_gurl =
      profile->GetPrefs()->GetBoolean(
          unified_consent::prefs::kUrlKeyedAnonymizedDataCollectionEnabled)
          ? std::make_optional(gurl)
          : std::nullopt;

  auto prompt_parameters =
      permissions::PermissionHatsTriggerHelper::PromptParametersForHats(
          request_type, action, prompt_disposition, prompt_disposition_reason,
          gesture_type,
          std::string(version_info::GetChannelString(chrome::GetChannel())),
          is_post_prompt ? permissions::kOnPromptResolved
                         : permissions::kOnPromptAppearing,
          prompt_display_duration,
          permissions::PermissionHatsTriggerHelper::
              GetOneTimePromptsDecidedBucket(profile->GetPrefs()),
          recorded_gurl, pepc_prompt_position, initial_permission_status,
          prompt_options);

  if (!permissions::PermissionHatsTriggerHelper::
          ArePromptTriggerCriteriaSatisfied(prompt_parameters)) {
    return;
  }

  std::optional<
      permissions::PermissionHatsTriggerHelper::SurveyParametersForHats>
      survey_parameters = permissions::PermissionHatsTriggerHelper::
          GetSurveyParametersForRequestType(request_type);

  auto* hats_service =
      HatsServiceFactory::GetForProfile(profile,
                                        /*create_if_necessary=*/true);
  if (!hats_service || !survey_parameters.has_value()) {
    return;
  }

  auto survey_data = permissions::PermissionHatsTriggerHelper::
      SurveyProductSpecificData::PopulateFrom(prompt_parameters);

#if !BUILDFLAG(IS_ANDROID)
  hats_service->LaunchSurvey(
      kHatsSurveyTriggerPermissionsPrompt, std::move(hats_shown_callback),
      base::DoNothing(), survey_data.survey_bits_data,
      survey_data.survey_string_data, survey_parameters->supplied_trigger_id,
#else
  // Launching surveys on android requires an active web contents.
  hats_service->LaunchSurveyForWebContents(
      kHatsSurveyTriggerPermissionsPrompt, web_contents,
      survey_data.survey_bits_data, survey_data.survey_string_data,
      std::move(hats_shown_callback), base::DoNothing(),
      survey_parameters->supplied_trigger_id,
#endif
      HatsService::SurveyOptions(survey_parameters->custom_survey_invitation,
                                 survey_parameters->message_identifier));
}

#if !BUILDFLAG(IS_ANDROID)
permissions::PermissionIgnoredReason
ChromePermissionsClient::DetermineIgnoreReason(
    content::WebContents* web_contents) {
  Profile* profile =
      Profile::FromBrowserContext(web_contents->GetBrowserContext());
  BrowserWindowInterface* const browser =
      ProfileBrowserCollection::GetForProfile(profile)->GetLastActiveBrowser();
  if (browser) {
    if (browser->GetTabStripModel()->empty()) {
      return permissions::PermissionIgnoredReason::WINDOW_CLOSED;
    } else if (web_contents->IsBeingDestroyed()) {
      return permissions::PermissionIgnoredReason::TAB_CLOSED;
    } else {
      return permissions::PermissionIgnoredReason::NAVIGATION;
    }
  }
  return permissions::PermissionIgnoredReason::UNKNOWN;
}
#endif

std::vector<std::unique_ptr<permissions::PermissionUiSelector>>
ChromePermissionsClient::CreatePermissionUiSelectors(
    content::BrowserContext* browser_context) {
  std::vector<std::unique_ptr<permissions::PermissionUiSelector>> selectors;
#if BUILDFLAG(SAFE_BROWSING_AVAILABLE)
  selectors.emplace_back(
      std::make_unique<ContextualNotificationPermissionUiSelector>());
#endif
  selectors.emplace_back(std::make_unique<PrefBasedQuietPermissionUiSelector>(
      Profile::FromBrowserContext(browser_context)));
  selectors.emplace_back(std::make_unique<PermissionsAiUiSelector>(
      Profile::FromBrowserContext(browser_context)));
  return selectors;
}

void ChromePermissionsClient::OnPromptResolved(
    const PermissionRequest* request,
    permissions::PermissionAction action,
    const PromptOptions& prompt_options,
    PermissionPromptDisposition prompt_disposition,
    PermissionPromptDispositionReason prompt_disposition_reason,
    std::optional<QuietUiReason> quiet_ui_reason,
    base::TimeDelta prompt_display_duration,
    std::optional<permissions::feature_params::PermissionElementPromptPosition>
        pepc_prompt_position,
    ContentSetting initial_permission_status,
    content::WebContents* web_contents) {
  permissions::RequestType request_type = request->request_type();
  const GURL& origin = request->requesting_origin();
  PermissionRequestGestureType gesture_type = request->GetGestureType();

  Profile* profile =
      Profile::FromBrowserContext(web_contents->GetBrowserContext());
  PermissionActionsHistoryFactory::GetForProfile(profile)->RecordAction(
      action, request_type, prompt_disposition);

  if (request_type == permissions::RequestType::kNotifications) {
    if (action == permissions::PermissionAction::GRANTED &&
        quiet_ui_reason.has_value() &&
        (quiet_ui_reason.value() ==
             QuietUiReason::kTriggeredDueToAbusiveRequests ||
         quiet_ui_reason.value() ==
             QuietUiReason::kTriggeredDueToAbusiveContent ||
         quiet_ui_reason.value() ==
             QuietUiReason::kTriggeredDueToDisruptiveBehavior)) {
      PermissionRevocationRequest::ExemptOriginFromFutureRevocations(profile,
                                                                     origin);
    }
#if BUILDFLAG(SAFE_BROWSING_AVAILABLE)
    if (action == permissions::PermissionAction::GRANTED) {
      if (g_browser_process->safe_browsing_service()) {
        g_browser_process->safe_browsing_service()
            ->MaybeSendNotificationsAcceptedReport(
                web_contents->GetPrimaryMainFrame(), profile,
                web_contents->GetLastCommittedURL(),
                web_contents->GetController().GetLastCommittedEntry()->GetURL(),
                origin, prompt_display_duration);
      }
    }
#endif
  }

  // We're interested only in the granted prompts as in case of a permission
  // grant, Chrome needs to inform the page about a permission status change.
  if (quiet_ui_reason &&
      (action == permissions::PermissionAction::GRANTED ||
       action == permissions::PermissionAction::GRANTED_ONCE) &&
      (request->request_type() == permissions::RequestType::kNotifications ||
       request->request_type() == permissions::RequestType::kGeolocation)) {
    content::PermissionController* permission_controller =
        web_contents->GetBrowserContext()->GetPermissionController();
    if (permission_controller) {
      permissions::PermissionUmaUtil::
          RecordOnPermissionStatusChangedEventSubscribed(
              request->request_type(),
              request->IsSourceSubscribedToPermissionChangeEvent(
                  permission_controller));
    }
  }

#if !BUILDFLAG(IS_ANDROID)
  // Infobar exists only on Desktop platforms.
  bool should_show_infobar = ShouldShowInfobarOnPromptResolved(
      web_contents, request, quiet_ui_reason, action);
  permissions::PermissionUmaUtil::RecordPageReloadInfoBarShown(
      should_show_infobar);
  if (should_show_infobar) {
    ShowInfobar(web_contents);
  }
#endif

  auto content_setting_type = RequestTypeToContentSettingsType(request_type);
  if (content_setting_type.has_value()) {
    permissions::PermissionHatsTriggerHelper::
        IncrementOneTimePermissionPromptsDecidedIfApplicable(
            content_setting_type.value(), profile->GetPrefs());
  }

  TriggerPromptHatsSurveyIfEnabled(
      web_contents, request_type, std::make_optional(action),
      prompt_disposition, prompt_disposition_reason, gesture_type,
      std::make_optional(prompt_display_duration), /*is_post_prompt=*/true,
      web_contents->GetPrimaryMainFrame()->GetLastCommittedOrigin().GetURL(),
      pepc_prompt_position, initial_permission_status, base::DoNothing(),
      prompt_options);
}

std::optional<bool>
ChromePermissionsClient::HadThreeConsecutiveNotificationPermissionDenies(
    content::BrowserContext* browser_context) {
  if (!QuietNotificationPermissionUiConfig::
          IsAdaptiveActivationDryRunEnabled()) {
    return std::nullopt;
  }
  return Profile::FromBrowserContext(browser_context)
      ->GetPrefs()
      ->GetBoolean(prefs::kHadThreeConsecutiveNotificationPermissionDenies);
}

std::optional<bool> ChromePermissionsClient::HasPreviouslyAutoRevokedPermission(
    content::BrowserContext* browser_context,
    const GURL& origin,
    ContentSettingsType permission) {
  if (permission != ContentSettingsType::NOTIFICATIONS) {
    return std::nullopt;
  }

  Profile* profile = Profile::FromBrowserContext(browser_context);
  return PermissionRevocationRequest::HasPreviouslyRevokedPermission(profile,
                                                                     origin);
}

std::optional<url::Origin> ChromePermissionsClient::GetAutoApprovalOrigin(
    content::BrowserContext* browser_context) {
#if BUILDFLAG(IS_CHROMEOS)
  // In kiosk mode for web apps and isolated web apps, all permission requests
  // are auto-approved for the origin of the main app.
  std::optional<url::Origin> current_kiosk_origin = GetCurrentKioskOrigin();
  if (current_kiosk_origin.has_value()) {
    return current_kiosk_origin;
  }

  // In Shimless RMA mode, permission requests are auto-approved during runtime
  // since the app has requested all permissions during install time.
  if (ash::features::IsShimlessRMA3pDiagnosticsAllowPermissionPolicyEnabled() &&
      ash::IsShimlessRmaAppBrowserContext(browser_context)) {
    return ash::shimless_rma::DiagnosticsAppProfileHelperDelegate::
        GetInstalledDiagnosticsAppOrigin();
  }
#endif
  return std::nullopt;
}

std::optional<permissions::PermissionAction>
ChromePermissionsClient::GetAutoApprovalStatus(
    content::BrowserContext* browser_context,
    const GURL& origin) {
  if (base::FeatureList::IsEnabled(
          permissions::features::kAllowMultipleOriginsForWebKioskPermissions)) {
    Profile* profile = Profile::FromBrowserContext(browser_context);
    if (IsWebKioskOriginAllowed(profile->GetPrefs(), origin)) {
      return permissions::PermissionAction::GRANTED_ONCE;
    }
  }

  std::optional<url::Origin> auto_approval_origin =
      GetAutoApprovalOrigin(browser_context);

  if (!auto_approval_origin.has_value()) {
    return std::nullopt;
  }

  if (url::Origin::Create(origin) == auto_approval_origin.value()) {
    return permissions::PermissionAction::GRANTED;
  }

  return permissions::PermissionAction::IGNORED;
}

bool ChromePermissionsClient::CanBypassEmbeddingOriginCheck(
    const GURL& requesting_origin,
    const GURL& embedding_origin) {
// TODO(crbug.com/532219239): This function should get url::Origin instead of
// GURL directly.
#if BUILDFLAG(ENABLE_EXTENSIONS_CORE)
  // Extensions are excluded from origin checks as currently they can request
  // permission from iframes when embedded in non-secure contexts
  // (https://crbug.com/40435309).
  if (requesting_origin.SchemeIs(extensions::kExtensionScheme)) {
    return true;
  }
#endif
  url::Origin embedder = url::Origin::Create(embedding_origin);

  // New Tab Page:
  // Bypass embedding origin check as the `requesting_origin` will later be
  // transformed to the DSE origin in `GetCanonicalOriginOverride()`.
  if (embedder == GetNewTabOrigin() || embedder == GetNtpOrigin()) {
    return true;
  }

  // Omnibox Popup, Omnibox Everywhere, and Contextual Tasks:
  // Bypass embedding origin check as the `requesting_origin` will later be
  // transformed to the DSE origin in `GetCanonicalOriginOverride()`.
  if (embedder == GetContextualTasksOrigin() ||
      embedder == GetOmniboxPopupOrigin() ||
      embedder == GetOmniboxEverywhereOrigin()) {
    return true;
  }

  return false;
}

std::optional<GURL> ChromePermissionsClient::GetCanonicalOriginOverride(
    const GURL& requesting_origin,
    const GURL& embedding_origin) {
  // TODO(crbug.com/532219239): This function should get url::Origin instead of
  // GURL directly.
  url::Origin requester = url::Origin::Create(requesting_origin);
  url::Origin embedder = url::Origin::Create(embedding_origin);

  // New Tab Page:
  // Transform chrome:// origins to the DSE origin so that permissions are
  // stored under and shared with the DSE. If the embedder is new tab without
  // without the requester being the new tab page, do not override the URL.
  // Only if the embedder is the new tab AND the requester is the new tab page,
  // override the canonical origin to be 'Google.com'.
  if (embedder == GetNewTabOrigin() || embedder == GetNtpOrigin()) {
    if (requester == GetNtpOrigin() || requester == GetNewTabOrigin()) {
      return GURL(UIThreadSearchTermsData().GoogleBaseURLValue())
          .DeprecatedGetOriginAsURL();
    }
    return requesting_origin;
  }

  // Contextual Tasks:
  // Transform chrome:// origins to the DSE origin so that permissions are
  // stored under and shared with the DSE.
  if (embedder == requester && embedder == GetContextualTasksOrigin()) {
    return GURL(UIThreadSearchTermsData().GoogleBaseURLValue())
        .DeprecatedGetOriginAsURL();
  }

  // Omnibox:
  // Transform chrome:// origins to the DSE origin so that permissions are
  // stored under and shared with the DSE.
  if (requester == embedder && (requester == GetOmniboxPopupOrigin() ||
                                requester == GetOmniboxEverywhereOrigin())) {
    return GURL(UIThreadSearchTermsData().GoogleBaseURLValue())
        .DeprecatedGetOriginAsURL();
  }

#if BUILDFLAG(ENABLE_EXTENSIONS_CORE)
  // Note that currently chrome extensions are allowed to use permissions even
  // when in embedded in non-secure contexts. This is unfortunate and we
  // should remove this at some point, but for now always use the requesting
  // origin for embedded extensions. https://crbug.com/40435309.
  if (requesting_origin.SchemeIs(extensions::kExtensionScheme)) {
    return requesting_origin;
  }
#endif

  return std::nullopt;
}

std::optional<GURL> ChromePermissionsClient::GetEmbeddingOriginOverride(
    const GURL& requesting_origin,
    content::RenderFrameHost* render_frame_host) {
  content::WebContents* web_contents =
      content::WebContents::FromRenderFrameHost(render_frame_host);
  content::WebContents* embedder_web_contents =
      web_contents->GetOutermostWebContents();
  url::Origin embedder =
      embedder_web_contents->GetPrimaryMainFrame()->GetLastCommittedOrigin();

  // New Tab Page:
  // Use the WebContents origin as the embedding origin when the requesting
  // origin is the NTP (chrome://new-tab-page).
  // Note that the embedding origin is later transformed to the DSE origin via
  // `GetCanonicalOriginOverride()`.
  if (embedder == GetNewTabOrigin() || embedder == GetNtpOrigin()) {
    return embedder.GetURL();
  }

  if (embedder == GetContextualTasksOrigin() ||
      embedder == GetOmniboxPopupOrigin() ||
      embedder == GetOmniboxEverywhereOrigin()) {
    // Omnibox Popup, Omnibox Everywhere, and Contextual Tasks:
    // Use the WebContents origin as the embedding origin.
    // Note that the embedding origin is later transformed to the DSE origin via
    // `GetCanonicalOriginOverride()`.
    return embedder.GetURL();
  }

#if BUILDFLAG(ENABLE_EXTENSIONS) && !BUILDFLAG(IS_ANDROID)
  // When a MIME handler extension is rendered as an OOPIF, key the
  // embedding origin to the extension so prompts and content settings
  // are attributed to the extension that produced the streamed content,
  // not the navigation host that triggered it.
  //
  // The override applies only when the requesting frame lies inside the
  // extension OOPIF subtree. Without this scoping, a top-level page
  // (e.g. evil.com) that embeds an invisible PDF in an iframe would
  // have its own permission prompts (e.g. camera) attributed to the
  // MIME handler extension, because `MimeHandlerStreamManager` is
  // attached to the WebContents the moment any PDF in the tab is
  // intercepted -- not only when the extension itself is the requester.
  // Inside the extension subtree the override applies to every
  // descendant -- including cross-origin children -- because those
  // frames only exist because the extension embedded them.
  if (auto* manager =
          extensions::mime_handler::MimeHandlerStreamManager::FromWebContents(
              web_contents)) {
    for (content::RenderFrameHost* ancestor = render_frame_host; ancestor;
         ancestor = ancestor->GetParent()) {
      if (manager->IsExtensionHost(ancestor)) {
        return ancestor->GetLastCommittedOrigin().GetURL();
      }
    }
  }
#endif  // BUILDFLAG(ENABLE_EXTENSIONS) && !BUILDFLAG(IS_ANDROID)

  return std::nullopt;
}

// Considers any `new tab page` or `new tab` origins as being from the new tab
// page.
bool ChromePermissionsClient::IsFromNewTabPage(
    content::WebContents* web_contents,
    const GURL& requester,
    bool already_overrode_requester) {
  url::Origin embedding_origin = GetEmbeddingOrigin(web_contents);
  url::Origin requesting_origin = url::Origin::Create(requester);

  // A page is not from the new tab page if its embedder is not a new tab page
  // type.
  if (embedding_origin != GetNtpOrigin() &&
      embedding_origin != GetNewTabOrigin()) {
    return false;
  }

  // If the PEPC flag is enabled, then checking that the final
  // `requesting_origin` is equal to 'Google' URL after overriding the requester
  // origin is allowed.
  if (already_overrode_requester &&
      ChromePermissionsClient::AllowEmbeddedPermissionPromptForSurface(
          web_contents)) {
    return requesting_origin == GetGoogleURLOrigin();
  }
  // Since the embedder is from the new tab page at this point, a page
  // is definitely from new tab page if the requester is of new tab or new tab
  // page origin.
  return requesting_origin == GetNtpOrigin() ||
         requesting_origin == GetNewTabOrigin();
}

bool ChromePermissionsClient::IsPrivilegedInternalWebUI(
    content::WebContents* web_contents,
    const GURL& requester,
    bool already_overrode_requester) {
  url::Origin embedding_origin = GetEmbeddingOrigin(web_contents);
  url::Origin requesting_origin = url::Origin::Create(requester);

  // Check that the embedding origin is the Omnibox Popup, Contextual Tasks, or
  // Omnibox Everywhere.
  if (!IsPrivilegedInternalWebUIForUIRouting(embedding_origin)) {
    return false;
  }

  // If the PEPC flag is enabled (or for Omnibox Everywhere, regardless of PEPC
  // flag), then check that the final `requesting_origin` is equal to 'Google'
  // URL after overriding the requester origin is allowed.
  if (already_overrode_requester &&
      ChromePermissionsClient::AllowEmbeddedPermissionPromptForSurface(
          web_contents)) {
    return requesting_origin == GetGoogleURLOrigin();
  }
  return embedding_origin == requesting_origin;
}

bool ChromePermissionsClient::IsPrivilegedInternalWebUIForUIRouting(
    content::WebContents* web_contents) {
  url::Origin embedding_origin = GetEmbeddingOrigin(web_contents);
  return IsPrivilegedInternalWebUIForUIRouting(embedding_origin);
}

bool ChromePermissionsClient::
    IsPrivilegedInternalWebUIForUIRouting(  // overloaded private version
        const url::Origin& embedding_origin) {
  return embedding_origin == GetContextualTasksOrigin() ||
         embedding_origin == GetOmniboxPopupOrigin() ||
         embedding_origin == GetOmniboxEverywhereOrigin();
}

bool ChromePermissionsClient::IsOmniboxEverywhere(
    content::WebContents* web_contents) {
  url::Origin embedding_origin = GetEmbeddingOrigin(web_contents);
  return embedding_origin == GetOmniboxEverywhereOrigin();
}

#if BUILDFLAG(IS_ANDROID)
bool ChromePermissionsClient::IsDseOrigin(
    content::BrowserContext* browser_context,
    const url::Origin& origin) {
  SearchPermissionsService* search_helper =
      SearchPermissionsService::Factory::GetForBrowserContext(browser_context);
  return search_helper && search_helper->IsDseOrigin(origin);
}

std::unique_ptr<ChromePermissionsClient::PermissionMessageDelegate>
ChromePermissionsClient::MaybeCreateMessageUI(
    content::WebContents* web_contents,
    const permissions::PermissionRequest& request,
    base::WeakPtr<permissions::PermissionPromptAndroid> prompt) {
  ContentSettingsType type = request.GetContentSettingsType();
  if (ShouldUseQuietUI(web_contents, request) ||
      // The quiet UI is enabled for both Notifications and Geolocation but the
      // Loud Clapper supports only Notifications.
      (type == ContentSettingsType::NOTIFICATIONS &&
       base::FeatureList::IsEnabled(
           permissions::kPermissionsAndroidClapperLoud))) {
    auto delegate =
        std::make_unique<PermissionBlockedMessageDelegate::Delegate>(
            std::move(prompt));
    return std::make_unique<PermissionBlockedMessageDelegate>(
        web_contents, std::move(delegate));
  }

  return {};
}

void ChromePermissionsClient::RepromptForAndroidPermissions(
    content::WebContents* web_contents,
    const std::vector<ContentSettingsType>& content_settings_types,
    const std::vector<ContentSettingsType>& filtered_content_settings_types,
    const std::vector<std::string>& required_permissions,
    const std::vector<std::string>& optional_permissions,
    PermissionsUpdatedCallback callback) {
  PermissionUpdateMessageController::CreateForWebContents(web_contents);
  PermissionUpdateMessageController::FromWebContents(web_contents)
      ->ShowMessage(content_settings_types, filtered_content_settings_types,
                    required_permissions, optional_permissions,
                    std::move(callback));
}

int ChromePermissionsClient::MapToJavaDrawableId(int resource_id) {
  return ResourceMapper::MapToJavaDrawableId(resource_id);
}

favicon::FaviconService* ChromePermissionsClient::GetFaviconService(
    content::BrowserContext* browser_context) {
  return FaviconServiceFactory::GetForProfile(
      Profile::FromBrowserContext(browser_context),
      ServiceAccessType::EXPLICIT_ACCESS);
}

const std::u16string ChromePermissionsClient::GetClientApplicationName() const {
  return l10n_util::GetStringUTF16(IDS_SHORT_PRODUCT_NAME);
}

#else
std::unique_ptr<permissions::PermissionPrompt>
ChromePermissionsClient::CreatePrompt(
    content::WebContents* web_contents,
    permissions::PermissionPrompt::Delegate* delegate) {
  return CreatePermissionPrompt(web_contents, delegate);
}

std::unique_ptr<
    permissions::EmbeddedPermissionPromptFlowModel::PromptContentScrim>
ChromePermissionsClient::CreatePromptContentScrim(
    content::WebContents* web_contents,
    permissions::EmbeddedPermissionPromptFlowModel* flow_model) {
  CHECK(web_contents);
  return CreatePermissionPromptContentScrim(*web_contents, flow_model);
}
#endif

bool ChromePermissionsClient::HasDevicePermission(
    ContentSettingsType type) const {
#if BUILDFLAG(IS_MAC) || BUILDFLAG(IS_WIN) || BUILDFLAG(IS_CHROMEOS)
  return system_permission_settings::IsAllowed(type);
#else
  return PermissionsClient::HasDevicePermission(type);
#endif
}

bool ChromePermissionsClient::CanRequestDevicePermission(
    ContentSettingsType type) const {
#if BUILDFLAG(IS_MAC) || BUILDFLAG(IS_WIN) || BUILDFLAG(IS_CHROMEOS)
  return system_permission_settings::CanPrompt(type);
#else
  return PermissionsClient::CanRequestDevicePermission(type);
#endif
}

// TODO(41014586): Integrate policy-set media permissions into
// SettingsSource.policy. Currently, AudioCaptureAllowed, VideoCaptureAllowed
// are not checked within |IsPermissionSetByAdministrator|, so
// |IsPermissionBlockedByDevicePolicy| and |IsPermissionAllowedByDevicePolicy|
// methods are needed to show the appropriate policy screen.
bool ChromePermissionsClient::IsPermissionBlockedByDevicePolicy(
    content::WebContents* web_contents,
    PermissionSetting setting,
    const content_settings::SettingInfo& info,
    ContentSettingsType type) const {
  auto* permission_info =
      content_settings::PermissionSettingsRegistry::GetInstance()->Get(type);

  if (IsPermissionSetByAdministator(setting, permission_info, info) &&
      permission_info->delegate().IsBlocked(setting)) {
    return true;
  }

  Profile* profile =
      Profile::FromBrowserContext(web_contents->GetBrowserContext());
  if (type == ContentSettingsType::MEDIASTREAM_MIC) {
    return GetDevicePolicy(profile, web_contents->GetLastCommittedURL(),
                           prefs::kAudioCaptureAllowed,
                           prefs::kAudioCaptureAllowedUrls) ==
           MediaStreamDevicePolicy::ALWAYS_DENY;
  }

  if (type == ContentSettingsType::MEDIASTREAM_CAMERA) {
    return GetDevicePolicy(profile, web_contents->GetLastCommittedURL(),
                           prefs::kVideoCaptureAllowed,
                           prefs::kVideoCaptureAllowedUrls) ==
           MediaStreamDevicePolicy::ALWAYS_DENY;
  }

  return false;
}

bool ChromePermissionsClient::IsPermissionAllowedByDevicePolicy(
    content::WebContents* web_contents,
    PermissionSetting setting,
    const content_settings::SettingInfo& info,
    ContentSettingsType type) const {
  auto* permission_info =
      content_settings::PermissionSettingsRegistry::GetInstance()->Get(type);
  if (IsPermissionSetByAdministator(setting, permission_info, info) &&
      permission_info->delegate().IsAnyPermissionAllowed(setting)) {
    return true;
  }

  Profile* profile =
      Profile::FromBrowserContext(web_contents->GetBrowserContext());
  if (type == ContentSettingsType::MEDIASTREAM_MIC) {
    return GetDevicePolicy(profile, web_contents->GetLastCommittedURL(),
                           prefs::kAudioCaptureAllowed,
                           prefs::kAudioCaptureAllowedUrls) ==
           MediaStreamDevicePolicy::ALWAYS_ALLOW;
  }

  if (type == ContentSettingsType::MEDIASTREAM_CAMERA) {
    return GetDevicePolicy(profile, web_contents->GetLastCommittedURL(),
                           prefs::kVideoCaptureAllowed,
                           prefs::kVideoCaptureAllowedUrls) ==
           MediaStreamDevicePolicy::ALWAYS_ALLOW;
  }

  return false;
}

bool ChromePermissionsClient::IsSystemDenied(ContentSettingsType type) const {
  return system_permission_settings::IsDenied(type);
}

bool ChromePermissionsClient::CanPromptSystemPermission(
    ContentSettingsType type) const {
  return system_permission_settings::CanPrompt(type);
}

bool ChromePermissionsClient::IsActorOperatingOnWebContents(
    content::WebContents* web_contents) const {
  auto* actor_service =
      actor::ActorKeyedService::Get(web_contents->GetBrowserContext());
  if (!actor_service) {
    return false;
  }

  const auto* tab_interface =
      tabs::TabInterface::MaybeGetFromContents(web_contents);
  return tab_interface && actor_service->IsActiveOnTab(*tab_interface);
}

url::Origin ChromePermissionsClient::GetEmbeddingOrigin(
    content::WebContents* web_contents) {
  return web_contents->GetPrimaryMainFrame()->GetLastCommittedOrigin();
}

url::Origin ChromePermissionsClient::GetGoogleURLOrigin() {
  return url::Origin::Create(
      GURL(UIThreadSearchTermsData().GoogleBaseURLValue()));
}
