// Copyright 2015 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/xr/service/xr_runtime_manager_impl.h"

#include <string>
#include <utility>

#include "base/command_line.h"
#include "base/feature_list.h"
#include "base/functional/bind.h"
#include "base/functional/callback.h"
#include "base/lazy_instance.h"
#include "base/logging.h"
#include "base/memory/singleton.h"
#include "base/no_destructor.h"
#include "base/observer_list.h"
#include "base/strings/string_number_conversions.h"
#include "base/strings/string_util.h"
#include "base/trace_event/trace_event.h"
#include "base/trace_event/typed_macros.h"
#include "build/build_config.h"
#include "content/browser/gpu/gpu_data_manager_impl.h"
#include "content/browser/xr/service/xr_frame_sink_client_impl.h"
#include "content/browser/xr/webxr_internals/mojom/webxr_internals.mojom.h"
#include "content/browser/xr/webxr_internals/webxr_internals_handler_impl.h"
#include "content/browser/xr/xr_utils.h"
#include "content/public/browser/browser_task_traits.h"
#include "content/public/browser/browser_thread.h"
#include "content/public/browser/device_service.h"
#include "content/public/browser/global_routing_id.h"
#include "content/public/browser/gpu_utils.h"
#include "content/public/browser/xr_runtime_manager.h"
#include "content/public/common/child_process_id_util.h"
#include "content/public/common/content_features.h"
#include "content/public/common/content_switches.h"
#include "device/vr/buildflags/buildflags.h"
#include "device/vr/orientation/orientation_device_provider.h"
#include "device/vr/public/cpp/features.h"
#include "device/vr/public/cpp/vr_device_provider.h"
#include "gpu/config/gpu_info.h"
#include "mojo/public/cpp/bindings/pending_remote.h"
#include "services/device/public/mojom/sensor_provider.mojom.h"
#include "ui/gl/gl_switches.h"

#if !BUILDFLAG(IS_ANDROID)
#include "content/browser/xr/service/isolated_device_provider.h"
#endif  // !BUILDFLAG(IS_ANDROID)

namespace content {

using XrRuntimeManagerObservers =
    base::ObserverList<XRRuntimeManager::Observer>;

namespace {
XRRuntimeManagerImpl* g_xr_runtime_manager = nullptr;

XrRuntimeManagerObservers& GetXrRuntimeManagerObservers() {
  static base::NoDestructor<XrRuntimeManagerObservers>
      xr_runtime_manager_observers;
  return *xr_runtime_manager_observers;
}

bool IsForcedRuntime(const base::CommandLine* command_line,
                     const std::string& name) {
  return (base::CompareCaseInsensitiveASCII(
              command_line->GetSwitchValueASCII(switches::kWebXrForceRuntime),
              name) == 0);
}

std::optional<device::mojom::XRDeviceId> GetForcedRuntime(
    device::mojom::XRSessionMode mode) {
  auto* cmd_line = base::CommandLine::ForCurrentProcess();
  if (!cmd_line->HasSwitch(switches::kWebXrForceRuntime)) {
    return std::nullopt;
  }

  switch (mode) {
    case device::mojom::XRSessionMode::kImmersiveAr:
#if BUILDFLAG(ENABLE_ARCORE)
      if (IsForcedRuntime(cmd_line, switches::kWebXrRuntimeArCore)) {
        return device::mojom::XRDeviceId::ARCORE_DEVICE_ID;
      }
#endif
#if BUILDFLAG(ENABLE_OPENXR)
      if (IsForcedRuntime(cmd_line, switches::kWebXrRuntimeOpenXr)) {
        return device::mojom::XRDeviceId::OPENXR_DEVICE_ID;
      }
#endif
      break;
    case device::mojom::XRSessionMode::kImmersiveVr:
#if BUILDFLAG(ENABLE_OPENXR)
      if (IsForcedRuntime(cmd_line, switches::kWebXrRuntimeOpenXr)) {
        return device::mojom::XRDeviceId::OPENXR_DEVICE_ID;
      }
#endif
#if BUILDFLAG(ENABLE_CARDBOARD)
      if (IsForcedRuntime(cmd_line, switches::kWebXrRuntimeCardboard)) {
        return device::mojom::XRDeviceId::CARDBOARD_DEVICE_ID;
      }
#endif
      break;
    case device::mojom::XRSessionMode::kInline:
      if (IsForcedRuntime(cmd_line,
                          switches::kWebXrRuntimeOrientationSensors)) {
        return device::mojom::XRDeviceId::ORIENTATION_DEVICE_ID;
      }
      break;
  }

  return device::mojom::XRDeviceId::FAKE_DEVICE_ID;
}

std::unique_ptr<device::XrFrameSinkClient> FrameSinkClientFactory(
    network::RendererProcessId render_process_id,
    int render_frame_id) {
  // The XrFrameSinkClientImpl needs to be constructed (and destructed) on the
  // main thread. Currently, the only runtime that uses this is ArCore, which
  // runs on the browser main thread (which per comments in
  // content/public/browser/browser_thread.h is also the UI thread).
  DCHECK(GetUIThreadTaskRunner({})->BelongsToCurrentThread())
      << "Must construct XrFrameSinkClient from UI thread";
  return std::make_unique<XrFrameSinkClientImpl>(GlobalRenderFrameHostId(
      content::ToChildProcessId(render_process_id), render_frame_id));
}

}  // namespace

// XRRuntimeManager statics
XRRuntimeManager* XRRuntimeManager::GetInstanceIfCreated() {
  return g_xr_runtime_manager;
}

void XRRuntimeManager::AddObserver(XRRuntimeManager::Observer* observer) {
  GetXrRuntimeManagerObservers().AddObserver(observer);
}

void XRRuntimeManager::RemoveObserver(XRRuntimeManager::Observer* observer) {
  GetXrRuntimeManagerObservers().RemoveObserver(observer);
}

void XRRuntimeManager::ExitImmersivePresentation() {
  if (!g_xr_runtime_manager) {
    return;
  }

  auto* browser_xr_runtime =
      g_xr_runtime_manager->GetCurrentlyPresentingImmersiveRuntime();
  if (!browser_xr_runtime) {
    return;
  }

  browser_xr_runtime->ExitActiveImmersiveSession();
}

// Static
scoped_refptr<XRRuntimeManagerImpl>
XRRuntimeManagerImpl::GetOrCreateRuntimeManagerInternal(
    WebContents* web_contents) {
  if (g_xr_runtime_manager) {
    return base::WrapRefCounted(g_xr_runtime_manager);
  }

  // Start by getting any providers specified by the XrIntegrationClient
  XRProviderList providers;
  auto* integration_client = GetXrIntegrationClient();

  if (integration_client) {
    auto additional_providers = integration_client->GetAdditionalProviders();
    providers.insert(providers.end(),
                     make_move_iterator(additional_providers.begin()),
                     make_move_iterator(additional_providers.end()));
  }

  // Then add any other "built-in" providers
#if !BUILDFLAG(IS_ANDROID)
  providers.push_back(std::make_unique<IsolatedVRDeviceProvider>());
#endif  // !BUILDFLAG(IS_ANDROID)

  const bool is_orientation_provider_forced =
      IsForcedRuntime(base::CommandLine::ForCurrentProcess(),
                      switches::kWebXrRuntimeOrientationSensors);

  // We can use the orientation provider if it's forced, or if the feature is
  // enabled and 2D chrome is not being rendered in a head-mounted display.
  // On such displays inline sessions can cause "swimmy" behavior, because the
  // content would move in response to the user's head motion, but in unexpected
  // ways.
  bool orientation_provider_enabled =
      is_orientation_provider_forced ||
      (base::FeatureList::IsEnabled(
           device::features::kWebXROrientationSensorDevice) &&
       !device::features::IsXrDevice());

  if (orientation_provider_enabled) {
    mojo::PendingRemote<device::mojom::SensorProvider> sensor_provider;
    GetDeviceService().BindSensorProvider(
        sensor_provider.InitWithNewPipeAndPassReceiver());
    providers.emplace_back(
        std::make_unique<device::VROrientationDeviceProvider>(
            std::move(sensor_provider)));
  }
  return CreateInstance(std::move(providers), web_contents);
}

scoped_refptr<XRRuntimeManagerImpl> XRRuntimeManagerImpl::GetOrCreateInstance(
    WebContents& web_contents) {
  return GetOrCreateRuntimeManagerInternal(&web_contents);
}

scoped_refptr<XRRuntimeManagerImpl>
XRRuntimeManagerImpl::GetOrCreateInstanceForTesting() {
  return GetOrCreateRuntimeManagerInternal(nullptr);
}

// static
content::WebContents* XRRuntimeManagerImpl::GetImmersiveSessionWebContents() {
  if (!g_xr_runtime_manager)
    return nullptr;
  BrowserXRRuntimeImpl* browser_xr_runtime =
      g_xr_runtime_manager->GetCurrentlyPresentingImmersiveRuntime();
  if (!browser_xr_runtime)
    return nullptr;
  VRServiceImpl* vr_service =
      browser_xr_runtime->GetServiceWithActiveImmersiveSession();
  return vr_service ? vr_service->GetWebContents() : nullptr;
}

void XRRuntimeManagerImpl::AddService(VRServiceImpl* service) {
  DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
  DVLOG(2) << __func__;

  if (AreAllProvidersInitialized())
    service->InitializationComplete();

  services_.insert(service);

  for (const auto& runtime : runtimes_) {
    runtime.second->OnServiceAdded(service);
  }
}

void XRRuntimeManagerImpl::RemoveService(VRServiceImpl* service) {
  DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
  DVLOG(2) << __func__;
  services_.erase(service);

  for (const auto& runtime : runtimes_) {
    runtime.second->OnServiceRemoved(service);
  }
}

BrowserXRRuntimeImpl* XRRuntimeManagerImpl::GetRuntime(
    device::mojom::XRDeviceId id) {
  auto it = runtimes_.find(id);
  if (it == runtimes_.end())
    return nullptr;

  return it->second.get();
}

content::WebXrLoggerManager& XRRuntimeManagerImpl::GetLoggerManager() {
  return logger_manager_;
}

BrowserXRRuntimeImpl* XRRuntimeManagerImpl::GetRuntimeForOptions(
    device::mojom::XRSessionOptions* options) {
  BrowserXRRuntimeImpl* runtime = nullptr;
  switch (options->mode) {
    case device::mojom::XRSessionMode::kImmersiveAr:
      runtime = GetImmersiveArRuntime();
      break;
    case device::mojom::XRSessionMode::kImmersiveVr:
      runtime = GetImmersiveVrRuntime();
      break;
    case device::mojom::XRSessionMode::kInline:
      runtime = GetInlineRuntime();
      break;
  }

  // Return the runtime from above if we got one and it supports all required
  // features.
  return runtime && runtime->SupportsAllFeatures(options->required_features)
             ? runtime
             : nullptr;
}

BrowserXRRuntimeImpl* XRRuntimeManagerImpl::GetImmersiveVrRuntime() {
  std::optional<device::mojom::XRDeviceId> maybe_runtime =
      GetForcedRuntime(device::mojom::XRSessionMode::kImmersiveVr);
  if (maybe_runtime.has_value()) {
    return GetRuntime(maybe_runtime.value());
  }

// OpenXR is the highest priority if it's available.
#if BUILDFLAG(ENABLE_OPENXR)
  auto* openxr = GetRuntime(device::mojom::XRDeviceId::OPENXR_DEVICE_ID);
  if (openxr) {
    return openxr;
  }
#endif

#if BUILDFLAG(IS_ANDROID)
#if BUILDFLAG(ENABLE_CARDBOARD)
  auto* cardboard = GetRuntime(device::mojom::XRDeviceId::CARDBOARD_DEVICE_ID);
  if (cardboard) {
    return cardboard;
  }
#endif
#endif

  return nullptr;
}

BrowserXRRuntimeImpl* XRRuntimeManagerImpl::GetImmersiveArRuntime() {
  std::optional<device::mojom::XRDeviceId> maybe_runtime =
      GetForcedRuntime(device::mojom::XRSessionMode::kImmersiveAr);
  if (maybe_runtime.has_value()) {
    auto* runtime = GetRuntime(maybe_runtime.value());
    return runtime && runtime->SupportsArBlendMode() ? runtime : nullptr;
  }

#if BUILDFLAG(ENABLE_OPENXR)
  // If OpenXR is available and the runtime supports an AR blend mode, prefer
  // it over ARCore to unify VR/AR rendering paths.
  auto* openxr = GetRuntime(device::mojom::XRDeviceId::OPENXR_DEVICE_ID);
  if (openxr && openxr->SupportsArBlendMode()) {
    return openxr;
  }
#endif

#if BUILDFLAG(ENABLE_ARCORE)
  auto* arcore_runtime =
      GetRuntime(device::mojom::XRDeviceId::ARCORE_DEVICE_ID);
  if (arcore_runtime && arcore_runtime->SupportsArBlendMode()) {
    return arcore_runtime;
  }
#endif

  return nullptr;
}

BrowserXRRuntimeImpl* XRRuntimeManagerImpl::GetInlineRuntime() {
  std::optional<device::mojom::XRDeviceId> maybe_runtime =
      GetForcedRuntime(device::mojom::XRSessionMode::kInline);
  if (maybe_runtime.has_value()) {
    return GetRuntime(maybe_runtime.value());
  }

  // Try the orientation provider if it exists.
  // If we don't have an orientation provider, then we don't have an
  // explicit runtime to back a non-immersive session.
  return GetRuntime(device::mojom::XRDeviceId::ORIENTATION_DEVICE_ID);
}

BrowserXRRuntimeImpl*
XRRuntimeManagerImpl::GetCurrentlyPresentingImmersiveRuntime() {
  auto it = std::ranges::find_if(
      runtimes_, [](const DeviceRuntimeMap::value_type& val) {
        return val.second->GetServiceWithActiveImmersiveSession() != nullptr;
      });

  if (it != runtimes_.end()) {
    return it->second.get();
  }

  return nullptr;
}

bool XRRuntimeManagerImpl::HasPendingImmersiveRequest() {
  return std::ranges::any_of(
      runtimes_, [](const DeviceRuntimeMap::value_type& val) {
        return val.second->HasPendingImmersiveSessionRequest();
      });
}

bool XRRuntimeManagerImpl::IsOtherClientPresenting(VRServiceImpl* service) {
  DCHECK(service);

  auto* runtime = GetCurrentlyPresentingImmersiveRuntime();
  if (!runtime)
    return false;  // No immersive runtime to be presenting.

  auto* presenting_service = runtime->GetServiceWithActiveImmersiveSession();

  // True if some other VRServiceImpl is presenting.
  return (presenting_service != service);
}

void XRRuntimeManagerImpl::SupportsSession(
    device::mojom::XRSessionOptionsPtr options,
    device::mojom::VRService::SupportsSessionCallback callback) {
  auto* runtime = GetRuntimeForOptions(options.get());

  if (!runtime) {
    TRACE_EVENT("xr",
                "XRRuntimeManagerImpl::SupportsSession: runtime not found",
                perfetto::Flow::Global(options->trace_id));

    std::move(callback).Run(false);
    return;
  }

  std::move(callback).Run(true);
}

void XRRuntimeManagerImpl::MakeXrCompatible() {
  auto* runtime = GetImmersiveVrRuntime();
  if (!runtime)
    runtime = GetImmersiveArRuntime();

  if (!runtime) {
    for (VRServiceImpl* service : services_)
      service->OnMakeXrCompatibleComplete(
          device::mojom::XrCompatibleResult::kNoDeviceAvailable);
    return;
  }

  if (!IsInitializedOnCompatibleAdapter(runtime)) {
#if BUILDFLAG(IS_WIN)
    std::optional<CHROME_LUID> luid = runtime->GetLuid();
    // IsInitializedOnCompatibleAdapter should have returned true if the
    // runtime doesn't specify a LUID.
    DCHECK(luid && (luid->HighPart != 0 || luid->LowPart != 0));

    // Set the XR compatible adapter LUID in GpuDataManager.
    // GpuDataManagerImpl::AppendGpuCommandLine passes this to the GPU process.
    content::GpuDataManagerImpl::GetInstance()->SetUseAdapterLuid(*luid);

    // Store the current GPU so we can revert back once XR is no longer needed.
    // If default_gpu_ is nonzero, we have already previously stored the
    // default GPU and should not overwrite it.
    if (default_gpu_.LowPart == 0 && default_gpu_.HighPart == 0) {
      default_gpu_ = content::GpuDataManager::GetInstance()
                         ->GetGPUInfo()
                         .active_gpu()
                         .luid;
    }
    xr_compatible_restarted_gpu_ = true;

    // Get notified when the new GPU process sends back its GPUInfo. This
    // indicates that the GPU process has finished initializing and the GPUInfo
    // contains the LUID of the active adapter.
    content::GpuDataManager::GetInstance()->AddObserver(this);

    content::KillGpuProcess();

    return;
#else
    // MakeXrCompatible is not yet supported on other platforms so
    // IsInitializedOnCompatibleAdapter should have returned true.
    NOTREACHED();
#endif
  }

  for (VRServiceImpl* service : services_)
    service->OnMakeXrCompatibleComplete(
        device::mojom::XrCompatibleResult::kAlreadyCompatible);
}

bool XRRuntimeManagerImpl::IsInitializedOnCompatibleAdapter(
    BrowserXRRuntimeImpl* runtime) {
#if BUILDFLAG(IS_WIN)
  std::optional<CHROME_LUID> luid = runtime->GetLuid();
  if (luid && (luid->HighPart != 0 || luid->LowPart != 0)) {
    CHROME_LUID active_luid =
        content::GpuDataManager::GetInstance()->GetGPUInfo().active_gpu().luid;
    return active_luid.HighPart == luid->HighPart &&
           active_luid.LowPart == luid->LowPart;
  }
#endif

  return true;
}

void XRRuntimeManagerImpl::OnGpuInfoUpdate() {
  content::GpuDataManager::GetInstance()->RemoveObserver(this);

  device::mojom::XrCompatibleResult xr_compatible_result;
  auto* runtime = GetImmersiveVrRuntime();

  if (runtime && IsInitializedOnCompatibleAdapter(runtime)) {
    xr_compatible_result =
        device::mojom::XrCompatibleResult::kCompatibleAfterRestart;
  } else {
    // We can still be incompatible after restarting if either:
    //  1. The runtime has been removed (usually means the VR headset was
    //     unplugged) since the GPU process restart was triggered. Per the WebXR
    //     spec, if there is no device, xr compatible is false.
    //  2. The GPU process is still not using the correct GPU after restarting.
    xr_compatible_result =
        device::mojom::XrCompatibleResult::kNotCompatibleAfterRestart;
  }

  for (VRServiceImpl* service : services_)
    service->OnMakeXrCompatibleComplete(xr_compatible_result);
}

XRRuntimeManagerImpl::XRRuntimeManagerImpl(XRProviderList providers,
                                           WebContents* web_contents)
    : providers_(std::move(providers)) {
  DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
  CHECK(!g_xr_runtime_manager);
  g_xr_runtime_manager = this;
  InitializeProviders(web_contents);
}

XRRuntimeManagerImpl::~XRRuntimeManagerImpl() {
  DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
  CHECK_EQ(g_xr_runtime_manager, this);
  g_xr_runtime_manager = nullptr;

  // If a GPU adapter LUID was specified for the GPU process, clear it so
  // subsequent GPU processes initialize on the default GPU.
  if (xr_compatible_restarted_gpu_) {
#if BUILDFLAG(IS_WIN)
    content::GpuDataManagerImpl::GetInstance()->ClearUseAdapterLuid();
#endif

    // Ensure this object is no longer registered as a GpuDataManager observer,
    // which may happen if MakeXrCompatible is called and the page is navigated
    // before the GPU process restarts.
    content::GpuDataManager::GetInstance()->RemoveObserver(this);

#if BUILDFLAG(IS_WIN)
    // If we changed the GPU, revert it back to the default GPU. This is
    // separate from xr_compatible_restarted_gpu_ because the GPU process may
    // not have been successfully initialized using the specified GPU and is
    // still on the default adapter.
    CHROME_LUID active_gpu =
        content::GpuDataManager::GetInstance()->GetGPUInfo().active_gpu().luid;
    if (active_gpu.LowPart != default_gpu_.LowPart ||
        active_gpu.HighPart != default_gpu_.HighPart) {
      content::KillGpuProcess();
    }
#endif
  }
}

scoped_refptr<XRRuntimeManagerImpl> XRRuntimeManagerImpl::CreateInstance(
    XRProviderList providers,
    WebContents* contents) {
  auto* ptr = new XRRuntimeManagerImpl(std::move(providers), contents);
  CHECK_EQ(ptr, g_xr_runtime_manager);
  return base::AdoptRef(ptr);
}

device::mojom::XRRuntime* XRRuntimeManagerImpl::GetRuntimeForTest(
    device::mojom::XRDeviceId id) {
  DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
  DeviceRuntimeMap::iterator iter =
      runtimes_.find(static_cast<device::mojom::XRDeviceId>(id));
  if (iter == runtimes_.end())
    return nullptr;

  return iter->second->GetRuntime();
}

size_t XRRuntimeManagerImpl::NumberOfConnectedServices() {
  return services_.size();
}

// The initializing service is available so that providers that need access to
// some aspect of the service, such as the WebContents, to perform
// initialization can do so, but the providers should be initialized in such a
// way that they are not explicitly tied to this service.
void XRRuntimeManagerImpl::InitializeProviders(WebContents* web_contents) {
  if (providers_initialized_)
    return;

  for (const auto& provider : providers_) {
    if (!provider) {
      continue;
    }

    // It is acceptable for the providers to potentially take/keep a reference
    // to ourselves here, since we own the providers and can guarantee that they
    // will not outlive us. Providers should not take a long-term reference to
    // the WebContents.
    provider->Initialize(this, web_contents);
  }

  providers_initialized_ = true;
}

void XRRuntimeManagerImpl::OnProviderInitialized() {
  DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
  ++num_initialized_providers_;
  if (AreAllProvidersInitialized()) {
    for (VRServiceImpl* service : services_)
      service->InitializationComplete();
  }
}

bool XRRuntimeManagerImpl::AreAllProvidersInitialized() {
  DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
  return num_initialized_providers_ == providers_.size();
}

void XRRuntimeManagerImpl::AddRuntime(
    device::mojom::XRDeviceId id,
    device::mojom::XRDeviceDataPtr device_data,
    mojo::PendingRemote<device::mojom::XRRuntime> runtime) {
  DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
  DCHECK(runtimes_.find(id) == runtimes_.end());

  TRACE_EVENT_INSTANT("xr", "AddRuntime", "id", id);

  webxr::mojom::RuntimeInfoPtr runtime_added_record =
      webxr::mojom::RuntimeInfo::New();
  runtime_added_record->device_id = id;
  runtime_added_record->supported_features = device_data->supported_features;
  runtime_added_record->is_ar_blend_mode_supported =
      device_data->is_ar_blend_mode_supported;
  GetLoggerManager().RecordRuntimeAdded(std::move(runtime_added_record));

  runtimes_[id] = std::make_unique<BrowserXRRuntimeImpl>(
      id, std::move(device_data), std::move(runtime));

  for (Observer& obs : GetXrRuntimeManagerObservers()) {
    obs.OnRuntimeAdded(runtimes_[id].get());
  }

  for (VRServiceImpl* service : services_) {
    service->RuntimesChanged();
    runtimes_[id]->OnServiceAdded(service);
  }
}

void XRRuntimeManagerImpl::RemoveRuntime(device::mojom::XRDeviceId id) {
  DVLOG(1) << __func__ << " id: " << id;
  TRACE_EVENT_INSTANT("xr", "RemoveRuntime", "id", id);

  DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
  auto it = runtimes_.find(id);
  CHECK(it != runtimes_.end());

  GetLoggerManager().RecordRuntimeRemoved(id);

  // Give the runtime a chance to clean itself up before notifying services
  // that it was removed.
  it->second->BeforeRuntimeRemoved();

  // Remove the runtime from runtimes_ before notifying services that it was
  // removed, since they will query for runtimes in RuntimesChanged.
  std::unique_ptr<BrowserXRRuntimeImpl> removed_runtime = std::move(it->second);
  runtimes_.erase(it);

  for (VRServiceImpl* service : services_)
    service->RuntimesChanged();
}

device::XrFrameSinkClientFactory
XRRuntimeManagerImpl::GetXrFrameSinkClientFactory() {
  return base::BindRepeating(&FrameSinkClientFactory);
}

std::vector<webxr::mojom::RuntimeInfoPtr>
XRRuntimeManagerImpl::GetActiveRuntimes() {
  std::vector<webxr::mojom::RuntimeInfoPtr> active_runtimes;
  for (auto& runtime : runtimes_) {
    webxr::mojom::RuntimeInfoPtr runtime_info =
        webxr::mojom::RuntimeInfo::New();
    runtime_info->device_id = runtime.first;
    runtime_info->supported_features = runtime.second->GetSupportedFeatures();
    runtime_info->is_ar_blend_mode_supported =
        runtime.second->SupportsArBlendMode();

    active_runtimes.push_back(std::move(runtime_info));
  }
  return active_runtimes;
}

}  // namespace content
