mirror of
https://github.com/servo/servo.git
synced 2025-06-06 16:45:39 +00:00
* script: Move Reflector to script_bindings. Signed-off-by: Josh Matthews <josh@joshmatthews.net> * script: Extract global() helper from DomObject into new trait. Move DomObject and related traits to script_bindings. Signed-off-by: Josh Matthews <josh@joshmatthews.net> --------- Signed-off-by: Josh Matthews <josh@joshmatthews.net>
1197 lines
45 KiB
Rust
1197 lines
45 KiB
Rust
/* 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/. */
|
||
|
||
//! The script runtime contains common traits and structs commonly used by the
|
||
//! script thread, the dom, and the worker threads.
|
||
|
||
#![allow(dead_code)]
|
||
|
||
use core::ffi::c_char;
|
||
use std::cell::{Cell, LazyCell, RefCell};
|
||
use std::collections::HashSet;
|
||
use std::ffi::CString;
|
||
use std::io::{stdout, Write};
|
||
use std::ops::Deref;
|
||
use std::os::raw::c_void;
|
||
use std::rc::Rc;
|
||
use std::sync::Mutex;
|
||
use std::time::{Duration, Instant};
|
||
use std::{os, ptr, thread};
|
||
|
||
use background_hang_monitor_api::ScriptHangAnnotation;
|
||
use content_security_policy::{CheckResult, PolicyDisposition};
|
||
use js::conversions::jsstr_to_string;
|
||
use js::glue::{
|
||
CollectServoSizes, CreateJobQueue, DeleteJobQueue, DispatchableRun, JobQueueTraps,
|
||
RUST_js_GetErrorMessage, SetBuildId, StreamConsumerConsumeChunk,
|
||
StreamConsumerNoteResponseURLs, StreamConsumerStreamEnd, StreamConsumerStreamError,
|
||
};
|
||
use js::jsapi::{
|
||
AsmJSOption, BuildIdCharVector, ContextOptionsRef, DisableIncrementalGC,
|
||
Dispatchable as JSRunnable, Dispatchable_MaybeShuttingDown, GCDescription, GCOptions,
|
||
GCProgress, GCReason, GetPromiseUserInputEventHandlingState, HandleObject, HandleString, Heap,
|
||
InitConsumeStreamCallback, InitDispatchToEventLoop, JSContext as RawJSContext, JSGCParamKey,
|
||
JSGCStatus, JSJitCompilerOption, JSObject, JSSecurityCallbacks, JSTracer,
|
||
JS_AddExtraGCRootsTracer, JS_InitDestroyPrincipalsCallback, JS_InitReadPrincipalsCallback,
|
||
JS_SetGCCallback, JS_SetGCParameter, JS_SetGlobalJitCompilerOption,
|
||
JS_SetOffthreadIonCompilationEnabled, JS_SetParallelParsingEnabled, JS_SetSecurityCallbacks,
|
||
JobQueue, MimeType, PromiseRejectionHandlingState, PromiseUserInputEventHandlingState,
|
||
RuntimeCode, SetDOMCallbacks, SetGCSliceCallback, SetJobQueue, SetPreserveWrapperCallbacks,
|
||
SetProcessBuildIdOp, SetPromiseRejectionTrackerCallback, StreamConsumer as JSStreamConsumer,
|
||
};
|
||
use js::jsval::UndefinedValue;
|
||
use js::panic::wrap_panic;
|
||
use js::rust::wrappers::{GetPromiseIsHandled, JS_GetPromiseResult};
|
||
pub(crate) use js::rust::ThreadSafeJSContext;
|
||
use js::rust::{
|
||
describe_scripted_caller, Handle, HandleObject as RustHandleObject, IntoHandle, JSEngine,
|
||
JSEngineHandle, ParentRuntime, Runtime as RustRuntime,
|
||
};
|
||
use malloc_size_of::MallocSizeOfOps;
|
||
use malloc_size_of_derive::MallocSizeOf;
|
||
use profile_traits::mem::{Report, ReportKind};
|
||
use profile_traits::path;
|
||
use profile_traits::time::ProfilerCategory;
|
||
use servo_config::{opts, pref};
|
||
use style::thread_state::{self, ThreadState};
|
||
|
||
use crate::body::BodyMixin;
|
||
use crate::dom::bindings::codegen::Bindings::PromiseBinding::PromiseJobCallback;
|
||
use crate::dom::bindings::codegen::Bindings::ResponseBinding::ResponseType as DOMResponseType;
|
||
use crate::dom::bindings::codegen::Bindings::ResponseBinding::Response_Binding::ResponseMethods;
|
||
use crate::dom::bindings::conversions::{
|
||
get_dom_class, private_from_object, 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, Trusted, TrustedPromise,
|
||
};
|
||
use crate::dom::bindings::reflector::{DomGlobal, DomObject};
|
||
use crate::dom::bindings::root::trace_roots;
|
||
use crate::dom::bindings::utils::DOM_CALLBACKS;
|
||
use crate::dom::bindings::{principals, settings_stack};
|
||
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::security_manager::CSPViolationReporter;
|
||
use crate::task_source::SendableTaskSource;
|
||
|
||
static JOB_QUEUE_TRAPS: JobQueueTraps = JobQueueTraps {
|
||
getIncumbentGlobal: Some(get_incumbent_global),
|
||
enqueuePromiseJob: Some(enqueue_promise_job),
|
||
empty: Some(empty),
|
||
};
|
||
|
||
static SECURITY_CALLBACKS: JSSecurityCallbacks = JSSecurityCallbacks {
|
||
contentSecurityPolicyAllows: Some(content_security_policy_allows),
|
||
subsumes: Some(principals::subsumes),
|
||
};
|
||
|
||
#[derive(Clone, Copy, Debug, Eq, Hash, JSTraceable, MallocSizeOf, PartialEq)]
|
||
pub(crate) enum ScriptThreadEventCategory {
|
||
AttachLayout,
|
||
ConstellationMsg,
|
||
DevtoolsMsg,
|
||
DocumentEvent,
|
||
DomEvent,
|
||
FileRead,
|
||
FormPlannedNavigation,
|
||
HistoryEvent,
|
||
ImageCacheMsg,
|
||
InputEvent,
|
||
NetworkEvent,
|
||
PortMessage,
|
||
Rendering,
|
||
Resize,
|
||
ScriptEvent,
|
||
SetScrollState,
|
||
SetViewport,
|
||
StylesheetLoad,
|
||
TimerEvent,
|
||
UpdateReplacedElement,
|
||
WebSocketEvent,
|
||
WorkerEvent,
|
||
WorkletEvent,
|
||
ServiceWorkerEvent,
|
||
EnterFullscreen,
|
||
ExitFullscreen,
|
||
PerformanceTimelineTask,
|
||
#[cfg(feature = "webgpu")]
|
||
WebGPUMsg,
|
||
}
|
||
|
||
impl From<ScriptThreadEventCategory> for ProfilerCategory {
|
||
fn from(category: ScriptThreadEventCategory) -> Self {
|
||
match category {
|
||
ScriptThreadEventCategory::AttachLayout => ProfilerCategory::ScriptAttachLayout,
|
||
ScriptThreadEventCategory::ConstellationMsg => ProfilerCategory::ScriptConstellationMsg,
|
||
ScriptThreadEventCategory::DevtoolsMsg => ProfilerCategory::ScriptDevtoolsMsg,
|
||
ScriptThreadEventCategory::DocumentEvent => ProfilerCategory::ScriptDocumentEvent,
|
||
ScriptThreadEventCategory::DomEvent => ProfilerCategory::ScriptDomEvent,
|
||
ScriptThreadEventCategory::EnterFullscreen => ProfilerCategory::ScriptEnterFullscreen,
|
||
ScriptThreadEventCategory::ExitFullscreen => ProfilerCategory::ScriptExitFullscreen,
|
||
ScriptThreadEventCategory::FileRead => ProfilerCategory::ScriptFileRead,
|
||
ScriptThreadEventCategory::FormPlannedNavigation => {
|
||
ProfilerCategory::ScriptPlannedNavigation
|
||
},
|
||
ScriptThreadEventCategory::HistoryEvent => ProfilerCategory::ScriptHistoryEvent,
|
||
ScriptThreadEventCategory::ImageCacheMsg => ProfilerCategory::ScriptImageCacheMsg,
|
||
ScriptThreadEventCategory::InputEvent => ProfilerCategory::ScriptInputEvent,
|
||
ScriptThreadEventCategory::NetworkEvent => ProfilerCategory::ScriptNetworkEvent,
|
||
ScriptThreadEventCategory::PerformanceTimelineTask => {
|
||
ProfilerCategory::ScriptPerformanceEvent
|
||
},
|
||
ScriptThreadEventCategory::PortMessage => ProfilerCategory::ScriptPortMessage,
|
||
ScriptThreadEventCategory::Resize => ProfilerCategory::ScriptResize,
|
||
ScriptThreadEventCategory::Rendering => ProfilerCategory::ScriptRendering,
|
||
ScriptThreadEventCategory::ScriptEvent => ProfilerCategory::ScriptEvent,
|
||
ScriptThreadEventCategory::ServiceWorkerEvent => {
|
||
ProfilerCategory::ScriptServiceWorkerEvent
|
||
},
|
||
ScriptThreadEventCategory::SetScrollState => ProfilerCategory::ScriptSetScrollState,
|
||
ScriptThreadEventCategory::SetViewport => ProfilerCategory::ScriptSetViewport,
|
||
ScriptThreadEventCategory::StylesheetLoad => ProfilerCategory::ScriptStylesheetLoad,
|
||
ScriptThreadEventCategory::TimerEvent => ProfilerCategory::ScriptTimerEvent,
|
||
ScriptThreadEventCategory::UpdateReplacedElement => {
|
||
ProfilerCategory::ScriptUpdateReplacedElement
|
||
},
|
||
ScriptThreadEventCategory::WebSocketEvent => ProfilerCategory::ScriptWebSocketEvent,
|
||
ScriptThreadEventCategory::WorkerEvent => ProfilerCategory::ScriptWorkerEvent,
|
||
ScriptThreadEventCategory::WorkletEvent => ProfilerCategory::ScriptWorkletEvent,
|
||
#[cfg(feature = "webgpu")]
|
||
ScriptThreadEventCategory::WebGPUMsg => ProfilerCategory::ScriptWebGPUMsg,
|
||
}
|
||
}
|
||
}
|
||
|
||
impl From<ScriptThreadEventCategory> for ScriptHangAnnotation {
|
||
fn from(category: ScriptThreadEventCategory) -> Self {
|
||
match category {
|
||
ScriptThreadEventCategory::AttachLayout => ScriptHangAnnotation::AttachLayout,
|
||
ScriptThreadEventCategory::ConstellationMsg => ScriptHangAnnotation::ConstellationMsg,
|
||
ScriptThreadEventCategory::DevtoolsMsg => ScriptHangAnnotation::DevtoolsMsg,
|
||
ScriptThreadEventCategory::DocumentEvent => ScriptHangAnnotation::DocumentEvent,
|
||
ScriptThreadEventCategory::DomEvent => ScriptHangAnnotation::DomEvent,
|
||
ScriptThreadEventCategory::FileRead => ScriptHangAnnotation::FileRead,
|
||
ScriptThreadEventCategory::FormPlannedNavigation => {
|
||
ScriptHangAnnotation::FormPlannedNavigation
|
||
},
|
||
ScriptThreadEventCategory::HistoryEvent => ScriptHangAnnotation::HistoryEvent,
|
||
ScriptThreadEventCategory::ImageCacheMsg => ScriptHangAnnotation::ImageCacheMsg,
|
||
ScriptThreadEventCategory::InputEvent => ScriptHangAnnotation::InputEvent,
|
||
ScriptThreadEventCategory::NetworkEvent => ScriptHangAnnotation::NetworkEvent,
|
||
ScriptThreadEventCategory::Rendering => ScriptHangAnnotation::Rendering,
|
||
ScriptThreadEventCategory::Resize => ScriptHangAnnotation::Resize,
|
||
ScriptThreadEventCategory::ScriptEvent => ScriptHangAnnotation::ScriptEvent,
|
||
ScriptThreadEventCategory::SetScrollState => ScriptHangAnnotation::SetScrollState,
|
||
ScriptThreadEventCategory::SetViewport => ScriptHangAnnotation::SetViewport,
|
||
ScriptThreadEventCategory::StylesheetLoad => ScriptHangAnnotation::StylesheetLoad,
|
||
ScriptThreadEventCategory::TimerEvent => ScriptHangAnnotation::TimerEvent,
|
||
ScriptThreadEventCategory::UpdateReplacedElement => {
|
||
ScriptHangAnnotation::UpdateReplacedElement
|
||
},
|
||
ScriptThreadEventCategory::WebSocketEvent => ScriptHangAnnotation::WebSocketEvent,
|
||
ScriptThreadEventCategory::WorkerEvent => ScriptHangAnnotation::WorkerEvent,
|
||
ScriptThreadEventCategory::WorkletEvent => ScriptHangAnnotation::WorkletEvent,
|
||
ScriptThreadEventCategory::ServiceWorkerEvent => {
|
||
ScriptHangAnnotation::ServiceWorkerEvent
|
||
},
|
||
ScriptThreadEventCategory::EnterFullscreen => ScriptHangAnnotation::EnterFullscreen,
|
||
ScriptThreadEventCategory::ExitFullscreen => ScriptHangAnnotation::ExitFullscreen,
|
||
ScriptThreadEventCategory::PerformanceTimelineTask => {
|
||
ScriptHangAnnotation::PerformanceTimelineTask
|
||
},
|
||
ScriptThreadEventCategory::PortMessage => ScriptHangAnnotation::PortMessage,
|
||
#[cfg(feature = "webgpu")]
|
||
ScriptThreadEventCategory::WebGPUMsg => ScriptHangAnnotation::WebGPUMsg,
|
||
}
|
||
}
|
||
}
|
||
|
||
#[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::already(&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)]
|
||
#[cfg_attr(crown, allow(crown::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.task_manager().dom_manipulation_task_source().queue(
|
||
task!(rejection_handled_event: move || {
|
||
let target = target.root();
|
||
let cx = GlobalScope::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(),
|
||
CanGc::note()
|
||
);
|
||
|
||
event.upcast::<Event>().fire(&target, CanGc::note());
|
||
})
|
||
);
|
||
},
|
||
};
|
||
})
|
||
}
|
||
|
||
#[allow(unsafe_code)]
|
||
unsafe extern "C" fn content_security_policy_allows(
|
||
cx: *mut RawJSContext,
|
||
runtime_code: RuntimeCode,
|
||
sample: HandleString,
|
||
) -> bool {
|
||
let mut allowed = false;
|
||
let cx = JSContext::from_ptr(cx);
|
||
wrap_panic(&mut || {
|
||
// SpiderMonkey provides null pointer when executing webassembly.
|
||
let sample = match sample {
|
||
sample if !sample.is_null() => Some(jsstr_to_string(*cx, *sample)),
|
||
_ => None,
|
||
};
|
||
let in_realm_proof = AlreadyInRealm::assert_for_cx(cx);
|
||
let global = GlobalScope::from_context(*cx, InRealm::Already(&in_realm_proof));
|
||
let Some(csp_list) = global.get_csp_list() else {
|
||
allowed = true;
|
||
return;
|
||
};
|
||
|
||
let is_js_evaluation_allowed = csp_list.is_js_evaluation_allowed() == CheckResult::Allowed;
|
||
let is_wasm_evaluation_allowed =
|
||
csp_list.is_wasm_evaluation_allowed() == CheckResult::Allowed;
|
||
let scripted_caller = describe_scripted_caller(*cx).unwrap_or_default();
|
||
|
||
allowed = match runtime_code {
|
||
RuntimeCode::JS if is_js_evaluation_allowed => true,
|
||
RuntimeCode::WASM if is_wasm_evaluation_allowed => true,
|
||
_ => false,
|
||
};
|
||
|
||
if !allowed {
|
||
// FIXME: Don't fire event if `script-src` and `default-src`
|
||
// were not passed.
|
||
for policy in csp_list.0 {
|
||
let task = CSPViolationReporter::new(
|
||
&global,
|
||
sample.clone(),
|
||
policy.disposition == PolicyDisposition::Report,
|
||
runtime_code,
|
||
scripted_caller.filename.clone(),
|
||
scripted_caller.line,
|
||
scripted_caller.col,
|
||
);
|
||
global
|
||
.task_manager()
|
||
.dom_manipulation_task_source()
|
||
.queue(task);
|
||
}
|
||
}
|
||
});
|
||
allowed
|
||
}
|
||
|
||
#[allow(unsafe_code)]
|
||
#[cfg_attr(crown, allow(crown::unrooted_must_root))]
|
||
/// <https://html.spec.whatwg.org/multipage/#notify-about-rejected-promises>
|
||
pub(crate) fn notify_about_rejected_promises(global: &GlobalScope) {
|
||
let cx = GlobalScope::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.task_manager().dom_manipulation_task_source().queue(
|
||
task!(unhandled_rejection_event: move || {
|
||
let target = target.root();
|
||
let cx = GlobalScope::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(),
|
||
CanGc::note()
|
||
);
|
||
|
||
let event_status = event.upcast::<Event>().fire(&target, CanGc::note());
|
||
|
||
// 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());
|
||
}
|
||
}
|
||
})
|
||
);
|
||
}
|
||
}
|
||
}
|
||
|
||
#[derive(JSTraceable)]
|
||
pub(crate) struct Runtime {
|
||
rt: RustRuntime,
|
||
pub(crate) microtask_queue: Rc<MicrotaskQueue>,
|
||
job_queue: *mut JobQueue,
|
||
networking_task_src: Option<Box<SendableTaskSource>>,
|
||
}
|
||
|
||
impl Runtime {
|
||
/// Create a new runtime, optionally with the given [`SendableTaskSource`] for networking.
|
||
///
|
||
/// # Safety
|
||
///
|
||
/// If panicking does not abort the program, any threads with child runtimes will continue
|
||
/// executing after the thread with the parent runtime panics, but they will be in an
|
||
/// invalid and undefined state.
|
||
///
|
||
/// This, like many calls to SpiderMoney API, is unsafe.
|
||
#[allow(unsafe_code)]
|
||
pub(crate) fn new(networking_task_source: Option<SendableTaskSource>) -> Runtime {
|
||
unsafe { Self::new_with_parent(None, networking_task_source) }
|
||
}
|
||
|
||
/// Create a new runtime, optionally with the given [`ParentRuntime`] and [`SendableTaskSource`]
|
||
/// for networking.
|
||
///
|
||
/// # Safety
|
||
///
|
||
/// If panicking does not abort the program, any threads with child runtimes will continue
|
||
/// executing after the thread with the parent runtime panics, but they will be in an
|
||
/// invalid and undefined state.
|
||
///
|
||
/// The `parent` pointer in the [`ParentRuntime`] argument must point to a valid object in memory.
|
||
///
|
||
/// This, like many calls to the SpiderMoney API, is unsafe.
|
||
#[allow(unsafe_code)]
|
||
pub(crate) unsafe fn new_with_parent(
|
||
parent: Option<ParentRuntime>,
|
||
networking_task_source: Option<SendableTaskSource>,
|
||
) -> Runtime {
|
||
LiveDOMReferences::initialize();
|
||
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(cx, Some(trace_rust_roots), ptr::null_mut());
|
||
|
||
JS_SetSecurityCallbacks(cx, &SECURITY_CALLBACKS);
|
||
|
||
JS_InitDestroyPrincipalsCallback(cx, Some(principals::destroy_servo_jsprincipal));
|
||
JS_InitReadPrincipalsCallback(cx, Some(principals::read_jsprincipal));
|
||
|
||
// Needed for debug assertions about whether GC is running.
|
||
if cfg!(debug_assertions) {
|
||
JS_SetGCCallback(cx, Some(debug_gc_callback), ptr::null_mut());
|
||
}
|
||
|
||
if opts::get().debug.gc_profile {
|
||
SetGCSliceCallback(cx, Some(gc_slice_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(cx);
|
||
|
||
unsafe extern "C" fn dispatch_to_event_loop(
|
||
closure: *mut c_void,
|
||
dispatchable: *mut JSRunnable,
|
||
) -> bool {
|
||
let networking_task_src: &SendableTaskSource = &*(closure as *mut SendableTaskSource);
|
||
let runnable = Runnable(dispatchable);
|
||
let task = task!(dispatch_to_event_loop_message: move || {
|
||
if let Some(cx) = RustRuntime::get() {
|
||
runnable.run(cx.as_ptr(), Dispatchable_MaybeShuttingDown::NotShuttingDown);
|
||
}
|
||
});
|
||
|
||
networking_task_src.queue_unconditionally(task);
|
||
true
|
||
}
|
||
|
||
let mut networking_task_src_ptr = std::ptr::null_mut();
|
||
if let Some(source) = networking_task_source {
|
||
networking_task_src_ptr = Box::into_raw(Box::new(source));
|
||
InitDispatchToEventLoop(
|
||
cx,
|
||
Some(dispatch_to_event_loop),
|
||
networking_task_src_ptr as *mut c_void,
|
||
);
|
||
}
|
||
|
||
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_INTERPRETER_ENABLE,
|
||
pref!(js_baseline_interpreter_enabled) as u32,
|
||
);
|
||
JS_SetGlobalJitCompilerOption(
|
||
cx,
|
||
JSJitCompilerOption::JSJITCOMPILER_BASELINE_ENABLE,
|
||
pref!(js_baseline_jit_enabled) as u32,
|
||
);
|
||
JS_SetGlobalJitCompilerOption(
|
||
cx,
|
||
JSJitCompilerOption::JSJITCOMPILER_ION_ENABLE,
|
||
pref!(js_ion_enabled) as u32,
|
||
);
|
||
cx_opts.compileOptions_.asmJSOption_ = if pref!(js_asmjs_enabled) {
|
||
AsmJSOption::Enabled
|
||
} else {
|
||
AsmJSOption::DisabledByAsmJSPref
|
||
};
|
||
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));
|
||
// TODO: handle js.throw_on_asmjs_validation_failure (needs new Spidermonkey)
|
||
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_jit_unsafe_eager_compilation_enabled) {
|
||
0
|
||
} else {
|
||
u32::MAX
|
||
},
|
||
);
|
||
JS_SetGlobalJitCompilerOption(
|
||
cx,
|
||
JSJitCompilerOption::JSJITCOMPILER_ION_NORMAL_WARMUP_TRIGGER,
|
||
if pref!(js_ion_unsafe_eager_compilation_enabled) {
|
||
0
|
||
} else {
|
||
u32::MAX
|
||
},
|
||
);
|
||
// 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)
|
||
// TODO: handle js.shared_memory.enabled
|
||
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),
|
||
);
|
||
// NOTE: This is disabled above, so enabling it here will do nothing for now.
|
||
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);
|
||
}
|
||
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) = 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) = 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) = 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) = 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) = 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) = 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) = 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);
|
||
}
|
||
|
||
Runtime {
|
||
rt: runtime,
|
||
microtask_queue,
|
||
job_queue,
|
||
networking_task_src: (!networking_task_src_ptr.is_null())
|
||
.then(|| Box::from_raw(networking_task_src_ptr)),
|
||
}
|
||
}
|
||
|
||
pub(crate) fn thread_safe_js_context(&self) -> ThreadSafeJSContext {
|
||
self.rt.thread_safe_js_context()
|
||
}
|
||
}
|
||
|
||
impl Drop for Runtime {
|
||
#[allow(unsafe_code)]
|
||
fn drop(&mut self) {
|
||
self.microtask_queue.clear();
|
||
|
||
unsafe {
|
||
DeleteJobQueue(self.job_queue);
|
||
}
|
||
THREAD_ACTIVE.with(|t| {
|
||
LiveDOMReferences::destruct();
|
||
t.set(false);
|
||
});
|
||
}
|
||
}
|
||
|
||
impl Deref for Runtime {
|
||
type Target = RustRuntime;
|
||
fn deref(&self) -> &RustRuntime {
|
||
&self.rt
|
||
}
|
||
}
|
||
|
||
pub struct JSEngineSetup(JSEngine);
|
||
|
||
impl Default for JSEngineSetup {
|
||
fn default() -> Self {
|
||
let engine = JSEngine::init().unwrap();
|
||
*JS_ENGINE.lock().unwrap() = Some(engine.handle());
|
||
Self(engine)
|
||
}
|
||
}
|
||
|
||
impl Drop for JSEngineSetup {
|
||
fn drop(&mut self) {
|
||
*JS_ENGINE.lock().unwrap() = None;
|
||
|
||
while !self.0.can_shutdown() {
|
||
thread::sleep(Duration::from_millis(50));
|
||
}
|
||
}
|
||
}
|
||
|
||
static JS_ENGINE: Mutex<Option<JSEngineHandle>> = Mutex::new(None);
|
||
|
||
fn in_range<T: PartialOrd + Copy>(val: T, min: T, max: T) -> Option<T> {
|
||
if val < min || val >= max {
|
||
None
|
||
} else {
|
||
Some(val)
|
||
}
|
||
}
|
||
|
||
thread_local!(static SEEN_POINTERS: LazyCell<RefCell<HashSet<*const c_void>>> = const {
|
||
LazyCell::new(|| RefCell::new(HashSet::new()))
|
||
});
|
||
|
||
#[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;
|
||
|
||
if dom_object.is_null() {
|
||
return 0;
|
||
}
|
||
let seen_pointer =
|
||
move |ptr| SEEN_POINTERS.with(|pointers| !pointers.borrow_mut().insert(ptr));
|
||
let mut ops = MallocSizeOfOps::new(
|
||
servo_allocator::usable_size,
|
||
None,
|
||
Some(Box::new(seen_pointer)),
|
||
);
|
||
(v.malloc_size_of)(&mut ops, dom_object)
|
||
},
|
||
Err(_e) => 0,
|
||
}
|
||
}
|
||
|
||
thread_local!(static GC_CYCLE_START: Cell<Option<Instant>> = const { Cell::new(None) });
|
||
thread_local!(static GC_SLICE_START: Cell<Option<Instant>> = const { Cell::new(None) });
|
||
|
||
#[allow(unsafe_code)]
|
||
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(Instant::now()));
|
||
println!("GC cycle began");
|
||
}),
|
||
GCProgress::GC_SLICE_BEGIN => GC_SLICE_START.with(|start| {
|
||
start.set(Some(Instant::now()));
|
||
println!("GC slice began");
|
||
}),
|
||
GCProgress::GC_SLICE_END => GC_SLICE_START.with(|start| {
|
||
let duration = start.get().unwrap().elapsed();
|
||
start.set(None);
|
||
println!("GC slice ended: duration={:?}", duration);
|
||
}),
|
||
GCProgress::GC_CYCLE_END => GC_CYCLE_START.with(|start| {
|
||
let duration = start.get().unwrap().elapsed();
|
||
start.set(None);
|
||
println!("GC cycle ended: duration={:?}", duration);
|
||
}),
|
||
};
|
||
if !desc.is_null() {
|
||
let desc: &GCDescription = &*desc;
|
||
let options = match desc.options_ {
|
||
GCOptions::Normal => "Normal",
|
||
GCOptions::Shrink => "Shrink",
|
||
GCOptions::Shutdown => "Shutdown",
|
||
};
|
||
println!(" isZone={}, options={}", desc.isZone_, options);
|
||
}
|
||
let _ = stdout().flush();
|
||
}
|
||
|
||
#[allow(unsafe_code)]
|
||
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(ThreadState::IN_GC),
|
||
JSGCStatus::JSGC_END => thread_state::exit(ThreadState::IN_GC),
|
||
}
|
||
}
|
||
|
||
thread_local!(
|
||
static THREAD_ACTIVE: Cell<bool> = const { Cell::new(true) };
|
||
);
|
||
|
||
pub(crate) fn runtime_is_alive() -> bool {
|
||
THREAD_ACTIVE.with(|t| t.get())
|
||
}
|
||
|
||
#[allow(unsafe_code)]
|
||
unsafe extern "C" fn trace_rust_roots(tr: *mut JSTracer, _data: *mut os::raw::c_void) {
|
||
if !THREAD_ACTIVE.with(|t| t.get()) {
|
||
return;
|
||
}
|
||
trace!("starting custom root handler");
|
||
trace_thread(tr);
|
||
trace_roots(tr);
|
||
trace_refcounted_objects(tr);
|
||
settings_stack::trace(tr);
|
||
trace!("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] as *const c_char, servo_id.len())
|
||
}
|
||
|
||
#[allow(unsafe_code)]
|
||
#[cfg(feature = "debugmozjs")]
|
||
unsafe fn set_gc_zeal_options(cx: *mut RawJSContext) {
|
||
use js::jsapi::SetGCZeal;
|
||
|
||
let level = match pref!(js_mem_gc_zeal_level) {
|
||
level @ 0..=14 => level as u8,
|
||
_ => return,
|
||
};
|
||
let frequency = match pref!(js_mem_gc_zeal_frequency) {
|
||
frequency if frequency >= 0 => frequency as u32,
|
||
// https://searchfox.org/mozilla-esr128/source/js/public/GCAPI.h#1392
|
||
_ => 5000,
|
||
};
|
||
SetGCZeal(cx, level, frequency);
|
||
}
|
||
|
||
#[allow(unsafe_code)]
|
||
#[cfg(not(feature = "debugmozjs"))]
|
||
unsafe fn set_gc_zeal_options(_: *mut RawJSContext) {}
|
||
|
||
#[derive(Clone, Copy)]
|
||
#[repr(transparent)]
|
||
pub(crate) struct JSContext(*mut RawJSContext);
|
||
|
||
#[allow(unsafe_code)]
|
||
impl JSContext {
|
||
/// Create a new [`JSContext`] object from the given raw pointer.
|
||
///
|
||
/// # Safety
|
||
///
|
||
/// The `RawJSContext` argument must point to a valid `RawJSContext` in memory.
|
||
pub(crate) unsafe fn from_ptr(raw_js_context: *mut RawJSContext) -> Self {
|
||
JSContext(raw_js_context)
|
||
}
|
||
|
||
#[allow(unsafe_code)]
|
||
pub(crate) fn get_reports(&self, path_seg: String) -> Vec<Report> {
|
||
SEEN_POINTERS.with(|pointers| pointers.borrow_mut().clear());
|
||
let stats = unsafe {
|
||
let mut stats = ::std::mem::zeroed();
|
||
if !CollectServoSizes(self.0, &mut stats, Some(get_size)) {
|
||
return vec![];
|
||
}
|
||
stats
|
||
};
|
||
|
||
let mut reports = vec![];
|
||
let mut report = |mut path_suffix, kind, size| {
|
||
let mut path = path![path_seg, "js"];
|
||
path.append(&mut path_suffix);
|
||
reports.push(Report { path, kind, size })
|
||
};
|
||
|
||
// A note about possibly confusing terminology: the JS GC "heap" is allocated via
|
||
// 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", "unused"],
|
||
ReportKind::ExplicitNonHeapSize,
|
||
stats.gcHeapUnused,
|
||
);
|
||
|
||
report(
|
||
path!["gc-heap", "admin"],
|
||
ReportKind::ExplicitNonHeapSize,
|
||
stats.gcHeapAdmin,
|
||
);
|
||
|
||
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!["non-heap"],
|
||
ReportKind::ExplicitNonHeapSize,
|
||
stats.nonHeap,
|
||
);
|
||
reports
|
||
}
|
||
}
|
||
|
||
#[allow(unsafe_code)]
|
||
impl Deref for JSContext {
|
||
type Target = *mut RawJSContext;
|
||
|
||
fn deref(&self) -> &Self::Target {
|
||
&self.0
|
||
}
|
||
}
|
||
|
||
pub(crate) struct StreamConsumer(*mut JSStreamConsumer);
|
||
|
||
#[allow(unsafe_code)]
|
||
impl StreamConsumer {
|
||
pub(crate) fn consume_chunk(&self, stream: &[u8]) -> bool {
|
||
unsafe {
|
||
let stream_ptr = stream.as_ptr();
|
||
StreamConsumerConsumeChunk(self.0, stream_ptr, stream.len())
|
||
}
|
||
}
|
||
|
||
pub(crate) fn stream_end(&self) {
|
||
unsafe {
|
||
StreamConsumerStreamEnd(self.0);
|
||
}
|
||
}
|
||
|
||
pub(crate) fn stream_error(&self, error_code: usize) {
|
||
unsafe {
|
||
StreamConsumerStreamError(self.0, error_code);
|
||
}
|
||
}
|
||
|
||
pub(crate) 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(CanGc::note()).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;
|
||
}
|
||
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(crate) 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);
|
||
}
|
||
}
|
||
}
|
||
|
||
#[derive(Clone, Copy, Debug)]
|
||
/// A compile-time marker that there are operations that could trigger a JS garbage collection
|
||
/// operation within the current stack frame. It is trivially copyable, so it should be passed
|
||
/// as a function argument and reused when calling other functions whenever possible. Since it
|
||
/// is only meaningful within the current stack frame, it is impossible to move it to a different
|
||
/// thread or into a task that will execute asynchronously.
|
||
pub(crate) struct CanGc(std::marker::PhantomData<*mut ()>);
|
||
|
||
impl CanGc {
|
||
/// Create a new CanGc value, representing that a GC operation is possible within the
|
||
/// current stack frame.
|
||
pub(crate) fn note() -> CanGc {
|
||
CanGc(std::marker::PhantomData)
|
||
}
|
||
}
|