diff options
Diffstat (limited to 'components/script/dom')
24 files changed, 713 insertions, 78 deletions
diff --git a/components/script/dom/abstractworker.rs b/components/script/dom/abstractworker.rs index 8754bb30d96..930f48cdcea 100644 --- a/components/script/dom/abstractworker.rs +++ b/components/script/dom/abstractworker.rs @@ -12,7 +12,10 @@ pub enum WorkerScriptMsg { /// Common variants associated with the script messages Common(CommonScriptMsg), /// Message sent through Worker.postMessage - DOMMessage(StructuredCloneData), + DOMMessage { + origin: String, + data: StructuredCloneData, + } } pub struct SimpleWorkerErrorHandler<T: DomObject> { diff --git a/components/script/dom/abstractworkerglobalscope.rs b/components/script/dom/abstractworkerglobalscope.rs index af7291c013c..6ffeba85d5e 100644 --- a/components/script/dom/abstractworkerglobalscope.rs +++ b/components/script/dom/abstractworkerglobalscope.rs @@ -75,7 +75,7 @@ impl ScriptPort for Receiver<DedicatedWorkerScriptMsg> { }; match common_msg { WorkerScriptMsg::Common(script_msg) => Ok(script_msg), - WorkerScriptMsg::DOMMessage(_) => panic!("unexpected worker event message!"), + WorkerScriptMsg::DOMMessage { .. } => panic!("unexpected worker event message!"), } } } diff --git a/components/script/dom/bindings/mod.rs b/components/script/dom/bindings/mod.rs index 48ee7674c92..0bcdbf4fcfd 100644 --- a/components/script/dom/bindings/mod.rs +++ b/components/script/dom/bindings/mod.rs @@ -154,6 +154,7 @@ pub mod settings_stack; pub mod str; pub mod structuredclone; pub mod trace; +pub mod transferable; pub mod utils; pub mod weakref; pub mod xmlname; diff --git a/components/script/dom/bindings/structuredclone.rs b/components/script/dom/bindings/structuredclone.rs index f35a4af7210..2e33613b29d 100644 --- a/components/script/dom/bindings/structuredclone.rs +++ b/components/script/dom/bindings/structuredclone.rs @@ -10,8 +10,10 @@ use crate::dom::bindings::conversions::root_from_handleobject; use crate::dom::bindings::error::{Error, Fallible}; use crate::dom::bindings::reflector::DomObject; use crate::dom::bindings::root::DomRoot; +use crate::dom::bindings::transferable::Transferable; use crate::dom::blob::{Blob, BlobImpl}; use crate::dom::globalscope::GlobalScope; +use crate::dom::messageport::MessagePort; use js::glue::CopyJSStructuredCloneData; use js::glue::DeleteJSAutoStructuredCloneBuffer; use js::glue::GetLengthOfJSStructuredCloneData; @@ -44,6 +46,7 @@ enum StructuredCloneTags { /// To support additional types, add new tags with values incremented from the last one before Max. Min = 0xFFFF8000, DomBlob = 0xFFFF8001, + MessagePort = 0xFFFF8002, Max = 0xFFFFFFFF, } @@ -189,26 +192,43 @@ unsafe extern "C" fn write_callback( } unsafe extern "C" fn read_transfer_callback( - _cx: *mut JSContext, - _r: *mut JSStructuredCloneReader, - _tag: u32, - _content: *mut raw::c_void, - _extra_data: u64, - _closure: *mut raw::c_void, - _return_object: RawMutableHandleObject, + cx: *mut JSContext, + r: *mut JSStructuredCloneReader, + tag: u32, + content: *mut raw::c_void, + extra_data: u64, + closure: *mut raw::c_void, + return_object: RawMutableHandleObject, ) -> bool { - false + if tag == StructuredCloneTags::MessagePort as u32 { + <MessagePort as Transferable>::transfer_receive(cx, r, closure, content, extra_data, return_object) + } else { + false + } } +/// <https://html.spec.whatwg.org/multipage/#structuredserializewithtransfer> unsafe extern "C" fn write_transfer_callback( _cx: *mut JSContext, - _obj: RawHandleObject, - _closure: *mut raw::c_void, - _tag: *mut u32, - _ownership: *mut TransferableOwnership, - _content: *mut *mut raw::c_void, - _extra_data: *mut u64, + obj: RawHandleObject, + closure: *mut raw::c_void, + tag: *mut u32, + ownership: *mut TransferableOwnership, + content: *mut *mut raw::c_void, + extra_data: *mut u64, ) -> bool { + if let Ok(port) = root_from_handleobject::<MessagePort>(Handle::from_raw(obj)) { + if let Some(true) = port.detached() { + return false; + } + + *tag = StructuredCloneTags::MessagePort as u32; + *ownership = TransferableOwnership::SCTAG_TMO_CUSTOM; + if port.transfer(closure, content, extra_data) { + port.set_detached(true); + return true; + } + } false } @@ -256,7 +276,11 @@ pub enum StructuredCloneData { impl StructuredCloneData { // TODO: should this be unsafe? /// Writes a structured clone. Returns a `DataClone` error if that fails. - pub fn write(cx: *mut JSContext, message: HandleValue) -> Fallible<StructuredCloneData> { + pub fn write( + cx: *mut JSContext, + message: HandleValue, + transfer: HandleValue, + ) -> Fallible<StructuredCloneData> { unsafe { let scbuf = NewJSAutoStructuredCloneBuffer( StructuredCloneScope::DifferentProcess, @@ -275,7 +299,7 @@ impl StructuredCloneData { policy, &STRUCTURED_CLONE_CALLBACKS, ptr::null_mut(), - HandleValue::undefined(), + transfer, ); if !result { JS_ClearPendingException(cx); @@ -306,7 +330,12 @@ impl StructuredCloneData { /// Reads a structured clone. /// /// Panics if `JS_ReadStructuredClone` fails. - fn read_clone(global: &GlobalScope, data: *mut u64, nbytes: size_t, rval: MutableHandleValue) { + fn read_clone( + global: &GlobalScope, + data: *mut u64, + nbytes: size_t, + rval: MutableHandleValue, + ) -> bool { let cx = global.get_cx(); let _ac = enter_realm(&*global); let mut sc_holder = StructuredCloneHolder { blob: None }; @@ -320,31 +349,33 @@ impl StructuredCloneData { WriteBytesToJSStructuredCloneData(data as *const u8, nbytes, scdata); - assert!(JS_ReadStructuredClone( - *cx, - scdata, - JS_STRUCTURED_CLONE_VERSION, - StructuredCloneScope::DifferentProcess, - rval, - &STRUCTURED_CLONE_CALLBACKS, - sc_holder_ptr as *mut raw::c_void - )); + let result = JS_ReadStructuredClone( + *cx, + scdata, + JS_STRUCTURED_CLONE_VERSION, + StructuredCloneScope::DifferentProcess, + rval, + &STRUCTURED_CLONE_CALLBACKS, + sc_holder_ptr as *mut raw::c_void + ); DeleteJSAutoStructuredCloneBuffer(scbuf); + + result } } /// Thunk for the actual `read_clone` method. Resolves proper variant for read_clone. - pub fn read(self, global: &GlobalScope, rval: MutableHandleValue) { + pub fn read(self, global: &GlobalScope, rval: MutableHandleValue) -> bool { match self { StructuredCloneData::Vector(mut vec_msg) => { let nbytes = vec_msg.len(); let data = vec_msg.as_mut_ptr() as *mut u64; - StructuredCloneData::read_clone(global, data, nbytes, rval); - }, + StructuredCloneData::read_clone(global, data, nbytes, rval) + } StructuredCloneData::Struct(data, nbytes) => { StructuredCloneData::read_clone(global, data, nbytes, rval) - }, + } } } } diff --git a/components/script/dom/bindings/transferable.rs b/components/script/dom/bindings/transferable.rs new file mode 100644 index 00000000000..fcf3ad93ab8 --- /dev/null +++ b/components/script/dom/bindings/transferable.rs @@ -0,0 +1,29 @@ +/* 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/. */ + +//! Trait representing the concept of [transferable objects] +//! (https://html.spec.whatwg.org/multipage/#transferable-objects). +use crate::dom::bindings::reflector::DomObject; +use js::jsapi::{JSContext, JSStructuredCloneReader, MutableHandleObject}; +use std::os::raw; + +pub trait Transferable : DomObject { + fn transfer( + &self, + closure: *mut raw::c_void, + content: *mut *mut raw::c_void, + extra_data: *mut u64, + ) -> bool; + fn transfer_receive( + cx: *mut JSContext, + r: *mut JSStructuredCloneReader, + closure: *mut raw::c_void, + content: *mut raw::c_void, + extra_data: u64, + return_object: MutableHandleObject, + ) -> bool; + fn detached(&self) -> Option<bool> { None } + fn set_detached(&self, _value: bool) { } + fn transferable(&self) -> bool { false } +} diff --git a/components/script/dom/dedicatedworkerglobalscope.rs b/components/script/dom/dedicatedworkerglobalscope.rs index a3adde75498..d0e8ba8258d 100644 --- a/components/script/dom/dedicatedworkerglobalscope.rs +++ b/components/script/dom/dedicatedworkerglobalscope.rs @@ -462,13 +462,20 @@ impl DedicatedWorkerGlobalScope { fn handle_script_event(&self, msg: WorkerScriptMsg) { match msg { - WorkerScriptMsg::DOMMessage(data) => { + WorkerScriptMsg::DOMMessage { origin, data } => { let scope = self.upcast::<WorkerGlobalScope>(); let target = self.upcast(); let _ac = enter_realm(self); rooted!(in(*scope.get_cx()) let mut message = UndefinedValue()); - data.read(scope.upcast(), message.handle_mut()); - MessageEvent::dispatch_jsval(target, scope.upcast(), message.handle(), None, None); + assert!(data.read(scope.upcast(), message.handle_mut())); + MessageEvent::dispatch_jsval( + target, + scope.upcast(), + message.handle(), + Some(&origin), + None, + vec![], + ); }, WorkerScriptMsg::Common(msg) => { self.upcast::<WorkerGlobalScope>().process_event(msg); @@ -562,11 +569,13 @@ unsafe extern "C" fn interrupt_callback(cx: *mut JSContext) -> bool { impl DedicatedWorkerGlobalScopeMethods for DedicatedWorkerGlobalScope { // https://html.spec.whatwg.org/multipage/#dom-dedicatedworkerglobalscope-postmessage fn PostMessage(&self, cx: SafeJSContext, message: HandleValue) -> ErrorResult { - let data = StructuredCloneData::write(*cx, message)?; + rooted!(in(*cx) let transfer = UndefinedValue()); + let data = StructuredCloneData::write(*cx, message, transfer.handle())?; let worker = self.worker.borrow().as_ref().unwrap().clone(); - let pipeline_id = self.upcast::<GlobalScope>().pipeline_id(); + let pipeline_id = self.global().pipeline_id(); + let origin = self.global().origin().immutable().ascii_serialization(); let task = Box::new(task!(post_worker_message: move || { - Worker::handle_message(worker, data); + Worker::handle_message(worker, origin, data); })); // TODO: Change this task source to a new `unshipped-port-message-queue` task source self.parent_sender diff --git a/components/script/dom/dissimilaroriginwindow.rs b/components/script/dom/dissimilaroriginwindow.rs index 8f7eda58100..31bb13051f7 100644 --- a/components/script/dom/dissimilaroriginwindow.rs +++ b/components/script/dom/dissimilaroriginwindow.rs @@ -150,7 +150,8 @@ impl DissimilarOriginWindowMethods for DissimilarOriginWindow { // Step 1-2, 6-8. // TODO(#12717): Should implement the `transfer` argument. - let data = StructuredCloneData::write(*cx, message)?; + rooted!(in(*cx) let transfer = UndefinedValue()); + let data = StructuredCloneData::write(*cx, message, transfer.handle())?; // Step 9. self.post_message(origin, data); diff --git a/components/script/dom/eventsource.rs b/components/script/dom/eventsource.rs index 92eb2c2f414..57d8eaf8ff2 100644 --- a/components/script/dom/eventsource.rs +++ b/components/script/dom/eventsource.rs @@ -236,6 +236,7 @@ impl EventSourceContext { DOMString::from(self.origin.clone()), None, event_source.last_event_id.borrow().clone(), + vec![], ) }; // Step 7 diff --git a/components/script/dom/globalscope.rs b/components/script/dom/globalscope.rs index c7635e98463..f1b6c0000b9 100644 --- a/components/script/dom/globalscope.rs +++ b/components/script/dom/globalscope.rs @@ -33,6 +33,7 @@ use crate::task_source::dom_manipulation::DOMManipulationTaskSource; use crate::task_source::file_reading::FileReadingTaskSource; use crate::task_source::networking::NetworkingTaskSource; use crate::task_source::performance_timeline::PerformanceTimelineTaskSource; +use crate::task_source::port_message::PortMessageQueue; use crate::task_source::remote_event::RemoteEventTaskSource; use crate::task_source::websocket::WebsocketTaskSource; use crate::task_source::TaskSourceName; @@ -504,7 +505,7 @@ impl GlobalScope { unreachable!(); } - /// `ScriptChan` to send messages to the networking task source of + /// `TaskSource` to send messages to the networking task source of /// this global scope. pub fn networking_task_source(&self) -> NetworkingTaskSource { if let Some(window) = self.downcast::<Window>() { @@ -516,7 +517,19 @@ impl GlobalScope { unreachable!(); } - /// `ScriptChan` to send messages to the remote-event task source of + /// `TaskSource` to send messages to the port message queue of + /// this global scope. + pub fn port_message_queue(&self) -> PortMessageQueue { + if let Some(window) = self.downcast::<Window>() { + return window.task_manager().port_message_queue(); + } + if let Some(worker) = self.downcast::<WorkerGlobalScope>() { + return worker.port_message_queue(); + } + unreachable!(); + } + + /// `TaskSource` to send messages to the remote-event task source of /// this global scope. pub fn remote_event_task_source(&self) -> RemoteEventTaskSource { if let Some(window) = self.downcast::<Window>() { @@ -528,7 +541,7 @@ impl GlobalScope { unreachable!(); } - /// `ScriptChan` to send messages to the websocket task source of + /// `TaskSource` to send messages to the websocket task source of /// this global scope. pub fn websocket_task_source(&self) -> WebsocketTaskSource { if let Some(window) = self.downcast::<Window>() { @@ -537,7 +550,7 @@ impl GlobalScope { if let Some(worker) = self.downcast::<WorkerGlobalScope>() { return worker.websocket_task_source(); } - unreachable!(); + unreachable!() } /// Evaluate JS code on this global scope. diff --git a/components/script/dom/history.rs b/components/script/dom/history.rs index 92c27dfb82d..05dff680c2c 100644 --- a/components/script/dom/history.rs +++ b/components/script/dom/history.rs @@ -185,7 +185,8 @@ impl History { // TODO: Step 4 // Step 5 - let serialized_data = StructuredCloneData::write(*cx, data)?.move_to_arraybuffer(); + rooted!(in(cx) let transfer = UndefinedValue()); + let serialized_data = StructuredCloneData::write(*cx, data, transfer.handle())?.move_to_arraybuffer(); let new_url: ServoUrl = match url { // Step 6 diff --git a/components/script/dom/messagechannel.rs b/components/script/dom/messagechannel.rs new file mode 100644 index 00000000000..e0d226d709a --- /dev/null +++ b/components/script/dom/messagechannel.rs @@ -0,0 +1,60 @@ +/* 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/. */ + +use crate::dom::bindings::codegen::Bindings::MessageChannelBinding::{MessageChannelMethods, Wrap}; +use crate::dom::bindings::error::{Error, Fallible}; +use crate::dom::bindings::reflector::{Reflector, reflect_dom_object}; +use crate::dom::bindings::root::{Dom, DomRoot}; +use crate::dom::globalscope::GlobalScope; +use crate::dom::messageport::MessagePort; +use dom_struct::dom_struct; + +#[dom_struct] +pub struct MessageChannel { + reflector_: Reflector, + port1: Dom<MessagePort>, + port2: Dom<MessagePort>, +} + +impl MessageChannel { + /// <https://html.spec.whatwg.org/multipage/#dom-messagechannel> + pub fn Constructor(global: &GlobalScope) -> Fallible<DomRoot<MessageChannel>> { + let incumbent = GlobalScope::incumbent().ok_or(Error::InvalidState)?; + + // Step 1 + let port1 = MessagePort::new(&incumbent); + + // Step 2 + let port2 = MessagePort::new(&incumbent); + + // Step 3 + port1.entangle(&port2); + + // Steps 4-6 + let channel = reflect_dom_object(Box::new( + MessageChannel { + reflector_: Reflector::new(), + port1: Dom::from_ref(&port1), + port2: Dom::from_ref(&port2), + }), + global, + Wrap + ); + + // Step 7 + Ok(channel) + } +} + +impl MessageChannelMethods for MessageChannel { + /// <https://html.spec.whatwg.org/multipage/#dom-messagechannel-port1> + fn Port1(&self) -> DomRoot<MessagePort> { + DomRoot::from_ref(&*self.port1) + } + + /// <https://html.spec.whatwg.org/multipage/#dom-messagechannel-port2> + fn Port2(&self) -> DomRoot<MessagePort> { + DomRoot::from_ref(&*self.port2) + } +} diff --git a/components/script/dom/messageevent.rs b/components/script/dom/messageevent.rs index 52c0bd1acf1..8d002923814 100644 --- a/components/script/dom/messageevent.rs +++ b/components/script/dom/messageevent.rs @@ -14,11 +14,14 @@ use crate::dom::bindings::trace::RootedTraceableBox; use crate::dom::event::Event; use crate::dom::eventtarget::EventTarget; use crate::dom::globalscope::GlobalScope; +use crate::dom::messageport::MessagePort; use crate::dom::windowproxy::WindowProxy; use crate::script_runtime::JSContext; use dom_struct::dom_struct; -use js::jsapi::{Heap, JSObject}; -use js::jsval::JSVal; +use js::conversions::ToJSValConvertible; +use js::jsapi::{Heap, JS_FreezeObject, JSContext, JSObject}; +use js::jsapi::HandleObject as RawHandleObject; +use js::jsval::{JSVal, UndefinedValue}; use js::rust::HandleValue; use servo_atoms::Atom; use std::ptr::NonNull; @@ -31,6 +34,7 @@ pub struct MessageEvent { origin: DOMString, source: Option<Dom<WindowProxy>>, lastEventId: DOMString, + ports: Vec<DomRoot<MessagePort>>, } impl MessageEvent { @@ -41,6 +45,7 @@ impl MessageEvent { DOMString::new(), None, DOMString::new(), + vec![], ) } @@ -50,13 +55,15 @@ impl MessageEvent { origin: DOMString, source: Option<&WindowProxy>, lastEventId: DOMString, + ports: Vec<DomRoot<MessagePort>>, ) -> DomRoot<MessageEvent> { let ev = Box::new(MessageEvent { event: Event::new_inherited(), data: Heap::default(), - origin: origin, source: source.map(Dom::from_ref), - lastEventId: lastEventId, + origin, + lastEventId, + ports, }); let ev = reflect_dom_object(ev, global, MessageEventBinding::Wrap); ev.data.set(data.get()); @@ -73,8 +80,9 @@ impl MessageEvent { origin: DOMString, source: Option<&WindowProxy>, lastEventId: DOMString, + ports: Vec<DomRoot<MessagePort>>, ) -> DomRoot<MessageEvent> { - let ev = MessageEvent::new_initialized(global, data, origin, source, lastEventId); + let ev = MessageEvent::new_initialized(global, data, origin, source, lastEventId, ports); { let event = ev.upcast::<Event>(); event.init_event(type_, bubbles, cancelable); @@ -100,6 +108,7 @@ impl MessageEvent { init.origin.clone(), source.as_ref().map(|source| &**source), init.lastEventId.clone(), + init.ports.clone().unwrap_or(vec![]) ); Ok(ev) } @@ -112,6 +121,7 @@ impl MessageEvent { message: HandleValue, origin: Option<&str>, source: Option<&WindowProxy>, + ports: Vec<DomRoot<MessagePort>>, ) { let messageevent = MessageEvent::new( scope, @@ -122,18 +132,19 @@ impl MessageEvent { DOMString::from(origin.unwrap_or("")), source, DOMString::new(), + ports, ); messageevent.upcast::<Event>().fire(target); } } impl MessageEventMethods for MessageEvent { - // https://html.spec.whatwg.org/multipage/#dom-messageevent-data + /// <https://html.spec.whatwg.org/multipage/#dom-messageevent-data> fn Data(&self, _cx: JSContext) -> JSVal { self.data.get() } - // https://html.spec.whatwg.org/multipage/#dom-messageevent-origin + /// <https://html.spec.whatwg.org/multipage/#dom-messageevent-origin> fn Origin(&self) -> DOMString { self.origin.clone() } @@ -145,13 +156,24 @@ impl MessageEventMethods for MessageEvent { .and_then(|source| NonNull::new(source.reflector().get_jsobject().get())) } - // https://html.spec.whatwg.org/multipage/#dom-messageevent-lasteventid + /// <https://html.spec.whatwg.org/multipage/#dom-messageevent-lasteventid> fn LastEventId(&self) -> DOMString { self.lastEventId.clone() } - // https://dom.spec.whatwg.org/#dom-event-istrusted + /// <https://dom.spec.whatwg.org/#dom-event-istrusted> fn IsTrusted(&self) -> bool { self.event.IsTrusted() } + + #[allow(unsafe_code)] + /// <https://html.spec.whatwg.org/multipage/#dom-messageevent-ports> + unsafe fn Ports(&self, cx: *mut JSContext) -> JSVal { + rooted!(in(cx) let mut ports = UndefinedValue()); + self.ports.to_jsval(cx, ports.handle_mut()); + + rooted!(in(cx) let obj = ports.to_object()); + JS_FreezeObject(cx, RawHandleObject::from(obj.handle())); + *ports + } } diff --git a/components/script/dom/messageport.rs b/components/script/dom/messageport.rs new file mode 100644 index 00000000000..10489f6d2ad --- /dev/null +++ b/components/script/dom/messageport.rs @@ -0,0 +1,395 @@ +/* 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/. */ + +use crate::dom::bindings::codegen::Bindings::EventHandlerBinding::EventHandlerNonNull; +use crate::dom::bindings::codegen::Bindings::MessagePortBinding::{MessagePortMethods, Wrap}; +use crate::dom::bindings::conversions::{ToJSValConvertible, root_from_object}; +use crate::dom::bindings::error::{Error, ErrorResult}; +use crate::dom::bindings::inheritance::{Castable, HasParent}; +use crate::dom::bindings::refcounted::Trusted; +use crate::dom::bindings::reflector::{DomObject, reflect_dom_object}; +use crate::dom::bindings::root::DomRoot; +use crate::dom::bindings::structuredclone::StructuredCloneData; +use crate::dom::bindings::trace::JSTraceable; +use crate::dom::bindings::transferable::Transferable; +use crate::dom::eventtarget::EventTarget; +use crate::dom::globalscope::GlobalScope; +use crate::dom::messageevent::MessageEvent; +use crate::task_source::TaskSource; +use crate::task_source::port_message::PortMessageQueue; +use js::jsapi::{JSContext, JSStructuredCloneReader, JSObject, JSTracer, MutableHandleObject}; +use js::jsval::UndefinedValue; +use js::rust::{CustomAutoRooterGuard, HandleValue}; +use servo_remutex::ReentrantMutex; +use std::cell::{Cell, RefCell}; +use std::collections::VecDeque; +use std::mem; +use std::os::raw; +use std::rc::Rc; +use std::sync::Arc; + +// FIXME: This is wrong, we need to figure out a better way of collecting message port objects per transfer +thread_local! { + pub static TRANSFERRED_MESSAGE_PORTS: RefCell<Vec<DomRoot<MessagePort>>> = RefCell::new(Vec::new()) +} + +struct PortMessageTask { + origin: String, + data: Vec<u8>, +} + +pub struct MessagePortInternal { + dom_port: RefCell<Option<Trusted<MessagePort>>>, + port_message_queue: RefCell<PortMessageQueue>, + enabled: Cell<bool>, + has_been_shipped: Cell<bool>, + entangled_port: RefCell<Option<Arc<ReentrantMutex<MessagePortInternal>>>>, + pending_port_messages: RefCell<VecDeque<PortMessageTask>>, +} + +impl MessagePortInternal { + fn new(port_message_queue: PortMessageQueue) -> MessagePortInternal { + MessagePortInternal { + dom_port: RefCell::new(None), + port_message_queue: RefCell::new(port_message_queue), + enabled: Cell::new(false), + has_been_shipped: Cell::new(false), + entangled_port: RefCell::new(None), + pending_port_messages: RefCell::new(VecDeque::new()), + } + } + + /// <https://html.spec.whatwg.org/multipage/#dom-messageport-postmessage> + // Step 7 substeps + #[allow(unrooted_must_root)] + fn process_pending_port_messages(&self) { + let PortMessageTask { origin, data } = match self.pending_port_messages.borrow_mut().pop_front() { + Some(task) => task, + None => return, + }; + + // Substep 1 + let final_target_port = self.dom_port.borrow().as_ref().unwrap().root(); + + // Substep 2 + let target_global = final_target_port.global(); + + // Substep 3-4 + rooted!(in(target_global.get_cx()) let mut message_clone = UndefinedValue()); + let deserialize_result = StructuredCloneData::Vector(data).read( + &target_global, + message_clone.handle_mut() + ); + if !deserialize_result { + return; + } + + // Substep 5 + let new_ports = TRANSFERRED_MESSAGE_PORTS.with(|list| { + mem::replace(&mut *list.borrow_mut(), vec![]) + }); + + // Substep 6 + MessageEvent::dispatch_jsval( + final_target_port.upcast(), + &target_global, + message_clone.handle(), + Some(&origin), + None, + new_ports, + ); + } +} + +#[derive(DenyPublicFields, DomObject, MallocSizeOf)] +#[must_root] +#[repr(C)] +pub struct MessagePort { + eventtarget: EventTarget, + detached: Cell<bool>, + #[ignore_malloc_size_of = "Defined in std"] + message_port_internal: Arc<ReentrantMutex<MessagePortInternal>>, +} + +#[allow(unsafe_code)] +unsafe impl JSTraceable for MessagePort { + unsafe fn trace(&self, trc: *mut JSTracer) { + if !self.detached.get() { + self.eventtarget.trace(trc); + } + // Otherwise, do nothing. + } +} + +impl HasParent for MessagePort { + type Parent = EventTarget; + + fn as_parent(&self) -> &EventTarget { + &self.eventtarget + } +} + +impl MessagePort { + fn new_inherited(global: &GlobalScope) -> MessagePort { + MessagePort { + eventtarget: EventTarget::new_inherited(), + detached: Cell::new(false), + message_port_internal: Arc::new( + ReentrantMutex::new( + MessagePortInternal::new(global.port_message_queue().clone()) + ) + ), + } + } + + fn new_transferred(message_port_internal: Arc<ReentrantMutex<MessagePortInternal>>) -> MessagePort { + MessagePort { + eventtarget: EventTarget::new_inherited(), + detached: Cell::new(false), + message_port_internal, + } + } + + /// <https://html.spec.whatwg.org/multipage/#create-a-new-messageport-object> + pub fn new(owner: &GlobalScope) -> DomRoot<MessagePort> { + let message_port = reflect_dom_object(Box::new(MessagePort::new_inherited(owner)), owner, Wrap); + { + let internal = message_port.message_port_internal.lock().unwrap(); + *internal.dom_port.borrow_mut() = Some(Trusted::new(&*message_port)); + } + message_port + } + + /// <https://html.spec.whatwg.org/multipage/#entangle> + pub fn entangle(&self, other: &MessagePort) { + { + let internal = self.message_port_internal.lock().unwrap(); + *internal.entangled_port.borrow_mut() = Some(other.message_port_internal.clone()); + } + let internal = other.message_port_internal.lock().unwrap(); + *internal.entangled_port.borrow_mut() = Some(self.message_port_internal.clone()); + } + + /// <https://html.spec.whatwg.org/multipage/#dom-messageport-postmessage> + // Step 7 substeps + fn process_pending_port_messages(&self) { + if self.detached.get() { return; } + let internal = self.message_port_internal.lock().unwrap(); + internal.process_pending_port_messages(); + } +} + +impl Transferable for MessagePort { + /// <https://html.spec.whatwg.org/multipage/#message-ports:transfer-steps> + #[allow(unsafe_code)] + fn transfer( + &self, + _closure: *mut raw::c_void, + content: *mut *mut raw::c_void, + extra_data: *mut u64 + ) -> bool { + { + let internal = self.message_port_internal.lock().unwrap(); + // Step 1 + internal.has_been_shipped.set(true); + + // Step 3 + if let Some(ref other_port) = *internal.entangled_port.borrow() { + let entangled_internal = other_port.lock().unwrap(); + // Substep 1 + entangled_internal.has_been_shipped.set(true); + }; // This line MUST contain a semicolon, due to the strict drop check rule + } + + unsafe { + // Steps 2, 3.2 and 4 + *content = Arc::into_raw(self.message_port_internal.clone()) as *mut raw::c_void; + + *extra_data = 0; + } + + true + } + + /// https://html.spec.whatwg.org/multipage/#message-ports:transfer-receiving-steps + #[allow(unrooted_must_root, unsafe_code)] + fn transfer_receive( + cx: *mut JSContext, + _r: *mut JSStructuredCloneReader, + _closure: *mut raw::c_void, + content: *mut raw::c_void, + _extra_data: u64, + return_object: MutableHandleObject + ) -> bool { + let internal = unsafe { Arc::from_raw(content as *const ReentrantMutex<MessagePortInternal>) }; + let value = MessagePort::new_transferred(internal); + + // Step 2 + let owner = unsafe { GlobalScope::from_context(cx) }; + let message_port = reflect_dom_object(Box::new(value), &*owner, Wrap); + + { + let internal = message_port.message_port_internal.lock().unwrap(); + + // Step 1 + internal.has_been_shipped.set(true); + + let dom_port = Trusted::new(&*message_port); + internal.enabled.set(false); + *internal.dom_port.borrow_mut() = Some(dom_port); + *internal.port_message_queue.borrow_mut() = owner.port_message_queue().clone(); + } + return_object.set(message_port.reflector().rootable().get()); + TRANSFERRED_MESSAGE_PORTS.with(|list| { + list.borrow_mut().push(message_port); + }); + + true + } + + fn detached(&self) -> Option<bool> { + Some(self.detached.get()) + } + + fn set_detached(&self, value: bool) { + self.detached.set(value); + } + + fn transferable(&self) -> bool { + !self.detached.get() + } +} + +impl MessagePortMethods for MessagePort { + #[allow(unsafe_code)] + /// <https://html.spec.whatwg.org/multipage/#dom-messageport-postmessage> + unsafe fn PostMessage( + &self, + cx: *mut JSContext, + message: HandleValue, + transfer: CustomAutoRooterGuard<Option<Vec<*mut JSObject>>>, + ) -> ErrorResult { + if self.detached.get() { return Ok(()); } + let internal = self.message_port_internal.lock().unwrap(); + // Step 1 + let target_port = internal.entangled_port.borrow(); + + // Step 3 + let mut doomed = false; + + rooted!(in(cx) let mut val = UndefinedValue()); + let transfer = match *transfer { + Some(ref vec) => { + let ports = vec.iter().filter_map(|&obj| root_from_object::<MessagePort>(obj).ok()); + for port in ports { + // Step 2 + if Arc::ptr_eq(&port.message_port_internal, &self.message_port_internal) { + return Err(Error::DataClone); + } + + // Step 4 + if let Some(target) = target_port.as_ref() { + if Arc::ptr_eq(&port.message_port_internal, target) { + doomed = true; + } + } + } + + vec.to_jsval(cx, val.handle_mut()); + val + } + None => { + Vec::<*mut JSObject>::new().to_jsval(cx, val.handle_mut()); + val + } + }; + + // Step 5 + let data = StructuredCloneData::write(cx, message, transfer.handle())?.move_to_arraybuffer(); + + // Step 6 + if target_port.is_none() || doomed { return Ok(()); } + + // Step 7 + let task = PortMessageTask { + origin: self.global().origin().immutable().ascii_serialization(), + data, + }; + + { + let target_port = target_port.as_ref().unwrap(); + let target_internal = target_port.lock().unwrap(); + target_internal.pending_port_messages.borrow_mut().push_back(task); + + if target_internal.enabled.get() { + let target_port = target_port.clone(); + let _ = target_internal.port_message_queue.borrow().queue( + task!(process_pending_port_messages: move || { + let internal = target_port.lock().unwrap(); + internal.process_pending_port_messages(); + }), + &self.global() + ); + } + } + + Ok(()) + } + + /// <https://html.spec.whatwg.org/multipage/#dom-messageport-start> + fn Start(&self) { + let len = { + let internal = self.message_port_internal.lock().unwrap(); + if internal.enabled.get() { + return; + } + internal.enabled.set(true); + let messages = internal.pending_port_messages.borrow(); + messages.len() + }; + + let global = self.global(); + for _ in 0..len { + let port = Trusted::new(self); + let _ = global.port_message_queue().queue( + task!(process_pending_port_messages: move || { + let this = port.root(); + this.process_pending_port_messages(); + }), + &global + ); + } + } + + /// <https://html.spec.whatwg.org/multipage/#dom-messageport-close> + fn Close(&self) { + // Step 1 + self.detached.set(true); + + // Step 2 + let maybe_port = { + let internal = self.message_port_internal.lock().unwrap(); + let mut maybe_port = internal.entangled_port.borrow_mut(); + maybe_port.take() + }; + + if let Some(other) = maybe_port { + let other_internal = other.lock().unwrap(); + *other_internal.entangled_port.borrow_mut() = None; + } + } + + /// <https://html.spec.whatwg.org/multipage/#handler-messageport-onmessage> + fn GetOnmessage(&self) -> Option<Rc<EventHandlerNonNull>> { + let eventtarget = self.upcast::<EventTarget>(); + eventtarget.get_event_handler_common("message") + } + + /// <https://html.spec.whatwg.org/multipage/#handler-messageport-onmessage> + fn SetOnmessage(&self, listener: Option<Rc<EventHandlerNonNull>>) { + self.Start(); + let eventtarget = self.upcast::<EventTarget>(); + eventtarget.set_event_handler_common("message", listener) + } +} diff --git a/components/script/dom/mod.rs b/components/script/dom/mod.rs index aa7400db1b6..302ae5baad4 100644 --- a/components/script/dom/mod.rs +++ b/components/script/dom/mod.rs @@ -401,7 +401,9 @@ pub mod mediaquerylist; pub mod mediaquerylistevent; pub mod mediastream; pub mod mediastreamtrack; +pub mod messagechannel; pub mod messageevent; +pub mod messageport; pub mod mimetype; pub mod mimetypearray; pub mod mouseevent; diff --git a/components/script/dom/serviceworker.rs b/components/script/dom/serviceworker.rs index dd968c34d94..8d33337f1c5 100644 --- a/components/script/dom/serviceworker.rs +++ b/components/script/dom/serviceworker.rs @@ -19,6 +19,8 @@ use crate::dom::globalscope::GlobalScope; use crate::script_runtime::JSContext; use crate::task::TaskOnce; use dom_struct::dom_struct; +use js::jsapi::JSContext; +use js::jsval::UndefinedValue; use js::rust::HandleValue; use script_traits::{DOMMessage, ScriptMsg}; use servo_url::ServoUrl; @@ -97,14 +99,18 @@ impl ServiceWorkerMethods for ServiceWorker { return Err(Error::InvalidState); } // Step 7 - let data = StructuredCloneData::write(*cx, message)?; - let msg_vec = DOMMessage(data.move_to_arraybuffer()); + rooted!(in(*cx) let transfer = UndefinedValue()); + let data = StructuredCloneData::write(*cx, message, transfer.handle())?; + let msg_vec = DOMMessage { + origin: self.global().origin().immutable().ascii_serialization(), + data: data.move_to_arraybuffer(), + }; let _ = self .global() .script_to_constellation_chan() .send(ScriptMsg::ForwardDOMMessage( msg_vec, - self.scope_url.clone(), + self.scope_url.clone() )); Ok(()) } diff --git a/components/script/dom/serviceworkerglobalscope.rs b/components/script/dom/serviceworkerglobalscope.rs index 5af7f07ab0f..8daf3b6dc2f 100644 --- a/components/script/dom/serviceworkerglobalscope.rs +++ b/components/script/dom/serviceworkerglobalscope.rs @@ -409,12 +409,12 @@ impl ServiceWorkerGlobalScope { use self::ServiceWorkerScriptMsg::*; match msg { - CommonWorker(WorkerScriptMsg::DOMMessage(data)) => { + CommonWorker(WorkerScriptMsg::DOMMessage { data, .. }) => { let scope = self.upcast::<WorkerGlobalScope>(); let target = self.upcast(); let _ac = enter_realm(&*scope); rooted!(in(*scope.get_cx()) let mut message = UndefinedValue()); - data.read(scope.upcast(), message.handle_mut()); + assert!(data.read(scope.upcast(), message.handle_mut())); ExtendableMessageEvent::dispatch_jsval(target, scope.upcast(), message.handle()); }, CommonWorker(WorkerScriptMsg::Common(msg)) => { diff --git a/components/script/dom/webidls/MessageChannel.webidl b/components/script/dom/webidls/MessageChannel.webidl new file mode 100644 index 00000000000..97baba289b8 --- /dev/null +++ b/components/script/dom/webidls/MessageChannel.webidl @@ -0,0 +1,14 @@ +/* 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/. */ +/* + * The origin of this IDL file is: + * https://html.spec.whatwg.org/multipage/#messagechannel + */ + +[Exposed=(Window,Worker)] +interface MessageChannel { + [Throws] constructor(); + readonly attribute MessagePort port1; + readonly attribute MessagePort port2; +}; diff --git a/components/script/dom/webidls/MessageEvent.webidl b/components/script/dom/webidls/MessageEvent.webidl index e5b91a043f3..63dd9019cc9 100644 --- a/components/script/dom/webidls/MessageEvent.webidl +++ b/components/script/dom/webidls/MessageEvent.webidl @@ -12,7 +12,7 @@ interface MessageEvent : Event { // FIXME(#22617): WindowProxy is not exposed in Worker globals readonly attribute object? source; //readonly attribute (WindowProxy or MessagePort)? source; - //readonly attribute MessagePort[]? ports; + readonly attribute /*FrozenArray<MessagePort>*/any ports; }; dictionary MessageEventInit : EventInit { @@ -22,5 +22,7 @@ dictionary MessageEventInit : EventInit { //DOMString channel; Window? source; //(WindowProxy or MessagePort)? source; - //sequence<MessagePort> ports; + sequence<MessagePort> ports; }; + +typedef (/*WindowProxy or */MessagePort or ServiceWorker) MessageEventSource; diff --git a/components/script/dom/webidls/MessagePort.webidl b/components/script/dom/webidls/MessagePort.webidl new file mode 100644 index 00000000000..c00eba5291a --- /dev/null +++ b/components/script/dom/webidls/MessagePort.webidl @@ -0,0 +1,17 @@ +/* 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/. */ +/* + * The origin of this IDL file is: + * https://html.spec.whatwg.org/multipage/#messageport + */ + +[Exposed=(Window,Worker)] +interface MessagePort : EventTarget { + [Throws] void postMessage(any message, optional sequence<object> transfer /*= []*/); + void start(); + void close(); + + // event handlers + attribute EventHandler onmessage; +}; diff --git a/components/script/dom/webidls/Window.webidl b/components/script/dom/webidls/Window.webidl index 65944aa3eb1..a4af9bf692b 100644 --- a/components/script/dom/webidls/Window.webidl +++ b/components/script/dom/webidls/Window.webidl @@ -63,9 +63,8 @@ unsigned long requestAnimationFrame(FrameRequestCallback callback); void cancelAnimationFrame(unsigned long handle); - //void postMessage(any message, DOMString targetOrigin, optional sequence<Transferable> transfer); [Throws] - void postMessage(any message, DOMString targetOrigin); + void postMessage(any message, USVString targetOrigin, optional sequence<object> transfer /*= []*/); // also has obsolete members }; diff --git a/components/script/dom/websocket.rs b/components/script/dom/websocket.rs index 3051cc0ff33..fcdd63e2594 100644 --- a/components/script/dom/websocket.rs +++ b/components/script/dom/websocket.rs @@ -598,6 +598,7 @@ impl TaskOnce for MessageReceivedTask { message.handle(), Some(&ws.origin().ascii_serialization()), None, + vec![], ); } } diff --git a/components/script/dom/window.rs b/components/script/dom/window.rs index 565308ff36f..7f3b6d1fb8d 100644 --- a/components/script/dom/window.rs +++ b/components/script/dom/window.rs @@ -17,6 +17,7 @@ use crate::dom::bindings::codegen::Bindings::WindowBinding::{ }; use crate::dom::bindings::codegen::Bindings::WindowBinding::{ScrollBehavior, ScrollToOptions}; use crate::dom::bindings::codegen::UnionTypes::RequestOrUSVString; +use crate::dom::bindings::conversions::ToJSValConvertible; use crate::dom::bindings::error::{Error, ErrorResult, Fallible}; use crate::dom::bindings::inheritance::Castable; use crate::dom::bindings::num::Finite; @@ -43,6 +44,7 @@ use crate::dom::location::Location; use crate::dom::mediaquerylist::{MediaQueryList, MediaQueryListMatchState}; use crate::dom::mediaquerylistevent::MediaQueryListEvent; use crate::dom::messageevent::MessageEvent; +use crate::dom::messageport::TRANSFERRED_MESSAGE_PORTS; use crate::dom::navigator::Navigator; use crate::dom::node::{document_from_node, from_untrusted_node_address, Node, NodeDamage}; use crate::dom::performance::Performance; @@ -80,12 +82,13 @@ use euclid::{Point2D, Rect, Scale, Size2D, Vector2D}; use ipc_channel::ipc::{channel, IpcSender}; use ipc_channel::router::ROUTER; use js::jsapi::JSAutoRealm; +use js::jsapi::JSObject; use js::jsapi::JSPROP_ENUMERATE; use js::jsapi::{GCReason, JS_GC}; use js::jsval::UndefinedValue; use js::jsval::{JSVal, NullValue}; use js::rust::wrappers::JS_DefineProperty; -use js::rust::HandleValue; +use js::rust::{CustomAutoRooterGuard, HandleValue}; use media::WindowGLContext; use msg::constellation_msg::PipelineId; use net_traits::image_cache::{ImageCache, ImageResponder, ImageResponse}; @@ -969,12 +972,18 @@ impl WindowMethods for Window { } // https://html.spec.whatwg.org/multipage/#dom-window-postmessage - fn PostMessage(&self, cx: JSContext, message: HandleValue, origin: DOMString) -> ErrorResult { + fn PostMessage( + &self, + cx: JSContext, + message: HandleValue, + origin: USVString, + transfer: CustomAutoRooterGuard<Option<Vec<*mut JSObject>>>, + ) -> ErrorResult { let source_global = GlobalScope::incumbent().expect("no incumbent global??"); let source = source_global.as_window(); // Step 3-5. - let origin = match &origin[..] { + let origin = match &origin.0[..] { "*" => None, "/" => Some(source.Document().origin().immutable().clone()), url => match ServoUrl::parse(&url) { @@ -984,8 +993,10 @@ impl WindowMethods for Window { }; // Step 1-2, 6-8. - // TODO(#12717): Should implement the `transfer` argument. - let data = StructuredCloneData::write(*cx, message)?; + rooted!(in(*cx) let mut val = UndefinedValue()); + (*transfer).as_ref().unwrap_or(&Vec::new()).to_jsval(*cx, val.handle_mut()); + + let data = StructuredCloneData::write(*cx, message, val.handle())?; // Step 9. self.post_message(origin, &*source.window_proxy(), data); @@ -2342,10 +2353,11 @@ impl Window { let task = task!(post_serialised_message: move || { let this = this.root(); let source = source.root(); + let document = this.Document(); // Step 7.1. - if let Some(target_origin) = target_origin { - if !target_origin.same_origin(this.Document().origin()) { + if let Some(ref target_origin) = target_origin { + if !target_origin.same_origin(document.origin()) { return; } } @@ -2355,13 +2367,15 @@ impl Window { let obj = this.reflector().get_jsobject(); let _ac = JSAutoRealm::new(*cx, obj.get()); rooted!(in(*cx) let mut message_clone = UndefinedValue()); - serialize_with_transfer_result.read( + assert!(serialize_with_transfer_result.read( this.upcast(), message_clone.handle_mut(), - ); + )); // Step 7.6. - // TODO: MessagePort array. + let new_ports = TRANSFERRED_MESSAGE_PORTS.with(|list| { + mem::replace(&mut *list.borrow_mut(), vec![]) + }); // Step 7.7. // TODO(#12719): Set the other attributes. @@ -2369,8 +2383,9 @@ impl Window { this.upcast(), this.upcast(), message_clone.handle(), - None, + Some(&document.origin().immutable().ascii_serialization()), Some(&*source), + new_ports, ); }); // FIXME(nox): Why are errors silenced here? diff --git a/components/script/dom/worker.rs b/components/script/dom/worker.rs index 1ae17571fbb..1a8d9c292dc 100644 --- a/components/script/dom/worker.rs +++ b/components/script/dom/worker.rs @@ -137,7 +137,11 @@ impl Worker { self.terminated.get() } - pub fn handle_message(address: TrustedWorkerAddress, data: StructuredCloneData) { + pub fn handle_message( + address: TrustedWorkerAddress, + origin: String, + data: StructuredCloneData, + ) { let worker = address.root(); if worker.is_terminated() { @@ -148,8 +152,8 @@ impl Worker { let target = worker.upcast(); let _ac = enter_realm(target); rooted!(in(*global.get_cx()) let mut message = UndefinedValue()); - data.read(&global, message.handle_mut()); - MessageEvent::dispatch_jsval(target, &global, message.handle(), None, None); + assert!(data.read(&global, message.handle_mut())); + MessageEvent::dispatch_jsval(target, &global, message.handle(), Some(&origin), None, vec![]); } pub fn dispatch_simple_error(address: TrustedWorkerAddress) { @@ -161,14 +165,18 @@ impl Worker { impl WorkerMethods for Worker { // https://html.spec.whatwg.org/multipage/#dom-worker-postmessage fn PostMessage(&self, cx: JSContext, message: HandleValue) -> ErrorResult { - let data = StructuredCloneData::write(*cx, message)?; + rooted!(in(*cx) let transfer = UndefinedValue()); + let data = StructuredCloneData::write(*cx, message, transfer.handle())?; let address = Trusted::new(self); // NOTE: step 9 of https://html.spec.whatwg.org/multipage/#dom-messageport-postmessage // indicates that a nonexistent communication channel should result in a silent error. let _ = self.sender.send(DedicatedWorkerScriptMsg::CommonWorker( address, - WorkerScriptMsg::DOMMessage(data), + WorkerScriptMsg::DOMMessage { + origin: self.global().origin().immutable().ascii_serialization(), + data, + }, )); Ok(()) } diff --git a/components/script/dom/workerglobalscope.rs b/components/script/dom/workerglobalscope.rs index 4ca2ad59d74..50cb8bb5aaf 100644 --- a/components/script/dom/workerglobalscope.rs +++ b/components/script/dom/workerglobalscope.rs @@ -33,6 +33,7 @@ use crate::task_source::dom_manipulation::DOMManipulationTaskSource; use crate::task_source::file_reading::FileReadingTaskSource; use crate::task_source::networking::NetworkingTaskSource; use crate::task_source::performance_timeline::PerformanceTimelineTaskSource; +use crate::task_source::port_message::PortMessageQueue; use crate::task_source::remote_event::RemoteEventTaskSource; use crate::task_source::websocket::WebsocketTaskSource; use crate::timers::{IsInterval, TimerCallback}; @@ -457,6 +458,10 @@ impl WorkerGlobalScope { PerformanceTimelineTaskSource(self.script_chan(), self.pipeline_id()) } + pub fn port_message_queue(&self) -> PortMessageQueue { + PortMessageQueue(self.script_chan(), self.pipeline_id()) + } + pub fn remote_event_task_source(&self) -> RemoteEventTaskSource { RemoteEventTaskSource(self.script_chan(), self.pipeline_id()) } |