mirror of
https://github.com/servo/servo.git
synced 2025-06-06 16:45:39 +00:00
async clipboard: implement writeText
(#36498)
Implement enough of the Clipboard API to have a working `writeText`. Testing: Unfortunately many clipboard-apis tests require testdriver, so only idlharness ones will pass now. --------- Signed-off-by: Gae24 <96017547+Gae24@users.noreply.github.com>
This commit is contained in:
parent
c6dc7c83a8
commit
2bab5d8d52
11 changed files with 173 additions and 41 deletions
138
components/script/dom/clipboard.rs
Normal file
138
components/script/dom/clipboard.rs
Normal file
|
@ -0,0 +1,138 @@
|
||||||
|
/* 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::rc::Rc;
|
||||||
|
|
||||||
|
use constellation_traits::BlobImpl;
|
||||||
|
use dom_struct::dom_struct;
|
||||||
|
use embedder_traits::EmbedderMsg;
|
||||||
|
|
||||||
|
use crate::dom::bindings::codegen::Bindings::ClipboardBinding::{
|
||||||
|
ClipboardMethods, PresentationStyle,
|
||||||
|
};
|
||||||
|
use crate::dom::bindings::refcounted::TrustedPromise;
|
||||||
|
use crate::dom::bindings::reflector::{DomGlobal, reflect_dom_object};
|
||||||
|
use crate::dom::bindings::root::DomRoot;
|
||||||
|
use crate::dom::bindings::str::DOMString;
|
||||||
|
use crate::dom::blob::Blob;
|
||||||
|
use crate::dom::eventtarget::EventTarget;
|
||||||
|
use crate::dom::globalscope::GlobalScope;
|
||||||
|
use crate::dom::promise::Promise;
|
||||||
|
use crate::dom::window::Window;
|
||||||
|
use crate::script_runtime::CanGc;
|
||||||
|
|
||||||
|
#[dom_struct]
|
||||||
|
pub(crate) struct Clipboard {
|
||||||
|
event_target: EventTarget,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Clipboard {
|
||||||
|
fn new_inherited() -> Clipboard {
|
||||||
|
Clipboard {
|
||||||
|
event_target: EventTarget::new_inherited(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn new(global: &GlobalScope, can_gc: CanGc) -> DomRoot<Clipboard> {
|
||||||
|
reflect_dom_object(Box::new(Clipboard::new_inherited()), global, can_gc)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ClipboardMethods<crate::DomTypeHolder> for Clipboard {
|
||||||
|
/// <https://w3c.github.io/clipboard-apis/#dom-clipboard-writetext>
|
||||||
|
fn WriteText(&self, data: DOMString, can_gc: CanGc) -> Rc<Promise> {
|
||||||
|
// Step 1 Let realm be this's relevant realm.
|
||||||
|
// Step 2 Let p be a new promise in realm.
|
||||||
|
let p = Promise::new(&self.global(), can_gc);
|
||||||
|
|
||||||
|
// Step 3 Run the following steps in parallel:
|
||||||
|
|
||||||
|
// TODO write permission could be removed from spec
|
||||||
|
// Step 3.1 Let r be the result of running check clipboard write permission.
|
||||||
|
// Step 3.2 If r is false, then:
|
||||||
|
// Step 3.2.1 Queue a global task on the permission task source, given realm’s global object,
|
||||||
|
// to reject p with "NotAllowedError" DOMException in realm.
|
||||||
|
// Step 3.2.2 Abort these steps.
|
||||||
|
|
||||||
|
let trusted_promise = TrustedPromise::new(p.clone());
|
||||||
|
let bytes = Vec::from(data);
|
||||||
|
|
||||||
|
// Step 3.3 Queue a global task on the clipboard task source,
|
||||||
|
// given realm’s global object, to perform the below steps:
|
||||||
|
self.global().task_manager().clipboard_task_source().queue(
|
||||||
|
task!(write_to_system_clipboard: move || {
|
||||||
|
let promise = trusted_promise.root();
|
||||||
|
let global = promise.global();
|
||||||
|
|
||||||
|
// Step 3.3.1 Let itemList be an empty sequence<Blob>.
|
||||||
|
let mut item_list = Vec::new();
|
||||||
|
|
||||||
|
// Step 3.3.2 Let textBlob be a new Blob created with: type attribute set to "text/plain;charset=utf-8",
|
||||||
|
// and its underlying byte sequence set to the UTF-8 encoding of data.
|
||||||
|
let text_blob = Blob::new(
|
||||||
|
&global,
|
||||||
|
BlobImpl::new_from_bytes(bytes, "text/plain;charset=utf-8".into()),
|
||||||
|
CanGc::note(),
|
||||||
|
);
|
||||||
|
|
||||||
|
// Step 3.3.3 Add textBlob to itemList.
|
||||||
|
item_list.push(text_blob);
|
||||||
|
|
||||||
|
// Step 3.3.4 Let option be set to "unspecified".
|
||||||
|
let option = PresentationStyle::Unspecified;
|
||||||
|
|
||||||
|
// Step 3.3.5 Write blobs and option to the clipboard with itemList and option.
|
||||||
|
write_blobs_and_option_to_the_clipboard(global.as_window(), item_list, option);
|
||||||
|
|
||||||
|
// Step 3.3.6 Resolve p.
|
||||||
|
promise.resolve_native(&(), CanGc::note());
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
|
// Step 3.4 Return p.
|
||||||
|
p
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <https://w3c.github.io/clipboard-apis/#write-blobs-and-option-to-the-clipboard>
|
||||||
|
fn write_blobs_and_option_to_the_clipboard(
|
||||||
|
window: &Window,
|
||||||
|
items: Vec<DomRoot<Blob>>,
|
||||||
|
_presentation_style: PresentationStyle,
|
||||||
|
) {
|
||||||
|
// TODO Step 1 Let webCustomFormats be a sequence<Blob>.
|
||||||
|
|
||||||
|
// Step 2 For each item in items:
|
||||||
|
for item in items {
|
||||||
|
// TODO support more formats than just text/plain
|
||||||
|
// Step 2.1 Let formatString be the result of running os specific well-known format given item’s type.
|
||||||
|
|
||||||
|
// Step 2.2 If formatString is empty then follow the below steps:
|
||||||
|
|
||||||
|
// Step 2.2.1 Let webCustomFormatString be the item’s type.
|
||||||
|
// Step 2.2.2 Let webCustomFormat be an empty type.
|
||||||
|
// Step 2.2.3 If webCustomFormatString starts with "web " prefix,
|
||||||
|
// then remove the "web " prefix and store the remaining string in webMimeTypeString.
|
||||||
|
// Step 2.2.4 Let webMimeType be the result of parsing a MIME type given webMimeTypeString.
|
||||||
|
// Step 2.2.5 If webMimeType is failure, then abort all steps.
|
||||||
|
// Step 2.2.6 Let webCustomFormat’s type's essence equal to webMimeType.
|
||||||
|
// Step 2.2.7 Set item’s type to webCustomFormat.
|
||||||
|
// Step 2.2.8 Append webCustomFormat to webCustomFormats.
|
||||||
|
|
||||||
|
// Step 2.3 Let payload be the result of UTF-8 decoding item’s underlying byte sequence.
|
||||||
|
// Step 2.4 Insert payload and presentationStyle into the system clipboard
|
||||||
|
// using formatString as the native clipboard format.
|
||||||
|
window.send_to_embedder(EmbedderMsg::SetClipboardText(
|
||||||
|
window.webview_id(),
|
||||||
|
String::from_utf8(
|
||||||
|
item.get_bytes()
|
||||||
|
.expect("No bytes found for Blob created by caller"),
|
||||||
|
)
|
||||||
|
.expect("DOMString contained invalid bytes"),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO Step 3 Write web custom formats given webCustomFormats.
|
||||||
|
// Needs support to arbitrary formats inside arboard
|
||||||
|
}
|
|
@ -250,6 +250,7 @@ pub(crate) mod channelmergernode;
|
||||||
pub(crate) mod channelsplitternode;
|
pub(crate) mod channelsplitternode;
|
||||||
pub(crate) mod characterdata;
|
pub(crate) mod characterdata;
|
||||||
pub(crate) mod client;
|
pub(crate) mod client;
|
||||||
|
pub(crate) mod clipboard;
|
||||||
pub(crate) mod clipboardevent;
|
pub(crate) mod clipboardevent;
|
||||||
pub(crate) mod clipboarditem;
|
pub(crate) mod clipboarditem;
|
||||||
pub(crate) mod closeevent;
|
pub(crate) mod closeevent;
|
||||||
|
|
|
@ -19,6 +19,7 @@ use crate::dom::bindings::str::DOMString;
|
||||||
use crate::dom::bindings::utils::to_frozen_array;
|
use crate::dom::bindings::utils::to_frozen_array;
|
||||||
#[cfg(feature = "bluetooth")]
|
#[cfg(feature = "bluetooth")]
|
||||||
use crate::dom::bluetooth::Bluetooth;
|
use crate::dom::bluetooth::Bluetooth;
|
||||||
|
use crate::dom::clipboard::Clipboard;
|
||||||
use crate::dom::gamepad::Gamepad;
|
use crate::dom::gamepad::Gamepad;
|
||||||
use crate::dom::gamepadevent::GamepadEventType;
|
use crate::dom::gamepadevent::GamepadEventType;
|
||||||
use crate::dom::mediadevices::MediaDevices;
|
use crate::dom::mediadevices::MediaDevices;
|
||||||
|
@ -57,6 +58,7 @@ pub(crate) struct Navigator {
|
||||||
gamepads: DomRefCell<Vec<MutNullableDom<Gamepad>>>,
|
gamepads: DomRefCell<Vec<MutNullableDom<Gamepad>>>,
|
||||||
permissions: MutNullableDom<Permissions>,
|
permissions: MutNullableDom<Permissions>,
|
||||||
mediasession: MutNullableDom<MediaSession>,
|
mediasession: MutNullableDom<MediaSession>,
|
||||||
|
clipboard: MutNullableDom<Clipboard>,
|
||||||
#[cfg(feature = "webgpu")]
|
#[cfg(feature = "webgpu")]
|
||||||
gpu: MutNullableDom<GPU>,
|
gpu: MutNullableDom<GPU>,
|
||||||
/// <https://www.w3.org/TR/gamepad/#dfn-hasgamepadgesture>
|
/// <https://www.w3.org/TR/gamepad/#dfn-hasgamepadgesture>
|
||||||
|
@ -79,6 +81,7 @@ impl Navigator {
|
||||||
gamepads: Default::default(),
|
gamepads: Default::default(),
|
||||||
permissions: Default::default(),
|
permissions: Default::default(),
|
||||||
mediasession: Default::default(),
|
mediasession: Default::default(),
|
||||||
|
clipboard: Default::default(),
|
||||||
#[cfg(feature = "webgpu")]
|
#[cfg(feature = "webgpu")]
|
||||||
gpu: Default::default(),
|
gpu: Default::default(),
|
||||||
has_gamepad_gesture: Cell::new(false),
|
has_gamepad_gesture: Cell::new(false),
|
||||||
|
@ -306,6 +309,12 @@ impl NavigatorMethods<crate::DomTypeHolder> for Navigator {
|
||||||
hardware_concurrency()
|
hardware_concurrency()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <https://w3c.github.io/clipboard-apis/#h-navigator-clipboard>
|
||||||
|
fn Clipboard(&self) -> DomRoot<Clipboard> {
|
||||||
|
self.clipboard
|
||||||
|
.or_init(|| Clipboard::new(&self.global(), CanGc::note()))
|
||||||
|
}
|
||||||
|
|
||||||
/// <https://servo.org/internal-no-spec>
|
/// <https://servo.org/internal-no-spec>
|
||||||
fn Servo(&self) -> DomRoot<ServoInternals> {
|
fn Servo(&self) -> DomRoot<ServoInternals> {
|
||||||
self.servo_internals
|
self.servo_internals
|
||||||
|
|
|
@ -133,6 +133,7 @@ impl TaskManager {
|
||||||
}
|
}
|
||||||
|
|
||||||
task_source_functions!(self, canvas_blob_task_source, Canvas);
|
task_source_functions!(self, canvas_blob_task_source, Canvas);
|
||||||
|
task_source_functions!(self, clipboard_task_source, Clipboard);
|
||||||
task_source_functions!(self, dom_manipulation_task_source, DOMManipulation);
|
task_source_functions!(self, dom_manipulation_task_source, DOMManipulation);
|
||||||
task_source_functions!(self, file_reading_task_source, FileReading);
|
task_source_functions!(self, file_reading_task_source, FileReading);
|
||||||
task_source_functions!(self, font_loading_task_source, FontLoading);
|
task_source_functions!(self, font_loading_task_source, FontLoading);
|
||||||
|
|
|
@ -23,6 +23,7 @@ use crate::task_manager::TaskManager;
|
||||||
#[derive(Clone, Copy, Debug, Eq, Hash, JSTraceable, MallocSizeOf, PartialEq, VariantArray)]
|
#[derive(Clone, Copy, Debug, Eq, Hash, JSTraceable, MallocSizeOf, PartialEq, VariantArray)]
|
||||||
pub(crate) enum TaskSourceName {
|
pub(crate) enum TaskSourceName {
|
||||||
Canvas,
|
Canvas,
|
||||||
|
Clipboard,
|
||||||
DOMManipulation,
|
DOMManipulation,
|
||||||
FileReading,
|
FileReading,
|
||||||
/// <https://drafts.csswg.org/css-font-loading/#task-source>
|
/// <https://drafts.csswg.org/css-font-loading/#task-source>
|
||||||
|
@ -48,6 +49,7 @@ impl From<TaskSourceName> for ScriptThreadEventCategory {
|
||||||
fn from(value: TaskSourceName) -> Self {
|
fn from(value: TaskSourceName) -> Self {
|
||||||
match value {
|
match value {
|
||||||
TaskSourceName::Canvas => ScriptThreadEventCategory::ScriptEvent,
|
TaskSourceName::Canvas => ScriptThreadEventCategory::ScriptEvent,
|
||||||
|
TaskSourceName::Clipboard => ScriptThreadEventCategory::ScriptEvent,
|
||||||
TaskSourceName::DOMManipulation => ScriptThreadEventCategory::ScriptEvent,
|
TaskSourceName::DOMManipulation => ScriptThreadEventCategory::ScriptEvent,
|
||||||
TaskSourceName::FileReading => ScriptThreadEventCategory::FileRead,
|
TaskSourceName::FileReading => ScriptThreadEventCategory::FileRead,
|
||||||
TaskSourceName::FontLoading => ScriptThreadEventCategory::FontLoading,
|
TaskSourceName::FontLoading => ScriptThreadEventCategory::FontLoading,
|
||||||
|
|
|
@ -87,6 +87,10 @@ DOMInterfaces = {
|
||||||
'canGc': ['Before', 'After', 'Remove', 'ReplaceWith']
|
'canGc': ['Before', 'After', 'Remove', 'ReplaceWith']
|
||||||
},
|
},
|
||||||
|
|
||||||
|
'Clipboard': {
|
||||||
|
'canGc': ['WriteText']
|
||||||
|
},
|
||||||
|
|
||||||
'ClipboardItem': {
|
'ClipboardItem': {
|
||||||
'canGc': ['Types']
|
'canGc': ['Types']
|
||||||
},
|
},
|
||||||
|
|
|
@ -2,7 +2,17 @@
|
||||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
* 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/. */
|
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||||
|
|
||||||
// https://w3c.github.io/clipboard-apis/#clipboard-item-interface
|
// https://w3c.github.io/clipboard-apis
|
||||||
|
|
||||||
|
typedef sequence<ClipboardItem> ClipboardItems;
|
||||||
|
|
||||||
|
[SecureContext, Exposed=Window, Pref="dom_async_clipboard_enabled"]
|
||||||
|
interface Clipboard : EventTarget {
|
||||||
|
// Promise<ClipboardItems> read();
|
||||||
|
// Promise<DOMString> readText();
|
||||||
|
// Promise<undefined> write(ClipboardItems data);
|
||||||
|
Promise<undefined> writeText(DOMString data);
|
||||||
|
};
|
||||||
|
|
||||||
typedef Promise<(DOMString or Blob)> ClipboardItemData;
|
typedef Promise<(DOMString or Blob)> ClipboardItemData;
|
||||||
|
|
||||||
|
|
|
@ -70,3 +70,8 @@ partial interface Navigator {
|
||||||
interface mixin NavigatorConcurrentHardware {
|
interface mixin NavigatorConcurrentHardware {
|
||||||
readonly attribute unsigned long long hardwareConcurrency;
|
readonly attribute unsigned long long hardwareConcurrency;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// https://w3c.github.io/clipboard-apis/#navigator-interface
|
||||||
|
partial interface Navigator {
|
||||||
|
[SecureContext, SameObject, Pref="dom_async_clipboard_enabled"] readonly attribute Clipboard clipboard;
|
||||||
|
};
|
||||||
|
|
|
@ -5,24 +5,6 @@
|
||||||
[ClipboardItem interface: operation supports(DOMString)]
|
[ClipboardItem interface: operation supports(DOMString)]
|
||||||
expected: FAIL
|
expected: FAIL
|
||||||
|
|
||||||
[Clipboard interface: existence and properties of interface object]
|
|
||||||
expected: FAIL
|
|
||||||
|
|
||||||
[Clipboard interface object length]
|
|
||||||
expected: FAIL
|
|
||||||
|
|
||||||
[Clipboard interface object name]
|
|
||||||
expected: FAIL
|
|
||||||
|
|
||||||
[Clipboard interface: existence and properties of interface prototype object]
|
|
||||||
expected: FAIL
|
|
||||||
|
|
||||||
[Clipboard interface: existence and properties of interface prototype object's "constructor" property]
|
|
||||||
expected: FAIL
|
|
||||||
|
|
||||||
[Clipboard interface: existence and properties of interface prototype object's @@unscopables property]
|
|
||||||
expected: FAIL
|
|
||||||
|
|
||||||
[Clipboard interface: operation read(optional ClipboardUnsanitizedFormats)]
|
[Clipboard interface: operation read(optional ClipboardUnsanitizedFormats)]
|
||||||
expected: FAIL
|
expected: FAIL
|
||||||
|
|
||||||
|
@ -32,15 +14,6 @@
|
||||||
[Clipboard interface: operation write(ClipboardItems)]
|
[Clipboard interface: operation write(ClipboardItems)]
|
||||||
expected: FAIL
|
expected: FAIL
|
||||||
|
|
||||||
[Clipboard interface: operation writeText(DOMString)]
|
|
||||||
expected: FAIL
|
|
||||||
|
|
||||||
[Clipboard must be primary interface of navigator.clipboard]
|
|
||||||
expected: FAIL
|
|
||||||
|
|
||||||
[Stringification of navigator.clipboard]
|
|
||||||
expected: FAIL
|
|
||||||
|
|
||||||
[Clipboard interface: navigator.clipboard must inherit property "read(optional ClipboardUnsanitizedFormats)" with the proper type]
|
[Clipboard interface: navigator.clipboard must inherit property "read(optional ClipboardUnsanitizedFormats)" with the proper type]
|
||||||
expected: FAIL
|
expected: FAIL
|
||||||
|
|
||||||
|
@ -55,15 +28,3 @@
|
||||||
|
|
||||||
[Clipboard interface: calling write(ClipboardItems) on navigator.clipboard with too few arguments must throw TypeError]
|
[Clipboard interface: calling write(ClipboardItems) on navigator.clipboard with too few arguments must throw TypeError]
|
||||||
expected: FAIL
|
expected: FAIL
|
||||||
|
|
||||||
[Clipboard interface: navigator.clipboard must inherit property "writeText(DOMString)" with the proper type]
|
|
||||||
expected: FAIL
|
|
||||||
|
|
||||||
[Clipboard interface: calling writeText(DOMString) on navigator.clipboard with too few arguments must throw TypeError]
|
|
||||||
expected: FAIL
|
|
||||||
|
|
||||||
[Navigator interface: attribute clipboard]
|
|
||||||
expected: FAIL
|
|
||||||
|
|
||||||
[Navigator interface: navigator must inherit property "clipboard" with the proper type]
|
|
||||||
expected: FAIL
|
|
||||||
|
|
2
tests/wpt/mozilla/meta/MANIFEST.json
vendored
2
tests/wpt/mozilla/meta/MANIFEST.json
vendored
|
@ -13503,7 +13503,7 @@
|
||||||
]
|
]
|
||||||
],
|
],
|
||||||
"interfaces.https.html": [
|
"interfaces.https.html": [
|
||||||
"fbbffae39a346f592fabfe5ad3d943936a51c868",
|
"ff5d8e487822e1b9bf4dfd91ab814d2affaf457b",
|
||||||
[
|
[
|
||||||
null,
|
null,
|
||||||
{}
|
{}
|
||||||
|
|
|
@ -38,6 +38,7 @@ test_interfaces([
|
||||||
"ChannelMergerNode",
|
"ChannelMergerNode",
|
||||||
"ChannelSplitterNode",
|
"ChannelSplitterNode",
|
||||||
"CharacterData",
|
"CharacterData",
|
||||||
|
"Clipboard",
|
||||||
"ClipboardEvent",
|
"ClipboardEvent",
|
||||||
"ClipboardItem",
|
"ClipboardItem",
|
||||||
"CloseEvent",
|
"CloseEvent",
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue