// 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/content_settings/page_specific_content_settings_delegate.h"

#include "base/feature_list.h"
#include "chrome/browser/browsing_data/chrome_browsing_data_model_delegate.h"
#include "chrome/browser/content_settings/chrome_content_settings_utils.h"
#include "chrome/browser/content_settings/host_content_settings_map_factory.h"
#include "chrome/browser/media/webrtc/media_capture_devices_dispatcher.h"
#include "chrome/browser/media/webrtc/media_stream_capture_indicator.h"
#include "chrome/browser/permissions/permission_decision_auto_blocker_factory.h"
#include "chrome/browser/permissions/system/system_permission_settings.h"
#include "chrome/browser/picture_in_picture/picture_in_picture_window_manager.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/common/chrome_switches.h"
#include "chrome/common/renderer_configuration.mojom.h"
#include "components/content_settings/browser/page_specific_content_settings.h"
#include "components/content_settings/core/common/content_settings.h"
#include "components/content_settings/core/common/content_settings_utils.h"
#include "components/content_settings/core/common/features.h"
#include "components/guest_view/buildflags/buildflags.h"
#include "components/permissions/permission_decision_auto_blocker.h"
#include "components/permissions/permission_recovery_success_rate_tracker.h"
#include "components/permissions/permission_uma_util.h"
#include "components/permissions/permission_util.h"
#include "components/prefs/pref_service.h"
#include "content/public/browser/navigation_handle.h"
#include "content/public/browser/render_frame_host.h"
#include "content/public/browser/render_process_host.h"
#include "extensions/buildflags/buildflags.h"
#include "pdf/buildflags.h"

#if BUILDFLAG(ENABLE_GUEST_VIEW)
#include "components/guest_view/browser/guest_view_base.h"
#endif  // BUILDFLAG(ENABLE_GUEST_VIEW)

#if BUILDFLAG(ENABLE_PDF)
#include "extensions/browser/mime_handler/mime_handler_stream_manager.h"  // nogncheck
#include "pdf/pdf_features.h"
#endif  // BUILDFLAG(ENABLE_PDF)

#if !BUILDFLAG(IS_ANDROID)
#include "chrome/browser/ui/tabs/public/tab_features.h"
#include "components/permissions/permission_indicators_tab_data.h"
#include "components/tabs/public/tab_interface.h"
#endif  // !BUILDFLAG(IS_ANDROID)

using content_settings::PageSpecificContentSettings;

class PageSpecificContentSettingsDelegate::MediaObserver
    : public MediaStreamCaptureIndicator::Observer {
 public:
  explicit MediaObserver(PageSpecificContentSettingsDelegate* delegate)
      : delegate_(delegate) {
    observation_.Observe(MediaCaptureDevicesDispatcher::GetInstance()
                             ->GetMediaStreamCaptureIndicator()
                             .get());
  }
  ~MediaObserver() override = default;

  // MediaStreamCaptureIndicator::Observer
  void OnIsCapturingVideoChanged(content::WebContents* web_contents,
                                 bool is_capturing_video) override {
    delegate_->OnIsCapturingVideoChanged(web_contents, is_capturing_video);
  }
  void OnIsCapturingAudioChanged(content::WebContents* web_contents,
                                 bool is_capturing_audio) override {
    delegate_->OnIsCapturingAudioChanged(web_contents, is_capturing_audio);
  }

 private:
  raw_ptr<PageSpecificContentSettingsDelegate> delegate_;
  base::ScopedObservation<MediaStreamCaptureIndicator,
                          MediaStreamCaptureIndicator::Observer>
      observation_{this};
};

PageSpecificContentSettingsDelegate::PageSpecificContentSettingsDelegate(
    content::WebContents* web_contents)
    : WebContentsObserver(web_contents),
      media_observer_(std::make_unique<MediaObserver>(this)) {}

PageSpecificContentSettingsDelegate::~PageSpecificContentSettingsDelegate() =
    default;

// static
PageSpecificContentSettingsDelegate*
PageSpecificContentSettingsDelegate::FromWebContents(
    content::WebContents* web_contents) {
  return static_cast<PageSpecificContentSettingsDelegate*>(
      PageSpecificContentSettings::GetDelegateForWebContents(web_contents));
}

// Helper function for recording indicator usage data.
void IndicatorUsageHistogramHelper(content::WebContents* web_contents,
                                   permissions::RequestTypeForUma request_type,
                                   bool is_capturing) {
#if !BUILDFLAG(IS_ANDROID)
  tabs::TabInterface* tab_model =
      tabs::TabInterface::MaybeGetFromContents(web_contents);
  if (!tab_model) {
    return;
  }
  permissions::PermissionIndicatorsTabData* permission_indicators_tab_data =
      tab_model->GetTabFeatures()->permission_indicators_tab_data();
  if (permission_indicators_tab_data) {
    permission_indicators_tab_data->OnMediaCaptureChanged(request_type,
                                                          is_capturing);
  }
#endif  // !BUILDFLAG(IS_ANDROID)
}

void PageSpecificContentSettingsDelegate::OnIsCapturingVideoChanged(
    content::WebContents* web_contents,
    bool is_capturing_video) {
  OnCapturingStateChanged(web_contents, ContentSettingsType::MEDIASTREAM_CAMERA,
                          is_capturing_video);
  IndicatorUsageHistogramHelper(
      web_contents,
      permissions::RequestTypeForUma::PERMISSION_MEDIASTREAM_CAMERA,
      is_capturing_video);
}

void PageSpecificContentSettingsDelegate::OnIsCapturingAudioChanged(
    content::WebContents* web_contents,
    bool is_capturing_audio) {
  OnCapturingStateChanged(web_contents, ContentSettingsType::MEDIASTREAM_MIC,
                          is_capturing_audio);
  IndicatorUsageHistogramHelper(
      web_contents, permissions::RequestTypeForUma::PERMISSION_MEDIASTREAM_MIC,
      is_capturing_audio);
}

void PageSpecificContentSettingsDelegate::OnCapturingStateChanged(
    content::WebContents* web_contents,
    ContentSettingsType type,
    bool is_capturing) {
  DCHECK(web_contents);

  PageSpecificContentSettings* pscs = PageSpecificContentSettings::GetForFrame(
      web_contents->GetPrimaryMainFrame());

  if (pscs == nullptr) {
    // There are cases, e.g. MPArch, where there is no active instance of
    // PageSpecificContentSettings for a frame.
    return;
  }

  pscs->OnCapturingStateChanged(type, is_capturing);

  content::WebContents* pip_web_contents =
      PictureInPictureWindowManager::GetInstance()->GetChildWebContents();
  if (pip_web_contents && pip_web_contents != web_contents) {
    OnCapturingStateChanged(pip_web_contents, type, is_capturing);
  }
}

void PageSpecificContentSettingsDelegate::UpdateLocationBar() {
  if (!web_contents()) {
    return;
  }
  content_settings::UpdateLocationBarUiForWebContents(web_contents());

  PageSpecificContentSettings* pscs = PageSpecificContentSettings::GetForFrame(
      web_contents()->GetPrimaryMainFrame());

  if (pscs == nullptr) {
    // There are cases, e.g. MPArch, where there is no active instance of
    // PageSpecificContentSettings for a frame.
    return;
  }

  PageSpecificContentSettings::MicrophoneCameraState state =
      pscs->GetMicrophoneCameraState();

  if (state.Has(PageSpecificContentSettings::kCameraAccessed) ||
      state.Has(PageSpecificContentSettings::kMicrophoneAccessed)) {
    auto* permission_tracker =
        permissions::PermissionRecoverySuccessRateTracker::FromWebContents(
            web_contents());

    if (state.Has(PageSpecificContentSettings::kMicrophoneAccessed)) {
      permission_tracker->TrackUsage(ContentSettingsType::MEDIASTREAM_MIC);
    }

    if (state.Has(PageSpecificContentSettings::kCameraAccessed)) {
      permission_tracker->TrackUsage(ContentSettingsType::MEDIASTREAM_CAMERA);
    }
  }
}

PrefService* PageSpecificContentSettingsDelegate::GetPrefs() {
  Profile* profile =
      Profile::FromBrowserContext(web_contents()->GetBrowserContext());
  if (!profile) {
    return nullptr;
  }

  return profile->GetPrefs();
}

HostContentSettingsMap* PageSpecificContentSettingsDelegate::GetSettingsMap() {
  return HostContentSettingsMapFactory::GetForProfile(
      Profile::FromBrowserContext(web_contents()->GetBrowserContext()));
}

std::unique_ptr<BrowsingDataModel::Delegate>
PageSpecificContentSettingsDelegate::CreateBrowsingDataModelDelegate() {
  return ChromeBrowsingDataModelDelegate::CreateForStoragePartition(
      Profile::FromBrowserContext(web_contents()->GetBrowserContext()),
      web_contents()->GetPrimaryMainFrame()->GetStoragePartition());
}

namespace {
// By default, JavaScript, images and auto dark are allowed, and blockable mixed
// content is blocked in guest content
#if BUILDFLAG(ENABLE_GUEST_VIEW)
void GetGuestViewDefaultContentSettingRules(
    bool incognito,
    RendererContentSettingRules* rules) {
  rules->mixed_content_rules.clear();
  rules->mixed_content_rules.push_back(ContentSettingPatternSource(
      ContentSettingsPattern::Wildcard(), ContentSettingsPattern::Wildcard(),
      content_settings::ContentSettingToValue(CONTENT_SETTING_BLOCK),
      content_settings::ProviderType::kNone, incognito));
}
#endif
}  // namespace

void PageSpecificContentSettingsDelegate::SetDefaultRendererContentSettingRules(
    content::RenderFrameHost* rfh,
    RendererContentSettingRules* rules) {
#if BUILDFLAG(ENABLE_GUEST_VIEW)
  bool is_off_the_record =
      web_contents()->GetBrowserContext()->IsOffTheRecord();

  if (guest_view::GuestViewBase::IsGuest(rfh)) {
    GetGuestViewDefaultContentSettingRules(is_off_the_record, rules);
    return;
  }
#endif
}

PageSpecificContentSettings::MicrophoneCameraState
PageSpecificContentSettingsDelegate::GetMicrophoneCameraState() {
  PageSpecificContentSettings::MicrophoneCameraState state;

  // Include capture devices in the state if there are still consumers of the
  // approved media stream.
  scoped_refptr<MediaStreamCaptureIndicator> media_indicator =
      MediaCaptureDevicesDispatcher::GetInstance()
          ->GetMediaStreamCaptureIndicator();
  if (media_indicator->IsCapturingAudio(web_contents())) {
    state.Put(PageSpecificContentSettings::kMicrophoneAccessed);
  }
  if (media_indicator->IsCapturingVideo(web_contents())) {
    state.Put(PageSpecificContentSettings::kCameraAccessed);
  }

  return state;
}

content::WebContents* PageSpecificContentSettingsDelegate::
    MaybeGetSyncedWebContentsForPictureInPicture(
        content::WebContents* web_contents) {
  DCHECK(web_contents);
  content::WebContents* parent_web_contents =
      PictureInPictureWindowManager::GetInstance()->GetWebContents();
  content::WebContents* child_web_contents =
      PictureInPictureWindowManager::GetInstance()->GetChildWebContents();

  // For document picture-in-picture window, return the opener web contents.
  if (web_contents == child_web_contents) {
    DCHECK(parent_web_contents);
    return parent_web_contents;
  }

  // For browser window that has opened a document picture-in-picture window,
  // return the PiP window web contents.
  if ((web_contents == parent_web_contents) && child_web_contents) {
    return child_web_contents;
  }
  return nullptr;
}

void PageSpecificContentSettingsDelegate::OnContentAllowed(
    ContentSettingsType type) {
  if (!(type == permissions::PermissionUtil::GetGeolocationType() ||
        type == ContentSettingsType::MEDIASTREAM_CAMERA ||
        type == ContentSettingsType::MEDIASTREAM_MIC)) {
    return;
  }
  content_settings::SettingInfo setting_info;
  GetSettingsMap()->GetWebsiteSetting(web_contents()->GetLastCommittedURL(),
                                      web_contents()->GetLastCommittedURL(),
                                      type, &setting_info);
  const base::Time grant_time = setting_info.metadata.last_modified();
  if (grant_time.is_null()) {
    return;
  }
  permissions::PermissionUmaUtil::RecordTimeElapsedBetweenGrantAndUse(
      type, base::Time::Now() - grant_time, setting_info.source);
  permissions::PermissionUmaUtil::RecordPermissionUsage(
      type, web_contents()->GetBrowserContext(),
      // TODO: This RenderFrameHost and origin might be wrong if there was a
      // concurrent navigation.
      web_contents()->GetPrimaryMainFrame(),
      web_contents()->GetLastCommittedURL());
}

void PageSpecificContentSettingsDelegate::OnContentBlocked(
    ContentSettingsType type) {
  if (type == ContentSettingsType::POPUPS) {
    content_settings::RecordPopupsAction(
        content_settings::POPUPS_ACTION_DISPLAYED_BLOCKED_ICON_IN_OMNIBOX);
  }
}

bool PageSpecificContentSettingsDelegate::IsBlockedOnSystemLevel(
    ContentSettingsType type) {
  DCHECK(type == ContentSettingsType::MEDIASTREAM_MIC ||
         type == ContentSettingsType::MEDIASTREAM_CAMERA);

  return system_permission_settings::IsDenied(type);
}

bool PageSpecificContentSettingsDelegate::IsFrameAllowlistedForJavaScript(
    content::RenderFrameHost* render_frame_host) {
#if BUILDFLAG(ENABLE_PDF)
  // OOPIF PDF viewer only.
  if (!chrome_pdf::features::IsOopifPdfEnabled()) {
    return false;
  }

  // There should be a `MimeHandlerStreamManager` if `render_frame_host`'s
  // `content::WebContents` has a PDF.
  auto* mime_handler_stream_manager =
      extensions::mime_handler::MimeHandlerStreamManager::FromRenderFrameHost(
          render_frame_host);
  if (!mime_handler_stream_manager) {
    return false;
  }

  // Allow the MIME handler extension frame and content frame to use JavaScript.
  if (mime_handler_stream_manager->IsExtensionHost(render_frame_host) ||
      mime_handler_stream_manager->IsContentHost(render_frame_host)) {
    return true;
  }
#endif  // BUILDFLAG(ENABLE_PDF)

  return false;
}

bool PageSpecificContentSettingsDelegate::IsPiPWindow(
    content::WebContents* web_contents) {
  DCHECK(web_contents);
  content::WebContents* child_web_contents =
      PictureInPictureWindowManager::GetInstance()->GetChildWebContents();

  return child_web_contents == web_contents;
}

void PageSpecificContentSettingsDelegate::PrimaryPageChanged(
    content::Page& page) {
  ClearPendingProtocolHandler();
}
