// 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 "content/browser/mojo_binder_policy_map_impl.h"

#include <string_view>

#include "base/feature_list.h"
#include "base/no_destructor.h"
#include "base/not_fatal_until.h"
#include "content/common/dom_automation_controller.mojom.h"
#include "content/common/frame.mojom.h"
#include "content/public/browser/content_browser_client.h"
#include "content/public/browser/mojo_binder_policy_map.h"
#include "content/public/common/content_client.h"
#include "device/gamepad/public/mojom/gamepad.mojom.h"
#include "media/mojo/mojom/media_player.mojom.h"
#include "media/mojo/mojom/webrtc_video_perf.mojom.h"
#include "services/network/public/mojom/restricted_cookie_manager.mojom.h"
#include "third_party/blink/public/mojom/blob/blob_url_store.mojom.h"
#include "third_party/blink/public/mojom/broadcastchannel/broadcast_channel.mojom.h"
#include "third_party/blink/public/mojom/cache_storage/cache_storage.mojom.h"
#include "third_party/blink/public/mojom/clipboard/clipboard.mojom.h"
#include "third_party/blink/public/mojom/file/file_utilities.mojom.h"
#include "third_party/blink/public/mojom/frame/back_forward_cache_controller.mojom.h"
#include "third_party/blink/public/mojom/frame/frame.mojom.h"
#include "third_party/blink/public/mojom/indexeddb/indexeddb.mojom.h"
#include "third_party/blink/public/mojom/loader/fetch_later.mojom.h"
#if BUILDFLAG(IS_MAC)
#include "third_party/blink/public/mojom/input/text_input_host.mojom.h"
#endif
#include "third_party/blink/public/mojom/loader/code_cache.mojom.h"
#include "third_party/blink/public/mojom/manifest/manifest_observer.mojom.h"
#include "third_party/blink/public/mojom/media/renderer_audio_output_stream_factory.mojom.h"
#include "third_party/blink/public/mojom/notifications/notification_service.mojom.h"
#include "third_party/blink/public/mojom/page/display_cutout.mojom.h"

namespace content {

#if BUILDFLAG(IS_MAC)
// Put crbug.com/115920 fix under flag, so we can measure its CWV impact.
BASE_FEATURE(kTextInputHostMojoCapabilityControlWorkaround,
             "TextInputHostMojoCapabilityControlWorkaround",
             base::FEATURE_ENABLED_BY_DEFAULT);
#endif

namespace {

enum class PolicyClass {
  kSameOriginPrerendering,
  kPreview,
};

// Register feature specific policies for interfaces registered in
// `internal::PopulateBinderMap` and `internal::PopulateBinderMapWithContext`.
void RegisterNonAssociatedPolicies(MojoBinderPolicyMap& map,
                                   PolicyClass policy) {
  // For Prerendering, kCancel is usually used for those interfaces that cannot
  // be granted because they can cause undesirable side-effects (e.g., playing
  // audio, showing notification) and are non-deferrable.
  // Please update `PrerenderCancelledInterface` and
  // `GetCancelledInterfaceType()` in
  // content/browser/preloading/prerender/prerender_metrics.h once you add a new
  // kCancel interface.

  map.SetNonAssociatedPolicy<device::mojom::GamepadHapticsManager>(
      MojoBinderNonAssociatedPolicy::kCancel);
  map.SetNonAssociatedPolicy<device::mojom::GamepadMonitor>(
      MojoBinderNonAssociatedPolicy::kCancel);

  if (policy == PolicyClass::kSameOriginPrerendering) {
    // ClipboardHost has sync messages, so it cannot be kDefer. However, the
    // renderer is not expected to request the interface; prerendering documents
    // do not have system focus nor user activation, which is required before
    // sending the request.
    map.SetNonAssociatedPolicy<blink::mojom::ClipboardHost>(
        MojoBinderNonAssociatedPolicy::kUnexpected);
  }

  // FileUtilitiesHost is only used by APIs that require user activations, being
  // impossible for a prerendered document. For the reason, this is marked as
  // kUnexpected.
  map.SetNonAssociatedPolicy<blink::mojom::FileUtilitiesHost>(
      MojoBinderNonAssociatedPolicy::kUnexpected);

  map.SetNonAssociatedPolicy<blink::mojom::CacheStorage>(
      MojoBinderNonAssociatedPolicy::kGrant);
  map.SetNonAssociatedPolicy<blink::mojom::IDBFactory>(
      MojoBinderNonAssociatedPolicy::kGrant);

  // Grant this interface because some sync web APIs rely on it; deferring it
  // leads to deadlock. However, granting this interface does not mean that
  // prerenders are allowed to create output streams.
  // RenderFrameAudioOutputStreamFactory understands which pages are
  // prerendering and does not fulfill their requests for audio streams.
  map.SetNonAssociatedPolicy<blink::mojom::RendererAudioOutputStreamFactory>(
      MojoBinderNonAssociatedPolicy::kGrant);
  map.SetNonAssociatedPolicy<network::mojom::RestrictedCookieManager>(
      MojoBinderNonAssociatedPolicy::kGrant);
  // Set policy to Grant for CodeCacheHost. Without this loads won't progress
  // since we wait for a response from code cache when loading resources.
  map.SetNonAssociatedPolicy<blink::mojom::CodeCacheHost>(
      MojoBinderNonAssociatedPolicy::kGrant);

  // Grant this for Media Capabilities APIs. This should be safe as the APIs
  // just query encoding / decoding information.
  map.SetNonAssociatedPolicy<media::mojom::WebrtcVideoPerfHistory>(
      content::MojoBinderNonAssociatedPolicy::kGrant);

#if BUILDFLAG(IS_MAC)
  // Set policy to Grant for TextInputHost.
  // This is used to return macOS IME sync call results to the browser process,
  // and will hang entire Chrome if paused.
  // This is a prospective fix added for crbug.com/1480850
  if (base::FeatureList::IsEnabled(
          kTextInputHostMojoCapabilityControlWorkaround)) {
    map.SetNonAssociatedPolicy<blink::mojom::TextInputHost>(
        MojoBinderNonAssociatedPolicy::kGrant);
  }
#endif
}

// Register same-origin prerendering policies for channel-associated interfaces
// registered in `RenderFrameHostImpl::SetUpMojoIfNeeded()`.
void RegisterChannelAssociatedPoliciesForSameOriginPrerendering(
    MojoBinderPolicyMap& map) {
  // Basic skeleton. All of them are critical to load a page so their policies
  // have to be kGrant.
  // TODO(crbug.com/40201285): Message-level control should be performed.
  map.SetAssociatedPolicy<mojom::FrameHost>(MojoBinderAssociatedPolicy::kGrant);
  map.SetAssociatedPolicy<blink::mojom::LocalFrameHost>(
      MojoBinderAssociatedPolicy::kGrant);
  map.SetAssociatedPolicy<blink::mojom::LocalMainFrameHost>(
      MojoBinderAssociatedPolicy::kGrant);

  // These interfaces do not leak sensitive information.
  map.SetAssociatedPolicy<blink::mojom::BackForwardCacheControllerHost>(
      MojoBinderAssociatedPolicy::kGrant);
  map.SetAssociatedPolicy<blink::mojom::ManifestUrlChangeObserver>(
      MojoBinderAssociatedPolicy::kGrant);
  map.SetAssociatedPolicy<mojom::DomAutomationControllerHost>(
      MojoBinderAssociatedPolicy::kGrant);

  // BroadcastChannel is granted for prerendering, as this API is restricted to
  // same-origin.
  map.SetAssociatedPolicy<blink::mojom::BroadcastChannelProvider>(
      MojoBinderAssociatedPolicy::kGrant);

  // Granting this interface does not mean prerendering pages are allowed to
  // play media. Feature-specific capability control is implemented to delay
  // playing media. See `RenderFrameImpl::DeferMediaLoad` for more information.
  map.SetAssociatedPolicy<media::mojom::MediaPlayerHost>(
      MojoBinderAssociatedPolicy::kGrant);

  // DisplayCutout supports the CSS viewport-fit property. It tracks
  // the current viewport-fit on a per-document basis, but only calls
  // the WebContents::NotifyViewportFitChanged and informs WebContents's
  // observers when the document is fullscreened. Prerendered documents cannot
  // enter fullscreen because they do not have transient activation, nor are
  // they active documents (see RenderFrameHostImpl::EnterFullscreen), so it is
  // safe to allow a prerendered document to use it.
  map.SetAssociatedPolicy<blink::mojom::DisplayCutoutHost>(
      MojoBinderAssociatedPolicy::kGrant);

  // Prerendering pages are allowed to create urls for blobs.
  map.SetAssociatedPolicy<blink::mojom::BlobURLStore>(
      MojoBinderAssociatedPolicy::kGrant);

  // Pages with FetchLater API calls should be allowed to prerender.
  // TODO(crbug.com/40276121): Update according to feedback from
  // https://github.com/WICG/pending-beacon/issues/82
  map.SetAssociatedPolicy<blink::mojom::FetchLaterLoaderFactory>(
      MojoBinderAssociatedPolicy::kGrant);
}

// Register mojo binder policies for same-origin prerendering for content/
// interfaces.
void RegisterContentBinderPoliciesForSameOriginPrerendering(
    MojoBinderPolicyMap& map) {
  RegisterNonAssociatedPolicies(map, PolicyClass::kSameOriginPrerendering);
  RegisterChannelAssociatedPoliciesForSameOriginPrerendering(map);
}

// Register mojo binder policies for preview mode for content/ interfaces.
void RegisterContentBinderPoliciesForPreview(MojoBinderPolicyMap& map) {
  RegisterNonAssociatedPolicies(map, PolicyClass::kPreview);
  // Inherits the policies for same-origin prerendering.
  // TODO(b:299240273): Adjust policies for preview.
  RegisterChannelAssociatedPoliciesForSameOriginPrerendering(map);
}

// A singleton class that stores the `MojoBinderPolicyMap` of interfaces which
// are obtained via `BrowserInterfaceBrowser` for frames.
// content/ initializes the policy map with predefined policies, then allows
// embedders to update the map.
class BrowserInterfaceBrokerMojoBinderPolicyMapHolder {
 public:
  BrowserInterfaceBrokerMojoBinderPolicyMapHolder() {
    RegisterContentBinderPoliciesForSameOriginPrerendering(same_origin_map_);
    GetContentClient()
        ->browser()
        ->RegisterMojoBinderPoliciesForSameOriginPrerendering(same_origin_map_);

    RegisterContentBinderPoliciesForPreview(preview_map_);
    GetContentClient()->browser()->RegisterMojoBinderPoliciesForPreview(
        preview_map_);
  }

  ~BrowserInterfaceBrokerMojoBinderPolicyMapHolder() = default;

  // Remove copy and move operations.
  BrowserInterfaceBrokerMojoBinderPolicyMapHolder(
      const BrowserInterfaceBrokerMojoBinderPolicyMapHolder& other) = delete;
  BrowserInterfaceBrokerMojoBinderPolicyMapHolder& operator=(
      const BrowserInterfaceBrokerMojoBinderPolicyMapHolder& other) = delete;
  BrowserInterfaceBrokerMojoBinderPolicyMapHolder(
      BrowserInterfaceBrokerMojoBinderPolicyMapHolder&&) = delete;
  BrowserInterfaceBrokerMojoBinderPolicyMapHolder& operator=(
      BrowserInterfaceBrokerMojoBinderPolicyMapHolder&&) = delete;

  const MojoBinderPolicyMapImpl* GetSameOriginPolicyMap() const {
    return &same_origin_map_;
  }
  const MojoBinderPolicyMapImpl* GetPreviewPolicyMap() const {
    return &preview_map_;
  }

 private:
  // TODO(crbug.com/40156088): Set default policy map for content/.
  // Changes to `same_origin_map_` require security review.
  MojoBinderPolicyMapImpl same_origin_map_;

  MojoBinderPolicyMapImpl preview_map_;
};

}  // namespace

MojoBinderPolicyMapImpl::MojoBinderPolicyMapImpl() = default;

MojoBinderPolicyMapImpl::MojoBinderPolicyMapImpl(
    const base::flat_map<std::string, MojoBinderNonAssociatedPolicy>& init_map)
    : non_associated_policy_map_(init_map) {}

MojoBinderPolicyMapImpl::~MojoBinderPolicyMapImpl() = default;

const MojoBinderPolicyMapImpl*
MojoBinderPolicyMapImpl::GetInstanceForSameOriginPrerendering() {
  static const base::NoDestructor<
      BrowserInterfaceBrokerMojoBinderPolicyMapHolder>
      map;

  return map->GetSameOriginPolicyMap();
}

const MojoBinderPolicyMapImpl*
MojoBinderPolicyMapImpl::GetInstanceForPreview() {
  static const base::NoDestructor<
      BrowserInterfaceBrokerMojoBinderPolicyMapHolder>
      map;

  return map->GetPreviewPolicyMap();
}

MojoBinderNonAssociatedPolicy
MojoBinderPolicyMapImpl::GetNonAssociatedMojoBinderPolicy(
    const std::string& interface_name,
    const MojoBinderNonAssociatedPolicy default_policy) const {
  const auto& found = non_associated_policy_map_.find(interface_name);
  if (found != non_associated_policy_map_.end())
    return found->second;
  return default_policy;
}

MojoBinderAssociatedPolicy
MojoBinderPolicyMapImpl::GetAssociatedMojoBinderPolicy(
    const std::string& interface_name,
    const MojoBinderAssociatedPolicy default_policy) const {
  const auto& found = associated_policy_map_.find(interface_name);
  if (found != associated_policy_map_.end())
    return found->second;
  return default_policy;
}

MojoBinderNonAssociatedPolicy
MojoBinderPolicyMapImpl::GetNonAssociatedMojoBinderPolicyOrDieForTesting(
    const std::string& interface_name) const {
  const auto& found = non_associated_policy_map_.find(interface_name);
  CHECK(found != non_associated_policy_map_.end(), base::NotFatalUntil::M130);
  return found->second;
}

MojoBinderAssociatedPolicy
MojoBinderPolicyMapImpl::GetAssociatedMojoBinderPolicyOrDieForTesting(
    const std::string& interface_name) const {
  const auto& found = associated_policy_map_.find(interface_name);
  CHECK(found != associated_policy_map_.end(), base::NotFatalUntil::M130);
  return found->second;
}

void MojoBinderPolicyMapImpl::SetPolicyByName(
    const std::string_view& name,
    MojoBinderNonAssociatedPolicy policy) {
  non_associated_policy_map_.emplace(name, policy);
}

void MojoBinderPolicyMapImpl::SetPolicyByName(
    const std::string_view& name,
    MojoBinderAssociatedPolicy policy) {
  associated_policy_map_.emplace(name, policy);
}

}  // namespace content