/* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ use crate::dom::bindings::cell::DomRefCell; use crate::dom::bindings::codegen::Bindings::EventSourceBinding::EventSourceBinding::EventSourceMethods; use crate::dom::bindings::codegen::Bindings::WindowBinding::WindowMethods; use crate::dom::bindings::codegen::Bindings::WorkerGlobalScopeBinding::WorkerGlobalScopeMethods; 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::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; use crate::dom::workerglobalscope::WorkerGlobalScope; use crate::dom::workletglobalscope::WorkletGlobalScope; use crate::microtask::{Microtask, MicrotaskQueue}; use crate::script_runtime::{CommonScriptMsg, JSContext as SafeJSContext, ScriptChan, ScriptPort}; use crate::script_thread::{MainThreadScriptChan, ScriptThread}; use crate::task::TaskCanceller; use crate::task_source::dom_manipulation::DOMManipulationTaskSource; use crate::task_source::file_reading::FileReadingTaskSource; use crate::task_source::networking::NetworkingTaskSource; use crate::task_source::performance_timeline::PerformanceTimelineTaskSource; use crate::task_source::port_message::PortMessageQueue; use crate::task_source::remote_event::RemoteEventTaskSource; use crate::task_source::websocket::WebsocketTaskSource; use crate::task_source::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::{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::{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::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, VecDeque}; use std::ffi::CString; use std::rc::Rc; use std::sync::atomic::{AtomicBool, Ordering}; use std::sync::Arc; use time::{get_time, Timespec}; #[derive(JSTraceable)] pub struct AutoCloseWorker(Arc); impl Drop for AutoCloseWorker { fn drop(&mut self) { self.0.store(true, Ordering::SeqCst); } } #[dom_struct] pub struct GlobalScope { eventtarget: EventTarget, crypto: MutNullableDom, next_worker_id: Cell, /// The message-port router id for this global, if it is managing ports. message_port_state: DomRefCell, /// Pipeline id associated with this global. pipeline_id: PipelineId, /// A flag to indicate whether the developer tools has requested /// live updates from the worker. devtools_wants_updates: Cell, /// Timers used by the Console API. console_timers: DomRefCell>, /// For providing instructions to an optional devtools server. #[ignore_malloc_size_of = "channels are hard"] devtools_chan: Option>, /// For sending messages to the memory profiler. #[ignore_malloc_size_of = "channels are hard"] mem_profiler_chan: profile_mem::ProfilerChan, /// For sending messages to the time profiler. #[ignore_malloc_size_of = "channels are hard"] time_profiler_chan: profile_time::ProfilerChan, /// A handle for communicating messages to the constellation thread. #[ignore_malloc_size_of = "channels are hard"] script_to_constellation_chan: ScriptToConstellationChan, #[ignore_malloc_size_of = "channels are hard"] scheduler_chan: IpcSender, /// in_error_reporting_mode: Cell, /// Associated resource threads for use by DOM objects like XMLHttpRequest, /// including resource_thread, filemanager_thread and storage_thread resource_threads: ResourceThreads, timers: OneshotTimers, /// The origin of the globalscope origin: MutableOrigin, /// The microtask queue associated with this global. /// /// It is refcounted because windows in the same script thread share the /// same microtask queue. /// /// #[ignore_malloc_size_of = "Rc is hard"] microtask_queue: Rc, /// Vector storing closing references of all workers #[ignore_malloc_size_of = "Arc"] list_auto_close_worker: DomRefCell>, /// Vector storing references of all eventsources. event_source_tracker: DOMTracker, /// Storage for watching rejected promises waiting for some client to /// consume their rejection. /// Promises in this list have been rejected in the last turn of the /// event loop without the rejection being handled. /// Note that this can contain nullptrs in place of promises removed because /// they're consumed before it'd be reported. /// /// #[ignore_malloc_size_of = "mozjs"] uncaught_rejections: DomRefCell>>>, /// Promises in this list have previously been reported as rejected /// (because they were in the above list), but the rejection was handled /// in the last turn of the event loop. /// /// #[ignore_malloc_size_of = "mozjs"] consumed_rejections: DomRefCell>>>, /// True if headless mode. is_headless: bool, /// An optional string allowing the user agent to be set for testing. 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, } /// 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), /// 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), } /// 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, ), /// 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(ports) => { let context = self.context.clone(); let _ = self.task_source.queue_with_canceller( task!(process_complete_transfer: move || { let global = context.root(); let router_id = match global.port_router_id() { Some(router_id) => router_id, None => { // If not managing any ports, no transfer can succeed, // so just send back everything. let _ = global.script_to_constellation_chan().send( ScriptMsg::MessagePortTransferResult(None, vec![], ports), ); return; } }; let mut succeeded = vec![]; let mut failed = HashMap::new(); for (id, buffer) in ports.into_iter() { if global.is_managing_port(&id) { succeeded.push(id.clone()); global.complete_port_transfer(id, buffer); } else { failed.insert(id, buffer); } } let _ = global.script_to_constellation_chan().send( ScriptMsg::MessagePortTransferResult(Some(router_id), succeeded, failed), ); }), &self.canceller, ); }, MessagePortMsg::CompletePendingTransfer(port_id, buffer) => { let context = self.context.clone(); let _ = self.task_source.queue_with_canceller( task!(complete_pending: move || { let global = context.root(); global.complete_port_transfer(port_id, buffer); }), &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, devtools_chan: Option>, mem_profiler_chan: profile_mem::ProfilerChan, time_profiler_chan: profile_time::ProfilerChan, script_to_constellation_chan: ScriptToConstellationChan, scheduler_chan: IpcSender, resource_threads: ResourceThreads, timer_event_chan: IpcSender, origin: MutableOrigin, microtask_queue: Rc, is_headless: bool, 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)), pipeline_id, devtools_wants_updates: Default::default(), console_timers: DomRefCell::new(Default::default()), devtools_chan, mem_profiler_chan, time_profiler_chan, script_to_constellation_chan, scheduler_chan: scheduler_chan.clone(), in_error_reporting_mode: Default::default(), resource_threads, timers: OneshotTimers::new(timer_event_chan, scheduler_chan), origin, microtask_queue, list_auto_close_worker: Default::default(), event_source_tracker: DOMTracker::new(), uncaught_rejections: Default::default(), consumed_rejections: Default::default(), is_headless, user_agent, } } /// The message-port router Id of the global, if any fn port_router_id(&self) -> Option { if let MessagePortState::Managed(id, _message_ports) = &*self.message_port_state.borrow() { Some(id.clone()) } else { None } } /// Is this global managing a given port? fn is_managing_port(&self, port_id: &MessagePortId) -> bool { if let MessagePortState::Managed(_router_id, message_ports) = &*self.message_port_state.borrow() { return message_ports.contains_key(port_id); } false } /// Complete the transfer of a message-port. fn complete_port_transfer(&self, port_id: MessagePortId, tasks: VecDeque) { 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!("complete_port_transfer called for an unknown 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 { panic!("complete_port_transfer called for an unknown port."); }; 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; } /// 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."); } } /// 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."); } } /// 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."); } } /// // 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 = message_ports .iter() .filter_map(|(id, port_info)| match port_info { ManagedMessagePort::Pending(_, _) => Some(id.clone()), _ => None, }) .collect(); for id in to_be_added.iter() { let (id, port_info) = message_ports .remove_entry(&id) .expect("Collected port-id to match an entry"); match port_info { ManagedMessagePort::Pending(port_impl, dom_port) => { let new_port_info = ManagedMessagePort::Added(port_impl, dom_port); let present = message_ports.insert(id, new_port_info); assert!(present.is_none()); }, _ => panic!("Only pending ports should be found in to_be_added"), } } let _ = self.script_to_constellation_chan() .send(ScriptMsg::CompleteMessagePortTransfer( router_id.clone(), to_be_added, )); } 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 = 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) { 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) { self.list_auto_close_worker .borrow_mut() .push(AutoCloseWorker(closing_worker)); } pub fn track_event_source(&self, event_source: &EventSource) { self.event_source_tracker.track(event_source); } pub fn close_event_sources(&self) -> bool { let mut canceled_any_fetch = false; self.event_source_tracker .for_each( |event_source: DomRoot| match event_source.ReadyState() { 2 => {}, _ => { event_source.cancel(); canceled_any_fetch = true; }, }, ); canceled_any_fetch } /// Returns the global scope of the realm that the given DOM object's reflector /// was created in. #[allow(unsafe_code)] pub fn from_reflector(reflector: &T) -> DomRoot { unsafe { GlobalScope::from_object(*reflector.reflector().get_jsobject()) } } /// Returns the global scope of the realm that the given JS object was created in. #[allow(unsafe_code)] pub unsafe fn from_object(obj: *mut JSObject) -> DomRoot { assert!(!obj.is_null()); let global = GetNonCCWObjectGlobal(obj); global_scope_from_global_static(global) } /// Returns the global scope for the given JSContext #[allow(unsafe_code)] pub unsafe fn from_context(cx: *mut JSContext) -> DomRoot { let global = CurrentGlobalOrNull(cx); global_scope_from_global(global, cx) } /// Returns the global object of the realm that the given JS object /// was created in, after unwrapping any wrappers. #[allow(unsafe_code)] pub unsafe fn from_object_maybe_wrapped( mut obj: *mut JSObject, cx: *mut JSContext, ) -> DomRoot { if IsWrapper(obj) { obj = UnwrapObjectDynamic(obj, cx, /* stopAtWindowProxy = */ 0); assert!(!obj.is_null()); } GlobalScope::from_object(obj) } pub fn add_uncaught_rejection(&self, rejection: HandleObject) { self.uncaught_rejections .borrow_mut() .push(Heap::boxed(rejection.get())); } pub fn remove_uncaught_rejection(&self, rejection: HandleObject) { let mut uncaught_rejections = self.uncaught_rejections.borrow_mut(); if let Some(index) = uncaught_rejections .iter() .position(|promise| *promise == Heap::boxed(rejection.get())) { uncaught_rejections.remove(index); } } pub fn get_uncaught_rejections(&self) -> &DomRefCell>>> { &self.uncaught_rejections } pub fn add_consumed_rejection(&self, rejection: HandleObject) { self.consumed_rejections .borrow_mut() .push(Heap::boxed(rejection.get())); } pub fn remove_consumed_rejection(&self, rejection: HandleObject) { let mut consumed_rejections = self.consumed_rejections.borrow_mut(); if let Some(index) = consumed_rejections .iter() .position(|promise| *promise == Heap::boxed(rejection.get())) { consumed_rejections.remove(index); } } pub fn get_consumed_rejections(&self) -> &DomRefCell>>> { &self.consumed_rejections } #[allow(unsafe_code)] pub fn get_cx(&self) -> SafeJSContext { unsafe { SafeJSContext::from_ptr(Runtime::get()) } } pub fn crypto(&self) -> DomRoot { self.crypto.or_init(|| Crypto::new(self)) } /// Get next worker id. pub fn get_next_worker_id(&self) -> WorkerId { let worker_id = self.next_worker_id.get(); let WorkerId(id_num) = worker_id; self.next_worker_id.set(WorkerId(id_num + 1)); worker_id } pub fn live_devtools_updates(&self) -> bool { self.devtools_wants_updates.get() } pub fn set_devtools_wants_updates(&self, value: bool) { self.devtools_wants_updates.set(value); } pub fn time(&self, label: DOMString) -> Result<(), ()> { let mut timers = self.console_timers.borrow_mut(); if timers.len() >= 10000 { return Err(()); } match timers.entry(label) { Entry::Vacant(entry) => { entry.insert(timestamp_in_ms(get_time())); Ok(()) }, Entry::Occupied(_) => Err(()), } } pub fn time_end(&self, label: &str) -> Result { self.console_timers .borrow_mut() .remove(label) .ok_or(()) .map(|start| timestamp_in_ms(get_time()) - start) } /// Get an `&IpcSender` to send messages /// to the devtools thread when available. pub fn devtools_chan(&self) -> Option<&IpcSender> { self.devtools_chan.as_ref() } /// Get a sender to the memory profiler thread. pub fn mem_profiler_chan(&self) -> &profile_mem::ProfilerChan { &self.mem_profiler_chan } /// Get a sender to the time profiler thread. pub fn time_profiler_chan(&self) -> &profile_time::ProfilerChan { &self.time_profiler_chan } /// Get a sender to the constellation thread. pub fn script_to_constellation_chan(&self) -> &ScriptToConstellationChan { &self.script_to_constellation_chan } pub fn scheduler_chan(&self) -> &IpcSender { &self.scheduler_chan } /// Get the `PipelineId` for this global scope. pub fn pipeline_id(&self) -> PipelineId { self.pipeline_id } /// Get the origin for this global scope pub fn origin(&self) -> &MutableOrigin { &self.origin } pub fn image_cache(&self) -> Arc { if let Some(window) = self.downcast::() { return window.image_cache(); } if let Some(worker) = self.downcast::() { return worker.image_cache(); } if let Some(worker) = self.downcast::() { return worker.image_cache(); } unreachable!(); } /// Get the [base url](https://html.spec.whatwg.org/multipage/#api-base-url) /// for this global scope. pub fn api_base_url(&self) -> ServoUrl { if let Some(window) = self.downcast::() { // https://html.spec.whatwg.org/multipage/#script-settings-for-browsing-contexts:api-base-url return window.Document().base_url(); } if let Some(worker) = self.downcast::() { // https://html.spec.whatwg.org/multipage/#script-settings-for-workers:api-base-url return worker.get_url().clone(); } if let Some(worklet) = self.downcast::() { // https://drafts.css-houdini.org/worklets/#script-settings-for-worklets return worklet.base_url(); } unreachable!(); } /// Get the URL for this global scope. pub fn get_url(&self) -> ServoUrl { if let Some(window) = self.downcast::() { return window.get_url(); } if let Some(worker) = self.downcast::() { return worker.get_url().clone(); } if let Some(worklet) = self.downcast::() { // TODO: is this the right URL to return? return worklet.base_url(); } unreachable!(); } /// Extract a `Window`, panic if the global object is not a `Window`. pub fn as_window(&self) -> &Window { self.downcast::().expect("expected a Window scope") } /// pub fn report_an_error(&self, error_info: ErrorInfo, value: HandleValue) { // Step 1. if self.in_error_reporting_mode.get() { return; } // Step 2. self.in_error_reporting_mode.set(true); // Steps 3-6. // FIXME(#13195): muted errors. let event = ErrorEvent::new( self, atom!("error"), EventBubbles::DoesNotBubble, EventCancelable::Cancelable, error_info.message.as_str().into(), error_info.filename.as_str().into(), error_info.lineno, error_info.column, value, ); // Step 7. let event_status = event.upcast::().fire(self.upcast::()); // Step 8. self.in_error_reporting_mode.set(false); // Step 9. if event_status == EventStatus::NotCanceled { // https://html.spec.whatwg.org/multipage/#runtime-script-errors-2 if let Some(dedicated) = self.downcast::() { dedicated.forward_error_to_worker_object(error_info); } } } /// Get the `&ResourceThreads` for this global scope. pub fn resource_threads(&self) -> &ResourceThreads { &self.resource_threads } /// Get the `CoreResourceThread` for this global scope. pub fn core_resource_thread(&self) -> CoreResourceThread { self.resource_threads().sender() } /// `ScriptChan` to send messages to the event loop of this global scope. pub fn script_chan(&self) -> Box { if let Some(window) = self.downcast::() { return MainThreadScriptChan(window.main_thread_script_chan().clone()).clone(); } if let Some(worker) = self.downcast::() { return worker.script_chan(); } unreachable!(); } /// `TaskSource` to send messages to the networking task source of /// this global scope. pub fn networking_task_source(&self) -> NetworkingTaskSource { if let Some(window) = self.downcast::() { return window.task_manager().networking_task_source(); } if let Some(worker) = self.downcast::() { return worker.networking_task_source(); } unreachable!(); } /// `TaskSource` to send messages to the port message queue of /// this global scope. pub fn port_message_queue(&self) -> PortMessageQueue { if let Some(window) = self.downcast::() { return window.task_manager().port_message_queue(); } if let Some(worker) = self.downcast::() { return worker.port_message_queue(); } unreachable!(); } /// `TaskSource` to send messages to the remote-event task source of /// this global scope. pub fn remote_event_task_source(&self) -> RemoteEventTaskSource { if let Some(window) = self.downcast::() { return window.task_manager().remote_event_task_source(); } if let Some(worker) = self.downcast::() { return worker.remote_event_task_source(); } unreachable!(); } /// `TaskSource` to send messages to the websocket task source of /// this global scope. pub fn websocket_task_source(&self) -> WebsocketTaskSource { if let Some(window) = self.downcast::() { return window.task_manager().websocket_task_source(); } if let Some(worker) = self.downcast::() { return worker.websocket_task_source(); } unreachable!(); } /// Evaluate JS code on this global scope. pub fn evaluate_js_on_global_with_result(&self, code: &str, rval: MutableHandleValue) -> bool { self.evaluate_script_on_global_with_result(code, "", rval, 1) } /// Evaluate a JS script on this global scope. #[allow(unsafe_code)] pub fn evaluate_script_on_global_with_result( &self, code: &str, filename: &str, rval: MutableHandleValue, line_number: u32, ) -> bool { let metadata = profile_time::TimerMetadata { url: if filename.is_empty() { self.get_url().as_str().into() } else { filename.into() }, iframe: profile_time::TimerMetadataFrameType::RootWindow, incremental: profile_time::TimerMetadataReflowType::FirstReflow, }; profile_time::profile( profile_time::ProfilerCategory::ScriptEvaluate, Some(metadata), self.time_profiler_chan().clone(), || { let cx = self.get_cx(); let globalhandle = self.reflector().get_jsobject(); let filename = CString::new(filename).unwrap(); let _ac = JSAutoRealm::new(*cx, globalhandle.get()); let _aes = AutoEntryScript::new(self); let options = CompileOptionsWrapper::new(*cx, filename.as_ptr(), line_number); debug!("evaluating Dom string"); let result = unsafe { EvaluateUtf8( *cx, options.ptr, code.as_ptr() as *const _, code.len() as libc::size_t, rval, ) }; if !result { debug!("error evaluating Dom string"); unsafe { report_pending_exception(*cx, true) }; } maybe_resume_unwind(); result }, ) } pub fn schedule_callback( &self, callback: OneshotTimerCallback, duration: MsDuration, ) -> OneshotTimerHandle { self.timers .schedule_callback(callback, duration, self.timer_source()) } pub fn unschedule_callback(&self, handle: OneshotTimerHandle) { self.timers.unschedule_callback(handle); } pub fn set_timeout_or_interval( &self, callback: TimerCallback, arguments: Vec, timeout: i32, is_interval: IsInterval, ) -> i32 { self.timers.set_timeout_or_interval( self, callback, arguments, timeout, is_interval, self.timer_source(), ) } pub fn clear_timeout_or_interval(&self, handle: i32) { self.timers.clear_timeout_or_interval(self, handle) } pub fn fire_timer(&self, handle: TimerEventId) { self.timers.fire_timer(handle, self) } pub fn resume(&self) { self.timers.resume() } pub fn suspend(&self) { self.timers.suspend() } pub fn slow_down_timers(&self) { self.timers.slow_down() } pub fn speed_up_timers(&self) { self.timers.speed_up() } fn timer_source(&self) -> TimerSource { if self.is::() { return TimerSource::FromWindow(self.pipeline_id()); } if self.is::() { return TimerSource::FromWorker; } unreachable!(); } /// Returns the task canceller of this global to ensure that everything is /// properly cancelled when the global scope is destroyed. pub fn task_canceller(&self, name: TaskSourceName) -> TaskCanceller { if let Some(window) = self.downcast::() { return window.task_manager().task_canceller(name); } if let Some(worker) = self.downcast::() { // Note: the "name" is not passed to the worker, // because 'closing' it only requires one task canceller for all task sources. // https://html.spec.whatwg.org/multipage/#dom-workerglobalscope-closing return worker.task_canceller(); } unreachable!(); } /// Perform a microtask checkpoint. pub fn perform_a_microtask_checkpoint(&self) { self.microtask_queue.checkpoint( self.get_cx(), |_| Some(DomRoot::from_ref(self)), vec![DomRoot::from_ref(self)], ); } /// Enqueue a microtask for subsequent execution. pub fn enqueue_microtask(&self, job: Microtask) { self.microtask_queue.enqueue(job, self.get_cx()); } /// Create a new sender/receiver pair that can be used to implement an on-demand /// event loop. Used for implementing web APIs that require blocking semantics /// without resorting to nested event loops. pub fn new_script_pair(&self) -> (Box, Box) { if let Some(window) = self.downcast::() { return window.new_script_pair(); } if let Some(worker) = self.downcast::() { return worker.new_script_pair(); } unreachable!(); } /// Returns the microtask queue of this global. pub fn microtask_queue(&self) -> &Rc { &self.microtask_queue } /// Process a single event as if it were the next event /// in the thread queue for this global scope. pub fn process_event(&self, msg: CommonScriptMsg) { if self.is::() { return ScriptThread::process_event(msg); } if let Some(worker) = self.downcast::() { return worker.process_event(msg); } unreachable!(); } pub fn dom_manipulation_task_source(&self) -> DOMManipulationTaskSource { if let Some(window) = self.downcast::() { return window.task_manager().dom_manipulation_task_source(); } if let Some(worker) = self.downcast::() { return worker.dom_manipulation_task_source(); } unreachable!(); } /// Channel to send messages to the file reading task source of /// this of this global scope. pub fn file_reading_task_source(&self) -> FileReadingTaskSource { if let Some(window) = self.downcast::() { return window.task_manager().file_reading_task_source(); } if let Some(worker) = self.downcast::() { return worker.file_reading_task_source(); } unreachable!(); } pub fn runtime_handle(&self) -> ParentRuntime { if self.is::() { ScriptThread::runtime_handle() } else if let Some(worker) = self.downcast::() { worker.runtime_handle() } else { unreachable!() } } /// Returns the ["current"] global object. /// /// ["current"]: https://html.spec.whatwg.org/multipage/#current #[allow(unsafe_code)] pub fn current() -> Option> { unsafe { let cx = Runtime::get(); assert!(!cx.is_null()); let global = CurrentGlobalOrNull(cx); if global.is_null() { None } else { Some(global_scope_from_global(global, cx)) } } } /// Returns the ["entry"] global object. /// /// ["entry"]: https://html.spec.whatwg.org/multipage/#entry pub fn entry() -> DomRoot { entry_global() } /// Returns the ["incumbent"] global object. /// /// ["incumbent"]: https://html.spec.whatwg.org/multipage/#incumbent pub fn incumbent() -> Option> { incumbent_global() } pub fn performance(&self) -> DomRoot { if let Some(window) = self.downcast::() { return window.Performance(); } if let Some(worker) = self.downcast::() { return worker.Performance(); } unreachable!(); } /// Channel to send messages to the performance timeline task source /// of this global scope. pub fn performance_timeline_task_source(&self) -> PerformanceTimelineTaskSource { if let Some(window) = self.downcast::() { return window.task_manager().performance_timeline_task_source(); } if let Some(worker) = self.downcast::() { return worker.performance_timeline_task_source(); } unreachable!(); } pub fn is_headless(&self) -> bool { self.is_headless } pub fn get_user_agent(&self) -> Cow<'static, str> { self.user_agent.clone() } /// https://www.w3.org/TR/CSP/#get-csp-of-object pub fn get_csp_list(&self) -> Option { if let Some(window) = self.downcast::() { return window.Document().get_csp_list().map(|c| c.clone()); } // TODO: Worker and Worklet global scopes. None } } fn timestamp_in_ms(time: Timespec) -> u64 { (time.sec * 1000 + (time.nsec / 1000000) as i64) as u64 } /// Returns the Rust global scope from a JS global object. #[allow(unsafe_code)] unsafe fn global_scope_from_global( global: *mut JSObject, cx: *mut JSContext, ) -> DomRoot { assert!(!global.is_null()); let clasp = get_object_class(global); assert_ne!( ((*clasp).flags & (JSCLASS_IS_DOMJSCLASS | JSCLASS_IS_GLOBAL)), 0 ); root_from_object(global, cx).unwrap() } /// Returns the Rust global scope from a JS global object. #[allow(unsafe_code)] unsafe fn global_scope_from_global_static(global: *mut JSObject) -> DomRoot { assert!(!global.is_null()); let clasp = get_object_class(global); assert_ne!( ((*clasp).flags & (JSCLASS_IS_DOMJSCLASS | JSCLASS_IS_GLOBAL)), 0 ); root_from_object_static(global).unwrap() }