aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorGae24 <96017547+Gae24@users.noreply.github.com>2025-04-07 01:47:57 +0200
committerGitHub <noreply@github.com>2025-04-06 23:47:57 +0000
commitd1243a1867bdef5106ee016f6f3575488d73f030 (patch)
tree3c937a522f213f94adcb214124d01c28fe6e1db0
parent33b00dbe405ec6b432e465b9ee28d433382d9f32 (diff)
downloadservo-d1243a1867bdef5106ee016f6f3575488d73f030.tar.gz
servo-d1243a1867bdef5106ee016f6f3575488d73f030.zip
dom: Implement `ClipboardItem` (#36336)
implement the `ClipboardItem` interface Testing: covered by existing wpt tests part of #36084 --------- Signed-off-by: Gae24 <96017547+Gae24@users.noreply.github.com>
-rw-r--r--components/config/prefs.rs2
-rw-r--r--components/script/dom/clipboarditem.rs181
-rw-r--r--components/script/dom/mod.rs1
-rw-r--r--components/script_bindings/codegen/Bindings.conf4
-rw-r--r--components/script_bindings/webidls/Clipboard.webidl26
-rw-r--r--ports/servoshell/prefs.rs1
-rw-r--r--tests/wpt/meta/__dir__.ini1
-rw-r--r--tests/wpt/meta/clipboard-apis/clipboard-item.https.html.ini12
-rw-r--r--tests/wpt/meta/clipboard-apis/idlharness.https.window.js.ini24
-rw-r--r--tests/wpt/mozilla/meta/MANIFEST.json2
-rw-r--r--tests/wpt/mozilla/meta/__dir__.ini2
-rw-r--r--tests/wpt/mozilla/tests/mozilla/interfaces.https.html1
12 files changed, 219 insertions, 38 deletions
diff --git a/components/config/prefs.rs b/components/config/prefs.rs
index 980b08f2e08..6ed3864e8b4 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,
@@ -246,6 +247,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 00000000000..c1c66a403b3
--- /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 417bcface58..7a299d6c7ac 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 b6105c2d43a..5a95a6131a6 100644
--- a/components/script_bindings/codegen/Bindings.conf
+++ b/components/script_bindings/codegen/Bindings.conf
@@ -87,6 +87,10 @@ DOMInterfaces = {
'canGc': ['Before', 'After', 'Remove', '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 00000000000..2d2bb409e1a
--- /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 19420b0325b..2bd95398f63 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 4f66fa65008..a05599bd7d1 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 057b25ac030..a76ef482f19 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 d16c54f589e..7edc93e052c 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 3bab4b6e18d..eb94f612b60 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 6a2d9b4a0e6..8468f1d6d10 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 dce05a55fd3..cc6e99d8da7 100644
--- a/tests/wpt/mozilla/tests/mozilla/interfaces.https.html
+++ b/tests/wpt/mozilla/tests/mozilla/interfaces.https.html
@@ -39,6 +39,7 @@ test_interfaces([
"ChannelSplitterNode",
"CharacterData",
"ClipboardEvent",
+ "ClipboardItem",
"CloseEvent",
"ConstantSourceNode",
"CryptoKey",