/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

#include "WebGPUChild.h"

#include <utility>

#include "Adapter.h"
#include "CompilationInfo.h"
#include "DeviceLostInfo.h"
#include "PipelineLayout.h"
#include "Sampler.h"
#include "Utility.h"
#include "js/RootingAPI.h"
#include "js/String.h"
#include "js/TypeDecls.h"
#include "js/Value.h"
#include "js/Warnings.h"  // JS::WarnUTF8
#include "mozilla/Assertions.h"
#include "mozilla/ProfilerMarkers.h"
#include "mozilla/dom/GPUUncapturedErrorEvent.h"
#include "mozilla/dom/Promise.h"
#include "mozilla/dom/ScriptSettings.h"
#include "mozilla/dom/WebGPUBinding.h"
#include "mozilla/webgpu/ComputePipeline.h"
#include "mozilla/webgpu/InternalError.h"
#include "mozilla/webgpu/OutOfMemoryError.h"
#include "mozilla/webgpu/PipelineError.h"
#include "mozilla/webgpu/PromiseHelpers.h"
#include "mozilla/webgpu/RenderPipeline.h"
#include "mozilla/webgpu/ValidationError.h"
#include "mozilla/webgpu/WebGPUTypes.h"
#include "mozilla/webgpu/ffi/wgpu.h"

namespace mozilla::webgpu {

void WebGPUChild::JsWarning(nsIGlobalObject* aGlobal,
                            const nsACString& aMessage) {
  const auto& flatString = PromiseFlatCString(aMessage);
  if (aGlobal) {
    dom::AutoJSAPI api;
    if (api.Init(aGlobal)) {
      JS::WarnUTF8(api.cx(), "Uncaptured WebGPU error: %s", flatString.get());
    }
  } else {
    printf_stderr("Uncaptured WebGPU error without device target: %s\n",
                  flatString.get());
  }
}

void on_message_queued(ffi::WGPUWebGPUChildPtr child) {
  auto* c = static_cast<WebGPUChild*>(child);
  c->ScheduleFlushQueuedMessages();
}

WebGPUChild::WebGPUChild()
    : mClient(ffi::wgpu_client_new(this, on_message_queued)) {}

WebGPUChild::~WebGPUChild() = default;

namespace ffi {
void wgpu_child_send_messages(WGPUWebGPUChildPtr aChild, uint32_t aNrOfMessages,
                              struct WGPUByteBuf aSerializedMessages) {
  auto* c = static_cast<WebGPUChild*>(aChild);
  auto messages =
      ipc::ByteBuf(aSerializedMessages.data, aSerializedMessages.len,
                   aSerializedMessages.capacity);
  c->SendSerializedMessages(aNrOfMessages, std::move(messages));
}

void wgpu_child_resolve_request_adapter_promise(
    WGPUWebGPUChildPtr aChild, RawId aAdapterId,
    const struct WGPUAdapterInformation* aAdapterInfo) {
  auto* c = static_cast<WebGPUChild*>(aChild);
  auto pending_promise = c->DequeueRequestAdapterPromise();

  MOZ_RELEASE_ASSERT(pending_promise.adapter_id == aAdapterId);

  if (aAdapterInfo == nullptr) {
    promise::MaybeResolveWithNull(std::move(pending_promise.promise));
  } else {
    auto info = std::make_shared<WGPUAdapterInformation>(*aAdapterInfo);
    RefPtr<Adapter> adapter = new Adapter(pending_promise.instance, c, info);
    promise::MaybeResolve(std::move(pending_promise.promise),
                          std::move(adapter));
  }
}

void wgpu_child_resolve_request_device_promise(WGPUWebGPUChildPtr aChild,
                                               RawId aDeviceId, RawId aQueueId,
                                               const nsCString* aError) {
  auto* c = static_cast<WebGPUChild*>(aChild);
  auto pending_promise = c->DequeueRequestDevicePromise();

  MOZ_RELEASE_ASSERT(pending_promise.device_id == aDeviceId);
  MOZ_RELEASE_ASSERT(pending_promise.queue_id == aQueueId);

  if (aError == nullptr) {
    RefPtr<Device> device =
        new Device(pending_promise.adapter, pending_promise.device_id,
                   pending_promise.queue_id, pending_promise.features,
                   pending_promise.limits, pending_promise.adapter_info,
                   pending_promise.lost_promise);
    device->SetLabel(pending_promise.label);
    promise::MaybeResolve(std::move(pending_promise.promise),
                          std::move(device));
  } else {
    promise::MaybeRejectWithOperationError(std::move(pending_promise.promise),
                                           nsCString(*aError));
  }
}

void wgpu_child_resolve_pop_error_scope_promise(WGPUWebGPUChildPtr aChild,
                                                RawId aDeviceId, uint8_t aTy,
                                                const nsCString* aMessage) {
  auto* c = static_cast<WebGPUChild*>(aChild);
  auto pending_promise = c->DequeuePopErrorScopePromise();

  MOZ_RELEASE_ASSERT(pending_promise.device->GetId() == aDeviceId);

  RefPtr<Error> error;

  switch ((PopErrorScopeResultType)aTy) {
    case PopErrorScopeResultType::NoError:
      promise::MaybeResolveWithNull(std::move(pending_promise.promise));
      return;

    case PopErrorScopeResultType::DeviceLost:
      promise::MaybeResolveWithNull(std::move(pending_promise.promise));
      return;

    case PopErrorScopeResultType::ThrowOperationError:
      promise::MaybeRejectWithOperationError(std::move(pending_promise.promise),
                                             nsCString(*aMessage));
      return;

    case PopErrorScopeResultType::OutOfMemory:
      error = new OutOfMemoryError(pending_promise.device->GetParentObject(),
                                   *aMessage);
      break;

    case PopErrorScopeResultType::ValidationError:
      error = new ValidationError(pending_promise.device->GetParentObject(),
                                  *aMessage);
      break;

    case PopErrorScopeResultType::InternalError:
      error = new InternalError(pending_promise.device->GetParentObject(),
                                *aMessage);
      break;
  }
  promise::MaybeResolve(std::move(pending_promise.promise), std::move(error));
}

void wgpu_child_resolve_create_pipeline_promise(WGPUWebGPUChildPtr aChild,
                                                RawId aPipelineId,
                                                bool aIsRenderPipeline,
                                                bool aIsValidationError,
                                                const nsCString* aError) {
  auto* c = static_cast<WebGPUChild*>(aChild);
  auto pending_promise = c->DequeueCreatePipelinePromise();

  MOZ_RELEASE_ASSERT(pending_promise.pipeline_id == aPipelineId);
  MOZ_RELEASE_ASSERT(pending_promise.is_render_pipeline == aIsRenderPipeline);

  if (aError == nullptr) {
    if (pending_promise.is_render_pipeline) {
      RefPtr<RenderPipeline> object = new RenderPipeline(
          pending_promise.device, pending_promise.pipeline_id);
      object->SetLabel(pending_promise.label);
      promise::MaybeResolve(std::move(pending_promise.promise),
                            std::move(object));
    } else {
      RefPtr<ComputePipeline> object = new ComputePipeline(
          pending_promise.device, pending_promise.pipeline_id);
      object->SetLabel(pending_promise.label);
      promise::MaybeResolve(std::move(pending_promise.promise),
                            std::move(object));
    }
  } else {
    dom::GPUPipelineErrorReason reason;
    if (aIsValidationError) {
      reason = dom::GPUPipelineErrorReason::Validation;
    } else {
      reason = dom::GPUPipelineErrorReason::Internal;
    }
    RefPtr<PipelineError> e = new PipelineError(*aError, reason);
    promise::MaybeRejectWithPipelineError(std::move(pending_promise.promise),
                                          std::move(e));
  }
}

void wgpu_child_resolve_create_shader_module_promise(
    WGPUWebGPUChildPtr aChild, RawId aShaderModuleId,
    struct WGPUFfiSlice_FfiShaderModuleCompilationMessage aMessages) {
  auto* c = static_cast<WebGPUChild*>(aChild);
  auto pending_promise = c->DequeueCreateShaderModulePromise();

  MOZ_RELEASE_ASSERT(pending_promise.shader_module->GetId() == aShaderModuleId);

  auto ffi_messages = Span(aMessages.data, aMessages.length);

  auto messages = nsTArray<WebGPUCompilationMessage>(aMessages.length);
  for (const auto& message : ffi_messages) {
    WebGPUCompilationMessage msg;
    msg.lineNum = message.line_number;
    msg.linePos = message.line_pos;
    msg.offset = message.utf16_offset;
    msg.length = message.utf16_length;
    msg.message = message.message;
    // wgpu currently only returns errors.
    msg.messageType = WebGPUCompilationMessageType::Error;
    messages.AppendElement(std::move(msg));
  }

  if (!messages.IsEmpty()) {
    auto shader_module = pending_promise.shader_module;
    reportCompilationMessagesToConsole(shader_module, std::cref(messages));
  }
  RefPtr<CompilationInfo> infoObject(
      new CompilationInfo(pending_promise.device));
  infoObject->SetMessages(messages);
  promise::MaybeResolve(std::move(pending_promise.promise),
                        std::move(infoObject));
};

void wgpu_child_resolve_buffer_map_promise(WGPUWebGPUChildPtr aChild,
                                           WGPUBufferId aBufferId,
                                           bool aIsWritable, uint64_t aOffset,
                                           uint64_t aSize,
                                           const nsCString* aError) {
  auto* c = static_cast<WebGPUChild*>(aChild);
  auto pending_promise = c->DequeueBufferMapPromise(aBufferId);

  if (aError == nullptr) {
    pending_promise.buffer->ResolveMapRequest(pending_promise.promise, aOffset,
                                              aSize, aIsWritable);
  } else {
    pending_promise.buffer->RejectMapRequest(pending_promise.promise, *aError);
  }
}

void wgpu_child_resolve_on_submitted_work_done_promise(
    WGPUWebGPUChildPtr aChild, WGPUQueueId aQueueId) {
  auto* c = static_cast<WebGPUChild*>(aChild);
  auto pending_promise = c->DequeueOnSubmittedWorkDonePromise(aQueueId);

  promise::MaybeResolveWithUndefined(std::move(pending_promise));
};
}  // namespace ffi

ipc::IPCResult WebGPUChild::RecvServerMessage(const ipc::ByteBuf& aByteBuf) {
  ffi::wgpu_client_receive_server_message(GetClient(), ToFFI(&aByteBuf));
  return IPC_OK();
}

void WebGPUChild::ScheduleFlushQueuedMessages() {
  if (mScheduledFlushQueuedMessages) {
    return;
  }
  mScheduledFlushQueuedMessages = true;

  nsContentUtils::RunInStableState(
      NewRunnableMethod("dom::WebGPUChild::ScheduledFlushQueuedMessages", this,
                        &WebGPUChild::ScheduledFlushQueuedMessages));
}

size_t WebGPUChild::QueueDataBuffer(ipc::ByteBuf&& bb) {
  auto buffer_index = mQueuedDataBuffers.Length();
  mQueuedDataBuffers.AppendElement(std::move(bb));
  return buffer_index;
}

size_t WebGPUChild::QueueShmemHandle(ipc::MutableSharedMemoryHandle&& handle) {
  auto shmem_handle_index = mQueuedHandles.Length();
  mQueuedHandles.AppendElement(std::move(handle));
  return shmem_handle_index;
}

void WebGPUChild::ScheduledFlushQueuedMessages() {
  MOZ_ASSERT(mScheduledFlushQueuedMessages);
  mScheduledFlushQueuedMessages = false;

  PROFILER_MARKER_UNTYPED("WebGPU: ScheduledFlushQueuedMessages",
                          GRAPHICS_WebGPU);
  FlushQueuedMessages();
}

void WebGPUChild::FlushQueuedMessages() {
  ipc::ByteBuf serialized_messages;
  auto nr_of_messages = ffi::wgpu_client_get_queued_messages(
      GetClient(), ToFFI(&serialized_messages));
  if (nr_of_messages == 0) {
    return;
  }

  SendSerializedMessages(nr_of_messages, std::move(serialized_messages));
}

void WebGPUChild::SendSerializedMessages(uint32_t aNrOfMessages,
                                         ipc::ByteBuf aSerializedMessages) {
  PROFILER_MARKER_FMT("WebGPU: SendSerializedMessages", GRAPHICS_WebGPU, {},
                      "messages: {}", aNrOfMessages);

  bool sent =
      SendMessages(aNrOfMessages, std::move(aSerializedMessages),
                   std::move(mQueuedDataBuffers), std::move(mQueuedHandles));
  mQueuedDataBuffers.Clear();
  mQueuedHandles.Clear();

  if (!sent) {
    ClearActorState();
  }
}

ipc::IPCResult WebGPUChild::RecvUncapturedError(RawId aDeviceId,
                                                const dom::GPUErrorFilter aType,
                                                const nsACString& aMessage) {
  MOZ_RELEASE_ASSERT(aDeviceId);

  RefPtr<Device> device;
  const auto itr = mDeviceMap.find(aDeviceId);
  if (itr != mDeviceMap.end()) {
    device = itr->second.get();
  }

  if (!device) {
    return IPC_OK();
  }

  // We don't want to spam the errors to the console indefinitely
  if (device->CheckNewWarning(aMessage)) {
    JsWarning(device->GetRelevantGlobal(), aMessage);

    dom::GPUUncapturedErrorEventInit init;
    switch (aType) {
      case dom::GPUErrorFilter::Validation:
        init.mError = new ValidationError(device->GetParentObject(), aMessage);
        break;
      case dom::GPUErrorFilter::Out_of_memory:
        init.mError = new OutOfMemoryError(device->GetParentObject(), aMessage);
        break;
      case dom::GPUErrorFilter::Internal:
        init.mError = new InternalError(device->GetParentObject(), aMessage);
        break;
    }
    RefPtr<mozilla::dom::GPUUncapturedErrorEvent> event =
        dom::GPUUncapturedErrorEvent::Constructor(device, u"uncapturederror"_ns,
                                                  init);
    device->DispatchEvent(*event);
  }
  return IPC_OK();
}

ipc::IPCResult WebGPUChild::RecvDeviceLost(RawId aDeviceId,
                                           const GPUDeviceLostReason aReason,
                                           const nsACString& aMessage) {
  // There might have been a race between getting back the response to a
  // `device.destroy()` call and actual device loss. If that was the case,
  // set the lost reason to "destroyed".
  auto device_lost_promise_entry =
      mPendingDeviceLostPromises.extract(aDeviceId);
  if (!device_lost_promise_entry.empty()) {
    auto promise = std::move(device_lost_promise_entry.mapped());
    RefPtr<DeviceLostInfo> info = new DeviceLostInfo(
        promise->GetParentObject(), dom::GPUDeviceLostReason::Destroyed,
        u"Device destroyed"_ns);
    promise::MaybeResolve(std::move(promise), std::move(info));
  } else {
    auto message = NS_ConvertUTF8toUTF16(aMessage);

    const auto itr = mDeviceMap.find(aDeviceId);
    if (itr != mDeviceMap.end()) {
      RefPtr<Device> device = itr->second.get();

      if (!device) {
        return IPC_OK();
      }

      device->ResolveLost(aReason, message);
    }
  }

  return IPC_OK();
}

void WebGPUChild::SwapChainPresent(RawId aTextureId,
                                   const RemoteTextureId& aRemoteTextureId,
                                   const RemoteTextureOwnerId& aOwnerId) {
  // The parent side needs to create a command encoder which will be submitted
  // and dropped right away so we create and release an encoder ID here.
  RawId commandEncoderId =
      ffi::wgpu_client_make_command_encoder_id(GetClient());
  RawId commandBufferId = ffi::wgpu_client_make_command_buffer_id(GetClient());
  ffi::wgpu_client_swap_chain_present(GetClient(), aTextureId, commandEncoderId,
                                      commandBufferId, aRemoteTextureId.mId,
                                      aOwnerId.mId);
  ffi::wgpu_client_free_command_encoder_id(GetClient(), commandEncoderId);
  ffi::wgpu_client_free_command_buffer_id(GetClient(), commandBufferId);
}

void WebGPUChild::RegisterDevice(Device* const aDevice) {
  mDeviceMap.insert({aDevice->GetId(), aDevice});
}

void WebGPUChild::UnregisterDevice(RawId aDeviceId) {
  mDeviceMap.erase(aDeviceId);
}

void WebGPUChild::EnqueueRequestAdapterPromise(
    PendingRequestAdapterPromise&& promise) {
  mPendingRequestAdapterPromises.push_back(std::move(promise));
}

void WebGPUChild::EnqueueRequestDevicePromise(
    PendingRequestDevicePromise&& promise) {
  mPendingRequestDevicePromises.push_back(std::move(promise));
}

void WebGPUChild::RegisterDeviceLostPromise(RawId id,
                                            RefPtr<dom::Promise>& promise) {
  mPendingDeviceLostPromises.insert({id, promise});
}

void WebGPUChild::EnqueuePopErrorScopePromise(
    PendingPopErrorScopePromise&& promise) {
  mPendingPopErrorScopePromises.push_back(std::move(promise));
}

void WebGPUChild::EnqueueCreatePipelinePromise(
    PendingCreatePipelinePromise&& promise) {
  mPendingCreatePipelinePromises.push_back(std::move(promise));
}

void WebGPUChild::EnqueueCreateShaderModulePromise(
    PendingCreateShaderModulePromise&& promise) {
  mPendingCreateShaderModulePromises.push_back(std::move(promise));
}

void WebGPUChild::EnqueueBufferMapPromise(RawId id,
                                          PendingBufferMapPromise&& promise) {
  mPendingBufferMapPromises[id].push_back(std::move(promise));
}

void WebGPUChild::EnqueueOnSubmittedWorkDonePromise(
    RawId id, RefPtr<dom::Promise>& promise) {
  mPendingOnSubmittedWorkDonePromises[id].push_back(promise);
}

PendingRequestAdapterPromise WebGPUChild::DequeueRequestAdapterPromise() {
  auto& promises = mPendingRequestAdapterPromises;
  MOZ_RELEASE_ASSERT(!promises.empty());
  auto promise = std::move(promises.front());
  promises.pop_front();
  return promise;
}

PendingRequestDevicePromise WebGPUChild::DequeueRequestDevicePromise() {
  auto& promises = mPendingRequestDevicePromises;
  MOZ_RELEASE_ASSERT(!promises.empty());
  auto promise = std::move(promises.front());
  promises.pop_front();
  return promise;
}

PendingPopErrorScopePromise WebGPUChild::DequeuePopErrorScopePromise() {
  auto& promises = mPendingPopErrorScopePromises;
  MOZ_RELEASE_ASSERT(!promises.empty());
  auto promise = std::move(promises.front());
  promises.pop_front();
  return promise;
}

PendingCreatePipelinePromise WebGPUChild::DequeueCreatePipelinePromise() {
  auto& promises = mPendingCreatePipelinePromises;
  MOZ_RELEASE_ASSERT(!promises.empty());
  auto promise = std::move(promises.front());
  promises.pop_front();
  return promise;
}

PendingCreateShaderModulePromise
WebGPUChild::DequeueCreateShaderModulePromise() {
  auto& promises = mPendingCreateShaderModulePromises;
  MOZ_RELEASE_ASSERT(!promises.empty());
  auto promise = std::move(promises.front());
  promises.pop_front();
  return promise;
}

PendingBufferMapPromise WebGPUChild::DequeueBufferMapPromise(RawId id) {
  auto& promises_map = mPendingBufferMapPromises;
  const auto& it = promises_map.find(id);
  MOZ_RELEASE_ASSERT(it != promises_map.end());

  auto& promises = it->second;
  MOZ_RELEASE_ASSERT(!promises.empty());
  auto promise = std::move(promises.front());
  promises.pop_front();

  if (promises.empty()) {
    promises_map.erase(it);
  }
  return promise;
}

RefPtr<dom::Promise> WebGPUChild::DequeueOnSubmittedWorkDonePromise(RawId id) {
  auto& promises_map = mPendingOnSubmittedWorkDonePromises;
  const auto& it = promises_map.find(id);
  MOZ_RELEASE_ASSERT(it != promises_map.end());

  auto& promises = it->second;
  MOZ_RELEASE_ASSERT(!promises.empty());
  auto promise = std::move(promises.front());
  promises.pop_front();

  if (promises.empty()) {
    promises_map.erase(it);
  }
  return promise;
}

void WebGPUChild::ActorDestroy(ActorDestroyReason) { ClearActorState(); }

void WebGPUChild::ClearActorState() {
  // The following code sections dispatch runnables that resolve/reject
  // promises.
  //
  // There are a few reasons why we defer promise resolution:
  //
  //  - this function can be indirectly called by `ScheduledFlushQueuedMessages`
  // which is a runnable that should not enter the JS runtime; see docs of
  // `nsContentUtils::RunInStableState`,
  //  - if the thread is shutting down we can no longer resolve the promises;
  //  `NS_DispatchToCurrentThread` will return an error in this case that we
  //  ignore,
  //  - it avoids reentrancy issues (ex: invalidating the iterators below).

  // Resolve the promise with null since the WebGPUChild has been destroyed.
  {
    for (auto& pending_promise : mPendingRequestAdapterPromises) {
      promise::MaybeResolveWithNull(std::move(pending_promise.promise));
    }
    mPendingRequestAdapterPromises.clear();
  }

  // Pretend this worked but return a lost device, per spec.
  {
    for (auto& pending_promise : mPendingRequestDevicePromises) {
      RefPtr<Device> device =
          new Device(pending_promise.adapter, pending_promise.device_id,
                     pending_promise.queue_id, pending_promise.features,
                     pending_promise.limits, pending_promise.adapter_info,
                     pending_promise.lost_promise);
      device->SetLabel(pending_promise.label);
      device->ResolveLost(dom::GPUDeviceLostReason::Unknown,
                          u"WebGPUChild destroyed"_ns);
      promise::MaybeResolve(std::move(pending_promise.promise),
                            std::move(device));
    }
    mPendingRequestDevicePromises.clear();
  }

  // Resolve all promises that were pending due to `device.destroy()` being
  // called.
  {
    for (auto& pending_promise_entry : mPendingDeviceLostPromises) {
      auto pending_promise = std::move(pending_promise_entry.second);

      RefPtr<DeviceLostInfo> info = new DeviceLostInfo(
          pending_promise->GetParentObject(),
          dom::GPUDeviceLostReason::Destroyed, u"Device destroyed"_ns);
      promise::MaybeResolve(std::move(pending_promise), std::move(info));
    }
    mPendingDeviceLostPromises.clear();
  }

  // Empty device map and resolve all lost promises with an "unknown" reason.
  {
    for (auto& device_map_entry : mDeviceMap) {
      RefPtr<Device> device = device_map_entry.second.get();

      if (device) {
        device->ResolveLost(dom::GPUDeviceLostReason::Unknown,
                            u"WebGPUChild destroyed"_ns);
      }
    }
    mDeviceMap.clear();
  }

  // Pretend this worked and there is no error, per spec.
  {
    for (auto& pending_promise : mPendingPopErrorScopePromises) {
      promise::MaybeResolveWithNull(std::move(pending_promise.promise));
    }
    mPendingPopErrorScopePromises.clear();
  }

  // Pretend this worked, per spec; see "Listen for timeline event".
  {
    for (auto& pending_promise : mPendingCreatePipelinePromises) {
      if (pending_promise.is_render_pipeline) {
        RefPtr<RenderPipeline> object = new RenderPipeline(
            pending_promise.device, pending_promise.pipeline_id);
        object->SetLabel(pending_promise.label);
        promise::MaybeResolve(std::move(pending_promise.promise),
                              std::move(object));
      } else {
        RefPtr<ComputePipeline> object = new ComputePipeline(
            pending_promise.device, pending_promise.pipeline_id);
        object->SetLabel(pending_promise.label);
        promise::MaybeResolve(std::move(pending_promise.promise),
                              std::move(object));
      }
    }
    mPendingCreatePipelinePromises.clear();
  }

  // Pretend this worked, per spec; see "Listen for timeline event".
  {
    for (auto& pending_promise : mPendingCreateShaderModulePromises) {
      nsTArray<WebGPUCompilationMessage> messages;
      RefPtr<CompilationInfo> infoObject(
          new CompilationInfo(pending_promise.device));
      infoObject->SetMessages(messages);
      promise::MaybeResolve(std::move(pending_promise.promise),
                            std::move(infoObject));
    }
    mPendingCreateShaderModulePromises.clear();
  }

  // Reject the promise as if unmap() has been called, per spec.
  {
    for (auto& pending_promises : mPendingBufferMapPromises) {
      for (auto& pending_promise : pending_promises.second) {
        pending_promise.buffer->RejectMapRequestWithAbortError(
            pending_promise.promise);
      }
    }
    mPendingBufferMapPromises.clear();
  }

  // Pretend this worked, per spec; see "Listen for timeline event".
  {
    for (auto& pending_promises : mPendingOnSubmittedWorkDonePromises) {
      for (auto& pending_promise : pending_promises.second) {
        promise::MaybeResolveWithUndefined(std::move(pending_promise));
      }
    }
    mPendingOnSubmittedWorkDonePromises.clear();
  }
}

void WebGPUChild::QueueSubmit(
    RawId aSelfId, RawId aDeviceId, nsTArray<RawId>& aCommandBuffers,
    const nsTArray<RawId>& aUsedExternalTextureSources) {
  ffi::wgpu_client_queue_submit(
      GetClient(), aDeviceId, aSelfId,
      {aCommandBuffers.Elements(), aCommandBuffers.Length()},
      {mSwapChainTexturesWaitingForSubmit.Elements(),
       mSwapChainTexturesWaitingForSubmit.Length()},
      {aUsedExternalTextureSources.Elements(),
       aUsedExternalTextureSources.Length()});
  mSwapChainTexturesWaitingForSubmit.Clear();

  PROFILER_MARKER_UNTYPED("WebGPU: QueueSubmit", GRAPHICS_WebGPU);
  FlushQueuedMessages();
}

void WebGPUChild::NotifyWaitForSubmit(RawId aTextureId) {
  mSwapChainTexturesWaitingForSubmit.AppendElement(aTextureId);
}

}  // namespace mozilla::webgpu
