diff options
author | yvt <i@yvt.jp> | 2021-07-10 17:24:27 +0900 |
---|---|---|
committer | yvt <i@yvt.jp> | 2021-07-10 17:55:42 +0900 |
commit | 01a7de50ab1843d85295f9dccad7f4c099e7208c (patch) | |
tree | ee53fb6e8889deb7b880ee969e6c662e6128d210 /components/script/script_runtime.rs | |
parent | ff8d2cdbbfc7a9dc7f38b7dd47cb350fde39388f (diff) | |
parent | 94b613fbdaa2b98f2179fc0bbda13c64e6fa0d38 (diff) | |
download | servo-01a7de50ab1843d85295f9dccad7f4c099e7208c.tar.gz servo-01a7de50ab1843d85295f9dccad7f4c099e7208c.zip |
Merge remote-tracking branch 'upstream/master' into feat-cow-infra
`tests/wpt/web-platform-tests/html/browsers/origin/cross-origin-objects/cross-origin-objects.html`
was reverted to the upstream version.
Diffstat (limited to 'components/script/script_runtime.rs')
-rw-r--r-- | components/script/script_runtime.rs | 1115 |
1 files changed, 867 insertions, 248 deletions
diff --git a/components/script/script_runtime.rs b/components/script/script_runtime.rs index f0377594881..9c61c2fe062 100644 --- a/components/script/script_runtime.rs +++ b/components/script/script_runtime.rs @@ -1,47 +1,108 @@ /* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ //! The script runtime contains common traits and structs commonly used by the //! script thread, the dom, and the worker threads. -use dom::bindings::codegen::Bindings::PromiseBinding::PromiseJobCallback; -use dom::bindings::js::{RootCollection, RootCollectionPtr, trace_roots}; -use dom::bindings::refcounted::{LiveDOMReferences, trace_refcounted_objects}; -use dom::bindings::settings_stack; -use dom::bindings::trace::{JSTraceable, trace_traceables}; -use dom::bindings::utils::{self, DOM_CALLBACKS}; -use dom::globalscope::GlobalScope; -use js::glue::CollectServoSizes; -use js::jsapi::{DisableIncrementalGC, GCDescription, GCProgress, HandleObject}; -use js::jsapi::{JSContext, JS_GetRuntime, JSRuntime, JSTracer, SetDOMCallbacks, SetGCSliceCallback}; -use js::jsapi::{JSGCInvocationKind, JSGCStatus, JS_AddExtraGCRootsTracer, JS_SetGCCallback}; -use js::jsapi::{JSGCMode, JSGCParamKey, JS_SetGCParameter, JS_SetGlobalJitCompilerOption}; -use js::jsapi::{JSJitCompilerOption, JS_SetOffthreadIonCompilationEnabled, JS_SetParallelParsingEnabled}; -use js::jsapi::{JSObject, RuntimeOptionsRef, SetPreserveWrapperCallback, SetEnqueuePromiseJobCallback}; +#![allow(dead_code)] + +use crate::body::BodyMixin; +use crate::dom::bindings::codegen::Bindings::PromiseBinding::PromiseJobCallback; +use crate::dom::bindings::codegen::Bindings::ResponseBinding::ResponseBinding::ResponseMethods; +use crate::dom::bindings::codegen::Bindings::ResponseBinding::ResponseType as DOMResponseType; +use crate::dom::bindings::conversions::get_dom_class; +use crate::dom::bindings::conversions::private_from_object; +use crate::dom::bindings::conversions::root_from_handleobject; +use crate::dom::bindings::error::{throw_dom_exception, Error}; +use crate::dom::bindings::inheritance::Castable; +use crate::dom::bindings::refcounted::{trace_refcounted_objects, LiveDOMReferences}; +use crate::dom::bindings::refcounted::{Trusted, TrustedPromise}; +use crate::dom::bindings::reflector::DomObject; +use crate::dom::bindings::root::trace_roots; +use crate::dom::bindings::settings_stack; +use crate::dom::bindings::trace::{trace_traceables, JSTraceable}; +use crate::dom::bindings::utils::{self, DOM_CALLBACKS}; +use crate::dom::event::{Event, EventBubbles, EventCancelable, EventStatus}; +use crate::dom::eventtarget::EventTarget; +use crate::dom::globalscope::GlobalScope; +use crate::dom::promise::Promise; +use crate::dom::promiserejectionevent::PromiseRejectionEvent; +use crate::dom::response::Response; +use crate::microtask::{EnqueuedPromiseCallback, Microtask, MicrotaskQueue}; +use crate::realms::{AlreadyInRealm, InRealm}; +use crate::script_module::EnsureModuleHooksInitialized; +use crate::script_thread::trace_thread; +use crate::task::TaskBox; +use crate::task_source::networking::NetworkingTaskSource; +use crate::task_source::{TaskSource, TaskSourceName}; +use js::glue::{CollectServoSizes, CreateJobQueue, DeleteJobQueue, DispatchableRun}; +use js::glue::{JobQueueTraps, RUST_js_GetErrorMessage, SetBuildId, StreamConsumerConsumeChunk}; +use js::glue::{ + StreamConsumerNoteResponseURLs, StreamConsumerStreamEnd, StreamConsumerStreamError, +}; +use js::jsapi::ContextOptionsRef; +use js::jsapi::GetPromiseUserInputEventHandlingState; +use js::jsapi::InitConsumeStreamCallback; +use js::jsapi::InitDispatchToEventLoop; +use js::jsapi::MimeType; +use js::jsapi::PromiseUserInputEventHandlingState; +use js::jsapi::StreamConsumer as JSStreamConsumer; +use js::jsapi::{BuildIdCharVector, DisableIncrementalGC, GCDescription, GCProgress}; +use js::jsapi::{Dispatchable as JSRunnable, Dispatchable_MaybeShuttingDown}; +use js::jsapi::{ + GCReason, JSGCInvocationKind, JSGCStatus, JS_AddExtraGCRootsTracer, + JS_RequestInterruptCallback, JS_SetGCCallback, +}; +use js::jsapi::{HandleObject, Heap, JobQueue}; +use js::jsapi::{JSContext as RawJSContext, JSTracer, SetDOMCallbacks, SetGCSliceCallback}; +use js::jsapi::{JSGCParamKey, JS_SetGCParameter, JS_SetGlobalJitCompilerOption}; +use js::jsapi::{ + JSJitCompilerOption, JS_SetOffthreadIonCompilationEnabled, JS_SetParallelParsingEnabled, +}; +use js::jsapi::{JSObject, PromiseRejectionHandlingState, SetPreserveWrapperCallbacks}; use js::jsapi::{JSSecurityCallbacks, JS_SetSecurityCallbacks}; +use js::jsapi::{SetJobQueue, SetProcessBuildIdOp, SetPromiseRejectionTrackerCallback}; +use js::jsval::UndefinedValue; use js::panic::wrap_panic; -use js::rust::Runtime; -use microtask::{EnqueuedPromiseCallback, Microtask}; +use js::rust::wrappers::{GetPromiseIsHandled, JS_GetPromiseResult}; +use js::rust::Handle; +use js::rust::HandleObject as RustHandleObject; +use js::rust::IntoHandle; +use js::rust::ParentRuntime; +use js::rust::Runtime as RustRuntime; +use js::rust::{JSEngine, JSEngineHandle}; +use malloc_size_of::MallocSizeOfOps; +use msg::constellation_msg::PipelineId; use profile_traits::mem::{Report, ReportKind, ReportsChan}; -use script_thread::{Runnable, STACK_ROOTS, trace_thread}; use servo_config::opts; -use servo_config::prefs::PREFS; +use servo_config::pref; use std::cell::Cell; -use std::io::{Write, stdout}; -use std::marker::PhantomData; +use std::ffi::CString; +use std::fmt; +use std::io::{stdout, Write}; +use std::ops::Deref; use std::os; use std::os::raw::c_void; -use std::panic::AssertUnwindSafe; use std::ptr; -use style::thread_state; -use time::{Tm, now}; +use std::rc::Rc; +use std::sync::Mutex; +use std::thread; +use std::time::Duration; +use style::thread_state::{self, ThreadState}; +use time::{now, Tm}; + +static JOB_QUEUE_TRAPS: JobQueueTraps = JobQueueTraps { + getIncumbentGlobal: Some(get_incumbent_global), + enqueuePromiseJob: Some(enqueue_promise_job), + empty: Some(empty), +}; //TODO contentsecuritypolicy -static SECURITY_CALLBACKS: JSSecurityCallbacks = JSSecurityCallbacks { +static SECURITY_CALLBACKS: JSSecurityCallbacks = JSSecurityCallbacks { contentSecurityPolicyAllows: None, - subsumes: Some(utils::subsumes)} -; + subsumes: Some(utils::subsumes), +}; /// Common messages used to control the event loops in both the script and the worker pub enum CommonScriptMsg { @@ -49,7 +110,23 @@ pub enum CommonScriptMsg { /// supplied channel. CollectReports(ReportsChan), /// Generic message that encapsulates event handling. - RunnableMsg(ScriptThreadEventCategory, Box<Runnable + Send>), + Task( + ScriptThreadEventCategory, + Box<dyn TaskBox>, + Option<PipelineId>, + TaskSourceName, + ), +} + +impl fmt::Debug for CommonScriptMsg { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + match *self { + CommonScriptMsg::CollectReports(_) => write!(f, "CollectReports(...)"), + CommonScriptMsg::Task(ref category, ref task, _, _) => { + f.debug_tuple("Task").field(category).field(task).finish() + }, + } + } } /// A cloneable interface for communicating with an event loop. @@ -57,7 +134,7 @@ pub trait ScriptChan: JSTraceable { /// Send a message to the associated event loop. fn send(&self, msg: CommonScriptMsg) -> Result<(), ()>; /// Clone this handle. - fn clone(&self) -> Box<ScriptChan + Send>; + fn clone(&self) -> Box<dyn ScriptChan + Send>; } #[derive(Clone, Copy, Debug, Eq, Hash, JSTraceable, PartialEq)] @@ -69,9 +146,11 @@ pub enum ScriptThreadEventCategory { DomEvent, FileRead, FormPlannedNavigation, + HistoryEvent, ImageCacheMsg, InputEvent, NetworkEvent, + PortMessage, Resize, ScriptEvent, SetScrollState, @@ -81,10 +160,12 @@ pub enum ScriptThreadEventCategory { UpdateReplacedElement, WebSocketEvent, WorkerEvent, + WorkletEvent, ServiceWorkerEvent, EnterFullscreen, ExitFullscreen, - WebVREvent + PerformanceTimelineTask, + WebGPUMsg, } /// An interface for receiving ScriptMsg values in an event loop. Used for synchronous DOM @@ -94,228 +175,533 @@ pub trait ScriptPort { fn recv(&self) -> Result<CommonScriptMsg, ()>; } -pub struct StackRootTLS<'a>(PhantomData<&'a u32>); +#[allow(unsafe_code)] +unsafe extern "C" fn get_incumbent_global(_: *const c_void, _: *mut RawJSContext) -> *mut JSObject { + let mut result = ptr::null_mut(); + wrap_panic(&mut || { + let incumbent_global = GlobalScope::incumbent(); + + assert!(incumbent_global.is_some()); + + result = incumbent_global + .map(|g| g.reflector().get_jsobject().get()) + .unwrap_or(ptr::null_mut()) + }); + result +} + +#[allow(unsafe_code)] +unsafe extern "C" fn empty(extra: *const c_void) -> bool { + let mut result = false; + wrap_panic(&mut || { + let microtask_queue = &*(extra as *const MicrotaskQueue); + result = microtask_queue.empty() + }); + result +} + +/// SM callback for promise job resolution. Adds a promise callback to the current +/// global's microtask queue. +#[allow(unsafe_code)] +unsafe extern "C" fn enqueue_promise_job( + extra: *const c_void, + cx: *mut RawJSContext, + promise: HandleObject, + job: HandleObject, + _allocation_site: HandleObject, + incumbent_global: HandleObject, +) -> bool { + let cx = JSContext::from_ptr(cx); + let mut result = false; + wrap_panic(&mut || { + let microtask_queue = &*(extra as *const MicrotaskQueue); + let global = if !incumbent_global.is_null() { + GlobalScope::from_object(incumbent_global.get()) + } else { + let realm = AlreadyInRealm::assert_for_cx(cx); + GlobalScope::from_context(*cx, InRealm::in_realm(&realm)) + }; + let pipeline = global.pipeline_id(); + let interaction = if promise.get().is_null() { + PromiseUserInputEventHandlingState::DontCare + } else { + GetPromiseUserInputEventHandlingState(promise) + }; + let is_user_interacting = + interaction == PromiseUserInputEventHandlingState::HadUserInteractionAtCreation; + microtask_queue.enqueue( + Microtask::Promise(EnqueuedPromiseCallback { + callback: PromiseJobCallback::new(cx, job.get()), + pipeline, + is_user_interacting, + }), + cx, + ); + result = true + }); + result +} + +#[allow(unsafe_code, unrooted_must_root)] +/// https://html.spec.whatwg.org/multipage/#the-hostpromiserejectiontracker-implementation +unsafe extern "C" fn promise_rejection_tracker( + cx: *mut RawJSContext, + _muted_errors: bool, + promise: HandleObject, + state: PromiseRejectionHandlingState, + _data: *mut c_void, +) { + // TODO: Step 2 - If script's muted errors is true, terminate these steps. + + // Step 3. + let cx = JSContext::from_ptr(cx); + let in_realm_proof = AlreadyInRealm::assert_for_cx(cx); + let global = GlobalScope::from_context(*cx, InRealm::Already(&in_realm_proof)); + + wrap_panic(&mut || { + match state { + // Step 4. + PromiseRejectionHandlingState::Unhandled => { + global.add_uncaught_rejection(promise); + }, + // Step 5. + PromiseRejectionHandlingState::Handled => { + // Step 5-1. + if global + .get_uncaught_rejections() + .borrow() + .contains(&Heap::boxed(promise.get())) + { + global.remove_uncaught_rejection(promise); + return; + } + + // Step 5-2. + if !global + .get_consumed_rejections() + .borrow() + .contains(&Heap::boxed(promise.get())) + { + return; + } + + // Step 5-3. + global.remove_consumed_rejection(promise); + + let target = Trusted::new(global.upcast::<EventTarget>()); + let promise = Promise::new_with_js_promise(Handle::from_raw(promise), cx); + let trusted_promise = TrustedPromise::new(promise.clone()); + + // Step 5-4. + global.dom_manipulation_task_source().queue( + task!(rejection_handled_event: move || { + let target = target.root(); + let cx = target.global().get_cx(); + let root_promise = trusted_promise.root(); + + rooted!(in(*cx) let mut reason = UndefinedValue()); + JS_GetPromiseResult(root_promise.reflector().get_jsobject(), reason.handle_mut()); + + let event = PromiseRejectionEvent::new( + &target.global(), + atom!("rejectionhandled"), + EventBubbles::DoesNotBubble, + EventCancelable::Cancelable, + root_promise, + reason.handle() + ); + + event.upcast::<Event>().fire(&target); + }), + global.upcast(), + ).unwrap(); + }, + }; + }) +} + +#[allow(unsafe_code, unrooted_must_root)] +/// https://html.spec.whatwg.org/multipage/#notify-about-rejected-promises +pub fn notify_about_rejected_promises(global: &GlobalScope) { + let cx = global.get_cx(); + unsafe { + // Step 2. + if global.get_uncaught_rejections().borrow().len() > 0 { + // Step 1. + let uncaught_rejections: Vec<TrustedPromise> = global + .get_uncaught_rejections() + .borrow() + .iter() + .map(|promise| { + let promise = + Promise::new_with_js_promise(Handle::from_raw(promise.handle()), cx); + + TrustedPromise::new(promise) + }) + .collect(); + + // Step 3. + global.get_uncaught_rejections().borrow_mut().clear(); + + let target = Trusted::new(global.upcast::<EventTarget>()); + + // Step 4. + global.dom_manipulation_task_source().queue( + task!(unhandled_rejection_event: move || { + let target = target.root(); + let cx = target.global().get_cx(); + + for promise in uncaught_rejections { + let promise = promise.root(); + + // Step 4-1. + let promise_is_handled = GetPromiseIsHandled(promise.reflector().get_jsobject()); + if promise_is_handled { + continue; + } + + // Step 4-2. + rooted!(in(*cx) let mut reason = UndefinedValue()); + JS_GetPromiseResult(promise.reflector().get_jsobject(), reason.handle_mut()); + + let event = PromiseRejectionEvent::new( + &target.global(), + atom!("unhandledrejection"), + EventBubbles::DoesNotBubble, + EventCancelable::Cancelable, + promise.clone(), + reason.handle() + ); + + let event_status = event.upcast::<Event>().fire(&target); + + // Step 4-3. + if event_status == EventStatus::Canceled { + // TODO: The promise rejection is not handled; we need to add it back to the list. + } + + // Step 4-4. + if !promise_is_handled { + target.global().add_consumed_rejection(promise.reflector().get_jsobject().into_handle()); + } + } + }), + global.upcast(), + ).unwrap(); + } + } +} + +#[derive(JSTraceable)] +pub struct Runtime { + rt: RustRuntime, + pub microtask_queue: Rc<MicrotaskQueue>, + job_queue: *mut JobQueue, +} -impl<'a> StackRootTLS<'a> { - pub fn new(roots: &'a RootCollection) -> StackRootTLS<'a> { - STACK_ROOTS.with(|ref r| { - r.set(Some(RootCollectionPtr(roots as *const _))) +impl Drop for Runtime { + #[allow(unsafe_code)] + fn drop(&mut self) { + unsafe { + DeleteJobQueue(self.job_queue); + } + THREAD_ACTIVE.with(|t| { + LiveDOMReferences::destruct(); + t.set(false); }); - StackRootTLS(PhantomData) } } -impl<'a> Drop for StackRootTLS<'a> { +impl Deref for Runtime { + type Target = RustRuntime; + fn deref(&self) -> &RustRuntime { + &self.rt + } +} + +pub struct JSEngineSetup(JSEngine); + +impl JSEngineSetup { + pub fn new() -> Self { + let engine = JSEngine::init().unwrap(); + *JS_ENGINE.lock().unwrap() = Some(engine.handle()); + Self(engine) + } +} + +impl Drop for JSEngineSetup { fn drop(&mut self) { - STACK_ROOTS.with(|ref r| r.set(None)); + *JS_ENGINE.lock().unwrap() = None; + + while !self.0.can_shutdown() { + thread::sleep(Duration::from_millis(50)); + } } } -/// SM callback for promise job resolution. Adds a promise callback to the current -/// global's microtask queue. +lazy_static! { + static ref JS_ENGINE: Mutex<Option<JSEngineHandle>> = Mutex::new(None); +} + #[allow(unsafe_code)] -unsafe extern "C" fn enqueue_job(cx: *mut JSContext, - job: HandleObject, - _allocation_site: HandleObject, - _data: *mut c_void) -> bool { - wrap_panic(AssertUnwindSafe(|| { - //XXXjdm - use a different global now? - let global = GlobalScope::from_object(job.get()); - let pipeline = global.pipeline_id(); - global.enqueue_microtask(Microtask::Promise(EnqueuedPromiseCallback { - callback: PromiseJobCallback::new(cx, job.get()), - pipeline: pipeline, - })); - true - }), false) +pub unsafe fn new_child_runtime( + parent: ParentRuntime, + networking_task_source: Option<NetworkingTaskSource>, +) -> Runtime { + new_rt_and_cx_with_parent(Some(parent), networking_task_source) } #[allow(unsafe_code)] -pub unsafe fn new_rt_and_cx() -> Runtime { +pub fn new_rt_and_cx(networking_task_source: Option<NetworkingTaskSource>) -> Runtime { + unsafe { new_rt_and_cx_with_parent(None, networking_task_source) } +} + +#[allow(unsafe_code)] +unsafe fn new_rt_and_cx_with_parent( + parent: Option<ParentRuntime>, + networking_task_source: Option<NetworkingTaskSource>, +) -> Runtime { LiveDOMReferences::initialize(); - let runtime = Runtime::new().unwrap(); + let (cx, runtime) = if let Some(parent) = parent { + let runtime = RustRuntime::create_with_parent(parent); + let cx = runtime.cx(); + (cx, runtime) + } else { + let runtime = RustRuntime::new(JS_ENGINE.lock().unwrap().as_ref().unwrap().clone()); + (runtime.cx(), runtime) + }; - JS_AddExtraGCRootsTracer(runtime.rt(), Some(trace_rust_roots), ptr::null_mut()); - JS_AddExtraGCRootsTracer(runtime.rt(), Some(trace_refcounted_objects), ptr::null_mut()); + JS_AddExtraGCRootsTracer(cx, Some(trace_rust_roots), ptr::null_mut()); JS_SetSecurityCallbacks(runtime.rt(), &SECURITY_CALLBACKS); // Needed for debug assertions about whether GC is running. if cfg!(debug_assertions) { - JS_SetGCCallback(runtime.rt(), Some(debug_gc_callback), ptr::null_mut()); + JS_SetGCCallback(cx, Some(debug_gc_callback), ptr::null_mut()); } if opts::get().gc_profile { - SetGCSliceCallback(runtime.rt(), Some(gc_slice_callback)); + SetGCSliceCallback(cx, Some(gc_slice_callback)); } - unsafe extern "C" fn empty_wrapper_callback(_: *mut JSContext, _: *mut JSObject) -> bool { true } - SetDOMCallbacks(runtime.rt(), &DOM_CALLBACKS); - SetPreserveWrapperCallback(runtime.rt(), Some(empty_wrapper_callback)); + unsafe extern "C" fn empty_wrapper_callback(_: *mut RawJSContext, _: HandleObject) -> bool { + true + } + unsafe extern "C" fn empty_has_released_callback(_: HandleObject) -> bool { + // fixme: return true when the Drop impl for a DOM object has been invoked + false + } + SetDOMCallbacks(cx, &DOM_CALLBACKS); + SetPreserveWrapperCallbacks( + cx, + Some(empty_wrapper_callback), + Some(empty_has_released_callback), + ); // Pre barriers aren't working correctly at the moment - DisableIncrementalGC(runtime.rt()); + DisableIncrementalGC(cx); - SetEnqueuePromiseJobCallback(runtime.rt(), Some(enqueue_job), ptr::null_mut()); - - set_gc_zeal_options(runtime.rt()); + unsafe extern "C" fn dispatch_to_event_loop( + closure: *mut c_void, + dispatchable: *mut JSRunnable, + ) -> bool { + let networking_task_src: &NetworkingTaskSource = &*(closure as *mut NetworkingTaskSource); + let runnable = Runnable(dispatchable); + let task = task!(dispatch_to_event_loop_message: move || { + runnable.run(RustRuntime::get(), Dispatchable_MaybeShuttingDown::NotShuttingDown); + }); - // Enable or disable the JITs. - let rt_opts = &mut *RuntimeOptionsRef(runtime.rt()); - if let Some(val) = PREFS.get("js.baseline.enabled").as_boolean() { - rt_opts.set_baseline_(val); - } - if let Some(val) = PREFS.get("js.ion.enabled").as_boolean() { - rt_opts.set_ion_(val); + networking_task_src.queue_unconditionally(task).is_ok() } - if let Some(val) = PREFS.get("js.asmjs.enabled").as_boolean() { - rt_opts.set_asmJS_(val); + + if let Some(source) = networking_task_source { + let networking_task_src = Box::new(source); + InitDispatchToEventLoop( + cx, + Some(dispatch_to_event_loop), + Box::into_raw(networking_task_src) as *mut c_void, + ); } - if let Some(val) = PREFS.get("js.strict.enabled").as_boolean() { - rt_opts.set_extraWarnings_(val); + + InitConsumeStreamCallback(cx, Some(consume_stream), Some(report_stream_error)); + + let microtask_queue = Rc::new(MicrotaskQueue::default()); + let job_queue = CreateJobQueue( + &JOB_QUEUE_TRAPS, + &*microtask_queue as *const _ as *const c_void, + ); + SetJobQueue(cx, job_queue); + SetPromiseRejectionTrackerCallback(cx, Some(promise_rejection_tracker), ptr::null_mut()); + + EnsureModuleHooksInitialized(runtime.rt()); + + set_gc_zeal_options(cx); + + // Enable or disable the JITs. + let cx_opts = &mut *ContextOptionsRef(cx); + JS_SetGlobalJitCompilerOption( + cx, + JSJitCompilerOption::JSJITCOMPILER_BASELINE_ENABLE, + pref!(js.baseline.enabled) as u32, + ); + JS_SetGlobalJitCompilerOption( + cx, + JSJitCompilerOption::JSJITCOMPILER_ION_ENABLE, + pref!(js.ion.enabled) as u32, + ); + cx_opts.set_asmJS_(pref!(js.asmjs.enabled)); + let wasm_enabled = pref!(js.wasm.enabled); + cx_opts.set_wasm_(wasm_enabled); + if wasm_enabled { + // If WASM is enabled without setting the buildIdOp, + // initializing a module will report an out of memory error. + // https://dxr.mozilla.org/mozilla-central/source/js/src/wasm/WasmTypes.cpp#458 + SetProcessBuildIdOp(Some(servo_build_id)); } + cx_opts.set_wasmBaseline_(pref!(js.wasm.baseline.enabled)); + cx_opts.set_wasmIon_(pref!(js.wasm.ion.enabled)); + cx_opts.set_strictMode_(pref!(js.strict.enabled)); // TODO: handle js.strict.debug.enabled // TODO: handle js.throw_on_asmjs_validation_failure (needs new Spidermonkey) - if let Some(val) = PREFS.get("js.native_regexp.enabled").as_boolean() { - rt_opts.set_nativeRegExp_(val); - } - if let Some(val) = PREFS.get("js.parallel_parsing.enabled").as_boolean() { - JS_SetParallelParsingEnabled(runtime.rt(), val); - } - if let Some(val) = PREFS.get("js.offthread_compilation_enabled").as_boolean() { - JS_SetOffthreadIonCompilationEnabled(runtime.rt(), val); - } - if let Some(val) = PREFS.get("js.baseline.unsafe_eager_compilation.enabled").as_boolean() { - let trigger: i32 = if val { + JS_SetGlobalJitCompilerOption( + cx, + JSJitCompilerOption::JSJITCOMPILER_NATIVE_REGEXP_ENABLE, + pref!(js.native_regex.enabled) as u32, + ); + JS_SetParallelParsingEnabled(cx, pref!(js.parallel_parsing.enabled)); + JS_SetOffthreadIonCompilationEnabled(cx, pref!(js.offthread_compilation.enabled)); + JS_SetGlobalJitCompilerOption( + cx, + JSJitCompilerOption::JSJITCOMPILER_BASELINE_WARMUP_TRIGGER, + if pref!(js.baseline.unsafe_eager_compilation.enabled) { 0 } else { - -1 - }; - JS_SetGlobalJitCompilerOption(runtime.rt(), - JSJitCompilerOption::JSJITCOMPILER_BASELINE_WARMUP_TRIGGER, - trigger as u32); - } - if let Some(val) = PREFS.get("js.ion.unsafe_eager_compilation.enabled").as_boolean() { - let trigger: i64 = if val { + u32::max_value() + }, + ); + JS_SetGlobalJitCompilerOption( + cx, + JSJitCompilerOption::JSJITCOMPILER_ION_NORMAL_WARMUP_TRIGGER, + if pref!(js.ion.unsafe_eager_compilation.enabled) { 0 } else { - -1 - }; - JS_SetGlobalJitCompilerOption(runtime.rt(), - JSJitCompilerOption::JSJITCOMPILER_ION_WARMUP_TRIGGER, - trigger as u32); - } + u32::max_value() + }, + ); // TODO: handle js.discard_system_source.enabled // TODO: handle js.asyncstack.enabled (needs new Spidermonkey) // TODO: handle js.throw_on_debugee_would_run (needs new Spidermonkey) // TODO: handle js.dump_stack_on_debugee_would_run (needs new Spidermonkey) - if let Some(val) = PREFS.get("js.werror.enabled").as_boolean() { - rt_opts.set_werror_(val); - } // TODO: handle js.shared_memory.enabled - if let Some(val) = PREFS.get("js.mem.high_water_mark").as_i64() { - JS_SetGCParameter(runtime.rt(), JSGCParamKey::JSGC_MAX_MALLOC_BYTES, val as u32 * 1024 * 1024); - } - if let Some(val) = PREFS.get("js.mem.max").as_i64() { - let max = if val <= 0 || val >= 0x1000 { - -1 - } else { - val * 1024 * 1024 - }; - JS_SetGCParameter(runtime.rt(), JSGCParamKey::JSGC_MAX_BYTES, max as u32); - } + JS_SetGCParameter( + cx, + JSGCParamKey::JSGC_MAX_BYTES, + in_range(pref!(js.mem.max), 1, 0x100) + .map(|val| (val * 1024 * 1024) as u32) + .unwrap_or(u32::max_value()), + ); // NOTE: This is disabled above, so enabling it here will do nothing for now. - if let Some(val) = PREFS.get("js.mem.gc.incremental.enabled").as_boolean() { - let compartment = if let Some(val) = PREFS.get("js.mem.gc.per_compartment.enabled").as_boolean() { - val - } else { - false - }; - let mode = if val { - JSGCMode::JSGC_MODE_INCREMENTAL - } else if compartment { - JSGCMode::JSGC_MODE_COMPARTMENT - } else { - JSGCMode::JSGC_MODE_GLOBAL - }; - JS_SetGCParameter(runtime.rt(), JSGCParamKey::JSGC_MODE, mode as u32); + JS_SetGCParameter( + cx, + JSGCParamKey::JSGC_INCREMENTAL_GC_ENABLED, + pref!(js.mem.gc.incremental.enabled) as u32, + ); + JS_SetGCParameter( + cx, + JSGCParamKey::JSGC_PER_ZONE_GC_ENABLED, + pref!(js.mem.gc.per_zone.enabled) as u32, + ); + if let Some(val) = in_range(pref!(js.mem.gc.incremental.slice_ms), 0, 100_000) { + JS_SetGCParameter(cx, JSGCParamKey::JSGC_SLICE_TIME_BUDGET_MS, val as u32); } - if let Some(val) = PREFS.get("js.mem.gc.incremental.slice_ms").as_i64() { - if val >= 0 && val < 100000 { - JS_SetGCParameter(runtime.rt(), JSGCParamKey::JSGC_SLICE_TIME_BUDGET, val as u32); - } - } - if let Some(val) = PREFS.get("js.mem.gc.compacting.enabled").as_boolean() { - JS_SetGCParameter(runtime.rt(), JSGCParamKey::JSGC_COMPACTING_ENABLED, val as u32); - } - if let Some(val) = PREFS.get("js.mem.gc.high_frequency_time_limit_ms").as_i64() { - if val >= 0 && val < 10000 { - JS_SetGCParameter(runtime.rt(), JSGCParamKey::JSGC_HIGH_FREQUENCY_TIME_LIMIT, val as u32); - } - } - if let Some(val) = PREFS.get("js.mem.gc.dynamic_mark_slice.enabled").as_boolean() { - JS_SetGCParameter(runtime.rt(), JSGCParamKey::JSGC_DYNAMIC_MARK_SLICE, val as u32); - } - // TODO: handle js.mem.gc.refresh_frame_slices.enabled - if let Some(val) = PREFS.get("js.mem.gc.dynamic_heap_growth.enabled").as_boolean() { - JS_SetGCParameter(runtime.rt(), JSGCParamKey::JSGC_DYNAMIC_HEAP_GROWTH, val as u32); + JS_SetGCParameter( + cx, + JSGCParamKey::JSGC_COMPACTING_ENABLED, + pref!(js.mem.gc.compacting.enabled) as u32, + ); + + if let Some(val) = in_range(pref!(js.mem.gc.high_frequency_time_limit_ms), 0, 10_000) { + JS_SetGCParameter(cx, JSGCParamKey::JSGC_HIGH_FREQUENCY_TIME_LIMIT, val as u32); } - if let Some(val) = PREFS.get("js.mem.gc.low_frequency_heap_growth").as_i64() { - if val >= 0 && val < 10000 { - JS_SetGCParameter(runtime.rt(), JSGCParamKey::JSGC_LOW_FREQUENCY_HEAP_GROWTH, val as u32); - } + if let Some(val) = in_range(pref!(js.mem.gc.low_frequency_heap_growth), 0, 10_000) { + JS_SetGCParameter(cx, JSGCParamKey::JSGC_LOW_FREQUENCY_HEAP_GROWTH, val as u32); } - if let Some(val) = PREFS.get("js.mem.gc.high_frequency_heap_growth_min").as_i64() { - if val >= 0 && val < 10000 { - JS_SetGCParameter(runtime.rt(), JSGCParamKey::JSGC_HIGH_FREQUENCY_HEAP_GROWTH_MIN, val as u32); - } + if let Some(val) = in_range(pref!(js.mem.gc.high_frequency_heap_growth_min), 0, 10_000) { + JS_SetGCParameter( + cx, + JSGCParamKey::JSGC_HIGH_FREQUENCY_LARGE_HEAP_GROWTH, + val as u32, + ); } - if let Some(val) = PREFS.get("js.mem.gc.high_frequency_heap_growth_max").as_i64() { - if val >= 0 && val < 10000 { - JS_SetGCParameter(runtime.rt(), JSGCParamKey::JSGC_HIGH_FREQUENCY_HEAP_GROWTH_MAX, val as u32); - } + if let Some(val) = in_range(pref!(js.mem.gc.high_frequency_heap_growth_max), 0, 10_000) { + JS_SetGCParameter( + cx, + JSGCParamKey::JSGC_HIGH_FREQUENCY_SMALL_HEAP_GROWTH, + val as u32, + ); } - if let Some(val) = PREFS.get("js.mem.gc.high_frequency_low_limit_mb").as_i64() { - if val >= 0 && val < 10000 { - JS_SetGCParameter(runtime.rt(), JSGCParamKey::JSGC_HIGH_FREQUENCY_LOW_LIMIT, val as u32); - } + if let Some(val) = in_range(pref!(js.mem.gc.high_frequency_low_limit_mb), 0, 10_000) { + JS_SetGCParameter(cx, JSGCParamKey::JSGC_SMALL_HEAP_SIZE_MAX, val as u32); } - if let Some(val) = PREFS.get("js.mem.gc.high_frequency_high_limit_mb").as_i64() { - if val >= 0 && val < 10000 { - JS_SetGCParameter(runtime.rt(), JSGCParamKey::JSGC_HIGH_FREQUENCY_HIGH_LIMIT, val as u32); - } + if let Some(val) = in_range(pref!(js.mem.gc.high_frequency_high_limit_mb), 0, 10_000) { + JS_SetGCParameter(cx, JSGCParamKey::JSGC_LARGE_HEAP_SIZE_MIN, val as u32); } - if let Some(val) = PREFS.get("js.mem.gc.allocation_threshold_mb").as_i64() { - if val >= 0 && val < 10000 { - JS_SetGCParameter(runtime.rt(), JSGCParamKey::JSGC_ALLOCATION_THRESHOLD, val as u32); - } + /*if let Some(val) = in_range(pref!(js.mem.gc.allocation_threshold_factor), 0, 10_000) { + JS_SetGCParameter(cx, JSGCParamKey::JSGC_NON_INCREMENTAL_FACTOR, val as u32); + }*/ + /* + // JSGC_SMALL_HEAP_INCREMENTAL_LIMIT + pref("javascript.options.mem.gc_small_heap_incremental_limit", 140); + + // JSGC_LARGE_HEAP_INCREMENTAL_LIMIT + pref("javascript.options.mem.gc_large_heap_incremental_limit", 110); + */ + if let Some(val) = in_range(pref!(js.mem.gc.empty_chunk_count_min), 0, 10_000) { + JS_SetGCParameter(cx, JSGCParamKey::JSGC_MIN_EMPTY_CHUNK_COUNT, val as u32); } - if let Some(val) = PREFS.get("js.mem.gc.decommit_threshold_mb").as_i64() { - if val >= 0 && val < 10000 { - JS_SetGCParameter(runtime.rt(), JSGCParamKey::JSGC_DECOMMIT_THRESHOLD, val as u32); - } + if let Some(val) = in_range(pref!(js.mem.gc.empty_chunk_count_max), 0, 10_000) { + JS_SetGCParameter(cx, JSGCParamKey::JSGC_MAX_EMPTY_CHUNK_COUNT, val as u32); } - if let Some(val) = PREFS.get("js.mem.gc.empty_chunk_count_min").as_i64() { - if val >= 0 && val < 10000 { - JS_SetGCParameter(runtime.rt(), JSGCParamKey::JSGC_MIN_EMPTY_CHUNK_COUNT, val as u32); - } + + Runtime { + rt: runtime, + microtask_queue, + job_queue, } - if let Some(val) = PREFS.get("js.mem.gc.empty_chunk_count_max").as_i64() { - if val >= 0 && val < 10000 { - JS_SetGCParameter(runtime.rt(), JSGCParamKey::JSGC_MAX_EMPTY_CHUNK_COUNT, val as u32); - } +} + +fn in_range<T: PartialOrd + Copy>(val: T, min: T, max: T) -> Option<T> { + if val < min || val >= max { + None + } else { + Some(val) } +} + +#[allow(unsafe_code)] +unsafe extern "C" fn get_size(obj: *mut JSObject) -> usize { + match get_dom_class(obj) { + Ok(v) => { + let dom_object = private_from_object(obj) as *const c_void; - runtime + if dom_object.is_null() { + return 0; + } + let mut ops = MallocSizeOfOps::new(servo_allocator::usable_size, None, None); + (v.malloc_size_of)(&mut ops, dom_object) + }, + Err(_e) => { + return 0; + }, + } } #[allow(unsafe_code)] -pub fn get_reports(cx: *mut JSContext, path_seg: String) -> Vec<Report> { +pub fn get_reports(cx: *mut RawJSContext, path_seg: String) -> Vec<Report> { let mut reports = vec![]; unsafe { - let rt = JS_GetRuntime(cx); let mut stats = ::std::mem::zeroed(); - if CollectServoSizes(rt, &mut stats) { + if CollectServoSizes(cx, &mut stats, Some(get_size)) { let mut report = |mut path_suffix, kind, size| { let mut path = path![path_seg, "js"]; path.append(&mut path_suffix); @@ -330,30 +716,42 @@ pub fn get_reports(cx: *mut JSContext, path_seg: String) -> Vec<Report> { // mmap/VirtualAlloc, which means it's not on the malloc "heap", so we use // `ExplicitNonHeapSize` as its kind. - report(path!["gc-heap", "used"], - ReportKind::ExplicitNonHeapSize, - stats.gcHeapUsed); + report( + path!["gc-heap", "used"], + ReportKind::ExplicitNonHeapSize, + stats.gcHeapUsed, + ); - report(path!["gc-heap", "unused"], - ReportKind::ExplicitNonHeapSize, - stats.gcHeapUnused); + report( + path!["gc-heap", "unused"], + ReportKind::ExplicitNonHeapSize, + stats.gcHeapUnused, + ); - report(path!["gc-heap", "admin"], - ReportKind::ExplicitNonHeapSize, - stats.gcHeapAdmin); + report( + path!["gc-heap", "admin"], + ReportKind::ExplicitNonHeapSize, + stats.gcHeapAdmin, + ); - report(path!["gc-heap", "decommitted"], - ReportKind::ExplicitNonHeapSize, - stats.gcHeapDecommitted); + report( + path!["gc-heap", "decommitted"], + ReportKind::ExplicitNonHeapSize, + stats.gcHeapDecommitted, + ); // SpiderMonkey uses the system heap, not jemalloc. - report(path!["malloc-heap"], - ReportKind::ExplicitSystemHeapSize, - stats.mallocHeap); + report( + path!["malloc-heap"], + ReportKind::ExplicitSystemHeapSize, + stats.mallocHeap, + ); - report(path!["non-heap"], - ReportKind::ExplicitNonHeapSize, - stats.nonHeap); + report( + path!["non-heap"], + ReportKind::ExplicitNonHeapSize, + stats.nonHeap, + ); } } reports @@ -363,34 +761,30 @@ thread_local!(static GC_CYCLE_START: Cell<Option<Tm>> = Cell::new(None)); thread_local!(static GC_SLICE_START: Cell<Option<Tm>> = Cell::new(None)); #[allow(unsafe_code)] -unsafe extern "C" fn gc_slice_callback(_rt: *mut JSRuntime, progress: GCProgress, desc: *const GCDescription) { +unsafe extern "C" fn gc_slice_callback( + _cx: *mut RawJSContext, + progress: GCProgress, + desc: *const GCDescription, +) { match progress { - GCProgress::GC_CYCLE_BEGIN => { - GC_CYCLE_START.with(|start| { - start.set(Some(now())); - println!("GC cycle began"); - }) - }, - GCProgress::GC_SLICE_BEGIN => { - GC_SLICE_START.with(|start| { - start.set(Some(now())); - println!("GC slice began"); - }) - }, - GCProgress::GC_SLICE_END => { - GC_SLICE_START.with(|start| { - let dur = now() - start.get().unwrap(); - start.set(None); - println!("GC slice ended: duration={}", dur); - }) - }, - GCProgress::GC_CYCLE_END => { - GC_CYCLE_START.with(|start| { - let dur = now() - start.get().unwrap(); - start.set(None); - println!("GC cycle ended: duration={}", dur); - }) - }, + GCProgress::GC_CYCLE_BEGIN => GC_CYCLE_START.with(|start| { + start.set(Some(now())); + println!("GC cycle began"); + }), + GCProgress::GC_SLICE_BEGIN => GC_SLICE_START.with(|start| { + start.set(Some(now())); + println!("GC slice began"); + }), + GCProgress::GC_SLICE_END => GC_SLICE_START.with(|start| { + let dur = now() - start.get().unwrap(); + start.set(None); + println!("GC slice ended: duration={}", dur); + }), + GCProgress::GC_CYCLE_END => GC_CYCLE_START.with(|start| { + let dur = now() - start.get().unwrap(); + start.set(None); + println!("GC cycle ended: duration={}", dur); + }), }; if !desc.is_null() { let desc: &GCDescription = &*desc; @@ -398,45 +792,270 @@ unsafe extern "C" fn gc_slice_callback(_rt: *mut JSRuntime, progress: GCProgress JSGCInvocationKind::GC_NORMAL => "GC_NORMAL", JSGCInvocationKind::GC_SHRINK => "GC_SHRINK", }; - println!(" isCompartment={}, invocation_kind={}", desc.isCompartment_, invocation_kind); + println!( + " isZone={}, invocation_kind={}", + desc.isZone_, invocation_kind + ); } let _ = stdout().flush(); } #[allow(unsafe_code)] -unsafe extern "C" fn debug_gc_callback(_rt: *mut JSRuntime, status: JSGCStatus, _data: *mut os::raw::c_void) { +unsafe extern "C" fn debug_gc_callback( + _cx: *mut RawJSContext, + status: JSGCStatus, + _reason: GCReason, + _data: *mut os::raw::c_void, +) { match status { - JSGCStatus::JSGC_BEGIN => thread_state::enter(thread_state::IN_GC), - JSGCStatus::JSGC_END => thread_state::exit(thread_state::IN_GC), + JSGCStatus::JSGC_BEGIN => thread_state::enter(ThreadState::IN_GC), + JSGCStatus::JSGC_END => thread_state::exit(ThreadState::IN_GC), } } +thread_local!( + static THREAD_ACTIVE: Cell<bool> = Cell::new(true); +); + #[allow(unsafe_code)] -unsafe extern fn trace_rust_roots(tr: *mut JSTracer, _data: *mut os::raw::c_void) { +unsafe extern "C" fn trace_rust_roots(tr: *mut JSTracer, _data: *mut os::raw::c_void) { + if !THREAD_ACTIVE.with(|t| t.get()) { + return; + } debug!("starting custom root handler"); trace_thread(tr); trace_traceables(tr); trace_roots(tr); + trace_refcounted_objects(tr); settings_stack::trace(tr); debug!("done custom root handler"); } #[allow(unsafe_code)] +unsafe extern "C" fn servo_build_id(build_id: *mut BuildIdCharVector) -> bool { + let servo_id = b"Servo\0"; + SetBuildId(build_id, &servo_id[0], servo_id.len()) +} + +#[allow(unsafe_code)] #[cfg(feature = "debugmozjs")] -unsafe fn set_gc_zeal_options(rt: *mut JSRuntime) { - use js::jsapi::{JS_DEFAULT_ZEAL_FREQ, JS_SetGCZeal}; +unsafe fn set_gc_zeal_options(cx: *mut RawJSContext) { + use js::jsapi::{JS_SetGCZeal, JS_DEFAULT_ZEAL_FREQ}; - let level = match PREFS.get("js.mem.gc.zeal.level").as_i64() { - Some(level @ 0...14) => level as u8, + let level = match pref!(js.mem.gc.zeal.level) { + level @ 0..=14 => level as u8, _ => return, }; - let frequency = match PREFS.get("js.mem.gc.zeal.frequency").as_i64() { - Some(frequency) if frequency >= 0 => frequency as u32, + let frequency = match pref!(js.mem.gc.zeal.frequency) { + frequency if frequency >= 0 => frequency as u32, _ => JS_DEFAULT_ZEAL_FREQ, }; - JS_SetGCZeal(rt, level, frequency); + JS_SetGCZeal(cx, level, frequency); } #[allow(unsafe_code)] #[cfg(not(feature = "debugmozjs"))] -unsafe fn set_gc_zeal_options(_: *mut JSRuntime) {} +unsafe fn set_gc_zeal_options(_: *mut RawJSContext) {} + +#[repr(transparent)] +/// A wrapper around a JSContext that is Send, +/// enabling an interrupt to be requested +/// from a thread other than the one running JS using that context. +pub struct ContextForRequestInterrupt(*mut RawJSContext); + +impl ContextForRequestInterrupt { + pub fn new(context: *mut RawJSContext) -> ContextForRequestInterrupt { + ContextForRequestInterrupt(context) + } + + #[allow(unsafe_code)] + /// Can be called from any thread, to request the callback set by + /// JS_AddInterruptCallback to be called + /// on the thread where that context is running. + pub fn request_interrupt(&self) { + unsafe { + JS_RequestInterruptCallback(self.0); + } + } +} + +#[allow(unsafe_code)] +/// It is safe to call `JS_RequestInterruptCallback(cx)` from any thread. +/// See the docs for the corresponding `requestInterrupt` method, +/// at `mozjs/js/src/vm/JSContext.h`. +unsafe impl Send for ContextForRequestInterrupt {} + +#[derive(Clone, Copy)] +#[repr(transparent)] +pub struct JSContext(*mut RawJSContext); + +#[allow(unsafe_code)] +impl JSContext { + pub unsafe fn from_ptr(raw_js_context: *mut RawJSContext) -> Self { + JSContext(raw_js_context) + } +} + +#[allow(unsafe_code)] +impl Deref for JSContext { + type Target = *mut RawJSContext; + + fn deref(&self) -> &Self::Target { + &self.0 + } +} + +pub struct StreamConsumer(*mut JSStreamConsumer); + +#[allow(unsafe_code)] +impl StreamConsumer { + pub fn consume_chunk(&self, stream: &[u8]) -> bool { + unsafe { + let stream_ptr = stream.as_ptr(); + return StreamConsumerConsumeChunk(self.0, stream_ptr, stream.len()); + } + } + + pub fn stream_end(&self) { + unsafe { + StreamConsumerStreamEnd(self.0); + } + } + + pub fn stream_error(&self, error_code: usize) { + unsafe { + StreamConsumerStreamError(self.0, error_code); + } + } + + pub fn note_response_urls( + &self, + maybe_url: Option<String>, + maybe_source_map_url: Option<String>, + ) { + unsafe { + let maybe_url = maybe_url.map(|url| CString::new(url).unwrap()); + let maybe_source_map_url = maybe_source_map_url.map(|url| CString::new(url).unwrap()); + + let maybe_url_param = match maybe_url.as_ref() { + Some(url) => url.as_ptr(), + None => ptr::null(), + }; + let maybe_source_map_url_param = match maybe_source_map_url.as_ref() { + Some(url) => url.as_ptr(), + None => ptr::null(), + }; + + StreamConsumerNoteResponseURLs(self.0, maybe_url_param, maybe_source_map_url_param); + } + } +} + +/// Implements the steps to compile webassembly response mentioned here +/// <https://webassembly.github.io/spec/web-api/#compile-a-potential-webassembly-response> +#[allow(unsafe_code)] +unsafe extern "C" fn consume_stream( + _cx: *mut RawJSContext, + obj: HandleObject, + _mime_type: MimeType, + _consumer: *mut JSStreamConsumer, +) -> bool { + let cx = JSContext::from_ptr(_cx); + let in_realm_proof = AlreadyInRealm::assert_for_cx(cx); + let global = GlobalScope::from_context(*cx, InRealm::Already(&in_realm_proof)); + + //Step 2.1 Upon fulfillment of source, store the Response with value unwrappedSource. + if let Ok(unwrapped_source) = + root_from_handleobject::<Response>(RustHandleObject::from_raw(obj), *cx) + { + //Step 2.2 Let mimeType be the result of extracting a MIME type from response’s header list. + let mimetype = unwrapped_source.Headers().extract_mime_type(); + + //Step 2.3 If mimeType is not `application/wasm`, return with a TypeError and abort these substeps. + if !&mimetype[..].eq_ignore_ascii_case(b"application/wasm") { + throw_dom_exception( + cx, + &global, + Error::Type("Response has unsupported MIME type".to_string()), + ); + return false; + } + + //Step 2.4 If response is not CORS-same-origin, return with a TypeError and abort these substeps. + match unwrapped_source.Type() { + DOMResponseType::Basic | DOMResponseType::Cors | DOMResponseType::Default => {}, + _ => { + throw_dom_exception( + cx, + &global, + Error::Type("Response.type must be 'basic', 'cors' or 'default'".to_string()), + ); + return false; + }, + } + + //Step 2.5 If response’s status is not an ok status, return with a TypeError and abort these substeps. + if !unwrapped_source.Ok() { + throw_dom_exception( + cx, + &global, + Error::Type("Response does not have ok status".to_string()), + ); + return false; + } + + // Step 2.6.1 If response body is locked, return with a TypeError and abort these substeps. + if unwrapped_source.is_locked() { + throw_dom_exception( + cx, + &global, + Error::Type("There was an error consuming the Response".to_string()), + ); + return false; + } + + // Step 2.6.2 If response body is alreaady consumed, return with a TypeError and abort these substeps. + if unwrapped_source.is_disturbed() { + throw_dom_exception( + cx, + &global, + Error::Type("Response already consumed".to_string()), + ); + return false; + } + unwrapped_source.set_stream_consumer(Some(StreamConsumer(_consumer))); + } else { + //Step 3 Upon rejection of source, return with reason. + throw_dom_exception( + cx, + &global, + Error::Type("expected Response or Promise resolving to Response".to_string()), + ); + return false; + } + return true; +} + +#[allow(unsafe_code)] +unsafe extern "C" fn report_stream_error(_cx: *mut RawJSContext, error_code: usize) { + error!( + "Error initializing StreamConsumer: {:?}", + RUST_js_GetErrorMessage(ptr::null_mut(), error_code as u32) + ); +} + +pub struct Runnable(*mut JSRunnable); + +#[allow(unsafe_code)] +unsafe impl Sync for Runnable {} +#[allow(unsafe_code)] +unsafe impl Send for Runnable {} + +#[allow(unsafe_code)] +impl Runnable { + fn run(&self, cx: *mut RawJSContext, maybe_shutting_down: Dispatchable_MaybeShuttingDown) { + unsafe { + DispatchableRun(cx, self.0, maybe_shutting_down); + } + } +} |