[WebGPU backend] Implement queue.WriteBuffer

Add BufferWGPU, CommandBufferWGPU.

Enable related end2end tests.

Implement webgpu::Queue::WriteBufferImpl,
SubmitImpl, Buffer::MapAsync and Command::CopyBufferToBuffer
to be able to run certain writeBuffer tests.

Bug: 413053623
Change-Id: I94c7e100fc2e54c0c951485129501426b7b1e469
Reviewed-on: https://dawn-review.googlesource.com/c/dawn/+/243114
Reviewed-by: Kai Ninomiya <kainino@chromium.org>
Commit-Queue: Shrek Shao <shrekshao@google.com>
diff --git a/src/dawn/native/BUILD.gn b/src/dawn/native/BUILD.gn
index c8468eb..1d6a81f 100644
--- a/src/dawn/native/BUILD.gn
+++ b/src/dawn/native/BUILD.gn
@@ -706,6 +706,10 @@
     sources += [
       "webgpu/BackendWGPU.cpp",
       "webgpu/BackendWGPU.h",
+      "webgpu/BufferWGPU.cpp",
+      "webgpu/BufferWGPU.h",
+      "webgpu/CommandBufferWGPU.cpp",
+      "webgpu/CommandBufferWGPU.h",
       "webgpu/DeviceWGPU.cpp",
       "webgpu/DeviceWGPU.h",
       "webgpu/Forward.h",
diff --git a/src/dawn/native/CMakeLists.txt b/src/dawn/native/CMakeLists.txt
index 2871791..d86d06b 100644
--- a/src/dawn/native/CMakeLists.txt
+++ b/src/dawn/native/CMakeLists.txt
@@ -571,6 +571,8 @@
     )
     list(APPEND private_headers
         "webgpu/BackendWGPU.h"
+        "webgpu/BufferWGPU.h"
+        "webgpu/CommandBufferWGPU.h"
         "webgpu/DeviceWGPU.h"
         "webgpu/Forward.h"
         "webgpu/PhysicalDeviceWGPU.h"
@@ -578,6 +580,8 @@
     )
     list(APPEND sources
         "webgpu/BackendWGPU.cpp"
+        "webgpu/BufferWGPU.cpp"
+        "webgpu/CommandBufferWGPU.cpp"
         "webgpu/DeviceWGPU.cpp"
         "webgpu/PhysicalDeviceWGPU.cpp"
         "webgpu/QueueWGPU.cpp"
diff --git a/src/dawn/native/webgpu/BufferWGPU.cpp b/src/dawn/native/webgpu/BufferWGPU.cpp
new file mode 100644
index 0000000..623520a
--- /dev/null
+++ b/src/dawn/native/webgpu/BufferWGPU.cpp
@@ -0,0 +1,142 @@
+// Copyright 2025 The Dawn & Tint Authors
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are met:
+//
+// 1. Redistributions of source code must retain the above copyright notice, this
+//    list of conditions and the following disclaimer.
+//
+// 2. Redistributions in binary form must reproduce the above copyright notice,
+//    this list of conditions and the following disclaimer in the documentation
+//    and/or other materials provided with the distribution.
+//
+// 3. Neither the name of the copyright holder nor the names of its
+//    contributors may be used to endorse or promote products derived from
+//    this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+// CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+// OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+#include "dawn/native/webgpu/BufferWGPU.h"
+
+#include <string>
+#include <utility>
+
+#include "dawn/common/StringViewUtils.h"
+#include "dawn/native/Buffer.h"
+#include "dawn/native/webgpu/DeviceWGPU.h"
+#include "dawn/native/webgpu/QueueWGPU.h"
+
+namespace dawn::native::webgpu {
+
+// static
+ResultOrError<Ref<Buffer>> Buffer::Create(Device* device,
+                                          const UnpackedPtr<BufferDescriptor>& descriptor) {
+    auto desc = ToAPI(*descriptor);
+    WGPUBuffer innerBuffer = device->wgpu.deviceCreateBuffer(device->GetInnerHandle(), desc);
+    if (innerBuffer == nullptr) {
+        // innerBuffer can be nullptr when mappedAtCreation == true and fails.
+        // Return an error buffer.
+        const BufferDescriptor* rawDescriptor = *descriptor;
+        return ToBackend(BufferBase::MakeError(device, rawDescriptor));
+    }
+
+    Ref<Buffer> buffer = AcquireRef(new Buffer(device, descriptor, innerBuffer));
+    return std::move(buffer);
+}
+
+Buffer::Buffer(Device* device,
+               const UnpackedPtr<BufferDescriptor>& descriptor,
+               WGPUBuffer innerBuffer)
+    : BufferBase(device, descriptor), mInnerBuffer(innerBuffer) {
+    mAllocatedSize = GetSize();
+}
+
+WGPUBuffer Buffer::GetInnerHandle() const {
+    return mInnerBuffer;
+}
+
+bool Buffer::IsCPUWritableAtCreation() const {
+    return ToBackend(GetDevice())->wgpu.bufferGetMapState(mInnerBuffer) ==
+           WGPUBufferMapState_Mapped;
+}
+
+MaybeError Buffer::MapAtCreationImpl() {
+    return DAWN_UNIMPLEMENTED_ERROR("Not implemented");
+}
+
+MaybeError Buffer::MapAsyncImpl(wgpu::MapMode mode, size_t offset, size_t size) {
+    struct MapAsyncResult {
+        WGPUMapAsyncStatus status;
+        std::string message;
+    } mapAsyncResult = {};
+
+    WGPUBufferMapCallbackInfo innerCallbackInfo = {};
+    innerCallbackInfo.mode = WGPUCallbackMode_WaitAnyOnly;
+    innerCallbackInfo.callback = [](WGPUMapAsyncStatus status, WGPUStringView message,
+                                    void* result_param, void* userdata_param) {
+        MapAsyncResult* result = reinterpret_cast<MapAsyncResult*>(result_param);
+        result->status = status;
+        result->message = ToString(message);
+    };
+    innerCallbackInfo.userdata1 = &mapAsyncResult;
+    innerCallbackInfo.userdata2 = this;
+
+    auto& wgpu = ToBackend(GetDevice())->wgpu;
+
+    // TODO(crbug.com/413053623): We do not have a way to efficiently process the async event
+    // on the inner webgpu layer. For now we simply wait on the future.
+    WGPUFutureWaitInfo waitInfo = {};
+    waitInfo.future = wgpu.bufferMapAsync(mInnerBuffer, static_cast<WGPUMapMode>(mode), offset,
+                                          size, innerCallbackInfo);
+    wgpu.instanceWaitAny(ToBackend(GetDevice())->GetInnerInstance(), 1, &waitInfo, UINT64_MAX);
+
+    if (mapAsyncResult.status != WGPUMapAsyncStatus_Success) {
+        return DAWN_INTERNAL_ERROR(mapAsyncResult.message);
+    }
+
+    // The frontend asks that the pointer returned by GetMappedPointer is from the start of
+    // the resource but WGPU gives us the pointer at offset. Remove the offset.
+    if (bool{mode & wgpu::MapMode::Write}) {
+        mMappedData =
+            static_cast<uint8_t*>(wgpu.bufferGetMappedRange(mInnerBuffer, offset, size)) - offset;
+    } else if (bool{mode & wgpu::MapMode::Read}) {
+        mMappedData = static_cast<uint8_t*>(const_cast<void*>(
+                          wgpu.bufferGetConstMappedRange(mInnerBuffer, offset, size))) -
+                      offset;
+    } else {
+        DAWN_UNREACHABLE();
+    }
+    return {};
+}
+
+void* Buffer::GetMappedPointer() {
+    // The mapping offset has already been removed.
+    return mMappedData;
+}
+
+void Buffer::UnmapImpl() {
+    if (mInnerBuffer) {
+        ToBackend(GetDevice())->wgpu.bufferUnmap(mInnerBuffer);
+    }
+    mMappedData = nullptr;
+}
+
+void Buffer::DestroyImpl() {
+    BufferBase::DestroyImpl();
+
+    if (mInnerBuffer) {
+        ToBackend(GetDevice())->wgpu.bufferRelease(mInnerBuffer);
+        mInnerBuffer = nullptr;
+    }
+}
+
+}  // namespace dawn::native::webgpu
diff --git a/src/dawn/native/webgpu/BufferWGPU.h b/src/dawn/native/webgpu/BufferWGPU.h
new file mode 100644
index 0000000..b171b4b
--- /dev/null
+++ b/src/dawn/native/webgpu/BufferWGPU.h
@@ -0,0 +1,62 @@
+// Copyright 2025 The Dawn & Tint Authors
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are met:
+//
+// 1. Redistributions of source code must retain the above copyright notice, this
+//    list of conditions and the following disclaimer.
+//
+// 2. Redistributions in binary form must reproduce the above copyright notice,
+//    this list of conditions and the following disclaimer in the documentation
+//    and/or other materials provided with the distribution.
+//
+// 3. Neither the name of the copyright holder nor the names of its
+//    contributors may be used to endorse or promote products derived from
+//    this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+// CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+// OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+#ifndef SRC_DAWN_NATIVE_WEBGPU_BUFFERWGPU_H_
+#define SRC_DAWN_NATIVE_WEBGPU_BUFFERWGPU_H_
+
+#include "dawn/native/Buffer.h"
+
+#include "dawn/native/webgpu/Forward.h"
+
+namespace dawn::native::webgpu {
+
+class Device;
+
+class Buffer final : public BufferBase {
+  public:
+    static ResultOrError<Ref<Buffer>> Create(Device* device,
+                                             const UnpackedPtr<BufferDescriptor>& descriptor);
+    Buffer(Device* device, const UnpackedPtr<BufferDescriptor>& descriptor, WGPUBuffer innerBuffer);
+
+    WGPUBuffer GetInnerHandle() const;
+
+  private:
+    MaybeError MapAsyncImpl(wgpu::MapMode mode, size_t offset, size_t size) override;
+    void UnmapImpl() override;
+    void DestroyImpl() override;
+    bool IsCPUWritableAtCreation() const override;
+    MaybeError MapAtCreationImpl() override;
+    void* GetMappedPointer() override;
+
+    WGPUBuffer mInnerBuffer = nullptr;
+
+    raw_ptr<void> mMappedData = nullptr;
+};
+
+}  // namespace dawn::native::webgpu
+
+#endif  // SRC_DAWN_NATIVE_WEBGPU_BUFFERWGPU_H_
diff --git a/src/dawn/native/webgpu/CommandBufferWGPU.cpp b/src/dawn/native/webgpu/CommandBufferWGPU.cpp
new file mode 100644
index 0000000..0ff5213
--- /dev/null
+++ b/src/dawn/native/webgpu/CommandBufferWGPU.cpp
@@ -0,0 +1,73 @@
+// Copyright 2025 The Dawn & Tint Authors
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are met:
+//
+// 1. Redistributions of source code must retain the above copyright notice, this
+//    list of conditions and the following disclaimer.
+//
+// 2. Redistributions in binary form must reproduce the above copyright notice,
+//    this list of conditions and the following disclaimer in the documentation
+//    and/or other materials provided with the distribution.
+//
+// 3. Neither the name of the copyright holder nor the names of its
+//    contributors may be used to endorse or promote products derived from
+//    this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+// CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+// OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+#include "dawn/native/webgpu/CommandBufferWGPU.h"
+
+#include "dawn/native/webgpu/BufferWGPU.h"
+#include "dawn/native/webgpu/DeviceWGPU.h"
+
+namespace dawn::native::webgpu {
+
+// static
+Ref<CommandBuffer> CommandBuffer::Create(CommandEncoder* encoder,
+                                         const CommandBufferDescriptor* descriptor) {
+    return AcquireRef(new CommandBuffer(encoder, descriptor));
+}
+
+CommandBuffer::CommandBuffer(CommandEncoder* encoder, const CommandBufferDescriptor* descriptor)
+    : CommandBufferBase(encoder, descriptor) {}
+
+WGPUCommandBuffer CommandBuffer::Encode() {
+    auto& wgpu = ToBackend(GetDevice())->wgpu;
+
+    // TODO(crbug.com/413053623): Use stored command encoder descriptor
+    WGPUCommandEncoder innerEncoder =
+        wgpu.deviceCreateCommandEncoder(ToBackend(GetDevice())->GetInnerHandle(), nullptr);
+
+    Command type;
+    while (mCommands.NextCommandId(&type)) {
+        switch (type) {
+            case Command::CopyBufferToBuffer: {
+                CopyBufferToBufferCmd* copy = mCommands.NextCommand<CopyBufferToBufferCmd>();
+                wgpu.commandEncoderCopyBufferToBuffer(
+                    innerEncoder, ToBackend(copy->source)->GetInnerHandle(), copy->sourceOffset,
+                    ToBackend(copy->destination)->GetInnerHandle(), copy->destinationOffset,
+                    copy->size);
+                break;
+            }
+            default:
+                DAWN_UNREACHABLE();
+        }
+    }
+
+    // TODO(crbug.com/413053623): Store WGPUCommandBufferDescriptor and assign here.
+    WGPUCommandBuffer result = wgpu.commandEncoderFinish(innerEncoder, nullptr);
+    wgpu.commandEncoderRelease(innerEncoder);
+    return result;
+}
+
+}  // namespace dawn::native::webgpu
diff --git a/src/dawn/native/webgpu/CommandBufferWGPU.h b/src/dawn/native/webgpu/CommandBufferWGPU.h
new file mode 100644
index 0000000..678a30b
--- /dev/null
+++ b/src/dawn/native/webgpu/CommandBufferWGPU.h
@@ -0,0 +1,50 @@
+// Copyright 2025 The Dawn & Tint Authors
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are met:
+//
+// 1. Redistributions of source code must retain the above copyright notice, this
+//    list of conditions and the following disclaimer.
+//
+// 2. Redistributions in binary form must reproduce the above copyright notice,
+//    this list of conditions and the following disclaimer in the documentation
+//    and/or other materials provided with the distribution.
+//
+// 3. Neither the name of the copyright holder nor the names of its
+//    contributors may be used to endorse or promote products derived from
+//    this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+// CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+// OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+#ifndef SRC_DAWN_NATIVE_WEBGPU_COMMANDBUFFERWGPU_H_
+#define SRC_DAWN_NATIVE_WEBGPU_COMMANDBUFFERWGPU_H_
+
+#include "dawn/native/CommandBuffer.h"
+
+#include "dawn/native/webgpu/Forward.h"
+
+namespace dawn::native::webgpu {
+
+class CommandBuffer final : public CommandBufferBase {
+  public:
+    static Ref<CommandBuffer> Create(CommandEncoder* encoder,
+                                     const CommandBufferDescriptor* descriptor);
+
+    WGPUCommandBuffer Encode();
+
+  private:
+    CommandBuffer(CommandEncoder* encoder, const CommandBufferDescriptor* descriptor);
+};
+
+}  // namespace dawn::native::webgpu
+
+#endif  // SRC_DAWN_NATIVE_WEBGPU_COMMANDBUFFERWGPU_H_
diff --git a/src/dawn/native/webgpu/DeviceWGPU.cpp b/src/dawn/native/webgpu/DeviceWGPU.cpp
index 911a272..aceb775 100644
--- a/src/dawn/native/webgpu/DeviceWGPU.cpp
+++ b/src/dawn/native/webgpu/DeviceWGPU.cpp
@@ -50,6 +50,9 @@
 #include "dawn/native/Surface.h"
 #include "dawn/native/SwapChain.h"
 #include "dawn/native/Texture.h"
+#include "dawn/native/webgpu/BackendWGPU.h"
+#include "dawn/native/webgpu/BufferWGPU.h"
+#include "dawn/native/webgpu/CommandBufferWGPU.h"
 #include "dawn/native/webgpu/PhysicalDeviceWGPU.h"
 #include "dawn/native/webgpu/QueueWGPU.h"
 
@@ -115,6 +118,10 @@
     return mInnerDevice;
 }
 
+WGPUInstance Device::GetInnerInstance() const {
+    return ToBackend(GetPhysicalDevice())->GetBackend()->GetInnerInstance();
+}
+
 MaybeError Device::Initialize(const UnpackedPtr<DeviceDescriptor>& descriptor) {
     Ref<Queue> queue;
     DAWN_TRY_ASSIGN(queue, Queue::Create(this, &descriptor->defaultQueue));
@@ -134,12 +141,14 @@
 }
 ResultOrError<Ref<BufferBase>> Device::CreateBufferImpl(
     const UnpackedPtr<BufferDescriptor>& descriptor) {
-    return Ref<BufferBase>{nullptr};
+    return Buffer::Create(this, descriptor);
 }
 ResultOrError<Ref<CommandBufferBase>> Device::CreateCommandBuffer(
     CommandEncoder* encoder,
     const CommandBufferDescriptor* descriptor) {
-    return Ref<CommandBufferBase>{nullptr};
+    // This is called by CommandEncoder::Finish
+    // TODO(crbug.com/413053623): Store CommandEncoderDescriptor and assign here.
+    return CommandBuffer::Create(encoder, descriptor);
 }
 Ref<ComputePipelineBase> Device::CreateUninitializedComputePipelineImpl(
     const UnpackedPtr<ComputePipelineDescriptor>& descriptor) {
diff --git a/src/dawn/native/webgpu/DeviceWGPU.h b/src/dawn/native/webgpu/DeviceWGPU.h
index 16c9ee9..714ebdd 100644
--- a/src/dawn/native/webgpu/DeviceWGPU.h
+++ b/src/dawn/native/webgpu/DeviceWGPU.h
@@ -56,6 +56,8 @@
 
     WGPUDevice GetInnerHandle() const;
 
+    WGPUInstance GetInnerInstance() const;
+
     const DawnProcTable& wgpu;
 
   private:
diff --git a/src/dawn/native/webgpu/PhysicalDeviceWGPU.cpp b/src/dawn/native/webgpu/PhysicalDeviceWGPU.cpp
index ab76b96..96ae1f3 100644
--- a/src/dawn/native/webgpu/PhysicalDeviceWGPU.cpp
+++ b/src/dawn/native/webgpu/PhysicalDeviceWGPU.cpp
@@ -79,6 +79,10 @@
     return mBackend->GetFunctions();
 }
 
+Backend* PhysicalDevice::GetBackend() const {
+    return mBackend;
+}
+
 bool PhysicalDevice::SupportsExternalImages() const {
     return false;
 }
diff --git a/src/dawn/native/webgpu/PhysicalDeviceWGPU.h b/src/dawn/native/webgpu/PhysicalDeviceWGPU.h
index 48273e3..5d99e07 100644
--- a/src/dawn/native/webgpu/PhysicalDeviceWGPU.h
+++ b/src/dawn/native/webgpu/PhysicalDeviceWGPU.h
@@ -50,6 +50,7 @@
         const Surface* surface) const override;
 
     const DawnProcTable& GetFunctions() const;
+    Backend* GetBackend() const;
 
   private:
     explicit PhysicalDevice(Backend* backend, WGPUAdapter innerAdapter);
diff --git a/src/dawn/native/webgpu/QueueWGPU.cpp b/src/dawn/native/webgpu/QueueWGPU.cpp
index 76bb6b7..691a96c 100644
--- a/src/dawn/native/webgpu/QueueWGPU.cpp
+++ b/src/dawn/native/webgpu/QueueWGPU.cpp
@@ -27,7 +27,11 @@
 
 #include "dawn/native/webgpu/QueueWGPU.h"
 
+#include <vector>
+
 #include "dawn/native/Queue.h"
+#include "dawn/native/webgpu/BufferWGPU.h"
+#include "dawn/native/webgpu/CommandBufferWGPU.h"
 #include "dawn/native/webgpu/DeviceWGPU.h"
 
 namespace dawn::native::webgpu {
@@ -37,12 +41,35 @@
     return AcquireRef(new Queue(device, descriptor));
 }
 
-Queue::Queue(Device* device, const QueueDescriptor* descriptor) : QueueBase(device, descriptor) {}
+Queue::Queue(Device* device, const QueueDescriptor* descriptor)
+    : QueueBase(device, descriptor),
+      mInnerQueue(device->wgpu.deviceGetQueue(device->GetInnerHandle())) {}
 
-Queue::~Queue() = default;
+Queue::~Queue() {
+    if (mInnerQueue) {
+        ToBackend(GetDevice())->wgpu.queueRelease(mInnerQueue);
+        mInnerQueue = nullptr;
+    }
+}
 
 MaybeError Queue::SubmitImpl(uint32_t commandCount, CommandBufferBase* const* commands) {
-    // TODO(crbug.com/413053623): finish implementing WebGPU backend.
+    if (commandCount == 0 || commands == nullptr) {
+        return {};
+    }
+
+    auto& wgpu = ToBackend(GetDevice())->wgpu;
+
+    std::vector<WGPUCommandBuffer> innerCommandBuffers(commandCount);
+    for (uint32_t i = 0; i < commandCount; ++i) {
+        innerCommandBuffers[i] = ToBackend(commands[i])->Encode();
+    }
+
+    wgpu.queueSubmit(mInnerQueue, commandCount, innerCommandBuffers.data());
+
+    for (uint32_t i = 0; i < commandCount; ++i) {
+        wgpu.commandBufferRelease(innerCommandBuffers[i]);
+    }
+
     return {};
 }
 
@@ -50,7 +77,9 @@
                                   uint64_t bufferOffset,
                                   const void* data,
                                   size_t size) {
-    // TODO(crbug.com/413053623): finish implementing WebGPU backend.
+    auto innerBuffer = ToBackend(buffer)->GetInnerHandle();
+    ToBackend(GetDevice())
+        ->wgpu.queueWriteBuffer(mInnerQueue, innerBuffer, bufferOffset, data, size);
     return {};
 }
 
diff --git a/src/dawn/native/webgpu/QueueWGPU.h b/src/dawn/native/webgpu/QueueWGPU.h
index ae7ec98..27b9786 100644
--- a/src/dawn/native/webgpu/QueueWGPU.h
+++ b/src/dawn/native/webgpu/QueueWGPU.h
@@ -54,6 +54,8 @@
     MaybeError SubmitPendingCommands() override;
     ResultOrError<bool> WaitForQueueSerial(ExecutionSerial serial, Nanoseconds timeout) override;
     MaybeError WaitForIdleForDestruction() override;
+
+    WGPUQueue mInnerQueue = nullptr;
 };
 
 }  // namespace dawn::native::webgpu
diff --git a/src/dawn/tests/end2end/BasicTests.cpp b/src/dawn/tests/end2end/BasicTests.cpp
index f82bc4f..a89d305 100644
--- a/src/dawn/tests/end2end/BasicTests.cpp
+++ b/src/dawn/tests/end2end/BasicTests.cpp
@@ -94,7 +94,8 @@
                       MetalBackend(),
                       OpenGLBackend(),
                       OpenGLESBackend(),
-                      VulkanBackend());
+                      VulkanBackend(),
+                      WebGPUBackend());
 
 }  // anonymous namespace
 }  // namespace dawn
diff --git a/src/dawn/tests/end2end/BufferTests.cpp b/src/dawn/tests/end2end/BufferTests.cpp
index 8b51d9b..34342f0 100644
--- a/src/dawn/tests/end2end/BufferTests.cpp
+++ b/src/dawn/tests/end2end/BufferTests.cpp
@@ -642,8 +642,8 @@
 }
 
 DAWN_INSTANTIATE_TEST_P(BufferMappingTests,
-                        {D3D11Backend(), D3D12Backend(), MetalBackend(), VulkanBackend(),
-                         OpenGLBackend(), OpenGLESBackend()},
+                        {D3D11Backend(), D3D12Backend(), MetalBackend(), OpenGLBackend(),
+                         OpenGLESBackend(), VulkanBackend(), WebGPUBackend()},
                         std::initializer_list<wgpu::CallbackMode>{
                             wgpu::CallbackMode::WaitAnyOnly, wgpu::CallbackMode::AllowProcessEvents,
                             wgpu::CallbackMode::AllowSpontaneous});
diff --git a/src/dawn/tests/end2end/DeviceLifetimeTests.cpp b/src/dawn/tests/end2end/DeviceLifetimeTests.cpp
index d8087ce..7b32557 100644
--- a/src/dawn/tests/end2end/DeviceLifetimeTests.cpp
+++ b/src/dawn/tests/end2end/DeviceLifetimeTests.cpp
@@ -53,9 +53,6 @@
 
 // Test that the device can be dropped while an onSubmittedWorkDone callback is in flight.
 TEST_P(DeviceLifetimeTests, DroppedWhileQueueOnSubmittedWorkDone) {
-    // TODO(crbug.com/413053623): implement webgpu::CommandBuffer
-    DAWN_SUPPRESS_TEST_IF(IsWebGPUOnWebGPU());
-
     // Submit some work.
     wgpu::CommandEncoder encoder = device.CreateCommandEncoder(nullptr);
     wgpu::CommandBuffer commandBuffer = encoder.Finish();
@@ -72,9 +69,6 @@
 
 // Test that the device can be dropped inside an onSubmittedWorkDone callback.
 TEST_P(DeviceLifetimeTests, DroppedInsideQueueOnSubmittedWorkDone) {
-    // TODO(crbug.com/413053623): implement webgpu::CommandBuffer
-    DAWN_SUPPRESS_TEST_IF(IsWebGPUOnWebGPU());
-
     // Submit some work.
     wgpu::CommandEncoder encoder = device.CreateCommandEncoder(nullptr);
     wgpu::CommandBuffer commandBuffer = encoder.Finish();
@@ -148,9 +142,6 @@
 
 // Test that the device can be dropped while a buffer created from it is being mapped.
 TEST_P(DeviceLifetimeTests, DroppedWhileMappingBuffer) {
-    // TODO(crbug.com/413053623): implement webgpu::Buffer
-    DAWN_SUPPRESS_TEST_IF(IsWebGPUOnWebGPU());
-
     wgpu::BufferDescriptor desc = {};
     desc.size = 4;
     desc.usage = wgpu::BufferUsage::MapRead | wgpu::BufferUsage::CopyDst;
@@ -168,9 +159,6 @@
 
 // Test that the device can be dropped before a mapped buffer created from it.
 TEST_P(DeviceLifetimeTests, DroppedBeforeMappedBuffer) {
-    // TODO(crbug.com/413053623): implement webgpu::Buffer
-    DAWN_SUPPRESS_TEST_IF(IsWebGPUOnWebGPU());
-
     wgpu::BufferDescriptor desc = {};
     desc.size = 4;
     desc.usage = wgpu::BufferUsage::MapRead | wgpu::BufferUsage::CopyDst;
@@ -183,7 +171,7 @@
 
 // Test that the device can be dropped before a mapped at creation buffer created from it.
 TEST_P(DeviceLifetimeTests, DroppedBeforeMappedAtCreationBuffer) {
-    // TODO(crbug.com/413053623): implement webgpu::Buffer
+    // TODO(crbug.com/413053623): implement webgpu::Buffer::MapAtCreationImpl
     DAWN_SUPPRESS_TEST_IF(IsWebGPUOnWebGPU());
 
     wgpu::BufferDescriptor desc = {};
@@ -198,9 +186,6 @@
 // Test that the device can be dropped before a buffer created from it, then mapping the buffer
 // fails.
 TEST_P(DeviceLifetimeTests, DroppedThenMapBuffer) {
-    // TODO(crbug.com/413053623): implement webgpu::Buffer
-    DAWN_SUPPRESS_TEST_IF(IsWebGPUOnWebGPU());
-
     wgpu::BufferDescriptor desc = {};
     desc.size = 4;
     desc.usage = wgpu::BufferUsage::MapRead | wgpu::BufferUsage::CopyDst;
@@ -219,9 +204,6 @@
 // Test that the device can be dropped before a buffer created from it, then mapping the buffer
 // twice (one inside callback) will both fail.
 TEST_P(DeviceLifetimeTests, Dropped_ThenMapBuffer_ThenMapBufferInCallback) {
-    // TODO(crbug.com/413053623): implement webgpu::Buffer
-    DAWN_SUPPRESS_TEST_IF(IsWebGPUOnWebGPU());
-
     wgpu::BufferDescriptor desc = {};
     desc.size = 4;
     desc.usage = wgpu::BufferUsage::MapRead | wgpu::BufferUsage::CopyDst;
@@ -250,9 +232,6 @@
 
 // Test that the device can be dropped inside a buffer map callback.
 TEST_P(DeviceLifetimeTests, DroppedInsideBufferMapCallback) {
-    // TODO(crbug.com/413053623): implement webgpu::Buffer
-    DAWN_SUPPRESS_TEST_IF(IsWebGPUOnWebGPU());
-
     wgpu::BufferDescriptor desc = {};
     desc.size = 4;
     desc.usage = wgpu::BufferUsage::MapRead | wgpu::BufferUsage::CopyDst;
@@ -285,9 +264,6 @@
 
 // Test that the device can be dropped while a write buffer operation is enqueued.
 TEST_P(DeviceLifetimeTests, DroppedWhileWriteBuffer) {
-    // TODO(crbug.com/413053623): implement webgpu::Buffer
-    DAWN_SUPPRESS_TEST_IF(IsWebGPUOnWebGPU());
-
     wgpu::BufferDescriptor desc = {};
     desc.size = 4;
     desc.usage = wgpu::BufferUsage::CopySrc | wgpu::BufferUsage::CopyDst;
@@ -302,9 +278,6 @@
 // a queue submit occurs. This is slightly different from the former test since it ensures
 // that pending work is flushed.
 TEST_P(DeviceLifetimeTests, DroppedWhileWriteBufferAndSubmit) {
-    // TODO(crbug.com/413053623): implement webgpu::Buffer
-    DAWN_SUPPRESS_TEST_IF(IsWebGPUOnWebGPU());
-
     wgpu::BufferDescriptor desc = {};
     desc.size = 4;
     desc.usage = wgpu::BufferUsage::CopySrc | wgpu::BufferUsage::CopyDst;
diff --git a/src/dawn/tests/end2end/DeviceLostTests.cpp b/src/dawn/tests/end2end/DeviceLostTests.cpp
index d59d67e..301d73e 100644
--- a/src/dawn/tests/end2end/DeviceLostTests.cpp
+++ b/src/dawn/tests/end2end/DeviceLostTests.cpp
@@ -259,9 +259,6 @@
 
 // Test that buffer.MapAsync for writing fails after device is lost
 TEST_P(DeviceLostTest, BufferMapAsyncFailsForWriting) {
-    // TODO(crbug.com/413053623): implement webgpu::Buffer
-    DAWN_SUPPRESS_TEST_IF(IsWebGPUOnWebGPU());
-
     wgpu::BufferDescriptor bufferDescriptor;
     bufferDescriptor.size = 4;
     bufferDescriptor.usage = wgpu::BufferUsage::MapWrite;
@@ -279,9 +276,6 @@
 // Test that BufferMapAsync for writing calls back with success when device lost after
 // mapping
 TEST_P(DeviceLostTest, BufferMapAsyncBeforeLossFailsForWriting) {
-    // TODO(crbug.com/413053623): implement webgpu::Buffer
-    DAWN_SUPPRESS_TEST_IF(IsWebGPUOnWebGPU());
-
     wgpu::BufferDescriptor bufferDescriptor;
     bufferDescriptor.size = 4;
     bufferDescriptor.usage = wgpu::BufferUsage::MapWrite;
@@ -297,7 +291,7 @@
 
 // Test that buffer.Unmap after device is lost
 TEST_P(DeviceLostTest, BufferUnmapAfterDeviceLost) {
-    // TODO(crbug.com/413053623): implement webgpu::Buffer
+    // TODO(crbug.com/413053623): implement webgpu::Buffer::MapAtCreationImpl
     DAWN_SUPPRESS_TEST_IF(IsWebGPUOnWebGPU());
 
     wgpu::BufferDescriptor bufferDescriptor;
@@ -367,9 +361,6 @@
 
 // Test that BufferMapAsync for reading fails after device is lost
 TEST_P(DeviceLostTest, BufferMapAsyncFailsForReading) {
-    // TODO(crbug.com/413053623): implement webgpu::Buffer
-    DAWN_SUPPRESS_TEST_IF(IsWebGPUOnWebGPU());
-
     wgpu::BufferDescriptor bufferDescriptor;
     bufferDescriptor.size = 4;
     bufferDescriptor.usage = wgpu::BufferUsage::MapRead | wgpu::BufferUsage::CopyDst;
@@ -388,9 +379,6 @@
 // Test that BufferMapAsync for reading calls back with success when device lost after
 // mapping
 TEST_P(DeviceLostTest, BufferMapAsyncBeforeLossFailsForReading) {
-    // TODO(crbug.com/413053623): implement webgpu::Buffer
-    DAWN_SUPPRESS_TEST_IF(IsWebGPUOnWebGPU());
-
     wgpu::BufferDescriptor bufferDescriptor;
     bufferDescriptor.size = sizeof(float);
     bufferDescriptor.usage = wgpu::BufferUsage::MapRead | wgpu::BufferUsage::CopyDst;
@@ -420,7 +408,7 @@
 
 // Test it's possible to GetMappedRange on a buffer created mapped after device loss
 TEST_P(DeviceLostTest, GetMappedRange_CreateBufferMappedAtCreationAfterLoss) {
-    // TODO(crbug.com/413053623): implement webgpu::Buffer
+    // TODO(crbug.com/413053623): implement webgpu::Buffer::MapAtCreationImpl
     DAWN_SUPPRESS_TEST_IF(IsWebGPUOnWebGPU());
 
     LoseDeviceForTesting();
@@ -437,7 +425,7 @@
 
 // Test that device loss doesn't change the result of GetMappedRange, mappedAtCreation version.
 TEST_P(DeviceLostTest, GetMappedRange_CreateBufferMappedAtCreationBeforeLoss) {
-    // TODO(crbug.com/413053623): implement webgpu::Buffer
+    // TODO(crbug.com/413053623): implement webgpu::Buffer::MapAtCreationImpl
     DAWN_SUPPRESS_TEST_IF(IsWebGPUOnWebGPU());
 
     wgpu::BufferDescriptor desc;
@@ -455,9 +443,6 @@
 
 // Test that device loss doesn't change the result of GetMappedRange, mapping for reading version.
 TEST_P(DeviceLostTest, GetMappedRange_MapAsyncReading) {
-    // TODO(crbug.com/413053623): implement webgpu::Buffer
-    DAWN_SUPPRESS_TEST_IF(IsWebGPUOnWebGPU());
-
     wgpu::BufferDescriptor desc;
     desc.size = 4;
     desc.usage = wgpu::BufferUsage::MapRead | wgpu::BufferUsage::CopyDst;
@@ -475,9 +460,6 @@
 
 // Test that device loss doesn't change the result of GetMappedRange, mapping for writing version.
 TEST_P(DeviceLostTest, GetMappedRange_MapAsyncWriting) {
-    // TODO(crbug.com/413053623): implement webgpu::Buffer
-    DAWN_SUPPRESS_TEST_IF(IsWebGPUOnWebGPU());
-
     wgpu::BufferDescriptor desc;
     desc.size = 4;
     desc.usage = wgpu::BufferUsage::MapWrite | wgpu::BufferUsage::CopySrc;
@@ -642,9 +624,6 @@
 
 // Attempting to set an object label after device loss should not cause an error.
 TEST_P(DeviceLostTest, SetLabelAfterDeviceLoss) {
-    // TODO(crbug.com/413053623): implement webgpu::Buffer
-    DAWN_SUPPRESS_TEST_IF(IsWebGPUOnWebGPU());
-
     std::string label = "test";
     wgpu::BufferDescriptor descriptor;
     descriptor.size = 4;
diff --git a/src/dawn/tests/end2end/QueueTests.cpp b/src/dawn/tests/end2end/QueueTests.cpp
index ae1c93e..b352728 100644
--- a/src/dawn/tests/end2end/QueueTests.cpp
+++ b/src/dawn/tests/end2end/QueueTests.cpp
@@ -53,7 +53,8 @@
                       NullBackend(),
                       OpenGLBackend(),
                       OpenGLESBackend(),
-                      VulkanBackend());
+                      VulkanBackend(),
+                      WebGPUBackend());
 
 class QueueWriteBufferTests : public DawnTest {};
 
@@ -204,6 +205,9 @@
 // Test a special code path: writing when dynamic uploader already contatins some unaligned
 // data, it might be necessary to use a ring buffer with properly aligned offset.
 TEST_P(QueueWriteBufferTests, UnalignedDynamicUploader) {
+    // TODO(crbug.com/413053623): implement webgpu::Texture
+    DAWN_SUPPRESS_TEST_IF(IsWebGPUOnWebGPU());
+
     utils::UnalignDynamicUploader(device);
 
     wgpu::BufferDescriptor descriptor;
@@ -272,7 +276,8 @@
                       MetalBackend(),
                       OpenGLBackend(),
                       OpenGLESBackend(),
-                      VulkanBackend());
+                      VulkanBackend(),
+                      WebGPUBackend());
 
 // For MinimumDataSpec bytesPerRow and rowsPerImage, compute a default from the copy extent.
 constexpr uint32_t kStrideComputeDefault = 0xFFFF'FFFEul;