aboutsummaryrefslogtreecommitdiffstats
path: root/components/script/script_runtime.rs
diff options
context:
space:
mode:
authoryvt <i@yvt.jp>2021-07-10 17:24:27 +0900
committeryvt <i@yvt.jp>2021-07-10 17:55:42 +0900
commit01a7de50ab1843d85295f9dccad7f4c099e7208c (patch)
treeee53fb6e8889deb7b880ee969e6c662e6128d210 /components/script/script_runtime.rs
parentff8d2cdbbfc7a9dc7f38b7dd47cb350fde39388f (diff)
parent94b613fbdaa2b98f2179fc0bbda13c64e6fa0d38 (diff)
downloadservo-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.rs1115
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);
+ }
+ }
+}