chromium/fuchsia_web/webengine/browser/frame_impl.cc

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

#include "fuchsia_web/webengine/browser/frame_impl.h"

#include <fidl/fuchsia.logger/cpp/fidl.h>
#include <fidl/fuchsia.logger/cpp/hlcpp_conversion.h>
#include <fidl/fuchsia.media.sessions2/cpp/hlcpp_conversion.h>
#include <fidl/fuchsia.ui.views/cpp/hlcpp_conversion.h>
#include <fuchsia/ui/gfx/cpp/fidl.h>
#include <lib/fpromise/result.h>
#include <lib/sys/cpp/component_context.h>

#include <limits>

#include "base/command_line.h"
#include "base/containers/contains.h"
#include "base/fuchsia/fuchsia_component_connect.h"
#include "base/fuchsia/fuchsia_logging.h"
#include "base/fuchsia/mem_buffer_util.h"
#include "base/fuchsia/process_context.h"
#include "base/functional/bind.h"
#include "base/json/json_writer.h"
#include "base/logging.h"
#include "base/metrics/user_metrics.h"
#include "base/strings/stringprintf.h"
#include "base/strings/utf_string_conversions.h"
#include "base/synchronization/lock.h"
#include "base/task/single_thread_task_runner.h"
#include "base/thread_annotations.h"
#include "base/time/time.h"
#include "base/trace_event/trace_event.h"
#include "build/chromecast_buildflags.h"
#include "content/public/browser/audio_stream_broker.h"
#include "content/public/browser/browser_accessibility_state.h"
#include "content/public/browser/browser_context.h"
#include "content/public/browser/browser_task_traits.h"
#include "content/public/browser/browser_thread.h"
#include "content/public/browser/host_zoom_map.h"
#include "content/public/browser/media_session.h"
#include "content/public/browser/message_port_provider.h"
#include "content/public/browser/navigation_entry.h"
#include "content/public/browser/navigation_handle.h"
#include "content/public/browser/permission_controller.h"
#include "content/public/browser/render_frame_host.h"
#include "content/public/browser/render_process_host.h"
#include "content/public/browser/render_widget_host.h"
#include "content/public/browser/render_widget_host_view.h"
#include "content/public/browser/renderer_preferences_util.h"
#include "content/public/browser/scoped_accessibility_mode.h"
#include "content/public/browser/web_contents.h"
#include "fuchsia_web/webengine/browser/context_impl.h"
#include "fuchsia_web/webengine/browser/event_filter.h"
#include "fuchsia_web/webengine/browser/frame_layout_manager.h"
#include "fuchsia_web/webengine/browser/frame_window_tree_host.h"
#include "fuchsia_web/webengine/browser/media_player_impl.h"
#include "fuchsia_web/webengine/browser/message_port.h"
#include "fuchsia_web/webengine/browser/navigation_policy_handler.h"
#include "fuchsia_web/webengine/browser/trace_event.h"
#include "fuchsia_web/webengine/browser/url_request_rewrite_type_converters.h"
#include "fuchsia_web/webengine/browser/web_engine_devtools_controller.h"
#include "media/mojo/mojom/audio_processing.mojom.h"
#include "mojo/public/cpp/bindings/associated_remote.h"
#include "mojo/public/cpp/system/platform_handle.h"
#include "net/base/net_errors.h"
#include "third_party/blink/public/common/associated_interfaces/associated_interface_provider.h"
#include "third_party/blink/public/common/logging/logging_utils.h"
#include "third_party/blink/public/common/messaging/web_message_port.h"
#include "third_party/blink/public/common/page/page_zoom.h"
#include "third_party/blink/public/common/permissions/permission_utils.h"
#include "third_party/blink/public/common/web_preferences/web_preferences.h"
#include "third_party/blink/public/mojom/loader/resource_load_info.mojom.h"
#include "third_party/blink/public/mojom/mediastream/media_stream.mojom.h"
#include "third_party/blink/public/mojom/navigation/was_activated_option.mojom.h"
#include "third_party/perfetto/include/perfetto/tracing/track_event_args.h"
#include "ui/aura/window.h"
#include "ui/compositor/compositor.h"
#include "ui/gfx/switches.h"
#include "ui/ozone/public/ozone_switches.h"
#include "ui/platform_window/fuchsia/view_ref_pair.h"
#include "ui/wm/core/base_focus_rules.h"
#include "url/gurl.h"
#include "url/origin.h"

#if BUILDFLAG(ENABLE_CAST_RECEIVER)
#include "components/cast_streaming/common/public/features.h"  //nogncheck
#include "components/cast_streaming/common/public/mojom/demuxer_connector.mojom.h"  //nogncheck
#include "components/cast_streaming/common/public/mojom/renderer_controller.mojom.h"  //nogncheck
#include "fuchsia_web/webengine/browser/receiver_session_client.h"  //nogncheck
#include "fuchsia_web/webengine/common/cast_streaming.h"            // nogncheck
#endif

namespace {

// Simulated screen bounds to use when headless rendering is enabled.
constexpr gfx::Size kHeadlessWindowSize = {1, 1};

// Name of the Inspect node that holds accessibility information.
constexpr char kAccessibilityInspectNodeName[] = "accessibility";

// A special value which matches all origins when specified in an origin list.
constexpr char kWildcardOrigin[] = "*";

// Used for attaching popup-related metadata to a WebContents.
constexpr char kPopupCreationInfo[] = "popup-creation-info";
class PopupFrameCreationInfoUserData : public base::SupportsUserData::Data {
 public:
  fuchsia::web::PopupFrameCreationInfo info;
};

class FrameFocusRules : public wm::BaseFocusRules {
 public:
  FrameFocusRules() = default;

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

  ~FrameFocusRules() override = default;

  // wm::BaseFocusRules implementation.
  bool SupportsChildActivation(const aura::Window*) const override;
};

bool FrameFocusRules::SupportsChildActivation(const aura::Window*) const {
  // TODO(crbug.com/40591214): Return a result based on window properties such
  // as visibility.
  return true;
}

// TODO(crbug.com/40710183): Use OnLoadScriptInjectorHost's origin matching
// code.
bool IsUrlMatchedByOriginList(const GURL& url,
                              const std::vector<std::string>& allowed_origins) {
  for (const std::string& origin : allowed_origins) {
    if (origin == kWildcardOrigin)
      return true;

    GURL origin_url(origin);
    if (!origin_url.is_valid()) {
      DLOG(WARNING)
          << "Ignored invalid origin spec when checking allowed list: "
          << origin;
      continue;
    }

    if (origin_url != url.DeprecatedGetOriginAsURL())
      continue;

    return true;
  }
  return false;
}

logging::LogSeverity FuchsiaWebConsoleLogLevelToLogSeverity(
    fuchsia::web::ConsoleLogLevel level) {
  switch (level) {
    case fuchsia::web::ConsoleLogLevel::DEBUG:
      return logging::LOGGING_VERBOSE;
    case fuchsia::web::ConsoleLogLevel::INFO:
      return logging::LOGGING_INFO;
    case fuchsia::web::ConsoleLogLevel::WARN:
      return logging::LOGGING_WARNING;
    case fuchsia::web::ConsoleLogLevel::ERROR:
      return logging::LOGGING_ERROR;
    case fuchsia::web::ConsoleLogLevel::NONE:
      return logging::LOGGING_NUM_SEVERITIES;
  }
}

logging::LogSeverity BlinkConsoleMessageLevelToLogSeverity(
    blink::mojom::ConsoleMessageLevel level) {
  switch (level) {
    case blink::mojom::ConsoleMessageLevel::kVerbose:
      return logging::LOGGING_VERBOSE;
    case blink::mojom::ConsoleMessageLevel::kInfo:
      return logging::LOGGING_INFO;
    case blink::mojom::ConsoleMessageLevel::kWarning:
      return logging::LOGGING_WARNING;
    case blink::mojom::ConsoleMessageLevel::kError:
      return logging::LOGGING_ERROR;
  }
}

bool IsHeadless() {
  return base::CommandLine::ForCurrentProcess()->HasSwitch(switches::kHeadless);
}

bool IsClonable(const fuchsia::web::CreateFrameParams& params) {
  fuchsia::web::CreateFrameParams cloned_params;
  return params.Clone(&cloned_params) == ZX_OK;
}

using FrameImplMap =
    base::small_map<std::map<content::WebContents*, FrameImpl*>>;

FrameImplMap& WebContentsToFrameImplMap() {
  static FrameImplMap frame_impl_map;
  return frame_impl_map;
}

blink::PermissionType FidlPermissionTypeToContentPermissionType(
    fuchsia::web::PermissionType fidl_type) {
  switch (fidl_type) {
    case fuchsia::web::PermissionType::MICROPHONE:
      return blink::PermissionType::AUDIO_CAPTURE;
    case fuchsia::web::PermissionType::CAMERA:
      return blink::PermissionType::VIDEO_CAPTURE;
    case fuchsia::web::PermissionType::PROTECTED_MEDIA_IDENTIFIER:
      return blink::PermissionType::PROTECTED_MEDIA_IDENTIFIER;
    case fuchsia::web::PermissionType::PERSISTENT_STORAGE:
      return blink::PermissionType::DURABLE_STORAGE;
  }
}

// Permission request callback for FrameImpl::RequestMediaAccessPermission.
void HandleMediaPermissionsRequestResult(
    const content::MediaStreamRequest& request,
    content::MediaResponseCallback callback,
    const std::vector<blink::mojom::PermissionStatus>& result) {
  // TODO(crbug.com/40216442): Generalize to multiple streams.
  blink::mojom::StreamDevicesPtr devices = blink::mojom::StreamDevices::New();

  int result_pos = 0;

  if (request.audio_type ==
      blink::mojom::MediaStreamType::DEVICE_AUDIO_CAPTURE) {
    if (result[result_pos] == blink::mojom::PermissionStatus::GRANTED) {
      devices->audio_device = blink::MediaStreamDevice(
          request.audio_type,
          request.requested_audio_device_ids.empty()
              ? ""
              : request.requested_audio_device_ids.front(),
          /*name=*/"");
    }
    result_pos++;
  }

  if (request.video_type ==
      blink::mojom::MediaStreamType::DEVICE_VIDEO_CAPTURE) {
    if (result[result_pos] == blink::mojom::PermissionStatus::GRANTED) {
      devices->video_device = blink::MediaStreamDevice(
          request.video_type,
          request.requested_video_device_ids.empty()
              ? ""
              : request.requested_video_device_ids.front(),
          /*name=*/"");
    }
  }

  blink::mojom::StreamDevicesSet stream_devices_set;
  if (devices->audio_device.has_value() || devices->video_device.has_value()) {
    stream_devices_set.stream_devices.emplace_back(std::move(devices));
  }
  std::move(callback).Run(
      stream_devices_set,
      stream_devices_set.stream_devices.empty()
          ? blink::mojom::MediaStreamRequestResult::NO_HARDWARE
          : blink::mojom::MediaStreamRequestResult::OK,
      nullptr);
}

std::optional<url::Origin> ParseAndValidateWebOrigin(
    const std::string& origin_str) {
  GURL origin_url(origin_str);
  if (!origin_url.username().empty() || !origin_url.password().empty() ||
      !origin_url.query().empty() || !origin_url.ref().empty()) {
    return std::nullopt;
  }

  if (!origin_url.path().empty() && origin_url.path() != "/")
    return std::nullopt;

  auto origin = url::Origin::Create(origin_url);
  if (origin.opaque())
    return std::nullopt;

  return origin;
}

int GetEffectFlagsForRenderUsage(fuchsia::media::AudioRenderUsage usage) {
  switch (usage) {
    case fuchsia::media::AudioRenderUsage::BACKGROUND:
      return media::AudioParameters::FUCHSIA_RENDER_USAGE_BACKGROUND;
    case fuchsia::media::AudioRenderUsage::MEDIA:
      return media::AudioParameters::FUCHSIA_RENDER_USAGE_MEDIA;
    case fuchsia::media::AudioRenderUsage::INTERRUPTION:
      return media::AudioParameters::FUCHSIA_RENDER_USAGE_INTERRUPTION;
    case fuchsia::media::AudioRenderUsage::SYSTEM_AGENT:
      return media::AudioParameters::FUCHSIA_RENDER_USAGE_SYSTEM_AGENT;
    case fuchsia::media::AudioRenderUsage::COMMUNICATION:
      return media::AudioParameters::FUCHSIA_RENDER_USAGE_COMMUNICATION;
  }
}

class AudioStreamBrokerFactory final
    : public content::AudioStreamBrokerFactory {
 public:
  AudioStreamBrokerFactory() : base_factory_(CreateImpl()) {
    DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
  }
  ~AudioStreamBrokerFactory() final {
    DCHECK_CURRENTLY_ON(content::BrowserThread::IO);
  }

  base::RepeatingCallback<void(fuchsia::media::AudioRenderUsage output_usage)>
  GetSetOutputUsagerCallback() {
    DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
    return base::BindRepeating(
        AudioStreamBrokerFactory::SetOutputUsageOnUIThread,
        weak_factory_.GetWeakPtr());
  }

  // contents::AudioStreamBrokerFactory implementation.
  std::unique_ptr<content::AudioStreamBroker> CreateAudioInputStreamBroker(
      int render_process_id,
      int render_frame_id,
      const std::string& device_id,
      const media::AudioParameters& params,
      uint32_t shared_memory_count,
      media::UserInputMonitorBase* user_input_monitor,
      bool enable_agc,
      media::mojom::AudioProcessingConfigPtr processing_config,
      content::AudioStreamBroker::DeleterCallback deleter,
      mojo::PendingRemote<blink::mojom::RendererAudioInputStreamFactoryClient>
          renderer_factory_client) final {
    return base_factory_->CreateAudioInputStreamBroker(
        render_process_id, render_frame_id, device_id, params,
        shared_memory_count, user_input_monitor, enable_agc,
        std::move(processing_config), std::move(deleter),
        std::move(renderer_factory_client));
  }

  std::unique_ptr<content::AudioStreamBroker> CreateAudioLoopbackStreamBroker(
      int render_process_id,
      int render_frame_id,
      content::AudioStreamBroker::LoopbackSource* source,
      const media::AudioParameters& params,
      uint32_t shared_memory_count,
      bool mute_source,
      content::AudioStreamBroker::DeleterCallback deleter,
      mojo::PendingRemote<blink::mojom::RendererAudioInputStreamFactoryClient>
          renderer_factory_client) final {
    return base_factory_->CreateAudioLoopbackStreamBroker(
        render_process_id, render_frame_id, source, params, shared_memory_count,
        mute_source, std::move(deleter), std::move(renderer_factory_client));
  }

  std::unique_ptr<content::AudioStreamBroker> CreateAudioOutputStreamBroker(
      int render_process_id,
      int render_frame_id,
      int stream_id,
      const std::string& output_device_id,
      const media::AudioParameters& params,
      const base::UnguessableToken& group_id,
      content::AudioStreamBroker::DeleterCallback deleter,
      mojo::PendingRemote<media::mojom::AudioOutputStreamProviderClient> client)
      final {
    DCHECK_CURRENTLY_ON(content::BrowserThread::IO);
    media::AudioParameters params_with_effects = params;
    if (output_usage_) {
      params_with_effects.set_effects(
          params.effects() |
          GetEffectFlagsForRenderUsage(output_usage_.value()));
    }
    return base_factory_->CreateAudioOutputStreamBroker(
        render_process_id, render_frame_id, stream_id, output_device_id,
        params_with_effects, group_id, std::move(deleter), std::move(client));
  }

 private:
  static void SetOutputUsageOnUIThread(
      base::WeakPtr<AudioStreamBrokerFactory> factory,
      fuchsia::media::AudioRenderUsage output_usage) {
    DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
    content::GetIOThreadTaskRunner({})->PostTask(
        FROM_HERE,
        base::BindOnce(&AudioStreamBrokerFactory::SetOutputUsageOnIOThread,
                       factory, output_usage));
  }

  void SetOutputUsageOnIOThread(fuchsia::media::AudioRenderUsage output_usage) {
    DCHECK_CURRENTLY_ON(content::BrowserThread::IO);
    output_usage_ = output_usage;
  }

  std::unique_ptr<content::AudioStreamBrokerFactory> base_factory_;
  std::optional<fuchsia::media::AudioRenderUsage> output_usage_;
  base::WeakPtrFactory<AudioStreamBrokerFactory> weak_factory_{this};
};

}  // namespace

FrameImpl::PendingPopup::PendingPopup(
    FrameImpl* frame_ptr,
    fidl::InterfaceHandle<fuchsia::web::Frame> handle,
    fuchsia::web::PopupFrameCreationInfo creation_info)
    : frame_ptr(std::move(frame_ptr)),
      handle(std::move(handle)),
      creation_info(std::move(creation_info)) {}
FrameImpl::PendingPopup::PendingPopup(PendingPopup&& other) = default;
FrameImpl::PendingPopup::~PendingPopup() = default;

// static
FrameImpl* FrameImpl::FromWebContents(content::WebContents* web_contents) {
  if (!web_contents)
    return nullptr;

  auto& map = WebContentsToFrameImplMap();
  auto it = map.find(web_contents);
  if (it == map.end())
    return nullptr;
  return it->second;
}

// static
FrameImpl* FrameImpl::FromRenderFrameHost(
    content::RenderFrameHost* render_frame_host) {
  return FromWebContents(
      content::WebContents::FromRenderFrameHost(render_frame_host));
}

FrameImpl::FrameImpl(std::unique_ptr<content::WebContents> web_contents,
                     ContextImpl* context,
                     fuchsia::web::CreateFrameParams params,
                     inspect::Node inspect_node,
                     fidl::InterfaceRequest<fuchsia::web::Frame> frame_request)
    : web_contents_(std::move(web_contents)),
      context_(context),
      console_log_tag_(params.has_debug_name() ? params.debug_name()
                                               : std::string()),
      params_for_popups_(std::move(params)),
      navigation_controller_(web_contents_.get(), this),
      permission_controller_(web_contents_.get()),
      binding_(this, std::move(frame_request)),
      media_blocker_(web_contents_.get()),
      theme_manager_(web_contents_.get(),
                     base::BindOnce(&FrameImpl::OnThemeManagerError,
                                    base::Unretained(this))),
      inspect_node_(std::move(inspect_node)),
      inspect_name_property_(
          params_for_popups_.has_debug_name()
              ? inspect_node_.CreateString("name",
                                           params_for_popups_.debug_name())
              : inspect::StringProperty()) {
  DCHECK(!WebContentsToFrameImplMap()[web_contents_.get()]);
  DCHECK(IsClonable(params));
  TRACE_EVENT(kWebEngineFidlCategory, "fuchsia.web/Frame created",
              perfetto::Flow::FromPointer(context_),
              perfetto::Flow::FromPointer(this));

  WebContentsToFrameImplMap()[web_contents_.get()] = this;

  web_contents_->SetDelegate(this);
  web_contents_->SetPageBaseBackgroundColor(SK_AlphaTRANSPARENT);
  Observe(web_contents_.get());

  url_request_rewrite_rules_manager_.AddWebContents(web_contents_.get());

  binding_.set_error_handler([this](zx_status_t status) {
    ZX_LOG_IF(ERROR, status != ZX_ERR_PEER_CLOSED, status)
        << " Frame disconnected.";
    context_->DestroyFrame(this);
  });

  content::UpdateFontRendererPreferencesFromSystemSettings(
      web_contents_->GetMutableRendererPrefs());
}

FrameImpl::~FrameImpl() {
  TRACE_EVENT(kWebEngineFidlCategory, "fuchsia.web/Frame destroyed",
              perfetto::TerminatingFlow::FromPointer(this));

  DestroyWindowTreeHost();
  context_->devtools_controller()->OnFrameDestroyed(web_contents_.get());

  auto& map = WebContentsToFrameImplMap();
  auto it = WebContentsToFrameImplMap().find(web_contents_.get());
  DCHECK(it != map.end() && it->second == this);
  map.erase(it);
}

void FrameImpl::EnableExplicitSitesFilter(std::string error_page) {
  explicit_sites_filter_error_page_ = std::move(error_page);
}

void FrameImpl::OverrideWebPreferences(
    blink::web_pref::WebPreferences* web_prefs) {
  if (content_area_settings_.has_hide_scrollbars()) {
    web_prefs->hide_scrollbars = content_area_settings_.hide_scrollbars();
  } else {
    // Verify that hide_scrollbars defaults to false, per FIDL API.
    DCHECK(!web_prefs->hide_scrollbars);
  }

  if (content_area_settings_.has_autoplay_policy()) {
    switch (content_area_settings_.autoplay_policy()) {
      case fuchsia::web::AutoplayPolicy::ALLOW:
        web_prefs->autoplay_policy =
            blink::mojom::AutoplayPolicy::kNoUserGestureRequired;
        break;
      case fuchsia::web::AutoplayPolicy::REQUIRE_USER_ACTIVATION:
        web_prefs->autoplay_policy =
            blink::mojom::AutoplayPolicy::kDocumentUserActivationRequired;
        break;
    }
  } else {
    // REQUIRE_USER_ACTIVATION is the default per the FIDL API.
    web_prefs->autoplay_policy =
        blink::mojom::AutoplayPolicy::kDocumentUserActivationRequired;
  }

  theme_manager_.ApplyThemeToWebPreferences(web_prefs);
}

zx::unowned_channel FrameImpl::GetBindingChannelForTest() const {
  return zx::unowned_channel(binding_.channel());
}

aura::Window* FrameImpl::root_window() const {
  return window_tree_host_->window();
}

void FrameImpl::ExecuteJavaScriptInternal(std::vector<std::string> origins,
                                          fuchsia::mem::Buffer script,
                                          ExecuteJavaScriptCallback callback,
                                          bool need_result) {
  if (!context_->IsJavaScriptInjectionAllowed()) {
    callback(fpromise::error(fuchsia::web::FrameError::INTERNAL_ERROR));
    return;
  }

  // Prevents script injection into the wrong document if the renderer recently
  // navigated to a different origin.
  if (!IsUrlMatchedByOriginList(web_contents_->GetLastCommittedURL(),
                                origins)) {
    callback(fpromise::error(fuchsia::web::FrameError::INVALID_ORIGIN));
    return;
  }

  std::optional<std::u16string> script_utf16 =
      base::ReadUTF8FromVMOAsUTF16(script);
  if (!script_utf16) {
    callback(fpromise::error(fuchsia::web::FrameError::BUFFER_NOT_UTF8));
    return;
  }

  content::RenderFrameHost::JavaScriptResultCallback result_callback;
  if (need_result) {
    result_callback = base::BindOnce(
        [](ExecuteJavaScriptCallback callback, base::Value result_value) {
          std::string result_json;
          if (!base::JSONWriter::Write(result_value, &result_json)) {
            callback(fpromise::error(fuchsia::web::FrameError::INTERNAL_ERROR));
            return;
          }

          callback(fpromise::ok(base::MemBufferFromString(
              std::move(result_json), "cr-execute-js-response")));
        },
        std::move(callback));
  }

  web_contents_->GetPrimaryMainFrame()->ExecuteJavaScript(
      *script_utf16, std::move(result_callback));

  if (!need_result) {
    // If no result is required then invoke callback() immediately.
    callback(fpromise::ok(fuchsia::mem::Buffer()));
  }
}

bool FrameImpl::IsWebContentsCreationOverridden(
    content::SiteInstance* source_site_instance,
    content::mojom::WindowContainerType window_container_type,
    const GURL& opener_url,
    const std::string& frame_name,
    const GURL& target_url) {
  // Specify a generous upper bound for unacknowledged popup windows, so that we
  // can catch bad client behavior while not interfering with normal operation.
  constexpr size_t kMaxPendingWebContentsCount = 10;

  if (!popup_listener_)
    return true;

  if (pending_popups_.size() >= kMaxPendingWebContentsCount) {
    // The content is producing popups faster than the embedder can process
    // them. Drop the popups so as to prevent resource exhaustion.
    LOG(WARNING) << "Too many pending popups, ignoring request.";

    // Don't produce a WebContents for this popup.
    return true;
  }

  return false;
}

content::WebContents* FrameImpl::AddNewContents(
    content::WebContents* source,
    std::unique_ptr<content::WebContents> new_contents,
    const GURL& target_url,
    WindowOpenDisposition disposition,
    const blink::mojom::WindowFeatures& window_features,
    bool user_gesture,
    bool* was_blocked) {
  DCHECK_EQ(source, web_contents_.get());

  // TODO(crbug.com/41476982): Add window disposition to the FIDL interface.
  switch (disposition) {
    case WindowOpenDisposition::NEW_FOREGROUND_TAB:
    case WindowOpenDisposition::NEW_BACKGROUND_TAB:
    case WindowOpenDisposition::NEW_PICTURE_IN_PICTURE:
    case WindowOpenDisposition::NEW_POPUP:
    case WindowOpenDisposition::NEW_WINDOW: {
      if (url_request_rewrite_rules_manager_.GetCachedRules()) {
        // There is no support for URL request rules rewriting with popups.
        *was_blocked = true;
        return nullptr;
      }

      auto* popup_creation_info =
          reinterpret_cast<PopupFrameCreationInfoUserData*>(
              new_contents->GetUserData(kPopupCreationInfo));
      fuchsia::web::PopupFrameCreationInfo popup_frame_creation_info =
          std::move(popup_creation_info->info);
      popup_frame_creation_info.set_initiated_by_user(user_gesture);
      // The PopupFrameCreationInfo won't be needed anymore, so clear it out.
      new_contents->SetUserData(kPopupCreationInfo, nullptr);

      // The constructor requires that the params can be cloned, so it cannot
      // fail here.
      fuchsia::web::CreateFrameParams params;
      zx_status_t status = params_for_popups_.Clone(&params);
      ZX_DCHECK(status == ZX_OK, status);
      fidl::InterfaceHandle<fuchsia::web::Frame> frame_handle;
      auto* popup_frame = context_->CreateFrameForWebContents(
          std::move(new_contents), std::move(params),
          frame_handle.NewRequest());

      fuchsia::web::ContentAreaSettings settings;
      status = content_area_settings_.Clone(&settings);
      ZX_DCHECK(status == ZX_OK, status);
      popup_frame->SetContentAreaSettings(std::move(settings));

      pending_popups_.emplace_back(popup_frame, std::move(frame_handle),
                                   std::move(popup_frame_creation_info));
      MaybeSendPopup();
      return nullptr;
    }

    // These kinds of windows don't produce Frames.
    case WindowOpenDisposition::CURRENT_TAB:
    case WindowOpenDisposition::SINGLETON_TAB:
    case WindowOpenDisposition::SAVE_TO_DISK:
    case WindowOpenDisposition::OFF_THE_RECORD:
    case WindowOpenDisposition::IGNORE_ACTION:
    case WindowOpenDisposition::SWITCH_TO_TAB:
    case WindowOpenDisposition::UNKNOWN:
      NOTIMPLEMENTED() << "Dropped new web contents (disposition: "
                       << static_cast<int>(disposition) << ")";
      return nullptr;
  }
}

void FrameImpl::WebContentsCreated(content::WebContents* source_contents,
                                   int opener_render_process_id,
                                   int opener_render_frame_id,
                                   const std::string& frame_name,
                                   const GURL& target_url,
                                   content::WebContents* new_contents) {
  auto creation_info = std::make_unique<PopupFrameCreationInfoUserData>();
  creation_info->info.set_initial_url(target_url.spec());
  new_contents->SetUserData(kPopupCreationInfo, std::move(creation_info));
}

void FrameImpl::MaybeSendPopup() {
  if (!popup_listener_)
    return;

  if (popup_ack_outstanding_ || pending_popups_.empty())
    return;

  auto popup = std::move(pending_popups_.front());
  pending_popups_.pop_front();

  popup_listener_->OnPopupFrameCreated(std::move(popup.handle),
                                       std::move(popup.creation_info), [this] {
                                         popup_ack_outstanding_ = false;
                                         MaybeSendPopup();
                                       });
  popup_ack_outstanding_ = true;
}

void FrameImpl::DestroyWindowTreeHost() {
  if (!window_tree_host_)
    return;

  aura::client::SetFocusClient(root_window(), nullptr);
  wm::SetActivationClient(root_window(), nullptr);
  root_window()->RemovePreTargetHandler(&event_filter_);
  root_window()->RemovePreTargetHandler(focus_controller_.get());
  web_contents_->GetNativeView()->Hide();
  window_tree_host_->Hide();
  window_tree_host_->compositor()->SetVisible(false);
  window_tree_host_.reset();
  accessibility_bridge_.reset();

  // Allows posted focus events to process before the FocusController is torn
  // down.
  content::GetUIThreadTaskRunner({})->DeleteSoon(FROM_HERE,
                                                 std::move(focus_controller_));
}

void FrameImpl::CloseAndDestroyFrame(zx_status_t error) {
  DCHECK(binding_.is_bound());
  binding_.Close(error);
  context_->DestroyFrame(this);
}

void FrameImpl::OnPopupListenerDisconnected(zx_status_t status) {
  ZX_LOG_IF(WARNING, status != ZX_ERR_PEER_CLOSED, status)
      << "Popup listener disconnected.";
  pending_popups_.clear();
}

void FrameImpl::OnMediaPlayerDisconnect() {
  media_player_ = nullptr;
}

bool FrameImpl::OnAccessibilityError(zx_status_t error) {
  // The task is posted so |accessibility_bridge_| does not tear |this| down
  // while events are still being processed.
  base::SingleThreadTaskRunner::GetCurrentDefault()->PostTask(
      FROM_HERE, base::BindOnce(&FrameImpl::CloseAndDestroyFrame,
                                weak_factory_.GetWeakPtr(), error));

  // The return value indicates to the accessibility bridge whether we should
  // attempt to reconnect. Since the frame has been destroyed, no reconnect
  // attempt should be made.
  return false;
}

#if BUILDFLAG(ENABLE_CAST_RECEIVER)
bool FrameImpl::MaybeHandleCastStreamingMessage(
    std::string* origin,
    fuchsia::web::WebMessage* message,
    PostMessageCallback* callback) {
  if (!context_->has_cast_streaming_enabled()) {
    return false;
  }

  if (!IsCastStreamingAppOrigin(*origin)) {
    return false;
  }

  if (receiver_session_client_ || !IsValidCastStreamingMessage(*message)) {
    // The Cast Streaming MessagePort should only be set once and |message|
    // should be a valid Cast Streaming Message.
    (*callback)(fpromise::error(fuchsia::web::FrameError::INVALID_ORIGIN));
    return true;
  }

  receiver_session_client_ = std::make_unique<ReceiverSessionClient>(
      std::move((*message->mutable_outgoing_transfer())[0].message_port()),
      IsCastStreamingVideoOnlyAppOrigin(*origin));
  (*callback)(fpromise::ok());
  return true;
}

void FrameImpl::MaybeStartCastStreaming(
    content::NavigationHandle* navigation_handle) {
  if (!context_->has_cast_streaming_enabled() || !receiver_session_client_ ||
      receiver_session_client_->HasReceiverSession()) {
    return;
  }

  mojo::AssociatedRemote<cast_streaming::mojom::DemuxerConnector>
      demuxer_connector;
  mojo::AssociatedRemote<cast_streaming::mojom::RendererController>
      renderer_controller;
  auto* remote_interfaces =
      navigation_handle->GetRenderFrameHost()->GetRemoteAssociatedInterfaces();
  remote_interfaces->GetInterface(&demuxer_connector);
  if (cast_streaming::IsCastRemotingEnabled()) {
    remote_interfaces->GetInterface(&renderer_controller);
  }
  receiver_session_client_->SetMojoEndpoints(std::move(demuxer_connector),
                                             std::move(renderer_controller));
}
#endif  // BUILDFLAG(ENABLE_CAST_RECEIVER)

void FrameImpl::UpdateRenderFrameZoomLevel(
    content::RenderFrameHost* render_frame_host) {
  float page_scale = content_area_settings_.has_page_scale()
                         ? content_area_settings_.page_scale()
                         : 1.0;
  content::HostZoomMap* host_zoom_map =
      content::HostZoomMap::GetForWebContents(web_contents_.get());
  host_zoom_map->SetTemporaryZoomLevel(
      render_frame_host->GetGlobalId(),
      blink::ZoomFactorToZoomLevel(page_scale));
}

void FrameImpl::ConnectToAccessibilityBridge() {
  // TODO(crbug.com/40212813): Replace callbacks with an interface that
  // FrameImpl implements.
  accessibility_bridge_ = std::make_unique<ui::AccessibilityBridgeFuchsiaImpl>(
      root_window(), fidl::HLCPPToNatural(window_tree_host_->CreateViewRef()),
      base::BindRepeating(&FrameImpl::SetAccessibilityEnabled,
                          base::Unretained(this)),
      base::BindRepeating(&FrameImpl::OnAccessibilityError,
                          base::Unretained(this)),
      inspect_node_.CreateChild(kAccessibilityInspectNodeName));
}

void FrameImpl::CreateView(fuchsia::ui::views::ViewToken view_token) {
  TRACE_EVENT(kWebEngineFidlCategory, "fuchsia.web/Frame.CreateView",
              perfetto::Flow::FromPointer(this));

  auto view_ref_pair = ui::ViewRefPair::New();
  CreateViewImpl(std::move(view_token), std::move(view_ref_pair.control_ref),
                 std::move(view_ref_pair.view_ref));
}

void FrameImpl::CreateViewWithViewRef(
    fuchsia::ui::views::ViewToken view_token,
    fuchsia::ui::views::ViewRefControl control_ref,
    fuchsia::ui::views::ViewRef view_ref) {
  TRACE_EVENT(kWebEngineFidlCategory, "fuchsia.web/Frame.CreateViewWithViewRef",
              perfetto::Flow::FromPointer(this));

  CreateViewImpl(std::move(view_token), std::move(control_ref),
                 std::move(view_ref));
}

void FrameImpl::CreateViewImpl(fuchsia::ui::views::ViewToken view_token,
                               fuchsia::ui::views::ViewRefControl control_ref,
                               fuchsia::ui::views::ViewRef view_ref) {
  if (IsHeadless()) {
    LOG(WARNING) << "CreateView() called on a HEADLESS Context.";
    CloseAndDestroyFrame(ZX_ERR_INVALID_ARGS);
    return;
  }

  if (!view_token.value.is_valid()) {
    LOG(WARNING) << "CreateView() called with invalid view_token.";
    CloseAndDestroyFrame(ZX_ERR_INVALID_ARGS);
    return;
  }

  // If a View to this Frame is already active then disconnect it.
  DestroyWindowTreeHost();

  ui::ViewRefPair view_ref_pair;
  view_ref_pair.control_ref = std::move(control_ref);
  view_ref_pair.view_ref = std::move(view_ref);
  SetupWindowTreeHost(std::move(view_token), std::move(view_ref_pair));

  ConnectToAccessibilityBridge();
}

void FrameImpl::CreateView2(fuchsia::web::CreateView2Args view_args) {
  TRACE_EVENT(kWebEngineFidlCategory, "fuchsia.web/Frame.CreateView2",
              perfetto::Flow::FromPointer(this));

  if (IsHeadless()) {
    LOG(WARNING) << "CreateView2() called on a HEADLESS Context.";
    CloseAndDestroyFrame(ZX_ERR_INVALID_ARGS);
    return;
  }

  if (!view_args.has_view_creation_token() ||
      !view_args.view_creation_token().value.is_valid()) {
    LOG(WARNING) << "CreateView2() called with invalid view_creation_token.";
    CloseAndDestroyFrame(ZX_ERR_INVALID_ARGS);
    return;
  }

  // If a View to this Frame is already active then disconnect it.
  DestroyWindowTreeHost();

  auto view_ref_pair = ui::ViewRefPair::New();
  SetupWindowTreeHost(std::move(*view_args.mutable_view_creation_token()),
                      std::move(view_ref_pair));

  ConnectToAccessibilityBridge();
}

void FrameImpl::GetMediaPlayer(
    fidl::InterfaceRequest<fuchsia::media::sessions2::Player> player) {
  TRACE_EVENT(kWebEngineFidlCategory, "fuchsia.web/Frame.GetMediaPlayer",
              perfetto::Flow::FromPointer(this));

  media_player_ = std::make_unique<MediaPlayerImpl>(
      content::MediaSession::Get(web_contents_.get()),
      fidl::HLCPPToNatural(player),
      base::BindOnce(&FrameImpl::OnMediaPlayerDisconnect,
                     base::Unretained(this)));
}

void FrameImpl::GetNavigationController(
    fidl::InterfaceRequest<fuchsia::web::NavigationController> controller) {
  TRACE_EVENT(kWebEngineFidlCategory,
              "fuchsia.web/Frame.GetNavigationController",
              perfetto::Flow::FromPointer(this));

  navigation_controller_.AddBinding(std::move(controller));
}

void FrameImpl::ExecuteJavaScript(std::vector<std::string> origins,
                                  fuchsia::mem::Buffer script,
                                  ExecuteJavaScriptCallback callback) {
  TRACE_EVENT(kWebEngineFidlCategory, "fuchsia.web/Frame.ExecuteJavaScript",
              perfetto::Flow::FromPointer(this));

  ExecuteJavaScriptInternal(std::move(origins), std::move(script),
                            std::move(callback), true);
}

void FrameImpl::ExecuteJavaScriptNoResult(
    std::vector<std::string> origins,
    fuchsia::mem::Buffer script,
    ExecuteJavaScriptNoResultCallback callback) {
  TRACE_EVENT(kWebEngineFidlCategory,
              "fuchsia.web/Frame.ExecuteJavaScriptNoResult",
              perfetto::Flow::FromPointer(this));

  ExecuteJavaScriptInternal(
      std::move(origins), std::move(script),
      [callback = std::move(callback)](
          fuchsia::web::Frame_ExecuteJavaScript_Result result_with_value) {
        if (result_with_value.is_err()) {
          callback(fpromise::error(result_with_value.err()));
        } else {
          callback(fpromise::ok());
        }
      },
      false);
}

void FrameImpl::AddBeforeLoadJavaScript(
    uint64_t id,
    std::vector<std::string> origins,
    fuchsia::mem::Buffer script,
    AddBeforeLoadJavaScriptCallback callback) {
  TRACE_EVENT(kWebEngineFidlCategory,
              "fuchsia.web/Frame.AddBeforeLoadJavaScript",
              perfetto::Flow::FromPointer(this));

  if (!context_->IsJavaScriptInjectionAllowed()) {
    callback(fpromise::error(fuchsia::web::FrameError::INTERNAL_ERROR));
    return;
  }

  std::optional<std::string> script_as_string =
      base::StringFromMemBuffer(script);
  if (!script_as_string) {
    LOG(ERROR) << "Couldn't read script from buffer.";
    callback(fpromise::error(fuchsia::web::FrameError::INTERNAL_ERROR));
    return;
  }

  // TODO(crbug.com/40707541): Only allow wildcards to be specified standalone.
  if (base::Contains(origins, kWildcardOrigin)) {
    script_injector_.AddScriptForAllOrigins(id, *script_as_string);
  } else {
    std::vector<url::Origin> origins_converted;
    for (const std::string& origin : origins) {
      url::Origin origin_parsed = url::Origin::Create(GURL(origin));
      if (origin_parsed.opaque()) {
        callback(fpromise::error(fuchsia::web::FrameError::INVALID_ORIGIN));
        return;
      }
      origins_converted.push_back(origin_parsed);
    }

    script_injector_.AddScript(id, origins_converted, *script_as_string);
  }

  callback(fpromise::ok());
}

void FrameImpl::RemoveBeforeLoadJavaScript(uint64_t id) {
  TRACE_EVENT(kWebEngineFidlCategory,
              "fuchsia.web/Frame.RemoveBeforeLoadJavaScript",
              perfetto::Flow::FromPointer(this));

  script_injector_.RemoveScript(id);
}

void FrameImpl::PostMessage(std::string origin,
                            fuchsia::web::WebMessage message,
                            PostMessageCallback callback) {
  TRACE_EVENT(kWebEngineFidlCategory, "fuchsia.web/Frame.PostMessage",
              perfetto::Flow::FromPointer(this));

#if BUILDFLAG(ENABLE_CAST_RECEIVER)
  if (MaybeHandleCastStreamingMessage(&origin, &message, &callback))
    return;
#endif

  fuchsia::web::Frame_PostMessage_Result result;
  if (origin.empty()) {
    callback(fpromise::error(fuchsia::web::FrameError::INVALID_ORIGIN));
    return;
  }

  if (!message.has_data()) {
    callback(fpromise::error(fuchsia::web::FrameError::NO_DATA_IN_MESSAGE));
    return;
  }

  std::optional<std::u16string> origin_utf16;
  if (origin != kWildcardOrigin)
    origin_utf16 = base::UTF8ToUTF16(origin);

  std::optional<std::u16string> data_utf16 =
      base::ReadUTF8FromVMOAsUTF16(message.data());
  if (!data_utf16) {
    callback(fpromise::error(fuchsia::web::FrameError::BUFFER_NOT_UTF8));
    return;
  }

  // Convert and pass along any MessagePorts contained in the message.
  std::vector<blink::WebMessagePort> message_ports;
  if (message.has_outgoing_transfer()) {
    for (const fuchsia::web::OutgoingTransferable& outgoing :
         message.outgoing_transfer()) {
      if (!outgoing.is_message_port()) {
        callback(fpromise::error(fuchsia::web::FrameError::INTERNAL_ERROR));
        return;
      }
    }

    for (fuchsia::web::OutgoingTransferable& outgoing :
         *message.mutable_outgoing_transfer()) {
      blink::WebMessagePort blink_port =
          BlinkMessagePortFromFidl(std::move(outgoing.message_port()));
      if (!blink_port.IsValid()) {
        callback(fpromise::error(fuchsia::web::FrameError::INTERNAL_ERROR));
        return;
      }
      message_ports.push_back(std::move(blink_port));
    }
  }

  content::MessagePortProvider::PostMessageToFrame(
      web_contents_->GetPrimaryPage(), std::u16string(), origin_utf16,
      std::move(*data_utf16), std::move(message_ports));
  callback(fpromise::ok());
}

void FrameImpl::SetNavigationEventListener(
    fidl::InterfaceHandle<fuchsia::web::NavigationEventListener> listener) {
  SetNavigationEventListener2(std::move(listener), /*flags=*/{});
}

void FrameImpl::SetNavigationEventListener2(
    fidl::InterfaceHandle<fuchsia::web::NavigationEventListener> listener,
    fuchsia::web::NavigationEventListenerFlags flags) {
  TRACE_EVENT(kWebEngineFidlCategory,
              "fuchsia.web/Frame.SetNavigationEventListener",
              perfetto::Flow::FromPointer(this));

  navigation_controller_.SetEventListener(std::move(listener), flags);
}

void FrameImpl::SetJavaScriptLogLevel(fuchsia::web::ConsoleLogLevel level) {
  TRACE_EVENT(kWebEngineFidlCategory, "fuchsia.web/Frame.SetJavaScriptLogLevel",
              perfetto::Flow::FromPointer(this));

  log_level_ = FuchsiaWebConsoleLogLevelToLogSeverity(level);
}

void FrameImpl::SetConsoleLogSink(fuchsia::logger::LogSinkHandle sink) {
  TRACE_EVENT(kWebEngineFidlCategory, "fuchsia.web/Frame.SetConsoleLogSink",
              perfetto::Flow::FromPointer(this));

  if (sink) {
    console_logger_ = base::ScopedFxLogger::CreateFromLogSink(
        fidl::HLCPPToNatural(sink), {console_log_tag_});
  } else {
    console_logger_ = {};
  }
}

void FrameImpl::ConfigureInputTypes(fuchsia::web::InputTypes types,
                                    fuchsia::web::AllowInputState allow) {
  TRACE_EVENT(kWebEngineFidlCategory, "fuchsia.web/Frame.ConfigureInputTypes",
              perfetto::Flow::FromPointer(this));

  event_filter_.ConfigureInputTypes(types, allow);
}

void FrameImpl::SetPopupFrameCreationListener(
    fidl::InterfaceHandle<fuchsia::web::PopupFrameCreationListener> listener) {
  TRACE_EVENT(kWebEngineFidlCategory,
              "fuchsia.web/Frame.SetPopupFrameCreationListener",
              perfetto::Flow::FromPointer(this));

  popup_listener_ = listener.Bind();
  popup_listener_.set_error_handler(
      fit::bind_member(this, &FrameImpl::OnPopupListenerDisconnected));
}

void FrameImpl::SetUrlRequestRewriteRules(
    std::vector<fuchsia::web::UrlRequestRewriteRule> rules,
    SetUrlRequestRewriteRulesCallback callback) {
  TRACE_EVENT(kWebEngineFidlCategory,
              "fuchsia.web/Frame.SetUrlRequestRewriteRules",
              perfetto::Flow::FromPointer(this));

  auto mojom_rules =
      mojo::ConvertTo<url_rewrite::mojom::UrlRequestRewriteRulesPtr>(
          std::move(rules));
  if (url_request_rewrite_rules_manager_.OnRulesUpdated(
          std::move(mojom_rules))) {
    std::move(callback)();
  } else {
    CloseAndDestroyFrame(ZX_ERR_INVALID_ARGS);
  }
}

void FrameImpl::EnableHeadlessRendering() {
  TRACE_EVENT(kWebEngineFidlCategory,
              "fuchsia.web/Frame.EnableHeadlessRendering",
              perfetto::Flow::FromPointer(this));

  if (!IsHeadless()) {
    LOG(ERROR) << "EnableHeadlessRendering() on non-HEADLESS Context.";
    CloseAndDestroyFrame(ZX_ERR_INVALID_ARGS);
    return;
  }

  auto view_ref_pair = ui::ViewRefPair::New();
  SetupWindowTreeHost(fuchsia::ui::views::ViewToken(),
                      std::move(view_ref_pair));

  gfx::Rect bounds(kHeadlessWindowSize);

  if (window_size_for_test_) {
    ConnectToAccessibilityBridge();
    bounds.set_size(*window_size_for_test_);
  }

  window_tree_host_->SetBoundsInPixels(bounds);

  // FrameWindowTreeHost will Show() itself when the View is attached, but
  // in headless mode there is no View, so Show() it explicitly.
  window_tree_host_->Show();
}

void FrameImpl::DisableHeadlessRendering() {
  TRACE_EVENT(kWebEngineFidlCategory,
              "fuchsia.web/Frame.DisableHeadlessRendering",
              perfetto::Flow::FromPointer(this));

  if (!IsHeadless()) {
    LOG(ERROR)
        << "Attempted to disable headless rendering on non-HEADLESS Context.";
    CloseAndDestroyFrame(ZX_ERR_INVALID_ARGS);
    return;
  }

  DestroyWindowTreeHost();
}

void FrameImpl::SetupWindowTreeHost(fuchsia::ui::views::ViewToken view_token,
                                    ui::ViewRefPair view_ref_pair) {
  DCHECK(!window_tree_host_);

  window_tree_host_ = std::make_unique<FrameWindowTreeHost>(
      std::move(view_token), std::move(view_ref_pair), web_contents_.get(),
      base::BindRepeating(&FrameImpl::OnPixelScaleUpdate,
                          base::Unretained(this)));

  InitWindowTreeHost();
}

void FrameImpl::SetupWindowTreeHost(
    fuchsia::ui::views::ViewCreationToken view_creation_token,
    ui::ViewRefPair view_ref_pair) {
  DCHECK(!window_tree_host_);

  window_tree_host_ = std::make_unique<FrameWindowTreeHost>(
      std::move(view_creation_token), std::move(view_ref_pair),
      web_contents_.get(),
      base::BindRepeating(&FrameImpl::OnPixelScaleUpdate,
                          base::Unretained(this)));

  InitWindowTreeHost();
}

void FrameImpl::InitWindowTreeHost() {
  DCHECK(window_tree_host_);

  window_tree_host_->InitHost();
  root_window()->AddPreTargetHandler(&event_filter_);

  // Add hooks which automatically set the focus state when input events are
  // received.
  focus_controller_ =
      std::make_unique<wm::FocusController>(new FrameFocusRules);
  root_window()->AddPreTargetHandler(focus_controller_.get());
  aura::client::SetFocusClient(root_window(), focus_controller_.get());

  wm::SetActivationClient(root_window(), focus_controller_.get());

  layout_manager_ =
      root_window()->SetLayoutManager(std::make_unique<FrameLayoutManager>());
  if (!render_size_override_.IsEmpty())
    layout_manager_->ForceContentDimensions(render_size_override_);

  root_window()->AddChild(web_contents_->GetNativeView());
  web_contents_->GetNativeView()->Show();

  // FrameWindowTreeHost will Show() itself when the View is actually attached
  // to the view-tree to be displayed. See https://crbug.com/1109270
}

void FrameImpl::SetMediaSettings(
    fuchsia::web::FrameMediaSettings media_settings) {
  TRACE_EVENT(kWebEngineFidlCategory, "fuchsia.web/Frame.SetMediaSettings",
              perfetto::Flow::FromPointer(this));

  media_settings_ = std::move(media_settings);
  if (media_settings.has_renderer_usage() && set_audio_output_usage_callback_)
    set_audio_output_usage_callback_.Run(media_settings.renderer_usage());
}

void FrameImpl::ForceContentDimensions(
    std::unique_ptr<fuchsia::ui::gfx::vec2> web_dips) {
  TRACE_EVENT(kWebEngineFidlCategory,
              "fuchsia.web/Frame.ForceContentDimensions",
              perfetto::Flow::FromPointer(this));

  if (!web_dips) {
    render_size_override_ = {};
    if (layout_manager_)
      layout_manager_->ForceContentDimensions({});
    return;
  }

  gfx::Size web_dips_converted(web_dips->x, web_dips->y);
  if (web_dips_converted.IsEmpty()) {
    LOG(ERROR) << "Rejecting zero-area size for ForceContentDimensions().";
    CloseAndDestroyFrame(ZX_ERR_INVALID_ARGS);
    return;
  }

  render_size_override_ = web_dips_converted;
  if (layout_manager_)
    layout_manager_->ForceContentDimensions(web_dips_converted);
}

void FrameImpl::SetPermissionState(
    fuchsia::web::PermissionDescriptor fidl_permission,
    std::string web_origin_string,
    fuchsia::web::PermissionState fidl_state) {
  TRACE_EVENT(kWebEngineFidlCategory, "fuchsia.web/Frame.SetPermissionState",
              perfetto::Flow::FromPointer(this));

  if (!fidl_permission.has_type()) {
    LOG(ERROR) << "PermissionDescriptor.type is not specified in "
                  "SetPermissionState().";
    CloseAndDestroyFrame(ZX_ERR_INVALID_ARGS);
    return;
  }

  blink::PermissionType type =
      FidlPermissionTypeToContentPermissionType(fidl_permission.type());

  blink::mojom::PermissionStatus state =
      (fidl_state == fuchsia::web::PermissionState::GRANTED)
          ? blink::mojom::PermissionStatus::GRANTED
          : blink::mojom::PermissionStatus::DENIED;

  // TODO(crbug.com/40724536): Remove this once the PermissionManager API is
  // available.
  if (web_origin_string == "*" &&
      type == blink::PermissionType::PROTECTED_MEDIA_IDENTIFIER) {
    permission_controller_.SetDefaultPermissionState(type, state);
    return;
  }

  // Handle per-origin permissions specifications.
  auto web_origin = ParseAndValidateWebOrigin(web_origin_string);
  if (!web_origin) {
    LOG(ERROR) << "SetPermissionState() called with invalid web_origin: "
               << web_origin_string;
    CloseAndDestroyFrame(ZX_ERR_INVALID_ARGS);
    return;
  }

  permission_controller_.SetPermissionState(type, web_origin.value(), state);
}

void FrameImpl::GetPrivateMemorySize(GetPrivateMemorySizeCallback callback) {
  TRACE_EVENT(kWebEngineFidlCategory, "fuchsia.web/Frame.GetPrivateMemorySize",
              perfetto::Flow::FromPointer(this));

  if (!web_contents_->GetPrimaryMainFrame()->GetProcess()->IsReady()) {
    // Renderer process is not yet started.
    callback(0);
    return;
  }

  zx_info_task_stats_t task_stats;
  zx_status_t status = zx_object_get_info(
      web_contents_->GetPrimaryMainFrame()->GetProcess()->GetProcess().Handle(),
      ZX_INFO_TASK_STATS, &task_stats, sizeof(task_stats), nullptr, nullptr);

  if (status != ZX_OK) {
    // Fail gracefully by returning zero.
    ZX_LOG(WARNING, status) << "zx_object_get_info(ZX_INFO_TASK_STATS)";
    callback(0);
    return;
  }

  callback(task_stats.mem_private_bytes);
}

void FrameImpl::SetNavigationPolicyProvider(
    fuchsia::web::NavigationPolicyProviderParams params,
    fidl::InterfaceHandle<fuchsia::web::NavigationPolicyProvider> provider) {
  TRACE_EVENT(kWebEngineFidlCategory,
              "fuchsia.web/Frame.SetNavigationPolicyProvider",
              perfetto::Flow::FromPointer(this));

  navigation_policy_handler_ = std::make_unique<NavigationPolicyHandler>(
      std::move(params), std::move(provider));
}

void FrameImpl::SetContentAreaSettings(
    fuchsia::web::ContentAreaSettings settings) {
  TRACE_EVENT(kWebEngineFidlCategory,
              "fuchsia.web/Frame.SetContentAreaSettings",
              perfetto::Flow::FromPointer(this));

  if (settings.has_hide_scrollbars())
    content_area_settings_.set_hide_scrollbars(settings.hide_scrollbars());
  if (settings.has_autoplay_policy())
    content_area_settings_.set_autoplay_policy(settings.autoplay_policy());
  if (settings.has_theme()) {
    content_area_settings_.set_theme(settings.theme());
    theme_manager_.SetTheme(settings.theme());
  }
  if (settings.has_page_scale()) {
    if (settings.page_scale() <= 0.0) {
      LOG(ERROR) << "SetPageScale() called with nonpositive scale.";
      CloseAndDestroyFrame(ZX_ERR_INVALID_ARGS);
      return;
    }
    if (!(content_area_settings_.has_page_scale() &&
          (settings.page_scale() == content_area_settings_.page_scale()))) {
      content_area_settings_.set_page_scale(settings.page_scale());
      UpdateRenderFrameZoomLevel(web_contents_->GetPrimaryMainFrame());
    }
  }

  web_contents_->OnWebPreferencesChanged();
}

void FrameImpl::ResetContentAreaSettings() {
  TRACE_EVENT(kWebEngineFidlCategory,
              "fuchsia.web/Frame.ResetContentAreaSettings",
              perfetto::Flow::FromPointer(this));

  content_area_settings_ = fuchsia::web::ContentAreaSettings();
  web_contents_->OnWebPreferencesChanged();
  UpdateRenderFrameZoomLevel(web_contents_->GetPrimaryMainFrame());
}

void FrameImpl::Close(fuchsia::web::FrameCloseRequest request) {
  // By default allow a couple of seconds in case the page content needs to
  // e.g. collate metrics and send them to the network.
  constexpr auto kDefaultFrameCloseTimeout = base::Seconds(2u);

  auto timeout = request.has_timeout()
                     ? base::TimeDelta::FromZxDuration(request.timeout())
                     : kDefaultFrameCloseTimeout;

  // If the content does not need any handlers to be run, or a zero timeout was
  // specified, then teardown the content immediately and close.
  if (!web_contents_->NeedToFireBeforeUnloadOrUnloadEvents() ||
      timeout.is_zero()) {
    CloseAndDestroyFrame(ZX_OK);
    return;
  }

  // Request that `web_contents_` allow the page to gracefully teardown:
  // - Destroy the WindowTreeHost, causing the page to receive "pagehide" and
  //   "visibilitychange" events.
  // - Fire the "beforeunload" event, ignoring the result.
  // - Fire the "onunload" event, and teardown the page if that completes.
  DestroyWindowTreeHost();
  web_contents_->DispatchBeforeUnload(false /* auto_cancel */);
  web_contents_->ClosePage();

  // (Re-)start the teardown timeout. If the page closes before this timer
  // fires then `CloseContents()` will be invoked, causing the `Frame` to be
  // closed with `ZX_OK`.
  close_page_timeout_.Start(
      FROM_HERE, timeout,
      base::BindOnce(&FrameImpl::CloseAndDestroyFrame, base::Unretained(this),
                     ZX_ERR_TIMED_OUT));
}

void FrameImpl::CloseContents(content::WebContents* source) {
  DCHECK_EQ(source, web_contents_.get());
  CloseAndDestroyFrame(ZX_OK);
}

void FrameImpl::SetBlockMediaLoading(bool blocked) {
  TRACE_EVENT(kWebEngineFidlCategory, "fuchsia.web/Frame.SetBlockMediaLoading",
              perfetto::Flow::FromPointer(this));

  media_blocker_.BlockMediaLoading(blocked);
}

bool FrameImpl::DidAddMessageToConsole(
    content::WebContents* source,
    blink::mojom::ConsoleMessageLevel log_level,
    const std::u16string& message,
    int32_t line_no,
    const std::u16string& source_id) {
  // Assert that log severities are strictly ascending, before using numerical
  // comparison to determine whether to emit a log.
  static_assert(logging::LOGGING_VERBOSE < logging::LOGGING_INFO);
  static_assert(logging::LOGGING_INFO < logging::LOGGING_WARNING);
  static_assert(logging::LOGGING_WARNING < logging::LOGGING_ERROR);
  static_assert(logging::LOGGING_ERROR < logging::LOGGING_NUM_SEVERITIES);

  logging::LogSeverity severity =
      BlinkConsoleMessageLevelToLogSeverity(log_level);
  if (severity < log_level_) {
    // Prevent the default logging mechanism from logging the message.
    return true;
  }

  if (!console_logger_.is_valid()) {
    // Log via the process' LogSink service if none was set on the Frame.
    // Connect on-demand, so that embedders need not provide a LogSink in the
    // CreateContextParams services, unless they actually enable logging.
    auto log_sink_client_end =
        base::fuchsia_component::Connect<fuchsia_logger::LogSink>();
    if (log_sink_client_end.is_error()) {
      DLOG(ERROR) << base::FidlConnectionErrorMessage(log_sink_client_end);
      return false;
    }
    console_logger_ = base::ScopedFxLogger::CreateFromLogSink(
        std::move(log_sink_client_end.value()), {console_log_tag_});

    if (!console_logger_.is_valid())
      return false;
  }

  std::string source_id_utf8 = base::UTF16ToUTF8(source_id);
  std::string message_utf8 = base::UTF16ToUTF8(message);
  console_logger_.LogMessage(source_id_utf8, line_no, message_utf8, severity);

  return true;
}

void FrameImpl::RequestMediaAccessPermission(
    content::WebContents* web_contents,
    const content::MediaStreamRequest& request,
    content::MediaResponseCallback callback) {
  DCHECK_EQ(web_contents_.get(), web_contents);

  std::vector<blink::PermissionType> permissions;

  if (request.audio_type ==
      blink::mojom::MediaStreamType::DEVICE_AUDIO_CAPTURE) {
    permissions.push_back(blink::PermissionType::AUDIO_CAPTURE);
  } else if (request.audio_type != blink::mojom::MediaStreamType::NO_SERVICE) {
    std::move(callback).Run(
        blink::mojom::StreamDevicesSet(),
        blink::mojom::MediaStreamRequestResult::NOT_SUPPORTED, nullptr);
    return;
  }

  if (request.video_type ==
      blink::mojom::MediaStreamType::DEVICE_VIDEO_CAPTURE) {
    permissions.push_back(blink::PermissionType::VIDEO_CAPTURE);
  } else if (request.video_type != blink::mojom::MediaStreamType::NO_SERVICE) {
    std::move(callback).Run(
        blink::mojom::StreamDevicesSet(),
        blink::mojom::MediaStreamRequestResult::NOT_SUPPORTED, nullptr);
    return;
  }

  auto* render_frame_host = content::RenderFrameHost::FromID(
      request.render_process_id, request.render_frame_id);
  if (!render_frame_host) {
    std::move(callback).Run(
        blink::mojom::StreamDevicesSet(),
        blink::mojom::MediaStreamRequestResult::INVALID_STATE, nullptr);
    return;
  }

  if (url::Origin::Create(request.security_origin) !=
      render_frame_host->GetLastCommittedOrigin()) {
    std::move(callback).Run(
        blink::mojom::StreamDevicesSet(),
        blink::mojom::MediaStreamRequestResult::INVALID_SECURITY_ORIGIN,
        nullptr);
    return;
  }

  content::PermissionController* permission_controller =
      web_contents_->GetBrowserContext()->GetPermissionController();
  DCHECK(permission_controller);

  permission_controller->RequestPermissionsFromCurrentDocument(
      render_frame_host,
      content::PermissionRequestDescription(permissions, request.user_gesture),
      base::BindOnce(&HandleMediaPermissionsRequestResult, request,
                     std::move(callback)));
}

bool FrameImpl::CheckMediaAccessPermission(
    content::RenderFrameHost* render_frame_host,
    const url::Origin& security_origin,
    blink::mojom::MediaStreamType type) {
  blink::PermissionType permission;
  switch (type) {
    case blink::mojom::MediaStreamType::DEVICE_AUDIO_CAPTURE:
      permission = blink::PermissionType::AUDIO_CAPTURE;
      break;
    case blink::mojom::MediaStreamType::DEVICE_VIDEO_CAPTURE:
      permission = blink::PermissionType::VIDEO_CAPTURE;
      break;
    default:
      NOTREACHED_IN_MIGRATION();
      return false;
  }

  // TODO(crbug.com/40223767): Remove `security_origin`.
  if (security_origin != render_frame_host->GetLastCommittedOrigin()) {
    return false;
  }

  content::PermissionController* permission_controller =
      web_contents_->GetBrowserContext()->GetPermissionController();
  DCHECK(permission_controller);

  return permission_controller->GetPermissionStatusForCurrentDocument(
             permission, render_frame_host) ==
         blink::mojom::PermissionStatus::GRANTED;
}

std::unique_ptr<content::AudioStreamBrokerFactory>
FrameImpl::CreateAudioStreamBrokerFactory(content::WebContents* web_contents) {
  DCHECK_EQ(web_contents, web_contents_.get());

  auto result = std::make_unique<AudioStreamBrokerFactory>();

  // Save callback to use to pass renderer usage to the factory in the future.
  set_audio_output_usage_callback_ = result->GetSetOutputUsagerCallback();
  if (media_settings_.has_renderer_usage())
    set_audio_output_usage_callback_.Run(media_settings_.renderer_usage());

  return result;
}

bool FrameImpl::CanOverscrollContent() {
  // Don't process "overscroll" events (e.g. pull-to-refresh, swipe back,
  // swipe forward).
  // TODO(crbug.com/40748448): Add overscroll toggle to Frame API.
  return false;
}

void FrameImpl::ReadyToCommitNavigation(
    content::NavigationHandle* navigation_handle) {
  if (!navigation_handle->IsInPrimaryMainFrame() ||
      navigation_handle->IsSameDocument() || navigation_handle->IsErrorPage()) {
    return;
  }

  script_injector_.InjectScriptsForURL(navigation_handle->GetURL(),
                                       navigation_handle->GetRenderFrameHost());

#if BUILDFLAG(ENABLE_CAST_RECEIVER)
  MaybeStartCastStreaming(navigation_handle);
#endif
}

void FrameImpl::DidFinishLoad(content::RenderFrameHost* render_frame_host,
                              const GURL& validated_url) {
  context_->devtools_controller()->OnFrameLoaded(web_contents_.get());
}

void FrameImpl::RenderFrameCreated(content::RenderFrameHost* frame_host) {
  // The top-level frame is given a transparent background color.
  // GetView() is guaranteed to be non-null until |frame_host| teardown.
  if (!frame_host->GetParentOrOuterDocument()) {
    frame_host->GetView()->SetBackgroundColor(SK_AlphaTRANSPARENT);
  }
}

void FrameImpl::RenderFrameHostChanged(content::RenderFrameHost* old_host,
                                       content::RenderFrameHost* new_host) {
  // UpdateRenderFrameZoomLevel() sets temporary zoom level for the current
  // RenderFrame. It needs to be called again whenever main RenderFrame is
  // changed.
  if (new_host->IsInPrimaryMainFrame())
    UpdateRenderFrameZoomLevel(new_host);
}

void FrameImpl::DidFirstVisuallyNonEmptyPaint() {
  base::RecordComputedAction("AppFirstPaint");
}

void FrameImpl::ResourceLoadComplete(
    content::RenderFrameHost* render_frame_host,
    const content::GlobalRequestID& request_id,
    const blink::mojom::ResourceLoadInfo& resource_load_info) {
  int net_error = resource_load_info.net_error;
  if (net_error != net::OK) {
    base::RecordComputedAction(
        base::StringPrintf("WebEngine.ResourceRequestError:%d", net_error));
  }
}

void FrameImpl::MediaStartedPlaying(const MediaPlayerInfo& video_type,
                                    const content::MediaPlayerId& id) {
  base::RecordComputedAction("MediaPlay");
}

void FrameImpl::MediaStoppedPlaying(
    const MediaPlayerInfo& video_type,
    const content::MediaPlayerId& id,
    WebContentsObserver::MediaStoppedReason reason) {
  base::RecordComputedAction("MediaPause");
}

void FrameImpl::OnPixelScaleUpdate(float pixel_scale) {
  if (accessibility_bridge_) {
    accessibility_bridge_->SetPixelScale(pixel_scale);
  }
}

void FrameImpl::SetAccessibilityEnabled(bool enabled) {
  if (!enabled) {
    scoped_accessibility_mode_.reset();
  } else if (!scoped_accessibility_mode_) {
    scoped_accessibility_mode_ =
        content::BrowserAccessibilityState::GetInstance()
            ->CreateScopedModeForProcess(ui::kAXModeComplete);
  }
}

void FrameImpl::OnThemeManagerError() {
  // TODO(crbug.com/40731307): Destroy the frame once a fake Display service is
  // implemented.
  // this->CloseAndDestroyFrame(ZX_ERR_INVALID_ARGS);
}