Support WGPUStringView in non-struct input arguments

- Add WGPUStringView struct. In C++ there is both wgpu::StringView and
  wgpu::NullableStringView to differentiate different types of strings.
  These structs appear identically in C.
- Add support for serializing / deserializing WGPUStringView in the wire,
  with tests.
- Update methods with input args to take WGPUStringView in addition to
  the legacy const char* path.
  Methods with WGPUStringView are suffixed with "2", but overloaded
  without the suffix in the C++ API.
- wgpu::StringView may be implicitly constructed from std::string_view,
  const char* to ease use in C++. Add tests for
  these constructors. wgpu::NullableStringView may be constructed from
  nullptr and std::nullopt
- Add tests for passing wgpu::StringView or passing it via implicit
  conversion into the API.
- WGPUStringView is passed into Dawn as std::string_view or as a
  std::optional<std::string_view> depending on whether the arg is
  nullable or not. This simplifies usage in Dawn and makes it more
  type safe.

Bug: 42241188
Change-Id: Ia3dc6ef5d55f382dc8d132d3f997bb84c08cda5b
Reviewed-on: https://dawn-review.googlesource.com/c/dawn/+/198795
Reviewed-by: Kai Ninomiya <kainino@chromium.org>
Commit-Queue: Austin Eng <enga@chromium.org>
diff --git a/generator/dawn_json_generator.py b/generator/dawn_json_generator.py
index fd621aa..48e52a3 100644
--- a/generator/dawn_json_generator.py
+++ b/generator/dawn_json_generator.py
@@ -801,8 +801,8 @@
     return ''.join([name.concatcase().lower() for name in names])
 
 
-def unreachable_code():
-    assert False
+def unreachable_code(msg="unreachable_code"):
+    assert False, msg
 
 
 ############################################################
@@ -872,12 +872,13 @@
                         return False
         return True
 
-    # TODO(42240932): Remove this filtering once the deprecated "callback info" structures are
-    # removed.
     def include_structure(structure):
         # TODO(352710628) support converting callback info.
         if structure.name.canonical_case().endswith(" callback info"):
             return False
+        if (structure.name.canonical_case() == "string view"
+                or structure.name.canonical_case() == "nullable string view"):
+            return False
         return True
 
     def jni_name(type):
@@ -924,6 +925,9 @@
     # Special case for 'bool' because it has a typedef for compatibility.
     if name.native and name.get() != 'bool':
         return name.concatcase()
+    elif name.get() == 'nullable string view':
+        # nullable string view type doesn't exist in C.
+        return c_prefix + 'StringView'
     else:
         return c_prefix + name.CamelCase()
 
@@ -1270,6 +1274,10 @@
                            [RENDER_PARAMS_BASE, params_dawn]))
 
         if 'cpp_headers' in targets:
+            imported_templates += [
+                "dawn/cpp_macros.tmpl",
+            ]
+
             renders.append(
                 FileRender('api_cpp.h', 'include/dawn/' + api + '_cpp.h', [
                     RENDER_PARAMS_BASE, params_all, {
@@ -1324,6 +1332,10 @@
                            [RENDER_PARAMS_BASE, params_upstream]))
 
         if 'emdawnwebgpu_headers' in targets:
+            imported_templates += [
+                "dawn/cpp_macros.tmpl",
+            ]
+
             assert api == 'webgpu'
             params_emscripten = parse_json(
                 loaded_json, enabled_tags=['compat', 'emscripten'])
@@ -1390,6 +1402,10 @@
                 }
             ]
 
+            imported_templates += [
+                "dawn/cpp_macros.tmpl",
+            ]
+
             impl_dir = metadata.impl_dir + '/' if metadata.impl_dir else ''
             native_dir = impl_dir + Name(metadata.native_namespace).Dirs()
             namespace = metadata.namespace
@@ -1596,13 +1612,15 @@
 
             by_category = params_kotlin['by_category']
             for structure in by_category['structure']:
-                renders.append(
-                    FileRender('art/api_kotlin_structure.kt',
-                               'java/' + jni_name(structure) + '.kt', [
-                                   RENDER_PARAMS_BASE, params_kotlin, {
-                                       'structure': structure
-                                   }
-                               ]))
+                if (structure.name.get() != "string view"
+                        and structure.name.get() != "nullable string view"):
+                    renders.append(
+                        FileRender('art/api_kotlin_structure.kt',
+                                   'java/' + jni_name(structure) + '.kt', [
+                                       RENDER_PARAMS_BASE, params_kotlin, {
+                                           'structure': structure
+                                       }
+                                   ]))
             for obj in by_category['object']:
                 renders.append(
                     FileRender(
diff --git a/generator/templates/api.h b/generator/templates/api.h
index 16f5166..a5a5d59 100644
--- a/generator/templates/api.h
+++ b/generator/templates/api.h
@@ -97,7 +97,7 @@
 {% endfor %}
 
 // Structure forward declarations
-{% for type in by_category["structure"] %}
+{% for type in by_category["structure"] if type.name.get() != "nullable string view" %}
     struct {{as_cType(type.name)}};
 {% endfor %}
 
@@ -196,7 +196,8 @@
     })
 
 {% endfor %}
-{% for type in by_category["structure"] %}
+
+{% for type in by_category["structure"] if type.name.get() != "nullable string view" %}
     {% for root in type.chain_roots %}
         // Can be chained in {{as_cType(root.name)}}
     {% endfor %}
diff --git a/generator/templates/api_cpp.h b/generator/templates/api_cpp.h
index 410ab84..cec17a7 100644
--- a/generator/templates/api_cpp.h
+++ b/generator/templates/api_cpp.h
@@ -24,6 +24,8 @@
 // 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.
+{% from 'dawn/cpp_macros.tmpl' import wgpu_string_constructors with context %}
+
 {% set API = metadata.api.upper() %}
 {% set api = API.lower() %}
 {% set CAPI = metadata.c_prefix %}
@@ -41,7 +43,11 @@
 #include <cstddef>
 #include <cstdint>
 #include <memory>
+#include <optional>
 #include <functional>
+#include <string_view>
+#include <type_traits>
+#include <utility>
 
 #include "{{c_header}}"
 #include "{{api}}/{{api}}_cpp_chained_struct.h"
@@ -328,7 +334,7 @@
 {% macro render_cpp_method_declaration(type, method, dfn=False) %}
     {% set CppType = as_cppType(type.name) %}
     {% set OriginalMethodName = method.name.CamelCase() %}
-    {% set MethodName = OriginalMethodName[:-1] if method.name.chunks[-1] == "f" else OriginalMethodName %}
+    {% set MethodName = OriginalMethodName[:-1] if method.name.chunks[-1] == "f" or method.name.chunks[-1] == "2" else OriginalMethodName %}
     {% set MethodName = CppType + "::" + MethodName if dfn else MethodName %}
     {{"ConvertibleStatus" if method.return_type.name.get() == "status" else as_cppType(method.return_type.name)}} {{MethodName}}(
         {%- for arg in method.arguments -%}
@@ -556,6 +562,14 @@
                 {{member_declaration}};
             {% endif %}
         {% endfor %}
+
+        //* Custom string constructors
+        {% if type.name.get() == "string view" %}
+            {{wgpu_string_constructors(as_cppType(type.name), false) | indent(4)}}
+        {% elif type.name.get() == "nullable string view" %}
+            {{wgpu_string_constructors(as_cppType(type.name), true) | indent(4)}}
+        {% endif %}
+
         {% if type.has_free_members_function %}
 
           private:
diff --git a/generator/templates/art/api_jni_types.kt b/generator/templates/art/api_jni_types.kt
index 60718c4..f27cd22 100644
--- a/generator/templates/art/api_jni_types.kt
+++ b/generator/templates/art/api_jni_types.kt
@@ -81,6 +81,6 @@
     {%- elif member.type.name.get() == 'bool' -%}
         Z
     {%- else -%}
-        {{ unreachable_code() }}
+        {{ unreachable_code('Unsupported type: ' + member.type.name.get()) }}
     {%- endif -%}
 {% endmacro %}
diff --git a/generator/templates/art/api_kotlin_types.kt b/generator/templates/art/api_kotlin_types.kt
index 65aef1b..2e41d4e 100644
--- a/generator/templates/art/api_kotlin_types.kt
+++ b/generator/templates/art/api_kotlin_types.kt
@@ -114,7 +114,7 @@
             Long
         {% endif %}
     {%- else -%}
-        {{ unreachable_code() }}
+        {{ unreachable_code('Unsupported type: ' + type.name.get()) }}
     {%- endif %}
 {% endmacro %}
 
diff --git a/generator/templates/dawn/cpp_macros.tmpl b/generator/templates/dawn/cpp_macros.tmpl
new file mode 100644
index 0000000..2539505
--- /dev/null
+++ b/generator/templates/dawn/cpp_macros.tmpl
@@ -0,0 +1,57 @@
+//* Copyright 2024 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.
+
+{% macro wgpu_string_constructors(CppType, is_nullable) %}
+    // NOLINTNEXTLINE(runtime/explicit) allow implicit construction
+    inline constexpr {{CppType}}(const std::string_view& sv) noexcept {
+        this->data = sv.data();
+        this->length = sv.length();
+    }
+    // NOLINTNEXTLINE(runtime/explicit) allow implicit construction
+    inline constexpr {{CppType}}(const char* s) {
+        this->data = s;
+        this->length = SIZE_MAX;  // use strlen
+    }
+    inline constexpr {{CppType}}(const char* data, size_t length) {
+        this->data = data;
+        this->length = length;
+    }
+    {% if is_nullable %}
+        inline constexpr {{CppType}}() noexcept = default;
+
+        // NOLINTNEXTLINE(runtime/explicit) allow implicit construction
+        inline constexpr {{CppType}}(std::nullptr_t) {
+            this->data = nullptr;
+            this->length = SIZE_MAX;
+        }
+        // NOLINTNEXTLINE(runtime/explicit) allow implicit construction
+        inline constexpr {{CppType}}(std::nullopt_t) {
+            this->data = nullptr;
+            this->length = SIZE_MAX;
+        }
+    {% endif %}
+{% endmacro %}
diff --git a/generator/templates/dawn/native/api_structs.cpp b/generator/templates/dawn/native/api_structs.cpp
index 59e835d..cf5c6c8 100644
--- a/generator/templates/dawn/native/api_structs.cpp
+++ b/generator/templates/dawn/native/api_structs.cpp
@@ -34,6 +34,8 @@
 
 #include <tuple>
 
+#include "dawn/common/Assert.h"
+
 #if defined(__GNUC__) || defined(__clang__)
 // error: 'offsetof' within non-standard-layout type '{{namespace}}::XXX' is conditionally-supported
 #pragma GCC diagnostic ignored "-Winvalid-offsetof"
@@ -104,7 +106,7 @@
                 return copy;
             }
         {% endif %}
-        bool {{CppType}}::operator==(const {{as_cppType(type.name)}}& rhs) const {
+        bool {{CppType}}::operator==(const {{CppType}}& rhs) const {
             return {% if type.extensible or type.chained -%}
                 (nextInChain == rhs.nextInChain) &&
             {%- endif %} std::tie(
@@ -166,4 +168,24 @@
 
     {% endfor %}
 
+    StringView::operator std::string_view() const {
+        const bool isNull = this->data == nullptr;
+        const bool useStrlen = this->length == SIZE_MAX;
+        DAWN_ASSERT(!(isNull && useStrlen));
+        return std::string_view(this->data, isNull      ? 0
+                                            : useStrlen ? strlen(this->data)
+                                                        : this->length);
+    }
+
+    NullableStringView::operator std::optional<std::string_view>() const {
+        const bool isNull = this->data == nullptr;
+        const bool useStrlen = this->length == SIZE_MAX;
+        if (isNull && useStrlen) {
+            return std::nullopt;
+        }
+        return std::string_view(this->data, isNull      ? 0
+                                            : useStrlen ? strlen(this->data)
+                                                        : this->length);
+    }
+
 } // namespace {{native_namespace}}
diff --git a/generator/templates/dawn/native/api_structs.h b/generator/templates/dawn/native/api_structs.h
index 33352e6..f2cecd0 100644
--- a/generator/templates/dawn/native/api_structs.h
+++ b/generator/templates/dawn/native/api_structs.h
@@ -24,6 +24,7 @@
 //* 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.
+{% from 'dawn/cpp_macros.tmpl' import wgpu_string_constructors with context %}
 
 {% set namespace_name = Name(metadata.native_namespace) %}
 {% set DIR = namespace_name.concatcase().upper() %}
@@ -38,7 +39,10 @@
 {% set native_namespace = namespace_name.namespace_case() %}
 {% set native_dir = impl_dir + namespace_name.Dirs() %}
 #include "{{native_dir}}/Forward.h"
+
 #include <cmath>
+#include <optional>
+#include <string_view>
 
 namespace {{native_namespace}} {
 
@@ -64,24 +68,25 @@
     using {{namespace}}::ChainedStructOut;
 
     {% for type in by_category["structure"] %}
+        {% set CppType = as_cppType(type.name) %}
         {% if type.chained %}
             {% set chainedStructType = "ChainedStructOut" if type.chained == "out" else "ChainedStruct" %}
-            struct {{as_cppType(type.name)}} : {{chainedStructType}} {
-                {{as_cppType(type.name)}}() {
+            struct {{CppType}} : {{chainedStructType}} {
+                {{CppType}}() {
                     sType = {{namespace}}::SType::{{type.name.CamelCase()}};
                 }
         {% else %}
-            struct {{as_cppType(type.name)}} {
+            struct {{CppType}} {
                 {% if type.has_free_members_function %}
-                    {{as_cppType(type.name)}}() = default;
+                    {{CppType}}() = default;
                 {% endif %}
         {% endif %}
             {% if type.has_free_members_function %}
-                ~{{as_cppType(type.name)}}();
-                {{as_cppType(type.name)}}(const {{as_cppType(type.name)}}&) = delete;
-                {{as_cppType(type.name)}}& operator=(const {{as_cppType(type.name)}}&) = delete;
-                {{as_cppType(type.name)}}({{as_cppType(type.name)}}&&);
-                {{as_cppType(type.name)}}& operator=({{as_cppType(type.name)}}&&);
+                ~{{CppType}}();
+                {{CppType}}(const {{CppType}}&) = delete;
+                {{CppType}}& operator=(const {{CppType}}&) = delete;
+                {{CppType}}({{CppType}}&&);
+                {{CppType}}& operator=({{CppType}}&&);
 
             {% endif %}
             {% if type.extensible %}
@@ -93,23 +98,36 @@
                 {% if type.chained and loop.first %}
                     //* Align the first member after ChainedStruct to match the C struct layout.
                     //* It has to be aligned both to its natural and ChainedStruct's alignment.
-                    alignas({{namespace}}::{{as_cppType(type.name)}}::kFirstMemberAlignment) {{member_declaration}};
+                    alignas({{namespace}}::{{CppType}}::kFirstMemberAlignment) {{member_declaration}};
                 {% else %}
                     {{member_declaration}};
                 {% endif %}
             {% endfor %}
 
+            //* Custom string constructors / conversion
+            {% if type.name.get() == "string view" %}
+                {{wgpu_string_constructors(CppType, false) | indent(8)}}
+
+                // NOLINTNEXTLINE(runtime/explicit) allow implicit conversion
+                operator std::string_view() const;
+            {% elif type.name.get() == "nullable string view" %}
+                {{wgpu_string_constructors(CppType, true) | indent(8)}}
+
+                // NOLINTNEXTLINE(runtime/explicit) allow implicit conversion
+                operator std::optional<std::string_view>() const;
+            {% endif %}
+
             {% if type.any_member_requires_struct_defaulting %}
                 // This method makes a copy of the struct, then, for any enum members with trivial
                 // defaulting (where something like "Undefined" is replaced with a default), applies
                 // all of the defaults for the struct, and recursively its by-value substructs (but
                 // NOT by-pointer substructs since they are const*). It must be called in an
                 // appropriate place in Dawn.
-                [[nodiscard]] {{as_cppType(type.name)}} WithTrivialFrontendDefaults() const;
+                [[nodiscard]] {{CppType}} WithTrivialFrontendDefaults() const;
             {% endif %}
             // Equality operators, mostly for testing. Note that this tests
             // strict pointer-pointer equality if the struct contains member pointers.
-            bool operator==(const {{as_cppType(type.name)}}& rhs) const;
+            bool operator==(const {{CppType}}& rhs) const;
 
             {% if type.has_free_members_function %}
               private:
diff --git a/generator/templates/dawn/native/dawn_platform.h b/generator/templates/dawn/native/dawn_platform.h
index 6855f96..a1909c1 100644
--- a/generator/templates/dawn/native/dawn_platform.h
+++ b/generator/templates/dawn/native/dawn_platform.h
@@ -75,7 +75,7 @@
     {% set ChainedStructName = Name("chained struct") %}
     {{render_structure_conversions(ChainedStructName)|indent}}
 
-    {% for type in by_category["structure"] %}
+    {% for type in by_category["structure"] if type.name.get() != "string view" and type.name.get() != "nullable string view" %}
         {{render_structure_conversions(type.name)|indent}}
 
     {% endfor %}
diff --git a/generator/templates/dawn/wire/WireCmd.cpp b/generator/templates/dawn/wire/WireCmd.cpp
index 39c34cf..f3338b5 100644
--- a/generator/templates/dawn/wire/WireCmd.cpp
+++ b/generator/templates/dawn/wire/WireCmd.cpp
@@ -202,29 +202,26 @@
                 {% continue %}
             {% endif %}
             //* Normal handling for pointer members and structs.
-            {% if member.annotation != "value" or member.type.category == "structure" %}
+            {% if member.annotation != "value" %}
                 {% if member.type.category != "object" and member.optional %}
-                    if (record.{{as_varName(member.name)}} != nullptr) {
-                {% else %}
-                    {
+                    if (record.{{as_varName(member.name)}} != nullptr)
                 {% endif %}
-                {% if member.annotation != "value" %}
-                        {% do assert(member.annotation != "const*const*", "const*const* not valid here") %}
-                        auto memberLength = {{member_length(member, "record.")}};
-                        auto size = WireAlignSizeofN<{{member_transfer_type(member)}}>(memberLength);
-                        DAWN_ASSERT(size);
-                        result += *size;
-                        //* Structures might contain more pointers so we need to add their extra size as well.
-                        {% if member.type.category == "structure" %}
-                            for (decltype(memberLength) i = 0; i < memberLength; ++i) {
-                                {% do assert(member.annotation == "const*" or member.annotation == "*", "unhandled annotation: " + member.annotation)%}
-                                result += {{as_cType(member.type.name)}}GetExtraRequiredSize(record.{{as_varName(member.name)}}[i]);
-                            }
-                        {% endif %}
-                    {% elif member.type.category == "structure" %}
-                        result += {{as_cType(member.type.name)}}GetExtraRequiredSize(record.{{as_varName(member.name)}});
+                {
+                    {% do assert(member.annotation != "const*const*", "const*const* not valid here") %}
+                    auto memberLength = {{member_length(member, "record.")}};
+                    auto size = WireAlignSizeofN<{{member_transfer_type(member)}}>(memberLength);
+                    DAWN_ASSERT(size);
+                    result += *size;
+                    //* Structures might contain more pointers so we need to add their extra size as well.
+                    {% if member.type.category == "structure" %}
+                        for (decltype(memberLength) i = 0; i < memberLength; ++i) {
+                            {% do assert(member.annotation == "const*" or member.annotation == "*", "unhandled annotation: " + member.annotation)%}
+                            result += {{as_cType(member.type.name)}}GetExtraRequiredSize(record.{{as_varName(member.name)}}[i]);
+                        }
                     {% endif %}
                 }
+            {% elif member.type.category == "structure" %}
+                result += {{as_cType(member.type.name)}}GetExtraRequiredSize(record.{{as_varName(member.name)}});
             {% endif %}
         {% endfor %}
         return result;
@@ -737,10 +734,95 @@
                                     DeserializeAllocator* allocator,
                                     const ObjectIdResolver& resolver);
 
+// Manually define serialization and deserialization for WGPUStringView because
+// it has a special encoding where:
+//  { .data = nullptr, .length = SIZE_MAX }  --> nil
+//  { .data = non-null, .length = SIZE_MAX } --> null-terminated, use strlen
+//  { .data = ..., .length = 0 }             --> ""
+//  { .data = ..., .length > 0 }             --> string of size `length`
+struct WGPUStringViewTransfer {
+    bool has_data;
+    uint64_t length;
+};
+
+size_t WGPUStringViewGetExtraRequiredSize(const WGPUStringView& record) {
+    size_t size = record.length;
+    if (size == SIZE_MAX) {
+        // This is a null-terminated string, or it's nil.
+        size = record.data ? std::strlen(record.data) : 0;
+    }
+    return Align(size, kWireBufferAlignment);
+}
+
+WireResult WGPUStringViewSerialize(
+    const WGPUStringView& record,
+    WGPUStringViewTransfer* transfer,
+    SerializeBuffer* buffer) {
+
+    bool has_data = record.data != nullptr;
+    uint64_t length = record.length;
+    transfer->has_data = has_data;
+
+    if (!has_data) {
+        transfer->length = length;
+        return WireResult::Success;
+    }
+    if (length == SIZE_MAX) {
+        length = std::strlen(record.data);
+    }
+    if (length > 0) {
+        char* memberBuffer;
+        WIRE_TRY(buffer->NextN(length, &memberBuffer));
+        memcpy(memberBuffer, record.data, length);
+    }
+    transfer->length = length;
+    return WireResult::Success;
+}
+
+WireResult WGPUStringViewDeserialize(
+    WGPUStringView* record,
+    const volatile WGPUStringViewTransfer* transfer,
+    DeserializeBuffer* deserializeBuffer,
+    DeserializeAllocator* allocator) {
+
+    bool has_data = transfer->has_data;
+    uint64_t length = transfer->length;
+
+    if (length > SIZE_MAX) {
+        return WireResult::FatalError;
+    }
+    if (!has_data) {
+        record->data = nullptr;
+        if (length != 0 && length != SIZE_MAX) {
+            // Invalid string.
+            return WireResult::FatalError;
+        }
+        record->length = static_cast<size_t>(length);
+        return WireResult::Success;
+    }
+    if (length == 0) {
+        record->data = "";
+        record->length = 0;
+        return WireResult::Success;
+    }
+
+    size_t stringLength = static_cast<size_t>(length);
+    const volatile char* stringInBuffer;
+    WIRE_TRY(deserializeBuffer->ReadN(stringLength, &stringInBuffer));
+
+    char* copiedString;
+    WIRE_TRY(GetSpace(allocator, stringLength, &copiedString));
+    memcpy(copiedString, const_cast<const char*>(stringInBuffer), stringLength);
+
+    record->data = copiedString;
+    record->length = stringLength;
+    return WireResult::Success;
+}
+
 //* Output structure [de]serialization first because it is used by commands.
 {% for type in by_category["structure"] %}
     {%- set name = as_cType(type.name) -%}
-    {% if type.name.CamelCase() not in client_side_structures -%}
+    {% if type.name.CamelCase() not in client_side_structures and name != "WGPUStringView" -%}
         {{write_record_serialization_helpers(type, name, type.members, is_cmd=False)}}
     {% endif %}
 {% endfor %}
diff --git a/src/dawn/dawn.json b/src/dawn/dawn.json
index 706ea80..43adfa0 100644
--- a/src/dawn/dawn.json
+++ b/src/dawn/dawn.json
@@ -360,6 +360,13 @@
                 "args": [
                     {"name": "label", "type": "char", "annotation": "const*", "length": "strlen"}
                 ]
+            },
+            {
+                "name": "set label 2",
+                "returns": "void",
+                "args": [
+                    {"name": "label", "type": "nullable string view"}
+                ]
             }
         ]
     },
@@ -394,6 +401,13 @@
                 "args": [
                     {"name": "label", "type": "char", "annotation": "const*", "length": "strlen"}
                 ]
+            },
+            {
+                "name": "set label 2",
+                "returns": "void",
+                "args": [
+                    {"name": "label", "type": "nullable string view"}
+                ]
             }
         ]
     },
@@ -602,6 +616,20 @@
         "category": "native",
         "wasm type": "i"
     },
+    "string view": {
+        "category": "structure",
+        "members": [
+            {"name": "data", "type": "char", "annotation": "const*", "optional": true},
+            {"name": "length", "type": "size_t", "default": "SIZE_MAX"}
+        ]
+    },
+    "nullable string view": {
+        "category": "structure",
+        "members": [
+            {"name": "data", "type": "char", "annotation": "const*", "optional": true},
+            {"name": "length", "type": "size_t", "default": "SIZE_MAX"}
+        ]
+    },
     "buffer": {
         "category": "object",
         "methods": [
@@ -663,6 +691,13 @@
                 ]
             },
             {
+                "name": "set label 2",
+                "returns": "void",
+                "args": [
+                    {"name": "label", "type": "nullable string view"}
+                ]
+            },
+            {
                 "name": "get usage",
                 "no autolock": true,
                 "returns": "buffer usage"
@@ -834,6 +869,13 @@
                 "args": [
                     {"name": "label", "type": "char", "annotation": "const*", "length": "strlen"}
                 ]
+            },
+            {
+                "name": "set label 2",
+                "returns": "void",
+                "args": [
+                    {"name": "label", "type": "nullable string view"}
+                ]
             }
         ]
     },
@@ -919,12 +961,25 @@
                 ]
             },
             {
+                "name": "inject validation error 2",
+                "tags": ["dawn"],
+                "args": [
+                    {"name": "message", "type": "string view"}
+                ]
+            },
+            {
                 "name": "insert debug marker",
                 "args": [
                     {"name": "marker label", "type": "char", "annotation": "const*", "length": "strlen"}
                 ]
             },
             {
+                "name": "insert debug marker 2",
+                "args": [
+                    {"name": "marker label", "type": "string view"}
+                ]
+            },
+            {
                 "name": "pop debug group",
                 "args": []
             },
@@ -935,6 +990,12 @@
                 ]
             },
             {
+                "name": "push debug group 2",
+                "args": [
+                    {"name": "group label", "type": "string view"}
+                ]
+            },
+            {
                 "name": "resolve query set",
                 "args": [
                     {"name": "query set", "type": "query set"},
@@ -967,6 +1028,13 @@
                 "args": [
                     {"name": "label", "type": "char", "annotation": "const*", "length": "strlen"}
                 ]
+            },
+            {
+                "name": "set label 2",
+                "returns": "void",
+                "args": [
+                    {"name": "label", "type": "nullable string view"}
+                ]
             }
         ]
     },
@@ -1084,6 +1152,12 @@
                 ]
             },
             {
+                "name": "insert debug marker 2",
+                "args": [
+                    {"name": "marker label", "type": "string view"}
+                ]
+            },
+            {
                 "name": "pop debug group",
                 "args": []
             },
@@ -1094,6 +1168,12 @@
                 ]
             },
             {
+                "name": "push debug group 2",
+                "args": [
+                    {"name": "group label", "type": "string view"}
+                ]
+            },
+            {
                 "name": "set pipeline",
                 "args": [
                     {"name": "pipeline", "type": "compute pipeline"}
@@ -1140,6 +1220,13 @@
                 "args": [
                     {"name": "label", "type": "char", "annotation": "const*", "length": "strlen"}
                 ]
+            },
+            {
+                "name": "set label 2",
+                "returns": "void",
+                "args": [
+                    {"name": "label", "type": "nullable string view"}
+                ]
             }
         ]
     },
@@ -1177,6 +1264,13 @@
                 "args": [
                     {"name": "label", "type": "char", "annotation": "const*", "length": "strlen"}
                 ]
+            },
+            {
+                "name": "set label 2",
+                "returns": "void",
+                "args": [
+                    {"name": "label", "type": "nullable string view"}
+                ]
             }
         ]
     },
@@ -1493,6 +1587,15 @@
                 ]
             },
             {
+                "name": "create error shader module 2",
+                "returns": "shader module",
+                "tags": ["dawn"],
+                "args": [
+                    {"name": "descriptor", "type": "shader module descriptor", "annotation": "const*"},
+                    {"name": "error message", "type": "string view"}
+                ]
+            },
+            {
                 "name": "create swap chain",
                 "tags": ["art", "dawn", "emscripten"],
                 "returns": "swap chain",
@@ -1591,6 +1694,14 @@
                 "tags": ["dawn"]
             },
             {
+                "name": "inject error 2",
+                "args": [
+                    {"name": "type", "type": "error type"},
+                    {"name": "message", "type": "string view"}
+                ],
+                "tags": ["dawn"]
+            },
+            {
                 "name": "force loss",
                 "args": [
                     {"name": "type", "type": "device lost reason"},
@@ -1599,6 +1710,14 @@
                 "tags": ["dawn"]
             },
             {
+                "name": "force loss 2",
+                "args": [
+                    {"name": "type", "type": "device lost reason"},
+                    {"name": "message", "type": "string view"}
+                ],
+                "tags": ["dawn"]
+            },
+            {
                 "name": "tick",
                 "no autolock": true,
                 "tags": ["dawn"]
@@ -1669,6 +1788,13 @@
                 ]
             },
             {
+                "name": "set label 2",
+                "returns": "void",
+                "args": [
+                    {"name": "label", "type": "nullable string view"}
+                ]
+            },
+            {
                 "name": "validate texture descriptor",
                 "tags": ["dawn"],
                 "args": [
@@ -1941,6 +2067,13 @@
                 ]
             },
             {
+                "name": "set label 2",
+                "returns": "void",
+                "args": [
+                    {"name": "label", "type": "nullable string view"}
+                ]
+            },
+            {
                 "name": "destroy",
                 "returns": "void"
             },
@@ -2008,6 +2141,13 @@
                 ]
             },
             {
+                "name": "set label 2",
+                "returns": "void",
+                "args": [
+                    {"name": "label", "type": "nullable string view"}
+                ]
+            },
+            {
                 "name": "get properties",
                 "returns": "status",
                 "args": [
@@ -2073,6 +2213,13 @@
                 ]
             },
             {
+                "name": "set label 2",
+                "returns": "void",
+                "args": [
+                    {"name": "label", "type": "nullable string view"}
+                ]
+            },
+            {
                 "name": "get properties",
                 "returns": "status",
                 "args": [
@@ -2813,6 +2960,13 @@
                 "args": [
                     {"name": "label", "type": "char", "annotation": "const*", "length": "strlen"}
                 ]
+            },
+            {
+                "name": "set label 2",
+                "returns": "void",
+                "args": [
+                    {"name": "label", "type": "nullable string view"}
+                ]
             }
         ]
     },
@@ -2895,6 +3049,13 @@
                 ]
             },
             {
+                "name": "set label 2",
+                "returns": "void",
+                "args": [
+                    {"name": "label", "type": "nullable string view"}
+                ]
+            },
+            {
                 "name": "get type",
                 "returns": "query type"
             },
@@ -3006,6 +3167,13 @@
                 "args": [
                     {"name": "label", "type": "char", "annotation": "const*", "length": "strlen"}
                 ]
+            },
+            {
+                "name": "set label 2",
+                "returns": "void",
+                "args": [
+                    {"name": "label", "type": "nullable string view"}
+                ]
             }
         ]
     },
@@ -3066,6 +3234,13 @@
                 "args": [
                     {"name": "label", "type": "char", "annotation": "const*", "length": "strlen"}
                 ]
+            },
+            {
+                "name": "set label 2",
+                "returns": "void",
+                "args": [
+                    {"name": "label", "type": "nullable string view"}
+                ]
             }
         ]
     },
@@ -3129,6 +3304,12 @@
                 ]
             },
             {
+                "name": "insert debug marker 2",
+                "args": [
+                    {"name": "marker label", "type": "string view"}
+                ]
+            },
+            {
                 "name": "pop debug group",
                 "args": []
             },
@@ -3139,6 +3320,12 @@
                 ]
             },
             {
+                "name": "push debug group 2",
+                "args": [
+                    {"name": "group label", "type": "string view"}
+                ]
+            },
+            {
                 "name": "set vertex buffer",
                 "args": [
                     {"name": "slot", "type": "uint32_t"},
@@ -3169,6 +3356,13 @@
                 "args": [
                     {"name": "label", "type": "char", "annotation": "const*", "length": "strlen"}
                 ]
+            },
+            {
+                "name": "set label 2",
+                "returns": "void",
+                "args": [
+                    {"name": "label", "type": "nullable string view"}
+                ]
             }
         ]
     },
@@ -3340,6 +3534,12 @@
                 ]
             },
             {
+                "name": "insert debug marker 2",
+                "args": [
+                    {"name": "marker label", "type": "string view"}
+                ]
+            },
+            {
                 "name": "pop debug group",
                 "args": []
             },
@@ -3350,6 +3550,12 @@
                 ]
             },
             {
+                "name": "push debug group 2",
+                "args": [
+                    {"name": "group label", "type": "string view"}
+                ]
+            },
+            {
                 "name": "set stencil reference",
                 "args": [
                     {"name": "reference", "type": "uint32_t"}
@@ -3429,6 +3635,13 @@
                 "args": [
                     {"name": "label", "type": "char", "annotation": "const*", "length": "strlen"}
                 ]
+            },
+            {
+                "name": "set label 2",
+                "returns": "void",
+                "args": [
+                    {"name": "label", "type": "nullable string view"}
+                ]
             }
         ]
     },
@@ -3456,8 +3669,14 @@
                 "args": [
                     {"name": "label", "type": "char", "annotation": "const*", "length": "strlen"}
                 ]
+            },
+            {
+                "name": "set label 2",
+                "returns": "void",
+                "args": [
+                    {"name": "label", "type": "nullable string view"}
+                ]
             }
-
         ]
     },
 
@@ -3640,6 +3859,13 @@
                 "args": [
                     {"name": "label", "type": "char", "annotation": "const*", "length": "strlen"}
                 ]
+            },
+            {
+                "name": "set label 2",
+                "returns": "void",
+                "args": [
+                    {"name": "label", "type": "nullable string view"}
+                ]
             }
         ]
     },
@@ -3694,6 +3920,13 @@
                 "args": [
                     {"name": "label", "type": "char", "annotation": "const*", "length": "strlen"}
                 ]
+            },
+            {
+                "name": "set label 2",
+                "returns": "void",
+                "args": [
+                    {"name": "label", "type": "nullable string view"}
+                ]
             }
         ]
     },
@@ -3828,11 +4061,17 @@
             },
             {
                 "name": "set label",
-                "tags": [],
                 "returns": "void",
                 "args": [
                     {"name": "label", "type": "char", "annotation": "const*", "length": "strlen"}
                 ]
+            },
+            {
+                "name": "set label 2",
+                "returns": "void",
+                "args": [
+                    {"name": "label", "type": "nullable string view"}
+                ]
             }
         ]
     },
@@ -4066,6 +4305,13 @@
                 ]
             },
             {
+                "name": "set label 2",
+                "returns": "void",
+                "args": [
+                    {"name": "label", "type": "nullable string view"}
+                ]
+            },
+            {
                 "name": "get width",
                 "returns": "uint32_t"
             },
@@ -4329,6 +4575,13 @@
                 "args": [
                     {"name": "label", "type": "char", "annotation": "const*", "length": "strlen"}
                 ]
+            },
+            {
+                "name": "set label 2",
+                "returns": "void",
+                "args": [
+                    {"name": "label", "type": "nullable string view"}
+                ]
             }
         ]
     },
diff --git a/src/dawn/native/CommandAllocator.cpp b/src/dawn/native/CommandAllocator.cpp
index 1fe51ee..ab73e78 100644
--- a/src/dawn/native/CommandAllocator.cpp
+++ b/src/dawn/native/CommandAllocator.cpp
@@ -228,4 +228,15 @@
     mEndPtr = reinterpret_cast<char*>(&mPlaceholderSpace[1]);
 }
 
+const char* CommandAllocator::CopyAsNullTerminatedString(std::string_view in) {
+    // Include extra null-terminator character. The string_view may not be
+    // null-terminated. It also may already have a null-terminator inside of
+    // it, in which case adding the null-terminator is unnecessary. However,
+    // this is unlikely, so always include the extra character.
+    char* out = AllocateData<char>(in.length() + 1);
+    memcpy(out, in.data(), in.length());
+    out[in.length()] = '\0';
+    return out;
+}
+
 }  // namespace dawn::native
diff --git a/src/dawn/native/CommandAllocator.h b/src/dawn/native/CommandAllocator.h
index 9ad0db7..ed5b4ae 100644
--- a/src/dawn/native/CommandAllocator.h
+++ b/src/dawn/native/CommandAllocator.h
@@ -32,6 +32,7 @@
 #include <cstdint>
 #include <limits>
 #include <memory>
+#include <string_view>
 #include <vector>
 
 #include "dawn/common/Assert.h"
@@ -206,6 +207,8 @@
         return result;
     }
 
+    const char* CopyAsNullTerminatedString(std::string_view in);
+
   private:
     // This is used for some internal computations and can be any power of two as long as code
     // using the CommandAllocator passes the static_asserts.
diff --git a/src/dawn/native/CommandEncoder.cpp b/src/dawn/native/CommandEncoder.cpp
index 3314159..aac38df 100644
--- a/src/dawn/native/CommandEncoder.cpp
+++ b/src/dawn/native/CommandEncoder.cpp
@@ -58,6 +58,7 @@
 #include "dawn/native/RenderPassWorkaroundsHelper.h"
 #include "dawn/native/RenderPipeline.h"
 #include "dawn/native/ValidationUtils_autogen.h"
+#include "dawn/native/utils/WGPUHelpers.h"
 #include "dawn/platform/DawnPlatform.h"
 #include "dawn/platform/tracing/TraceEvent.h"
 
@@ -1875,23 +1876,25 @@
         "encoding %s.ClearBuffer(%s, %u, %u).", this, buffer, offset, size);
 }
 
-void CommandEncoder::APIInjectValidationError(const char* message) {
-    if (!mEncodingContext.ConsumedError(mEncodingContext.CheckCurrentEncoder(this),
-                                        "injecting validation error: %s.", message)) {
-        mEncodingContext.HandleError(DAWN_MAKE_ERROR(InternalErrorType::Validation, message));
-    }
+void CommandEncoder::APIInjectValidationError2(std::string_view message) {
+    message = utils::NormalizeLabel(message);
+    mEncodingContext.TryEncode(
+        this,
+        [&](CommandAllocator*) -> MaybeError {
+            return DAWN_MAKE_ERROR(InternalErrorType::Validation, std::string(message));
+        },
+        "injecting validation error: %s.", message);
 }
 
-void CommandEncoder::APIInsertDebugMarker(const char* groupLabel) {
+void CommandEncoder::APIInsertDebugMarker2(std::string_view groupLabel) {
+    groupLabel = utils::NormalizeLabel(groupLabel);
     mEncodingContext.TryEncode(
         this,
         [&](CommandAllocator* allocator) -> MaybeError {
             InsertDebugMarkerCmd* cmd =
                 allocator->Allocate<InsertDebugMarkerCmd>(Command::InsertDebugMarker);
-            cmd->length = strlen(groupLabel);
-
-            char* label = allocator->AllocateData<char>(cmd->length + 1);
-            memcpy(label, groupLabel, cmd->length + 1);
+            cmd->length = groupLabel.length();
+            allocator->CopyAsNullTerminatedString(groupLabel);
 
             return {};
         },
@@ -1915,19 +1918,18 @@
         "encoding %s.PopDebugGroup().", this);
 }
 
-void CommandEncoder::APIPushDebugGroup(const char* groupLabel) {
+void CommandEncoder::APIPushDebugGroup2(std::string_view groupLabel) {
+    groupLabel = utils::NormalizeLabel(groupLabel);
     mEncodingContext.TryEncode(
         this,
         [&](CommandAllocator* allocator) -> MaybeError {
             PushDebugGroupCmd* cmd =
                 allocator->Allocate<PushDebugGroupCmd>(Command::PushDebugGroup);
-            cmd->length = strlen(groupLabel);
-
-            char* label = allocator->AllocateData<char>(cmd->length + 1);
-            memcpy(label, groupLabel, cmd->length + 1);
+            cmd->length = groupLabel.length();
+            const char* label = allocator->CopyAsNullTerminatedString(groupLabel);
 
             mDebugGroupStackSize++;
-            mEncodingContext.PushDebugGroupLabel(groupLabel);
+            mEncodingContext.PushDebugGroupLabel(std::string_view(label, cmd->length));
 
             return {};
         },
@@ -2036,7 +2038,7 @@
     auto deviceLock(GetDevice()->GetScopedLock());
 
     Ref<CommandBufferBase> commandBuffer;
-    if (GetDevice()->ConsumedError(Finish(descriptor), &commandBuffer)) {
+    if (GetDevice()->ConsumedError(Finish(descriptor), &commandBuffer, "finishing %s.", this)) {
         Ref<CommandBufferBase> errorCommandBuffer =
             CommandBufferBase::MakeError(GetDevice(), descriptor ? descriptor->label : nullptr);
         errorCommandBuffer->SetEncoderLabel(this->GetLabel());
diff --git a/src/dawn/native/CommandEncoder.h b/src/dawn/native/CommandEncoder.h
index 552e65a..e742ca1 100644
--- a/src/dawn/native/CommandEncoder.h
+++ b/src/dawn/native/CommandEncoder.h
@@ -89,10 +89,14 @@
                                  const Extent3D* copySize);
     void APIClearBuffer(BufferBase* destination, uint64_t destinationOffset, uint64_t size);
 
-    void APIInjectValidationError(const char* message);
-    void APIInsertDebugMarker(const char* groupLabel);
+    // TODO(crbug.com/42241188): Remove const char* version of the methods.
+    void APIInjectValidationError(const char* message) { APIInjectValidationError2(message); }
+    void APIInjectValidationError2(std::string_view message);
+    void APIInsertDebugMarker(const char* groupLabel) { APIInsertDebugMarker2(groupLabel); }
+    void APIInsertDebugMarker2(std::string_view groupLabel);
     void APIPopDebugGroup();
-    void APIPushDebugGroup(const char* groupLabel);
+    void APIPushDebugGroup(const char* groupLabel) { APIPushDebugGroup2(groupLabel); }
+    void APIPushDebugGroup2(std::string_view groupLabel);
 
     void APIResolveQuerySet(QuerySetBase* querySet,
                             uint32_t firstQuery,
diff --git a/src/dawn/native/CompilationMessages.cpp b/src/dawn/native/CompilationMessages.cpp
index 11a7bd0..3fed812 100644
--- a/src/dawn/native/CompilationMessages.cpp
+++ b/src/dawn/native/CompilationMessages.cpp
@@ -90,13 +90,13 @@
 
 OwnedCompilationMessages::~OwnedCompilationMessages() = default;
 
-void OwnedCompilationMessages::AddUnanchoredMessage(std::string message,
+void OwnedCompilationMessages::AddUnanchoredMessage(std::string_view message,
                                                     wgpu::CompilationMessageType type) {
     AddMessage(message, {nullptr, nullptr, static_cast<WGPUCompilationMessageType>(type), 0, 0, 0,
                          0, 0, 0, 0});
 }
 
-void OwnedCompilationMessages::AddMessageForTesting(std::string message,
+void OwnedCompilationMessages::AddMessageForTesting(std::string_view message,
                                                     wgpu::CompilationMessageType type,
                                                     uint64_t lineNum,
                                                     uint64_t linePos,
@@ -168,7 +168,7 @@
     return {};
 }
 
-void OwnedCompilationMessages::AddMessage(std::string messageString,
+void OwnedCompilationMessages::AddMessage(std::string_view messageString,
                                           const WGPUCompilationMessage& message) {
     // Cannot add messages after GetCompilationInfo has been called.
     DAWN_ASSERT(mCompilationInfo.messages == nullptr);
@@ -177,7 +177,7 @@
     // The message string won't be populated until GetCompilationInfo.
     DAWN_ASSERT(message.message == nullptr);
 
-    mMessageStrings.push_back(messageString);
+    mMessageStrings.push_back(std::string(messageString));
     mMessages.push_back(message);
 }
 
diff --git a/src/dawn/native/CompilationMessages.h b/src/dawn/native/CompilationMessages.h
index d77b085..55c7c8b 100644
--- a/src/dawn/native/CompilationMessages.h
+++ b/src/dawn/native/CompilationMessages.h
@@ -52,12 +52,12 @@
 
     // Adds a message on line 0 (before the first line).
     void AddUnanchoredMessage(
-        std::string message,
+        std::string_view message,
         wgpu::CompilationMessageType type = wgpu::CompilationMessageType::Info);
     // For testing only. Uses the linePos/offset/length for both utf8 and utf16
     // (which is incorrect for non-ASCII strings).
     void AddMessageForTesting(
-        std::string message,
+        std::string_view message,
         wgpu::CompilationMessageType type = wgpu::CompilationMessageType::Info,
         uint64_t lineNum = 0,
         uint64_t linePos = 0,
@@ -72,7 +72,7 @@
 
   private:
     MaybeError AddMessage(const tint::diag::Diagnostic& diagnostic);
-    void AddMessage(std::string messageString, const WGPUCompilationMessage& message);
+    void AddMessage(std::string_view messageString, const WGPUCompilationMessage& message);
     void AddFormattedTintMessages(const tint::diag::List& diagnostics);
 
     WGPUCompilationInfo mCompilationInfo;
diff --git a/src/dawn/native/Device.cpp b/src/dawn/native/Device.cpp
index 4e88ab2..fb2ac7a 100644
--- a/src/dawn/native/Device.cpp
+++ b/src/dawn/native/Device.cpp
@@ -983,14 +983,15 @@
     return {};
 }
 
-void DeviceBase::APIForceLoss(wgpu::DeviceLostReason reason, const char* message) {
+void DeviceBase::APIForceLoss2(wgpu::DeviceLostReason reason, std::string_view message) {
+    message = utils::NormalizeLabel(message);
     if (mState != State::Alive) {
         return;
     }
     // Note that since we are passing None as the allowedErrors, an additional message will be
     // appended noting that the error was unexpected. Since this call is for testing only it is not
     // too important, but useful for users to understand where the extra message is coming from.
-    HandleError(DAWN_INTERNAL_ERROR(message), InternalErrorType::None, ToAPI(reason));
+    HandleError(DAWN_INTERNAL_ERROR(std::string(message)), InternalErrorType::None, ToAPI(reason));
 }
 
 DeviceBase::State DeviceBase::GetState() const {
@@ -1604,8 +1605,8 @@
     return ReturnToAPI(std::move(result));
 }
 
-ShaderModuleBase* DeviceBase::APICreateErrorShaderModule(const ShaderModuleDescriptor* descriptor,
-                                                         const char* errorMessage) {
+ShaderModuleBase* DeviceBase::APICreateErrorShaderModule2(const ShaderModuleDescriptor* descriptor,
+                                                          std::string_view errorMessage) {
     Ref<ShaderModuleBase> result =
         ShaderModuleBase::MakeError(this, descriptor ? descriptor->label : nullptr);
     std::unique_ptr<OwnedCompilationMessages> compilationMessages(
@@ -2014,7 +2015,7 @@
     return mEnabledFeatures.EnumerateFeatures(features);
 }
 
-void DeviceBase::APIInjectError(wgpu::ErrorType type, const char* message) {
+void DeviceBase::APIInjectError2(wgpu::ErrorType type, std::string_view message) {
     if (ConsumedError(ValidateErrorType(type))) {
         return;
     }
@@ -2027,7 +2028,9 @@
         return;
     }
 
-    HandleError(DAWN_MAKE_ERROR(FromWGPUErrorType(type), message), InternalErrorType::OutOfMemory);
+    message = utils::NormalizeLabel(message);
+    HandleError(DAWN_MAKE_ERROR(FromWGPUErrorType(type), std::string(message)),
+                InternalErrorType::OutOfMemory);
 }
 
 void DeviceBase::APIValidateTextureDescriptor(const TextureDescriptor* descriptorOrig) {
@@ -2504,7 +2507,12 @@
 }
 
 void DeviceBase::APISetLabel(const char* label) {
-    mLabel = label;
+    mLabel = label ? label : "";
+    SetLabelImpl();
+}
+
+void DeviceBase::APISetLabel2(std::optional<std::string_view> label) {
+    mLabel = utils::NormalizeLabel(label);
     SetLabelImpl();
 }
 
diff --git a/src/dawn/native/Device.h b/src/dawn/native/Device.h
index 267829a..183ce56 100644
--- a/src/dawn/native/Device.h
+++ b/src/dawn/native/Device.h
@@ -279,7 +279,11 @@
     SamplerBase* APICreateSampler(const SamplerDescriptor* descriptor);
     ShaderModuleBase* APICreateShaderModule(const ShaderModuleDescriptor* descriptor);
     ShaderModuleBase* APICreateErrorShaderModule(const ShaderModuleDescriptor* descriptor,
-                                                 const char* errorMessage);
+                                                 const char* errorMessage) {
+        return APICreateErrorShaderModule2(descriptor, errorMessage);
+    }
+    ShaderModuleBase* APICreateErrorShaderModule2(const ShaderModuleDescriptor* descriptor,
+                                                  std::string_view errorMessage);
     // TODO(crbug.com/dawn/2320): Remove after deprecation.
     SwapChainBase* APICreateSwapChain(Surface* surface, const SwapChainDescriptor* descriptor);
     TextureBase* APICreateTexture(const TextureDescriptor* descriptor);
@@ -301,7 +305,11 @@
     wgpu::Status APIGetLimits(SupportedLimits* limits) const;
     bool APIHasFeature(wgpu::FeatureName feature) const;
     size_t APIEnumerateFeatures(wgpu::FeatureName* features) const;
-    void APIInjectError(wgpu::ErrorType type, const char* message);
+    void APIInjectError(wgpu::ErrorType type, const char* message) {
+        // TODO(crbug.com/42241188): Remove const char* version of the method.
+        APIInjectError2(type, message);
+    }
+    void APIInjectError2(wgpu::ErrorType type, std::string_view message);
     bool APITick();
     void APIValidateTextureDescriptor(const TextureDescriptor* desc);
 
@@ -371,7 +379,11 @@
     void EmitLog(const char* message);
     void EmitLog(WGPULoggingType loggingType, const char* message);
     void EmitCompilationLog(const ShaderModuleBase* module);
-    void APIForceLoss(wgpu::DeviceLostReason reason, const char* message);
+    void APIForceLoss(wgpu::DeviceLostReason reason, const char* message) {
+        // TODO(crbug.com/42241188): Remove const char* version of the method.
+        return APIForceLoss2(reason, message);
+    }
+    void APIForceLoss2(wgpu::DeviceLostReason reason, std::string_view message);
     QueueBase* GetQueue() const;
 
     friend class IgnoreLazyClearCountScope;
@@ -419,7 +431,9 @@
 
     const CacheKey& GetCacheKey() const;
     const std::string& GetLabel() const;
+    // TODO(crbug.com/42241188): Remove const char* version of the method.
     void APISetLabel(const char* label);
+    void APISetLabel2(std::optional<std::string_view> label);
     void APIDestroy();
 
     virtual void AppendDebugLayerMessages(ErrorData* error) {}
diff --git a/src/dawn/native/EncodingContext.cpp b/src/dawn/native/EncodingContext.cpp
index e70bd2f..1000783 100644
--- a/src/dawn/native/EncodingContext.cpp
+++ b/src/dawn/native/EncodingContext.cpp
@@ -208,7 +208,7 @@
     return std::move(mComputePassUsages);
 }
 
-void EncodingContext::PushDebugGroupLabel(const char* groupLabel) {
+void EncodingContext::PushDebugGroupLabel(std::string_view groupLabel) {
     mDebugGroupLabels.emplace_back(groupLabel);
 }
 
diff --git a/src/dawn/native/EncodingContext.h b/src/dawn/native/EncodingContext.h
index 601c5fe..909ced9 100644
--- a/src/dawn/native/EncodingContext.h
+++ b/src/dawn/native/EncodingContext.h
@@ -160,7 +160,7 @@
     RenderPassUsages AcquireRenderPassUsages();
     ComputePassUsages AcquireComputePassUsages();
 
-    void PushDebugGroupLabel(const char* groupLabel);
+    void PushDebugGroupLabel(std::string_view groupLabel);
     void PopDebugGroupLabel();
 
   private:
@@ -195,7 +195,7 @@
     bool mDestroyed = false;
 
     std::unique_ptr<ErrorData> mError;
-    std::vector<std::string> mDebugGroupLabels;
+    std::vector<std::string_view> mDebugGroupLabels;
 };
 
 }  // namespace dawn::native
diff --git a/src/dawn/native/ErrorData.cpp b/src/dawn/native/ErrorData.cpp
index 9d111c8..fd01df5 100644
--- a/src/dawn/native/ErrorData.cpp
+++ b/src/dawn/native/ErrorData.cpp
@@ -43,7 +43,7 @@
                                              const char* file,
                                              const char* function,
                                              int line) {
-    std::unique_ptr<ErrorData> error = std::make_unique<ErrorData>(type, message);
+    std::unique_ptr<ErrorData> error = std::make_unique<ErrorData>(type, std::move(message));
     error->AppendBacktrace(file, function, line);
 
     auto [var, present] = GetEnvironmentVar("DAWN_DEBUG_BREAK_ON_ERROR");
@@ -72,8 +72,8 @@
     mContexts.push_back(std::move(context));
 }
 
-void ErrorData::AppendDebugGroup(std::string label) {
-    mDebugGroups.push_back(std::move(label));
+void ErrorData::AppendDebugGroup(std::string_view label) {
+    mDebugGroups.push_back(std::string(label));
 }
 
 void ErrorData::AppendBackendMessage(std::string message) {
diff --git a/src/dawn/native/ErrorData.h b/src/dawn/native/ErrorData.h
index 38956d8..c1d72d2 100644
--- a/src/dawn/native/ErrorData.h
+++ b/src/dawn/native/ErrorData.h
@@ -75,7 +75,7 @@
             AppendContext(absl::StrFormat("[Failed to format error: \"%s\"]", formatStr));
         }
     }
-    void AppendDebugGroup(std::string label);
+    void AppendDebugGroup(std::string_view label);
     void AppendBackendMessage(std::string message);
     template <typename... Args>
     void AppendBackendMessage(const char* formatStr, const Args&... args) {
diff --git a/src/dawn/native/ObjectBase.cpp b/src/dawn/native/ObjectBase.cpp
index c3e9fbe..2242825 100644
--- a/src/dawn/native/ObjectBase.cpp
+++ b/src/dawn/native/ObjectBase.cpp
@@ -34,6 +34,7 @@
 #include "dawn/native/Device.h"
 #include "dawn/native/ObjectBase.h"
 #include "dawn/native/ObjectType_autogen.h"
+#include "dawn/native/utils/WGPUHelpers.h"
 
 namespace dawn::native {
 
@@ -105,7 +106,11 @@
 }
 
 void ApiObjectBase::APISetLabel(const char* label) {
-    SetLabel(label);
+    SetLabel(std::string(label ? label : ""));
+}
+
+void ApiObjectBase::APISetLabel2(std::optional<std::string_view> label) {
+    SetLabel(std::string(utils::NormalizeLabel(label)));
 }
 
 void ApiObjectBase::SetLabel(std::string label) {
diff --git a/src/dawn/native/ObjectBase.h b/src/dawn/native/ObjectBase.h
index 67c9465..ed80cb7 100644
--- a/src/dawn/native/ObjectBase.h
+++ b/src/dawn/native/ObjectBase.h
@@ -29,6 +29,7 @@
 #define SRC_DAWN_NATIVE_OBJECTBASE_H_
 
 #include <mutex>
+#include <optional>
 #include <string>
 
 #include "absl/strings/str_format.h"
@@ -139,7 +140,9 @@
     void Destroy();
 
     // Dawn API
+    // TODO(crbug.com/42241188): Remove const char* version of the method.
     void APISetLabel(const char* label);
+    void APISetLabel2(std::optional<std::string_view> label);
 
   protected:
     // Overriding of the RefCounted's DeleteThis function ensures that instances of objects
diff --git a/src/dawn/native/ProgrammableEncoder.cpp b/src/dawn/native/ProgrammableEncoder.cpp
index 3372931..47f7af0 100644
--- a/src/dawn/native/ProgrammableEncoder.cpp
+++ b/src/dawn/native/ProgrammableEncoder.cpp
@@ -38,6 +38,7 @@
 #include "dawn/native/Device.h"
 #include "dawn/native/ObjectType_autogen.h"
 #include "dawn/native/ValidationUtils_autogen.h"
+#include "dawn/native/utils/WGPUHelpers.h"
 
 namespace dawn::native {
 
@@ -67,16 +68,15 @@
     return {};
 }
 
-void ProgrammableEncoder::APIInsertDebugMarker(const char* groupLabel) {
+void ProgrammableEncoder::APIInsertDebugMarker2(std::string_view groupLabel) {
+    groupLabel = utils::NormalizeLabel(groupLabel);
     mEncodingContext->TryEncode(
         this,
         [&](CommandAllocator* allocator) -> MaybeError {
             InsertDebugMarkerCmd* cmd =
                 allocator->Allocate<InsertDebugMarkerCmd>(Command::InsertDebugMarker);
-            cmd->length = strlen(groupLabel);
-
-            char* label = allocator->AllocateData<char>(cmd->length + 1);
-            memcpy(label, groupLabel, cmd->length + 1);
+            cmd->length = groupLabel.length();
+            allocator->CopyAsNullTerminatedString(groupLabel);
 
             return {};
         },
@@ -100,19 +100,18 @@
         "encoding %s.PopDebugGroup().", this);
 }
 
-void ProgrammableEncoder::APIPushDebugGroup(const char* groupLabel) {
+void ProgrammableEncoder::APIPushDebugGroup2(std::string_view groupLabel) {
+    groupLabel = utils::NormalizeLabel(groupLabel);
     mEncodingContext->TryEncode(
         this,
         [&](CommandAllocator* allocator) -> MaybeError {
             PushDebugGroupCmd* cmd =
                 allocator->Allocate<PushDebugGroupCmd>(Command::PushDebugGroup);
-            cmd->length = strlen(groupLabel);
-
-            char* label = allocator->AllocateData<char>(cmd->length + 1);
-            memcpy(label, groupLabel, cmd->length + 1);
+            cmd->length = groupLabel.length();
+            const char* label = allocator->CopyAsNullTerminatedString(groupLabel);
 
             mDebugGroupStackSize++;
-            mEncodingContext->PushDebugGroupLabel(groupLabel);
+            mEncodingContext->PushDebugGroupLabel(std::string_view(label, cmd->length));
 
             return {};
         },
diff --git a/src/dawn/native/ProgrammableEncoder.h b/src/dawn/native/ProgrammableEncoder.h
index 74583e4..cb89145 100644
--- a/src/dawn/native/ProgrammableEncoder.h
+++ b/src/dawn/native/ProgrammableEncoder.h
@@ -48,9 +48,12 @@
   public:
     ProgrammableEncoder(DeviceBase* device, const char* label, EncodingContext* encodingContext);
 
-    void APIInsertDebugMarker(const char* groupLabel);
+    // TODO(crbug.com/42241188): Remove const char* version of the methods.
+    void APIInsertDebugMarker(const char* groupLabel) { APIInsertDebugMarker2(groupLabel); }
+    void APIInsertDebugMarker2(std::string_view groupLabel);
     void APIPopDebugGroup();
-    void APIPushDebugGroup(const char* groupLabel);
+    void APIPushDebugGroup(const char* groupLabel) { APIPushDebugGroup2(groupLabel); }
+    void APIPushDebugGroup2(std::string_view groupLabel);
 
   protected:
     bool IsValidationEnabled() const;
diff --git a/src/dawn/native/Surface.cpp b/src/dawn/native/Surface.cpp
index c0b322b..fbad6ef 100644
--- a/src/dawn/native/Surface.cpp
+++ b/src/dawn/native/Surface.cpp
@@ -661,7 +661,11 @@
 }
 
 void Surface::APISetLabel(const char* label) {
-    mLabel = label;
+    mLabel = label ? label : "";
+}
+
+void Surface::APISetLabel2(std::optional<std::string_view> label) {
+    mLabel = utils::NormalizeLabel(label);
 }
 
 }  // namespace dawn::native
diff --git a/src/dawn/native/Surface.h b/src/dawn/native/Surface.h
index 9ac666a..3907328 100644
--- a/src/dawn/native/Surface.h
+++ b/src/dawn/native/Surface.h
@@ -128,7 +128,9 @@
     wgpu::TextureFormat APIGetPreferredFormat(AdapterBase* adapter) const;
     void APIPresent();
     void APIUnconfigure();
+    // TODO(crbug.com/42241188): Remove const char* version of the method.
     void APISetLabel(const char* label);
+    void APISetLabel2(std::optional<std::string_view> label);
 
   private:
     Surface(InstanceBase* instance, ErrorMonad::ErrorTag tag);
diff --git a/src/dawn/native/utils/WGPUHelpers.cpp b/src/dawn/native/utils/WGPUHelpers.cpp
index b1606ac..74d61a1 100644
--- a/src/dawn/native/utils/WGPUHelpers.cpp
+++ b/src/dawn/native/utils/WGPUHelpers.cpp
@@ -211,4 +211,16 @@
     return (label == nullptr || strlen(label) == 0) ? "None" : label;
 }
 
+std::string_view NormalizeLabel(std::string_view in) {
+    return std::string_view(in.data(), strnlen(in.data(), in.length()));
+}
+
+std::string_view NormalizeLabel(std::optional<std::string_view> in) {
+    if (in) {
+        return NormalizeLabel(*in);
+    } else {
+        return {};
+    }
+}
+
 }  // namespace dawn::native::utils
diff --git a/src/dawn/native/utils/WGPUHelpers.h b/src/dawn/native/utils/WGPUHelpers.h
index c501fd7..745a16d 100644
--- a/src/dawn/native/utils/WGPUHelpers.h
+++ b/src/dawn/native/utils/WGPUHelpers.h
@@ -167,6 +167,10 @@
     *apiSize = 0;
 }
 
+// Normalize the label, truncating it at the first null-terminator, if any.
+std::string_view NormalizeLabel(std::string_view in);
+std::string_view NormalizeLabel(std::optional<std::string_view> in);
+
 }  // namespace dawn::native::utils
 
 #endif  // SRC_DAWN_NATIVE_UTILS_WGPUHELPERS_H_
diff --git a/src/dawn/tests/BUILD.gn b/src/dawn/tests/BUILD.gn
index bf0d055..6b6321f 100644
--- a/src/dawn/tests/BUILD.gn
+++ b/src/dawn/tests/BUILD.gn
@@ -327,6 +327,7 @@
     "unittests/CommandAllocatorTests.cpp",
     "unittests/CommandLineParserTests.cpp",
     "unittests/ContentLessObjectCacheTests.cpp",
+    "unittests/CppAPITests.cpp",
     "unittests/DefaultTests.cpp",
     "unittests/EnumClassBitmasksTests.cpp",
     "unittests/EnumMaskIteratorTests.cpp",
diff --git a/src/dawn/tests/unittests/CppAPITests.cpp b/src/dawn/tests/unittests/CppAPITests.cpp
new file mode 100644
index 0000000..dcb72a0
--- /dev/null
+++ b/src/dawn/tests/unittests/CppAPITests.cpp
@@ -0,0 +1,225 @@
+// Copyright 2024 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 <gmock/gmock.h>
+#include <gtest/gtest.h>
+
+#include "dawn/native/dawn_platform.h"
+
+namespace dawn::native {
+namespace {
+
+// Test that default construction or assignment to wgpu::NullableStringView produces the nil string.
+TEST(CppAPITests, WGPUStringDefault) {
+    {
+        wgpu::NullableStringView s;
+        EXPECT_EQ(s.data, nullptr);
+        EXPECT_EQ(s.length, SIZE_MAX);
+    }
+    {
+        wgpu::NullableStringView s{};
+        EXPECT_EQ(s.data, nullptr);
+        EXPECT_EQ(s.length, SIZE_MAX);
+    }
+    {
+        wgpu::NullableStringView s = {};
+        EXPECT_EQ(s.data, nullptr);
+        EXPECT_EQ(s.length, SIZE_MAX);
+    }
+    {
+        wgpu::NullableStringView s = wgpu::NullableStringView();
+        EXPECT_EQ(s.data, nullptr);
+        EXPECT_EQ(s.length, SIZE_MAX);
+    }
+
+    // Test that resetting the string, clears both data and length.
+    std::string_view sv("hello world!");
+    {
+        wgpu::NullableStringView s(sv);
+        s = {};
+        EXPECT_EQ(s.data, nullptr);
+        EXPECT_EQ(s.length, SIZE_MAX);
+    }
+    {
+        wgpu::NullableStringView s(sv);
+        s = wgpu::NullableStringView();
+        EXPECT_EQ(s.data, nullptr);
+        EXPECT_EQ(s.length, SIZE_MAX);
+    }
+}
+
+// Test that construction or assignment to wgpu::NullableStringView from const char*.
+TEST(CppAPITests, WGPUStringFromCstr) {
+    {
+        wgpu::NullableStringView s("hello world!");
+        EXPECT_STREQ(s.data, "hello world!");
+        EXPECT_EQ(s.length, SIZE_MAX);
+    }
+    {
+        wgpu::NullableStringView s{"hello world!"};
+        EXPECT_STREQ(s.data, "hello world!");
+        EXPECT_EQ(s.length, SIZE_MAX);
+    }
+    {
+        wgpu::NullableStringView s = {"hello world!"};
+        EXPECT_STREQ(s.data, "hello world!");
+        EXPECT_EQ(s.length, SIZE_MAX);
+    }
+    {
+        wgpu::NullableStringView s = wgpu::NullableStringView("hello world!");
+        EXPECT_STREQ(s.data, "hello world!");
+        EXPECT_EQ(s.length, SIZE_MAX);
+    }
+
+    // Test that setting to a cstr clears the length.
+    std::string_view sv("hello world!");
+    {
+        wgpu::NullableStringView s(sv);
+        s = "other str";
+        EXPECT_STREQ(s.data, "other str");
+        EXPECT_EQ(s.length, SIZE_MAX);
+    }
+}
+
+// Test that construction or assignment to wgpu::NullableStringView from std::string_view
+TEST(CppAPITests, WGPUStringFromStdStringView) {
+    std::string_view sv("hello\x00world!");
+    {
+        wgpu::NullableStringView s(sv);
+        EXPECT_EQ(s.data, sv.data());
+        EXPECT_EQ(s.length, sv.length());
+    }
+    {
+        wgpu::NullableStringView s{sv};
+        EXPECT_EQ(s.data, sv.data());
+        EXPECT_EQ(s.length, sv.length());
+    }
+    {
+        wgpu::NullableStringView s = {sv};
+        EXPECT_EQ(s.data, sv.data());
+        EXPECT_EQ(s.length, sv.length());
+    }
+    {
+        wgpu::NullableStringView s = wgpu::NullableStringView(sv);
+        EXPECT_EQ(s.data, sv.data());
+        EXPECT_EQ(s.length, sv.length());
+    }
+}
+
+// Test that construction or assignment to wgpu::NullableStringView from pointer and length
+TEST(CppAPITests, WGPUStringFromPtrAndLength) {
+    std::string_view sv("hello\x00world!");
+    {
+        wgpu::NullableStringView s(sv.data(), sv.length());
+        EXPECT_EQ(s.data, sv.data());
+        EXPECT_EQ(s.length, sv.length());
+    }
+    {
+        wgpu::NullableStringView s{sv.data(), sv.length()};
+        EXPECT_EQ(s.data, sv.data());
+        EXPECT_EQ(s.length, sv.length());
+    }
+    {
+        wgpu::NullableStringView s = {sv.data(), sv.length()};
+        EXPECT_EQ(s.data, sv.data());
+        EXPECT_EQ(s.length, sv.length());
+    }
+    {
+        wgpu::NullableStringView s = wgpu::NullableStringView(sv.data(), sv.length());
+        EXPECT_EQ(s.data, sv.data());
+        EXPECT_EQ(s.length, sv.length());
+    }
+}
+
+// Test that construction or assignment to wgpu::NullableStringView from nullptr
+TEST(CppAPITests, WGPUStringFromNullptr) {
+    {
+        wgpu::NullableStringView s(nullptr);
+        EXPECT_EQ(s.data, nullptr);
+        EXPECT_EQ(s.length, SIZE_MAX);
+    }
+    {
+        wgpu::NullableStringView s{nullptr};
+        EXPECT_EQ(s.data, nullptr);
+        EXPECT_EQ(s.length, SIZE_MAX);
+    }
+    {
+        wgpu::NullableStringView s = {nullptr};
+        EXPECT_EQ(s.data, nullptr);
+        EXPECT_EQ(s.length, SIZE_MAX);
+    }
+    {
+        wgpu::NullableStringView s = wgpu::NullableStringView(nullptr);
+        EXPECT_EQ(s.data, nullptr);
+        EXPECT_EQ(s.length, SIZE_MAX);
+    }
+
+    // Test that setting to nullptr, clears both data and length.
+    std::string_view sv("hello world!");
+    {
+        wgpu::NullableStringView s(sv);
+        s = nullptr;
+        EXPECT_EQ(s.data, nullptr);
+        EXPECT_EQ(s.length, SIZE_MAX);
+    }
+}
+
+// Test that construction or assignment to wgpu::NullableStringView from std::nullopt
+TEST(CppAPITests, WGPUStringFromNullopt) {
+    {
+        wgpu::NullableStringView s(std::nullopt);
+        EXPECT_EQ(s.data, nullptr);
+        EXPECT_EQ(s.length, SIZE_MAX);
+    }
+    {
+        wgpu::NullableStringView s{std::nullopt};
+        EXPECT_EQ(s.data, nullptr);
+        EXPECT_EQ(s.length, SIZE_MAX);
+    }
+    {
+        wgpu::NullableStringView s = {std::nullopt};
+        EXPECT_EQ(s.data, nullptr);
+        EXPECT_EQ(s.length, SIZE_MAX);
+    }
+    {
+        wgpu::NullableStringView s = wgpu::NullableStringView(std::nullopt);
+        EXPECT_EQ(s.data, nullptr);
+        EXPECT_EQ(s.length, SIZE_MAX);
+    }
+
+    // Test that setting to std::nullopt, clears both data and length.
+    std::string_view sv("hello world!");
+    {
+        wgpu::NullableStringView s(sv);
+        s = std::nullopt;
+        EXPECT_EQ(s.data, nullptr);
+        EXPECT_EQ(s.length, SIZE_MAX);
+    }
+}
+
+}  // anonymous namespace
+}  // namespace dawn::native
diff --git a/src/dawn/tests/unittests/validation/CommandBufferValidationTests.cpp b/src/dawn/tests/unittests/validation/CommandBufferValidationTests.cpp
index 093876f..0e7ab0b 100644
--- a/src/dawn/tests/unittests/validation/CommandBufferValidationTests.cpp
+++ b/src/dawn/tests/unittests/validation/CommandBufferValidationTests.cpp
@@ -37,6 +37,7 @@
 namespace {
 
 using ::testing::HasSubstr;
+using ::testing::StartsWith;
 
 class CommandBufferValidationTest : public ValidationTest {};
 
@@ -342,6 +343,80 @@
     ASSERT_DEVICE_ERROR(encoder.Finish(), HasSubstr("my error"));
 }
 
+// Test that calling inject validation error with a std::string_view produces an error which
+// preserves the string.
+TEST_F(CommandBufferValidationTest, InjectedValidateErrorStringView) {
+    wgpu::CommandEncoder encoder = device.CreateCommandEncoder();
+    std::string_view sv = "my error";
+    encoder.InjectValidationError(sv);
+    ASSERT_DEVICE_ERROR(encoder.Finish(), HasSubstr(sv));
+}
+
+// Test that calling inject validation error with various wgpu::NullableStringView produces
+// an error which preserves the string.
+TEST_F(CommandBufferValidationTest, InjectedValidateErrorVariousStringTypes) {
+    // Use strlen
+    {
+        wgpu::CommandEncoder encoder = device.CreateCommandEncoder();
+        const char* s = "my error";
+        encoder.InjectValidationError(s);
+        ASSERT_DEVICE_ERROR(encoder.Finish(), HasSubstr(s));
+    }
+
+    // Use explicit length which truncates a null-terminated string.
+    {
+        wgpu::CommandEncoder encoder = device.CreateCommandEncoder();
+        encoder.InjectValidationError(std::string_view("my error bad", 8));
+        ASSERT_DEVICE_ERROR(encoder.Finish(),
+                            testing::AllOf(HasSubstr("my error"), Not(HasSubstr("bad"))));
+    }
+
+    // Empty, nullptr string
+    {
+        wgpu::CommandEncoder encoder = device.CreateCommandEncoder();
+        encoder.InjectValidationError(std::string_view(nullptr, 0));
+        // empty error string, followed by a newline and error context
+        ASSERT_DEVICE_ERROR(encoder.Finish(), StartsWith("\n"));
+    }
+
+    // Empty, non-null string
+    {
+        wgpu::CommandEncoder encoder = device.CreateCommandEncoder();
+        encoder.InjectValidationError(std::string_view("foobar", 0));
+        // empty error string, followed by a newline and error context
+        ASSERT_DEVICE_ERROR(encoder.Finish(), StartsWith("\n"));
+    }
+
+    // Set label on encoder and inject validation error
+    {
+        wgpu::CommandEncoder encoder = device.CreateCommandEncoder();
+        encoder.SetLabel("my encoder");
+        encoder.InjectValidationError("err");
+        ASSERT_DEVICE_ERROR(encoder.Finish(), HasSubstr("my encoder"));
+    }
+
+    // Set label on encoder, then clear it, and inject validation error
+    {
+        wgpu::CommandEncoder encoder = device.CreateCommandEncoder();
+        encoder.SetLabel("my encoder");
+        encoder.SetLabel(std::nullopt);
+        encoder.InjectValidationError("err");
+        ASSERT_DEVICE_ERROR(encoder.Finish(), HasSubstr("CommandEncoder (unlabeled)"));
+    }
+
+    // Encoder label has a null terminator and the injected error has a null terminator.
+    // Both get truncated at the null terminator, but they don't truncate each other.
+    // The error should have both the first part of the encoder label and the first
+    // part of the injected error.
+    {
+        wgpu::CommandEncoder encoder = device.CreateCommandEncoder();
+        encoder.SetLabel(std::string_view("my\0encoder", 10));
+        encoder.InjectValidationError(std::string_view("err\0or", 6));
+        ASSERT_DEVICE_ERROR(encoder.Finish(), AllOf(HasSubstr("validation error: err."),
+                                                    HasSubstr("[CommandEncoder \"my\"]")));
+    }
+}
+
 TEST_F(CommandBufferValidationTest, DestroyEncoder) {
     // Skip these tests if we are using wire because the destroy functionality is not exposed
     // and needs to use a cast to call manually. We cannot test this in the wire case since the
diff --git a/src/dawn/tests/unittests/wire/WireArgumentTests.cpp b/src/dawn/tests/unittests/wire/WireArgumentTests.cpp
index 517ad89..ee11864 100644
--- a/src/dawn/tests/unittests/wire/WireArgumentTests.cpp
+++ b/src/dawn/tests/unittests/wire/WireArgumentTests.cpp
@@ -35,9 +35,18 @@
 namespace {
 
 using testing::_;
+using testing::AllOf;
+using testing::Eq;
+using testing::Field;
 using testing::Return;
 using testing::Sequence;
 
+MATCHER_P2(EqBytes, bytes, size, "") {
+    const char* dataToCheck = arg;
+    bool isMatch = (memcmp(dataToCheck, bytes, size) == 0);
+    return isMatch;
+}
+
 class WireArgumentTests : public WireTest {
   public:
     WireArgumentTests() {}
@@ -164,6 +173,57 @@
     FlushClient();
 }
 
+// Test that the wire is able to send WGPUStringViews
+TEST_F(WireArgumentTests, WGPUStringView) {
+    // Create shader module
+    wgpu::ShaderModuleDescriptor vertexDescriptor = {};
+    wgpu::ShaderModule vsModule = device.CreateShaderModule(&vertexDescriptor);
+    WGPUShaderModule apiVsModule = api.GetNewShaderModule();
+    EXPECT_CALL(api, DeviceCreateShaderModule(apiDevice, _)).WillOnce(Return(apiVsModule));
+
+    const char* label = "null-terminated label\0more string";
+    vsModule.SetLabel(std::string_view(label));
+    EXPECT_CALL(api, ShaderModuleSetLabel2(apiVsModule,
+                                           AllOf(Field(&WGPUStringView::data, EqBytes(label, 21u)),
+                                                 Field(&WGPUStringView::length, Eq(21u)))));
+    FlushClient();
+
+    // Give it a longer, explicit length that contains the null-terminator.
+    vsModule.SetLabel(std::string_view(label, 34));
+    EXPECT_CALL(api, ShaderModuleSetLabel2(apiVsModule,
+                                           AllOf(Field(&WGPUStringView::data, EqBytes(label, 34u)),
+                                                 Field(&WGPUStringView::length, Eq(34u)))));
+    FlushClient();
+
+    // Give it a shorder, explicit length.
+    vsModule.SetLabel(std::string_view(label, 2));
+    EXPECT_CALL(api, ShaderModuleSetLabel2(apiVsModule,
+                                           AllOf(Field(&WGPUStringView::data, EqBytes(label, 2u)),
+                                                 Field(&WGPUStringView::length, Eq(2u)))));
+    FlushClient();
+
+    // Give it a zero length.
+    vsModule.SetLabel(std::string_view(label, 0));
+    EXPECT_CALL(
+        api, ShaderModuleSetLabel2(apiVsModule, AllOf(Field(&WGPUStringView::data, EqBytes("", 1u)),
+                                                      Field(&WGPUStringView::length, Eq(0u)))));
+    FlushClient();
+
+    // Give it zero length and data.
+    vsModule.SetLabel(std::string_view(nullptr, 0));
+    EXPECT_CALL(api,
+                ShaderModuleSetLabel2(apiVsModule, AllOf(Field(&WGPUStringView::data, nullptr),
+                                                         Field(&WGPUStringView::length, Eq(0u)))));
+    FlushClient();
+
+    // Give it the nil string with nullopt.
+    vsModule.SetLabel(std::nullopt);
+    EXPECT_CALL(api, ShaderModuleSetLabel2(apiVsModule,
+                                           AllOf(Field(&WGPUStringView::data, nullptr),
+                                                 Field(&WGPUStringView::length, Eq(SIZE_MAX)))));
+    FlushClient();
+}
+
 // Test that the wire is able to send objects as value arguments
 TEST_F(WireArgumentTests, ObjectAsValueArgument) {
     wgpu::CommandEncoder cmdBufEncoder = device.CreateCommandEncoder();