<!-- Please describe your changes on the following line: --> This PR implements the current draft Houdini Worklets specification (https://drafts.css-houdini.org/worklets/). The implementation is intended to provide a responsive environment for worklet execution, and in particular to ensure that the primary worklet executor does not garbage collect, and does not block loading module code. The implementation does this by providing a thread pool, and performing GC and module loading in a backup thread, not in the primary thread. --- <!-- Thank you for contributing to Servo! Please replace each `[ ]` by `[X]` when the step is complete, and replace `__` with appropriate data: --> - [x] `./mach build -d` does not report any errors - [x] `./mach test-tidy` does not report any errors - [x] These changes fix #16206 - [x] There are tests for these changes <!-- Also, please make sure that "Allow edits from maintainers" checkbox is checked, so that we can help you if you get stuck somewhere along the way.--> <!-- Pull requests that do not address these steps are welcome, but they will require additional verification as part of the review process. --> Source-Repo: https://github.com/servo/servo Source-Revision: ac99a48aeaa184d3acdb39d249636a140c4b7393
582 lines
20 KiB
Rust
582 lines
20 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 http://mozilla.org/MPL/2.0/. */
|
|
|
|
use devtools_traits::{ScriptToDevtoolsControlMsg, WorkerId};
|
|
use dom::bindings::cell::DOMRefCell;
|
|
use dom::bindings::codegen::Bindings::WindowBinding::WindowMethods;
|
|
use dom::bindings::conversions::root_from_object;
|
|
use dom::bindings::error::{ErrorInfo, report_pending_exception};
|
|
use dom::bindings::inheritance::Castable;
|
|
use dom::bindings::js::{MutNullableJS, Root};
|
|
use dom::bindings::reflector::DomObject;
|
|
use dom::bindings::settings_stack::{AutoEntryScript, entry_global, incumbent_global};
|
|
use dom::bindings::str::DOMString;
|
|
use dom::crypto::Crypto;
|
|
use dom::dedicatedworkerglobalscope::DedicatedWorkerGlobalScope;
|
|
use dom::errorevent::ErrorEvent;
|
|
use dom::event::{Event, EventBubbles, EventCancelable, EventStatus};
|
|
use dom::eventtarget::EventTarget;
|
|
use dom::window::Window;
|
|
use dom::workerglobalscope::WorkerGlobalScope;
|
|
use dom::workletglobalscope::WorkletGlobalScope;
|
|
use dom_struct::dom_struct;
|
|
use ipc_channel::ipc::IpcSender;
|
|
use js::{JSCLASS_IS_DOMJSCLASS, JSCLASS_IS_GLOBAL};
|
|
use js::glue::{IsWrapper, UnwrapObject};
|
|
use js::jsapi::{CurrentGlobalOrNull, GetGlobalForObjectCrossCompartment};
|
|
use js::jsapi::{HandleValue, Evaluate2, JSAutoCompartment, JSContext};
|
|
use js::jsapi::{JSObject, JS_GetContext};
|
|
use js::jsapi::{JS_GetObjectRuntime, MutableHandleValue};
|
|
use js::panic::maybe_resume_unwind;
|
|
use js::rust::{CompileOptionsWrapper, Runtime, get_object_class};
|
|
use libc;
|
|
use microtask::Microtask;
|
|
use msg::constellation_msg::PipelineId;
|
|
use net_traits::{CoreResourceThread, ResourceThreads, IpcSend};
|
|
use profile_traits::{mem, time};
|
|
use script_runtime::{CommonScriptMsg, ScriptChan, ScriptPort};
|
|
use script_thread::{MainThreadScriptChan, RunnableWrapper, ScriptThread};
|
|
use script_traits::{MsDuration, ScriptMsg as ConstellationMsg, TimerEvent};
|
|
use script_traits::{TimerEventId, TimerSchedulerMsg, TimerSource};
|
|
use servo_url::{MutableOrigin, ServoUrl};
|
|
use std::cell::Cell;
|
|
use std::collections::HashMap;
|
|
use std::collections::hash_map::Entry;
|
|
use std::ffi::CString;
|
|
use task_source::file_reading::FileReadingTaskSource;
|
|
use task_source::networking::NetworkingTaskSource;
|
|
use time::{Timespec, get_time};
|
|
use timers::{IsInterval, OneshotTimerCallback, OneshotTimerHandle};
|
|
use timers::{OneshotTimers, TimerCallback};
|
|
|
|
#[dom_struct]
|
|
pub struct GlobalScope {
|
|
eventtarget: EventTarget,
|
|
crypto: MutNullableJS<Crypto>,
|
|
next_worker_id: Cell<WorkerId>,
|
|
|
|
/// Pipeline id associated with this global.
|
|
pipeline_id: PipelineId,
|
|
|
|
/// A flag to indicate whether the developer tools has requested
|
|
/// live updates from the worker.
|
|
devtools_wants_updates: Cell<bool>,
|
|
|
|
/// Timers used by the Console API.
|
|
console_timers: DOMRefCell<HashMap<DOMString, u64>>,
|
|
|
|
/// For providing instructions to an optional devtools server.
|
|
#[ignore_heap_size_of = "channels are hard"]
|
|
devtools_chan: Option<IpcSender<ScriptToDevtoolsControlMsg>>,
|
|
|
|
/// For sending messages to the memory profiler.
|
|
#[ignore_heap_size_of = "channels are hard"]
|
|
mem_profiler_chan: mem::ProfilerChan,
|
|
|
|
/// For sending messages to the time profiler.
|
|
#[ignore_heap_size_of = "channels are hard"]
|
|
time_profiler_chan: time::ProfilerChan,
|
|
|
|
/// A handle for communicating messages to the constellation thread.
|
|
#[ignore_heap_size_of = "channels are hard"]
|
|
constellation_chan: IpcSender<ConstellationMsg>,
|
|
|
|
#[ignore_heap_size_of = "channels are hard"]
|
|
scheduler_chan: IpcSender<TimerSchedulerMsg>,
|
|
|
|
/// https://html.spec.whatwg.org/multipage/#in-error-reporting-mode
|
|
in_error_reporting_mode: Cell<bool>,
|
|
|
|
/// Associated resource threads for use by DOM objects like XMLHttpRequest,
|
|
/// including resource_thread, filemanager_thread and storage_thread
|
|
resource_threads: ResourceThreads,
|
|
|
|
timers: OneshotTimers,
|
|
|
|
/// The origin of the globalscope
|
|
origin: MutableOrigin,
|
|
}
|
|
|
|
impl GlobalScope {
|
|
pub fn new_inherited(
|
|
pipeline_id: PipelineId,
|
|
devtools_chan: Option<IpcSender<ScriptToDevtoolsControlMsg>>,
|
|
mem_profiler_chan: mem::ProfilerChan,
|
|
time_profiler_chan: time::ProfilerChan,
|
|
constellation_chan: IpcSender<ConstellationMsg>,
|
|
scheduler_chan: IpcSender<TimerSchedulerMsg>,
|
|
resource_threads: ResourceThreads,
|
|
timer_event_chan: IpcSender<TimerEvent>,
|
|
origin: MutableOrigin)
|
|
-> Self {
|
|
GlobalScope {
|
|
eventtarget: EventTarget::new_inherited(),
|
|
crypto: Default::default(),
|
|
next_worker_id: Cell::new(WorkerId(0)),
|
|
pipeline_id: pipeline_id,
|
|
devtools_wants_updates: Default::default(),
|
|
console_timers: DOMRefCell::new(Default::default()),
|
|
devtools_chan: devtools_chan,
|
|
mem_profiler_chan: mem_profiler_chan,
|
|
time_profiler_chan: time_profiler_chan,
|
|
constellation_chan: constellation_chan,
|
|
scheduler_chan: scheduler_chan.clone(),
|
|
in_error_reporting_mode: Default::default(),
|
|
resource_threads: resource_threads,
|
|
timers: OneshotTimers::new(timer_event_chan, scheduler_chan),
|
|
origin: origin,
|
|
}
|
|
}
|
|
|
|
/// Returns the global scope of the realm that the given DOM object's reflector
|
|
/// was created in.
|
|
#[allow(unsafe_code)]
|
|
pub fn from_reflector<T: DomObject>(reflector: &T) -> Root<Self> {
|
|
unsafe { GlobalScope::from_object(*reflector.reflector().get_jsobject()) }
|
|
}
|
|
|
|
/// Returns the global scope of the realm that the given JS object was created in.
|
|
#[allow(unsafe_code)]
|
|
pub unsafe fn from_object(obj: *mut JSObject) -> Root<Self> {
|
|
assert!(!obj.is_null());
|
|
let global = GetGlobalForObjectCrossCompartment(obj);
|
|
global_scope_from_global(global)
|
|
}
|
|
|
|
/// Returns the global scope for the given JSContext
|
|
#[allow(unsafe_code)]
|
|
pub unsafe fn from_context(cx: *mut JSContext) -> Root<Self> {
|
|
let global = CurrentGlobalOrNull(cx);
|
|
global_scope_from_global(global)
|
|
}
|
|
|
|
/// Returns the global object of the realm that the given JS object
|
|
/// was created in, after unwrapping any wrappers.
|
|
#[allow(unsafe_code)]
|
|
pub unsafe fn from_object_maybe_wrapped(mut obj: *mut JSObject) -> Root<Self> {
|
|
if IsWrapper(obj) {
|
|
obj = UnwrapObject(obj, /* stopAtWindowProxy = */ 0);
|
|
assert!(!obj.is_null());
|
|
}
|
|
GlobalScope::from_object(obj)
|
|
}
|
|
|
|
#[allow(unsafe_code)]
|
|
pub fn get_cx(&self) -> *mut JSContext {
|
|
unsafe {
|
|
let runtime = JS_GetObjectRuntime(
|
|
self.reflector().get_jsobject().get());
|
|
assert!(!runtime.is_null());
|
|
let context = JS_GetContext(runtime);
|
|
assert!(!context.is_null());
|
|
context
|
|
}
|
|
}
|
|
|
|
pub fn crypto(&self) -> Root<Crypto> {
|
|
self.crypto.or_init(|| Crypto::new(self))
|
|
}
|
|
|
|
/// Get next worker id.
|
|
pub fn get_next_worker_id(&self) -> WorkerId {
|
|
let worker_id = self.next_worker_id.get();
|
|
let WorkerId(id_num) = worker_id;
|
|
self.next_worker_id.set(WorkerId(id_num + 1));
|
|
worker_id
|
|
}
|
|
|
|
pub fn live_devtools_updates(&self) -> bool {
|
|
self.devtools_wants_updates.get()
|
|
}
|
|
|
|
pub fn set_devtools_wants_updates(&self, value: bool) {
|
|
self.devtools_wants_updates.set(value);
|
|
}
|
|
|
|
pub fn time(&self, label: DOMString) -> Result<(), ()> {
|
|
let mut timers = self.console_timers.borrow_mut();
|
|
if timers.len() >= 10000 {
|
|
return Err(());
|
|
}
|
|
match timers.entry(label) {
|
|
Entry::Vacant(entry) => {
|
|
entry.insert(timestamp_in_ms(get_time()));
|
|
Ok(())
|
|
},
|
|
Entry::Occupied(_) => Err(()),
|
|
}
|
|
}
|
|
|
|
pub fn time_end(&self, label: &str) -> Result<u64, ()> {
|
|
self.console_timers.borrow_mut().remove(label).ok_or(()).map(|start| {
|
|
timestamp_in_ms(get_time()) - start
|
|
})
|
|
}
|
|
|
|
/// Get an `&IpcSender<ScriptToDevtoolsControlMsg>` to send messages
|
|
/// to the devtools thread when available.
|
|
pub fn devtools_chan(&self) -> Option<&IpcSender<ScriptToDevtoolsControlMsg>> {
|
|
self.devtools_chan.as_ref()
|
|
}
|
|
|
|
/// Get a sender to the memory profiler thread.
|
|
pub fn mem_profiler_chan(&self) -> &mem::ProfilerChan {
|
|
&self.mem_profiler_chan
|
|
}
|
|
|
|
/// Get a sender to the time profiler thread.
|
|
pub fn time_profiler_chan(&self) -> &time::ProfilerChan {
|
|
&self.time_profiler_chan
|
|
}
|
|
|
|
/// Get a sender to the constellation thread.
|
|
pub fn constellation_chan(&self) -> &IpcSender<ConstellationMsg> {
|
|
&self.constellation_chan
|
|
}
|
|
|
|
pub fn scheduler_chan(&self) -> &IpcSender<TimerSchedulerMsg> {
|
|
&self.scheduler_chan
|
|
}
|
|
|
|
/// Get the `PipelineId` for this global scope.
|
|
pub fn pipeline_id(&self) -> PipelineId {
|
|
self.pipeline_id
|
|
}
|
|
|
|
/// Get the origin for this global scope
|
|
pub fn origin(&self) -> &MutableOrigin {
|
|
&self.origin
|
|
}
|
|
|
|
/// Get the [base url](https://html.spec.whatwg.org/multipage/#api-base-url)
|
|
/// for this global scope.
|
|
pub fn api_base_url(&self) -> ServoUrl {
|
|
if let Some(window) = self.downcast::<Window>() {
|
|
// https://html.spec.whatwg.org/multipage/#script-settings-for-browsing-contexts:api-base-url
|
|
return window.Document().base_url();
|
|
}
|
|
if let Some(worker) = self.downcast::<WorkerGlobalScope>() {
|
|
// https://html.spec.whatwg.org/multipage/#script-settings-for-workers:api-base-url
|
|
return worker.get_url().clone();
|
|
}
|
|
if let Some(worker) = self.downcast::<WorkletGlobalScope>() {
|
|
// https://drafts.css-houdini.org/worklets/#script-settings-for-worklets
|
|
return worker.base_url();
|
|
}
|
|
unreachable!();
|
|
}
|
|
|
|
/// Get the URL for this global scope.
|
|
pub fn get_url(&self) -> ServoUrl {
|
|
if let Some(window) = self.downcast::<Window>() {
|
|
return window.get_url();
|
|
}
|
|
if let Some(worker) = self.downcast::<WorkerGlobalScope>() {
|
|
return worker.get_url().clone();
|
|
}
|
|
if let Some(worker) = self.downcast::<WorkletGlobalScope>() {
|
|
// TODO: is this the right URL to return?
|
|
return worker.base_url();
|
|
}
|
|
unreachable!();
|
|
}
|
|
|
|
/// Extract a `Window`, panic if the global object is not a `Window`.
|
|
pub fn as_window(&self) -> &Window {
|
|
self.downcast::<Window>().expect("expected a Window scope")
|
|
}
|
|
|
|
/// https://html.spec.whatwg.org/multipage/#report-the-error
|
|
pub fn report_an_error(&self, error_info: ErrorInfo, value: HandleValue) {
|
|
// Step 1.
|
|
if self.in_error_reporting_mode.get() {
|
|
return;
|
|
}
|
|
|
|
// Step 2.
|
|
self.in_error_reporting_mode.set(true);
|
|
|
|
// Steps 3-12.
|
|
// FIXME(#13195): muted errors.
|
|
let event = ErrorEvent::new(self,
|
|
atom!("error"),
|
|
EventBubbles::DoesNotBubble,
|
|
EventCancelable::Cancelable,
|
|
error_info.message.as_str().into(),
|
|
error_info.filename.as_str().into(),
|
|
error_info.lineno,
|
|
error_info.column,
|
|
value);
|
|
|
|
// Step 13.
|
|
let event_status = event.upcast::<Event>().fire(self.upcast::<EventTarget>());
|
|
|
|
// Step 15
|
|
if event_status == EventStatus::NotCanceled {
|
|
if let Some(dedicated) = self.downcast::<DedicatedWorkerGlobalScope>() {
|
|
dedicated.forward_error_to_worker_object(error_info);
|
|
}
|
|
}
|
|
|
|
// Step 14
|
|
self.in_error_reporting_mode.set(false);
|
|
}
|
|
|
|
/// Get the `&ResourceThreads` for this global scope.
|
|
pub fn resource_threads(&self) -> &ResourceThreads {
|
|
&self.resource_threads
|
|
}
|
|
|
|
/// Get the `CoreResourceThread` for this global scope.
|
|
pub fn core_resource_thread(&self) -> CoreResourceThread {
|
|
self.resource_threads().sender()
|
|
}
|
|
|
|
/// `ScriptChan` to send messages to the event loop of this global scope.
|
|
pub fn script_chan(&self) -> Box<ScriptChan + Send> {
|
|
if let Some(window) = self.downcast::<Window>() {
|
|
return MainThreadScriptChan(window.main_thread_script_chan().clone()).clone();
|
|
}
|
|
if let Some(worker) = self.downcast::<WorkerGlobalScope>() {
|
|
return worker.script_chan();
|
|
}
|
|
unreachable!();
|
|
}
|
|
|
|
/// `ScriptChan` to send messages to the networking task source of
|
|
/// this of this global scope.
|
|
pub fn networking_task_source(&self) -> NetworkingTaskSource {
|
|
if let Some(window) = self.downcast::<Window>() {
|
|
return window.networking_task_source();
|
|
}
|
|
if let Some(worker) = self.downcast::<WorkerGlobalScope>() {
|
|
return worker.networking_task_source();
|
|
}
|
|
unreachable!();
|
|
}
|
|
|
|
/// Evaluate JS code on this global scope.
|
|
pub fn evaluate_js_on_global_with_result(
|
|
&self, code: &str, rval: MutableHandleValue) -> bool {
|
|
self.evaluate_script_on_global_with_result(code, "", rval, 1)
|
|
}
|
|
|
|
/// Evaluate a JS script on this global scope.
|
|
#[allow(unsafe_code)]
|
|
pub fn evaluate_script_on_global_with_result(
|
|
&self, code: &str, filename: &str, rval: MutableHandleValue, line_number: u32) -> bool {
|
|
let metadata = time::TimerMetadata {
|
|
url: if filename.is_empty() {
|
|
self.get_url().as_str().into()
|
|
} else {
|
|
filename.into()
|
|
},
|
|
iframe: time::TimerMetadataFrameType::RootWindow,
|
|
incremental: time::TimerMetadataReflowType::FirstReflow,
|
|
};
|
|
time::profile(
|
|
time::ProfilerCategory::ScriptEvaluate,
|
|
Some(metadata),
|
|
self.time_profiler_chan().clone(),
|
|
|| {
|
|
let cx = self.get_cx();
|
|
let globalhandle = self.reflector().get_jsobject();
|
|
let code: Vec<u16> = code.encode_utf16().collect();
|
|
let filename = CString::new(filename).unwrap();
|
|
|
|
let _ac = JSAutoCompartment::new(cx, globalhandle.get());
|
|
let _aes = AutoEntryScript::new(self);
|
|
let options = CompileOptionsWrapper::new(cx, filename.as_ptr(), line_number);
|
|
|
|
debug!("evaluating JS string");
|
|
let result = unsafe {
|
|
Evaluate2(cx, options.ptr, code.as_ptr(),
|
|
code.len() as libc::size_t,
|
|
rval)
|
|
};
|
|
|
|
if !result {
|
|
debug!("error evaluating JS string");
|
|
unsafe { report_pending_exception(cx, true) };
|
|
}
|
|
|
|
maybe_resume_unwind();
|
|
result
|
|
}
|
|
)
|
|
}
|
|
|
|
pub fn schedule_callback(
|
|
&self, callback: OneshotTimerCallback, duration: MsDuration)
|
|
-> OneshotTimerHandle {
|
|
self.timers.schedule_callback(callback, duration, self.timer_source())
|
|
}
|
|
|
|
pub fn unschedule_callback(&self, handle: OneshotTimerHandle) {
|
|
self.timers.unschedule_callback(handle);
|
|
}
|
|
|
|
pub fn set_timeout_or_interval(
|
|
&self,
|
|
callback: TimerCallback,
|
|
arguments: Vec<HandleValue>,
|
|
timeout: i32,
|
|
is_interval: IsInterval)
|
|
-> i32 {
|
|
self.timers.set_timeout_or_interval(
|
|
self, callback, arguments, timeout, is_interval, self.timer_source())
|
|
}
|
|
|
|
pub fn clear_timeout_or_interval(&self, handle: i32) {
|
|
self.timers.clear_timeout_or_interval(self, handle)
|
|
}
|
|
|
|
pub fn fire_timer(&self, handle: TimerEventId) {
|
|
self.timers.fire_timer(handle, self)
|
|
}
|
|
|
|
pub fn resume(&self) {
|
|
self.timers.resume()
|
|
}
|
|
|
|
pub fn suspend(&self) {
|
|
self.timers.suspend()
|
|
}
|
|
|
|
pub fn slow_down_timers(&self) {
|
|
self.timers.slow_down()
|
|
}
|
|
|
|
pub fn speed_up_timers(&self) {
|
|
self.timers.speed_up()
|
|
}
|
|
|
|
fn timer_source(&self) -> TimerSource {
|
|
if self.is::<Window>() {
|
|
return TimerSource::FromWindow(self.pipeline_id());
|
|
}
|
|
if self.is::<WorkerGlobalScope>() {
|
|
return TimerSource::FromWorker;
|
|
}
|
|
unreachable!();
|
|
}
|
|
|
|
/// Returns a wrapper for runnables to ensure they are cancelled if
|
|
/// the global scope is being destroyed.
|
|
pub fn get_runnable_wrapper(&self) -> RunnableWrapper {
|
|
if let Some(window) = self.downcast::<Window>() {
|
|
return window.get_runnable_wrapper();
|
|
}
|
|
if let Some(worker) = self.downcast::<WorkerGlobalScope>() {
|
|
return worker.get_runnable_wrapper();
|
|
}
|
|
unreachable!();
|
|
}
|
|
|
|
/// Perform a microtask checkpoint.
|
|
pub fn perform_a_microtask_checkpoint(&self) {
|
|
if self.is::<Window>() {
|
|
return ScriptThread::invoke_perform_a_microtask_checkpoint();
|
|
}
|
|
if let Some(worker) = self.downcast::<WorkerGlobalScope>() {
|
|
return worker.perform_a_microtask_checkpoint();
|
|
}
|
|
if let Some(worker) = self.downcast::<WorkletGlobalScope>() {
|
|
return worker.perform_a_microtask_checkpoint();
|
|
}
|
|
unreachable!();
|
|
}
|
|
|
|
/// Enqueue a microtask for subsequent execution.
|
|
pub fn enqueue_microtask(&self, job: Microtask) {
|
|
if self.is::<Window>() {
|
|
return ScriptThread::enqueue_microtask(job);
|
|
}
|
|
if let Some(worker) = self.downcast::<WorkerGlobalScope>() {
|
|
return worker.enqueue_microtask(job);
|
|
}
|
|
if let Some(worker) = self.downcast::<WorkletGlobalScope>() {
|
|
return worker.enqueue_microtask(job);
|
|
}
|
|
unreachable!();
|
|
}
|
|
|
|
/// Create a new sender/receiver pair that can be used to implement an on-demand
|
|
/// event loop. Used for implementing web APIs that require blocking semantics
|
|
/// without resorting to nested event loops.
|
|
pub fn new_script_pair(&self) -> (Box<ScriptChan + Send>, Box<ScriptPort + Send>) {
|
|
if let Some(window) = self.downcast::<Window>() {
|
|
return window.new_script_pair();
|
|
}
|
|
if let Some(worker) = self.downcast::<WorkerGlobalScope>() {
|
|
return worker.new_script_pair();
|
|
}
|
|
unreachable!();
|
|
}
|
|
|
|
/// Process a single event as if it were the next event
|
|
/// in the thread queue for this global scope.
|
|
pub fn process_event(&self, msg: CommonScriptMsg) {
|
|
if self.is::<Window>() {
|
|
return ScriptThread::process_event(msg);
|
|
}
|
|
if let Some(worker) = self.downcast::<WorkerGlobalScope>() {
|
|
return worker.process_event(msg);
|
|
}
|
|
unreachable!();
|
|
}
|
|
|
|
/// Channel to send messages to the file reading task source of
|
|
/// this of this global scope.
|
|
pub fn file_reading_task_source(&self) -> FileReadingTaskSource {
|
|
if let Some(window) = self.downcast::<Window>() {
|
|
return window.file_reading_task_source();
|
|
}
|
|
if let Some(worker) = self.downcast::<WorkerGlobalScope>() {
|
|
return worker.file_reading_task_source();
|
|
}
|
|
unreachable!();
|
|
}
|
|
|
|
/// Returns the ["current"] global object.
|
|
///
|
|
/// ["current"]: https://html.spec.whatwg.org/multipage/#current
|
|
#[allow(unsafe_code)]
|
|
pub fn current() -> Root<Self> {
|
|
unsafe {
|
|
let cx = Runtime::get();
|
|
assert!(!cx.is_null());
|
|
let global = CurrentGlobalOrNull(cx);
|
|
global_scope_from_global(global)
|
|
}
|
|
}
|
|
|
|
/// Returns the ["entry"] global object.
|
|
///
|
|
/// ["entry"]: https://html.spec.whatwg.org/multipage/#entry
|
|
pub fn entry() -> Root<Self> {
|
|
entry_global()
|
|
}
|
|
|
|
/// Returns the ["incumbent"] global object.
|
|
///
|
|
/// ["incumbent"]: https://html.spec.whatwg.org/multipage/#incumbent
|
|
pub fn incumbent() -> Option<Root<Self>> {
|
|
incumbent_global()
|
|
}
|
|
}
|
|
|
|
fn timestamp_in_ms(time: Timespec) -> u64 {
|
|
(time.sec * 1000 + (time.nsec / 1000000) as i64) as u64
|
|
}
|
|
|
|
/// Returns the Rust global scope from a JS global object.
|
|
#[allow(unsafe_code)]
|
|
unsafe fn global_scope_from_global(global: *mut JSObject) -> Root<GlobalScope> {
|
|
assert!(!global.is_null());
|
|
let clasp = get_object_class(global);
|
|
assert!(((*clasp).flags & (JSCLASS_IS_DOMJSCLASS | JSCLASS_IS_GLOBAL)) != 0);
|
|
root_from_object(global).unwrap()
|
|
}
|