| /* 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 std::str::FromStr; |
| |
| use constellation_traits::BlobImpl; |
| use data_url::mime::Mime; |
| use dom_struct::dom_struct; |
| use embedder_traits::EmbedderMsg; |
| use js::rust::HandleValue as SafeHandleValue; |
| |
| use crate::dom::bindings::codegen::Bindings::ClipboardBinding::{ |
| ClipboardMethods, PresentationStyle, |
| }; |
| use crate::dom::bindings::error::Error; |
| 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::clipboarditem::Representation; |
| use crate::dom::eventtarget::EventTarget; |
| use crate::dom::globalscope::GlobalScope; |
| use crate::dom::promise::Promise; |
| use crate::dom::promisenativehandler::{Callback, PromiseNativeHandler}; |
| use crate::dom::window::Window; |
| use crate::realms::{InRealm, enter_realm}; |
| use crate::routed_promise::{RoutedPromiseListener, route_promise}; |
| use crate::script_runtime::{CanGc, JSContext as SafeJSContext}; |
| |
| /// The fulfillment handler for the reacting to representationDataPromise part of |
| /// <https://w3c.github.io/clipboard-apis/#dom-clipboard-readtext>. |
| #[derive(Clone, JSTraceable, MallocSizeOf)] |
| struct RepresentationDataPromiseFulfillmentHandler { |
| #[ignore_malloc_size_of = "Rc are hard"] |
| promise: Rc<Promise>, |
| } |
| |
| impl Callback for RepresentationDataPromiseFulfillmentHandler { |
| /// The fulfillment case of Step 3.4.1.1.4.3 of |
| /// <https://w3c.github.io/clipboard-apis/#dom-clipboard-readtext>. |
| fn callback(&self, cx: SafeJSContext, v: SafeHandleValue, _realm: InRealm, can_gc: CanGc) { |
| // If v is a DOMString, then follow the below steps: |
| // Resolve p with v. |
| // Return p. |
| self.promise.resolve(cx, v, can_gc); |
| |
| // NOTE: Since we ask text from arboard, v can't be a Blob |
| // If v is a Blob, then follow the below steps: |
| // Let string be the result of UTF-8 decoding v’s underlying byte sequence. |
| // Resolve p with string. |
| // Return p. |
| } |
| } |
| |
| /// The rejection handler for the reacting to representationDataPromise part of |
| /// <https://w3c.github.io/clipboard-apis/#dom-clipboard-readtext>. |
| #[derive(Clone, JSTraceable, MallocSizeOf)] |
| struct RepresentationDataPromiseRejectionHandler { |
| #[ignore_malloc_size_of = "Rc are hard"] |
| promise: Rc<Promise>, |
| } |
| |
| impl Callback for RepresentationDataPromiseRejectionHandler { |
| /// The rejection case of Step 3.4.1.1.4.3 of |
| /// <https://w3c.github.io/clipboard-apis/#dom-clipboard-readtext>. |
| fn callback(&self, _cx: SafeJSContext, _v: SafeHandleValue, _realm: InRealm, can_gc: CanGc) { |
| // Reject p with "NotFoundError" DOMException in realm. |
| // Return p. |
| self.promise.reject_error(Error::NotFound, can_gc); |
| } |
| } |
| |
| #[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-readtext> |
| fn ReadText(&self, can_gc: CanGc) -> Rc<Promise> { |
| // Step 1 Let realm be this's relevant realm. |
| let global = self.global(); |
| |
| // Step 2 Let p be a new promise in realm. |
| let p = Promise::new(&global, can_gc); |
| |
| // Step 3 Run the following steps in parallel: |
| |
| // TODO Step 3.1 Let r be the result of running check clipboard read 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. |
| |
| // Step 3.3 Let data be a copy of the system clipboard data. |
| let window = global.as_window(); |
| let sender = route_promise(&p, self, global.task_manager().clipboard_task_source()); |
| window.send_to_embedder(EmbedderMsg::GetClipboardText(window.webview_id(), sender)); |
| |
| // Step 3.4 Queue a global task on the clipboard task source, |
| // given realm’s global object, to perform the below steps: |
| // NOTE: We queue the task inside route_promise and perform the steps inside handle_response |
| |
| p |
| } |
| |
| /// <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 |
| } |
| } |
| |
| impl RoutedPromiseListener<Result<String, String>> for Clipboard { |
| fn handle_response( |
| &self, |
| response: Result<String, String>, |
| promise: &Rc<Promise>, |
| can_gc: CanGc, |
| ) { |
| let global = self.global(); |
| let text = response.unwrap_or_default(); |
| |
| // Step 3.4.1 For each systemClipboardItem in data: |
| // Step 3.4.1.1 For each systemClipboardRepresentation in systemClipboardItem: |
| // TODO: Arboard provide the first item that has a String representation |
| |
| // Step 3.4.1.1.1 Let mimeType be the result of running the |
| // well-known mime type from os specific format algorithm given systemClipboardRepresentation’s name. |
| // Note: This is done by arboard, so we just convert the format to a MIME |
| let mime_type = Mime::from_str("text/plain").unwrap(); |
| |
| // Step 3.4.1.1.2 If mimeType is null, continue this loop. |
| // Note: Since the previous step is infallible, we don't need to handle this case |
| |
| // Step 3.4.1.1.3 Let representation be a new representation. |
| let representation = Representation { |
| mime_type, |
| is_custom: false, |
| data: Promise::new_resolved( |
| &global, |
| GlobalScope::get_cx(), |
| DOMString::from(text), |
| can_gc, |
| ), |
| }; |
| |
| // Step 3.4.1.1.4 If representation’s MIME type essence is "text/plain", then: |
| |
| // Step 3.4.1.1.4.1 Set representation’s MIME type to mimeType. |
| // Note: Done when creating a new representation |
| |
| // Step 3.4.1.1.4.2 Let representationDataPromise be the representation’s data. |
| // Step 3.4.1.1.4.3 React to representationDataPromise: |
| let fulfillment_handler = Box::new(RepresentationDataPromiseFulfillmentHandler { |
| promise: promise.clone(), |
| }); |
| let rejection_handler = Box::new(RepresentationDataPromiseRejectionHandler { |
| promise: promise.clone(), |
| }); |
| let handler = PromiseNativeHandler::new( |
| &global, |
| Some(fulfillment_handler), |
| Some(rejection_handler), |
| can_gc, |
| ); |
| let realm = enter_realm(&*global); |
| let comp = InRealm::Entered(&realm); |
| representation |
| .data |
| .append_native_handler(&handler, comp, can_gc); |
| |
| // Step 3.4.2 Reject p with "NotFoundError" DOMException in realm. |
| // Step 3.4.3 Return p. |
| // NOTE: We follow the same behaviour of Gecko by doing nothing if no text is available instead of rejecting 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 |
| } |