/* 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::compartments::InCompartment; use crate::dom::bindings::cell::DomRefCell; use crate::dom::bindings::codegen::Bindings::FunctionBinding::Function; use crate::dom::bindings::codegen::Bindings::RequestBinding::RequestInit; use crate::dom::bindings::codegen::Bindings::WorkerBinding::WorkerType; use crate::dom::bindings::codegen::Bindings::WorkerGlobalScopeBinding::WorkerGlobalScopeMethods; use crate::dom::bindings::codegen::UnionTypes::RequestOrUSVString; use crate::dom::bindings::error::{report_pending_exception, Error, ErrorResult, Fallible}; use crate::dom::bindings::inheritance::Castable; use crate::dom::bindings::reflector::DomObject; use crate::dom::bindings::root::{DomRoot, MutNullableDom}; use crate::dom::bindings::settings_stack::AutoEntryScript; use crate::dom::bindings::str::{DOMString, USVString}; use crate::dom::bindings::trace::RootedTraceableBox; use crate::dom::crypto::Crypto; use crate::dom::dedicatedworkerglobalscope::DedicatedWorkerGlobalScope; use crate::dom::globalscope::GlobalScope; use crate::dom::performance::Performance; use crate::dom::promise::Promise; use crate::dom::serviceworkerglobalscope::ServiceWorkerGlobalScope; use crate::dom::window::{base64_atob, base64_btoa}; use crate::dom::workerlocation::WorkerLocation; use crate::dom::workernavigator::WorkerNavigator; use crate::fetch; use crate::script_runtime::JSContext as SafeJSContext; use crate::script_runtime::{get_reports, CommonScriptMsg, Runtime, ScriptChan, ScriptPort}; 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::remote_event::RemoteEventTaskSource; use crate::task_source::websocket::WebsocketTaskSource; use crate::timers::{IsInterval, TimerCallback}; use crossbeam_channel::Receiver; use devtools_traits::{DevtoolScriptControlMsg, WorkerId}; use dom_struct::dom_struct; use ipc_channel::ipc::IpcSender; use js::jsapi::{JSAutoRealm, JSContext}; use js::jsval::UndefinedValue; use js::panic::maybe_resume_unwind; use js::rust::{HandleValue, ParentRuntime}; use msg::constellation_msg::PipelineId; use net_traits::request::{ CredentialsMode, Destination, ParserMetadata, RequestBuilder as NetRequestInit, }; use net_traits::IpcSend; use script_traits::WorkerGlobalScopeInit; use script_traits::{TimerEvent, TimerEventId}; use servo_url::{MutableOrigin, ServoUrl}; use std::cell::Ref; use std::default::Default; use std::rc::Rc; use std::sync::atomic::{AtomicBool, Ordering}; use std::sync::Arc; use time::precise_time_ns; pub fn prepare_workerscope_init( global: &GlobalScope, devtools_sender: Option>, ) -> WorkerGlobalScopeInit { let init = WorkerGlobalScopeInit { resource_threads: global.resource_threads().clone(), mem_profiler_chan: global.mem_profiler_chan().clone(), to_devtools_sender: global.devtools_chan().cloned(), time_profiler_chan: global.time_profiler_chan().clone(), from_devtools_sender: devtools_sender, script_to_constellation_chan: global.script_to_constellation_chan().clone(), scheduler_chan: global.scheduler_chan().clone(), worker_id: global.get_next_worker_id(), pipeline_id: global.pipeline_id(), origin: global.origin().immutable().clone(), is_headless: global.is_headless(), user_agent: global.get_user_agent(), }; init } // https://html.spec.whatwg.org/multipage/#the-workerglobalscope-common-interface #[dom_struct] pub struct WorkerGlobalScope { globalscope: GlobalScope, worker_name: DOMString, worker_type: WorkerType, worker_id: WorkerId, worker_url: DomRefCell, #[ignore_malloc_size_of = "Arc"] closing: Option>, #[ignore_malloc_size_of = "Defined in js"] runtime: Runtime, location: MutNullableDom, navigator: MutNullableDom, #[ignore_malloc_size_of = "Defined in ipc-channel"] /// Optional `IpcSender` for sending the `DevtoolScriptControlMsg` /// to the server from within the worker from_devtools_sender: Option>, #[ignore_malloc_size_of = "Defined in std"] /// This `Receiver` will be ignored later if the corresponding /// `IpcSender` doesn't exist from_devtools_receiver: Receiver, navigation_start_precise: u64, performance: MutNullableDom, } impl WorkerGlobalScope { pub fn new_inherited( init: WorkerGlobalScopeInit, worker_name: DOMString, worker_type: WorkerType, worker_url: ServoUrl, runtime: Runtime, from_devtools_receiver: Receiver, timer_event_chan: IpcSender, closing: Option>, ) -> Self { Self { globalscope: GlobalScope::new_inherited( init.pipeline_id, init.to_devtools_sender, init.mem_profiler_chan, init.time_profiler_chan, init.script_to_constellation_chan, init.scheduler_chan, init.resource_threads, timer_event_chan, MutableOrigin::new(init.origin), runtime.microtask_queue.clone(), init.is_headless, init.user_agent, ), worker_id: init.worker_id, worker_name, worker_type, worker_url: DomRefCell::new(worker_url), closing, runtime, location: Default::default(), navigator: Default::default(), from_devtools_sender: init.from_devtools_sender, from_devtools_receiver, navigation_start_precise: precise_time_ns(), performance: Default::default(), } } pub fn runtime_handle(&self) -> ParentRuntime { self.runtime.prepare_for_new_child() } pub fn from_devtools_sender(&self) -> Option> { self.from_devtools_sender.clone() } pub fn from_devtools_receiver(&self) -> &Receiver { &self.from_devtools_receiver } pub fn get_cx(&self) -> *mut JSContext { self.runtime.cx() } pub fn is_closing(&self) -> bool { if let Some(ref closing) = self.closing { closing.load(Ordering::SeqCst) } else { false } } pub fn get_url(&self) -> Ref { self.worker_url.borrow() } pub fn set_url(&self, url: ServoUrl) { *self.worker_url.borrow_mut() = url; } pub fn get_worker_id(&self) -> WorkerId { self.worker_id.clone() } pub fn task_canceller(&self) -> TaskCanceller { TaskCanceller { cancelled: self.closing.clone(), } } pub fn pipeline_id(&self) -> PipelineId { self.globalscope.pipeline_id() } } impl WorkerGlobalScopeMethods for WorkerGlobalScope { // https://html.spec.whatwg.org/multipage/#dom-workerglobalscope-self fn Self_(&self) -> DomRoot { DomRoot::from_ref(self) } // https://html.spec.whatwg.org/multipage/#dom-workerglobalscope-location fn Location(&self) -> DomRoot { self.location .or_init(|| WorkerLocation::new(self, self.worker_url.borrow().clone())) } // https://html.spec.whatwg.org/multipage/#handler-workerglobalscope-onerror error_event_handler!(error, GetOnerror, SetOnerror); // https://html.spec.whatwg.org/multipage/#dom-workerglobalscope-importscripts fn ImportScripts(&self, url_strings: Vec) -> ErrorResult { let mut urls = Vec::with_capacity(url_strings.len()); for url in url_strings { let url = self.worker_url.borrow().join(&url); match url { Ok(url) => urls.push(url), Err(_) => return Err(Error::Syntax), }; } rooted!(in(self.runtime.cx()) let mut rval = UndefinedValue()); for url in urls { let global_scope = self.upcast::(); let request = NetRequestInit::new(url.clone()) .destination(Destination::Script) .credentials_mode(CredentialsMode::Include) .parser_metadata(ParserMetadata::NotParserInserted) .use_url_credentials(true) .origin(global_scope.origin().immutable().clone()) .pipeline_id(Some(self.upcast::().pipeline_id())) .referrer_policy(None); let (url, source) = match fetch::load_whole_resource( request, &global_scope.resource_threads().sender(), &global_scope, ) { Err(_) => return Err(Error::Network), Ok((metadata, bytes)) => (metadata.final_url, String::from_utf8(bytes).unwrap()), }; let result = self.runtime.evaluate_script( self.reflector().get_jsobject(), &source, url.as_str(), 1, rval.handle_mut(), ); maybe_resume_unwind(); match result { Ok(_) => (), Err(_) => { println!("evaluate_script failed"); return Err(Error::JSFailed); }, } } Ok(()) } // https://html.spec.whatwg.org/multipage/#dom-worker-navigator fn Navigator(&self) -> DomRoot { self.navigator.or_init(|| WorkerNavigator::new(self)) } // https://html.spec.whatwg.org/multipage/#dfn-Crypto fn Crypto(&self) -> DomRoot { self.upcast::().crypto() } // https://html.spec.whatwg.org/multipage/#dom-windowbase64-btoa fn Btoa(&self, btoa: DOMString) -> Fallible { base64_btoa(btoa) } // https://html.spec.whatwg.org/multipage/#dom-windowbase64-atob fn Atob(&self, atob: DOMString) -> Fallible { base64_atob(atob) } // https://html.spec.whatwg.org/multipage/#dom-windowtimers-settimeout fn SetTimeout( &self, _cx: SafeJSContext, callback: Rc, timeout: i32, args: Vec, ) -> i32 { self.upcast::().set_timeout_or_interval( TimerCallback::FunctionTimerCallback(callback), args, timeout, IsInterval::NonInterval, ) } // https://html.spec.whatwg.org/multipage/#dom-windowtimers-settimeout fn SetTimeout_( &self, _cx: SafeJSContext, callback: DOMString, timeout: i32, args: Vec, ) -> i32 { self.upcast::().set_timeout_or_interval( TimerCallback::StringTimerCallback(callback), args, timeout, IsInterval::NonInterval, ) } // https://html.spec.whatwg.org/multipage/#dom-windowtimers-cleartimeout fn ClearTimeout(&self, handle: i32) { self.upcast::() .clear_timeout_or_interval(handle); } // https://html.spec.whatwg.org/multipage/#dom-windowtimers-setinterval fn SetInterval( &self, _cx: SafeJSContext, callback: Rc, timeout: i32, args: Vec, ) -> i32 { self.upcast::().set_timeout_or_interval( TimerCallback::FunctionTimerCallback(callback), args, timeout, IsInterval::Interval, ) } // https://html.spec.whatwg.org/multipage/#dom-windowtimers-setinterval fn SetInterval_( &self, _cx: SafeJSContext, callback: DOMString, timeout: i32, args: Vec, ) -> i32 { self.upcast::().set_timeout_or_interval( TimerCallback::StringTimerCallback(callback), args, timeout, IsInterval::Interval, ) } // https://html.spec.whatwg.org/multipage/#dom-windowtimers-clearinterval fn ClearInterval(&self, handle: i32) { self.ClearTimeout(handle); } #[allow(unrooted_must_root)] // https://fetch.spec.whatwg.org/#fetch-method fn Fetch( &self, input: RequestOrUSVString, init: RootedTraceableBox, comp: InCompartment, ) -> Rc { fetch::Fetch(self.upcast(), input, init, comp) } // https://w3c.github.io/hr-time/#the-performance-attribute fn Performance(&self) -> DomRoot { self.performance.or_init(|| { let global_scope = self.upcast::(); Performance::new(global_scope, self.navigation_start_precise) }) } // https://html.spec.whatwg.org/multipage/#dom-origin fn Origin(&self) -> USVString { USVString( self.upcast::() .origin() .immutable() .ascii_serialization(), ) } } impl WorkerGlobalScope { #[allow(unsafe_code)] pub fn execute_script(&self, source: DOMString) { let _aes = AutoEntryScript::new(self.upcast()); rooted!(in(self.runtime.cx()) let mut rval = UndefinedValue()); match self.runtime.evaluate_script( self.reflector().get_jsobject(), &source, self.worker_url.borrow().as_str(), 1, rval.handle_mut(), ) { Ok(_) => (), Err(_) => { if self.is_closing() { println!("evaluate_script failed (terminated)"); } else { // TODO: An error needs to be dispatched to the parent. // https://github.com/servo/servo/issues/6422 println!("evaluate_script failed"); unsafe { let _ac = JSAutoRealm::new( self.runtime.cx(), self.reflector().get_jsobject().get(), ); report_pending_exception(self.runtime.cx(), true); } } }, } } pub fn script_chan(&self) -> Box { let dedicated = self.downcast::(); let service_worker = self.downcast::(); if let Some(dedicated) = dedicated { return dedicated.script_chan(); } else if let Some(service_worker) = service_worker { return service_worker.script_chan(); } else { panic!("need to implement a sender for SharedWorker") } } pub fn dom_manipulation_task_source(&self) -> DOMManipulationTaskSource { DOMManipulationTaskSource(self.script_chan(), self.pipeline_id()) } pub fn file_reading_task_source(&self) -> FileReadingTaskSource { FileReadingTaskSource(self.script_chan(), self.pipeline_id()) } pub fn networking_task_source(&self) -> NetworkingTaskSource { NetworkingTaskSource(self.script_chan(), self.pipeline_id()) } pub fn performance_timeline_task_source(&self) -> PerformanceTimelineTaskSource { PerformanceTimelineTaskSource(self.script_chan(), self.pipeline_id()) } pub fn remote_event_task_source(&self) -> RemoteEventTaskSource { RemoteEventTaskSource(self.script_chan(), self.pipeline_id()) } pub fn websocket_task_source(&self) -> WebsocketTaskSource { WebsocketTaskSource(self.script_chan(), self.pipeline_id()) } pub fn new_script_pair(&self) -> (Box, Box) { let dedicated = self.downcast::(); if let Some(dedicated) = dedicated { return dedicated.new_script_pair(); } else { panic!("need to implement a sender for SharedWorker/ServiceWorker") } } pub fn process_event(&self, msg: CommonScriptMsg) { match msg { CommonScriptMsg::Task(_, task, _, _) => task.run_box(), CommonScriptMsg::CollectReports(reports_chan) => { let cx = self.get_cx(); let path_seg = format!("url({})", self.get_url()); let reports = get_reports(cx, path_seg); reports_chan.send(reports); }, } } pub fn handle_fire_timer(&self, timer_id: TimerEventId) { self.upcast::().fire_timer(timer_id); } pub fn close(&self) { if let Some(ref closing) = self.closing { closing.store(true, Ordering::SeqCst); } } }