diff options
author | Gae24 <96017547+Gae24@users.noreply.github.com> | 2025-04-07 01:47:57 +0200 |
---|---|---|
committer | GitHub <noreply@github.com> | 2025-04-06 23:47:57 +0000 |
commit | d1243a1867bdef5106ee016f6f3575488d73f030 (patch) | |
tree | 3c937a522f213f94adcb214124d01c28fe6e1db0 | |
parent | 33b00dbe405ec6b432e465b9ee28d433382d9f32 (diff) | |
download | servo-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.rs | 2 | ||||
-rw-r--r-- | components/script/dom/clipboarditem.rs | 181 | ||||
-rw-r--r-- | components/script/dom/mod.rs | 1 | ||||
-rw-r--r-- | components/script_bindings/codegen/Bindings.conf | 4 | ||||
-rw-r--r-- | components/script_bindings/webidls/Clipboard.webidl | 26 | ||||
-rw-r--r-- | ports/servoshell/prefs.rs | 1 | ||||
-rw-r--r-- | tests/wpt/meta/__dir__.ini | 1 | ||||
-rw-r--r-- | tests/wpt/meta/clipboard-apis/clipboard-item.https.html.ini | 12 | ||||
-rw-r--r-- | tests/wpt/meta/clipboard-apis/idlharness.https.window.js.ini | 24 | ||||
-rw-r--r-- | tests/wpt/mozilla/meta/MANIFEST.json | 2 | ||||
-rw-r--r-- | tests/wpt/mozilla/meta/__dir__.ini | 2 | ||||
-rw-r--r-- | tests/wpt/mozilla/tests/mozilla/interfaces.https.html | 1 |
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", |