Repository: servo/servo
Pull request #36336: dom: Implement `ClipboardItem`
Author: login: Gae24
Created: 2025-04-04T13:46:52Z | Merged: 2025-04-07T00:19:05Z

implement the `ClipboardItem` interface

Testing: covered by existing wpt tests

part of #36084


Diff:
diff --git a/components/config/prefs.rs b/components/config/prefs.rs
index 5cdfc18ac6b68..a86b5a29e7da6 100644
--- a/components/config/prefs.rs
+++ b/components/config/prefs.rs
@@ -69,6 +69,7 @@ pub struct Preferences {
     /// List of comma-separated backends to be used by wgpu.
     pub dom_webgpu_wgpu_backend: String,
     pub dom_abort_controller_enabled: bool,
+    pub dom_async_clipboard_enabled: bool,
     pub dom_bluetooth_enabled: bool,
     pub dom_bluetooth_testing_enabled: bool,
     pub dom_allow_scripts_to_close_windows: bool,
@@ -243,6 +244,7 @@ impl Preferences {
             devtools_server_port: 0,
             dom_abort_controller_enabled: false,
             dom_allow_scripts_to_close_windows: false,
+            dom_async_clipboard_enabled: false,
             dom_bluetooth_enabled: false,
             dom_bluetooth_testing_enabled: false,
             dom_canvas_capture_enabled: false,
diff --git a/components/script/dom/clipboarditem.rs b/components/script/dom/clipboarditem.rs
new file mode 100644
index 0000000000000..c1c66a403b3b3
--- /dev/null
+++ b/components/script/dom/clipboarditem.rs
@@ -0,0 +1,181 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at https://mozilla.org/MPL/2.0/. */
+
+use std::ops::Deref;
+use std::rc::Rc;
+use std::str::FromStr;
+
+use data_url::mime::Mime;
+use dom_struct::dom_struct;
+use js::rust::{HandleObject, MutableHandleValue};
+use script_bindings::record::Record;
+
+use crate::dom::bindings::cell::DomRefCell;
+use crate::dom::bindings::codegen::Bindings::ClipboardBinding::{
+    ClipboardItemMethods, ClipboardItemOptions, PresentationStyle,
+};
+use crate::dom::bindings::error::{Error, Fallible};
+use crate::dom::bindings::frozenarray::CachedFrozenArray;
+use crate::dom::bindings::reflector::{Reflector, reflect_dom_object_with_proto};
+use crate::dom::bindings::root::DomRoot;
+use crate::dom::bindings::str::DOMString;
+use crate::dom::promise::Promise;
+use crate::dom::window::Window;
+use crate::script_runtime::{CanGc, JSContext};
+
+/// <https://w3c.github.io/clipboard-apis/#web-custom-format>
+const CUSTOM_FORMAT_PREFIX: &str = "web ";
+
+/// <https://w3c.github.io/clipboard-apis/#representation>
+#[derive(JSTraceable, MallocSizeOf)]
+struct Representation {
+    #[no_trace]
+    #[ignore_malloc_size_of = "Extern type"]
+    mime_type: Mime,
+    is_custom: bool,
+    #[ignore_malloc_size_of = "Rc is hard"]
+    data: Rc<Promise>,
+}
+
+#[dom_struct]
+pub(crate) struct ClipboardItem {
+    reflector_: Reflector,
+    representations: DomRefCell<Vec<Representation>>,
+    presentation_style: DomRefCell<PresentationStyle>,
+    #[ignore_malloc_size_of = "mozjs"]
+    frozen_types: CachedFrozenArray,
+}
+
+impl ClipboardItem {
+    fn new_inherited() -> ClipboardItem {
+        ClipboardItem {
+            reflector_: Reflector::new(),
+            representations: Default::default(),
+            presentation_style: Default::default(),
+            frozen_types: CachedFrozenArray::new(),
+        }
+    }
+
+    fn new(window: &Window, proto: Option<HandleObject>, can_gc: CanGc) -> DomRoot<ClipboardItem> {
+        reflect_dom_object_with_proto(
+            Box::new(ClipboardItem::new_inherited()),
+            window,
+            proto,
+            can_gc,
+        )
+    }
+}
+
+impl ClipboardItemMethods<crate::DomTypeHolder> for ClipboardItem {
+    /// <https://w3c.github.io/clipboard-apis/#dom-clipboarditem-clipboarditem>
+    fn Constructor(
+        global: &Window,
+        proto: Option<HandleObject>,
+        can_gc: CanGc,
+        items: Record<DOMString, Rc<Promise>>,
+        options: &ClipboardItemOptions,
+    ) -> Fallible<DomRoot<ClipboardItem>> {
+        // Step 1 If items is empty, then throw a TypeError.
+        if items.is_empty() {
+            return Err(Error::Type(String::from("No item provided")));
+        }
+
+        // Step 2 If options is empty, then set options["presentationStyle"] = "unspecified".
+        // NOTE: This is done inside bindings
+
+        // Step 3 Set this's clipboard item to a new clipboard item.
+        let clipboard_item = ClipboardItem::new(global, proto, can_gc);
+
+        // Step 4 Set this's clipboard item's presentation style to options["presentationStyle"].
+        *clipboard_item.presentation_style.borrow_mut() = options.presentationStyle;
+
+        // Step 6 For each (key, value) in items:
+        for (key, value) in items.deref() {
+            // Step 6.2 Let isCustom be false.
+
+            // Step 6.3 If key starts with `"web "` prefix, then
+            // Step 6.3.1 Remove `"web "` prefix and assign the remaining string to key.
+            let (key, is_custom) = match key.strip_prefix(CUSTOM_FORMAT_PREFIX) {
+                None => (key.str(), false),
+                // Step 6.3.2 Set isCustom true
+                Some(stripped) => (stripped, true),
+            };
+
+            // Step 6.5 Let mimeType be the result of parsing a MIME type given key.
+            // Step 6.6 If mimeType is failure, then throw a TypeError.
+            let mime_type =
+                Mime::from_str(key).map_err(|_| Error::Type(String::from("Invalid mime type")))?;
+
+            // Step 6.7 If this's clipboard item's list of representations contains a representation
+            // whose MIME type is mimeType and whose [representation/isCustom] is isCustom, then throw a TypeError.
+            if clipboard_item
+                .representations
+                .borrow()
+                .iter()
+                .any(|representation| {
+                    representation.mime_type == mime_type && representation.is_custom == is_custom
+                })
+            {
+                return Err(Error::Type(String::from("Tried to add a duplicate mime")));
+            }
+
+            // Step 6.1 Let representation be a new representation.
+            // Step 6.4 Set representation’s isCustom flag to isCustom.
+            // Step 6.8 Set representation’s MIME type to mimeType.
+            // Step 6.9 Set representation’s data to value.
+            let representation = Representation {
+                mime_type,
+                is_custom,
+                data: value.clone(),
+            };
+
+            // Step 6.10 Append representation to this's clipboard item's list of representations.
+            clipboard_item
+                .representations
+                .borrow_mut()
+                .push(representation);
+        }
+
+        // NOTE: The steps for creating a frozen array from the list of mimeType are done in the Types() method
+
+        Ok(clipboard_item)
+    }
+
+    /// <https://w3c.github.io/clipboard-apis/#dom-clipboarditem-presentationstyle>
+    fn PresentationStyle(&self) -> PresentationStyle {
+        *self.presentation_style.borrow()
+    }
+
+    /// <https://w3c.github.io/clipboard-apis/#dom-clipboarditem-types>
+    fn Types(&self, cx: JSContext, can_gc: CanGc, retval: MutableHandleValue) {
+        self.frozen_types.get_or_init(
+            || {
+                // Step 5 Let types be a list of DOMString.
+                let mut types = Vec::new();
+
+                self.representations
+                    .borrow()
+                    .iter()
+                    .for_each(|representation| {
+                        // Step 6.11 Let mimeTypeString be the result of serializing a MIME type with mimeType.
+                        let mime_type_string = representation.mime_type.to_string();
+
+                        // Step 6.12 If isCustom is true, prefix mimeTypeString with `"web "`.
+                        let mime_type_string = if representation.is_custom {
+                            format!("{}{}", CUSTOM_FORMAT_PREFIX, mime_type_string)
+                        } else {
+                            mime_type_string
+                        };
+
+                        // Step 6.13 Add mimeTypeString to types.
+                        types.push(DOMString::from(mime_type_string));
+                    });
+                types
+            },
+            cx,
+            retval,
+            can_gc,
+        );
+    }
+}
diff --git a/components/script/dom/mod.rs b/components/script/dom/mod.rs
index 40db03ed65585..2e08bd684e353 100644
--- a/components/script/dom/mod.rs
+++ b/components/script/dom/mod.rs
@@ -251,6 +251,7 @@ pub(crate) mod channelsplitternode;
 pub(crate) mod characterdata;
 pub(crate) mod client;
 pub(crate) mod clipboardevent;
+pub(crate) mod clipboarditem;
 pub(crate) mod closeevent;
 pub(crate) mod comment;
 pub(crate) mod compositionevent;
diff --git a/components/script_bindings/codegen/Bindings.conf b/components/script_bindings/codegen/Bindings.conf
index a3adf0f39ab9b..012227022af92 100644
--- a/components/script_bindings/codegen/Bindings.conf
+++ b/components/script_bindings/codegen/Bindings.conf
@@ -83,6 +83,10 @@ DOMInterfaces = {
     'canGc': ['Before', 'After', 'ReplaceWith']
 },
 
+'ClipboardItem': {
+    'canGc': ['Types']
+},
+
 'CountQueuingStrategy': {
     'canGc': ['GetSize'],
 },
diff --git a/components/script_bindings/webidls/Clipboard.webidl b/components/script_bindings/webidls/Clipboard.webidl
new file mode 100644
index 0000000000000..2d2bb409e1ab4
--- /dev/null
+++ b/components/script_bindings/webidls/Clipboard.webidl
@@ -0,0 +1,26 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+// https://w3c.github.io/clipboard-apis/#clipboard-item-interface
+
+typedef Promise<(DOMString or Blob)> ClipboardItemData;
+
+[SecureContext, Exposed=Window, Pref="dom_async_clipboard_enabled"]
+interface ClipboardItem {
+  [Throws] constructor(record<DOMString, ClipboardItemData> items,
+              optional ClipboardItemOptions options = {});
+
+  readonly attribute PresentationStyle presentationStyle;
+  readonly attribute /* FrozenArray<DOMString> */ any types;
+
+  // Promise<Blob> getType(DOMString type);
+
+  // static boolean supports(DOMString type);
+};
+
+enum PresentationStyle { "unspecified", "inline", "attachment" };
+
+dictionary ClipboardItemOptions {
+  PresentationStyle presentationStyle = "unspecified";
+};
diff --git a/ports/servoshell/prefs.rs b/ports/servoshell/prefs.rs
index eff5bf63e1b5e..287ec947f98d6 100644
--- a/ports/servoshell/prefs.rs
+++ b/ports/servoshell/prefs.rs
@@ -548,6 +548,7 @@ pub(crate) fn parse_command_line_arguments(args: Vec<String>) -> ArgumentParsing
 
     if opt_match.opt_present("enable-experimental-web-platform-features") {
         vec![
+            "dom_async_clipboard_enabled",
             "dom_fontface_enabled",
             "dom_imagebitmap_enabled",
             "dom_intersection_observer_enabled",
diff --git a/tests/wpt/meta/__dir__.ini b/tests/wpt/meta/__dir__.ini
index e0c8b66255c00..9200e7f0da20b 100644
--- a/tests/wpt/meta/__dir__.ini
+++ b/tests/wpt/meta/__dir__.ini
@@ -1,4 +1,5 @@
 prefs: [
+  "dom_async_clipboard_enabled:true",
   "dom_fontface_enabled:true",
   "dom_imagebitmap_enabled:true",
   "dom_intersection_observer_enabled:true",
diff --git a/tests/wpt/meta/clipboard-apis/clipboard-item.https.html.ini b/tests/wpt/meta/clipboard-apis/clipboard-item.https.html.ini
index 057b25ac030e0..a76ef482f199e 100644
--- a/tests/wpt/meta/clipboard-apis/clipboard-item.https.html.ini
+++ b/tests/wpt/meta/clipboard-apis/clipboard-item.https.html.ini
@@ -5,18 +5,6 @@
   [ClipboardItem() succeeds with empty options]
     expected: FAIL
 
-  [ClipboardItem({}) fails with empty dictionary input]
-    expected: FAIL
-
-  [ClipboardItem(Blob) fails]
-    expected: FAIL
-
-  [ClipboardItem() fails with null input]
-    expected: FAIL
-
-  [ClipboardItem() fails with no input]
-    expected: FAIL
-
   [types() returns correct values]
     expected: FAIL
 
diff --git a/tests/wpt/meta/clipboard-apis/idlharness.https.window.js.ini b/tests/wpt/meta/clipboard-apis/idlharness.https.window.js.ini
index d16c54f589e59..7edc93e052c0c 100644
--- a/tests/wpt/meta/clipboard-apis/idlharness.https.window.js.ini
+++ b/tests/wpt/meta/clipboard-apis/idlharness.https.window.js.ini
@@ -1,28 +1,4 @@
 [idlharness.https.window.html]
-  [ClipboardItem interface: existence and properties of interface object]
-    expected: FAIL
-
-  [ClipboardItem interface object length]
-    expected: FAIL
-
-  [ClipboardItem interface object name]
-    expected: FAIL
-
-  [ClipboardItem interface: existence and properties of interface prototype object]
-    expected: FAIL
-
-  [ClipboardItem interface: existence and properties of interface prototype object's "constructor" property]
-    expected: FAIL
-
-  [ClipboardItem interface: existence and properties of interface prototype object's @@unscopables property]
-    expected: FAIL
-
-  [ClipboardItem interface: attribute presentationStyle]
-    expected: FAIL
-
-  [ClipboardItem interface: attribute types]
-    expected: FAIL
-
   [ClipboardItem interface: operation getType(DOMString)]
     expected: FAIL
 
diff --git a/tests/wpt/mozilla/meta/MANIFEST.json b/tests/wpt/mozilla/meta/MANIFEST.json
index 3bab4b6e18daa..eb94f612b6090 100644
--- a/tests/wpt/mozilla/meta/MANIFEST.json
+++ b/tests/wpt/mozilla/meta/MANIFEST.json
@@ -13503,7 +13503,7 @@
      ]
     ],
     "interfaces.https.html": [
-     "dce05a55fd33326768635c6b3cdb193d526fccdd",
+     "cc6e99d8da7cd82edefd1c0b0fce984ae85462de",
      [
       null,
       {}
diff --git a/tests/wpt/mozilla/meta/__dir__.ini b/tests/wpt/mozilla/meta/__dir__.ini
index 6a2d9b4a0e66a..8468f1d6d100a 100644
--- a/tests/wpt/mozilla/meta/__dir__.ini
+++ b/tests/wpt/mozilla/meta/__dir__.ini
@@ -1 +1 @@
-prefs: ["dom_imagebitmap_enabled:true", "dom_offscreen_canvas_enabled:true", "dom_xpath_enabled:true", "dom_intersection_observer_enabled:true", "dom_resize_observer_enabled:true", "dom_notification_enabled:true", "dom_fontface_enabled:true", "dom_urlpattern_enabled:true", "dom_serviceworker_enabled:true"]
+prefs: ["dom_imagebitmap_enabled:true", "dom_offscreen_canvas_enabled:true", "dom_xpath_enabled:true", "dom_intersection_observer_enabled:true", "dom_resize_observer_enabled:true", "dom_notification_enabled:true", "dom_fontface_enabled:true", "dom_urlpattern_enabled:true", "dom_serviceworker_enabled:true", "dom_async_clipboard_enabled:true"]
diff --git a/tests/wpt/mozilla/tests/mozilla/interfaces.https.html b/tests/wpt/mozilla/tests/mozilla/interfaces.https.html
index dce05a55fd333..cc6e99d8da7cd 100644
--- a/tests/wpt/mozilla/tests/mozilla/interfaces.https.html
+++ b/tests/wpt/mozilla/tests/mozilla/interfaces.https.html
@@ -39,6 +39,7 @@
   "ChannelSplitterNode",
   "CharacterData",
   "ClipboardEvent",
+  "ClipboardItem",
   "CloseEvent",
   "ConstantSourceNode",
   "CryptoKey",


Reviews:
user: login: simonwuelker
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-04-04T14:02:19Z
user: login: Taym95
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-04-04T14:04:16Z
user: login: Gae24
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-04T14:48:29Z
user: login: simonwuelker
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-04-04T14:53:08Z
user: login: jdm
body: Two small changes and this should be good to go!
state: CHANGES_REQUESTED
author_association: MEMBER
submitted_at: 2025-04-06T15:45:27Z
user: login: Gae24
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-06T21:01:32Z
user: login: Gae24
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-06T21:01:49Z
user: login: jdm
state: APPROVED
author_association: MEMBER
submitted_at: 2025-04-06T23:47:52Z

Review comments:
diff_hunk: @@ -0,0 +1,173 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at https://mozilla.org/MPL/2.0/. */
+
+use std::ops::Deref;
+use std::rc::Rc;
+
+use dom_struct::dom_struct;
+use js::rust::{HandleObject, MutableHandleValue};
+use script_bindings::record::Record;
+
+use crate::dom::bindings::cell::DomRefCell;
+use crate::dom::bindings::codegen::Bindings::ClipboardItemBinding::{
+    ClipboardItemMethods, ClipboardItemOptions, PresentationStyle,
+};
+use crate::dom::bindings::error::{Error, Fallible};
+use crate::dom::bindings::frozenarray::CachedFrozenArray;
+use crate::dom::bindings::reflector::{Reflector, reflect_dom_object_with_proto};
+use crate::dom::bindings::root::DomRoot;
+use crate::dom::bindings::str::DOMString;
+use crate::dom::promise::Promise;
+use crate::dom::window::Window;
+use crate::script_runtime::{CanGc, JSContext};
+
+/// <https://w3c.github.io/clipboard-apis/#web-custom-format>
+const CUSTOM_FORMAT_PREFIX: &str = "web ";
+
+/// <https://w3c.github.io/clipboard-apis/#representation>
+#[derive(JSTraceable, MallocSizeOf)]
+struct Representation {
+    mime_type: DOMString,
+    is_custom: bool,
+    #[ignore_malloc_size_of = "promises are hard"]
path: components/script/dom/clipboarditem.rs
user: login: Taym95
body: ```suggestion
    #[ignore_malloc_size_of = "Rc is hard"]
```
created_at: 2025-04-04T13:49:33Z
author_association: MEMBER
diff_hunk: @@ -0,0 +1,26 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
path: components/script_bindings/webidls/ClipboardItem.webidl
user: login: Taym95
body: This is part of Clipboard.webidl in firefox
created_at: 2025-04-04T13:56:15Z
author_association: MEMBER
diff_hunk: @@ -0,0 +1,173 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at https://mozilla.org/MPL/2.0/. */
+
+use std::ops::Deref;
+use std::rc::Rc;
+
+use dom_struct::dom_struct;
+use js::rust::{HandleObject, MutableHandleValue};
+use script_bindings::record::Record;
+
+use crate::dom::bindings::cell::DomRefCell;
+use crate::dom::bindings::codegen::Bindings::ClipboardItemBinding::{
+    ClipboardItemMethods, ClipboardItemOptions, PresentationStyle,
+};
+use crate::dom::bindings::error::{Error, Fallible};
+use crate::dom::bindings::frozenarray::CachedFrozenArray;
+use crate::dom::bindings::reflector::{Reflector, reflect_dom_object_with_proto};
+use crate::dom::bindings::root::DomRoot;
+use crate::dom::bindings::str::DOMString;
+use crate::dom::promise::Promise;
+use crate::dom::window::Window;
+use crate::script_runtime::{CanGc, JSContext};
+
+/// <https://w3c.github.io/clipboard-apis/#web-custom-format>
+const CUSTOM_FORMAT_PREFIX: &str = "web ";
+
+/// <https://w3c.github.io/clipboard-apis/#representation>
+#[derive(JSTraceable, MallocSizeOf)]
+struct Representation {
+    mime_type: DOMString,
+    is_custom: bool,
+    #[ignore_malloc_size_of = "promises are hard"]
+    data: Rc<Promise>,
+}
+
+#[dom_struct]
+pub(crate) struct ClipboardItem {
+    reflector_: Reflector,
+    representations: DomRefCell<Vec<Representation>>,
+    presentation_style: DomRefCell<PresentationStyle>,
+    #[ignore_malloc_size_of = "mozjs"]
+    frozen_types: CachedFrozenArray,
+}
+
+impl ClipboardItem {
+    fn new_inherited() -> ClipboardItem {
+        ClipboardItem {
+            reflector_: Reflector::new(),
+            representations: Default::default(),
+            presentation_style: Default::default(),
+            frozen_types: CachedFrozenArray::new(),
+        }
+    }
+
+    fn new(window: &Window, proto: Option<HandleObject>, can_gc: CanGc) -> DomRoot<ClipboardItem> {
+        reflect_dom_object_with_proto(
+            Box::new(ClipboardItem::new_inherited()),
+            window,
+            proto,
+            can_gc,
+        )
+    }
+}
+
+impl ClipboardItemMethods<crate::DomTypeHolder> for ClipboardItem {
+    /// <https://w3c.github.io/clipboard-apis/#dom-clipboarditem-clipboarditem>
+    fn Constructor(
+        global: &Window,
+        proto: Option<HandleObject>,
+        can_gc: CanGc,
+        items: Record<DOMString, Rc<Promise>>,
+        options: &ClipboardItemOptions,
+    ) -> Fallible<DomRoot<ClipboardItem>> {
+        // If items is empty, then throw a TypeError.
+        if items.is_empty() {
+            return Err(Error::Type(String::from("No item provided")));
+        }
+
+        // If options is empty, then set options["presentationStyle"] = "unspecified".
+        // NOTE: This is done inside bindings
+
+        // Set this's clipboard item to a new clipboard item.
+        let clipboard_item = ClipboardItem::new(global, proto, can_gc);
+
+        // Set this's clipboard item's presentation style to options["presentationStyle"].
+        *clipboard_item.presentation_style.borrow_mut() = options.presentationStyle;
+
+        // For each (key, value) in items:
+        for (key, value) in items.deref() {
+            // If key starts with `"web "` prefix, then
+            // Remove `"web "` prefix and assign the remaining string to key.
path: components/script/dom/clipboarditem.rs
user: login: simonwuelker
body: You're missing part of the spec text:

> Step 6.1 Let isCustom be false.

and

> Step 6.3.2 Set isCustom true

Would you mind adding spec numbers to the comments? That makes this sort of thing easier to catch.
created_at: 2025-04-04T14:00:13Z
author_association: MEMBER
diff_hunk: @@ -251,6 +251,7 @@ pub(crate) mod channelsplitternode;
 pub(crate) mod characterdata;
 pub(crate) mod client;
 pub(crate) mod clipboardevent;
+mod clipboarditem;
path: components/script/dom/mod.rs
user: login: Taym95
body: ```suggestion
pub(crate) clipboarditem;
```
created_at: 2025-04-04T14:01:16Z
author_association: MEMBER
diff_hunk: @@ -0,0 +1,173 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at https://mozilla.org/MPL/2.0/. */
+
+use std::ops::Deref;
+use std::rc::Rc;
+
+use dom_struct::dom_struct;
+use js::rust::{HandleObject, MutableHandleValue};
+use script_bindings::record::Record;
+
+use crate::dom::bindings::cell::DomRefCell;
+use crate::dom::bindings::codegen::Bindings::ClipboardItemBinding::{
+    ClipboardItemMethods, ClipboardItemOptions, PresentationStyle,
+};
+use crate::dom::bindings::error::{Error, Fallible};
+use crate::dom::bindings::frozenarray::CachedFrozenArray;
+use crate::dom::bindings::reflector::{Reflector, reflect_dom_object_with_proto};
+use crate::dom::bindings::root::DomRoot;
+use crate::dom::bindings::str::DOMString;
+use crate::dom::promise::Promise;
+use crate::dom::window::Window;
+use crate::script_runtime::{CanGc, JSContext};
+
+/// <https://w3c.github.io/clipboard-apis/#web-custom-format>
+const CUSTOM_FORMAT_PREFIX: &str = "web ";
+
+/// <https://w3c.github.io/clipboard-apis/#representation>
+#[derive(JSTraceable, MallocSizeOf)]
+struct Representation {
+    mime_type: DOMString,
+    is_custom: bool,
+    #[ignore_malloc_size_of = "promises are hard"]
+    data: Rc<Promise>,
+}
+
+#[dom_struct]
+pub(crate) struct ClipboardItem {
+    reflector_: Reflector,
+    representations: DomRefCell<Vec<Representation>>,
+    presentation_style: DomRefCell<PresentationStyle>,
+    #[ignore_malloc_size_of = "mozjs"]
+    frozen_types: CachedFrozenArray,
+}
+
+impl ClipboardItem {
+    fn new_inherited() -> ClipboardItem {
+        ClipboardItem {
+            reflector_: Reflector::new(),
+            representations: Default::default(),
+            presentation_style: Default::default(),
+            frozen_types: CachedFrozenArray::new(),
+        }
+    }
+
+    fn new(window: &Window, proto: Option<HandleObject>, can_gc: CanGc) -> DomRoot<ClipboardItem> {
+        reflect_dom_object_with_proto(
+            Box::new(ClipboardItem::new_inherited()),
+            window,
+            proto,
+            can_gc,
+        )
+    }
+}
+
+impl ClipboardItemMethods<crate::DomTypeHolder> for ClipboardItem {
+    /// <https://w3c.github.io/clipboard-apis/#dom-clipboarditem-clipboarditem>
+    fn Constructor(
+        global: &Window,
+        proto: Option<HandleObject>,
+        can_gc: CanGc,
+        items: Record<DOMString, Rc<Promise>>,
+        options: &ClipboardItemOptions,
+    ) -> Fallible<DomRoot<ClipboardItem>> {
+        // If items is empty, then throw a TypeError.
+        if items.is_empty() {
+            return Err(Error::Type(String::from("No item provided")));
+        }
+
+        // If options is empty, then set options["presentationStyle"] = "unspecified".
+        // NOTE: This is done inside bindings
+
+        // Set this's clipboard item to a new clipboard item.
+        let clipboard_item = ClipboardItem::new(global, proto, can_gc);
+
+        // Set this's clipboard item's presentation style to options["presentationStyle"].
+        *clipboard_item.presentation_style.borrow_mut() = options.presentationStyle;
+
+        // For each (key, value) in items:
+        for (key, value) in items.deref() {
+            // If key starts with `"web "` prefix, then
+            // Remove `"web "` prefix and assign the remaining string to key.
+            let (key, is_custom) = match key.strip_prefix(CUSTOM_FORMAT_PREFIX) {
+                None => (key.str(), false),
+                Some(stripped) => (stripped, true),
+            };
+
+            // TODO Let mimeType be the result of parsing a MIME type given key.
+            // If mimeType is failure, then throw a TypeError.
path: components/script/dom/clipboarditem.rs
user: login: Taym95
body: better to create subtask to main issue as follow up
created_at: 2025-04-04T14:02:34Z
author_association: MEMBER
diff_hunk: @@ -0,0 +1,173 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at https://mozilla.org/MPL/2.0/. */
+
+use std::ops::Deref;
+use std::rc::Rc;
+
+use dom_struct::dom_struct;
+use js::rust::{HandleObject, MutableHandleValue};
+use script_bindings::record::Record;
+
+use crate::dom::bindings::cell::DomRefCell;
+use crate::dom::bindings::codegen::Bindings::ClipboardItemBinding::{
+    ClipboardItemMethods, ClipboardItemOptions, PresentationStyle,
+};
+use crate::dom::bindings::error::{Error, Fallible};
+use crate::dom::bindings::frozenarray::CachedFrozenArray;
+use crate::dom::bindings::reflector::{Reflector, reflect_dom_object_with_proto};
+use crate::dom::bindings::root::DomRoot;
+use crate::dom::bindings::str::DOMString;
+use crate::dom::promise::Promise;
+use crate::dom::window::Window;
+use crate::script_runtime::{CanGc, JSContext};
+
+/// <https://w3c.github.io/clipboard-apis/#web-custom-format>
+const CUSTOM_FORMAT_PREFIX: &str = "web ";
+
+/// <https://w3c.github.io/clipboard-apis/#representation>
+#[derive(JSTraceable, MallocSizeOf)]
+struct Representation {
+    mime_type: DOMString,
+    is_custom: bool,
+    #[ignore_malloc_size_of = "promises are hard"]
+    data: Rc<Promise>,
+}
+
+#[dom_struct]
+pub(crate) struct ClipboardItem {
+    reflector_: Reflector,
+    representations: DomRefCell<Vec<Representation>>,
+    presentation_style: DomRefCell<PresentationStyle>,
+    #[ignore_malloc_size_of = "mozjs"]
+    frozen_types: CachedFrozenArray,
+}
+
+impl ClipboardItem {
+    fn new_inherited() -> ClipboardItem {
+        ClipboardItem {
+            reflector_: Reflector::new(),
+            representations: Default::default(),
+            presentation_style: Default::default(),
+            frozen_types: CachedFrozenArray::new(),
+        }
+    }
+
+    fn new(window: &Window, proto: Option<HandleObject>, can_gc: CanGc) -> DomRoot<ClipboardItem> {
+        reflect_dom_object_with_proto(
+            Box::new(ClipboardItem::new_inherited()),
+            window,
+            proto,
+            can_gc,
+        )
+    }
+}
+
+impl ClipboardItemMethods<crate::DomTypeHolder> for ClipboardItem {
+    /// <https://w3c.github.io/clipboard-apis/#dom-clipboarditem-clipboarditem>
+    fn Constructor(
+        global: &Window,
+        proto: Option<HandleObject>,
+        can_gc: CanGc,
+        items: Record<DOMString, Rc<Promise>>,
+        options: &ClipboardItemOptions,
+    ) -> Fallible<DomRoot<ClipboardItem>> {
+        // If items is empty, then throw a TypeError.
+        if items.is_empty() {
+            return Err(Error::Type(String::from("No item provided")));
+        }
+
+        // If options is empty, then set options["presentationStyle"] = "unspecified".
+        // NOTE: This is done inside bindings
+
+        // Set this's clipboard item to a new clipboard item.
+        let clipboard_item = ClipboardItem::new(global, proto, can_gc);
+
+        // Set this's clipboard item's presentation style to options["presentationStyle"].
+        *clipboard_item.presentation_style.borrow_mut() = options.presentationStyle;
+
+        // For each (key, value) in items:
+        for (key, value) in items.deref() {
+            // If key starts with `"web "` prefix, then
+            // Remove `"web "` prefix and assign the remaining string to key.
path: components/script/dom/clipboarditem.rs
user: login: Gae24
body: Right, forgot to add them back, since isCustom is initialized by the match statement. Is it fine to just add the spec text, or do I rework the code to match it?
created_at: 2025-04-04T14:48:29Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,173 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at https://mozilla.org/MPL/2.0/. */
+
+use std::ops::Deref;
+use std::rc::Rc;
+
+use dom_struct::dom_struct;
+use js::rust::{HandleObject, MutableHandleValue};
+use script_bindings::record::Record;
+
+use crate::dom::bindings::cell::DomRefCell;
+use crate::dom::bindings::codegen::Bindings::ClipboardItemBinding::{
+    ClipboardItemMethods, ClipboardItemOptions, PresentationStyle,
+};
+use crate::dom::bindings::error::{Error, Fallible};
+use crate::dom::bindings::frozenarray::CachedFrozenArray;
+use crate::dom::bindings::reflector::{Reflector, reflect_dom_object_with_proto};
+use crate::dom::bindings::root::DomRoot;
+use crate::dom::bindings::str::DOMString;
+use crate::dom::promise::Promise;
+use crate::dom::window::Window;
+use crate::script_runtime::{CanGc, JSContext};
+
+/// <https://w3c.github.io/clipboard-apis/#web-custom-format>
+const CUSTOM_FORMAT_PREFIX: &str = "web ";
+
+/// <https://w3c.github.io/clipboard-apis/#representation>
+#[derive(JSTraceable, MallocSizeOf)]
+struct Representation {
+    mime_type: DOMString,
+    is_custom: bool,
+    #[ignore_malloc_size_of = "promises are hard"]
+    data: Rc<Promise>,
+}
+
+#[dom_struct]
+pub(crate) struct ClipboardItem {
+    reflector_: Reflector,
+    representations: DomRefCell<Vec<Representation>>,
+    presentation_style: DomRefCell<PresentationStyle>,
+    #[ignore_malloc_size_of = "mozjs"]
+    frozen_types: CachedFrozenArray,
+}
+
+impl ClipboardItem {
+    fn new_inherited() -> ClipboardItem {
+        ClipboardItem {
+            reflector_: Reflector::new(),
+            representations: Default::default(),
+            presentation_style: Default::default(),
+            frozen_types: CachedFrozenArray::new(),
+        }
+    }
+
+    fn new(window: &Window, proto: Option<HandleObject>, can_gc: CanGc) -> DomRoot<ClipboardItem> {
+        reflect_dom_object_with_proto(
+            Box::new(ClipboardItem::new_inherited()),
+            window,
+            proto,
+            can_gc,
+        )
+    }
+}
+
+impl ClipboardItemMethods<crate::DomTypeHolder> for ClipboardItem {
+    /// <https://w3c.github.io/clipboard-apis/#dom-clipboarditem-clipboarditem>
+    fn Constructor(
+        global: &Window,
+        proto: Option<HandleObject>,
+        can_gc: CanGc,
+        items: Record<DOMString, Rc<Promise>>,
+        options: &ClipboardItemOptions,
+    ) -> Fallible<DomRoot<ClipboardItem>> {
+        // If items is empty, then throw a TypeError.
+        if items.is_empty() {
+            return Err(Error::Type(String::from("No item provided")));
+        }
+
+        // If options is empty, then set options["presentationStyle"] = "unspecified".
+        // NOTE: This is done inside bindings
+
+        // Set this's clipboard item to a new clipboard item.
+        let clipboard_item = ClipboardItem::new(global, proto, can_gc);
+
+        // Set this's clipboard item's presentation style to options["presentationStyle"].
+        *clipboard_item.presentation_style.borrow_mut() = options.presentationStyle;
+
+        // For each (key, value) in items:
+        for (key, value) in items.deref() {
+            // If key starts with `"web "` prefix, then
+            // Remove `"web "` prefix and assign the remaining string to key.
path: components/script/dom/clipboarditem.rs
user: login: simonwuelker
body: I think it's  fine to just add the text.

The important thing is that it's easy to verify that the code matches the spec text.
created_at: 2025-04-04T14:53:08Z
author_association: MEMBER
diff_hunk: @@ -0,0 +1,176 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at https://mozilla.org/MPL/2.0/. */
+
+use std::ops::Deref;
+use std::rc::Rc;
+
+use dom_struct::dom_struct;
+use js::rust::{HandleObject, MutableHandleValue};
+use script_bindings::record::Record;
+
+use crate::dom::bindings::cell::DomRefCell;
+use crate::dom::bindings::codegen::Bindings::ClipboardBinding::{
+    ClipboardItemMethods, ClipboardItemOptions, PresentationStyle,
+};
+use crate::dom::bindings::error::{Error, Fallible};
+use crate::dom::bindings::frozenarray::CachedFrozenArray;
+use crate::dom::bindings::reflector::{Reflector, reflect_dom_object_with_proto};
+use crate::dom::bindings::root::DomRoot;
+use crate::dom::bindings::str::DOMString;
+use crate::dom::promise::Promise;
+use crate::dom::window::Window;
+use crate::script_runtime::{CanGc, JSContext};
+
+/// <https://w3c.github.io/clipboard-apis/#web-custom-format>
+const CUSTOM_FORMAT_PREFIX: &str = "web ";
+
+/// <https://w3c.github.io/clipboard-apis/#representation>
+#[derive(JSTraceable, MallocSizeOf)]
+struct Representation {
+    mime_type: DOMString,
+    is_custom: bool,
+    #[ignore_malloc_size_of = "Rc is hard"]
+    data: Rc<Promise>,
+}
+
+#[dom_struct]
+pub(crate) struct ClipboardItem {
+    reflector_: Reflector,
+    representations: DomRefCell<Vec<Representation>>,
+    presentation_style: DomRefCell<PresentationStyle>,
+    #[ignore_malloc_size_of = "mozjs"]
+    frozen_types: CachedFrozenArray,
+}
+
+impl ClipboardItem {
+    fn new_inherited() -> ClipboardItem {
+        ClipboardItem {
+            reflector_: Reflector::new(),
+            representations: Default::default(),
+            presentation_style: Default::default(),
+            frozen_types: CachedFrozenArray::new(),
+        }
+    }
+
+    fn new(window: &Window, proto: Option<HandleObject>, can_gc: CanGc) -> DomRoot<ClipboardItem> {
+        reflect_dom_object_with_proto(
+            Box::new(ClipboardItem::new_inherited()),
+            window,
+            proto,
+            can_gc,
+        )
+    }
+}
+
+impl ClipboardItemMethods<crate::DomTypeHolder> for ClipboardItem {
+    /// <https://w3c.github.io/clipboard-apis/#dom-clipboarditem-clipboarditem>
+    fn Constructor(
+        global: &Window,
+        proto: Option<HandleObject>,
+        can_gc: CanGc,
+        items: Record<DOMString, Rc<Promise>>,
+        options: &ClipboardItemOptions,
+    ) -> Fallible<DomRoot<ClipboardItem>> {
+        // Step 1 If items is empty, then throw a TypeError.
+        if items.is_empty() {
+            return Err(Error::Type(String::from("No item provided")));
+        }
+
+        // Step 2 If options is empty, then set options["presentationStyle"] = "unspecified".
+        // NOTE: This is done inside bindings
+
+        // Step 3 Set this's clipboard item to a new clipboard item.
+        let clipboard_item = ClipboardItem::new(global, proto, can_gc);
+
+        // Step 4 Set this's clipboard item's presentation style to options["presentationStyle"].
+        *clipboard_item.presentation_style.borrow_mut() = options.presentationStyle;
+
+        // Step 6 For each (key, value) in items:
+        for (key, value) in items.deref() {
+            // Step 6.2 Let isCustom be false.
+
+            // Step 6.3 If key starts with `"web "` prefix, then
+            // Step 6.3.1 Remove `"web "` prefix and assign the remaining string to key.
+            let (key, is_custom) = match key.strip_prefix(CUSTOM_FORMAT_PREFIX) {
+                None => (key.str(), false),
+                // Step 6.3.2 Set isCustom true
+                Some(stripped) => (stripped, true),
+            };
+
+            // Step 6.5 TODO Let mimeType be the result of parsing a MIME type given key.
path: components/script/dom/clipboarditem.rs
user: login: jdm
body: Let's go ahead and use https://docs.rs/data-url/latest/data_url/mime/struct.Mime.html#impl-FromStr-for-Mime to parse this here.
created_at: 2025-04-06T15:41:26Z
author_association: MEMBER
diff_hunk: @@ -0,0 +1,176 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at https://mozilla.org/MPL/2.0/. */
+
+use std::ops::Deref;
+use std::rc::Rc;
+
+use dom_struct::dom_struct;
+use js::rust::{HandleObject, MutableHandleValue};
+use script_bindings::record::Record;
+
+use crate::dom::bindings::cell::DomRefCell;
+use crate::dom::bindings::codegen::Bindings::ClipboardBinding::{
+    ClipboardItemMethods, ClipboardItemOptions, PresentationStyle,
+};
+use crate::dom::bindings::error::{Error, Fallible};
+use crate::dom::bindings::frozenarray::CachedFrozenArray;
+use crate::dom::bindings::reflector::{Reflector, reflect_dom_object_with_proto};
+use crate::dom::bindings::root::DomRoot;
+use crate::dom::bindings::str::DOMString;
+use crate::dom::promise::Promise;
+use crate::dom::window::Window;
+use crate::script_runtime::{CanGc, JSContext};
+
+/// <https://w3c.github.io/clipboard-apis/#web-custom-format>
+const CUSTOM_FORMAT_PREFIX: &str = "web ";
+
+/// <https://w3c.github.io/clipboard-apis/#representation>
+#[derive(JSTraceable, MallocSizeOf)]
+struct Representation {
+    mime_type: DOMString,
+    is_custom: bool,
+    #[ignore_malloc_size_of = "Rc is hard"]
+    data: Rc<Promise>,
+}
+
+#[dom_struct]
+pub(crate) struct ClipboardItem {
+    reflector_: Reflector,
+    representations: DomRefCell<Vec<Representation>>,
+    presentation_style: DomRefCell<PresentationStyle>,
+    #[ignore_malloc_size_of = "mozjs"]
+    frozen_types: CachedFrozenArray,
+}
+
+impl ClipboardItem {
+    fn new_inherited() -> ClipboardItem {
+        ClipboardItem {
+            reflector_: Reflector::new(),
+            representations: Default::default(),
+            presentation_style: Default::default(),
+            frozen_types: CachedFrozenArray::new(),
+        }
+    }
+
+    fn new(window: &Window, proto: Option<HandleObject>, can_gc: CanGc) -> DomRoot<ClipboardItem> {
+        reflect_dom_object_with_proto(
+            Box::new(ClipboardItem::new_inherited()),
+            window,
+            proto,
+            can_gc,
+        )
+    }
+}
+
+impl ClipboardItemMethods<crate::DomTypeHolder> for ClipboardItem {
+    /// <https://w3c.github.io/clipboard-apis/#dom-clipboarditem-clipboarditem>
+    fn Constructor(
+        global: &Window,
+        proto: Option<HandleObject>,
+        can_gc: CanGc,
+        items: Record<DOMString, Rc<Promise>>,
+        options: &ClipboardItemOptions,
+    ) -> Fallible<DomRoot<ClipboardItem>> {
+        // Step 1 If items is empty, then throw a TypeError.
+        if items.is_empty() {
+            return Err(Error::Type(String::from("No item provided")));
+        }
+
+        // Step 2 If options is empty, then set options["presentationStyle"] = "unspecified".
+        // NOTE: This is done inside bindings
+
+        // Step 3 Set this's clipboard item to a new clipboard item.
+        let clipboard_item = ClipboardItem::new(global, proto, can_gc);
+
+        // Step 4 Set this's clipboard item's presentation style to options["presentationStyle"].
+        *clipboard_item.presentation_style.borrow_mut() = options.presentationStyle;
+
+        // Step 6 For each (key, value) in items:
+        for (key, value) in items.deref() {
+            // Step 6.2 Let isCustom be false.
+
+            // Step 6.3 If key starts with `"web "` prefix, then
+            // Step 6.3.1 Remove `"web "` prefix and assign the remaining string to key.
+            let (key, is_custom) = match key.strip_prefix(CUSTOM_FORMAT_PREFIX) {
+                None => (key.str(), false),
+                // Step 6.3.2 Set isCustom true
+                Some(stripped) => (stripped, true),
+            };
+
+            // Step 6.5 TODO Let mimeType be the result of parsing a MIME type given key.
+            // Step 6.6 If mimeType is failure, then throw a TypeError.
+            let mime_type = key;
+
+            // Step 6.7 If this's clipboard item's list of representations contains a representation
+            // whose MIME type is mimeType and whose [representation/isCustom] is isCustom, then throw a TypeError.
+            if clipboard_item
+                .representations
+                .borrow()
+                .iter()
+                .any(|representation| {
+                    representation.mime_type == mime_type && representation.is_custom == is_custom
+                })
+            {
+                return Err(Error::Type(String::from("Tried to add a duplicate mime")));
+            }
+
+            // Step 6.1 Let representation be a new representation.
+            // Step 6.4 Set representation’s isCustom flag to isCustom.
+            // Step 6.8 Set representation’s MIME type to mimeType.
+            // Step 6.9 Set representation’s data to value.
+            let representation = Representation {
+                mime_type: mime_type.into(),
+                is_custom,
+                data: value.clone(),
+            };
+
+            // Step 6.10 Append representation to this's clipboard item's list of representations.
+            clipboard_item
+                .representations
+                .borrow_mut()
+                .push(representation);
+        }
+
+        // NOTE: The steps for creating a frozen array from the list of mimeType are done in the Types() method
+
+        Ok(clipboard_item)
+    }
+
+    /// <https://w3c.github.io/clipboard-apis/#dom-clipboarditem-presentationstyle>
+    fn PresentationStyle(&self) -> PresentationStyle {
+        *self.presentation_style.borrow()
+    }
+
+    /// <https://w3c.github.io/clipboard-apis/#dom-clipboarditem-types>
+    fn Types(&self, cx: JSContext, can_gc: CanGc, retval: MutableHandleValue) {
+        self.frozen_types.get_or_init(
+            || {
+                // Step 5 Let types be a list of DOMString.
+                let mut types = Vec::new();
+
+                self.representations
+                    .borrow()
+                    .iter()
+                    .for_each(|representation| {
+                        // Step 6.11 TODO Let mimeTypeString be the result of serializing a MIME type with mimeType.
path: components/script/dom/clipboarditem.rs
user: login: jdm
body: https://docs.rs/data-url/latest/data_url/mime/struct.Mime.html#impl-Display-for-Mime
created_at: 2025-04-06T15:44:21Z
author_association: MEMBER
diff_hunk: @@ -0,0 +1,176 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at https://mozilla.org/MPL/2.0/. */
+
+use std::ops::Deref;
+use std::rc::Rc;
+
+use dom_struct::dom_struct;
+use js::rust::{HandleObject, MutableHandleValue};
+use script_bindings::record::Record;
+
+use crate::dom::bindings::cell::DomRefCell;
+use crate::dom::bindings::codegen::Bindings::ClipboardBinding::{
+    ClipboardItemMethods, ClipboardItemOptions, PresentationStyle,
+};
+use crate::dom::bindings::error::{Error, Fallible};
+use crate::dom::bindings::frozenarray::CachedFrozenArray;
+use crate::dom::bindings::reflector::{Reflector, reflect_dom_object_with_proto};
+use crate::dom::bindings::root::DomRoot;
+use crate::dom::bindings::str::DOMString;
+use crate::dom::promise::Promise;
+use crate::dom::window::Window;
+use crate::script_runtime::{CanGc, JSContext};
+
+/// <https://w3c.github.io/clipboard-apis/#web-custom-format>
+const CUSTOM_FORMAT_PREFIX: &str = "web ";
+
+/// <https://w3c.github.io/clipboard-apis/#representation>
+#[derive(JSTraceable, MallocSizeOf)]
+struct Representation {
+    mime_type: DOMString,
+    is_custom: bool,
+    #[ignore_malloc_size_of = "Rc is hard"]
+    data: Rc<Promise>,
+}
+
+#[dom_struct]
+pub(crate) struct ClipboardItem {
+    reflector_: Reflector,
+    representations: DomRefCell<Vec<Representation>>,
+    presentation_style: DomRefCell<PresentationStyle>,
+    #[ignore_malloc_size_of = "mozjs"]
+    frozen_types: CachedFrozenArray,
+}
+
+impl ClipboardItem {
+    fn new_inherited() -> ClipboardItem {
+        ClipboardItem {
+            reflector_: Reflector::new(),
+            representations: Default::default(),
+            presentation_style: Default::default(),
+            frozen_types: CachedFrozenArray::new(),
+        }
+    }
+
+    fn new(window: &Window, proto: Option<HandleObject>, can_gc: CanGc) -> DomRoot<ClipboardItem> {
+        reflect_dom_object_with_proto(
+            Box::new(ClipboardItem::new_inherited()),
+            window,
+            proto,
+            can_gc,
+        )
+    }
+}
+
+impl ClipboardItemMethods<crate::DomTypeHolder> for ClipboardItem {
+    /// <https://w3c.github.io/clipboard-apis/#dom-clipboarditem-clipboarditem>
+    fn Constructor(
+        global: &Window,
+        proto: Option<HandleObject>,
+        can_gc: CanGc,
+        items: Record<DOMString, Rc<Promise>>,
+        options: &ClipboardItemOptions,
+    ) -> Fallible<DomRoot<ClipboardItem>> {
+        // Step 1 If items is empty, then throw a TypeError.
+        if items.is_empty() {
+            return Err(Error::Type(String::from("No item provided")));
+        }
+
+        // Step 2 If options is empty, then set options["presentationStyle"] = "unspecified".
+        // NOTE: This is done inside bindings
+
+        // Step 3 Set this's clipboard item to a new clipboard item.
+        let clipboard_item = ClipboardItem::new(global, proto, can_gc);
+
+        // Step 4 Set this's clipboard item's presentation style to options["presentationStyle"].
+        *clipboard_item.presentation_style.borrow_mut() = options.presentationStyle;
+
+        // Step 6 For each (key, value) in items:
+        for (key, value) in items.deref() {
+            // Step 6.2 Let isCustom be false.
+
+            // Step 6.3 If key starts with `"web "` prefix, then
+            // Step 6.3.1 Remove `"web "` prefix and assign the remaining string to key.
+            let (key, is_custom) = match key.strip_prefix(CUSTOM_FORMAT_PREFIX) {
+                None => (key.str(), false),
+                // Step 6.3.2 Set isCustom true
+                Some(stripped) => (stripped, true),
+            };
+
+            // Step 6.5 TODO Let mimeType be the result of parsing a MIME type given key.
path: components/script/dom/clipboarditem.rs
user: login: Gae24
body: Done.
created_at: 2025-04-06T21:01:32Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,176 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at https://mozilla.org/MPL/2.0/. */
+
+use std::ops::Deref;
+use std::rc::Rc;
+
+use dom_struct::dom_struct;
+use js::rust::{HandleObject, MutableHandleValue};
+use script_bindings::record::Record;
+
+use crate::dom::bindings::cell::DomRefCell;
+use crate::dom::bindings::codegen::Bindings::ClipboardBinding::{
+    ClipboardItemMethods, ClipboardItemOptions, PresentationStyle,
+};
+use crate::dom::bindings::error::{Error, Fallible};
+use crate::dom::bindings::frozenarray::CachedFrozenArray;
+use crate::dom::bindings::reflector::{Reflector, reflect_dom_object_with_proto};
+use crate::dom::bindings::root::DomRoot;
+use crate::dom::bindings::str::DOMString;
+use crate::dom::promise::Promise;
+use crate::dom::window::Window;
+use crate::script_runtime::{CanGc, JSContext};
+
+/// <https://w3c.github.io/clipboard-apis/#web-custom-format>
+const CUSTOM_FORMAT_PREFIX: &str = "web ";
+
+/// <https://w3c.github.io/clipboard-apis/#representation>
+#[derive(JSTraceable, MallocSizeOf)]
+struct Representation {
+    mime_type: DOMString,
+    is_custom: bool,
+    #[ignore_malloc_size_of = "Rc is hard"]
+    data: Rc<Promise>,
+}
+
+#[dom_struct]
+pub(crate) struct ClipboardItem {
+    reflector_: Reflector,
+    representations: DomRefCell<Vec<Representation>>,
+    presentation_style: DomRefCell<PresentationStyle>,
+    #[ignore_malloc_size_of = "mozjs"]
+    frozen_types: CachedFrozenArray,
+}
+
+impl ClipboardItem {
+    fn new_inherited() -> ClipboardItem {
+        ClipboardItem {
+            reflector_: Reflector::new(),
+            representations: Default::default(),
+            presentation_style: Default::default(),
+            frozen_types: CachedFrozenArray::new(),
+        }
+    }
+
+    fn new(window: &Window, proto: Option<HandleObject>, can_gc: CanGc) -> DomRoot<ClipboardItem> {
+        reflect_dom_object_with_proto(
+            Box::new(ClipboardItem::new_inherited()),
+            window,
+            proto,
+            can_gc,
+        )
+    }
+}
+
+impl ClipboardItemMethods<crate::DomTypeHolder> for ClipboardItem {
+    /// <https://w3c.github.io/clipboard-apis/#dom-clipboarditem-clipboarditem>
+    fn Constructor(
+        global: &Window,
+        proto: Option<HandleObject>,
+        can_gc: CanGc,
+        items: Record<DOMString, Rc<Promise>>,
+        options: &ClipboardItemOptions,
+    ) -> Fallible<DomRoot<ClipboardItem>> {
+        // Step 1 If items is empty, then throw a TypeError.
+        if items.is_empty() {
+            return Err(Error::Type(String::from("No item provided")));
+        }
+
+        // Step 2 If options is empty, then set options["presentationStyle"] = "unspecified".
+        // NOTE: This is done inside bindings
+
+        // Step 3 Set this's clipboard item to a new clipboard item.
+        let clipboard_item = ClipboardItem::new(global, proto, can_gc);
+
+        // Step 4 Set this's clipboard item's presentation style to options["presentationStyle"].
+        *clipboard_item.presentation_style.borrow_mut() = options.presentationStyle;
+
+        // Step 6 For each (key, value) in items:
+        for (key, value) in items.deref() {
+            // Step 6.2 Let isCustom be false.
+
+            // Step 6.3 If key starts with `"web "` prefix, then
+            // Step 6.3.1 Remove `"web "` prefix and assign the remaining string to key.
+            let (key, is_custom) = match key.strip_prefix(CUSTOM_FORMAT_PREFIX) {
+                None => (key.str(), false),
+                // Step 6.3.2 Set isCustom true
+                Some(stripped) => (stripped, true),
+            };
+
+            // Step 6.5 TODO Let mimeType be the result of parsing a MIME type given key.
+            // Step 6.6 If mimeType is failure, then throw a TypeError.
+            let mime_type = key;
+
+            // Step 6.7 If this's clipboard item's list of representations contains a representation
+            // whose MIME type is mimeType and whose [representation/isCustom] is isCustom, then throw a TypeError.
+            if clipboard_item
+                .representations
+                .borrow()
+                .iter()
+                .any(|representation| {
+                    representation.mime_type == mime_type && representation.is_custom == is_custom
+                })
+            {
+                return Err(Error::Type(String::from("Tried to add a duplicate mime")));
+            }
+
+            // Step 6.1 Let representation be a new representation.
+            // Step 6.4 Set representation’s isCustom flag to isCustom.
+            // Step 6.8 Set representation’s MIME type to mimeType.
+            // Step 6.9 Set representation’s data to value.
+            let representation = Representation {
+                mime_type: mime_type.into(),
+                is_custom,
+                data: value.clone(),
+            };
+
+            // Step 6.10 Append representation to this's clipboard item's list of representations.
+            clipboard_item
+                .representations
+                .borrow_mut()
+                .push(representation);
+        }
+
+        // NOTE: The steps for creating a frozen array from the list of mimeType are done in the Types() method
+
+        Ok(clipboard_item)
+    }
+
+    /// <https://w3c.github.io/clipboard-apis/#dom-clipboarditem-presentationstyle>
+    fn PresentationStyle(&self) -> PresentationStyle {
+        *self.presentation_style.borrow()
+    }
+
+    /// <https://w3c.github.io/clipboard-apis/#dom-clipboarditem-types>
+    fn Types(&self, cx: JSContext, can_gc: CanGc, retval: MutableHandleValue) {
+        self.frozen_types.get_or_init(
+            || {
+                // Step 5 Let types be a list of DOMString.
+                let mut types = Vec::new();
+
+                self.representations
+                    .borrow()
+                    .iter()
+                    .for_each(|representation| {
+                        // Step 6.11 TODO Let mimeTypeString be the result of serializing a MIME type with mimeType.
path: components/script/dom/clipboarditem.rs
user: login: Gae24
body: Done.
created_at: 2025-04-06T21:01:49Z
author_association: CONTRIBUTOR

PR comments:
user: login: github-actions[bot]
created_at: 2025-04-04T14:04:50Z
body: 🔨 Triggering try run ([#14267148717](https://github.com/servo/servo/actions/runs/14267148717)) for Linux (WPT)
author_association: NONE
user: login: github-actions[bot]
created_at: 2025-04-04T14:31:23Z
body: <span>Test results for linux-wpt from try job ([#14267148717](https://github.com/servo/servo/actions/runs/14267148717)):<div><details><summary>Flaky unexpected result (23)</summary><ul><li><span>FAIL [expected PASS] `/css/css-fonts/font-palette-28.html`</span></li><li><span>OK `/css/css-fonts/variations/at-font-face-font-matching.html` ([#20684](http://github.com/servo/servo/issues/20684))</span><ul><li><span>FAIL [expected PASS] subtest: `Matching font-style: 'oblique 0deg' should prefer 'oblique 0deg' over 'oblique 5deg'` 
```
assert_equals: Unexpected font on test element expected 487 but got 532
```
</span></li></ul></li><li><span>OK `/css/css-grid/alignment/grid-content-alignment-with-abspos-001.html` ([#34339](http://github.com/servo/servo/issues/34339))</span><ul><li><span>FAIL [expected PASS] subtest: `.grid 1` 
```
assert_equals: 
&lt;div class="grid" data-expected-width="800" data-expected-height="600"&gt;
    &lt;div class="a" id="item" data-offset-x="329" data-offset-y="269" data-expected-width="142" data-expected-height="62" style="place-self: center;"&gt;&lt;/div&gt;
  &lt;/div&gt;
offsetLeft expected 329 but got 0
```
</span></li></ul></li><li><span>FAIL [expected PASS] `/css/css-tables/table-cell-overflow-auto-scrolled.html` ([#35011](http://github.com/servo/servo/issues/35011))</span></li><li><span>OK `/css/css-ui/text-overflow-023.html` ([#19861](http://github.com/servo/servo/issues/19861))</span><ul><li><span>FAIL [expected PASS] subtest: `Checks hit testing on the ellipsis` 
```
assert_equals: the element targeted by a hit on the ellipsis is the elided inline. expected "target" but got "parent"
```
</span></li></ul></li><li><span>OK [expected ERROR] `/fetch/fetch-later/quota/same-origin-iframe/multiple-iframes.tentative.https.window.html` ([#35176](http://github.com/servo/servo/issues/35176))</span></li><li><span>OK `/fetch/metadata/generated/css-font-face.sub.tentative.html` ([#34624](http://github.com/servo/servo/issues/34624))</span><ul><li><span>PASS [expected FAIL] subtest: `sec-fetch-storage-access - Not sent to non-trustworthy same-origin destination`</span></li><li><span>PASS [expected FAIL] subtest: `sec-fetch-storage-access - Not sent to non-trustworthy cross-site destination`</span></li></ul></li><li><span>OK `/html/browsers/browsing-the-web/navigating-across-documents/navigation-unload-cross-origin.sub.window.html` ([#29056](http://github.com/servo/servo/issues/29056))</span><ul><li><span>FAIL [expected PASS] subtest: `Cross-origin navigation started from unload handler must be ignored` 
```
promise_test: Unhandled rejection with value: object "SecurityError: The operation is insecure."
```
</span></li></ul></li><li><span>OK `/html/browsers/browsing-the-web/navigating-across-documents/navigation-unload-same-origin.window.html` ([#29049](http://github.com/servo/servo/issues/29049))</span><ul><li><span>PASS [expected FAIL] subtest: `Same-origin navigation started from unload handler must be ignored`</span></li></ul></li><li><span>OK `/html/browsers/history/the-history-interface/traverse_the_history_2.html` ([#21383](http://github.com/servo/servo/issues/21383))</span><ul><li><span>PASS [expected FAIL] subtest: `Multiple history traversals, last would be aborted`</span></li></ul></li><li><span>PASS [expected FAIL] `/html/canvas/element/manual/text/canvas.2d.disconnected.html` ([#30063](http://github.com/servo/servo/issues/30063))</span></li><li><span>OK [expected CRASH] `/html/canvas/offscreen/canvas-host/2d.canvas.host.size.large.worker.html` ([#30164](http://github.com/servo/servo/issues/30164))</span></li><li><span>OK [expected TIMEOUT] `/html/semantics/embedded-content/media-elements/playing-the-media-resource/loop-from-ended.tentative.html` ([#33778](http://github.com/servo/servo/issues/33778))</span><ul><li><span>FAIL [expected TIMEOUT] subtest: `play() with loop set to true after playback ended` 
```
this argument is not a finite floating-point value
```
</span></li></ul></li><li><span>OK `/html/semantics/embedded-content/the-iframe-element/iframe-loading-lazy-nav-location-assign.html` ([#32863](http://github.com/servo/servo/issues/32863))</span><ul><li><span>FAIL [expected PASS] subtest: `Navigating iframe loading='lazy' before it is loaded: location.assign` 
```
uncaught exception: Error: assert_equals: expected "http://web-platform.test:8000/html/semantics/embedded-content/the-iframe-element/support/blank.htm?nav" but got "http://web-platform.test:8000/html/semantics/embedded-content/the-iframe-element/support/blank.htm?src"
```
</span></li></ul></li><li><span>CRASH [expected TIMEOUT] `/html/semantics/embedded-content/the-iframe-element/iframe_sandbox_popups_nonescaping-3.html` ([#24066](http://github.com/servo/servo/issues/24066))</span></li><li><span>OK `/html/semantics/forms/form-submission-0/form-data-set-usv.html` ([#34934](http://github.com/servo/servo/issues/34934))</span><ul><li><span>FAIL [expected PASS] subtest: `Strings from form controls should be converted to Unicode scalar values in form submission` 
```
assert_equals: expected "69 6e 70 75 74 31 ef bf bd 3d 69 6e 70 75 74 31 ef bf bd 0d 0a 69 6e 70 75 74 32 ef bf bd 3d 69 6e 70 75 74 32 ef bf bd 0d 0a 69 6e 70 75 74 33 ef bf bd 3d 69 6e 70 75 74 33 ef bf bd 0d 0a 69 6e 70 75 74 34 ef bf bd 3d 69 6e 70 75 74 34 ef bf bd 0d 0a" but got "\n  \n  \n    option\n  \n  \n  \n\n\n\n\"use strict\";\n\nconst form = document.querySelector(\"form\");\n\nfor (let el of Array.from(form.querySelectorAll(\"input\"))) { // Firefox/Edge support\n  el.name = el.id + \"\\uDC01\";\n  el.value = el.id + \"\\uDC01\";\n}\n\nconst select = document.querySelector(\"select\");\nselect.name = select.id + \"\\uDC01\";\nselect.firstElementChild.value = select.id + \"\\uDC01\";\n\n"
```
</span></li></ul></li><li><span>OK `/html/semantics/forms/form-submission-0/text-plain.window.html` ([#28687](http://github.com/servo/servo/issues/28687))</span><ul><li><span>PASS [expected FAIL] subtest: `text/plain: Basic test (normal form)`</span></li></ul></li><li><span>TIMEOUT [expected OK] `/mixed-content/gen/top.http-rp/opt-in/audio-tag.https.html` ([#35744](http://github.com/servo/servo/issues/35744))</span><ul><li><span>TIMEOUT [expected FAIL] subtest: `Mixed-Content: Expects blocked for audio-tag to cross-http origin and no-redirect redirection from https context.` 
```
Test timed out
```
</span></li><li><span>NOTRUN [expected FAIL] subtest: `Mixed-Content: Expects blocked for audio-tag to cross-http origin and swap-scheme redirection from https context.`</span></li><li><span>NOTRUN [expected FAIL] subtest: `Mixed-Content: Expects blocked for audio-tag to cross-https origin and swap-scheme redirection from https context.`</span></li><li><span>NOTRUN [expected FAIL] subtest: `Mixed-Content: Expects blocked for audio-tag to same-http origin and keep-scheme redirection from https context.`</span></li><li><span>NOTRUN [expected FAIL] subtest: `Mixed-Content: Expects blocked for audio-tag to same-http origin and no-redirect redirection from https context.`</span></li><li><span>NOTRUN [expected FAIL] subtest: `Mixed-Content: Expects blocked for audio-tag to same-http origin and swap-scheme redirection from https context.`</span></li><li><span>NOTRUN [expected FAIL] subtest: `Mixed-Content: Expects blocked for audio-tag to same-https origin and swap-scheme redirection from https context.`</span></li></ul></li><li><span>OK [expected TIMEOUT] `/performance-timeline/navigation-id-detached-frame.tentative.html` ([#34773](http://github.com/servo/servo/issues/34773))</span><ul><li><span>PASS [expected TIMEOUT] subtest: `The navigation_id getter does not crash a window of detached frame`</span></li></ul></li><li><span>OK `/resize-observer/change-layout-in-error.html` ([#32629](http://github.com/servo/servo/issues/32629))</span><ul><li><span>PASS [expected FAIL] subtest: `Changing layout in window error handler should not result in lifecyle loop when resize observer loop limit is reached.`</span></li></ul></li><li><span>OK `/resize-observer/eventloop.html` ([#33599](http://github.com/servo/servo/issues/33599))</span><ul><li><span>FAIL [expected PASS] subtest: `test0: multiple notifications inside same event loop` 
```
assert_equals: new loop expected 1 but got 0
```
</span></li></ul></li><li><span>TIMEOUT [expected OK] `/resource-timing/nested-context-navigations-iframe.html` ([#24311](http://github.com/servo/servo/issues/24311))</span><ul><li><span>TIMEOUT [expected PASS] subtest: `Test that crossorigin iframe navigations are not observable by the parent, even after history navigations by the parent` 
```
Test timed out
```
</span></li><li><span>NOTRUN [expected PASS] subtest: `Test that cross-site iframe navigations are not observable by the parent, even after history navigations by the parent`</span></li><li><span>NOTRUN [expected PASS] subtest: `Test that iframe navigations are not observable by the parent`</span></li><li><span>NOTRUN [expected PASS] subtest: `Test that crossorigin iframe navigations are not observable by the parent`</span></li><li><span>NOTRUN [expected PASS] subtest: `Test that cross-site iframe navigations are not observable by the parent`</span></li><li><span>NOTRUN [expected PASS] subtest: `Test that iframe refreshes are not observable by the parent`</span></li><li><span>NOTRUN [expected PASS] subtest: `Test that crossorigin iframe refreshes are not observable by the parent`</span></li><li><span>NOTRUN [expected PASS] subtest: `Test that cross-site iframe refreshes are not observable by the parent`</span></li></ul></li><li><span>ERROR `/service-workers/idlharness.https.any.html` ([#36250](http://github.com/servo/servo/issues/36250))</span><ul><li><span>PASS [expected TIMEOUT] subtest: `ServiceWorkerContainer interface: operation register((TrustedScriptURL or USVString), optional RegistrationOptions)`</span></li><li><span>PASS [expected TIMEOUT] subtest: `NavigationPreloadManager interface: operation enable()`</span></li><li><span>PASS [expected TIMEOUT] subtest: `NavigationPreloadManager interface: operation disable()`</span></li><li><span>PASS [expected TIMEOUT] subtest: `NavigationPreloadManager interface: operation setHeaderValue(ByteString)`</span></li><li><span>PASS [expected TIMEOUT] subtest: `NavigationPreloadManager interface: operation getState()`</span></li></ul></li></ul></details><details><summary>Stable unexpected results that are known to be intermittent (20)</summary><ul><li><span>OK `/FileAPI/url/url-with-fetch.any.worker.html` ([#21517](http://github.com/servo/servo/issues/21517))</span><ul><li><span>FAIL [expected PASS] subtest: `Revoke blob URL after calling fetch, fetch should succeed` 
```
promise_test: Unhandled rejection with value: object "TypeError: Network error occurred"
```
</span></li></ul></li><li><span>OK `/_webgl/conformance/textures/misc/texture-upload-size.html` ([#21770](http://github.com/servo/servo/issues/21770))</span><ul><li><span>FAIL [expected PASS] subtest: `WebGL test #44` 
```
assert_true: could not create image (SVG) expected true got false
```
</span></li><li><span>PASS [expected FAIL] subtest: `WebGL test #60`</span></li></ul></li><li><span>FAIL [expected PASS] `/css/compositing/mix-blend-mode/mix-blend-mode-video-sibling.html` ([#32849](http://github.com/servo/servo/issues/32849))</span></li><li><span>OK `/fetch/metadata/generated/css-font-face.https.sub.tentative.html` ([#32732](http://github.com/servo/servo/issues/32732))</span><ul><li><span>PASS [expected FAIL] subtest: `sec-fetch-site - Same-Site -&gt; Same Origin`</span></li></ul></li><li><span>OK `/html/browsers/browsing-the-web/navigating-across-documents/008.html` ([#24456](http://github.com/servo/servo/issues/24456))</span><ul><li><span>FAIL [expected PASS] subtest: `Link with onclick form submit to javascript url and href navigation ` 
```
assert_equals: expected "href" but got "click"
```
</span></li></ul></li><li><span>OK `/html/browsers/browsing-the-web/navigating-across-documents/initial-empty-document/iframe-src-aboutblank-navigate-immediately.html` ([#29048](http://github.com/servo/servo/issues/29048))</span><ul><li><span>FAIL [expected PASS] subtest: `Navigating to a different document with link click` 
```
assert_equals: expected "http://web-platform.test:8000/common/blank.html?1" but got "about:blank"
```
</span></li><li><span>FAIL [expected PASS] subtest: `Navigating to a different document with form submission` 
```
assert_equals: expected "http://web-platform.test:8000/common/blank.html?1=" but got "about:blank"
```
</span></li></ul></li><li><span>OK `/html/browsers/history/the-history-interface/traverse_the_history_4.html` ([#21383](http://github.com/servo/servo/issues/21383))</span><ul><li><span>FAIL [expected PASS] subtest: `Multiple history traversals, last would be aborted` 
```
assert_array_equals: Pages opened during history navigation expected property 1 to be 5 but got 3 (expected array [6, 5] got [6, 3])
```
</span></li></ul></li><li><span>CRASH [expected OK] `/html/canvas/element/canvas-host/2d.canvas.host.size.large.html` ([#36155](http://github.com/servo/servo/issues/36155))</span></li><li><span>OK [expected CRASH] `/html/canvas/offscreen/canvas-host/2d.canvas.host.size.large.html` ([#34117](http://github.com/servo/servo/issues/34117))</span></li><li><span>OK `/html/interaction/focus/the-autofocus-attribute/document-with-fragment-valid.html` ([#28259](http://github.com/servo/servo/issues/28259))</span><ul><li><span>FAIL [expected PASS] subtest: `Autofocus elements in top-level browsing context's documents with URL fragments should be skipped.` 
```
assert_true: expected true got false
```
</span></li></ul></li><li><span>OK [expected TIMEOUT] `/html/interaction/focus/the-autofocus-attribute/supported-elements.html` ([#24145](http://github.com/servo/servo/issues/24145))</span><ul><li><span>FAIL [expected NOTRUN] subtest: `Area element should support autofocus` 
```
promise_test: Unhandled rejection with value: object "TypeError: w.document.querySelector(...) is null"
```
</span></li></ul></li><li><span>OK `/html/semantics/embedded-content/the-iframe-element/iframe-loading-lazy-reload-location-reload.html` ([#32595](http://github.com/servo/servo/issues/32595))</span><ul><li><span>FAIL [expected PASS] subtest: `Reloading iframe loading='lazy' before it is loaded: location.reload` 
```
uncaught exception: Error: assert_equals: expected "http://web-platform.test:8000/html/semantics/embedded-content/the-iframe-element/support/blank.htm?src" but got "about:blank"
```
</span></li></ul></li><li><span>OK [expected TIMEOUT] `/html/semantics/embedded-content/the-iframe-element/iframe_sandbox_popups_escaping-2.html` ([#22667](http://github.com/servo/servo/issues/22667))</span><ul><li><span>FAIL [expected TIMEOUT] subtest: `Check that popups from a sandboxed iframe escape the sandbox if
       allow-popups-to-escape-sandbox is used` 
```
assert_equals: It came from a sandboxed iframe expected "null" but got "http://web-platform.test:8000"
```
</span></li></ul></li><li><span>OK [expected TIMEOUT] `/html/semantics/embedded-content/the-iframe-element/iframe_sandbox_popups_escaping-3.html` ([#24057](http://github.com/servo/servo/issues/24057))</span></li><li><span>TIMEOUT `/html/semantics/embedded-content/the-iframe-element/iframe_sandbox_popups_nonescaping-1.html` ([#24066](http://github.com/servo/servo/issues/24066))</span><ul><li><span>NOTRUN [expected FAIL] subtest: `Check that popups from a sandboxed iframe do not escape the sandbox`</span></li></ul></li><li><span>TIMEOUT [expected CRASH] `/html/semantics/embedded-content/the-iframe-element/iframe_sandbox_popups_nonescaping-2.html` ([#22154](http://github.com/servo/servo/issues/22154))</span><ul><li><span>NOTRUN [expected FAIL] subtest: `Check that popups from a sandboxed iframe do not escape the sandbox`</span></li></ul></li><li><span>TIMEOUT [expected OK] `/html/semantics/forms/form-submission-0/reparent-form-during-planned-navigation-task.html` ([#29724](http://github.com/servo/servo/issues/29724))</span><ul><li><span>TIMEOUT [expected PASS] subtest: `reparent-form-during-planned-navigation-task` 
```
Test timed out
```
</span></li></ul></li><li><span>OK `/navigation-timing/test-navigation-type-reload.html` ([#33334](http://github.com/servo/servo/issues/33334))</span><ul><li><span>FAIL [expected PASS] subtest: `Reload domComplete &gt; Original domComplete` 
```
assert_true: Reload domComplete &gt; Original domComplete expected true got false
```
</span></li><li><span>FAIL [expected PASS] subtest: `Reload loadEventEnd &gt; Original loadEventEnd` 
```
assert_true: Reload loadEventEnd &gt; Original loadEventEnd expected true got false
```
</span></li><li><span>FAIL [expected PASS] subtest: `Reload loadEventStart &gt; Original loadEventStart` 
```
assert_true: Reload loadEventStart &gt; Original loadEventStart expected true got false
```
</span></li></ul></li><li><span>TIMEOUT [expected OK] `/webmessaging/with-ports/018.html` ([#24485](http://github.com/servo/servo/issues/24485))</span><ul><li><span>TIMEOUT [expected PASS] subtest: `origin of the script that invoked the method, javascript:` 
```
Test timed out
```
</span></li></ul></li><li><span>ERROR [expected OK] `/workers/constructors/Worker/Worker-constructor.html` ([#22991](http://github.com/servo/servo/issues/22991))</span></li></ul></details></div></span>
author_association: NONE
user: login: github-actions[bot]
created_at: 2025-04-04T14:31:33Z
body: ✨ Try run ([#14267148717](https://github.com/servo/servo/actions/runs/14267148717)) succeeded.
author_association: NONE
user: login: github-actions[bot]
created_at: 2025-04-06T21:33:09Z
body: 🔨 Triggering try run ([#14296825188](https://github.com/servo/servo/actions/runs/14296825188)) for Linux (WPT)
author_association: NONE
user: login: github-actions[bot]
created_at: 2025-04-06T21:59:18Z
body: <span>Test results for linux-wpt from try job ([#14296825188](https://github.com/servo/servo/actions/runs/14296825188)):<div><details><summary>Flaky unexpected result (24)</summary><ul><li><span>TIMEOUT `/FileAPI/url/url-in-tags-revoke.window.html` ([#19978](http://github.com/servo/servo/issues/19978))</span><ul><li><span>TIMEOUT [expected PASS] subtest: `Fetching a blob URL immediately before revoking it works in &lt;script&gt; tags.` 
```
Test timed out
```
</span></li></ul></li><li><span>OK `/FileAPI/url/url-with-fetch.any.html` ([#21517](http://github.com/servo/servo/issues/21517))</span><ul><li><span>FAIL [expected PASS] subtest: `Revoke blob URL after calling fetch, fetch should succeed` 
```
promise_test: Unhandled rejection with value: object "TypeError: Network error occurred"
```
</span></li></ul></li><li><span>OK `/css/css-fonts/variations/at-font-face-font-matching.html` ([#20684](http://github.com/servo/servo/issues/20684))</span><ul><li><span>PASS [expected FAIL] subtest: `Matching font-style: 'oblique 0deg' should prefer 'oblique -40deg -30deg' over 'italic'`</span></li></ul></li><li><span>OK `/custom-elements/form-associated/ElementInternals-setFormValue.html` ([#29174](http://github.com/servo/servo/issues/29174))</span><ul><li><span>PASS [expected FAIL] subtest: `setFormValue with an empty FormData should submit nothing`</span></li></ul></li><li><span>OK `/encoding/legacy-mb-japanese/iso-2022-jp/iso2022jp-encode-form-csiso2022jp.html?1-1000`</span><ul><li><span>FAIL [expected PASS] subtest: `U+A5 ¥ %1B%28%4A%5C%1B%28%42` 
```
assert_equals: expected "%1B%28%4A%5C" but got ""
```
</span></li><li><span>FAIL [expected PASS] subtest: `U+A7 § %1B%24%42%21%78%1B%28%42` 
```
str is undefined
```
</span></li><li><span>FAIL [expected PASS] subtest: `U+A8 ¨ %1B%24%42%21%2F%1B%28%42` 
```
str is undefined
```
</span></li><li><span>FAIL [expected PASS] subtest: `U+B0 ° %1B%24%42%21%6B%1B%28%42` 
```
str is undefined
```
</span></li><li><span>FAIL [expected PASS] subtest: `U+B1 ± %1B%24%42%21%5E%1B%28%42` 
```
str is undefined
```
</span></li><li><span>FAIL [expected PASS] subtest: `U+B4 ´ %1B%24%42%21%2D%1B%28%42` 
```
str is undefined
```
</span></li><li><span>FAIL [expected PASS] subtest: `U+B6 ¶ %1B%24%42%22%79%1B%28%42` 
```
str is undefined
```
</span></li><li><span>FAIL [expected PASS] subtest: `U+D7 × %1B%24%42%21%5F%1B%28%42` 
```
str is undefined
```
</span></li><li><span>FAIL [expected PASS] subtest: `U+F7 ÷ %1B%24%42%21%60%1B%28%42` 
```
str is undefined
```
</span></li><li><span>FAIL [expected PASS] subtest: `U+391 Α %1B%24%42%26%21%1B%28%42` 
```
str is undefined
```
</span></li><li><span>And 390 more unexpected results...</span></li></ul></li><li><span>OK `/encoding/legacy-mb-japanese/shift_jis/sjis-encode-href.html?1-1000`</span><ul><li><span>FAIL [expected PASS] subtest: `U+80  %80` 
```
assert_equals: expected "%80" but got ""
```
</span></li><li><span>FAIL [expected PASS] subtest: `U+A5 ¥ %5C` 
```
str is undefined
```
</span></li><li><span>FAIL [expected PASS] subtest: `U+A7 § %81%98` 
```
str is undefined
```
</span></li><li><span>FAIL [expected PASS] subtest: `U+A8 ¨ %81%4E` 
```
str is undefined
```
</span></li><li><span>FAIL [expected PASS] subtest: `U+B0 ° %81%8B` 
```
str is undefined
```
</span></li><li><span>FAIL [expected PASS] subtest: `U+B1 ± %81%7D` 
```
str is undefined
```
</span></li><li><span>FAIL [expected PASS] subtest: `U+B4 ´ %81%4C` 
```
str is undefined
```
</span></li><li><span>FAIL [expected PASS] subtest: `U+B6 ¶ %81%F7` 
```
str is undefined
```
</span></li><li><span>FAIL [expected PASS] subtest: `U+D7 × %81%7E` 
```
str is undefined
```
</span></li><li><span>FAIL [expected PASS] subtest: `U+F7 ÷ %81%80` 
```
str is undefined
```
</span></li><li><span>And 390 more unexpected results...</span></li></ul></li><li><span>OK `/encoding/legacy-mb-korean/euc-kr/euckr-encode-form-errors-misc.html?1001-2000`</span><ul><li><span>FAIL [expected PASS] subtest: `punctuation, currency, symbols U+2014 — %26%238212%3B` 
```
assert_equals: expected "%26%238212%3B" but got "%26%23378%3B"
```
</span></li><li><span>FAIL [expected PASS] subtest: `punctuation, currency, symbols U+2016 ‖ %26%238214%3B` 
```
assert_equals: expected "%26%238214%3B" but got "%26%23379%3B"
```
</span></li><li><span>FAIL [expected PASS] subtest: `punctuation, currency, symbols U+2017 ‗ %26%238215%3B` 
```
assert_equals: expected "%26%238215%3B" but got "%26%23380%3B"
```
</span></li><li><span>FAIL [expected PASS] subtest: `punctuation, currency, symbols U+201A ‚ %26%238218%3B` 
```
assert_equals: expected "%26%238218%3B" but got "%26%23381%3B"
```
</span></li><li><span>FAIL [expected PASS] subtest: `punctuation, currency, symbols U+201B ‛ %26%238219%3B` 
```
assert_equals: expected "%26%238219%3B" but got "%26%23382%3B"
```
</span></li><li><span>FAIL [expected PASS] subtest: `punctuation, currency, symbols U+201E „ %26%238222%3B` 
```
assert_equals: expected "%26%238222%3B" but got "%26%23383%3B"
```
</span></li><li><span>FAIL [expected PASS] subtest: `punctuation, currency, symbols U+201F ‟ %26%238223%3B` 
```
assert_equals: expected "%26%238223%3B" but got "%26%23384%3B"
```
</span></li><li><span>FAIL [expected PASS] subtest: `punctuation, currency, symbols U+2022 • %26%238226%3B` 
```
assert_equals: expected "%26%238226%3B" but got "%26%23385%3B"
```
</span></li><li><span>FAIL [expected PASS] subtest: `punctuation, currency, symbols U+2023 ‣ %26%238227%3B` 
```
assert_equals: expected "%26%238227%3B" but got "%26%23386%3B"
```
</span></li><li><span>FAIL [expected PASS] subtest: `punctuation, currency, symbols U+2024 ․ %26%238228%3B` 
```
assert_equals: expected "%26%238228%3B" but got "%26%23387%3B"
```
</span></li><li><span>And 190 more unexpected results...</span></li></ul></li><li><span>OK `/fetch/metadata/generated/css-font-face.sub.tentative.html` ([#34624](http://github.com/servo/servo/issues/34624))</span><ul><li><span>PASS [expected FAIL] subtest: `sec-fetch-storage-access - Not sent to non-trustworthy cross-site destination`</span></li></ul></li><li><span>OK `/html/browsers/browsing-the-web/navigating-across-documents/005.html` ([#27062](http://github.com/servo/servo/issues/27062))</span><ul><li><span>PASS [expected FAIL] subtest: `Link with onclick navigation and href navigation `</span></li></ul></li><li><span>OK `/html/browsers/browsing-the-web/navigating-across-documents/009.html` ([#24456](http://github.com/servo/servo/issues/24456))</span><ul><li><span>PASS [expected FAIL] subtest: `Link with onclick form submit to javascript url with document.write and href navigation `</span></li></ul></li><li><span>OK `/html/browsers/browsing-the-web/navigating-across-documents/initial-empty-document/iframe-nosrc.html` ([#34819](http://github.com/servo/servo/issues/34819))</span><ul><li><span>PASS [expected FAIL] subtest: `link click`</span></li></ul></li><li><span>OK `/html/browsers/browsing-the-web/navigating-across-documents/initial-empty-document/iframe-src-aboutblank-navigate-immediately.html` ([#29048](http://github.com/servo/servo/issues/29048))</span><ul><li><span>FAIL [expected PASS] subtest: `Navigating to a different document with form submission` 
```
assert_equals: expected "http://web-platform.test:8000/common/blank.html?1=" but got "about:blank"
```
</span></li></ul></li><li><span>OK `/html/browsers/browsing-the-web/navigating-across-documents/replace-before-load/a-click.html` ([#28697](http://github.com/servo/servo/issues/28697))</span><ul><li><span>PASS [expected FAIL] subtest: `aElement.click() before the load event must NOT replace`</span></li></ul></li><li><span>OK `/html/browsers/windows/browsing-context-names/duplicate-name-order.html` ([#34623](http://github.com/servo/servo/issues/34623))</span><ul><li><span>PASS [expected FAIL] subtest: `Duplicate name lookup order`</span></li></ul></li><li><span>CRASH [expected OK] `/html/canvas/element/canvas-host/2d.canvas.host.size.large.html` ([#36155](http://github.com/servo/servo/issues/36155))</span></li><li><span>TIMEOUT [expected ERROR] `/html/canvas/element/manual/imagebitmap/createImageBitmap-drawImage.html` ([#32745](http://github.com/servo/servo/issues/32745))</span><ul><li><span>TIMEOUT [expected FAIL] subtest: `createImageBitmap from an HTMLVideoElement from a data URL, and drawImage on the created ImageBitmap` 
```
Test timed out
```
</span></li><li><span>NOTRUN [expected FAIL] subtest: `createImageBitmap from an HTMLVideoElement from a data URL scaled down, and drawImage on the created ImageBitmap`</span></li><li><span>NOTRUN [expected FAIL] subtest: `createImageBitmap from an HTMLVideoElement from a data URL scaled up, and drawImage on the created ImageBitmap`</span></li><li><span>NOTRUN [expected FAIL] subtest: `createImageBitmap from an HTMLVideoElement from a data URL resized, and drawImage on the created ImageBitmap`</span></li><li><span>NOTRUN [expected FAIL] subtest: `createImageBitmap from an HTMLVideoElement from a data URL with negative sw/sh, and drawImage on the created ImageBitmap`</span></li><li><span>NOTRUN [expected FAIL] subtest: `createImageBitmap from a bitmap HTMLImageElement, and drawImage on the created ImageBitmap`</span></li><li><span>NOTRUN [expected FAIL] subtest: `createImageBitmap from a bitmap HTMLImageElement scaled down, and drawImage on the created ImageBitmap`</span></li><li><span>NOTRUN [expected FAIL] subtest: `createImageBitmap from a bitmap HTMLImageElement scaled up, and drawImage on the created ImageBitmap`</span></li><li><span>NOTRUN [expected FAIL] subtest: `createImageBitmap from a bitmap HTMLImageElement resized, and drawImage on the created ImageBitmap`</span></li><li><span>NOTRUN [expected FAIL] subtest: `createImageBitmap from a bitmap HTMLImageElement with negative sw/sh, and drawImage on the created ImageBitmap`</span></li><li><span>And 6 more unexpected results...</span></li></ul></li><li><span>TIMEOUT [expected ERROR] `/html/canvas/element/manual/imagebitmap/createImageBitmap-flipY.html` ([#32745](http://github.com/servo/servo/issues/32745))</span><ul><li><span>TIMEOUT [expected FAIL] subtest: `createImageBitmap from an HTMLVideoElement from a data URL imageOrientation: "from-image", and drawImage on the created ImageBitmap` 
```
Test timed out
```
</span></li><li><span>NOTRUN [expected FAIL] subtest: `createImageBitmap from an HTMLVideoElement from a data URL imageOrientation: "flipY", and drawImage on the created ImageBitmap`</span></li><li><span>NOTRUN [expected FAIL] subtest: `createImageBitmap from a bitmap HTMLImageElement imageOrientation: "from-image", and drawImage on the created ImageBitmap`</span></li><li><span>NOTRUN [expected FAIL] subtest: `createImageBitmap from a bitmap HTMLImageElement imageOrientation: "flipY", and drawImage on the created ImageBitmap`</span></li><li><span>NOTRUN [expected FAIL] subtest: `createImageBitmap from a vector HTMLImageElement imageOrientation: "from-image", and drawImage on the created ImageBitmap`</span></li><li><span>NOTRUN [expected FAIL] subtest: `createImageBitmap from a vector HTMLImageElement imageOrientation: "flipY", and drawImage on the created ImageBitmap`</span></li><li><span>NOTRUN [expected TIMEOUT] subtest: `createImageBitmap from a bitmap SVGImageElement imageOrientation: "from-image", and drawImage on the created ImageBitmap`</span></li></ul></li><li><span>TIMEOUT [expected OK] `/html/interaction/focus/the-autofocus-attribute/autofocus-dialog.html` ([#29087](http://github.com/servo/servo/issues/29087))</span><ul><li><span>TIMEOUT [expected FAIL] subtest: `&lt;dialog&gt;-contained autofocus element gets focused when the dialog is shown` 
```
Test timed out
```
</span></li></ul></li><li><span>OK `/html/interaction/focus/the-autofocus-attribute/document-with-fragment-valid.html` ([#28259](http://github.com/servo/servo/issues/28259))</span><ul><li><span>FAIL [expected PASS] subtest: `Autofocus elements in top-level browsing context's documents with URL fragments should be skipped.` 
```
assert_true: expected true got false
```
</span></li></ul></li><li><span>TIMEOUT [expected OK] `/html/interaction/focus/the-autofocus-attribute/skip-another-top-level-browsing-context.html` ([#24161](http://github.com/servo/servo/issues/24161))</span><ul><li><span>TIMEOUT [expected PASS] subtest: `Autofocus elements queued in another top-level browsing context's documents should be skipped.` 
```
Test timed out
```
</span></li></ul></li><li><span>OK [expected TIMEOUT] `/html/semantics/embedded-content/the-iframe-element/iframe_sandbox_popups_escaping-1.html` ([#22647](http://github.com/servo/servo/issues/22647))</span><ul><li><span>FAIL [expected TIMEOUT] subtest: `Check that popups from a sandboxed iframe escape the sandbox if
       allow-popups-to-escape-sandbox is used` 
```
assert_equals: It came from a sandboxed iframe expected "null" but got "http://web-platform.test:8000"
```
</span></li></ul></li><li><span>OK `/html/semantics/scripting-1/the-script-element/execution-timing/077.html` ([#22139](http://github.com/servo/servo/issues/22139))</span><ul><li><span>FAIL [expected PASS] subtest: ` adding several types of scripts through the DOM and removing some of them confuses scheduler ` 
```
assert_array_equals: expected property 1 to be "Script #1 ran" but got "Script #3 ran" (expected array ["Script #2 ran", "Script #1 ran", "Script #3 ran", "Script #4 ran"] got ["Script #2 ran", "Script #3 ran", "Script #4 ran", "Script #1 ran"])
```
</span></li></ul></li><li><span>OK `/resize-observer/eventloop.html` ([#33599](http://github.com/servo/servo/issues/33599))</span><ul><li><span>FAIL [expected PASS] subtest: `test0: multiple notifications inside same event loop` 
```
assert_equals: new loop expected 1 but got 0
```
</span></li></ul></li><li><span>ERROR `/service-workers/idlharness.https.any.html` ([#36250](http://github.com/servo/servo/issues/36250))</span><ul><li><span>PASS [expected TIMEOUT] subtest: `ServiceWorkerContainer interface: operation register((TrustedScriptURL or USVString), optional RegistrationOptions)`</span></li><li><span>PASS [expected TIMEOUT] subtest: `NavigationPreloadManager interface: operation enable()`</span></li><li><span>PASS [expected TIMEOUT] subtest: `NavigationPreloadManager interface: operation disable()`</span></li><li><span>PASS [expected TIMEOUT] subtest: `NavigationPreloadManager interface: operation setHeaderValue(ByteString)`</span></li><li><span>PASS [expected TIMEOUT] subtest: `NavigationPreloadManager interface: operation getState()`</span></li></ul></li></ul></details><details><summary>Stable unexpected results that are known to be intermittent (19)</summary><ul><li><span>OK `/_webgl/conformance/textures/misc/texture-upload-size.html` ([#21770](http://github.com/servo/servo/issues/21770))</span><ul><li><span>FAIL [expected PASS] subtest: `WebGL test #44` 
```
assert_true: could not create image (SVG) expected true got false
```
</span></li><li><span>PASS [expected FAIL] subtest: `WebGL test #60`</span></li></ul></li><li><span>FAIL [expected PASS] `/css/compositing/mix-blend-mode/mix-blend-mode-video-sibling.html` ([#32849](http://github.com/servo/servo/issues/32849))</span></li><li><span>OK `/fetch/metadata/generated/css-font-face.https.sub.tentative.html` ([#32732](http://github.com/servo/servo/issues/32732))</span><ul><li><span>PASS [expected FAIL] subtest: `sec-fetch-dest`</span></li><li><span>PASS [expected FAIL] subtest: `sec-fetch-storage-access - Cross-site`</span></li></ul></li><li><span>OK `/html/browsers/browsing-the-web/navigating-across-documents/008.html` ([#24456](http://github.com/servo/servo/issues/24456))</span><ul><li><span>FAIL [expected PASS] subtest: `Link with onclick form submit to javascript url and href navigation ` 
```
assert_equals: expected "href" but got "click"
```
</span></li></ul></li><li><span>OK `/html/browsers/browsing-the-web/navigating-across-documents/navigation-unload-cross-origin.sub.window.html` ([#29056](http://github.com/servo/servo/issues/29056))</span><ul><li><span>FAIL [expected PASS] subtest: `Cross-origin navigation started from unload handler must be ignored` 
```
promise_test: Unhandled rejection with value: object "SecurityError: The operation is insecure."
```
</span></li></ul></li><li><span>ERROR [expected TIMEOUT] `/html/canvas/element/manual/imagebitmap/createImageBitmap-transfer.html` ([#34119](http://github.com/servo/servo/issues/34119))</span></li><li><span>PASS [expected FAIL] `/html/canvas/element/manual/text/canvas.2d.disconnected.html` ([#30063](http://github.com/servo/servo/issues/30063))</span></li><li><span>OK [expected CRASH] `/html/canvas/offscreen/canvas-host/2d.canvas.host.size.large.html` ([#34117](http://github.com/servo/servo/issues/34117))</span></li><li><span>OK [expected CRASH] `/html/canvas/offscreen/canvas-host/2d.canvas.host.size.large.worker.html` ([#30164](http://github.com/servo/servo/issues/30164))</span></li><li><span>TIMEOUT `/html/interaction/focus/the-autofocus-attribute/supported-elements.html` ([#24145](http://github.com/servo/servo/issues/24145))</span><ul><li><span>TIMEOUT [expected FAIL] subtest: `Contenteditable element should support autofocus` 
```
Test timed out
```
</span></li><li><span>NOTRUN [expected FAIL] subtest: `Element with tabindex should support autofocus`</span></li><li><span>NOTRUN [expected PASS] subtest: `Non-HTMLElement should not support autofocus`</span></li><li><span>NOTRUN [expected FAIL] subtest: `Host element with delegatesFocus should support autofocus`</span></li><li><span>NOTRUN [expected FAIL] subtest: `Host element with delegatesFocus including no focusable descendants should be skipped`</span></li></ul></li><li><span>OK [expected TIMEOUT] `/html/semantics/embedded-content/the-iframe-element/iframe_sandbox_popups_escaping-2.html` ([#22667](http://github.com/servo/servo/issues/22667))</span><ul><li><span>FAIL [expected TIMEOUT] subtest: `Check that popups from a sandboxed iframe escape the sandbox if
       allow-popups-to-escape-sandbox is used` 
```
assert_equals: It came from a sandboxed iframe expected "null" but got "http://web-platform.test:8000"
```
</span></li></ul></li><li><span>OK [expected TIMEOUT] `/html/semantics/embedded-content/the-iframe-element/iframe_sandbox_popups_escaping-3.html` ([#24057](http://github.com/servo/servo/issues/24057))</span></li><li><span>OK [expected TIMEOUT] `/html/semantics/embedded-content/the-iframe-element/iframe_sandbox_popups_nonescaping-1.html` ([#24066](http://github.com/servo/servo/issues/24066))</span></li><li><span>TIMEOUT [expected CRASH] `/html/semantics/embedded-content/the-iframe-element/iframe_sandbox_popups_nonescaping-2.html` ([#22154](http://github.com/servo/servo/issues/22154))</span><ul><li><span>NOTRUN [expected FAIL] subtest: `Check that popups from a sandboxed iframe do not escape the sandbox`</span></li></ul></li><li><span>OK [expected TIMEOUT] `/html/semantics/embedded-content/the-iframe-element/iframe_sandbox_popups_nonescaping-3.html` ([#24066](http://github.com/servo/servo/issues/24066))</span><ul><li><span>FAIL [expected NOTRUN] subtest: `Check that popups from a sandboxed iframe do not escape the sandbox` 
```
assert_equals: It came from a sandboxed iframe expected "null" but got "http://web-platform.test:8000"
```
</span></li></ul></li><li><span>TIMEOUT [expected OK] `/html/semantics/forms/form-submission-0/reparent-form-during-planned-navigation-task.html` ([#29724](http://github.com/servo/servo/issues/29724))</span><ul><li><span>TIMEOUT [expected PASS] subtest: `reparent-form-during-planned-navigation-task` 
```
Test timed out
```
</span></li></ul></li><li><span>OK `/navigation-timing/test-navigation-type-reload.html` ([#33334](http://github.com/servo/servo/issues/33334))</span><ul><li><span>PASS [expected FAIL] subtest: `Reload domContentLoadedEventEnd &gt; Original domContentLoadedEventEnd`</span></li></ul></li><li><span>OK `/resize-observer/change-layout-in-error.html` ([#32629](http://github.com/servo/servo/issues/32629))</span><ul><li><span>PASS [expected FAIL] subtest: `Changing layout in window error handler should not result in lifecyle loop when resize observer loop limit is reached.`</span></li></ul></li><li><span>ERROR [expected OK] `/workers/constructors/Worker/Worker-constructor.html` ([#22991](http://github.com/servo/servo/issues/22991))</span></li></ul></details></div></span>
author_association: NONE
user: login: github-actions[bot]
created_at: 2025-04-06T21:59:29Z
body: ✨ Try run ([#14296825188](https://github.com/servo/servo/actions/runs/14296825188)) succeeded.
author_association: NONE