// Copyright 2012 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/ui/content_settings/content_setting_bubble_model.h"

#include <stddef.h>

#include <memory>
#include <string>
#include <utility>

#include "base/auto_reset.h"
#include "base/command_line.h"
#include "base/feature_list.h"
#include "base/functional/bind.h"
#include "base/lazy_instance.h"
#include "base/metrics/histogram_functions.h"
#include "base/metrics/user_metrics.h"
#include "base/metrics/user_metrics_action.h"
#include "base/notreached.h"
#include "base/scoped_observation.h"
#include "base/strings/utf_string_conversions.h"
#include "build/build_config.h"
#include "chrome/browser/browser_process.h"
#include "chrome/browser/content_settings/chrome_content_settings_utils.h"
#include "chrome/browser/content_settings/cookie_settings_factory.h"
#include "chrome/browser/content_settings/host_content_settings_map_factory.h"
#include "chrome/browser/content_settings/mixed_content_settings_tab_helper.h"
#include "chrome/browser/content_settings/page_specific_content_settings_delegate.h"
#include "chrome/browser/custom_handlers/protocol_handler_registry_factory.h"
#include "chrome/browser/download/download_request_limiter.h"
#include "chrome/browser/infobars/infobar_features.h"
#include "chrome/browser/media/webrtc/media_capture_devices_dispatcher.h"
#include "chrome/browser/media/webrtc/permission_bubble_media_access_handler.h"
#include "chrome/browser/permissions/permission_actions_history_factory.h"
#include "chrome/browser/permissions/permission_decision_auto_blocker_factory.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/ui/collected_cookies_infobar_delegate.h"
#include "chrome/browser/ui/content_settings/content_setting_bubble_model_delegate.h"
#include "chrome/browser/ui/ui_features.h"
#include "chrome/browser/ui/url_identity.h"
#include "chrome/browser/ui/views/site_data/page_specific_site_data_dialog_controller.h"
#include "chrome/common/chrome_switches.h"
#include "chrome/grit/generated_resources.h"
#include "chrome/grit/theme_resources.h"
#include "components/blocked_content/popup_blocker_tab_helper.h"
#include "components/content_settings/browser/page_specific_content_settings.h"
#include "components/content_settings/common/content_settings_agent.mojom.h"
#include "components/content_settings/core/browser/content_settings_utils.h"
#include "components/content_settings/core/browser/cookie_settings.h"
#include "components/content_settings/core/common/content_settings.h"
#include "components/content_settings/core/common/content_settings_constraints.h"
#include "components/content_settings/core/common/content_settings_types.h"
#include "components/content_settings/core/common/content_settings_utils.h"
#include "components/custom_handlers/protocol_handler_registry.h"
#include "components/infobars/content/content_infobar_manager.h"
#include "components/permissions/constants.h"
#include "components/permissions/features.h"
#include "components/permissions/permission_decision_auto_blocker.h"
#include "components/permissions/permission_request.h"
#include "components/permissions/permission_request_manager.h"
#include "components/permissions/permission_uma_util.h"
#include "components/permissions/permission_util.h"
#include "components/permissions/permissions_client.h"
#include "components/strings/grit/components_strings.h"
#include "components/subresource_filter/content/browser/content_subresource_filter_throttle_manager.h"
#include "components/subresource_filter/content/browser/content_subresource_filter_web_contents_helper.h"
#include "components/subresource_filter/content/browser/subresource_filter_content_settings_manager.h"
#include "components/subresource_filter/core/browser/subresource_filter_constants.h"
#include "components/subresource_filter/core/browser/subresource_filter_features.h"
#include "components/tabs/public/tab_interface.h"
#include "components/url_formatter/elide_url.h"
#include "components/url_formatter/url_formatter.h"
#include "components/vector_icons/vector_icons.h"
#include "content/public/browser/page.h"
#include "content/public/browser/permission_controller.h"
#include "content/public/browser/permission_descriptor_util.h"
#include "content/public/browser/render_process_host.h"
#include "content/public/browser/render_view_host.h"
#include "content/public/browser/weak_document_ptr.h"
#include "content/public/browser/web_contents.h"
#include "content/public/browser/web_contents_delegate.h"
#include "mojo/public/cpp/bindings/associated_remote.h"
#include "services/device/public/cpp/device_features.h"
#include "services/network/public/cpp/is_potentially_trustworthy.h"
#include "third_party/blink/public/common/associated_interfaces/associated_interface_provider.h"
#include "third_party/blink/public/common/features.h"
#include "third_party/blink/public/common/loader/network_utils.h"
#include "third_party/blink/public/common/permissions/permission_utils.h"
#include "ui/base/l10n/l10n_util.h"
#include "ui/base/resource/resource_bundle.h"
#include "ui/base/ui_base_features.h"
#include "ui/base/window_open_disposition.h"
#include "ui/base/window_open_disposition_utils.h"
#include "ui/events/event.h"
#include "ui/gfx/vector_icon_types.h"
#include "ui/resources/grit/ui_resources.h"

#if BUILDFLAG(IS_MAC)
#include "base/apple/bundle_locations.h"
#include "base/mac/mac_util.h"
#include "chrome/browser/permissions/system/system_media_capture_permissions_mac.h"
#include "chrome/browser/web_applications/os_integration/mac/web_app_shortcut_mac.h"
#include "chrome/browser/web_applications/web_app_tab_helper.h"
#endif

using base::UserMetricsAction;
using content::WebContents;
using content_settings::PageSpecificContentSettings;
using content_settings::SettingInfo;
using content_settings::SettingSource;
using content_settings::mojom::SessionModel;

namespace {
using ContentSettingBubbleAction =
    ContentSettingBubbleModel::ContentSettingBubbleAction;

void RecordActionHistogram(ContentSettingsType type,
                           ContentSettingBubbleAction action) {
  switch (type) {
    case ContentSettingsType::STORAGE_ACCESS:
      base::UmaHistogramEnumeration(
          "ContentSettings.Bubble.StorageAccess.Action", action);
      break;
    default:
      // Currently only defined and implemented for StorageAccess.
      NOTREACHED();
  }
}

using QuietUiReason = permissions::PermissionRequestManager::QuietUiReason;

const std::u16string& GetDefaultDisplayURLForTesting() {
  static const base::NoDestructor<std::u16string> kDefaultDisplayURL(
      u"http://www.example.com");
  return *kDefaultDisplayURL;
}

// An override display URL in content setting bubble UI for testing.
std::optional<bool> g_display_url_override_for_testing;

// Returns a boolean indicating whether the setting should be managed by the
// user (i.e. it is not controlled by policy). Also takes a (nullable) out-param
// which is populated by the actual setting for the given URL.
bool GetSettingManagedByUser(const GURL& url,
                             ContentSettingsType type,
                             Profile* profile,
                             ContentSetting* out_setting) {
  HostContentSettingsMap* map =
      HostContentSettingsMapFactory::GetForProfile(profile);
  SettingInfo info;
  ContentSetting setting;
  if (type == ContentSettingsType::COOKIES) {
    // TODO(crbug.com/40247160): Consider whether the following check should
    // somehow determine real CookieSettingOverrides rather than default to
    // none.
    setting = CookieSettingsFactory::GetForProfile(profile)->GetCookieSetting(
        url, net::SiteForCookies::FromUrl(url), url,
        net::CookieSettingOverrides(), &info);
  } else {
    setting = map->GetContentSetting(url, url, type, &info);
  }

  if (out_setting) {
    *out_setting = setting;
  }

  // Prevent creation of content settings for illegal urls like about:blank by
  // disallowing user management.
  return info.source == SettingSource::kUser &&
         map->CanSetNarrowestContentSetting(url, url, type);
}

ContentSettingBubbleModel::ListItem CreateUrlListItem(int32_t id,
                                                      const GURL& url) {
  ContentSettingBubbleModel::ListItem item(
      nullptr, ContentSettingBubbleModel::FormatUrlWithBullet(url),
      std::u16string(), true /* has_link */, false /* has_blocked_badge */, id);
  item.url = url;
  return item;
}

struct ContentSettingsTypeIdEntry {
  ContentSettingsType type;
  int id;
};

int GetIdForContentType(base::span<const ContentSettingsTypeIdEntry> entries,
                        ContentSettingsType type) {
  if (auto found =
          std::ranges::find(entries, type, &ContentSettingsTypeIdEntry::type);
      found != entries.end()) {
    return found->id;
  }
  return 0;
}

void SetAllowRunningInsecureContent(
    MixedContentSettingsTabHelper* mixed_content_settings,
    content::RenderFrameHost* frame) {
  // Set Insecure Content flag only if running insecure content is allowed
  if (mixed_content_settings &&
      !mixed_content_settings->IsRunningInsecureContentAllowed(*frame)) {
    return;
  }

  mojo::AssociatedRemote<content_settings::mojom::ContentSettingsAgent> agent;
  frame->GetRemoteAssociatedInterfaces()->GetInterface(&agent);
  agent->SetAllowRunningInsecureContent();
}

constexpr UrlIdentity::TypeSet allowed_types = {
    UrlIdentity::Type::kDefault, UrlIdentity::Type::kFile,
    UrlIdentity::Type::kIsolatedWebApp};

std::u16string GetUrlForDisplay(
    Profile* profile,
    const GURL& url,
    const UrlIdentity::FormatOptions& options = UrlIdentity::FormatOptions()) {
  if (g_display_url_override_for_testing.value_or(false)) {
    return GetDefaultDisplayURLForTesting();  // IN-TEST
  }
  UrlIdentity identity =
      UrlIdentity::CreateFromUrl(profile, url, allowed_types, options);
  return identity.name;
}

// Permissions eligible for auto-revocation by Safety Hub need to track
// `last_visited` timestamp for the site. Automation uses it to decide if the
// site is considered "unused". Unused site permissions are then auto-revoked.
content_settings::ContentSettingConstraints CreateConstraintsForAutoRevocation(
    ContentSettingsType content_type,
    ContentSetting setting) {
  content_settings::ContentSettingConstraints constraints;

  if (base::FeatureList::IsEnabled(
          permissions::features::
              kSafetyHubUnusedPermissionRevocationForAllSurfaces) &&
      content_settings::CanBeAutoRevokedAsUnusedPermission(content_type,
                                                           setting)) {
    constraints.set_track_last_visit_for_autoexpiration(true);
  }
  return constraints;
}

}  // namespace

// static
base::AutoReset<std::optional<bool>>
ContentSettingBubbleModel::CreateScopedDisplayURLOverrideForTesting() {
  return base::AutoReset<std::optional<bool>>(
      &g_display_url_override_for_testing, true);
}

// static
std::u16string ContentSettingBubbleModel::FormatTitleWithBullet(
    const std::u16string& title) {
  return l10n_util::GetStringFUTF16(IDS_LIST_BULLET, title);
}

// static
std::u16string ContentSettingBubbleModel::FormatUrlWithBullet(const GURL& url) {
  std::u16string title =
      url.spec().empty()
          ? l10n_util::GetStringUTF16(IDS_TAB_LOADING_TITLE)
          : url_formatter::FormatUrl(
                url, url_formatter::kFormatUrlOmitUsernamePassword,
                base::UnescapeRule::NONE, nullptr, nullptr, nullptr);
  return FormatTitleWithBullet(title);
}

// ContentSettingSimpleBubbleModel ---------------------------------------------
ContentSettingBubbleModel::ListItem::ListItem(const gfx::VectorIcon* image,
                                              const std::u16string& title,
                                              const std::u16string& description,
                                              bool has_link,
                                              bool has_blocked_badge,
                                              int32_t item_id)
    : image(image),
      title(title),
      description(description),
      has_link(has_link),
      has_blocked_badge(has_blocked_badge),
      item_id(item_id) {}

ContentSettingBubbleModel::ListItem::ListItem(const ListItem& other) = default;
ContentSettingBubbleModel::ListItem&
ContentSettingBubbleModel::ListItem::operator=(const ListItem& other) = default;

ContentSettingSimpleBubbleModel::ContentSettingSimpleBubbleModel(
    Delegate* delegate,
    content::Page& page,
    ContentSettingsType content_type)
    : ContentSettingBubbleModel(delegate, page), content_type_(content_type) {
  SetTitle();
  SetMessage();
  SetManageText();
  SetCustomLink();
}

ContentSettingSimpleBubbleModel*
ContentSettingSimpleBubbleModel::AsSimpleBubbleModel() {
  return this;
}

bool ContentSettingSimpleBubbleModel::IsContentAllowed() {
  PageSpecificContentSettings* content_settings =
      PageSpecificContentSettings::GetForPage(GetPage());

  if (content_type() == ContentSettingsType::COOKIES) {
    ContentSetting setting;
    GetSettingManagedByUser(GetPage().GetMainDocument().GetLastCommittedURL(),
                            content_type(), GetProfile(), &setting);
    // We check the content setting here as well because 3PC access influences
    // the allowed/blocked status even though the icon is meant for 1PC control.
    return content_settings->IsContentAllowed(content_type()) &&
           setting == CONTENT_SETTING_ALLOW;
  }
  return content_settings->IsContentAllowed(content_type()) &&
         !content_settings->IsContentBlocked(content_type());
}

void ContentSettingSimpleBubbleModel::SetTitle() {
  static const ContentSettingsTypeIdEntry kBlockedTitleIDs[] = {
      {ContentSettingsType::COOKIES, IDS_BLOCKED_ON_DEVICE_SITE_DATA_TITLE},
      {ContentSettingsType::IMAGES, IDS_BLOCKED_IMAGES_TITLE},
      {ContentSettingsType::JAVASCRIPT, IDS_BLOCKED_JAVASCRIPT_TITLE},
      {ContentSettingsType::MIXEDSCRIPT,
       IDS_BLOCKED_DISPLAYING_INSECURE_CONTENT_TITLE},
      {ContentSettingsType::SOUND, IDS_BLOCKED_SOUND_TITLE},
      {ContentSettingsType::CLIPBOARD_READ_WRITE, IDS_BLOCKED_CLIPBOARD_TITLE},
      {ContentSettingsType::GEOLOCATION, IDS_BLOCKED_GEOLOCATION_TITLE},
      {ContentSettingsType::MIDI_SYSEX, IDS_BLOCKED_MIDI_SYSEX_TITLE},
      {ContentSettingsType::SENSORS, IDS_BLOCKED_SENSORS_TITLE},
#if BUILDFLAG(IS_WIN)
      {ContentSettingsType::PROTECTED_MEDIA_IDENTIFIER,
       IDS_BLOCKED_PROTECTED_CONTENT_IDENTIFIERS_TITLE},
#endif
  };
  // Fields as for kBlockedTitleIDs, above.
  static const ContentSettingsTypeIdEntry kAccessedTitleIDs[] = {
      {ContentSettingsType::COOKIES, IDS_ACCESSED_ON_DEVICE_SITE_DATA_TITLE},
      {ContentSettingsType::CLIPBOARD_READ_WRITE, IDS_ALLOWED_CLIPBOARD_TITLE},
      {ContentSettingsType::GEOLOCATION, IDS_ALLOWED_GEOLOCATION_TITLE},
      {ContentSettingsType::MIDI_SYSEX, IDS_ALLOWED_MIDI_SYSEX_TITLE},
      {ContentSettingsType::SENSORS, IDS_ALLOWED_SENSORS_TITLE},
#if BUILDFLAG(IS_WIN)
      {ContentSettingsType::PROTECTED_MEDIA_IDENTIFIER,
       IDS_ALLOWED_PROTECTED_CONTENT_IDENTIFIERS_TITLE},
#endif
  };

  int title_id = [&]() {
#if BUILDFLAG(IS_CHROMEOS)
    if (content_type() == ContentSettingsType::SMART_CARD_GUARD) {
      return IDS_ACCESSED_SMART_CARD_READER_TITLE;
    }
#endif
    if (IsContentAllowed()) {
      return GetIdForContentType(kAccessedTitleIDs, content_type());
    }
    return GetIdForContentType(kBlockedTitleIDs, content_type());
  }();
  if (title_id) {
    set_title(l10n_util::GetStringUTF16(title_id));
  }
}

void ContentSettingSimpleBubbleModel::SetMessage() {
  // TODO(crbug.com/40633805): Make the two arrays below static again once
  // we no longer need to check base::FeatureList.
  const ContentSettingsTypeIdEntry kBlockedMessageIDs[] = {
      {ContentSettingsType::COOKIES, IDS_BLOCKED_ON_DEVICE_SITE_DATA_MESSAGE},
      {ContentSettingsType::IMAGES, IDS_BLOCKED_IMAGES_MESSAGE},
      {ContentSettingsType::JAVASCRIPT, IDS_BLOCKED_JAVASCRIPT_MESSAGE},
      // {ContentSettingsType::POPUPS, No message. intentionally left out},
      {ContentSettingsType::MIXEDSCRIPT,
       IDS_BLOCKED_DISPLAYING_INSECURE_CONTENT},
      {ContentSettingsType::GEOLOCATION, IDS_BLOCKED_GEOLOCATION_MESSAGE},
      {ContentSettingsType::MIDI_SYSEX, IDS_BLOCKED_MIDI_SYSEX_MESSAGE},
      {ContentSettingsType::CLIPBOARD_READ_WRITE,
       IDS_BLOCKED_CLIPBOARD_MESSAGE},
      {ContentSettingsType::SENSORS,
       base::FeatureList::IsEnabled(features::kGenericSensorExtraClasses)
           ? IDS_BLOCKED_SENSORS_MESSAGE
           : IDS_BLOCKED_MOTION_SENSORS_MESSAGE},
#if BUILDFLAG(IS_WIN)
      {ContentSettingsType::PROTECTED_MEDIA_IDENTIFIER,
       IDS_BLOCKED_PROTECTED_CONTENT_IDENTIFIERS_MESSAGE},
#endif
  };
  // Fields as for kBlockedMessageIDs, above.
  const ContentSettingsTypeIdEntry kAccessedMessageIDs[] = {
      {ContentSettingsType::COOKIES, IDS_ACCESSED_ON_DEVICE_SITE_DATA_MESSAGE},
      {ContentSettingsType::GEOLOCATION, IDS_ALLOWED_GEOLOCATION_MESSAGE},
      {ContentSettingsType::MIDI_SYSEX, IDS_ALLOWED_MIDI_SYSEX_MESSAGE},
      {ContentSettingsType::CLIPBOARD_READ_WRITE,
       IDS_ALLOWED_CLIPBOARD_MESSAGE},
      {ContentSettingsType::SENSORS,
       base::FeatureList::IsEnabled(features::kGenericSensorExtraClasses)
           ? IDS_ALLOWED_SENSORS_MESSAGE
           : IDS_ALLOWED_MOTION_SENSORS_MESSAGE},
#if BUILDFLAG(IS_WIN)
      {ContentSettingsType::PROTECTED_MEDIA_IDENTIFIER,
       IDS_ALLOWED_PROTECTED_CONTENT_IDENTIFIERS_MESSAGE},
#endif
  };

  int message_id = [&]() {
#if BUILDFLAG(IS_CHROMEOS)
    if (content_type() == ContentSettingsType::SMART_CARD_GUARD) {
      return IDS_ACCESSED_SMART_CARD_READER_BODY;
    }
#endif
    if (IsContentAllowed()) {
      return GetIdForContentType(kAccessedMessageIDs, content_type());
    }
    return GetIdForContentType(kBlockedMessageIDs, content_type());
  }();
  if (message_id) {
    set_message(l10n_util::GetStringUTF16(message_id));
  }
}

void ContentSettingSimpleBubbleModel::SetManageText() {
  set_manage_text(l10n_util::GetStringUTF16(IDS_MANAGE));
}

void ContentSettingSimpleBubbleModel::OnManageButtonClicked() {
  if (delegate()) {
    delegate()->ShowContentSettingsPage(content_type());
  }

  if (content_type() == ContentSettingsType::POPUPS) {
    content_settings::RecordPopupsAction(
        content_settings::POPUPS_ACTION_CLICKED_MANAGE_POPUPS_BLOCKING);
  }
}

void ContentSettingSimpleBubbleModel::SetCustomLink() {
  static const ContentSettingsTypeIdEntry kCustomIDs[] = {
      {ContentSettingsType::MIXEDSCRIPT, IDS_ALLOW_INSECURE_CONTENT_BUTTON},
  };
  int custom_link_id = GetIdForContentType(kCustomIDs, content_type());
  if (custom_link_id) {
    set_custom_link(l10n_util::GetStringUTF16(custom_link_id));
  }
}

void ContentSettingSimpleBubbleModel::OnCustomLinkClicked() {}

// ContentSettingMixedScriptBubbleModel ----------------------------------------

class ContentSettingMixedScriptBubbleModel
    : public ContentSettingSimpleBubbleModel {
 public:
  ContentSettingMixedScriptBubbleModel(Delegate* delegate, content::Page& page);

  ContentSettingMixedScriptBubbleModel(
      const ContentSettingMixedScriptBubbleModel&) = delete;
  ContentSettingMixedScriptBubbleModel& operator=(
      const ContentSettingMixedScriptBubbleModel&) = delete;

  ~ContentSettingMixedScriptBubbleModel() override = default;

 private:
  void SetManageText();

  // ContentSettingBubbleModel:
  void OnLearnMoreClicked() override;
  void OnCustomLinkClicked() override;
};

ContentSettingMixedScriptBubbleModel::ContentSettingMixedScriptBubbleModel(
    Delegate* delegate,
    content::Page& page)
    : ContentSettingSimpleBubbleModel(delegate,
                                      page,
                                      ContentSettingsType::MIXEDSCRIPT) {
  set_custom_link_enabled(true);
  set_show_learn_more(true);
  SetManageText();
}

void ContentSettingMixedScriptBubbleModel::OnLearnMoreClicked() {
  if (delegate()) {
    delegate()->ShowLearnMorePage(content_type());
  }
}

void ContentSettingMixedScriptBubbleModel::OnCustomLinkClicked() {
  MixedContentSettingsTabHelper* mixed_content_settings =
      MixedContentSettingsTabHelper::FromWebContents(web_contents());
  if (mixed_content_settings) {
    // Update browser side settings to allow active mixed content.
    mixed_content_settings->AllowRunningOfInsecureContent(
        GetPage().GetMainDocument());
  }

  // Update renderer side settings to allow active mixed content.
  GetPage().GetMainDocument().ForEachRenderFrameHostWithAction(
      [mixed_content_settings](content::RenderFrameHost* frame) {
        // Stop the child frame enumeration if we have reached a fenced frame.
        // This is correct since fence frames should ignore InsecureContent
        // setting.
        if (frame->IsFencedFrameRoot()) {
          return content::RenderFrameHost::FrameIterationAction::kSkipChildren;
        }
        SetAllowRunningInsecureContent(mixed_content_settings, frame);
        return content::RenderFrameHost::FrameIterationAction::kContinue;
      });
}

// Don't set any manage text since none is displayed.
void ContentSettingMixedScriptBubbleModel::SetManageText() {
  set_manage_text_style(ContentSettingBubbleModel::ManageTextStyle::kNone);
}

// ContentSettingRPHBubbleModel ------------------------------------------------

namespace {

// These states must match the order of appearance of the radio buttons
// in the XIB file for the Mac port.
enum RPHState {
  RPH_ALLOW = 0,
  RPH_BLOCK,
  RPH_IGNORE,
};

}  // namespace

ContentSettingRPHBubbleModel::ContentSettingRPHBubbleModel(
    Delegate* delegate,
    content::Page& page,
    custom_handlers::ProtocolHandlerRegistry* registry)
    : ContentSettingSimpleBubbleModel(delegate,
                                      page,
                                      ContentSettingsType::PROTOCOL_HANDLERS),
      registry_(registry),
      pending_handler_(
          custom_handlers::ProtocolHandler::EmptyProtocolHandler()),
      previous_handler_(
          custom_handlers::ProtocolHandler::EmptyProtocolHandler()) {
  auto* content_settings =
      PageSpecificContentSettingsDelegate::FromWebContents(web_contents());
  pending_handler_ = content_settings->pending_protocol_handler();
  previous_handler_ = content_settings->previous_protocol_handler();

  std::u16string protocol = pending_handler_.GetProtocolDisplayName();

  // Note that we ignore the |title| parameter.
  if (previous_handler_.IsEmpty()) {
    set_title(l10n_util::GetStringFUTF16(
        IDS_REGISTER_PROTOCOL_HANDLER_CONFIRM,
        base::UTF8ToUTF16(pending_handler_.url().GetHost()), protocol));
  } else {
    set_title(l10n_util::GetStringFUTF16(
        IDS_REGISTER_PROTOCOL_HANDLER_CONFIRM_REPLACE,
        base::UTF8ToUTF16(pending_handler_.url().GetHost()), protocol,
        base::UTF8ToUTF16(previous_handler_.url().GetHost())));
  }

  std::u16string radio_allow_label =
      l10n_util::GetStringUTF16(IDS_REGISTER_PROTOCOL_HANDLER_ACCEPT);
  std::u16string radio_deny_label =
      l10n_util::GetStringUTF16(IDS_REGISTER_PROTOCOL_HANDLER_DENY);
  std::u16string radio_ignore_label =
      l10n_util::GetStringUTF16(IDS_REGISTER_PROTOCOL_HANDLER_IGNORE);

  const GURL& url = GetPage().GetMainDocument().GetLastCommittedURL();
  RadioGroup radio_group;
  radio_group.url = url;

  radio_group.radio_items = {radio_allow_label, radio_deny_label,
                             radio_ignore_label};
  ContentSetting setting = content_settings->pending_protocol_handler_setting();
  if (setting == CONTENT_SETTING_ALLOW) {
    radio_group.default_item = RPH_ALLOW;
  } else if (setting == CONTENT_SETTING_BLOCK) {
    radio_group.default_item = RPH_BLOCK;
  } else {
    radio_group.default_item = RPH_IGNORE;
  }

  set_radio_group(radio_group);
}

ContentSettingRPHBubbleModel::~ContentSettingRPHBubbleModel() = default;

void ContentSettingRPHBubbleModel::CommitChanges() {
  PerformActionForSelectedItem();

  // The user has one chance to deal with the RPH content setting UI,
  // then we remove it.
  PageSpecificContentSettingsDelegate::FromWebContents(web_contents())
      ->ClearPendingProtocolHandler();
  content_settings::UpdateLocationBarUiForWebContents(web_contents());
}

void ContentSettingRPHBubbleModel::RegisterProtocolHandler() {
  // A no-op if the handler hasn't been ignored, but needed in case the user
  // selects sequences like register/ignore/register.
  registry_->RemoveIgnoredHandler(pending_handler_);

  registry_->OnAcceptRegisterProtocolHandler(pending_handler_);
  PageSpecificContentSettingsDelegate::FromWebContents(web_contents())
      ->set_pending_protocol_handler_setting(CONTENT_SETTING_ALLOW);
}

void ContentSettingRPHBubbleModel::UnregisterProtocolHandler() {
  registry_->OnDenyRegisterProtocolHandler(pending_handler_);
  PageSpecificContentSettingsDelegate::FromWebContents(web_contents())
      ->set_pending_protocol_handler_setting(CONTENT_SETTING_BLOCK);
  ClearOrSetPreviousHandler();
}

void ContentSettingRPHBubbleModel::IgnoreProtocolHandler() {
  registry_->OnIgnoreRegisterProtocolHandler(pending_handler_);
  PageSpecificContentSettingsDelegate::FromWebContents(web_contents())
      ->set_pending_protocol_handler_setting(CONTENT_SETTING_DEFAULT);
  ClearOrSetPreviousHandler();
}

void ContentSettingRPHBubbleModel::ClearOrSetPreviousHandler() {
  if (previous_handler_.IsEmpty()) {
    registry_->ClearDefault(pending_handler_.protocol());
  } else {
    registry_->OnAcceptRegisterProtocolHandler(previous_handler_);
  }
}

void ContentSettingRPHBubbleModel::PerformActionForSelectedItem() {
  if (selected_item() == RPH_ALLOW) {
    RegisterProtocolHandler();
  } else if (selected_item() == RPH_BLOCK) {
    UnregisterProtocolHandler();
  } else if (selected_item() == RPH_IGNORE) {
    IgnoreProtocolHandler();
  } else {
    NOTREACHED();
  }
}

// ContentSettingSingleRadioGroup ----------------------------------------------

ContentSettingSingleRadioGroup::ContentSettingSingleRadioGroup(
    Delegate* delegate,
    content::Page& page,
    ContentSettingsType content_type)
    : ContentSettingSimpleBubbleModel(delegate, page, content_type),
      block_setting_(CONTENT_SETTING_BLOCK) {
  SetRadioGroup();
  set_is_user_modifiable(
      GetSettingManagedByUser(GetPage().GetMainDocument().GetLastCommittedURL(),
                              content_type, GetProfile(), nullptr));
}

ContentSettingSingleRadioGroup::~ContentSettingSingleRadioGroup() = default;

void ContentSettingSingleRadioGroup::CommitChanges() {
  if (settings_changed()) {
    ContentSetting setting = selected_item() == kAllowButtonIndex
                                 ? CONTENT_SETTING_ALLOW
                                 : block_setting_;
    permissions::PermissionUmaUtil::ScopedRevocationReporter
        scoped_revocation_reporter(
            GetProfile(), bubble_content().radio_group.url, GURL(),
            content_type(), permissions::PermissionSourceUI::PAGE_ACTION);
    SetNarrowestContentSetting(setting);
  }
}

bool ContentSettingSingleRadioGroup::settings_changed() const {
  return selected_item() != bubble_content().radio_group.default_item;
}

// Initialize the radio group by setting the appropriate labels for the
// content type and setting the default value based on the content setting.
void ContentSettingSingleRadioGroup::SetRadioGroup() {
  const GURL& url = GetPage().GetMainDocument().GetLastCommittedURL();
  const std::u16string& display_url = GetUrlForDisplay(GetProfile(), url);
  bool allowed = IsContentAllowed();

  // For the frame busting case the content is blocked but its content type is
  // popup, and the popup PageSpecificContentSettings is unaware of the frame
  // busting block. Since the popup bubble won't happen without blocking, it's
  // safe to manually set this.
  if (content_type() == ContentSettingsType::POPUPS) {
    allowed = false;
  }

  DCHECK(!allowed ||
         PageSpecificContentSettings::GetForPage(GetPage())->IsContentAllowed(
             content_type()));

  RadioGroup radio_group;
  radio_group.url = url;

  static const ContentSettingsTypeIdEntry kBlockedAllowIDs[] = {
      {ContentSettingsType::COOKIES, IDS_BLOCKED_ON_DEVICE_SITE_DATA_UNBLOCK},
      {ContentSettingsType::IMAGES, IDS_BLOCKED_IMAGES_UNBLOCK},
      {ContentSettingsType::JAVASCRIPT, IDS_BLOCKED_JAVASCRIPT_UNBLOCK},
      {ContentSettingsType::POPUPS, IDS_BLOCKED_POPUPS_REDIRECTS_UNBLOCK},
      {ContentSettingsType::SOUND, IDS_BLOCKED_SOUND_UNBLOCK},
      {ContentSettingsType::GEOLOCATION, IDS_BLOCKED_GEOLOCATION_UNBLOCK},
      {ContentSettingsType::MIDI_SYSEX, IDS_BLOCKED_MIDI_SYSEX_UNBLOCK},
      {ContentSettingsType::CLIPBOARD_READ_WRITE,
       IDS_BLOCKED_CLIPBOARD_UNBLOCK},
      {ContentSettingsType::SENSORS, IDS_BLOCKED_SENSORS_UNBLOCK},
#if BUILDFLAG(IS_WIN)
      {ContentSettingsType::PROTECTED_MEDIA_IDENTIFIER,
       IDS_BLOCKED_PROTECTED_CONTENT_IDENTIFIERS_UNBLOCK},
#endif
  };
  // Fields as for kBlockedAllowIDs, above.
  static const ContentSettingsTypeIdEntry kAllowedAllowIDs[] = {
      {ContentSettingsType::COOKIES, IDS_ALLOWED_ON_DEVICE_SITE_DATA_NO_ACTION},
      {ContentSettingsType::GEOLOCATION, IDS_ALLOWED_GEOLOCATION_NO_ACTION},
      {ContentSettingsType::MIDI_SYSEX, IDS_ALLOWED_MIDI_SYSEX_NO_ACTION},
      {ContentSettingsType::CLIPBOARD_READ_WRITE,
       IDS_ALLOWED_CLIPBOARD_NO_ACTION},
      {ContentSettingsType::SENSORS, IDS_ALLOWED_SENSORS_NO_ACTION},
#if BUILDFLAG(IS_WIN)
      {ContentSettingsType::PROTECTED_MEDIA_IDENTIFIER,
       IDS_ALLOWED_PROTECTED_CONTENT_IDENTIFIERS_NO_ACTION},
#endif
  };

  std::u16string radio_allow_label;
  if (allowed) {
    int resource_id = GetIdForContentType(kAllowedAllowIDs, content_type());
    radio_allow_label = l10n_util::GetStringUTF16(resource_id);
  } else {
    int resource_id = GetIdForContentType(kBlockedAllowIDs, content_type());
    if (content_type() == ContentSettingsType::COOKIES) {
      radio_allow_label = l10n_util::GetStringUTF16(resource_id);
    } else {
      radio_allow_label = l10n_util::GetStringFUTF16(resource_id, display_url);
    }
  }

  static const ContentSettingsTypeIdEntry kBlockedBlockIDs[] = {
      {ContentSettingsType::COOKIES, IDS_BLOCKED_ON_DEVICE_SITE_DATA_NO_ACTION},
      {ContentSettingsType::IMAGES, IDS_BLOCKED_IMAGES_NO_ACTION},
      {ContentSettingsType::JAVASCRIPT, IDS_BLOCKED_JAVASCRIPT_NO_ACTION},
      {ContentSettingsType::POPUPS, IDS_BLOCKED_POPUPS_REDIRECTS_NO_ACTION},
      {ContentSettingsType::SOUND, IDS_BLOCKED_SOUND_NO_ACTION},
      {ContentSettingsType::GEOLOCATION, IDS_BLOCKED_GEOLOCATION_NO_ACTION},
      {ContentSettingsType::MIDI_SYSEX, IDS_BLOCKED_MIDI_SYSEX_NO_ACTION},
      {ContentSettingsType::CLIPBOARD_READ_WRITE,
       IDS_BLOCKED_CLIPBOARD_NO_ACTION},
      {ContentSettingsType::SENSORS, IDS_BLOCKED_SENSORS_NO_ACTION},
#if BUILDFLAG(IS_WIN)
      {ContentSettingsType::PROTECTED_MEDIA_IDENTIFIER,
       IDS_BLOCKED_PROTECTED_CONTENT_IDENTIFIERS_NO_ACTION},
#endif
  };
  static const ContentSettingsTypeIdEntry kAllowedBlockIDs[] = {
      {ContentSettingsType::COOKIES, IDS_ALLOWED_ON_DEVICE_SITE_DATA_BLOCK},
      {ContentSettingsType::GEOLOCATION, IDS_ALLOWED_GEOLOCATION_BLOCK},
      {ContentSettingsType::MIDI_SYSEX, IDS_ALLOWED_MIDI_SYSEX_BLOCK},
      {ContentSettingsType::CLIPBOARD_READ_WRITE, IDS_ALLOWED_CLIPBOARD_BLOCK},
      {ContentSettingsType::SENSORS, IDS_ALLOWED_SENSORS_BLOCK},
#if BUILDFLAG(IS_WIN)
      {ContentSettingsType::PROTECTED_MEDIA_IDENTIFIER,
       IDS_ALLOWED_PROTECTED_CONTENT_IDENTIFIERS_BLOCK},
#endif
  };

  std::u16string radio_block_label;
  if (allowed) {
    int resource_id = GetIdForContentType(kAllowedBlockIDs, content_type());
    if (content_type() == ContentSettingsType::COOKIES) {
      radio_block_label = l10n_util::GetStringUTF16(resource_id);
    } else {
      radio_block_label = l10n_util::GetStringFUTF16(resource_id, display_url);
    }
  } else {
    radio_block_label = l10n_util::GetStringUTF16(
        GetIdForContentType(kBlockedBlockIDs, content_type()));
  }

  radio_group.radio_items = {radio_allow_label, radio_block_label};

  ContentSetting setting;
  GetSettingManagedByUser(url, content_type(), GetProfile(), &setting);
  if (setting == CONTENT_SETTING_ALLOW) {
    radio_group.default_item = kAllowButtonIndex;
    // |block_setting_| is already set to |CONTENT_SETTING_BLOCK|.
  } else {
    radio_group.default_item = 1;
    block_setting_ = setting;
  }
  set_radio_group(radio_group);
}

void ContentSettingSingleRadioGroup::SetNarrowestContentSetting(
    ContentSetting setting) {
  if (!GetProfile()) {
    return;
  }

  auto* map = HostContentSettingsMapFactory::GetForProfile(GetProfile());
  map->SetNarrowestContentSetting(
      bubble_content().radio_group.url, bubble_content().radio_group.url,
      content_type(), setting,
      CreateConstraintsForAutoRevocation(content_type(), setting));
}

// ContentSettingStorageAccessBubbleModel --------------------------------------

ContentSettingStorageAccessBubbleModel::ContentSettingStorageAccessBubbleModel(
    Delegate* delegate,
    content::Page& page)
    : ContentSettingBubbleModel(delegate, page) {
  RecordActionHistogram(ContentSettingsType::STORAGE_ACCESS,
                        ContentSettingBubbleAction::kOpened);
  set_title(l10n_util::GetStringUTF16(IDS_SITE_SETTINGS_TYPE_STORAGE_ACCESS));

  const std::u16string& display_url = GetUrlForDisplay(
      GetProfile(), GetPage().GetMainDocument().GetLastCommittedURL(),
      UrlIdentity::FormatOptions{
          .default_options = {
              UrlIdentity::DefaultFormatOptions::kOmitCryptographicScheme}});

  // TODO(crbug.com/40064079): Consider to add subtitles to all permissions.
  set_subtitle(display_url);

  set_message(l10n_util::GetStringFUTF16(
      IDS_STORAGE_ACCESS_PERMISSION_BUBBLE_MESSAGE, display_url));

  auto* page_content_settings =
      PageSpecificContentSettings::GetForPage(GetPage());
  set_site_list(page_content_settings->GetTwoSiteRequests(
      ContentSettingsType::STORAGE_ACCESS));

  set_manage_text_style(ManageTextStyle::kHoverButton);
  set_manage_text(l10n_util::GetStringUTF16(IDS_STORAGE_ACCESS_MANAGE_TEXT));
  set_manage_tooltip(
      l10n_util::GetStringUTF16(IDS_STORAGE_ACCESS_MANAGE_TOOLTIP));
}

ContentSettingStorageAccessBubbleModel::
    ~ContentSettingStorageAccessBubbleModel() = default;

void ContentSettingStorageAccessBubbleModel::CommitChanges() {
  if (!GetProfile()) {
    return;
  }

  for (const auto& entry : changed_permissions_) {
    GURL primary = entry.first.GetURL();
    const GURL& secondary = GetPage().GetMainDocument().GetLastCommittedURL();
    ContentSetting setting =
        entry.second ? CONTENT_SETTING_ALLOW : CONTENT_SETTING_BLOCK;
    permissions::PermissionUmaUtil::ScopedRevocationReporter
        scoped_revocation_reporter(
            GetProfile(), primary, secondary,
            ContentSettingsType::STORAGE_ACCESS,
            permissions::PermissionSourceUI::PAGE_ACTION);
    auto* map = HostContentSettingsMapFactory::GetForProfile(GetProfile());
    content_settings::ContentSettingConstraints constraints;
    constraints.set_lifetime(
        permissions::kStorageAccessAPIExplicitPermissionLifetime);
    map->SetNarrowestContentSetting(primary, secondary,
                                    ContentSettingsType::STORAGE_ACCESS,
                                    setting, constraints);
  }
}

void ContentSettingStorageAccessBubbleModel::OnSiteRowClicked(
    const net::SchemefulSite& site,
    bool is_allowed) {
  RecordActionHistogram(ContentSettingsType::STORAGE_ACCESS,
                        is_allowed
                            ? ContentSettingBubbleAction::kPermissionAllowed
                            : ContentSettingBubbleAction::kPermissionBlocked);
  changed_permissions_[site] = is_allowed;
}

void ContentSettingStorageAccessBubbleModel::OnManageButtonClicked() {
  if (!delegate()) {
    return;
  }

  RecordActionHistogram(ContentSettingsType::STORAGE_ACCESS,
                        ContentSettingBubbleAction::kManageButtonClicked);
  delegate()->ShowContentSettingsPage(ContentSettingsType::STORAGE_ACCESS);
}

// ContentSettingCookiesBubbleModel --------------------------------------------

class ContentSettingCookiesBubbleModel : public ContentSettingSingleRadioGroup {
 public:
  ContentSettingCookiesBubbleModel(Delegate* delegate, content::Page& page);

  ContentSettingCookiesBubbleModel(const ContentSettingCookiesBubbleModel&) =
      delete;
  ContentSettingCookiesBubbleModel& operator=(
      const ContentSettingCookiesBubbleModel&) = delete;

  ~ContentSettingCookiesBubbleModel() override;

  // ContentSettingBubbleModel:
  void CommitChanges() override;
  void OnManageButtonClicked() override;
};

ContentSettingCookiesBubbleModel::ContentSettingCookiesBubbleModel(
    Delegate* delegate,
    content::Page& page)
    : ContentSettingSingleRadioGroup(delegate,
                                     page,
                                     ContentSettingsType::COOKIES) {}

ContentSettingCookiesBubbleModel::~ContentSettingCookiesBubbleModel() = default;

void ContentSettingCookiesBubbleModel::CommitChanges() {
  // On some plattforms e.g. MacOS X it is possible to close a tab while the
  // cookies settings bubble is open. This resets the web contents to NULL.
  if (settings_changed()) {
    if (infobars::IsInfoBarMigrated(
            infobars::InfoBarDelegate::COLLECTED_COOKIES_INFOBAR_DELEGATE)) {
      PageSpecificSiteDataDialogController::ShowCollectedCookiesInfoBar(
          web_contents());
    } else {
      CollectedCookiesInfoBarDelegate::Create(
          infobars::ContentInfoBarManager::FromWebContents(web_contents()));
    }
  }
  ContentSettingSingleRadioGroup::CommitChanges();
}

void ContentSettingCookiesBubbleModel::OnManageButtonClicked() {
  delegate()->ShowCollectedCookiesDialog(web_contents());
}

// ContentSettingPopupBubbleModel ----------------------------------------------

class ContentSettingPopupBubbleModel
    : public ContentSettingSingleRadioGroup,
      public blocked_content::UrlListManager::Observer {
 public:
  ContentSettingPopupBubbleModel(Delegate* delegate, content::Page& page);

  ContentSettingPopupBubbleModel(const ContentSettingPopupBubbleModel&) =
      delete;
  ContentSettingPopupBubbleModel& operator=(
      const ContentSettingPopupBubbleModel&) = delete;

  ~ContentSettingPopupBubbleModel() override;

  // ContentSettingBubbleModel:
  void CommitChanges() override;

  // PopupBlockerTabHelper::Observer:
  void BlockedUrlAdded(int32_t id, const GURL& url) override;

 private:
  void OnListItemClicked(int index, const ui::Event& event) override;

  int32_t item_id_from_item_index(int index) const {
    return bubble_content().list_items[index].item_id;
  }

  base::ScopedObservation<blocked_content::UrlListManager,
                          blocked_content::UrlListManager::Observer>
      url_list_observation_{this};
};

ContentSettingPopupBubbleModel::ContentSettingPopupBubbleModel(
    Delegate* delegate,
    content::Page& page)
    : ContentSettingSingleRadioGroup(delegate,
                                     page,
                                     ContentSettingsType::POPUPS) {
  set_title(l10n_util::GetStringUTF16(IDS_BLOCKED_POPUPS_TITLE));

  // Build blocked popup list.
  auto* helper =
      blocked_content::PopupBlockerTabHelper::FromWebContents(web_contents());
  std::map<int32_t, GURL> blocked_popups = helper->GetBlockedPopupRequests();
  for (const auto& blocked_popup : blocked_popups) {
    AddListItem(CreateUrlListItem(blocked_popup.first, blocked_popup.second));
  }

  url_list_observation_.Observe(helper->manager());
  content_settings::RecordPopupsAction(
      content_settings::POPUPS_ACTION_DISPLAYED_BUBBLE);
}

void ContentSettingPopupBubbleModel::BlockedUrlAdded(int32_t id,
                                                     const GURL& url) {
  AddListItem(CreateUrlListItem(id, url));
}

void ContentSettingPopupBubbleModel::OnListItemClicked(int index,
                                                       const ui::Event& event) {
  auto* helper =
      blocked_content::PopupBlockerTabHelper::FromWebContents(web_contents());
  helper->ShowBlockedPopup(item_id_from_item_index(index),
                           ui::DispositionFromEventFlags(event.flags()));
  RemoveListItem(index);
  content_settings::RecordPopupsAction(
      content_settings::POPUPS_ACTION_CLICKED_LIST_ITEM_CLICKED);
}

void ContentSettingPopupBubbleModel::CommitChanges() {
  // User selected to always allow pop-ups from.
  if (settings_changed() && selected_item() == kAllowButtonIndex) {
    // Increases the counter.
    content_settings::RecordPopupsAction(
        content_settings::POPUPS_ACTION_SELECTED_ALWAYS_ALLOW_POPUPS_FROM);
  }
  ContentSettingSingleRadioGroup::CommitChanges();
}

ContentSettingPopupBubbleModel::~ContentSettingPopupBubbleModel() = default;

// ContentSettingMediaStreamBubbleModel ----------------------------------------

ContentSettingMediaStreamBubbleModel::ContentSettingMediaStreamBubbleModel(
    Delegate* delegate,
    content::Page& page)
    : ContentSettingBubbleModel(delegate, page) {
  // TODO(msramek): The media bubble has three states - mic only, camera only,
  // and both. There is a lot of duplicated code which does the same thing
  // for camera and microphone separately. Consider refactoring it to avoid
  // duplication.

  // Initialize the content settings associated with the individual radio
  // buttons.
  radio_item_setting_[0] = CONTENT_SETTING_ASK;
  radio_item_setting_[1] = CONTENT_SETTING_BLOCK;

  PageSpecificContentSettings* content_settings =
      PageSpecificContentSettings::GetForPage(GetPage());
  state_ = content_settings->GetMicrophoneCameraState();
  CHECK(CameraAccessed() || MicrophoneAccessed());

  if (CameraAccessed()) {
    content_settings->OnActivityIndicatorBubbleOpened(
        ContentSettingsType::MEDIASTREAM_CAMERA);
  }

  if (MicrophoneAccessed()) {
    content_settings->OnActivityIndicatorBubbleOpened(
        ContentSettingsType::MEDIASTREAM_MIC);
  }

  if (content_settings->media_stream_access_origin().is_empty()) {
    return;
  }

  // If the permission is turned off in MacOS system preferences, overwrite
  // the bubble to enable the user to trigger the system dialog.
  if (ShouldShowSystemMediaPermissions()) {
#if BUILDFLAG(IS_MAC)
    InitializeSystemMediaPermissionBubble();
    return;
#endif  // BUILDFLAG(IS_MAC)
  }

  SetTitle();
  SetMessage();
  SetRadioGroup();
  SetManageText();
  SetCustomLink();
  SetIsUserModifiable();
}

ContentSettingMediaStreamBubbleModel::~ContentSettingMediaStreamBubbleModel() =
    default;

void ContentSettingMediaStreamBubbleModel::CommitChanges() {
  PageSpecificContentSettings* content_settings =
      PageSpecificContentSettings::GetForPage(GetPage());

  if (CameraAccessed()) {
    content_settings->OnActivityIndicatorBubbleClosed(
        ContentSettingsType::MEDIASTREAM_CAMERA);
  }

  if (MicrophoneAccessed()) {
    content_settings->OnActivityIndicatorBubbleClosed(
        ContentSettingsType::MEDIASTREAM_MIC);
  }

  // No need for radio group in the bubble UI shown when permission is blocked
  // on a system level.
  if (!ShouldShowSystemMediaPermissions()) {
    // Update the media settings if the radio button selection was changed.
    if (selected_item() != bubble_content().radio_group.default_item) {
      UpdateSettings(radio_item_setting_[selected_item()]);
    }
  }
}

ContentSettingMediaStreamBubbleModel*
ContentSettingMediaStreamBubbleModel::AsMediaStreamBubbleModel() {
  return this;
}

void ContentSettingMediaStreamBubbleModel::OnManageButtonClicked() {
  CHECK(CameraAccessed() || MicrophoneAccessed());
  if (!delegate()) {
    return;
  }

  CommitChanges();

  if (MicrophoneAccessed() && CameraAccessed()) {
    delegate()->ShowMediaSettingsPage();
  } else {
    delegate()->ShowContentSettingsPage(
        CameraAccessed() ? ContentSettingsType::MEDIASTREAM_CAMERA
                         : ContentSettingsType::MEDIASTREAM_MIC);
  }
}

void ContentSettingMediaStreamBubbleModel::OnDoneButtonClicked() {
  if (ShouldShowSystemMediaPermissions()) {
#if BUILDFLAG(IS_MAC)
    DCHECK(CameraAccessed() || MicrophoneAccessed());

    base::RecordAction(UserMetricsAction("Media.OpenPreferencesClicked"));
    DCHECK(ShouldShowSystemMediaPermissions());

    if (CameraAccessed()) {
      base::mac::OpenSystemSettingsPane(
          base::mac::SystemSettingsPane::kPrivacySecurity_Camera);
    } else if (MicrophoneAccessed()) {
      base::mac::OpenSystemSettingsPane(
          base::mac::SystemSettingsPane::kPrivacySecurity_Microphone);
    }
    return;
#endif  // BUILDFLAG(IS_MAC)
  }
}

bool ContentSettingMediaStreamBubbleModel::MicrophoneAccessed() const {
  return state_.Has(PageSpecificContentSettings::kMicrophoneAccessed);
}

bool ContentSettingMediaStreamBubbleModel::CameraAccessed() const {
  return state_.Has(PageSpecificContentSettings::kCameraAccessed);
}

bool ContentSettingMediaStreamBubbleModel::MicrophoneBlocked() const {
  return state_.Has(PageSpecificContentSettings::kMicrophoneBlocked);
}

bool ContentSettingMediaStreamBubbleModel::CameraBlocked() const {
  return state_.Has(PageSpecificContentSettings::kCameraBlocked);
}

void ContentSettingMediaStreamBubbleModel::SetIsUserModifiable() {
  CHECK(CameraAccessed() || MicrophoneAccessed());
  PageSpecificContentSettings* page_content_settings =
      PageSpecificContentSettings::GetForPage(GetPage());

  bool is_camera_modifiable = GetSettingManagedByUser(
      page_content_settings->media_stream_access_origin(),
      ContentSettingsType::MEDIASTREAM_CAMERA, GetProfile(), nullptr);
  bool is_mic_modifiable = GetSettingManagedByUser(
      page_content_settings->media_stream_access_origin(),
      ContentSettingsType::MEDIASTREAM_MIC, GetProfile(), nullptr);

  set_is_user_modifiable((MicrophoneAccessed() && is_mic_modifiable) ||
                         (CameraAccessed() && is_camera_modifiable));
}

void ContentSettingMediaStreamBubbleModel::SetTitle() {
  CHECK(CameraAccessed() || MicrophoneAccessed());
  int title_id = 0;
  if (MicrophoneBlocked() && CameraBlocked()) {
    title_id = IDS_MICROPHONE_CAMERA_BLOCKED_TITLE;
  } else if (MicrophoneBlocked()) {
    title_id = IDS_MICROPHONE_BLOCKED_TITLE;
  } else if (CameraBlocked()) {
    title_id = IDS_CAMERA_BLOCKED_TITLE;
  } else if (MicrophoneAccessed() && CameraAccessed()) {
    title_id = IDS_MICROPHONE_CAMERA_ALLOWED_TITLE;
  } else if (MicrophoneAccessed()) {
    title_id = IDS_MICROPHONE_ACCESSED_TITLE;
  } else if (CameraAccessed()) {
    title_id = IDS_CAMERA_ACCESSED_TITLE;
  } else {
    NOTREACHED();
  }
  set_title(l10n_util::GetStringUTF16(title_id));
}

void ContentSettingMediaStreamBubbleModel::SetMessage() {
  CHECK(CameraAccessed() || MicrophoneAccessed());
  int message_id = 0;
  if (MicrophoneBlocked() && CameraBlocked()) {
    message_id = IDS_MICROPHONE_CAMERA_BLOCKED;
  } else if (MicrophoneBlocked()) {
    message_id = IDS_MICROPHONE_BLOCKED;
  } else if (CameraBlocked()) {
    message_id = IDS_CAMERA_BLOCKED;
  } else if (MicrophoneAccessed() && CameraAccessed()) {
    message_id = IDS_MICROPHONE_CAMERA_ALLOWED;
  } else if (MicrophoneAccessed()) {
    message_id = IDS_MICROPHONE_ACCESSED;
  } else if (CameraAccessed()) {
    message_id = IDS_CAMERA_ACCESSED;
  } else {
    NOTREACHED();
  }
  set_message(l10n_util::GetStringUTF16(message_id));
}

void ContentSettingMediaStreamBubbleModel::SetRadioGroup() {
  PageSpecificContentSettings* content_settings =
      PageSpecificContentSettings::GetForPage(GetPage());
  GURL url = content_settings->media_stream_access_origin();
  RadioGroup radio_group;
  radio_group.url = url;
  const std::u16string& display_url = GetUrlForDisplay(GetProfile(), url);

  CHECK(CameraAccessed() || MicrophoneAccessed());
  int radio_allow_label_id = 0;
  int radio_block_label_id = 0;
  if (state_.Has(PageSpecificContentSettings::kMicrophoneBlocked) ||
      state_.Has(PageSpecificContentSettings::kCameraBlocked)) {
    if (network::IsUrlPotentiallyTrustworthy(url)) {
      radio_item_setting_[0] = CONTENT_SETTING_ALLOW;
      radio_allow_label_id = IDS_BLOCKED_MEDIASTREAM_CAMERA_ALLOW;
      if (MicrophoneAccessed()) {
        radio_allow_label_id =
            CameraAccessed() ? IDS_BLOCKED_MEDIASTREAM_MIC_AND_CAMERA_ALLOW
                             : IDS_BLOCKED_MEDIASTREAM_MIC_ALLOW;
      }
    } else {
      radio_allow_label_id = IDS_BLOCKED_MEDIASTREAM_CAMERA_ASK;
      if (MicrophoneAccessed()) {
        radio_allow_label_id = CameraAccessed()
                                   ? IDS_BLOCKED_MEDIASTREAM_MIC_AND_CAMERA_ASK
                                   : IDS_BLOCKED_MEDIASTREAM_MIC_ASK;
      }
    }
    radio_block_label_id = IDS_BLOCKED_MEDIASTREAM_CAMERA_NO_ACTION;
    if (MicrophoneAccessed()) {
      radio_block_label_id =
          CameraAccessed() ? IDS_BLOCKED_MEDIASTREAM_MIC_AND_CAMERA_NO_ACTION
                           : IDS_BLOCKED_MEDIASTREAM_MIC_NO_ACTION;
    }
  } else {
    bool has_pan_tilt_zoom_permission_granted =
        web_contents()
            ->GetBrowserContext()
            ->GetPermissionController()
            ->GetPermissionStatusForCurrentDocument(
                content::PermissionDescriptorUtil::
                    CreatePermissionDescriptorForPermissionType(
                        blink::PermissionType::CAMERA_PAN_TILT_ZOOM),
                &GetPage().GetMainDocument()) ==
        blink::mojom::PermissionStatus::GRANTED;

    if (MicrophoneAccessed() && CameraAccessed()) {
      radio_allow_label_id =
          has_pan_tilt_zoom_permission_granted
              ? IDS_ALLOWED_MEDIASTREAM_MIC_AND_CAMERA_PAN_TILT_ZOOM_NO_ACTION
              : IDS_ALLOWED_MEDIASTREAM_MIC_AND_CAMERA_NO_ACTION;
      radio_block_label_id = IDS_ALLOWED_MEDIASTREAM_MIC_AND_CAMERA_BLOCK;
    } else if (MicrophoneAccessed()) {
      radio_allow_label_id = IDS_ALLOWED_MEDIASTREAM_MIC_NO_ACTION;
      radio_block_label_id = IDS_ALLOWED_MEDIASTREAM_MIC_BLOCK;
    } else {
      radio_allow_label_id = has_pan_tilt_zoom_permission_granted
                                 ? IDS_ALLOWED_CAMERA_PAN_TILT_ZOOM_NO_ACTION
                                 : IDS_ALLOWED_MEDIASTREAM_CAMERA_NO_ACTION;
      radio_block_label_id = IDS_ALLOWED_MEDIASTREAM_CAMERA_BLOCK;
    }
  }

  std::u16string radio_allow_label =
      l10n_util::GetStringFUTF16(radio_allow_label_id, display_url);
  std::u16string radio_block_label =
      l10n_util::GetStringUTF16(radio_block_label_id);

  radio_group.default_item =
      (MicrophoneAccessed() && content_settings->IsContentBlocked(
                                   ContentSettingsType::MEDIASTREAM_MIC)) ||
              (CameraAccessed() && content_settings->IsContentBlocked(
                                       ContentSettingsType::MEDIASTREAM_CAMERA))
          ? 1
          : 0;
  radio_group.radio_items = {radio_allow_label, radio_block_label};
  set_radio_group(radio_group);
}

void ContentSettingMediaStreamBubbleModel::UpdateSettings(
    ContentSetting setting) {
  const GURL& url = bubble_content().radio_group.url;
  // The same urls must be used as in other places (e.g. the infobar) in
  // order to override the existing rule. Otherwise a new rule is created.
  // TODO(markusheintz): Extract to a helper so that there is only a single
  // place to touch.
  HostContentSettingsMap* map =
      HostContentSettingsMapFactory::GetForProfile(GetProfile());
  if (MicrophoneAccessed()) {
    permissions::PermissionUmaUtil::ScopedRevocationReporter
        scoped_revocation_reporter(
            GetProfile(), url, GURL(), ContentSettingsType::MEDIASTREAM_MIC,
            permissions::PermissionSourceUI::PAGE_ACTION);
    map->SetContentSettingDefaultScope(
        url, GURL(), ContentSettingsType::MEDIASTREAM_MIC, setting,
        CreateConstraintsForAutoRevocation(ContentSettingsType::MEDIASTREAM_MIC,
                                           setting));
  }
  if (CameraAccessed()) {
    permissions::PermissionUmaUtil::ScopedRevocationReporter
        scoped_revocation_reporter(
            GetProfile(), url, GURL(), ContentSettingsType::MEDIASTREAM_CAMERA,
            permissions::PermissionSourceUI::PAGE_ACTION);
    map->SetContentSettingDefaultScope(
        url, GURL(), ContentSettingsType::MEDIASTREAM_CAMERA, setting,
        CreateConstraintsForAutoRevocation(
            ContentSettingsType::MEDIASTREAM_CAMERA, setting));
  }
}

#if BUILDFLAG(IS_MAC)
void ContentSettingMediaStreamBubbleModel::
    InitializeSystemMediaPermissionBubble() {
  DCHECK(CameraAccessed() || MicrophoneAccessed());
  base::RecordAction(
      base::UserMetricsAction("Media.ShowSystemMediaPermissionBubble"));
  int title_id = 0;
  if (MicrophoneAccessed() && CameraAccessed() &&
      (system_permission_settings::CheckSystemVideoCapturePermission() ==
           system_permission_settings::SystemPermission::kDenied ||
       system_permission_settings::CheckSystemAudioCapturePermission() ==
           system_permission_settings::SystemPermission::kDenied)) {
    title_id = IDS_CAMERA_MIC_TURNED_OFF_IN_MACOS;
    AddListItem(ContentSettingBubbleModel::ListItem(
        &(features::IsRoundedIconsEnabled() ? vector_icons::kVideocamFilledIcon
                                            : vector_icons::kVideocamOldIcon),
        l10n_util::GetStringUTF16(IDS_CAMERA),
        l10n_util::GetStringUTF16(IDS_TURNED_OFF), false, true, 0));
    AddListItem(ContentSettingBubbleModel::ListItem(
        &(features::IsRoundedIconsEnabled() ? vector_icons::kMicFilledIcon
                                            : vector_icons::kMicOldIcon),
        l10n_util::GetStringUTF16(IDS_MIC),
        l10n_util::GetStringUTF16(IDS_TURNED_OFF), false, true, 1));
  } else if (CameraAccessed() &&
             system_permission_settings::CheckSystemVideoCapturePermission() ==
                 system_permission_settings::SystemPermission::kDenied) {
    title_id = IDS_CAMERA_TURNED_OFF_IN_MACOS;
    AddListItem(ContentSettingBubbleModel::ListItem(
        &(features::IsRoundedIconsEnabled() ? vector_icons::kVideocamFilledIcon
                                            : vector_icons::kVideocamOldIcon),
        l10n_util::GetStringUTF16(IDS_CAMERA),
        l10n_util::GetStringUTF16(IDS_TURNED_OFF), false, true, 0));
  } else if (MicrophoneAccessed() &&
             system_permission_settings::CheckSystemAudioCapturePermission() ==
                 system_permission_settings::SystemPermission::kDenied) {
    title_id = IDS_MIC_TURNED_OFF_IN_MACOS;
    AddListItem(ContentSettingBubbleModel::ListItem(
        &(features::IsRoundedIconsEnabled() ? vector_icons::kMicFilledIcon
                                            : vector_icons::kMicOldIcon),
        l10n_util::GetStringUTF16(IDS_MIC),
        l10n_util::GetStringUTF16(IDS_TURNED_OFF), false, true, 1));
  }

  set_title(l10n_util::GetStringUTF16(title_id));
  set_manage_text_style(ContentSettingBubbleModel::ManageTextStyle::kNone);
  SetCustomLink();
  set_done_button_text(l10n_util::GetStringUTF16(IDS_OPEN_SETTINGS_LINK));
}
#endif  // BUILDFLAG(IS_MAC)

bool ContentSettingMediaStreamBubbleModel::ShouldShowSystemMediaPermissions() {
#if BUILDFLAG(IS_MAC)
  return (((system_permission_settings::CheckSystemVideoCapturePermission() ==
                system_permission_settings::SystemPermission::kDenied &&
            CameraAccessed() && !CameraBlocked()) ||
           (system_permission_settings::CheckSystemAudioCapturePermission() ==
                system_permission_settings::SystemPermission::kDenied &&
            MicrophoneAccessed() && !MicrophoneBlocked())) &&
          !(CameraAccessed() && CameraBlocked()) &&
          !(MicrophoneAccessed() && MicrophoneBlocked()));
#else
  return false;
#endif  // BUILDFLAG(IS_MAC)
}

void ContentSettingMediaStreamBubbleModel::SetManageText() {
  CHECK(CameraAccessed() || MicrophoneAccessed());
  set_manage_text(l10n_util::GetStringUTF16(IDS_MANAGE));
}

void ContentSettingMediaStreamBubbleModel::SetCustomLink() {
  PageSpecificContentSettings* content_settings =
      PageSpecificContentSettings::GetForPage(GetPage());
  if (content_settings->IsMicrophoneCameraStateChanged()) {
    set_custom_link(
        l10n_util::GetStringUTF16(IDS_MEDIASTREAM_SETTING_CHANGED_MESSAGE));
  }
}

// ContentSettingGeolocationBubbleModel --------------------------------------

ContentSettingGeolocationBubbleModel::ContentSettingGeolocationBubbleModel(
    Delegate* delegate,
    content::Page& page)
    : ContentSettingSingleRadioGroup(delegate,
                                     page,
                                     ContentSettingsType::GEOLOCATION) {
  SetCustomLink();
  // Get the stored geolocation content setting and the system permission
  // state to determine whether geolocation is blocked by a system permission.
  //
  // The content setting must be read from HostContentSettingsMap.
  // PageSpecificContentSettings cannot be used because it combines the
  // site-level and system-level permissions, indicating the feature is
  // blocked if either the site-level or system-level permission is not
  // granted. We need to distinguish these cases to ensure the bubble that
  // launches the system dialog is not shown if the site-level permission was
  // not granted.
  const GURL& url = GetPage().GetMainDocument().GetLastCommittedURL();
  ContentSetting content_setting =
      HostContentSettingsMapFactory::GetForProfile(GetProfile())
          ->GetContentSetting(url, url, ContentSettingsType::GEOLOCATION);
  if (content_setting == CONTENT_SETTING_ALLOW &&
      !system_permission_settings::IsAllowed(
          ContentSettingsType::GEOLOCATION)) {
    // If the permission is turned off in system preferences, overwrite the
    // bubble to enable the user to trigger the system dialog.
    InitializeSystemGeolocationPermissionBubble();
  }
}

ContentSettingGeolocationBubbleModel::~ContentSettingGeolocationBubbleModel() =
    default;

void ContentSettingGeolocationBubbleModel::OnDoneButtonClicked() {
  if (show_system_geolocation_bubble_) {
    base::RecordAction(UserMetricsAction(
        "ContentSettings.GeolocationDialog.OpenPreferencesClicked"));

    system_permission_settings::OpenSystemSettings(
        web_contents(), ContentSettingsType::GEOLOCATION);
  }
}

void ContentSettingGeolocationBubbleModel::OnManageButtonClicked() {
  if (delegate()) {
    delegate()->ShowContentSettingsPage(ContentSettingsType::GEOLOCATION);
  }
}

void ContentSettingGeolocationBubbleModel::CommitChanges() {
  if (show_system_geolocation_bubble_) {
    return;
  }
  ContentSettingSingleRadioGroup::CommitChanges();
}

void ContentSettingGeolocationBubbleModel::
    InitializeSystemGeolocationPermissionBubble() {
#if BUILDFLAG(OS_LEVEL_GEOLOCATION_PERMISSION_SUPPORTED)
#if BUILDFLAG(IS_MAC) || BUILDFLAG(IS_WIN) || BUILDFLAG(IS_CHROMEOS)
  set_title(l10n_util::GetStringUTF16(IDS_GEOLOCATION_TURNED_OFF_IN_OS));
#else
  // The system-level location permission is not supported on Linux.
  NOTREACHED();
#endif

  clear_message();
  AddListItem(ContentSettingBubbleModel::ListItem(
      &(features::IsRoundedIconsEnabled() ? vector_icons::kLocationOnIcon
                                          : vector_icons::kLocationOnOldIcon),
      l10n_util::GetStringUTF16(IDS_GEOLOCATION),
      l10n_util::GetStringUTF16(IDS_TURNED_OFF), false, true, 0));
  set_manage_text_style(ContentSettingBubbleModel::ManageTextStyle::kNone);
  set_done_button_text(l10n_util::GetStringUTF16(IDS_OPEN_SETTINGS_LINK));
  set_radio_group(RadioGroup());
  show_system_geolocation_bubble_ = true;
#endif  // BUILDFLAG(OS_LEVEL_GEOLOCATION_PERMISSION_SUPPORTED)
}

void ContentSettingGeolocationBubbleModel::SetCustomLink() {
  auto* map = HostContentSettingsMapFactory::GetForProfile(
      web_contents()->GetBrowserContext());
  SettingInfo info;
  const GURL url =
      GetPage().GetMainDocument().GetLastCommittedOrigin().GetURL();
  map->GetWebsiteSetting(url, url, ContentSettingsType::GEOLOCATION, &info);
  if (info.metadata.session_model() == SessionModel::ONE_TIME) {
    set_custom_link(l10n_util::GetStringUTF16(IDS_GEOLOCATION_WILL_ASK_AGAIN));
  }
}

// ContentSettingNotificationsBubbleModel ------------------------------------

#if BUILDFLAG(IS_MAC)
ContentSettingNotificationsBubbleModel::ContentSettingNotificationsBubbleModel(
    Delegate* delegate,
    content::Page& page)
    : ContentSettingSimpleBubbleModel(delegate,
                                      page,
                                      ContentSettingsType::NOTIFICATIONS) {
  set_title(l10n_util::GetStringUTF16(IDS_NOTIFICATIONS_TURNED_OFF_IN_MACOS));
  AddListItem(ContentSettingBubbleModel::ListItem(
      &(features::IsRoundedIconsEnabled()
            ? vector_icons::kNotificationsOffIcon
            : vector_icons::kNotificationsOffChromeRefreshOldIcon),
      l10n_util::GetStringUTF16(IDS_NOTIFICATIONS),
      l10n_util::GetStringUTF16(IDS_TURNED_OFF), /*has_link=*/false,
      /*has_blocked_badge=*/false, 0));
  set_manage_text_style(ContentSettingBubbleModel::ManageTextStyle::kNone);
  set_done_button_text(l10n_util::GetStringUTF16(IDS_OPEN_SETTINGS_LINK));
}

ContentSettingNotificationsBubbleModel::
    ~ContentSettingNotificationsBubbleModel() = default;

void ContentSettingNotificationsBubbleModel::OnDoneButtonClicked() {
  std::string bundle_identifier = base::apple::MainBundleIdentifier();
  if (std::optional<webapps::AppId> app_id =
          web_app::WebAppTabHelper::GetAppIdForNotificationAttribution(
              web_contents());
      app_id.has_value()) {
    bundle_identifier = web_app::GetBundleIdentifierForShim(*app_id);
  }
  base::mac::OpenSystemSettingsPane(
      base::mac::SystemSettingsPane::kNotifications, bundle_identifier);
}
#endif

// ContentSettingSubresourceFilterBubbleModel ----------------------------------

ContentSettingSubresourceFilterBubbleModel::
    ContentSettingSubresourceFilterBubbleModel(Delegate* delegate,
                                               content::Page& page)
    : ContentSettingBubbleModel(delegate, page) {
  SetTitle();
  SetMessage();
  SetManageText();
  set_done_button_text(l10n_util::GetStringUTF16(IDS_OK));
  set_show_learn_more(true);
  subresource_filter::ContentSubresourceFilterThrottleManager::LogAction(
      subresource_filter::SubresourceFilterAction::kDetailsShown);
}

ContentSettingSubresourceFilterBubbleModel::
    ~ContentSettingSubresourceFilterBubbleModel() = default;

void ContentSettingSubresourceFilterBubbleModel::SetTitle() {
  set_title(l10n_util::GetStringUTF16(IDS_BLOCKED_ADS_PROMPT_TITLE));
}

void ContentSettingSubresourceFilterBubbleModel::SetManageText() {
  set_manage_text(l10n_util::GetStringUTF16(IDS_ALWAYS_ALLOW_ADS));
  set_manage_text_style(ContentSettingBubbleModel::ManageTextStyle::kCheckbox);
}

void ContentSettingSubresourceFilterBubbleModel::SetMessage() {
  set_message(l10n_util::GetStringUTF16(IDS_BLOCKED_ADS_PROMPT_EXPLANATION));
}

void ContentSettingSubresourceFilterBubbleModel::OnManageCheckboxChecked(
    bool is_checked) {
  set_done_button_text(
      l10n_util::GetStringUTF16(is_checked ? IDS_APP_MENU_RELOAD : IDS_OK));
  is_checked_ = is_checked;
}

void ContentSettingSubresourceFilterBubbleModel::OnLearnMoreClicked() {
  DCHECK(delegate());
  subresource_filter::ContentSubresourceFilterThrottleManager::LogAction(
      subresource_filter::SubresourceFilterAction::kClickedLearnMore);
  delegate()->ShowLearnMorePage(ContentSettingsType::ADS);
}

void ContentSettingSubresourceFilterBubbleModel::CommitChanges() {
  if (is_checked_) {
    if (GetPage().IsPrimary()) {
      subresource_filter::ContentSubresourceFilterThrottleManager::FromPage(
          GetPage())
          ->OnReloadRequested();
    } else {
      subresource_filter::SubresourceFilterContentSettingsManager(
          HostContentSettingsMapFactory::GetForProfile(GetProfile()))
          .AllowlistSite(GetPage().GetMainDocument().GetLastCommittedURL());
    }
  }
}

ContentSettingSubresourceFilterBubbleModel*
ContentSettingSubresourceFilterBubbleModel::AsSubresourceFilterBubbleModel() {
  return this;
}

// ContentSettingDownloadsBubbleModel ------------------------------------------

ContentSettingDownloadsBubbleModel::ContentSettingDownloadsBubbleModel(
    Delegate* delegate,
    content::Page& page)
    : ContentSettingBubbleModel(delegate, page) {
  SetTitle();
  SetManageText();
  SetRadioGroup();
  DownloadRequestLimiter* download_request_limiter =
      g_browser_process->download_request_limiter();
  set_is_user_modifiable(GetSettingManagedByUser(
      download_request_limiter->GetDownloadOrigin(web_contents()),
      ContentSettingsType::AUTOMATIC_DOWNLOADS, GetProfile(), nullptr));
}

ContentSettingDownloadsBubbleModel::~ContentSettingDownloadsBubbleModel() =
    default;

void ContentSettingDownloadsBubbleModel::CommitChanges() {
  if (selected_item() != bubble_content().radio_group.default_item) {
    permissions::PermissionUmaUtil::ScopedRevocationReporter
        scoped_revocation_reporter(
            GetProfile(), bubble_content().radio_group.url, GURL(),
            ContentSettingsType::AUTOMATIC_DOWNLOADS,
            permissions::PermissionSourceUI::PAGE_ACTION);
    ContentSetting setting = selected_item() == kAllowButtonIndex
                                 ? CONTENT_SETTING_ALLOW
                                 : CONTENT_SETTING_BLOCK;
    auto* map = HostContentSettingsMapFactory::GetForProfile(GetProfile());
    map->SetNarrowestContentSetting(
        bubble_content().radio_group.url, bubble_content().radio_group.url,
        ContentSettingsType::AUTOMATIC_DOWNLOADS, setting,
        CreateConstraintsForAutoRevocation(
            ContentSettingsType::AUTOMATIC_DOWNLOADS, setting));
  }
}

ContentSettingDownloadsBubbleModel*
ContentSettingDownloadsBubbleModel::AsDownloadsBubbleModel() {
  return this;
}

// Initialize the radio group by setting the appropriate labels for the
// content type and setting the default value based on the content setting.
void ContentSettingDownloadsBubbleModel::SetRadioGroup() {
  DownloadRequestLimiter* download_request_limiter =
      g_browser_process->download_request_limiter();
  const GURL& download_origin =
      download_request_limiter->GetDownloadOrigin(web_contents());

  const std::u16string& display_url =
      GetUrlForDisplay(GetProfile(), download_origin);

  DCHECK(download_request_limiter);

  RadioGroup radio_group;
  radio_group.url = download_origin;
  switch (download_request_limiter->GetDownloadUiStatus(web_contents())) {
    case DownloadRequestLimiter::DOWNLOAD_UI_ALLOWED:
      radio_group.radio_items = {
          l10n_util::GetStringUTF16(IDS_ALLOWED_DOWNLOAD_NO_ACTION),
          l10n_util::GetStringFUTF16(IDS_ALLOWED_DOWNLOAD_BLOCK, display_url)};
      radio_group.default_item = kAllowButtonIndex;
      break;
    case DownloadRequestLimiter::DOWNLOAD_UI_BLOCKED:
      radio_group.radio_items = {
          l10n_util::GetStringFUTF16(IDS_BLOCKED_DOWNLOAD_UNBLOCK, display_url),
          l10n_util::GetStringUTF16(IDS_BLOCKED_DOWNLOAD_NO_ACTION)};
      radio_group.default_item = 1;
      break;
    case DownloadRequestLimiter::DOWNLOAD_UI_DEFAULT:
      DUMP_WILL_BE_NOTREACHED();
      return;
  }
  set_radio_group(radio_group);
}

void ContentSettingDownloadsBubbleModel::SetTitle() {
  DownloadRequestLimiter* download_request_limiter =
      g_browser_process->download_request_limiter();
  DCHECK(download_request_limiter);

  switch (download_request_limiter->GetDownloadUiStatus(web_contents())) {
    case DownloadRequestLimiter::DOWNLOAD_UI_ALLOWED:
      set_title(l10n_util::GetStringUTF16(IDS_ALLOWED_DOWNLOAD_TITLE));
      return;
    case DownloadRequestLimiter::DOWNLOAD_UI_BLOCKED:
      set_title(l10n_util::GetStringUTF16(IDS_BLOCKED_DOWNLOAD_TITLE));
      return;
    case DownloadRequestLimiter::DOWNLOAD_UI_DEFAULT:
      // No title otherwise.
      return;
  }
}

void ContentSettingDownloadsBubbleModel::SetManageText() {
  set_manage_text(l10n_util::GetStringUTF16(IDS_MANAGE));
}

void ContentSettingDownloadsBubbleModel::OnManageButtonClicked() {
  if (delegate()) {
    delegate()->ShowContentSettingsPage(
        ContentSettingsType::AUTOMATIC_DOWNLOADS);
  }
}

// ContentSettingFramebustBlockBubbleModel -------------------------------------
ContentSettingFramebustBlockBubbleModel::
    ContentSettingFramebustBlockBubbleModel(Delegate* delegate,
                                            content::Page& page)
    : ContentSettingSingleRadioGroup(delegate,
                                     page,
                                     ContentSettingsType::POPUPS) {
  set_title(l10n_util::GetStringUTF16(IDS_REDIRECT_BLOCKED_MESSAGE));
  auto* helper = FramebustBlockTabHelper::From(
      tabs::TabInterface::GetFromContents(web_contents()));

  // Build the blocked urls list.
  for (const auto& blocked_url : helper->blocked_urls()) {
    AddListItem(CreateUrlListItem(0 /* id */, blocked_url));
  }

  url_list_observation_.Observe(helper->manager());
}

ContentSettingFramebustBlockBubbleModel::
    ~ContentSettingFramebustBlockBubbleModel() = default;

void ContentSettingFramebustBlockBubbleModel::OnListItemClicked(
    int index,
    const ui::Event& event) {
  FramebustBlockTabHelper::From(
      tabs::TabInterface::GetFromContents(web_contents()))
      ->OnBlockedUrlClicked(index);
}

ContentSettingFramebustBlockBubbleModel*
ContentSettingFramebustBlockBubbleModel::AsFramebustBlockBubbleModel() {
  return this;
}

void ContentSettingFramebustBlockBubbleModel::BlockedUrlAdded(
    int32_t id,
    const GURL& blocked_url) {
  AddListItem(CreateUrlListItem(0 /* id */, blocked_url));
}

// ContentSettingQuietRequestBubbleModel ----------------------------------
ContentSettingQuietRequestBubbleModel::ContentSettingQuietRequestBubbleModel(
    Delegate* delegate,
    content::Page& page)
    : ContentSettingBubbleModel(delegate, page) {
  // TODO(crbug.com/40110076): This block is more defensive than it needs to be
  // because ContentSettingImageModelBrowserTest exercises it without setting up
  // the correct PermissionRequestManager state. Fix that.
  permissions::PermissionRequestManager* manager =
      permissions::PermissionRequestManager::FromWebContents(web_contents());
  auto quiet_ui_reason = manager->ReasonForUsingQuietUi();
  if (!quiet_ui_reason) {
    return;
  }
  CHECK_GT(manager->Requests().size(), 0u);
  DCHECK_EQ(manager->Requests().size(), 1u);
  const permissions::RequestType request_type =
      manager->Requests()[0]->request_type();
  int bubble_title_string_id = 0;
  switch (request_type) {
    case permissions::RequestType::kNotifications:
      bubble_title_string_id = IDS_NOTIFICATIONS_QUIET_PERMISSION_BUBBLE_TITLE;
      break;
    case permissions::RequestType::kGeolocation:
      bubble_title_string_id = IDS_GEOLOCATION_QUIET_PERMISSION_BUBBLE_TITLE;
      break;
    default:
      NOTREACHED();
  }
  set_title(l10n_util::GetStringUTF16(bubble_title_string_id));
  switch (*quiet_ui_reason) {
    case QuietUiReason::kEnabledInPrefs:
      DCHECK(request_type == permissions::RequestType::kNotifications ||
             request_type == permissions::RequestType::kGeolocation);
      set_message(l10n_util::GetStringUTF16(
          request_type == permissions::RequestType::kNotifications
              ? IDS_NOTIFICATIONS_QUIET_PERMISSION_BUBBLE_DESCRIPTION
              : IDS_GEOLOCATION_QUIET_PERMISSION_BUBBLE_DESCRIPTION));
      set_done_button_text(l10n_util::GetStringUTF16(
          IDS_NOTIFICATIONS_QUIET_PERMISSION_BUBBLE_ALLOW_BUTTON));
      set_show_learn_more(false);
      base::RecordAction(
          base::UserMetricsAction("Notifications.Quiet.AnimatedIconClicked"));
      break;
    case QuietUiReason::kTriggeredByCrowdDeny:
      DCHECK_EQ(request_type, permissions::RequestType::kNotifications);
      set_message(l10n_util::GetStringUTF16(
          IDS_NOTIFICATIONS_QUIET_PERMISSION_BUBBLE_CROWD_DENY_DESCRIPTION));
      set_done_button_text(l10n_util::GetStringUTF16(
          IDS_NOTIFICATIONS_QUIET_PERMISSION_BUBBLE_ALLOW_BUTTON));
      set_show_learn_more(false);
      base::RecordAction(
          base::UserMetricsAction("Notifications.Quiet.StaticIconClicked"));
      break;
    case QuietUiReason::kTriggeredDueToAbusiveRequests:
    case QuietUiReason::kTriggeredDueToAbusiveContent:
      DCHECK_EQ(request_type, permissions::RequestType::kNotifications);
      set_message(l10n_util::GetStringUTF16(
          IDS_NOTIFICATIONS_QUIET_PERMISSION_BUBBLE_ABUSIVE_DESCRIPTION));
      // TODO(crbug.com/40131070): It is rather confusing to have the `Cancel`
      // button allow the permission, but we want the primary to block.
      set_cancel_button_text(l10n_util::GetStringUTF16(
          IDS_NOTIFICATIONS_QUIET_PERMISSION_BUBBLE_COMPACT_ALLOW_BUTTON));
      set_done_button_text(l10n_util::GetStringUTF16(
          IDS_NOTIFICATIONS_QUIET_PERMISSION_BUBBLE_CONTINUE_BLOCKING_BUTTON));
      set_show_learn_more(true);
      set_manage_text_style(ManageTextStyle::kNone);
      base::RecordAction(
          base::UserMetricsAction("Notifications.Quiet.StaticIconClicked"));
      break;
    case QuietUiReason::kTriggeredDueToDisruptiveBehavior:
      DCHECK_EQ(request_type, permissions::RequestType::kNotifications);
      set_message(l10n_util::GetStringUTF16(
          IDS_NOTIFICATIONS_QUIET_PERMISSION_BUBBLE_DISRUPTIVE_DESCRIPTION));
      set_cancel_button_text(l10n_util::GetStringUTF16(
          IDS_NOTIFICATIONS_QUIET_PERMISSION_BUBBLE_COMPACT_ALLOW_BUTTON));
      set_done_button_text(l10n_util::GetStringUTF16(
          IDS_NOTIFICATIONS_QUIET_PERMISSION_BUBBLE_CONTINUE_BLOCKING_BUTTON));
      set_show_learn_more(true);
      set_manage_text_style(ManageTextStyle::kNone);
      base::RecordAction(
          base::UserMetricsAction("Notifications.Quiet.StaticIconClicked"));
      break;
    case QuietUiReason::kServicePredictedVeryUnlikelyGrant:
    case QuietUiReason::kOnDevicePredictedVeryUnlikelyGrant:
    // TODO(crbug.com/412962300) use custom string
    case QuietUiReason::kTriggeredDueToLackOfGesture:
      int bubble_message_string_id = 0;
      int bubble_done_button_string_id = 0;
      switch (request_type) {
        case permissions::RequestType::kNotifications:
          bubble_message_string_id =
              IDS_NOTIFICATIONS_QUIET_PERMISSION_BUBBLE_PREDICTION_SERVICE_DESCRIPTION;
          bubble_done_button_string_id =
              IDS_NOTIFICATIONS_QUIET_PERMISSION_BUBBLE_ALLOW_BUTTON;
          break;
        case permissions::RequestType::kGeolocation:
          bubble_message_string_id =
              IDS_GEOLOCATION_QUIET_PERMISSION_BUBBLE_PREDICTION_SERVICE_DESCRIPTION;
          bubble_done_button_string_id =
              IDS_GEOLOCATION_QUIET_PERMISSION_BUBBLE_ALLOW_BUTTON;
          break;
        default:
          NOTREACHED();
      }
      set_message(l10n_util::GetStringUTF16(bubble_message_string_id));
      set_done_button_text(
          l10n_util::GetStringUTF16(bubble_done_button_string_id));
      set_show_learn_more(false);
      base::RecordAction(
          base::UserMetricsAction("Notifications.Quiet.AnimatedIconClicked"));
      break;
  }
}

ContentSettingQuietRequestBubbleModel::
    ~ContentSettingQuietRequestBubbleModel() = default;

ContentSettingQuietRequestBubbleModel*
ContentSettingQuietRequestBubbleModel::AsQuietRequestBubbleModel() {
  return this;
}

void ContentSettingQuietRequestBubbleModel::OnManageButtonClicked() {
  permissions::PermissionRequestManager* manager =
      permissions::PermissionRequestManager::FromWebContents(web_contents());
  CHECK_GT(manager->Requests().size(), 0u);
  DCHECK_EQ(manager->Requests().size(), 1u);
  manager->set_manage_clicked();
  if (is_UMA_for_test) {
    // `delegate()->ShowContentSettingsPage` opens a new tab. It is not needed
    // for UMA tests.
    return;
  }
  const permissions::RequestType request_type =
      manager->Requests()[0]->request_type();
  if (delegate()) {
    switch (request_type) {
      case permissions::RequestType::kNotifications:
        delegate()->ShowContentSettingsPage(ContentSettingsType::NOTIFICATIONS);
        break;
      case permissions::RequestType::kGeolocation:
        delegate()->ShowContentSettingsPage(ContentSettingsType::GEOLOCATION);
        break;
      default:
        NOTREACHED();
    }
  }
}

void ContentSettingQuietRequestBubbleModel::OnLearnMoreClicked() {
  permissions::PermissionRequestManager* manager =
      permissions::PermissionRequestManager::FromWebContents(web_contents());
  manager->set_learn_more_clicked();
  if (is_UMA_for_test) {
    // `delegate()->ShowLearnMorePage` opens a new tab. It is not needed for UMA
    // tests.
    return;
  }

  if (delegate()) {
    // We only show learn more button for Notification quiet ui dialog when it
    // is triggered due to abusive requests or contents. We don't have any learn
    // more button for the geolocation quiet ui dialogs.
    DCHECK_EQ(
        permissions::PermissionRequestManager::FromWebContents(web_contents())
            ->Requests()[0]
            ->request_type(),
        permissions::RequestType::kNotifications);
    delegate()->ShowLearnMorePage(ContentSettingsType::NOTIFICATIONS);
  }
}

void ContentSettingQuietRequestBubbleModel::OnDoneButtonClicked() {
  permissions::PermissionRequestManager* manager =
      permissions::PermissionRequestManager::FromWebContents(web_contents());
  CHECK_GT(manager->Requests().size(), 0u);
  DCHECK_EQ(manager->Requests().size(), 1u);
  auto quiet_ui_reason = manager->ReasonForUsingQuietUi();
  const permissions::PermissionRequest& request = *manager->Requests()[0];
  DCHECK(quiet_ui_reason);
  DCHECK(request.request_type() == permissions::RequestType::kNotifications ||
         request.request_type() == permissions::RequestType::kGeolocation);

  // GEOLOCATION_WITH_OPTIONS is currently not supported on desktop.
  //
  // TODO(crbug.com/430494523): Plumb through the selected PromptOptions once it
  // is.
  CHECK_NE(request.GetContentSettingsType(),
           ContentSettingsType::GEOLOCATION_WITH_OPTIONS);

  switch (*quiet_ui_reason) {
    case QuietUiReason::kEnabledInPrefs:
    case QuietUiReason::kTriggeredDueToLackOfGesture:
    case QuietUiReason::kTriggeredByCrowdDeny:
    case QuietUiReason::kServicePredictedVeryUnlikelyGrant:
    case QuietUiReason::kOnDevicePredictedVeryUnlikelyGrant:
      manager->Accept(/*prompt_options=*/std::monostate());
      break;
    case QuietUiReason::kTriggeredDueToAbusiveRequests:
    case QuietUiReason::kTriggeredDueToAbusiveContent:
    case QuietUiReason::kTriggeredDueToDisruptiveBehavior:
      manager->Deny(/*prompt_options=*/std::monostate());
      base::RecordAction(base::UserMetricsAction(
          "Notifications.Quiet.ContinueBlockingClicked"));
      break;
  }
}

void ContentSettingQuietRequestBubbleModel::OnCancelButtonClicked() {
  permissions::PermissionRequestManager* manager =
      permissions::PermissionRequestManager::FromWebContents(web_contents());

  auto quiet_ui_reason = manager->ReasonForUsingQuietUi();
  if (!quiet_ui_reason) {
    return;
  }
  switch (*quiet_ui_reason) {
    case QuietUiReason::kEnabledInPrefs:
    case QuietUiReason::kTriggeredDueToLackOfGesture:
    case QuietUiReason::kTriggeredByCrowdDeny:
    case QuietUiReason::kServicePredictedVeryUnlikelyGrant:
    case QuietUiReason::kOnDevicePredictedVeryUnlikelyGrant:
      // No-op.
      break;
    case QuietUiReason::kTriggeredDueToAbusiveRequests:
    case QuietUiReason::kTriggeredDueToAbusiveContent:
    case QuietUiReason::kTriggeredDueToDisruptiveBehavior:
      CHECK_EQ(manager->Requests()[0]->request_type(),
               permissions::RequestType::kNotifications);
      manager->Accept(/*prompt_options=*/std::monostate());
      base::RecordAction(
          base::UserMetricsAction("Notifications.Quiet.ShowForSiteClicked"));
      break;
  }
}

// ContentSettingBubbleModel ---------------------------------------------------

// This class must be placed last because it needs the definition of the other
// classes declared in this file.

const int ContentSettingBubbleModel::kAllowButtonIndex = 0;

// static
std::unique_ptr<ContentSettingBubbleModel>
ContentSettingBubbleModel::CreateContentSettingBubbleModel(
    Delegate* delegate,
    content::Page& page,
    ContentSettingsType content_type) {
  switch (content_type) {
    case ContentSettingsType::COOKIES:
      return std::make_unique<ContentSettingCookiesBubbleModel>(delegate, page);
    case ContentSettingsType::POPUPS:
      return std::make_unique<ContentSettingPopupBubbleModel>(delegate, page);
    case ContentSettingsType::MIXEDSCRIPT:
      return std::make_unique<ContentSettingMixedScriptBubbleModel>(delegate,
                                                                    page);
    case ContentSettingsType::PROTOCOL_HANDLERS: {
      custom_handlers::ProtocolHandlerRegistry* registry =
          ProtocolHandlerRegistryFactory::GetForBrowserContext(
              page.GetMainDocument().GetBrowserContext());
      return std::make_unique<ContentSettingRPHBubbleModel>(delegate, page,
                                                            registry);
    }
    case ContentSettingsType::AUTOMATIC_DOWNLOADS:
      return std::make_unique<ContentSettingDownloadsBubbleModel>(delegate,
                                                                  page);
    case ContentSettingsType::ADS:
      return std::make_unique<ContentSettingSubresourceFilterBubbleModel>(
          delegate, page);
    case ContentSettingsType::IMAGES:
    case ContentSettingsType::JAVASCRIPT:
    case ContentSettingsType::SOUND:
    case ContentSettingsType::CLIPBOARD_READ_WRITE:
    case ContentSettingsType::MIDI_SYSEX:
    case ContentSettingsType::SENSORS:
#if BUILDFLAG(IS_WIN)
    case ContentSettingsType::PROTECTED_MEDIA_IDENTIFIER:
#endif
      return std::make_unique<ContentSettingSingleRadioGroup>(delegate, page,
                                                              content_type);
    case ContentSettingsType::STORAGE_ACCESS:
      return std::make_unique<ContentSettingStorageAccessBubbleModel>(delegate,
                                                                      page);
#if BUILDFLAG(IS_CHROMEOS)
    case ContentSettingsType::SMART_CARD_GUARD:
      return std::make_unique<ContentSettingSimpleBubbleModel>(delegate, page,
                                                               content_type);
#endif
    default:
      NOTREACHED() << "No bubble for the content type "
                   << static_cast<int32_t>(content_type) << ".";
  }
}

ContentSettingBubbleModel::ContentSettingBubbleModel(Delegate* delegate,
                                                     content::Page& page)
    : page_(page.GetSafeRef()), owner_(nullptr), delegate_(delegate) {}

ContentSettingBubbleModel::~ContentSettingBubbleModel() = default;

WebContents* ContentSettingBubbleModel::web_contents() const {
  return WebContents::FromRenderFrameHost(&page_->GetMainDocument());
}

content::Page& ContentSettingBubbleModel::GetPage() const {
  return *page_;
}

ContentSettingBubbleModel::RadioGroup::RadioGroup() = default;

ContentSettingBubbleModel::RadioGroup::~RadioGroup() = default;

ContentSettingBubbleModel::MediaMenu::MediaMenu() = default;

ContentSettingBubbleModel::MediaMenu::MediaMenu(const MediaMenu& other) =
    default;

ContentSettingBubbleModel::MediaMenu::~MediaMenu() = default;

ContentSettingBubbleModel::BubbleContent::BubbleContent() = default;

ContentSettingBubbleModel::BubbleContent::~BubbleContent() = default;

ContentSettingSimpleBubbleModel*
ContentSettingBubbleModel::AsSimpleBubbleModel() {
  // In general, bubble models might not inherit from the simple bubble model.
  return nullptr;
}

ContentSettingMediaStreamBubbleModel*
ContentSettingBubbleModel::AsMediaStreamBubbleModel() {
  // In general, bubble models might not inherit from the media bubble model.
  return nullptr;
}

ContentSettingQuietRequestBubbleModel*
ContentSettingBubbleModel::AsQuietRequestBubbleModel() {
  return nullptr;
}

ContentSettingSubresourceFilterBubbleModel*
ContentSettingBubbleModel::AsSubresourceFilterBubbleModel() {
  return nullptr;
}

ContentSettingDownloadsBubbleModel*
ContentSettingBubbleModel::AsDownloadsBubbleModel() {
  return nullptr;
}

ContentSettingFramebustBlockBubbleModel*
ContentSettingBubbleModel::AsFramebustBlockBubbleModel() {
  return nullptr;
}

Profile* ContentSettingBubbleModel::GetProfile() const {
  return Profile::FromBrowserContext(web_contents()->GetBrowserContext());
}

void ContentSettingBubbleModel::AddListItem(const ListItem& item) {
  bubble_content_.list_items.push_back(item);
  if (owner_) {
    owner_->OnListItemAdded(item);
  }
}

void ContentSettingBubbleModel::RemoveListItem(int index) {
  if (owner_) {
    owner_->OnListItemRemovedAt(index);
  }

  bubble_content_.list_items.erase(bubble_content_.list_items.begin() + index);
}
