diff options
author | Gregory Terzian <gterzian@users.noreply.github.com> | 2019-06-26 00:25:48 +0800 |
---|---|---|
committer | Gregory Terzian <gterzian@users.noreply.github.com> | 2019-10-19 14:28:18 +0800 |
commit | 2f8932a6a1e2666567435114383b3acd1899aca7 (patch) | |
tree | 8490d198d1c22015afeae84ad25f21ecca462415 /components/script | |
parent | c3b17c1201441c9a24c4b272108aea0196fbf1b9 (diff) | |
download | servo-2f8932a6a1e2666567435114383b3acd1899aca7.tar.gz servo-2f8932a6a1e2666567435114383b3acd1899aca7.zip |
continue messageport, transferable, postmessage options
Diffstat (limited to 'components/script')
32 files changed, 1457 insertions, 671 deletions
diff --git a/components/script/Cargo.toml b/components/script/Cargo.toml index efef253317c..cb30f437a11 100644 --- a/components/script/Cargo.toml +++ b/components/script/Cargo.toml @@ -34,6 +34,7 @@ tinyfiledialogs = "3.0" app_units = "0.7" backtrace = {version = "0.3", optional = true} base64 = "0.10.1" +bincode = "1" bitflags = "1.0" bluetooth_traits = {path = "../bluetooth_traits"} canvas_traits = {path = "../canvas_traits"} @@ -97,7 +98,6 @@ servo_config = {path = "../config"} servo_geometry = {path = "../geometry" } servo-media = {git = "https://github.com/servo/media"} servo_rand = {path = "../rand"} -servo_remutex = {path = "../remutex"} servo_url = {path = "../url"} sparkle = "0.1" smallvec = { version = "0.6", features = ["std", "union"] } diff --git a/components/script/dom/abstractworker.rs b/components/script/dom/abstractworker.rs index 930f48cdcea..36ff18fe01b 100644 --- a/components/script/dom/abstractworker.rs +++ b/components/script/dom/abstractworker.rs @@ -4,8 +4,9 @@ use crate::dom::bindings::refcounted::Trusted; use crate::dom::bindings::reflector::DomObject; -use crate::dom::bindings::structuredclone::StructuredCloneData; use crate::script_runtime::CommonScriptMsg; +use script_traits::StructuredSerializedData; +use servo_url::ImmutableOrigin; /// Messages used to control the worker event loops pub enum WorkerScriptMsg { @@ -13,9 +14,9 @@ pub enum WorkerScriptMsg { Common(CommonScriptMsg), /// Message sent through Worker.postMessage DOMMessage { - origin: String, - data: StructuredCloneData, - } + origin: ImmutableOrigin, + data: StructuredSerializedData, + }, } pub struct SimpleWorkerErrorHandler<T: DomObject> { diff --git a/components/script/dom/abstractworkerglobalscope.rs b/components/script/dom/abstractworkerglobalscope.rs index 6ffeba85d5e..e8e41375b70 100644 --- a/components/script/dom/abstractworkerglobalscope.rs +++ b/components/script/dom/abstractworkerglobalscope.rs @@ -155,4 +155,7 @@ pub fn run_worker_event_loop<T, TimerMsg, WorkerMsg, Event>( .upcast::<GlobalScope>() .perform_a_microtask_checkpoint(); } + worker_scope + .upcast::<GlobalScope>() + .perform_a_message_port_garbage_collection_checkpoint(); } diff --git a/components/script/dom/bindings/codegen/Bindings.conf b/components/script/dom/bindings/codegen/Bindings.conf index e3de60005bc..3ff7a044b53 100644 --- a/components/script/dom/bindings/codegen/Bindings.conf +++ b/components/script/dom/bindings/codegen/Bindings.conf @@ -30,6 +30,10 @@ DOMInterfaces = { 'weakReferenceable': True, }, +'MessagePort': { + 'weakReferenceable': True, +}, + #FIXME(jdm): This should be 'register': False, but then we don't generate enum types 'TestBinding': { 'inCompartments': ['PromiseAttribute', 'PromiseNativeHandler'], diff --git a/components/script/dom/bindings/structuredclone.rs b/components/script/dom/bindings/structuredclone.rs index 2e33613b29d..25c440365c8 100644 --- a/components/script/dom/bindings/structuredclone.rs +++ b/components/script/dom/bindings/structuredclone.rs @@ -6,7 +6,7 @@ //! (https://html.spec.whatwg.org/multipage/#safe-passing-of-structured-data). use crate::compartments::enter_realm; -use crate::dom::bindings::conversions::root_from_handleobject; +use crate::dom::bindings::conversions::{root_from_object, ToJSValConvertible}; use crate::dom::bindings::error::{Error, Fallible}; use crate::dom::bindings::reflector::DomObject; use crate::dom::bindings::root::DomRoot; @@ -14,6 +14,7 @@ use crate::dom::bindings::transferable::Transferable; use crate::dom::blob::{Blob, BlobImpl}; use crate::dom::globalscope::GlobalScope; use crate::dom::messageport::MessagePort; +use crate::script_runtime::JSContext as SafeJSContext; use js::glue::CopyJSStructuredCloneData; use js::glue::DeleteJSAutoStructuredCloneBuffer; use js::glue::GetLengthOfJSStructuredCloneData; @@ -30,12 +31,15 @@ use js::jsapi::{JSObject, JS_ClearPendingException}; use js::jsapi::{JSStructuredCloneCallbacks, JSStructuredCloneReader, JSStructuredCloneWriter}; use js::jsapi::{JS_ReadBytes, JS_WriteBytes}; use js::jsapi::{JS_ReadUint32Pair, JS_WriteUint32Pair}; +use js::jsval::UndefinedValue; use js::rust::wrappers::{JS_ReadStructuredClone, JS_WriteStructuredClone}; -use js::rust::{Handle, HandleValue, MutableHandleValue}; -use libc::size_t; +use js::rust::{CustomAutoRooterGuard, HandleValue, MutableHandleValue}; +use msg::constellation_msg::MessagePortId; +use script_traits::transferable::MessagePortImpl; +use script_traits::StructuredSerializedData; +use std::collections::HashMap; use std::os::raw; use std::ptr; -use std::slice; // TODO: Should we add Min and Max const to https://github.com/servo/rust-mozjs/blob/master/src/consts.rs? // TODO: Determine for sure which value Min and Max should have. @@ -129,19 +133,24 @@ impl StructuredCloneReader { unsafe fn read_blob( cx: *mut JSContext, r: *mut JSStructuredCloneReader, - sc_holder: &mut StructuredCloneHolder, + sc_holder: &mut StructuredDataHolder, ) -> *mut JSObject { let structured_reader = StructuredCloneReader { r: r }; let blob_buffer = structured_reader.read_bytes(); let type_str = structured_reader.read_str(); let target_global = GlobalScope::from_context(cx); - let blob = Blob::new( + let read_blob = Blob::new( &target_global, BlobImpl::new_from_bytes(blob_buffer), type_str, ); - let js_object = blob.reflector().get_jsobject().get(); - sc_holder.blob = Some(blob); + let js_object = read_blob.reflector().get_jsobject().get(); + match sc_holder { + StructuredDataHolder::Read { blob, .. } => { + *blob = Some(read_blob); + }, + _ => panic!("Unexpected variant of StructuredDataHolder"), + } js_object } @@ -174,7 +183,7 @@ unsafe extern "C" fn read_callback( "tag should be higher than StructuredCloneTags::Min" ); if tag == StructuredCloneTags::DomBlob as u32 { - return read_blob(cx, r, &mut *(closure as *mut StructuredCloneHolder)); + return read_blob(cx, r, &mut *(closure as *mut StructuredDataHolder)); } return ptr::null_mut(); } @@ -185,7 +194,7 @@ unsafe extern "C" fn write_callback( obj: RawHandleObject, _closure: *mut raw::c_void, ) -> bool { - if let Ok(blob) = root_from_handleobject::<Blob>(Handle::from_raw(obj), cx) { + if let Ok(blob) = root_from_object::<Blob>(*obj, cx) { return write_blob(blob, w).is_ok(); } return false; @@ -193,39 +202,44 @@ unsafe extern "C" fn write_callback( unsafe extern "C" fn read_transfer_callback( cx: *mut JSContext, - r: *mut JSStructuredCloneReader, + _r: *mut JSStructuredCloneReader, tag: u32, - content: *mut raw::c_void, + _content: *mut raw::c_void, extra_data: u64, closure: *mut raw::c_void, return_object: RawMutableHandleObject, ) -> bool { if tag == StructuredCloneTags::MessagePort as u32 { - <MessagePort as Transferable>::transfer_receive(cx, r, closure, content, extra_data, return_object) - } else { - false + let mut sc_holder = &mut *(closure as *mut StructuredDataHolder); + let owner = GlobalScope::from_context(cx); + if let Ok(_) = <MessagePort as Transferable>::transfer_receive( + &owner, + &mut sc_holder, + extra_data, + return_object, + ) { + return true; + } } + false } /// <https://html.spec.whatwg.org/multipage/#structuredserializewithtransfer> unsafe extern "C" fn write_transfer_callback( - _cx: *mut JSContext, + cx: *mut JSContext, obj: RawHandleObject, closure: *mut raw::c_void, tag: *mut u32, ownership: *mut TransferableOwnership, - content: *mut *mut raw::c_void, + _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; - } - + if let Ok(port) = root_from_object::<MessagePort>(*obj, cx) { *tag = StructuredCloneTags::MessagePort as u32; *ownership = TransferableOwnership::SCTAG_TMO_CUSTOM; - if port.transfer(closure, content, extra_data) { - port.set_detached(true); + let mut sc_holder = &mut *(closure as *mut StructuredDataHolder); + if let Ok(data) = port.transfer(&mut sc_holder) { + *extra_data = data; return true; } } @@ -242,10 +256,13 @@ unsafe extern "C" fn free_transfer_callback( } unsafe extern "C" fn can_transfer_callback( - _cx: *mut JSContext, - _obj: RawHandleObject, + cx: *mut JSContext, + obj: RawHandleObject, _closure: *mut raw::c_void, ) -> bool { + if let Ok(_port) = root_from_object::<MessagePort>(*obj, cx) { + return true; + } false } @@ -261,123 +278,143 @@ static STRUCTURED_CLONE_CALLBACKS: JSStructuredCloneCallbacks = JSStructuredClon canTransfer: Some(can_transfer_callback), }; -struct StructuredCloneHolder { - blob: Option<DomRoot<Blob>>, +/// A data holder for results from, and inputs to, structured-data read/write operations. +/// https://html.spec.whatwg.org/multipage/#safe-passing-of-structured-data +pub enum StructuredDataHolder { + Read { + /// A deserialized blob, stored temporarily here to keep it rooted. + blob: Option<DomRoot<Blob>>, + /// A vec of transfer-received DOM ports, + /// to be made available to script through a message event. + message_ports: Option<Vec<DomRoot<MessagePort>>>, + /// A map of port implementations, + /// used as part of the "transfer-receiving" steps of ports, + /// to produce the DOM ports stored in `message_ports` above. + port_impls: Option<HashMap<MessagePortId, MessagePortImpl>>, + }, + /// A data holder into which transferred ports + /// can be written as part of their transfer steps. + Write(Option<HashMap<MessagePortId, MessagePortImpl>>), } -/// A buffer for a structured clone. -pub enum StructuredCloneData { - /// A non-serializable (default) variant - Struct(*mut u64, size_t), - /// A variant that can be serialized - Vector(Vec<u8>), -} +/// Writes a structured clone. Returns a `DataClone` error if that fails. +pub fn write( + cx: SafeJSContext, + message: HandleValue, + transfer: Option<CustomAutoRooterGuard<Vec<*mut JSObject>>>, +) -> Fallible<StructuredSerializedData> { + unsafe { + rooted!(in(*cx) let mut val = UndefinedValue()); + if let Some(transfer) = transfer { + transfer.to_jsval(*cx, val.handle_mut()); + } -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, - transfer: HandleValue, - ) -> Fallible<StructuredCloneData> { - unsafe { - let scbuf = NewJSAutoStructuredCloneBuffer( - StructuredCloneScope::DifferentProcess, - &STRUCTURED_CLONE_CALLBACKS, - ); - let scdata = &mut ((*scbuf).data_); - let policy = CloneDataPolicy { - // TODO: SAB? - sharedArrayBuffer_: false, - }; - let result = JS_WriteStructuredClone( - cx, - message, - scdata, - StructuredCloneScope::DifferentProcess, - policy, - &STRUCTURED_CLONE_CALLBACKS, - ptr::null_mut(), - transfer, - ); - if !result { - JS_ClearPendingException(cx); - return Err(Error::DataClone); - } + let mut sc_holder = StructuredDataHolder::Write(None); + let sc_holder_ptr = &mut sc_holder as *mut _; - let nbytes = GetLengthOfJSStructuredCloneData(scdata); - let mut data = Vec::with_capacity(nbytes); - CopyJSStructuredCloneData(scdata, data.as_mut_ptr()); - data.set_len(nbytes); + let scbuf = NewJSAutoStructuredCloneBuffer( + StructuredCloneScope::DifferentProcess, + &STRUCTURED_CLONE_CALLBACKS, + ); + let scdata = &mut ((*scbuf).data_); + let policy = CloneDataPolicy { + // TODO: SAB? + sharedArrayBuffer_: false, + }; + let result = JS_WriteStructuredClone( + *cx, + message, + scdata, + StructuredCloneScope::DifferentProcess, + policy, + &STRUCTURED_CLONE_CALLBACKS, + sc_holder_ptr as *mut raw::c_void, + val.handle(), + ); + if !result { + JS_ClearPendingException(*cx); + return Err(Error::DataClone); + } - DeleteJSAutoStructuredCloneBuffer(scbuf); + let nbytes = GetLengthOfJSStructuredCloneData(scdata); + let mut data = Vec::with_capacity(nbytes); + CopyJSStructuredCloneData(scdata, data.as_mut_ptr()); + data.set_len(nbytes); - Ok(StructuredCloneData::Vector(data)) - } - } + DeleteJSAutoStructuredCloneBuffer(scbuf); - /// Converts a StructuredCloneData to Vec<u8> for inter-thread sharing - pub fn move_to_arraybuffer(self) -> Vec<u8> { - match self { - StructuredCloneData::Struct(data, nbytes) => unsafe { - slice::from_raw_parts(data as *mut u8, nbytes).to_vec() - }, - StructuredCloneData::Vector(msg) => msg, - } - } + let mut port_impls = match sc_holder { + StructuredDataHolder::Write(port_impls) => port_impls, + _ => panic!("Unexpected variant of StructuredDataHolder"), + }; - /// Reads a structured clone. - /// - /// Panics if `JS_ReadStructuredClone` fails. - 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 }; - let sc_holder_ptr = &mut sc_holder as *mut _; - unsafe { - let scbuf = NewJSAutoStructuredCloneBuffer( - StructuredCloneScope::DifferentProcess, - &STRUCTURED_CLONE_CALLBACKS, - ); - let scdata = &mut ((*scbuf).data_); - - WriteBytesToJSStructuredCloneData(data as *const u8, nbytes, scdata); - - 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 - } + let data = StructuredSerializedData { + serialized: data, + ports: port_impls.take(), + }; + + Ok(data) } +} - /// Thunk for the actual `read_clone` method. Resolves proper variant for read_clone. - 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::Struct(data, nbytes) => { - StructuredCloneData::read_clone(global, data, nbytes, rval) +/// Read structured serialized data, possibly containing transferred objects. +/// Returns a vec of rooted transfer-received ports, or an error. +pub fn read( + global: &GlobalScope, + mut data: StructuredSerializedData, + rval: MutableHandleValue, +) -> Result<Vec<DomRoot<MessagePort>>, ()> { + let cx = global.get_cx(); + let _ac = enter_realm(&*global); + let mut sc_holder = StructuredDataHolder::Read { + blob: None, + message_ports: None, + port_impls: data.ports.take(), + }; + let sc_holder_ptr = &mut sc_holder as *mut _; + unsafe { + let scbuf = NewJSAutoStructuredCloneBuffer( + StructuredCloneScope::DifferentProcess, + &STRUCTURED_CLONE_CALLBACKS, + ); + let scdata = &mut ((*scbuf).data_); + + WriteBytesToJSStructuredCloneData( + data.serialized.as_mut_ptr() as *const u8, + data.serialized.len(), + scdata, + ); + + 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); + + if result { + let (mut message_ports, port_impls) = match sc_holder { + StructuredDataHolder::Read { + message_ports, + port_impls, + .. + } => (message_ports, port_impls), + _ => panic!("Unexpected variant of StructuredDataHolder"), + }; + + // Any transfer-received port-impls should have been taken out. + assert!(port_impls.is_none()); + + match message_ports.take() { + Some(ports) => return Ok(ports), + None => return Ok(Vec::with_capacity(0)), } } + Err(()) } } - -unsafe impl Send for StructuredCloneData {} diff --git a/components/script/dom/bindings/trace.rs b/components/script/dom/bindings/trace.rs index a914b8161a6..4b79bc7085b 100644 --- a/components/script/dom/bindings/trace.rs +++ b/components/script/dom/bindings/trace.rs @@ -79,7 +79,8 @@ use media::WindowGLContext; use metrics::{InteractiveMetrics, InteractiveWindow}; use mime::Mime; use msg::constellation_msg::{ - BrowsingContextId, HistoryStateId, PipelineId, TopLevelBrowsingContextId, + BrowsingContextId, HistoryStateId, MessagePortId, MessagePortRouterId, PipelineId, + TopLevelBrowsingContextId, }; use net_traits::filemanager_thread::RelativePos; use net_traits::image::base::{Image, ImageMetadata}; @@ -93,6 +94,7 @@ use profile_traits::mem::ProfilerChan as MemProfilerChan; use profile_traits::time::ProfilerChan as TimeProfilerChan; use script_layout_interface::rpc::LayoutRPC; use script_layout_interface::OpaqueStyleAndLayoutData; +use script_traits::transferable::MessagePortImpl; use script_traits::DrawAPaintImageResult; use script_traits::{DocumentActivity, ScriptToConstellationChan, TimerEventId, TimerSource}; use script_traits::{UntrustedNodeAddress, WindowSizeData, WindowSizeType}; @@ -154,6 +156,11 @@ pub unsafe trait JSTraceable { unsafe_no_jsmanaged_fields!(Box<dyn TaskBox>, Box<dyn EventLoopWaker>); +unsafe_no_jsmanaged_fields!(MessagePortImpl); +unsafe_no_jsmanaged_fields!(MessagePortId); +unsafe_no_jsmanaged_fields!(RefCell<Option<MessagePortId>>); +unsafe_no_jsmanaged_fields!(MessagePortRouterId); + unsafe_no_jsmanaged_fields!(CSSError); unsafe_no_jsmanaged_fields!(&'static Encoding); diff --git a/components/script/dom/bindings/transferable.rs b/components/script/dom/bindings/transferable.rs index fcf3ad93ab8..fb2f4a991a2 100644 --- a/components/script/dom/bindings/transferable.rs +++ b/components/script/dom/bindings/transferable.rs @@ -4,26 +4,19 @@ //! 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; +use crate::dom::bindings::root::DomRoot; +use crate::dom::bindings::structuredclone::StructuredDataHolder; +use crate::dom::globalscope::GlobalScope; +use js::jsapi::MutableHandleObject; -pub trait Transferable : DomObject { - fn transfer( - &self, - closure: *mut raw::c_void, - content: *mut *mut raw::c_void, - extra_data: *mut u64, - ) -> bool; +pub trait Transferable: DomObject { + fn transfer(&self, sc_holder: &mut StructuredDataHolder) -> Result<u64, ()>; fn transfer_receive( - cx: *mut JSContext, - r: *mut JSStructuredCloneReader, - closure: *mut raw::c_void, - content: *mut raw::c_void, + owner: &DomRoot<GlobalScope>, + sc_holder: &mut StructuredDataHolder, extra_data: u64, return_object: MutableHandleObject, - ) -> bool; - fn detached(&self) -> Option<bool> { None } - fn set_detached(&self, _value: bool) { } - fn transferable(&self) -> bool { false } + ) -> Result<(), ()>; } diff --git a/components/script/dom/bindings/utils.rs b/components/script/dom/bindings/utils.rs index 7146e1533b7..ef3b270b130 100644 --- a/components/script/dom/bindings/utils.rs +++ b/components/script/dom/bindings/utils.rs @@ -10,10 +10,13 @@ use crate::dom::bindings::codegen::PrototypeList::{MAX_PROTO_CHAIN_LENGTH, PROTO use crate::dom::bindings::conversions::{jsstring_to_str, private_from_proto_check}; use crate::dom::bindings::error::throw_invalid_this; use crate::dom::bindings::inheritance::TopTypeId; +use crate::dom::bindings::root::DomRoot; use crate::dom::bindings::str::DOMString; use crate::dom::bindings::trace::trace_object; +use crate::dom::messageport::MessagePort; use crate::dom::windowproxy; use crate::script_runtime::JSContext as SafeJSContext; +use js::conversions::ToJSValConvertible; use js::glue::{CallJitGetterOp, CallJitMethodOp, CallJitSetterOp, IsWrapper}; use js::glue::{GetCrossCompartmentWrapper, JS_GetReservedSlot, WrapperNew}; use js::glue::{UnwrapObjectDynamic, RUST_JSID_TO_INT, RUST_JSID_TO_STRING}; @@ -22,7 +25,7 @@ use js::jsapi::HandleId as RawHandleId; use js::jsapi::HandleObject as RawHandleObject; use js::jsapi::MutableHandleObject as RawMutableHandleObject; use js::jsapi::{AutoIdVector, CallArgs, DOMCallbacks, GetNonCCWObjectGlobal}; -use js::jsapi::{Heap, JSAutoRealm, JSContext}; +use js::jsapi::{Heap, JSAutoRealm, JSContext, JS_FreezeObject}; use js::jsapi::{JSJitInfo, JSObject, JSTracer, JSWrapObjectCallbacks}; use js::jsapi::{JS_EnumerateStandardClasses, JS_GetLatin1StringCharsAndLength}; use js::jsapi::{JS_IsExceptionPending, JS_IsGlobalObject}; @@ -117,6 +120,19 @@ impl Clone for DOMJSClass { } unsafe impl Sync for DOMJSClass {} +/// Returns a JSVal representing a frozen array of ports +pub fn message_ports_to_frozen_array( + message_ports: &[DomRoot<MessagePort>], + cx: SafeJSContext, +) -> JSVal { + rooted!(in(*cx) let mut ports = UndefinedValue()); + unsafe { message_ports.to_jsval(*cx, ports.handle_mut()) }; + + rooted!(in(*cx) let obj = ports.to_object()); + unsafe { JS_FreezeObject(*cx, RawHandleObject::from(obj.handle())) }; + *ports +} + /// Returns the ProtoOrIfaceArray for the given global object. /// Fails if `global` is not a DOM global object. pub fn get_proto_or_iface_array(global: *mut JSObject) -> *mut ProtoOrIfaceArray { diff --git a/components/script/dom/dedicatedworkerglobalscope.rs b/components/script/dom/dedicatedworkerglobalscope.rs index d0e8ba8258d..3745ef9f1ac 100644 --- a/components/script/dom/dedicatedworkerglobalscope.rs +++ b/components/script/dom/dedicatedworkerglobalscope.rs @@ -10,13 +10,15 @@ use crate::dom::abstractworkerglobalscope::{SendableWorkerScriptChan, WorkerThre use crate::dom::bindings::cell::DomRefCell; use crate::dom::bindings::codegen::Bindings::DedicatedWorkerGlobalScopeBinding; use crate::dom::bindings::codegen::Bindings::DedicatedWorkerGlobalScopeBinding::DedicatedWorkerGlobalScopeMethods; +use crate::dom::bindings::codegen::Bindings::MessagePortBinding::PostMessageOptions; use crate::dom::bindings::codegen::Bindings::WorkerBinding::WorkerType; use crate::dom::bindings::error::{ErrorInfo, ErrorResult}; use crate::dom::bindings::inheritance::Castable; use crate::dom::bindings::reflector::DomObject; use crate::dom::bindings::root::{DomRoot, RootCollection, ThreadLocalStackRoots}; use crate::dom::bindings::str::DOMString; -use crate::dom::bindings::structuredclone::StructuredCloneData; +use crate::dom::bindings::structuredclone; +use crate::dom::bindings::trace::RootedTraceableBox; use crate::dom::errorevent::ErrorEvent; use crate::dom::event::{Event, EventBubbles, EventCancelable, EventStatus}; use crate::dom::eventtarget::EventTarget; @@ -36,10 +38,10 @@ use devtools_traits::DevtoolScriptControlMsg; use dom_struct::dom_struct; use ipc_channel::ipc::{self, IpcReceiver, IpcSender}; use ipc_channel::router::ROUTER; -use js::jsapi::JSContext; use js::jsapi::JS_AddInterruptCallback; +use js::jsapi::{Heap, JSContext, JSObject}; use js::jsval::UndefinedValue; -use js::rust::HandleValue; +use js::rust::{CustomAutoRooter, CustomAutoRooterGuard, HandleValue}; use msg::constellation_msg::{PipelineId, TopLevelBrowsingContextId}; use net_traits::image_cache::ImageCache; use net_traits::request::{CredentialsMode, Destination, ParserMetadata}; @@ -467,15 +469,19 @@ impl DedicatedWorkerGlobalScope { let target = self.upcast(); let _ac = enter_realm(self); rooted!(in(*scope.get_cx()) let mut message = UndefinedValue()); - assert!(data.read(scope.upcast(), message.handle_mut())); - MessageEvent::dispatch_jsval( - target, - scope.upcast(), - message.handle(), - Some(&origin), - None, - vec![], - ); + if let Ok(ports) = structuredclone::read(scope.upcast(), data, message.handle_mut()) + { + MessageEvent::dispatch_jsval( + target, + scope.upcast(), + message.handle(), + Some(&origin.ascii_serialization()), + None, + ports, + ); + } else { + MessageEvent::dispatch_error(target, scope.upcast()); + } }, WorkerScriptMsg::Common(msg) => { self.upcast::<WorkerGlobalScope>().process_event(msg); @@ -554,30 +560,22 @@ impl DedicatedWorkerGlobalScope { )) .unwrap(); } -} - -#[allow(unsafe_code)] -unsafe extern "C" fn interrupt_callback(cx: *mut JSContext) -> bool { - let worker = DomRoot::downcast::<WorkerGlobalScope>(GlobalScope::from_context(cx)) - .expect("global is not a worker scope"); - assert!(worker.is::<DedicatedWorkerGlobalScope>()); - - // A false response causes the script to terminate - !worker.is_closing() -} -impl DedicatedWorkerGlobalScopeMethods for DedicatedWorkerGlobalScope { // https://html.spec.whatwg.org/multipage/#dom-dedicatedworkerglobalscope-postmessage - fn PostMessage(&self, cx: SafeJSContext, message: HandleValue) -> ErrorResult { - rooted!(in(*cx) let transfer = UndefinedValue()); - let data = StructuredCloneData::write(*cx, message, transfer.handle())?; + fn post_message_impl( + &self, + cx: SafeJSContext, + message: HandleValue, + transfer: CustomAutoRooterGuard<Vec<*mut JSObject>>, + ) -> ErrorResult { + let data = structuredclone::write(cx, message, Some(transfer))?; let worker = self.worker.borrow().as_ref().unwrap().clone(); - let pipeline_id = self.global().pipeline_id(); - let origin = self.global().origin().immutable().ascii_serialization(); + let global_scope = self.upcast::<GlobalScope>(); + let pipeline_id = global_scope.pipeline_id(); + let origin = global_scope.origin().immutable().ascii_serialization(); let task = Box::new(task!(post_worker_message: move || { Worker::handle_message(worker, origin, data); })); - // TODO: Change this task source to a new `unshipped-port-message-queue` task source self.parent_sender .send(CommonScriptMsg::Task( WorkerEvent, @@ -588,6 +586,48 @@ impl DedicatedWorkerGlobalScopeMethods for DedicatedWorkerGlobalScope { .unwrap(); Ok(()) } +} + +#[allow(unsafe_code)] +unsafe extern "C" fn interrupt_callback(cx: *mut JSContext) -> bool { + let worker = DomRoot::downcast::<WorkerGlobalScope>(GlobalScope::from_context(cx)) + .expect("global is not a worker scope"); + assert!(worker.is::<DedicatedWorkerGlobalScope>()); + + // A false response causes the script to terminate + !worker.is_closing() +} + +impl DedicatedWorkerGlobalScopeMethods for DedicatedWorkerGlobalScope { + /// https://html.spec.whatwg.org/multipage/#dom-dedicatedworkerglobalscope-postmessage + fn PostMessage( + &self, + cx: SafeJSContext, + message: HandleValue, + transfer: CustomAutoRooterGuard<Vec<*mut JSObject>>, + ) -> ErrorResult { + self.post_message_impl(cx, message, transfer) + } + + /// https://html.spec.whatwg.org/multipage/#dom-dedicatedworkerglobalscope-postmessage + fn PostMessage_( + &self, + cx: SafeJSContext, + message: HandleValue, + options: RootedTraceableBox<PostMessageOptions>, + ) -> ErrorResult { + let mut rooted = CustomAutoRooter::new( + options + .transfer + .as_ref() + .unwrap_or(&Vec::with_capacity(0)) + .iter() + .map(|js: &RootedTraceableBox<Heap<*mut JSObject>>| js.get()) + .collect(), + ); + let guard = CustomAutoRooterGuard::new(*cx, &mut rooted); + self.post_message_impl(cx, message, guard) + } // https://html.spec.whatwg.org/multipage/#dom-dedicatedworkerglobalscope-close fn Close(&self) { diff --git a/components/script/dom/dissimilaroriginwindow.rs b/components/script/dom/dissimilaroriginwindow.rs index 31bb13051f7..c628923604e 100644 --- a/components/script/dom/dissimilaroriginwindow.rs +++ b/components/script/dom/dissimilaroriginwindow.rs @@ -4,21 +4,23 @@ use crate::dom::bindings::codegen::Bindings::DissimilarOriginWindowBinding; use crate::dom::bindings::codegen::Bindings::DissimilarOriginWindowBinding::DissimilarOriginWindowMethods; +use crate::dom::bindings::codegen::Bindings::WindowBinding::WindowPostMessageOptions; use crate::dom::bindings::error::{Error, ErrorResult}; use crate::dom::bindings::root::{Dom, DomRoot, MutNullableDom}; -use crate::dom::bindings::str::DOMString; -use crate::dom::bindings::structuredclone::StructuredCloneData; +use crate::dom::bindings::str::USVString; +use crate::dom::bindings::structuredclone; +use crate::dom::bindings::trace::RootedTraceableBox; use crate::dom::dissimilaroriginlocation::DissimilarOriginLocation; use crate::dom::globalscope::GlobalScope; use crate::dom::windowproxy::WindowProxy; use crate::script_runtime::JSContext; use dom_struct::dom_struct; use ipc_channel::ipc; +use js::jsapi::{Heap, JSObject}; use js::jsval::{JSVal, UndefinedValue}; -use js::rust::HandleValue; +use js::rust::{CustomAutoRooter, CustomAutoRooterGuard, HandleValue}; use msg::constellation_msg::PipelineId; -use script_traits::ScriptMsg; -use servo_url::ImmutableOrigin; +use script_traits::{ScriptMsg, StructuredSerializedData}; use servo_url::ServoUrl; /// Represents a dissimilar-origin `Window` that exists in another script thread. @@ -133,29 +135,42 @@ impl DissimilarOriginWindowMethods for DissimilarOriginWindow { false } - // https://html.spec.whatwg.org/multipage/#dom-window-postmessage - fn PostMessage(&self, cx: JSContext, message: HandleValue, origin: DOMString) -> ErrorResult { - // Step 3-5. - let origin = match &origin[..] { - "*" => None, - "/" => { - // TODO: Should be the origin of the incumbent settings object. - None - }, - url => match ServoUrl::parse(&url) { - Ok(url) => Some(url.origin()), - Err(_) => return Err(Error::Syntax), - }, - }; + /// https://html.spec.whatwg.org/multipage/#dom-window-postmessage + fn PostMessage( + &self, + cx: JSContext, + message: HandleValue, + target_origin: USVString, + mut transfer: CustomAutoRooterGuard<Option<Vec<*mut JSObject>>>, + ) -> ErrorResult { + if transfer.is_some() { + let mut rooted = CustomAutoRooter::new(transfer.take().unwrap()); + let transfer = Some(CustomAutoRooterGuard::new(*cx, &mut rooted)); + self.post_message_impl(&target_origin, cx, message, transfer) + } else { + self.post_message_impl(&target_origin, cx, message, None) + } + } - // Step 1-2, 6-8. - // TODO(#12717): Should implement the `transfer` argument. - rooted!(in(*cx) let transfer = UndefinedValue()); - let data = StructuredCloneData::write(*cx, message, transfer.handle())?; + /// https://html.spec.whatwg.org/multipage/#dom-window-postmessage-options + fn PostMessage_( + &self, + cx: JSContext, + message: HandleValue, + options: RootedTraceableBox<WindowPostMessageOptions>, + ) -> ErrorResult { + let mut rooted = CustomAutoRooter::new( + options + .transfer + .as_ref() + .unwrap_or(&Vec::with_capacity(0)) + .iter() + .map(|js: &RootedTraceableBox<Heap<*mut JSObject>>| js.get()) + .collect(), + ); + let transfer = Some(CustomAutoRooterGuard::new(*cx, &mut rooted)); - // Step 9. - self.post_message(origin, data); - Ok(()) + self.post_message_impl(&options.targetOrigin, cx, message, transfer) } // https://html.spec.whatwg.org/multipage/#dom-opener @@ -187,17 +202,54 @@ impl DissimilarOriginWindowMethods for DissimilarOriginWindow { } impl DissimilarOriginWindow { - pub fn post_message(&self, origin: Option<ImmutableOrigin>, data: StructuredCloneData) { + /// https://html.spec.whatwg.org/multipage/#window-post-message-steps + fn post_message_impl( + &self, + target_origin: &USVString, + cx: JSContext, + message: HandleValue, + transfer: Option<CustomAutoRooterGuard<Vec<*mut JSObject>>>, + ) -> ErrorResult { + // Step 6-7. + let data = structuredclone::write(cx, message, transfer)?; + + self.post_message(target_origin, data) + } + + /// https://html.spec.whatwg.org/multipage/#window-post-message-steps + pub fn post_message( + &self, + target_origin: &USVString, + data: StructuredSerializedData, + ) -> ErrorResult { + // Step 1. + let target = self.window_proxy.browsing_context_id(); + // Step 2. let incumbent = match GlobalScope::incumbent() { - None => return warn!("postMessage called with no incumbent global"), + None => panic!("postMessage called with no incumbent global"), Some(incumbent) => incumbent, }; + + let source_origin = incumbent.origin().immutable().clone(); + + // Step 3-5. + let target_origin = match target_origin.0[..].as_ref() { + "*" => None, + "/" => Some(source_origin.clone()), + url => match ServoUrl::parse(&url) { + Ok(url) => Some(url.origin().clone()), + Err(_) => return Err(Error::Syntax), + }, + }; let msg = ScriptMsg::PostMessage { - target: self.window_proxy.browsing_context_id(), + target, source: incumbent.pipeline_id(), - target_origin: origin, - data: data.move_to_arraybuffer(), + source_origin, + target_origin, + data: data, }; + // Step 8 let _ = incumbent.script_to_constellation_chan().send(msg); + Ok(()) } } diff --git a/components/script/dom/document.rs b/components/script/dom/document.rs index 6581dbd54bb..ce88906deff 100644 --- a/components/script/dom/document.rs +++ b/components/script/dom/document.rs @@ -1917,10 +1917,11 @@ impl Document { } } } + + let global_scope = self.window.upcast::<GlobalScope>(); // Step 10, 14 + // https://html.spec.whatwg.org/multipage/#unloading-document-cleanup-steps if !self.salvageable.get() { - // https://html.spec.whatwg.org/multipage/#unloading-document-cleanup-steps - let global_scope = self.window.upcast::<GlobalScope>(); // Step 1 of clean-up steps. global_scope.close_event_sources(); let msg = ScriptMsg::DiscardDocument; diff --git a/components/script/dom/eventsource.rs b/components/script/dom/eventsource.rs index 57d8eaf8ff2..bbda6c24396 100644 --- a/components/script/dom/eventsource.rs +++ b/components/script/dom/eventsource.rs @@ -236,7 +236,7 @@ impl EventSourceContext { DOMString::from(self.origin.clone()), None, event_source.last_event_id.borrow().clone(), - vec![], + Vec::with_capacity(0), ) }; // Step 7 diff --git a/components/script/dom/extendablemessageevent.rs b/components/script/dom/extendablemessageevent.rs index c2ede65745a..dab06b7d79d 100644 --- a/components/script/dom/extendablemessageevent.rs +++ b/components/script/dom/extendablemessageevent.rs @@ -10,10 +10,12 @@ use crate::dom::bindings::reflector::reflect_dom_object; use crate::dom::bindings::root::DomRoot; use crate::dom::bindings::str::DOMString; use crate::dom::bindings::trace::RootedTraceableBox; +use crate::dom::bindings::utils::message_ports_to_frozen_array; use crate::dom::event::Event; use crate::dom::eventtarget::EventTarget; use crate::dom::extendableevent::ExtendableEvent; use crate::dom::globalscope::GlobalScope; +use crate::dom::messageport::MessagePort; use crate::dom::serviceworkerglobalscope::ServiceWorkerGlobalScope; use crate::script_runtime::JSContext; use dom_struct::dom_struct; @@ -29,6 +31,7 @@ pub struct ExtendableMessageEvent { data: Heap<JSVal>, origin: DOMString, lastEventId: DOMString, + ports: Vec<DomRoot<MessagePort>>, } impl ExtendableMessageEvent { @@ -40,12 +43,14 @@ impl ExtendableMessageEvent { data: HandleValue, origin: DOMString, lastEventId: DOMString, + ports: Vec<DomRoot<MessagePort>>, ) -> DomRoot<ExtendableMessageEvent> { let ev = Box::new(ExtendableMessageEvent { event: ExtendableEvent::new_inherited(), data: Heap::default(), - origin: origin, - lastEventId: lastEventId, + origin, + lastEventId, + ports, }); let ev = reflect_dom_object(ev, global, ExtendableMessageEventBinding::Wrap); { @@ -71,13 +76,19 @@ impl ExtendableMessageEvent { init.data.handle(), init.origin.clone().unwrap(), init.lastEventId.clone().unwrap(), + vec![], ); Ok(ev) } } impl ExtendableMessageEvent { - pub fn dispatch_jsval(target: &EventTarget, scope: &GlobalScope, message: HandleValue) { + pub fn dispatch_jsval( + target: &EventTarget, + scope: &GlobalScope, + message: HandleValue, + ports: Vec<DomRoot<MessagePort>>, + ) { let Extendablemessageevent = ExtendableMessageEvent::new( scope, atom!("message"), @@ -86,6 +97,7 @@ impl ExtendableMessageEvent { message, DOMString::new(), DOMString::new(), + ports, ); Extendablemessageevent.upcast::<Event>().fire(target); } @@ -111,4 +123,9 @@ impl ExtendableMessageEventMethods for ExtendableMessageEvent { fn IsTrusted(&self) -> bool { self.event.IsTrusted() } + + /// https://w3c.github.io/ServiceWorker/#extendablemessage-event-ports + fn Ports(&self, cx: JSContext) -> JSVal { + message_ports_to_frozen_array(self.ports.as_slice(), cx) + } } diff --git a/components/script/dom/globalscope.rs b/components/script/dom/globalscope.rs index f1b6c0000b9..7d07e67f9ec 100644 --- a/components/script/dom/globalscope.rs +++ b/components/script/dom/globalscope.rs @@ -9,17 +9,21 @@ use crate::dom::bindings::codegen::Bindings::WorkerGlobalScopeBinding::WorkerGlo use crate::dom::bindings::conversions::{root_from_object, root_from_object_static}; use crate::dom::bindings::error::{report_pending_exception, ErrorInfo}; use crate::dom::bindings::inheritance::Castable; +use crate::dom::bindings::refcounted::Trusted; use crate::dom::bindings::reflector::DomObject; use crate::dom::bindings::root::{DomRoot, MutNullableDom}; use crate::dom::bindings::settings_stack::{entry_global, incumbent_global, AutoEntryScript}; use crate::dom::bindings::str::DOMString; -use crate::dom::bindings::weakref::DOMTracker; +use crate::dom::bindings::structuredclone; +use crate::dom::bindings::weakref::{DOMTracker, WeakRef}; use crate::dom::crypto::Crypto; use crate::dom::dedicatedworkerglobalscope::DedicatedWorkerGlobalScope; use crate::dom::errorevent::ErrorEvent; use crate::dom::event::{Event, EventBubbles, EventCancelable, EventStatus}; use crate::dom::eventsource::EventSource; use crate::dom::eventtarget::EventTarget; +use crate::dom::messageevent::MessageEvent; +use crate::dom::messageport::MessagePort; use crate::dom::paintworkletglobalscope::PaintWorkletGlobalScope; use crate::dom::performance::Performance; use crate::dom::window::Window; @@ -36,34 +40,40 @@ 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::TaskSource; use crate::task_source::TaskSourceName; use crate::timers::{IsInterval, OneshotTimerCallback, OneshotTimerHandle}; use crate::timers::{OneshotTimers, TimerCallback}; use content_security_policy::CspList; use devtools_traits::{ScriptToDevtoolsControlMsg, WorkerId}; use dom_struct::dom_struct; -use ipc_channel::ipc::IpcSender; +use ipc_channel::ipc::{self, IpcSender}; +use ipc_channel::router::ROUTER; use js::glue::{IsWrapper, UnwrapObjectDynamic}; use js::jsapi::JSObject; use js::jsapi::{CurrentGlobalOrNull, GetNonCCWObjectGlobal}; use js::jsapi::{HandleObject, Heap}; use js::jsapi::{JSAutoRealm, JSContext}; +use js::jsval::UndefinedValue; use js::panic::maybe_resume_unwind; use js::rust::wrappers::EvaluateUtf8; use js::rust::{get_object_class, CompileOptionsWrapper, ParentRuntime, Runtime}; use js::rust::{HandleValue, MutableHandleValue}; use js::{JSCLASS_IS_DOMJSCLASS, JSCLASS_IS_GLOBAL}; -use msg::constellation_msg::PipelineId; +use msg::constellation_msg::{MessagePortId, MessagePortRouterId, PipelineId}; use net_traits::image_cache::ImageCache; use net_traits::{CoreResourceThread, IpcSend, ResourceThreads}; use profile_traits::{mem as profile_mem, time as profile_time}; -use script_traits::{MsDuration, ScriptToConstellationChan, TimerEvent}; +use script_traits::transferable::MessagePortImpl; +use script_traits::{ + MessagePortMsg, MsDuration, PortMessageTask, ScriptMsg, ScriptToConstellationChan, TimerEvent, +}; use script_traits::{TimerEventId, TimerSchedulerMsg, TimerSource}; use servo_url::{MutableOrigin, ServoUrl}; use std::borrow::Cow; use std::cell::Cell; use std::collections::hash_map::Entry; -use std::collections::HashMap; +use std::collections::{HashMap, VecDeque}; use std::ffi::CString; use std::rc::Rc; use std::sync::atomic::{AtomicBool, Ordering}; @@ -85,6 +95,9 @@ pub struct GlobalScope { crypto: MutNullableDom<Crypto>, next_worker_id: Cell<WorkerId>, + /// The message-port router id for this global, if it is managing ports. + message_port_state: DomRefCell<MessagePortState>, + /// Pipeline id associated with this global. pipeline_id: PipelineId, @@ -168,6 +181,77 @@ pub struct GlobalScope { user_agent: Cow<'static, str>, } +/// A wrapper for glue-code between the ipc router and the event-loop. +struct MessageListener { + canceller: TaskCanceller, + task_source: PortMessageQueue, + context: Trusted<GlobalScope>, +} + +/// Data representing a message-port managed by this global. +#[derive(JSTraceable, MallocSizeOf)] +pub enum ManagedMessagePort { + /// We keep ports pending when they are first transfer-received, + /// and only add them, and ask the constellation to complete the transfer, + /// in a subsequent task if the port hasn't been re-transfered. + Pending(MessagePortImpl, WeakRef<MessagePort>), + /// A port who was transferred into, or initially created in, this realm, + /// and that hasn't been re-transferred in the same task it was noted. + Added(MessagePortImpl, WeakRef<MessagePort>), +} + +/// State representing whether this global is currently managing messageports. +#[derive(JSTraceable, MallocSizeOf)] +pub enum MessagePortState { + /// The message-port router id for this global, and a map of managed ports. + Managed( + MessagePortRouterId, + HashMap<MessagePortId, ManagedMessagePort>, + ), + /// This global is not managing any ports at this time. + UnManaged, +} + +impl MessageListener { + /// A new message came in, handle it via a task enqueued on the event-loop. + /// A task is required, since we are using a trusted globalscope, + /// and we can only access the root from the event-loop. + fn notify(&self, msg: MessagePortMsg) { + match msg { + MessagePortMsg::CompleteTransfer(port_id, tasks) => { + let context = self.context.clone(); + let _ = self.task_source.queue_with_canceller( + task!(process_complete_transfer: move || { + let global = context.root(); + global.complete_port_transfer(port_id, tasks); + }), + &self.canceller, + ); + }, + MessagePortMsg::NewTask(port_id, task) => { + let context = self.context.clone(); + let _ = self.task_source.queue_with_canceller( + task!(process_new_task: move || { + let global = context.root(); + global.route_task_to_port(port_id, task); + }), + &self.canceller, + ); + }, + MessagePortMsg::RemoveMessagePort(port_id) => { + let context = self.context.clone(); + let _ = self.task_source.queue_with_canceller( + task!(process_remove_message_port: move || { + let global = context.root(); + global.remove_message_port(&port_id); + }), + &self.canceller, + ); + }, + } + } +} + impl GlobalScope { pub fn new_inherited( pipeline_id: PipelineId, @@ -184,6 +268,7 @@ impl GlobalScope { user_agent: Cow<'static, str>, ) -> Self { Self { + message_port_state: DomRefCell::new(MessagePortState::UnManaged), eventtarget: EventTarget::new_inherited(), crypto: Default::default(), next_worker_id: Cell::new(WorkerId(0)), @@ -209,6 +294,397 @@ impl GlobalScope { } } + /// Complete the transfer of a message-port. + fn complete_port_transfer(&self, port_id: MessagePortId, tasks: VecDeque<PortMessageTask>) { + let should_start = if let MessagePortState::Managed(_id, message_ports) = + &mut *self.message_port_state.borrow_mut() + { + match message_ports.get_mut(&port_id) { + None => { + panic!("CompleteTransfer msg received in a global not managing the port."); + }, + Some(ManagedMessagePort::Pending(_, _)) => { + panic!("CompleteTransfer msg received for a pending port."); + }, + Some(ManagedMessagePort::Added(port_impl, _port)) => { + port_impl.complete_transfer(tasks); + port_impl.enabled() + }, + } + } else { + return warn!("CompleteTransfer msg received in a global not managing any ports."); + }; + if should_start { + self.start_message_port(&port_id); + } + } + + /// Update our state to un-managed, + /// and tell the constellation to drop the sender to our message-port router. + pub fn remove_message_ports_router(&self) { + if let MessagePortState::Managed(router_id, _message_ports) = + &*self.message_port_state.borrow() + { + let _ = self + .script_to_constellation_chan() + .send(ScriptMsg::RemoveMessagePortRouter(router_id.clone())); + } + *self.message_port_state.borrow_mut() = MessagePortState::UnManaged; + } + + /// <https://html.spec.whatwg.org/multipage/#entangle> + pub fn entangle_ports(&self, port1: MessagePortId, port2: MessagePortId) { + if let MessagePortState::Managed(_id, message_ports) = + &mut *self.message_port_state.borrow_mut() + { + for (port_id, entangled_id) in &[(port1, port2), (port2, port1)] { + match message_ports.get_mut(&port_id) { + None => { + return warn!("entangled_ports called on a global not managing the port."); + }, + Some(ManagedMessagePort::Pending(port_impl, dom_port)) => { + dom_port + .root() + .expect("Port to be entangled to not have been GC'ed") + .entangle(entangled_id.clone()); + port_impl.entangle(entangled_id.clone()); + }, + Some(ManagedMessagePort::Added(port_impl, dom_port)) => { + dom_port + .root() + .expect("Port to be entangled to not have been GC'ed") + .entangle(entangled_id.clone()); + port_impl.entangle(entangled_id.clone()); + }, + } + } + } else { + panic!("entangled_ports called on a global not managing any ports."); + } + + let _ = self + .script_to_constellation_chan() + .send(ScriptMsg::EntanglePorts(port1, port2)); + } + + /// Remove all referrences to a port. + pub fn remove_message_port(&self, port_id: &MessagePortId) { + let is_empty = if let MessagePortState::Managed(_id, message_ports) = + &mut *self.message_port_state.borrow_mut() + { + match message_ports.remove(&port_id) { + None => panic!("remove_message_port called on a global not managing the port."), + Some(_) => message_ports.is_empty(), + } + } else { + return warn!("remove_message_port called on a global not managing any ports."); + }; + if is_empty { + // Remove our port router, + // it will be setup again if we start managing ports again. + self.remove_message_ports_router(); + } + } + + /// Handle the transfer of a port in the current task. + pub fn mark_port_as_transferred(&self, port_id: &MessagePortId) -> MessagePortImpl { + if let MessagePortState::Managed(_id, message_ports) = + &mut *self.message_port_state.borrow_mut() + { + let mut port = match message_ports.remove(&port_id) { + None => { + panic!("mark_port_as_transferred called on a global not managing the port.") + }, + Some(ManagedMessagePort::Pending(port_impl, _)) => port_impl, + Some(ManagedMessagePort::Added(port_impl, _)) => port_impl, + }; + port.set_has_been_shipped(); + let _ = self + .script_to_constellation_chan() + .send(ScriptMsg::MessagePortShipped(port_id.clone())); + port + } else { + panic!("mark_port_as_transferred called on a global not managing any ports."); + } + } + + /// <https://html.spec.whatwg.org/multipage/#dom-messageport-start> + pub fn start_message_port(&self, port_id: &MessagePortId) { + if let MessagePortState::Managed(_id, message_ports) = + &mut *self.message_port_state.borrow_mut() + { + let port = match message_ports.get_mut(&port_id) { + None => panic!("start_message_port called on a unknown port."), + Some(ManagedMessagePort::Pending(port_impl, _)) => port_impl, + Some(ManagedMessagePort::Added(port_impl, _)) => port_impl, + }; + if let Some(message_buffer) = port.start() { + for task in message_buffer { + let port_id = port_id.clone(); + let this = Trusted::new(&*self); + let _ = self.port_message_queue().queue( + task!(process_pending_port_messages: move || { + let target_global = this.root(); + target_global.route_task_to_port(port_id, task); + }), + &self, + ); + } + } + } else { + return warn!("start_message_port called on a global not managing any ports."); + } + } + + /// <https://html.spec.whatwg.org/multipage/#dom-messageport-close> + pub fn close_message_port(&self, port_id: &MessagePortId) { + if let MessagePortState::Managed(_id, message_ports) = + &mut *self.message_port_state.borrow_mut() + { + let port = match message_ports.get_mut(&port_id) { + None => panic!("close_message_port called on an unknown port."), + Some(ManagedMessagePort::Pending(port_impl, _)) => port_impl, + Some(ManagedMessagePort::Added(port_impl, _)) => port_impl, + }; + port.close(); + } else { + return warn!("close_message_port called on a global not managing any ports."); + } + } + + /// <https://html.spec.whatwg.org/multipage/#message-port-post-message-steps> + // Steps 6 and 7 + pub fn post_messageport_msg(&self, port_id: MessagePortId, task: PortMessageTask) { + if let MessagePortState::Managed(_id, message_ports) = + &mut *self.message_port_state.borrow_mut() + { + let port = match message_ports.get_mut(&port_id) { + None => panic!("post_messageport_msg called on an unknown port."), + Some(ManagedMessagePort::Pending(port_impl, _)) => port_impl, + Some(ManagedMessagePort::Added(port_impl, _)) => port_impl, + }; + if let Some(entangled_id) = port.entangled_port_id() { + // Step 7 + let this = Trusted::new(&*self); + let _ = self.port_message_queue().queue( + task!(post_message: move || { + let global = this.root(); + // Note: we do this in a task, as this will ensure the global and constellation + // are aware of any transfer that might still take place in the current task. + global.route_task_to_port(entangled_id, task); + }), + self, + ); + } + } else { + return warn!("post_messageport_msg called on a global not managing any ports."); + } + } + + /// If we don't know about the port, + /// send the message to the constellation for routing. + fn re_route_port_task(&self, port_id: MessagePortId, task: PortMessageTask) { + let _ = self + .script_to_constellation_chan() + .send(ScriptMsg::RerouteMessagePort(port_id, task)); + } + + /// Route the task to be handled by the relevant port. + pub fn route_task_to_port(&self, port_id: MessagePortId, task: PortMessageTask) { + let should_dispatch = if let MessagePortState::Managed(_id, message_ports) = + &mut *self.message_port_state.borrow_mut() + { + if !message_ports.contains_key(&port_id) { + self.re_route_port_task(port_id, task); + return; + } + let (port_impl, dom_port) = match message_ports.get_mut(&port_id) { + None => panic!("route_task_to_port called for an unknown port."), + Some(ManagedMessagePort::Pending(port_impl, dom_port)) => (port_impl, dom_port), + Some(ManagedMessagePort::Added(port_impl, dom_port)) => (port_impl, dom_port), + }; + + // If the port is not enabled yet, or if is awaiting the completion of it's transfer, + // the task will be buffered and dispatched upon enablement or completion of the transfer. + if let Some(task_to_dispatch) = port_impl.handle_incoming(task) { + // Get a corresponding DOM message-port object. + let dom_port = match dom_port.root() { + Some(dom_port) => dom_port, + None => panic!("Messageport Gc'ed too early"), + }; + Some((dom_port, task_to_dispatch)) + } else { + None + } + } else { + self.re_route_port_task(port_id, task); + return; + }; + if let Some((dom_port, PortMessageTask { origin, data })) = should_dispatch { + // Substep 3-4 + rooted!(in(*self.get_cx()) let mut message_clone = UndefinedValue()); + if let Ok(ports) = structuredclone::read(self, data, message_clone.handle_mut()) { + // Substep 6 + // Dispatch the event, using the dom message-port. + MessageEvent::dispatch_jsval( + &dom_port.upcast(), + self, + message_clone.handle(), + Some(&origin.ascii_serialization()), + None, + ports, + ); + } else { + // Step 4, fire messageerror event. + MessageEvent::dispatch_error(&dom_port.upcast(), self); + } + } + } + + /// Check all ports that have been transfer-received in the previous task, + /// and complete their transfer if they haven't been re-transferred. + pub fn maybe_add_pending_ports(&self) { + if let MessagePortState::Managed(router_id, message_ports) = + &mut *self.message_port_state.borrow_mut() + { + let to_be_added: Vec<MessagePortId> = message_ports + .iter() + .filter_map(|(id, port_info)| match port_info { + ManagedMessagePort::Pending(_, _) => Some(id.clone()), + _ => None, + }) + .collect(); + for id in to_be_added { + let (id, port_info) = message_ports + .remove_entry(&id) + .expect("Collected port-id to match an entry"); + if let ManagedMessagePort::Pending(port_impl, dom_port) = port_info { + let _ = self + .script_to_constellation_chan() + .send(ScriptMsg::NewMessagePort( + router_id.clone(), + port_impl.message_port_id().clone(), + )); + let new_port_info = ManagedMessagePort::Added(port_impl, dom_port); + let present = message_ports.insert(id, new_port_info); + assert!(present.is_none()); + } + } + } else { + warn!("maybe_add_pending_ports called on a global not managing any ports."); + } + } + + /// https://html.spec.whatwg.org/multipage/#ports-and-garbage-collection + pub fn perform_a_message_port_garbage_collection_checkpoint(&self) { + let is_empty = if let MessagePortState::Managed(_id, message_ports) = + &mut *self.message_port_state.borrow_mut() + { + let to_be_removed: Vec<MessagePortId> = message_ports + .iter() + .filter_map(|(id, port_info)| { + if let ManagedMessagePort::Added(_port_impl, dom_port) = port_info { + if dom_port.root().is_none() { + // Let the constellation know to drop this port and the one it is entangled with, + // and to forward this message to the script-process where the entangled is found. + let _ = self + .script_to_constellation_chan() + .send(ScriptMsg::RemoveMessagePort(id.clone())); + return Some(id.clone()); + } + } + None + }) + .collect(); + for id in to_be_removed { + message_ports.remove(&id); + } + message_ports.is_empty() + } else { + false + }; + if is_empty { + self.remove_message_ports_router(); + } + } + + /// Start tracking a message-port + pub fn track_message_port(&self, dom_port: &MessagePort, port_impl: Option<MessagePortImpl>) { + let mut current_state = self.message_port_state.borrow_mut(); + + if let MessagePortState::UnManaged = &*current_state { + // Setup a route for IPC, for messages from the constellation to our ports. + let (port_control_sender, port_control_receiver) = + ipc::channel().expect("ipc channel failure"); + let context = Trusted::new(self); + let (task_source, canceller) = ( + self.port_message_queue(), + self.task_canceller(TaskSourceName::PortMessage), + ); + let listener = MessageListener { + canceller, + task_source, + context, + }; + ROUTER.add_route( + port_control_receiver.to_opaque(), + Box::new(move |message| { + let msg = message.to(); + match msg { + Ok(msg) => listener.notify(msg), + Err(err) => warn!("Error receiving a MessagePortMsg: {:?}", err), + } + }), + ); + let router_id = MessagePortRouterId::new(); + *current_state = MessagePortState::Managed(router_id.clone(), HashMap::new()); + let _ = self + .script_to_constellation_chan() + .send(ScriptMsg::NewMessagePortRouter( + router_id, + port_control_sender, + )); + } + + if let MessagePortState::Managed(router_id, message_ports) = &mut *current_state { + if let Some(port_impl) = port_impl { + // We keep transfer-received ports as "pending", + // and only ask the constellation to complete the transfer + // if they're not re-shipped in the current task. + message_ports.insert( + dom_port.message_port_id().clone(), + ManagedMessagePort::Pending(port_impl, WeakRef::new(dom_port)), + ); + + // Queue a task to complete the transfer, + // unless the port is re-transferred in the current task. + let this = Trusted::new(&*self); + let _ = self.port_message_queue().queue( + task!(process_pending_port_messages: move || { + let target_global = this.root(); + target_global.maybe_add_pending_ports(); + }), + &self, + ); + } else { + // If this is a newly-created port, let the constellation immediately know. + let port_impl = MessagePortImpl::new(dom_port.message_port_id().clone()); + message_ports.insert( + dom_port.message_port_id().clone(), + ManagedMessagePort::Added(port_impl, WeakRef::new(dom_port)), + ); + let _ = self + .script_to_constellation_chan() + .send(ScriptMsg::NewMessagePort( + router_id.clone(), + dom_port.message_port_id().clone(), + )); + }; + } else { + panic!("track_message_port should have first switched the state to managed."); + } + } + pub fn track_worker(&self, closing_worker: Arc<AtomicBool>) { self.list_auto_close_worker .borrow_mut() @@ -550,7 +1026,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 05dff680c2c..c02cdb42472 100644 --- a/components/script/dom/history.rs +++ b/components/script/dom/history.rs @@ -11,7 +11,7 @@ use crate::dom::bindings::inheritance::Castable; use crate::dom::bindings::reflector::{reflect_dom_object, DomObject, Reflector}; use crate::dom::bindings::root::{Dom, DomRoot}; use crate::dom::bindings::str::{DOMString, USVString}; -use crate::dom::bindings::structuredclone::StructuredCloneData; +use crate::dom::bindings::structuredclone; use crate::dom::event::Event; use crate::dom::eventtarget::EventTarget; use crate::dom::globalscope::GlobalScope; @@ -27,7 +27,7 @@ use msg::constellation_msg::{HistoryStateId, TraversalDirection}; use net_traits::{CoreResourceMsg, IpcSend}; use profile_traits::ipc; use profile_traits::ipc::channel; -use script_traits::ScriptMsg; +use script_traits::{ScriptMsg, StructuredSerializedData}; use servo_url::ServoUrl; use std::cell::Cell; @@ -115,11 +115,16 @@ impl History { }; match serialized_data { - Some(serialized_data) => { + Some(data) => { + let data = StructuredSerializedData { + serialized: data, + ports: None, + }; let global_scope = self.window.upcast::<GlobalScope>(); rooted!(in(*global_scope.get_cx()) let mut state = UndefinedValue()); - StructuredCloneData::Vector(serialized_data) - .read(&global_scope, state.handle_mut()); + if let Err(_) = structuredclone::read(&global_scope, data, state.handle_mut()) { + warn!("Error reading structuredclone data"); + } self.state.set(state.get()); }, None => { @@ -185,8 +190,7 @@ impl History { // TODO: Step 4 // Step 5 - rooted!(in(cx) let transfer = UndefinedValue()); - let serialized_data = StructuredCloneData::write(*cx, data, transfer.handle())?.move_to_arraybuffer(); + let serialized_data = structuredclone::write(cx, data, None)?; let new_url: ServoUrl = match url { // Step 6 @@ -255,7 +259,7 @@ impl History { }; let _ = self.window.upcast::<GlobalScope>().resource_threads().send( - CoreResourceMsg::SetHistoryState(state_id, serialized_data.clone()), + CoreResourceMsg::SetHistoryState(state_id, serialized_data.serialized.clone()), ); // TODO: Step 9 Update current entry to represent a GET request @@ -267,7 +271,9 @@ impl History { // Step 11 let global_scope = self.window.upcast::<GlobalScope>(); rooted!(in(*cx) let mut state = UndefinedValue()); - StructuredCloneData::Vector(serialized_data).read(&global_scope, state.handle_mut()); + if let Err(_) = structuredclone::read(&global_scope, serialized_data, state.handle_mut()) { + warn!("Error reading structuredclone data"); + } // Step 12 self.state.set(state.get()); diff --git a/components/script/dom/messagechannel.rs b/components/script/dom/messagechannel.rs index e0d226d709a..efe883c6e6e 100644 --- a/components/script/dom/messagechannel.rs +++ b/components/script/dom/messagechannel.rs @@ -4,7 +4,7 @@ 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::reflector::{reflect_dom_object, Reflector}; use crate::dom::bindings::root::{Dom, DomRoot}; use crate::dom::globalscope::GlobalScope; use crate::dom::messageport::MessagePort; @@ -28,18 +28,24 @@ impl MessageChannel { // Step 2 let port2 = MessagePort::new(&incumbent); + incumbent.track_message_port(&*port1, None); + incumbent.track_message_port(&*port2, None); + // Step 3 - port1.entangle(&port2); + incumbent.entangle_ports( + port1.message_port_id().clone(), + port2.message_port_id().clone(), + ); // Steps 4-6 - let channel = reflect_dom_object(Box::new( - MessageChannel { + let channel = reflect_dom_object( + Box::new(MessageChannel { reflector_: Reflector::new(), port1: Dom::from_ref(&port1), port2: Dom::from_ref(&port2), }), global, - Wrap + Wrap, ); // Step 7 diff --git a/components/script/dom/messageevent.rs b/components/script/dom/messageevent.rs index 8d002923814..cb8ebaaebd1 100644 --- a/components/script/dom/messageevent.rs +++ b/components/script/dom/messageevent.rs @@ -11,6 +11,7 @@ use crate::dom::bindings::reflector::{reflect_dom_object, DomObject}; use crate::dom::bindings::root::{Dom, DomRoot}; use crate::dom::bindings::str::DOMString; use crate::dom::bindings::trace::RootedTraceableBox; +use crate::dom::bindings::utils::message_ports_to_frozen_array; use crate::dom::event::Event; use crate::dom::eventtarget::EventTarget; use crate::dom::globalscope::GlobalScope; @@ -18,10 +19,8 @@ use crate::dom::messageport::MessagePort; use crate::dom::windowproxy::WindowProxy; use crate::script_runtime::JSContext; use dom_struct::dom_struct; -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::jsapi::{Heap, JSObject}; +use js::jsval::JSVal; use js::rust::HandleValue; use servo_atoms::Atom; use std::ptr::NonNull; @@ -108,7 +107,7 @@ impl MessageEvent { init.origin.clone(), source.as_ref().map(|source| &**source), init.lastEventId.clone(), - init.ports.clone().unwrap_or(vec![]) + init.ports.clone().unwrap_or(vec![]), ); Ok(ev) } @@ -136,6 +135,26 @@ impl MessageEvent { ); messageevent.upcast::<Event>().fire(target); } + + pub fn dispatch_error(target: &EventTarget, scope: &GlobalScope) { + let init = MessageEventBinding::MessageEventInit::empty(); + let source = init + .source + .as_ref() + .and_then(|inner| inner.as_ref().map(|source| source.window_proxy())); + let messageevent = MessageEvent::new( + scope, + atom!("messageerror"), + init.parent.bubbles, + init.parent.cancelable, + init.data.handle(), + init.origin.clone(), + source.as_ref().map(|source| &**source), + init.lastEventId.clone(), + init.ports.clone().unwrap_or(vec![]), + ); + messageevent.upcast::<Event>().fire(target); + } } impl MessageEventMethods for MessageEvent { @@ -166,14 +185,8 @@ impl MessageEventMethods for MessageEvent { 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 + fn Ports(&self, cx: JSContext) -> JSVal { + message_ports_to_frozen_array(self.ports.as_slice(), cx) } } diff --git a/components/script/dom/messageport.rs b/components/script/dom/messageport.rs index 10489f6d2ad..67d470a10a2 100644 --- a/components/script/dom/messageport.rs +++ b/components/script/dom/messageport.rs @@ -3,393 +3,343 @@ * 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::codegen::Bindings::MessagePortBinding::{ + MessagePortMethods, PostMessageOptions, Wrap, +}; +use crate::dom::bindings::conversions::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::inheritance::Castable; +use crate::dom::bindings::reflector::{reflect_dom_object, DomObject}; use crate::dom::bindings::root::DomRoot; -use crate::dom::bindings::structuredclone::StructuredCloneData; -use crate::dom::bindings::trace::JSTraceable; +use crate::dom::bindings::structuredclone::{self, StructuredDataHolder}; +use crate::dom::bindings::trace::RootedTraceableBox; 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 crate::script_runtime::JSContext as SafeJSContext; +use dom_struct::dom_struct; +use js::jsapi::Heap; +use js::jsapi::{JSObject, MutableHandleObject}; +use js::rust::{CustomAutoRooter, CustomAutoRooterGuard, HandleValue}; +use msg::constellation_msg::{MessagePortId, MessagePortIndex, PipelineNamespaceId}; +use script_traits::PortMessageTask; use std::cell::{Cell, RefCell}; -use std::collections::VecDeque; -use std::mem; -use std::os::raw; +use std::collections::HashMap; +use std::convert::TryInto; +use std::num::NonZeroU32; 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>>, +#[dom_struct] +/// The MessagePort used in the DOM. +pub struct MessagePort { + eventtarget: EventTarget, + message_port_id: MessagePortId, + entangled_port: RefCell<Option<MessagePortId>>, + detached: Cell<bool>, } -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), +impl MessagePort { + fn new_inherited(message_port_id: MessagePortId) -> MessagePort { + MessagePort { + eventtarget: EventTarget::new_inherited(), entangled_port: RefCell::new(None), - pending_port_messages: RefCell::new(VecDeque::new()), + detached: Cell::new(false), + message_port_id, } } - /// <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, - }; + /// <https://html.spec.whatwg.org/multipage/#create-a-new-messageport-object> + pub fn new(owner: &GlobalScope) -> DomRoot<MessagePort> { + let port_id = MessagePortId::new(); + reflect_dom_object(Box::new(MessagePort::new_inherited(port_id)), owner, Wrap) + } - // Substep 1 - let final_target_port = self.dom_port.borrow().as_ref().unwrap().root(); + /// Create a new port for an incoming transfer-received one. + fn new_transferred( + owner: &GlobalScope, + transferred_port: MessagePortId, + entangled_port: Option<MessagePortId>, + ) -> DomRoot<MessagePort> { + reflect_dom_object( + Box::new(MessagePort { + message_port_id: transferred_port, + eventtarget: EventTarget::new_inherited(), + detached: Cell::new(false), + entangled_port: RefCell::new(entangled_port), + }), + owner, + Wrap, + ) + } - // Substep 2 - let target_global = final_target_port.global(); + /// <https://html.spec.whatwg.org/multipage/#entangle> + pub fn entangle(&self, other_id: MessagePortId) { + *self.entangled_port.borrow_mut() = Some(other_id); + } - // 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; - } + pub fn message_port_id(&self) -> &MessagePortId { + &self.message_port_id + } - // 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, - ); + pub fn detached(&self) -> bool { + self.detached.get() } -} -#[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>>, -} + /// <https://html.spec.whatwg.org/multipage/#handler-messageport-onmessage> + fn set_onmessage(&self, listener: Option<Rc<EventHandlerNonNull>>) { + let eventtarget = self.upcast::<EventTarget>(); + eventtarget.set_event_handler_common("message", listener); + } -#[allow(unsafe_code)] -unsafe impl JSTraceable for MessagePort { - unsafe fn trace(&self, trc: *mut JSTracer) { - if !self.detached.get() { - self.eventtarget.trace(trc); + /// <https://html.spec.whatwg.org/multipage/#message-port-post-message-steps> + fn post_message_impl( + &self, + cx: SafeJSContext, + message: HandleValue, + transfer: CustomAutoRooterGuard<Vec<*mut JSObject>>, + ) -> ErrorResult { + if self.detached.get() { + return Ok(()); } - // Otherwise, do nothing. - } -} -impl HasParent for MessagePort { - type Parent = EventTarget; + // Step 1 is the transfer argument. - fn as_parent(&self) -> &EventTarget { - &self.eventtarget - } -} + let target_port = self.entangled_port.borrow(); -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()) - ) - ), - } - } + // Step 3 + let mut doomed = false; - fn new_transferred(message_port_internal: Arc<ReentrantMutex<MessagePortInternal>>) -> MessagePort { - MessagePort { - eventtarget: EventTarget::new_inherited(), - detached: Cell::new(false), - message_port_internal, - } - } + let ports = transfer + .iter() + .filter_map(|&obj| root_from_object::<MessagePort>(obj, *cx).ok()); + for port in ports { + // Step 2 + if port.message_port_id() == self.message_port_id() { + return Err(Error::DataClone); + } - /// <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)); + // Step 4 + if let Some(target_id) = target_port.as_ref() { + if port.message_port_id() == target_id { + doomed = true; + } + } } - 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()); + // Step 5 + let data = structuredclone::write(cx, message, Some(transfer))?; + + if doomed { + // TODO: The spec says to optionally report such a case to a dev console. + return Ok(()); } - 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(); + // Step 6, done in MessagePortImpl. + + let incumbent = match GlobalScope::incumbent() { + None => unreachable!("postMessage called with no incumbent global"), + Some(incumbent) => incumbent, + }; + + // Step 7 + let task = PortMessageTask { + origin: incumbent.origin().immutable().clone(), + data, + }; + + // Have the global proxy this call to the corresponding MessagePortImpl. + self.global() + .post_messageport_msg(self.message_port_id().clone(), task); + Ok(()) } } 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 + fn transfer(&self, sc_holder: &mut StructuredDataHolder) -> Result<u64, ()> { + if self.detached.get() { + return Err(()); } - unsafe { - // Steps 2, 3.2 and 4 - *content = Arc::into_raw(self.message_port_internal.clone()) as *mut raw::c_void; + let port_impls = match sc_holder { + StructuredDataHolder::Write(port_impls) => port_impls, + _ => panic!("Unexpected variant of StructuredDataHolder"), + }; - *extra_data = 0; + self.detached.set(true); + let id = self.message_port_id(); + + // 1. Run local transfer logic, and return the object to be transferred. + let transferred_port = self.global().mark_port_as_transferred(id); + + // 2. Store the transferred object at a given key. + if let Some(ports) = port_impls.as_mut() { + ports.insert(id.clone(), transferred_port); + } else { + let mut ports = HashMap::new(); + ports.insert(id.clone(), transferred_port); + *port_impls = Some(ports); } - true + let PipelineNamespaceId(name_space) = id.clone().namespace_id; + let MessagePortIndex(index) = id.clone().index; + let index = index.get(); + + let mut big: [u8; 8] = [0; 8]; + let name_space = name_space.to_ne_bytes(); + let index = index.to_ne_bytes(); + + let (left, right) = big.split_at_mut(4); + left.copy_from_slice(&name_space); + right.copy_from_slice(&index); + + // 3. Return a u64 representation of the key where the object is stored. + Ok(u64::from_ne_bytes(big)) } /// 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); - }); + owner: &DomRoot<GlobalScope>, + sc_holder: &mut StructuredDataHolder, + extra_data: u64, + return_object: MutableHandleObject, + ) -> Result<(), ()> { + let (message_ports, port_impls) = match sc_holder { + StructuredDataHolder::Read { + message_ports, + port_impls, + .. + } => (message_ports, port_impls), + _ => panic!("Unexpected variant of StructuredDataHolder"), + }; - true - } + // 1. Re-build the key for the storage location + // of the transferred object. + let big: [u8; 8] = extra_data.to_ne_bytes(); + let (name_space, index) = big.split_at(4); + + let namespace_id = PipelineNamespaceId(u32::from_ne_bytes( + name_space + .try_into() + .expect("name_space to be a slice of four."), + )); + let index = MessagePortIndex( + NonZeroU32::new(u32::from_ne_bytes( + index.try_into().expect("index to be a slice of four."), + )) + .expect("Index to be non-zero"), + ); - fn detached(&self) -> Option<bool> { - Some(self.detached.get()) - } + let id = MessagePortId { + namespace_id, + index, + }; - fn set_detached(&self, value: bool) { - self.detached.set(value); - } + // 2. Get the transferred object from its storage, using the key. + // Assign the transfer-received port-impl, and total number of transferred ports. + let (ports_len, port_impl) = if let Some(ports) = port_impls.as_mut() { + let ports_len = ports.len(); + let port_impl = ports.remove(&id).expect("Transferred port to be stored"); + if ports.is_empty() { + *port_impls = None; + } + (ports_len, port_impl) + } else { + panic!("A messageport was transfer-received, yet the SC holder does not have any port impls"); + }; + + let transferred_port = + MessagePort::new_transferred(&**owner, id.clone(), port_impl.entangled_port_id()); + owner.track_message_port(&transferred_port, Some(port_impl)); + + return_object.set(transferred_port.reflector().rootable().get()); - fn transferable(&self) -> bool { - !self.detached.get() + // Store the DOM port where it will be passed along to script in the message-event. + if let Some(ports) = message_ports.as_mut() { + ports.push(transferred_port); + } else { + let mut ports = Vec::with_capacity(ports_len); + ports.push(transferred_port); + *message_ports = Some(ports); + } + + Ok(()) } } impl MessagePortMethods for MessagePort { - #[allow(unsafe_code)] /// <https://html.spec.whatwg.org/multipage/#dom-messageport-postmessage> - unsafe fn PostMessage( + fn PostMessage( &self, - cx: *mut JSContext, + cx: SafeJSContext, message: HandleValue, - transfer: CustomAutoRooterGuard<Option<Vec<*mut JSObject>>>, + transfer: CustomAutoRooterGuard<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() - ); - } + if self.detached.get() { + return Ok(()); } + self.post_message_impl(cx, message, transfer) + } - Ok(()) + /// <https://html.spec.whatwg.org/multipage/#dom-messageport-postmessage> + fn PostMessage_( + &self, + cx: SafeJSContext, + message: HandleValue, + options: RootedTraceableBox<PostMessageOptions>, + ) -> ErrorResult { + if self.detached.get() { + return Ok(()); + } + let mut rooted = CustomAutoRooter::new( + options + .transfer + .as_ref() + .unwrap_or(&Vec::with_capacity(0)) + .iter() + .map(|js: &RootedTraceableBox<Heap<*mut JSObject>>| js.get()) + .collect(), + ); + let guard = CustomAutoRooterGuard::new(*cx, &mut rooted); + self.post_message_impl(cx, message, guard) } /// <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 - ); + if self.detached.get() { + return; } + self.global().start_message_port(self.message_port_id()); } /// <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; + if self.detached.get() { + return; } + self.detached.set(true); + self.global().close_message_port(self.message_port_id()); } /// <https://html.spec.whatwg.org/multipage/#handler-messageport-onmessage> fn GetOnmessage(&self) -> Option<Rc<EventHandlerNonNull>> { + if self.detached.get() { + return None; + } 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) + if self.detached.get() { + return; + } + self.set_onmessage(listener); + // Note: we cannot use the event_handler macro, due to the need to start the port. + self.global().start_message_port(self.message_port_id()); } + + /// <https://html.spec.whatwg.org/multipage/#handler-messageport-onmessageerror> + event_handler!(messageerror, GetOnmessageerror, SetOnmessageerror); } diff --git a/components/script/dom/serviceworker.rs b/components/script/dom/serviceworker.rs index 8d33337f1c5..ec64036d305 100644 --- a/components/script/dom/serviceworker.rs +++ b/components/script/dom/serviceworker.rs @@ -4,6 +4,7 @@ use crate::dom::abstractworker::SimpleWorkerErrorHandler; use crate::dom::bindings::cell::DomRefCell; +use crate::dom::bindings::codegen::Bindings::MessagePortBinding::PostMessageOptions; use crate::dom::bindings::codegen::Bindings::ServiceWorkerBinding::{ ServiceWorkerMethods, ServiceWorkerState, Wrap, }; @@ -13,15 +14,15 @@ use crate::dom::bindings::refcounted::Trusted; use crate::dom::bindings::reflector::{reflect_dom_object, DomObject}; use crate::dom::bindings::root::DomRoot; use crate::dom::bindings::str::USVString; -use crate::dom::bindings::structuredclone::StructuredCloneData; +use crate::dom::bindings::structuredclone; +use crate::dom::bindings::trace::RootedTraceableBox; use crate::dom::eventtarget::EventTarget; 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 js::jsapi::{Heap, JSObject}; +use js::rust::{CustomAutoRooter, CustomAutoRooterGuard, HandleValue}; use script_traits::{DOMMessage, ScriptMsg}; use servo_url::ServoUrl; use std::cell::Cell; @@ -79,41 +80,76 @@ impl ServiceWorker { pub fn get_script_url(&self) -> ServoUrl { ServoUrl::parse(&self.script_url.borrow().clone()).unwrap() } -} - -impl ServiceWorkerMethods for ServiceWorker { - // https://w3c.github.io/ServiceWorker/#service-worker-state-attribute - fn State(&self) -> ServiceWorkerState { - self.state.get() - } - // https://w3c.github.io/ServiceWorker/#service-worker-url-attribute - fn ScriptURL(&self) -> USVString { - USVString(self.script_url.borrow().clone()) - } - - // https://w3c.github.io/ServiceWorker/#service-worker-postmessage - fn PostMessage(&self, cx: JSContext, message: HandleValue) -> ErrorResult { + /// https://w3c.github.io/ServiceWorker/#service-worker-postmessage + fn post_message_impl( + &self, + cx: JSContext, + message: HandleValue, + transfer: CustomAutoRooterGuard<Vec<*mut JSObject>>, + ) -> ErrorResult { // Step 1 if let ServiceWorkerState::Redundant = self.state.get() { return Err(Error::InvalidState); } // Step 7 - rooted!(in(*cx) let transfer = UndefinedValue()); - let data = StructuredCloneData::write(*cx, message, transfer.handle())?; + let data = structuredclone::write(cx, message, Some(transfer))?; + let incumbent = GlobalScope::incumbent().expect("no incumbent global?"); let msg_vec = DOMMessage { - origin: self.global().origin().immutable().ascii_serialization(), - data: data.move_to_arraybuffer(), + origin: incumbent.origin().immutable().clone(), + data, }; let _ = self .global() .script_to_constellation_chan() .send(ScriptMsg::ForwardDOMMessage( msg_vec, - self.scope_url.clone() + self.scope_url.clone(), )); Ok(()) } +} + +impl ServiceWorkerMethods for ServiceWorker { + // https://w3c.github.io/ServiceWorker/#service-worker-state-attribute + fn State(&self) -> ServiceWorkerState { + self.state.get() + } + + // https://w3c.github.io/ServiceWorker/#service-worker-url-attribute + fn ScriptURL(&self) -> USVString { + USVString(self.script_url.borrow().clone()) + } + + /// https://w3c.github.io/ServiceWorker/#service-worker-postmessage + fn PostMessage( + &self, + cx: JSContext, + message: HandleValue, + transfer: CustomAutoRooterGuard<Vec<*mut JSObject>>, + ) -> ErrorResult { + self.post_message_impl(cx, message, transfer) + } + + /// https://w3c.github.io/ServiceWorker/#service-worker-postmessage + fn PostMessage_( + &self, + cx: JSContext, + message: HandleValue, + options: RootedTraceableBox<PostMessageOptions>, + ) -> ErrorResult { + let mut rooted = CustomAutoRooter::new( + options + .transfer + .as_ref() + .unwrap_or(&Vec::with_capacity(0)) + .iter() + .map(|js: &RootedTraceableBox<Heap<*mut JSObject>>| js.get()) + .collect(), + ); + let guard = CustomAutoRooterGuard::new(*cx, &mut rooted); + self.post_message_impl(cx, message, guard) + } // https://w3c.github.io/ServiceWorker/#service-worker-container-onerror-attribute event_handler!(error, GetOnerror, SetOnerror); diff --git a/components/script/dom/serviceworkerglobalscope.rs b/components/script/dom/serviceworkerglobalscope.rs index 8daf3b6dc2f..657f43627ed 100644 --- a/components/script/dom/serviceworkerglobalscope.rs +++ b/components/script/dom/serviceworkerglobalscope.rs @@ -12,12 +12,14 @@ use crate::dom::bindings::codegen::Bindings::WorkerBinding::WorkerType; use crate::dom::bindings::inheritance::Castable; use crate::dom::bindings::root::{DomRoot, RootCollection, ThreadLocalStackRoots}; use crate::dom::bindings::str::DOMString; +use crate::dom::bindings::structuredclone; use crate::dom::dedicatedworkerglobalscope::AutoWorkerReset; use crate::dom::event::Event; use crate::dom::eventtarget::EventTarget; use crate::dom::extendableevent::ExtendableEvent; use crate::dom::extendablemessageevent::ExtendableMessageEvent; use crate::dom::globalscope::GlobalScope; +use crate::dom::messageevent::MessageEvent; use crate::dom::worker::TrustedWorkerAddress; use crate::dom::workerglobalscope::WorkerGlobalScope; use crate::fetch::load_whole_resource; @@ -414,8 +416,17 @@ impl ServiceWorkerGlobalScope { let target = self.upcast(); let _ac = enter_realm(&*scope); rooted!(in(*scope.get_cx()) let mut message = UndefinedValue()); - assert!(data.read(scope.upcast(), message.handle_mut())); - ExtendableMessageEvent::dispatch_jsval(target, scope.upcast(), message.handle()); + if let Ok(ports) = structuredclone::read(scope.upcast(), data, message.handle_mut()) + { + ExtendableMessageEvent::dispatch_jsval( + target, + scope.upcast(), + message.handle(), + ports, + ); + } else { + MessageEvent::dispatch_error(target, scope.upcast()); + } }, CommonWorker(WorkerScriptMsg::Common(msg)) => { self.upcast::<WorkerGlobalScope>().process_event(msg); diff --git a/components/script/dom/webidls/DedicatedWorkerGlobalScope.webidl b/components/script/dom/webidls/DedicatedWorkerGlobalScope.webidl index 488cbe052dd..2df97dbacfe 100644 --- a/components/script/dom/webidls/DedicatedWorkerGlobalScope.webidl +++ b/components/script/dom/webidls/DedicatedWorkerGlobalScope.webidl @@ -5,9 +5,9 @@ // https://html.spec.whatwg.org/multipage/#dedicatedworkerglobalscope [Global=(Worker,DedicatedWorker), Exposed=DedicatedWorker] /*sealed*/ interface DedicatedWorkerGlobalScope : WorkerGlobalScope { - [Throws] - void postMessage(any message/*, optional sequence<Transferable> transfer*/); - attribute EventHandler onmessage; + [Throws] void postMessage(any message, sequence<object> transfer); + [Throws] void postMessage(any message, optional PostMessageOptions options = {}); + attribute EventHandler onmessage; void close(); }; diff --git a/components/script/dom/webidls/DissimilarOriginWindow.webidl b/components/script/dom/webidls/DissimilarOriginWindow.webidl index 7c0ca4a2c80..bbfcb75b69b 100644 --- a/components/script/dom/webidls/DissimilarOriginWindow.webidl +++ b/components/script/dom/webidls/DissimilarOriginWindow.webidl @@ -25,7 +25,8 @@ interface DissimilarOriginWindow : GlobalScope { void close(); readonly attribute boolean closed; - [Throws] void postMessage(any message, DOMString targetOrigin); + [Throws] void postMessage(any message, USVString targetOrigin, optional sequence<object> transfer /*= []*/); + [Throws] void postMessage(any message, optional WindowPostMessageOptions options = {}); attribute any opener; void blur(); void focus(); diff --git a/components/script/dom/webidls/ExtendableMessageEvent.webidl b/components/script/dom/webidls/ExtendableMessageEvent.webidl index d623d4cb898..1976fd77dd8 100644 --- a/components/script/dom/webidls/ExtendableMessageEvent.webidl +++ b/components/script/dom/webidls/ExtendableMessageEvent.webidl @@ -12,7 +12,7 @@ interface ExtendableMessageEvent : ExtendableEvent { readonly attribute DOMString origin; readonly attribute DOMString lastEventId; // [SameObject] readonly attribute (Client or ServiceWorker /*or MessagePort*/)? source; - // readonly attribute FrozenArray<MessagePort>? ports; + readonly attribute /*FrozenArray<MessagePort>*/any ports; }; dictionary ExtendableMessageEventInit : ExtendableEventInit { diff --git a/components/script/dom/webidls/MessagePort.webidl b/components/script/dom/webidls/MessagePort.webidl index c00eba5291a..c2fd1dbd1f4 100644 --- a/components/script/dom/webidls/MessagePort.webidl +++ b/components/script/dom/webidls/MessagePort.webidl @@ -8,10 +8,16 @@ [Exposed=(Window,Worker)] interface MessagePort : EventTarget { - [Throws] void postMessage(any message, optional sequence<object> transfer /*= []*/); + [Throws] void postMessage(any message, sequence<object> transfer /*= []*/); + [Throws] void postMessage(any message, optional PostMessageOptions options = {}); void start(); void close(); // event handlers attribute EventHandler onmessage; + attribute EventHandler onmessageerror; +}; + +dictionary PostMessageOptions { + sequence<object> transfer; }; diff --git a/components/script/dom/webidls/ServiceWorker.webidl b/components/script/dom/webidls/ServiceWorker.webidl index 24a700ab103..60bb6dfc4b9 100644 --- a/components/script/dom/webidls/ServiceWorker.webidl +++ b/components/script/dom/webidls/ServiceWorker.webidl @@ -7,7 +7,8 @@ interface ServiceWorker : EventTarget { readonly attribute USVString scriptURL; readonly attribute ServiceWorkerState state; - [Throws] void postMessage(any message/*, optional sequence<object> transfer = []*/); + [Throws] void postMessage(any message, sequence<object> transfer); + [Throws] void postMessage(any message, optional PostMessageOptions options = {}); // event attribute EventHandler onstatechange; diff --git a/components/script/dom/webidls/Window.webidl b/components/script/dom/webidls/Window.webidl index a4af9bf692b..26edbca5c7a 100644 --- a/components/script/dom/webidls/Window.webidl +++ b/components/script/dom/webidls/Window.webidl @@ -65,6 +65,8 @@ [Throws] void postMessage(any message, USVString targetOrigin, optional sequence<object> transfer /*= []*/); + [Throws] + void postMessage(any message, optional WindowPostMessageOptions options = {}); // also has obsolete members }; @@ -172,3 +174,8 @@ partial interface Window { [Pref="css.animations.testing.enabled"] readonly attribute unsigned long runningAnimationCount; }; + +dictionary WindowPostMessageOptions { + USVString targetOrigin = "/"; + sequence<object> transfer; +}; diff --git a/components/script/dom/webidls/Worker.webidl b/components/script/dom/webidls/Worker.webidl index 4e55d6fbe00..93df577ec32 100644 --- a/components/script/dom/webidls/Worker.webidl +++ b/components/script/dom/webidls/Worker.webidl @@ -14,8 +14,8 @@ interface Worker : EventTarget { [Throws] constructor(USVString scriptURL, optional WorkerOptions options = {}); void terminate(); - [Throws] void postMessage(any message/*, sequence<object> transfer*/); - // void postMessage(any message, optional PostMessageOptions options); + [Throws] void postMessage(any message, sequence<object> transfer); + [Throws] void postMessage(any message, optional PostMessageOptions options = {}); attribute EventHandler onmessage; attribute EventHandler onmessageerror; }; diff --git a/components/script/dom/window.rs b/components/script/dom/window.rs index 7f3b6d1fb8d..37b5b5e3b2c 100644 --- a/components/script/dom/window.rs +++ b/components/script/dom/window.rs @@ -13,11 +13,10 @@ use crate::dom::bindings::codegen::Bindings::MediaQueryListBinding::MediaQueryLi use crate::dom::bindings::codegen::Bindings::PermissionStatusBinding::PermissionState; use crate::dom::bindings::codegen::Bindings::RequestBinding::RequestInit; use crate::dom::bindings::codegen::Bindings::WindowBinding::{ - self, FrameRequestCallback, WindowMethods, + self, FrameRequestCallback, WindowMethods, WindowPostMessageOptions, }; 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; @@ -25,7 +24,7 @@ use crate::dom::bindings::refcounted::Trusted; use crate::dom::bindings::reflector::DomObject; use crate::dom::bindings::root::{Dom, DomRoot, MutNullableDom}; use crate::dom::bindings::str::{DOMString, USVString}; -use crate::dom::bindings::structuredclone::StructuredCloneData; +use crate::dom::bindings::structuredclone; use crate::dom::bindings::trace::RootedTraceableBox; use crate::dom::bindings::utils::{GlobalStaticData, WindowProxyHandler}; use crate::dom::bindings::weakref::DOMTracker; @@ -44,7 +43,6 @@ 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; @@ -81,6 +79,7 @@ use euclid::default::{Point2D as UntypedPoint2D, Rect as UntypedRect}; use euclid::{Point2D, Rect, Scale, Size2D, Vector2D}; use ipc_channel::ipc::{channel, IpcSender}; use ipc_channel::router::ROUTER; +use js::jsapi::Heap; use js::jsapi::JSAutoRealm; use js::jsapi::JSObject; use js::jsapi::JSPROP_ENUMERATE; @@ -88,7 +87,7 @@ use js::jsapi::{GCReason, JS_GC}; use js::jsval::UndefinedValue; use js::jsval::{JSVal, NullValue}; use js::rust::wrappers::JS_DefineProperty; -use js::rust::{CustomAutoRooterGuard, HandleValue}; +use js::rust::{CustomAutoRooter, CustomAutoRooterGuard, HandleValue}; use media::WindowGLContext; use msg::constellation_msg::PipelineId; use net_traits::image_cache::{ImageCache, ImageResponder, ImageResponse}; @@ -107,7 +106,10 @@ use script_layout_interface::rpc::{ use script_layout_interface::{PendingImageState, TrustedNodeAddress}; use script_traits::webdriver_msg::{WebDriverJSError, WebDriverJSResult}; use script_traits::{ConstellationControlMsg, DocumentState, HistoryEntryReplacement, LoadData}; -use script_traits::{ScriptMsg, ScriptToConstellationChan, ScrollState, TimerEvent, TimerEventId}; +use script_traits::{ + ScriptMsg, ScriptToConstellationChan, ScrollState, StructuredSerializedData, TimerEvent, + TimerEventId, +}; use script_traits::{TimerSchedulerMsg, WindowSizeData, WindowSizeType}; use selectors::attr::CaseSensitivity; use servo_geometry::{f32_rect_to_au_rect, MaxRect}; @@ -976,31 +978,53 @@ impl WindowMethods for Window { &self, cx: JSContext, message: HandleValue, - origin: USVString, - transfer: CustomAutoRooterGuard<Option<Vec<*mut JSObject>>>, + target_origin: USVString, + mut transfer: CustomAutoRooterGuard<Option<Vec<*mut JSObject>>>, ) -> ErrorResult { - let source_global = GlobalScope::incumbent().expect("no incumbent global??"); - let source = source_global.as_window(); + let incumbent = GlobalScope::incumbent().expect("no incumbent global?"); + let source = incumbent.as_window(); + let source_origin = source.Document().origin().immutable().clone(); + + if transfer.is_some() { + let mut rooted = CustomAutoRooter::new(transfer.take().unwrap()); + let transfer = Some(CustomAutoRooterGuard::new(*cx, &mut rooted)); + self.post_message_impl(&target_origin, source_origin, source, cx, message, transfer) + } else { + self.post_message_impl(&target_origin, source_origin, source, cx, message, None) + } + } - // Step 3-5. - let origin = match &origin.0[..] { - "*" => None, - "/" => Some(source.Document().origin().immutable().clone()), - url => match ServoUrl::parse(&url) { - Ok(url) => Some(url.origin().clone()), - Err(_) => return Err(Error::Syntax), - }, - }; + /// <https://html.spec.whatwg.org/multipage/#dom-messageport-postmessage> + fn PostMessage_( + &self, + cx: JSContext, + message: HandleValue, + options: RootedTraceableBox<WindowPostMessageOptions>, + ) -> ErrorResult { + let mut rooted = CustomAutoRooter::new( + options + .transfer + .as_ref() + .unwrap_or(&Vec::with_capacity(0)) + .iter() + .map(|js: &RootedTraceableBox<Heap<*mut JSObject>>| js.get()) + .collect(), + ); + let transfer = Some(CustomAutoRooterGuard::new(*cx, &mut rooted)); - // Step 1-2, 6-8. - rooted!(in(*cx) let mut val = UndefinedValue()); - (*transfer).as_ref().unwrap_or(&Vec::new()).to_jsval(*cx, val.handle_mut()); + let incumbent = GlobalScope::incumbent().expect("no incumbent global?"); + let source = incumbent.as_window(); - let data = StructuredCloneData::write(*cx, message, val.handle())?; + let source_origin = source.Document().origin().immutable().clone(); - // Step 9. - self.post_message(origin, &*source.window_proxy(), data); - Ok(()) + self.post_message_impl( + &options.targetOrigin, + source_origin, + source, + cx, + message, + transfer, + ) } // https://html.spec.whatwg.org/multipage/#dom-window-captureevents @@ -1298,6 +1322,34 @@ impl WindowMethods for Window { } impl Window { + /// https://html.spec.whatwg.org/multipage/#window-post-message-steps + fn post_message_impl( + &self, + target_origin: &USVString, + source_origin: ImmutableOrigin, + source: &Window, + cx: JSContext, + message: HandleValue, + transfer: Option<CustomAutoRooterGuard<Vec<*mut JSObject>>>, + ) -> ErrorResult { + // Step 1-2, 6-8. + let data = structuredclone::write(cx, message, transfer)?; + + // Step 3-5. + let target_origin = match target_origin.0[..].as_ref() { + "*" => None, + "/" => Some(source_origin.clone()), + url => match ServoUrl::parse(&url) { + Ok(url) => Some(url.origin().clone()), + Err(_) => return Err(Error::Syntax), + }, + }; + + // Step 9. + self.post_message(target_origin, source_origin, &*source.window_proxy(), data); + Ok(()) + } + // https://drafts.css-houdini.org/css-paint-api-1/#paint-worklet pub fn paint_worklet(&self) -> DomRoot<Worklet> { self.paint_worklet.or_init(|| self.new_paint_worklet()) @@ -1344,6 +1396,9 @@ impl Window { // thread, informing it that it can safely free the memory. self.Document().upcast::<Node>().teardown(); + // Tell the constellation to drop the sender to our message-port router, if there is any. + self.upcast::<GlobalScope>().remove_message_ports_router(); + // Clean up any active promises // https://github.com/servo/servo/issues/15318 if let Some(custom_elements) = self.custom_element_registry.get() { @@ -2345,8 +2400,9 @@ impl Window { pub fn post_message( &self, target_origin: Option<ImmutableOrigin>, + source_origin: ImmutableOrigin, source: &WindowProxy, - serialize_with_transfer_result: StructuredCloneData, + data: StructuredSerializedData, ) { let this = Trusted::new(self); let source = Trusted::new(source); @@ -2367,26 +2423,23 @@ impl Window { let obj = this.reflector().get_jsobject(); let _ac = JSAutoRealm::new(*cx, obj.get()); rooted!(in(*cx) let mut message_clone = UndefinedValue()); - assert!(serialize_with_transfer_result.read( - this.upcast(), - message_clone.handle_mut(), - )); - - // Step 7.6. - let new_ports = TRANSFERRED_MESSAGE_PORTS.with(|list| { - mem::replace(&mut *list.borrow_mut(), vec![]) - }); - - // Step 7.7. - // TODO(#12719): Set the other attributes. - MessageEvent::dispatch_jsval( - this.upcast(), - this.upcast(), - message_clone.handle(), - Some(&document.origin().immutable().ascii_serialization()), - Some(&*source), - new_ports, - ); + if let Ok(ports) = structuredclone::read(this.upcast(), data, message_clone.handle_mut()) { + // Step 7.6, 7.7 + MessageEvent::dispatch_jsval( + this.upcast(), + this.upcast(), + message_clone.handle(), + Some(&source_origin.ascii_serialization()), + Some(&*source), + ports, + ); + } else { + // Step 4, fire messageerror. + MessageEvent::dispatch_error( + this.upcast(), + this.upcast(), + ); + } }); // FIXME(nox): Why are errors silenced here? // TODO(#12718): Use the "posted message task source". diff --git a/components/script/dom/worker.rs b/components/script/dom/worker.rs index 1a8d9c292dc..3b9830f8ded 100644 --- a/components/script/dom/worker.rs +++ b/components/script/dom/worker.rs @@ -5,6 +5,7 @@ use crate::compartments::enter_realm; use crate::dom::abstractworker::SimpleWorkerErrorHandler; use crate::dom::abstractworker::WorkerScriptMsg; +use crate::dom::bindings::codegen::Bindings::MessagePortBinding::PostMessageOptions; use crate::dom::bindings::codegen::Bindings::WorkerBinding; use crate::dom::bindings::codegen::Bindings::WorkerBinding::{WorkerMethods, WorkerOptions}; use crate::dom::bindings::error::{Error, ErrorResult, Fallible}; @@ -13,7 +14,8 @@ use crate::dom::bindings::refcounted::Trusted; use crate::dom::bindings::reflector::{reflect_dom_object, DomObject}; use crate::dom::bindings::root::DomRoot; use crate::dom::bindings::str::USVString; -use crate::dom::bindings::structuredclone::StructuredCloneData; +use crate::dom::bindings::structuredclone; +use crate::dom::bindings::trace::RootedTraceableBox; use crate::dom::dedicatedworkerglobalscope::{ DedicatedWorkerGlobalScope, DedicatedWorkerScriptMsg, }; @@ -27,10 +29,10 @@ use crossbeam_channel::{unbounded, Sender}; use devtools_traits::{DevtoolsPageInfo, ScriptToDevtoolsControlMsg}; use dom_struct::dom_struct; use ipc_channel::ipc; -use js::jsapi::JS_RequestInterruptCallback; +use js::jsapi::{Heap, JSObject, JS_RequestInterruptCallback}; use js::jsval::UndefinedValue; -use js::rust::HandleValue; -use script_traits::WorkerScriptLoadOrigin; +use js::rust::{CustomAutoRooter, CustomAutoRooterGuard, HandleValue}; +use script_traits::{StructuredSerializedData, WorkerScriptLoadOrigin}; use std::cell::Cell; use std::sync::atomic::{AtomicBool, Ordering}; use std::sync::Arc; @@ -140,7 +142,7 @@ impl Worker { pub fn handle_message( address: TrustedWorkerAddress, origin: String, - data: StructuredCloneData, + data: StructuredSerializedData, ) { let worker = address.root(); @@ -152,21 +154,34 @@ impl Worker { let target = worker.upcast(); let _ac = enter_realm(target); rooted!(in(*global.get_cx()) let mut message = UndefinedValue()); - assert!(data.read(&global, message.handle_mut())); - MessageEvent::dispatch_jsval(target, &global, message.handle(), Some(&origin), None, vec![]); + if let Ok(ports) = structuredclone::read(&global, data, message.handle_mut()) { + MessageEvent::dispatch_jsval( + target, + &global, + message.handle(), + Some(&origin), + None, + ports, + ); + } else { + // Step 4 of the "port post message steps" of the implicit messageport, fire messageerror. + MessageEvent::dispatch_error(target, &global); + } } pub fn dispatch_simple_error(address: TrustedWorkerAddress) { let worker = address.root(); worker.upcast().fire_event(atom!("error")); } -} -impl WorkerMethods for Worker { - // https://html.spec.whatwg.org/multipage/#dom-worker-postmessage - fn PostMessage(&self, cx: JSContext, message: HandleValue) -> ErrorResult { - rooted!(in(*cx) let transfer = UndefinedValue()); - let data = StructuredCloneData::write(*cx, message, transfer.handle())?; + /// https://html.spec.whatwg.org/multipage/#dom-dedicatedworkerglobalscope-postmessage + fn post_message_impl( + &self, + cx: JSContext, + message: HandleValue, + transfer: CustomAutoRooterGuard<Vec<*mut JSObject>>, + ) -> ErrorResult { + let data = structuredclone::write(cx, message, Some(transfer))?; let address = Trusted::new(self); // NOTE: step 9 of https://html.spec.whatwg.org/multipage/#dom-messageport-postmessage @@ -174,12 +189,44 @@ impl WorkerMethods for Worker { let _ = self.sender.send(DedicatedWorkerScriptMsg::CommonWorker( address, WorkerScriptMsg::DOMMessage { - origin: self.global().origin().immutable().ascii_serialization(), + origin: self.global().origin().immutable().clone(), data, }, )); Ok(()) } +} + +impl WorkerMethods for Worker { + /// https://html.spec.whatwg.org/multipage/#dom-worker-postmessage + fn PostMessage( + &self, + cx: JSContext, + message: HandleValue, + transfer: CustomAutoRooterGuard<Vec<*mut JSObject>>, + ) -> ErrorResult { + self.post_message_impl(cx, message, transfer) + } + + /// https://html.spec.whatwg.org/multipage/#dom-worker-postmessage + fn PostMessage_( + &self, + cx: JSContext, + message: HandleValue, + options: RootedTraceableBox<PostMessageOptions>, + ) -> ErrorResult { + let mut rooted = CustomAutoRooter::new( + options + .transfer + .as_ref() + .unwrap_or(&Vec::with_capacity(0)) + .iter() + .map(|js: &RootedTraceableBox<Heap<*mut JSObject>>| js.get()) + .collect(), + ); + let guard = CustomAutoRooterGuard::new(*cx, &mut rooted); + self.post_message_impl(cx, message, guard) + } #[allow(unsafe_code)] // https://html.spec.whatwg.org/multipage/#terminate-a-worker diff --git a/components/script/script_thread.rs b/components/script/script_thread.rs index fac11949d70..6a7a69651e7 100644 --- a/components/script/script_thread.rs +++ b/components/script/script_thread.rs @@ -38,7 +38,6 @@ use crate::dom::bindings::reflector::DomObject; use crate::dom::bindings::root::ThreadLocalStackRoots; use crate::dom::bindings::root::{Dom, DomRoot, MutNullableDom, RootCollection}; use crate::dom::bindings::str::DOMString; -use crate::dom::bindings::structuredclone::StructuredCloneData; use crate::dom::bindings::trace::JSTraceable; use crate::dom::bindings::utils::WRAP_CALLBACKS; use crate::dom::customelementregistry::{ @@ -133,6 +132,7 @@ use script_traits::CompositorEvent::{ CompositionEvent, KeyboardEvent, MouseButtonEvent, MouseMoveEvent, ResizeEvent, TouchEvent, WheelEvent, }; +use script_traits::StructuredSerializedData; use script_traits::{CompositorEvent, ConstellationControlMsg}; use script_traits::{ DiscardBrowsingContext, DocumentActivity, EventResult, HistoryEntryReplacement, @@ -1589,6 +1589,11 @@ impl ScriptThread { continue; } let window = document.window(); + + window + .upcast::<GlobalScope>() + .perform_a_message_port_garbage_collection_checkpoint(); + let pending_reflows = window.get_pending_reflow_count(); if pending_reflows > 0 { window.reflow(ReflowGoal::Full, ReflowReason::ImageLoaded); @@ -1867,12 +1872,14 @@ impl ScriptThread { source: source_pipeline_id, source_browsing_context, target_origin: origin, + source_origin, data, } => self.handle_post_message_msg( target_pipeline_id, source_pipeline_id, source_browsing_context, origin, + source_origin, data, ), ConstellationControlMsg::UpdatePipelineId( @@ -2525,7 +2532,8 @@ impl ScriptThread { source_pipeline_id: PipelineId, source_browsing_context: TopLevelBrowsingContextId, origin: Option<ImmutableOrigin>, - data: Vec<u8>, + source_origin: ImmutableOrigin, + data: StructuredSerializedData, ) { match { self.documents.borrow().find_window(pipeline_id) } { None => return warn!("postMessage after target pipeline {} closed.", pipeline_id), @@ -2547,7 +2555,7 @@ impl ScriptThread { Some(source) => source, }; // FIXME(#22512): enqueues a task; unnecessary delay. - window.post_message(origin, &*source, StructuredCloneData::Vector(data)) + window.post_message(origin, source_origin, &*source, data) }, } } diff --git a/components/script/serviceworker_manager.rs b/components/script/serviceworker_manager.rs index 6e7884a891e..109628ae94e 100644 --- a/components/script/serviceworker_manager.rs +++ b/components/script/serviceworker_manager.rs @@ -8,7 +8,6 @@ //! active_workers map use crate::dom::abstractworker::WorkerScriptMsg; -use crate::dom::bindings::structuredclone::StructuredCloneData; use crate::dom::serviceworkerglobalscope::{ServiceWorkerGlobalScope, ServiceWorkerScriptMsg}; use crate::dom::serviceworkerregistration::longest_prefix_match; use crossbeam_channel::{unbounded, Receiver, RecvError, Sender}; @@ -136,7 +135,6 @@ impl ServiceWorkerManager { fn forward_message(&self, msg: DOMMessage, sender: &Sender<ServiceWorkerScriptMsg>) { let DOMMessage { origin, data } = msg; - let data = StructuredCloneData::Vector(data); let _ = sender.send(ServiceWorkerScriptMsg::CommonWorker( WorkerScriptMsg::DOMMessage { origin, data }, )); diff --git a/components/script/task_source/port_message.rs b/components/script/task_source/port_message.rs index 8541f89bc64..e9d1766f521 100644 --- a/components/script/task_source/port_message.rs +++ b/components/script/task_source/port_message.rs @@ -9,7 +9,7 @@ use msg::constellation_msg::PipelineId; use std::fmt; #[derive(JSTraceable)] -pub struct PortMessageQueue(pub Box<ScriptChan + Send + 'static>, pub PipelineId); +pub struct PortMessageQueue(pub Box<dyn ScriptChan + Send + 'static>, pub PipelineId); impl Clone for PortMessageQueue { fn clone(&self) -> PortMessageQueue { @@ -26,11 +26,7 @@ impl fmt::Debug for PortMessageQueue { impl TaskSource for PortMessageQueue { const NAME: TaskSourceName = TaskSourceName::PortMessage; - fn queue_with_canceller<T>( - &self, - task: T, - canceller: &TaskCanceller, - ) -> Result<(), ()> + fn queue_with_canceller<T>(&self, task: T, canceller: &TaskCanceller) -> Result<(), ()> where T: TaskOnce + 'static, { |