script/dom/
globalscope.rs

1/* This Source Code Form is subject to the terms of the Mozilla Public
2 * License, v. 2.0. If a copy of the MPL was not distributed with this
3 * file, You can obtain one at https://mozilla.org/MPL/2.0/. */
4
5use std::borrow::Cow;
6use std::cell::{Cell, OnceCell, Ref, RefCell};
7use std::collections::hash_map::Entry;
8use std::collections::{HashMap, HashSet, VecDeque};
9use std::ffi::CStr;
10use std::mem;
11use std::ops::{Deref, Index};
12use std::ptr::NonNull;
13use std::rc::Rc;
14use std::sync::Arc;
15use std::sync::atomic::{AtomicBool, Ordering};
16use std::thread::JoinHandle;
17use std::time::{Duration, Instant};
18
19use content_security_policy::CspList;
20use crossbeam_channel::Sender;
21use devtools_traits::{PageError, ScriptToDevtoolsControlMsg, get_time_stamp};
22use dom_struct::dom_struct;
23use embedder_traits::{
24    ConsoleLogLevel, EmbedderMsg, JavaScriptEvaluationError, ScriptToEmbedderChan,
25};
26use fonts::FontContext;
27use indexmap::IndexSet;
28use ipc_channel::ipc::{self};
29use ipc_channel::router::ROUTER;
30use js::jsapi::{
31    CurrentGlobalOrNull, GetNonCCWObjectGlobal, HandleObject, Heap, JSContext, JSObject, JSScript,
32};
33use js::jsval::UndefinedValue;
34use js::panic::maybe_resume_unwind;
35use js::realm::CurrentRealm;
36use js::rust::{
37    CustomAutoRooter, CustomAutoRooterGuard, HandleValue, MutableHandleValue, ParentRuntime,
38    Runtime, get_object_class,
39};
40use js::{JSCLASS_IS_DOMJSCLASS, JSCLASS_IS_GLOBAL};
41use net_traits::blob_url_store::BlobBuf;
42use net_traits::filemanager_thread::{
43    FileManagerResult, FileManagerThreadMsg, ReadFileProgress, RelativePos,
44};
45use net_traits::image_cache::ImageCache;
46use net_traits::policy_container::{PolicyContainer, RequestPolicyContainer};
47use net_traits::request::{
48    InsecureRequestsPolicy, Origin as RequestOrigin, Referrer, RequestBuilder, RequestClient,
49};
50use net_traits::response::HttpsState;
51use net_traits::{
52    CoreResourceMsg, CoreResourceThread, ReferrerPolicy, ResourceThreads, fetch_async,
53};
54use profile_traits::{
55    generic_channel as profile_generic_channel, ipc as profile_ipc, mem as profile_mem,
56    time as profile_time,
57};
58use rustc_hash::{FxBuildHasher, FxHashMap};
59use script_bindings::interfaces::GlobalScopeHelpers;
60use script_bindings::settings_stack::run_a_script;
61use servo_base::generic_channel;
62use servo_base::generic_channel::{GenericCallback, GenericSend};
63use servo_base::id::{
64    BlobId, BroadcastChannelRouterId, MessagePortId, MessagePortRouterId, PipelineId,
65    ServiceWorkerId, ServiceWorkerRegistrationId, WebViewId,
66};
67use servo_constellation_traits::{
68    BlobData, BlobImpl, BroadcastChannelMsg, ConstellationInterest, FileBlob, MessagePortImpl,
69    MessagePortMsg, PortMessageTask, ScriptToConstellationChan, ScriptToConstellationMessage,
70};
71use servo_url::{ImmutableOrigin, MutableOrigin, ServoUrl};
72use storage_traits::StorageThreads;
73use strum::VariantArray;
74use timers::{TimerEventRequest, TimerId};
75use uuid::Uuid;
76#[cfg(feature = "webgpu")]
77use webgpu_traits::{DeviceLostReason, WebGPUDevice};
78
79use super::bindings::codegen::Bindings::MessagePortBinding::StructuredSerializeOptions;
80#[cfg(feature = "webgpu")]
81use super::bindings::codegen::Bindings::WebGPUBinding::GPUDeviceLostReason;
82use super::bindings::trace::{HashMapTracedValues, RootedTraceableBox};
83use super::serviceworkerglobalscope::ServiceWorkerGlobalScope;
84use super::transformstream::CrossRealmTransform;
85use crate::DomTypeHolder;
86use crate::dom::bindings::cell::{DomRefCell, RefMut};
87use crate::dom::bindings::codegen::Bindings::BroadcastChannelBinding::BroadcastChannelMethods;
88use crate::dom::bindings::codegen::Bindings::EventSourceBinding::EventSource_Binding::EventSourceMethods;
89use crate::dom::bindings::codegen::Bindings::FunctionBinding::Function;
90use crate::dom::bindings::codegen::Bindings::NotificationBinding::NotificationPermissionCallback;
91use crate::dom::bindings::codegen::Bindings::PermissionStatusBinding::{
92    PermissionName, PermissionState,
93};
94use crate::dom::bindings::codegen::Bindings::WindowBinding::WindowMethods;
95use crate::dom::bindings::codegen::Bindings::WorkerGlobalScopeBinding::WorkerGlobalScopeMethods;
96use crate::dom::bindings::conversions::{root_from_object, root_from_object_static};
97#[cfg(feature = "js_backtrace")]
98use crate::dom::bindings::error::LAST_EXCEPTION_BACKTRACE;
99use crate::dom::bindings::error::{
100    Error, ErrorInfo, Fallible, report_pending_exception, take_and_report_pending_exception_for_api,
101};
102use crate::dom::bindings::frozenarray::CachedFrozenArray;
103use crate::dom::bindings::inheritance::Castable;
104use crate::dom::bindings::refcounted::{Trusted, TrustedPromise};
105use crate::dom::bindings::reflector::{DomGlobal, DomObject};
106use crate::dom::bindings::root::{Dom, DomRoot, MutNullableDom};
107use crate::dom::bindings::settings_stack::{entry_global, incumbent_global};
108use crate::dom::bindings::str::DOMString;
109use crate::dom::bindings::structuredclone;
110use crate::dom::bindings::trace::CustomTraceable;
111use crate::dom::bindings::weakref::{DOMTracker, WeakRef};
112use crate::dom::blob::Blob;
113use crate::dom::broadcastchannel::BroadcastChannel;
114use crate::dom::dedicatedworkerglobalscope::{
115    DedicatedWorkerControlMsg, DedicatedWorkerGlobalScope,
116};
117use crate::dom::errorevent::ErrorEvent;
118use crate::dom::event::{Event, EventBubbles, EventCancelable};
119use crate::dom::eventsource::EventSource;
120use crate::dom::eventtarget::EventTarget;
121use crate::dom::file::File;
122use crate::dom::global_scope_script_execution::{ErrorReporting, compile_script, evaluate_script};
123use crate::dom::idbfactory::IDBFactory;
124use crate::dom::messageport::MessagePort;
125use crate::dom::paintworkletglobalscope::PaintWorkletGlobalScope;
126use crate::dom::performance::performance::Performance;
127use crate::dom::performance::performanceentry::EntryType;
128use crate::dom::promise::Promise;
129use crate::dom::readablestream::{CrossRealmTransformReadable, ReadableStream};
130use crate::dom::serviceworker::ServiceWorker;
131use crate::dom::serviceworkerregistration::ServiceWorkerRegistration;
132use crate::dom::sharedworkerglobalscope::SharedWorkerGlobalScope;
133use crate::dom::stream::underlyingsourcecontainer::UnderlyingSourceType;
134use crate::dom::stream::writablestream::CrossRealmTransformWritable;
135use crate::dom::types::{AbortSignal, DebuggerGlobalScope, MessageEvent};
136#[cfg(feature = "webgpu")]
137use crate::dom::webgpu::gpudevice::GPUDevice;
138#[cfg(feature = "webgpu")]
139use crate::dom::webgpu::identityhub::IdentityHub;
140use crate::dom::window::Window;
141use crate::dom::workerglobalscope::WorkerGlobalScope;
142use crate::dom::workletglobalscope::WorkletGlobalScope;
143use crate::fetch::{DeferredFetchRecordId, FetchGroup, QueuedDeferredFetchRecord};
144use crate::messaging::{CommonScriptMsg, ScriptEventLoopReceiver, ScriptEventLoopSender};
145use crate::microtask::Microtask;
146use crate::network_listener::{FetchResponseListener, NetworkListener};
147use crate::realms::{InRealm, enter_auto_realm};
148use crate::script_module::{
149    ImportMap, ModuleRequest, ModuleStatus, ResolvedModule, ScriptFetchOptions,
150};
151use crate::script_runtime::{CanGc, JSContext as SafeJSContext, ThreadSafeJSContext};
152use crate::script_thread::{ScriptThread, with_script_thread};
153use crate::task_manager::TaskManager;
154use crate::task_source::SendableTaskSource;
155use crate::timers::{
156    IsInterval, OneshotTimerCallback, OneshotTimerHandle, OneshotTimers, TimerCallback,
157    TimerEventId, TimerSource,
158};
159use crate::unminify::unminified_path;
160
161#[derive(JSTraceable, MallocSizeOf)]
162pub(crate) struct AutoCloseWorker {
163    /// <https://html.spec.whatwg.org/multipage/#dom-workerglobalscope-closing>
164    #[conditional_malloc_size_of]
165    closing: Arc<AtomicBool>,
166    /// A handle to join on the worker thread.
167    #[ignore_malloc_size_of = "JoinHandle"]
168    join_handle: Option<JoinHandle<()>>,
169    /// A sender of control messages,
170    /// currently only used to signal shutdown.
171    #[no_trace]
172    control_sender: Sender<DedicatedWorkerControlMsg>,
173    /// The context to request an interrupt on the worker thread.
174    #[ignore_malloc_size_of = "mozjs"]
175    #[no_trace]
176    context: ThreadSafeJSContext,
177}
178
179impl Drop for AutoCloseWorker {
180    /// <https://html.spec.whatwg.org/multipage/#terminate-a-worker>
181    fn drop(&mut self) {
182        // Step 1.
183        self.closing.store(true, Ordering::SeqCst);
184
185        if self
186            .control_sender
187            .send(DedicatedWorkerControlMsg::Exit)
188            .is_err()
189        {
190            warn!("Couldn't send an exit message to a dedicated worker.");
191        }
192
193        self.context.request_interrupt_callback();
194
195        // TODO: step 2 and 3.
196        // Step 4 is unnecessary since we don't use actual ports for dedicated workers.
197        if self
198            .join_handle
199            .take()
200            .expect("No handle to join on worker.")
201            .join()
202            .is_err()
203        {
204            warn!("Failed to join on dedicated worker thread.");
205        }
206    }
207}
208
209#[dom_struct]
210pub(crate) struct GlobalScope {
211    eventtarget: EventTarget,
212
213    /// A [`TaskManager`] for this [`GlobalScope`].
214    task_manager: OnceCell<TaskManager>,
215
216    /// The message-port router id for this global, if it is managing ports.
217    message_port_state: DomRefCell<MessagePortState>,
218
219    /// The broadcast channels state this global, if it is managing any.
220    broadcast_channel_state: DomRefCell<BroadcastChannelState>,
221
222    /// Tracks the number of active listeners per constellation interest category.
223    /// When the count transitions from 0 to 1, a RegisterInterest message is sent.
224    /// When it transitions from 1 to 0, an UnregisterInterest message is sent.
225    #[no_trace]
226    constellation_interest_counts: RefCell<HashMap<ConstellationInterest, usize>>,
227
228    /// The blobs managed by this global, if any.
229    blob_state: DomRefCell<HashMapTracedValues<BlobId, BlobInfo, FxBuildHasher>>,
230
231    /// <https://w3c.github.io/ServiceWorker/#environment-settings-object-service-worker-registration-object-map>
232    registration_map: DomRefCell<
233        HashMapTracedValues<
234            ServiceWorkerRegistrationId,
235            Dom<ServiceWorkerRegistration>,
236            FxBuildHasher,
237        >,
238    >,
239
240    /// <https://w3c.github.io/IndexedDB/#factory-interface>
241    indexeddb: MutNullableDom<IDBFactory>,
242
243    /// <https://w3c.github.io/ServiceWorker/#environment-settings-object-service-worker-object-map>
244    worker_map: DomRefCell<HashMapTracedValues<ServiceWorkerId, Dom<ServiceWorker>, FxBuildHasher>>,
245
246    /// Pipeline id associated with this global.
247    #[no_trace]
248    pipeline_id: PipelineId,
249
250    /// Timers (milliseconds) used by the Console API.
251    console_timers: DomRefCell<HashMap<DOMString, Instant>>,
252
253    /// module map is used when importing JavaScript modules
254    /// <https://html.spec.whatwg.org/multipage/#concept-settings-object-module-map>
255    #[ignore_malloc_size_of = "mozjs"]
256    module_map: DomRefCell<HashMapTracedValues<ModuleRequest, ModuleStatus>>,
257
258    /// For providing instructions to an optional devtools server.
259    #[no_trace]
260    devtools_chan: Option<GenericCallback<ScriptToDevtoolsControlMsg>>,
261
262    /// For sending messages to the memory profiler.
263    #[no_trace]
264    mem_profiler_chan: profile_mem::ProfilerChan,
265
266    /// For sending messages to the time profiler.
267    #[no_trace]
268    time_profiler_chan: profile_time::ProfilerChan,
269
270    /// A handle for communicating messages to the constellation thread.
271    #[no_trace]
272    script_to_constellation_chan: ScriptToConstellationChan,
273
274    /// A handle for communicating messages to the Embedder.
275    #[no_trace]
276    script_to_embedder_chan: ScriptToEmbedderChan,
277
278    /// <https://html.spec.whatwg.org/multipage/#in-error-reporting-mode>
279    in_error_reporting_mode: Cell<bool>,
280
281    /// Associated resource threads for use by DOM objects like XMLHttpRequest,
282    /// including resource_thread and filemanager_thread
283    #[no_trace]
284    resource_threads: ResourceThreads,
285
286    /// Associated resource threads for use by DOM objects like XMLHttpRequest,
287    /// including indexeddb thread and storage_thread
288    #[no_trace]
289    storage_threads: StorageThreads,
290
291    /// The mechanism by which time-outs and intervals are scheduled.
292    /// <https://html.spec.whatwg.org/multipage/#timers>
293    timers: OnceCell<OneshotTimers>,
294
295    /// The origin of the globalscope
296    #[no_trace]
297    origin: MutableOrigin,
298
299    /// <https://html.spec.whatwg.org/multipage/#concept-environment-creation-url>
300    #[no_trace]
301    creation_url: DomRefCell<ServoUrl>,
302
303    /// <https://html.spec.whatwg.org/multipage/#concept-environment-top-level-creation-url>
304    #[no_trace]
305    top_level_creation_url: Option<ServoUrl>,
306
307    /// A map for storing the previous permission state read results.
308    permission_state_invocation_results: DomRefCell<HashMap<PermissionName, PermissionState>>,
309
310    /// Vector storing closing references of all workers
311    list_auto_close_worker: DomRefCell<Vec<AutoCloseWorker>>,
312
313    /// Vector storing references of all eventsources.
314    event_source_tracker: DOMTracker<EventSource>,
315
316    /// Dependent AbortSignals that must be kept alive per
317    /// <https://dom.spec.whatwg.org/#abort-signal-garbage-collection?
318    abort_signal_dependents: DomRefCell<IndexSet<Dom<AbortSignal>>>,
319
320    /// Storage for watching rejected promises waiting for some client to
321    /// consume their rejection.
322    /// Promises in this list have been rejected in the last turn of the
323    /// event loop without the rejection being handled.
324    /// Note that this can contain nullptrs in place of promises removed because
325    /// they're consumed before it'd be reported.
326    ///
327    /// <https://html.spec.whatwg.org/multipage/#about-to-be-notified-rejected-promises-list>
328    #[ignore_malloc_size_of = "mozjs"]
329    // `Heap` values must stay boxed, as they need semantics like `Pin`
330    // (that is, they cannot be moved).
331    #[allow(clippy::vec_box)]
332    uncaught_rejections: DomRefCell<Vec<Box<Heap<*mut JSObject>>>>,
333
334    /// Promises in this list have previously been reported as rejected
335    /// (because they were in the above list), but the rejection was handled
336    /// in the last turn of the event loop.
337    ///
338    /// <https://html.spec.whatwg.org/multipage/#outstanding-rejected-promises-weak-set>
339    #[ignore_malloc_size_of = "mozjs"]
340    // `Heap` values must stay boxed, as they need semantics like `Pin`
341    // (that is, they cannot be moved).
342    #[allow(clippy::vec_box)]
343    consumed_rejections: DomRefCell<Vec<Box<Heap<*mut JSObject>>>>,
344
345    /// Identity Manager for WebGPU resources
346    #[ignore_malloc_size_of = "defined in wgpu"]
347    #[no_trace]
348    #[cfg(feature = "webgpu")]
349    gpu_id_hub: Arc<IdentityHub>,
350
351    /// WebGPU devices
352    #[cfg(feature = "webgpu")]
353    gpu_devices: DomRefCell<HashMapTracedValues<WebGPUDevice, WeakRef<GPUDevice>, FxBuildHasher>>,
354
355    // https://w3c.github.io/performance-timeline/#supportedentrytypes-attribute
356    #[ignore_malloc_size_of = "mozjs"]
357    frozen_supported_performance_entry_types: CachedFrozenArray,
358
359    /// currect https state (from previous request)
360    #[no_trace]
361    https_state: Cell<HttpsState>,
362
363    /// The stack of active group labels for the Console APIs.
364    console_group_stack: DomRefCell<Vec<DOMString>>,
365
366    /// The count map for the Console APIs.
367    ///
368    /// <https://console.spec.whatwg.org/#count>
369    console_count_map: DomRefCell<HashMap<DOMString, usize>>,
370
371    /// Is considered in a secure context
372    inherited_secure_context: Option<bool>,
373
374    /// Directory to store unminified scripts for this window if unminify-js
375    /// opt is enabled.
376    unminified_js_dir: Option<String>,
377
378    /// The byte length queuing strategy size function that will be initialized once
379    /// `size` getter of `ByteLengthQueuingStrategy` is called.
380    ///
381    /// <https://streams.spec.whatwg.org/#byte-length-queuing-strategy-size-function>
382    #[ignore_malloc_size_of = "callbacks are hard"]
383    byte_length_queuing_strategy_size_function: OnceCell<Rc<Function>>,
384
385    /// The count queuing strategy size function that will be initialized once
386    /// `size` getter of `CountQueuingStrategy` is called.
387    ///
388    /// <https://streams.spec.whatwg.org/#count-queuing-strategy-size-function>
389    #[ignore_malloc_size_of = "callbacks are hard"]
390    count_queuing_strategy_size_function: OnceCell<Rc<Function>>,
391
392    #[ignore_malloc_size_of = "callbacks are hard"]
393    notification_permission_request_callback_map:
394        DomRefCell<HashMap<String, Rc<NotificationPermissionCallback>>>,
395
396    /// An import map allows control over module specifier resolution.
397    /// For now, only Window global objects have their import map modified from the initial empty one.
398    ///
399    /// <https://html.spec.whatwg.org/multipage/#import-maps>
400    import_map: DomRefCell<ImportMap>,
401
402    /// <https://html.spec.whatwg.org/multipage/#resolved-module-set>
403    resolved_module_set: DomRefCell<HashSet<ResolvedModule>>,
404
405    /// The [`FontContext`] for this [`GlobalScope`] if it has one. This is used for
406    /// canvas and layout, so if this [`GlobalScope`] doesn't need to use either, this
407    /// might be `None`.
408    #[conditional_malloc_size_of]
409    #[no_trace]
410    font_context: Option<Arc<FontContext>>,
411
412    /// <https://fetch.spec.whatwg.org/#environment-settings-object-fetch-group>
413    #[no_trace]
414    fetch_group: RefCell<FetchGroup>,
415}
416
417/// A wrapper for glue-code between the ipc router and the event-loop.
418struct MessageListener {
419    task_source: SendableTaskSource,
420    context: Trusted<GlobalScope>,
421}
422
423/// A wrapper for broadcasts coming in over IPC, and the event-loop.
424struct BroadcastListener {
425    task_source: SendableTaskSource,
426    context: Trusted<GlobalScope>,
427}
428
429type FileListenerCallback =
430    Box<dyn Fn(&mut js::context::JSContext, Rc<Promise>, Fallible<Vec<u8>>) + Send>;
431
432/// A wrapper for the handling of file data received by the ipc router
433struct FileListener {
434    /// State should progress as either of:
435    /// - Some(Empty) => Some(Receiving) => None
436    /// - Some(Empty) => None
437    state: Option<FileListenerState>,
438    task_source: SendableTaskSource,
439}
440
441enum FileListenerTarget {
442    Promise(TrustedPromise, FileListenerCallback),
443    Stream(Trusted<ReadableStream>),
444}
445
446enum FileListenerState {
447    Empty(FileListenerTarget),
448    Receiving(Vec<u8>, FileListenerTarget),
449}
450
451#[derive(JSTraceable, MallocSizeOf)]
452/// A holder of a weak reference for a DOM blob or file.
453pub(crate) enum BlobTracker {
454    /// A weak ref to a DOM file.
455    File(WeakRef<File>),
456    /// A weak ref to a DOM blob.
457    Blob(WeakRef<Blob>),
458}
459
460#[derive(JSTraceable, MallocSizeOf)]
461/// The info pertaining to a blob managed by this global.
462pub(crate) struct BlobInfo {
463    /// The weak ref to the corresponding DOM object.
464    tracker: BlobTracker,
465    /// The data and logic backing the DOM object.
466    #[no_trace]
467    blob_impl: BlobImpl,
468    /// Whether this blob has an outstanding URL,
469    /// <https://w3c.github.io/FileAPI/#url>.
470    has_url: bool,
471}
472
473/// The result of looking-up the data for a Blob,
474/// containing either the in-memory bytes,
475/// or the file-id.
476enum BlobResult {
477    Bytes(Vec<u8>),
478    File(Uuid, usize),
479}
480
481/// Data representing a message-port managed by this global.
482#[derive(JSTraceable, MallocSizeOf)]
483#[cfg_attr(crown, crown::unrooted_must_root_lint::must_root)]
484pub(crate) struct ManagedMessagePort {
485    /// The DOM port.
486    dom_port: Dom<MessagePort>,
487    /// The logic and data backing the DOM port.
488    /// The option is needed to take out the port-impl
489    /// as part of its transferring steps,
490    /// without having to worry about rooting the dom-port.
491    #[no_trace]
492    port_impl: Option<MessagePortImpl>,
493    /// We keep ports pending when they are first transfer-received,
494    /// and only add them, and ask the constellation to complete the transfer,
495    /// in a subsequent task if the port hasn't been re-transfered.
496    pending: bool,
497    /// Whether the port has been closed by script in this global,
498    /// so it can be removed.
499    explicitly_closed: bool,
500    /// The handler for `message` or `messageerror` used in the cross realm transform,
501    /// if any was setup with this port.
502    cross_realm_transform: Option<CrossRealmTransform>,
503}
504
505/// State representing whether this global is currently managing broadcast channels.
506#[derive(JSTraceable, MallocSizeOf)]
507#[cfg_attr(crown, crown::unrooted_must_root_lint::must_root)]
508pub(crate) enum BroadcastChannelState {
509    /// The broadcast-channel router id for this global, and a queue of managed channels.
510    /// Step 9, "sort destinations"
511    /// of <https://html.spec.whatwg.org/multipage/#dom-broadcastchannel-postmessage>
512    /// requires keeping track of creation order, hence the queue.
513    Managed(
514        #[no_trace] BroadcastChannelRouterId,
515        /// The map of channel-name to queue of channels, in order of creation.
516        HashMap<DOMString, VecDeque<Dom<BroadcastChannel>>>,
517    ),
518    /// This global is not managing any broadcast channels at this time.
519    UnManaged,
520}
521
522/// State representing whether this global is currently managing messageports.
523#[derive(JSTraceable, MallocSizeOf)]
524#[cfg_attr(crown, crown::unrooted_must_root_lint::must_root)]
525pub(crate) enum MessagePortState {
526    /// The message-port router id for this global, and a map of managed ports.
527    Managed(
528        #[no_trace] MessagePortRouterId,
529        HashMapTracedValues<MessagePortId, ManagedMessagePort, FxBuildHasher>,
530    ),
531    /// This global is not managing any ports at this time.
532    UnManaged,
533}
534
535impl BroadcastListener {
536    /// Handle a broadcast coming in over IPC,
537    /// by queueing the appropriate task on the relevant event-loop.
538    fn handle(&self, event: BroadcastChannelMsg) {
539        let context = self.context.clone();
540
541        // Note: strictly speaking we should just queue the message event tasks,
542        // not queue a task that then queues more tasks.
543        // This however seems to be hard to avoid in the light of the IPC.
544        // One can imagine queueing tasks directly,
545        // for channels that would be in the same script-thread.
546        self.task_source
547            .queue(task!(broadcast_message_event: move || {
548                let global = context.root();
549                // Step 10 of https://html.spec.whatwg.org/multipage/#dom-broadcastchannel-postmessage,
550                // For each BroadcastChannel object destination in destinations, queue a task.
551                global.broadcast_message_event(event, None);
552            }));
553    }
554}
555
556impl MessageListener {
557    /// A new message came in, handle it via a task enqueued on the event-loop.
558    /// A task is required, since we are using a trusted globalscope,
559    /// and we can only access the root from the event-loop.
560    fn notify(&self, msg: MessagePortMsg) {
561        match msg {
562            MessagePortMsg::CompleteTransfer(ports) => {
563                let context = self.context.clone();
564                self.task_source.queue(
565                    task!(process_complete_transfer: move |cx| {
566                        let global = context.root();
567
568                        let router_id = match global.port_router_id() {
569                            Some(router_id) => router_id,
570                            None => {
571                                // If not managing any ports, no transfer can succeed,
572                                // so just send back everything.
573                                let _ = global.script_to_constellation_chan().send(
574                                    ScriptToConstellationMessage::MessagePortTransferResult(None, vec![], ports),
575                                );
576                                return;
577                            }
578                        };
579
580                        let mut succeeded = vec![];
581                        let mut failed = FxHashMap::default();
582
583                        for (id, info) in ports.into_iter() {
584                            if global.is_managing_port(&id) {
585                                succeeded.push(id);
586                                global.complete_port_transfer(
587                                    cx,
588                                    id,
589                                    info.port_message_queue,
590                                    info.disentangled,
591                                );
592                            } else {
593                                failed.insert(id, info);
594                            }
595                        }
596                        let _ = global.script_to_constellation_chan().send(
597                            ScriptToConstellationMessage::MessagePortTransferResult(Some(router_id), succeeded, failed),
598                        );
599                    })
600                );
601            },
602            MessagePortMsg::CompletePendingTransfer(port_id, info) => {
603                let context = self.context.clone();
604                self.task_source.queue(task!(complete_pending: move |cx| {
605                    let global = context.root();
606                    global.complete_port_transfer(cx, port_id, info.port_message_queue, info.disentangled);
607                }));
608            },
609            MessagePortMsg::CompleteDisentanglement(port_id) => {
610                let context = self.context.clone();
611                self.task_source
612                    .queue(task!(try_complete_disentanglement: move |cx| {
613                        let global = context.root();
614                        global.try_complete_disentanglement(cx, port_id);
615                    }));
616            },
617            MessagePortMsg::NewTask(port_id, task) => {
618                let context = self.context.clone();
619                self.task_source.queue(task!(process_new_task: move |cx| {
620                    let global = context.root();
621                    global.route_task_to_port(cx, port_id, task);
622                }));
623            },
624        }
625    }
626}
627
628/// Callback used to enqueue file chunks to streams as part of FileListener.
629fn stream_handle_incoming(
630    cx: &mut js::context::JSContext,
631    stream: &ReadableStream,
632    bytes: Fallible<Vec<u8>>,
633) {
634    match bytes {
635        Ok(b) => {
636            stream.enqueue_native(b, CanGc::from_cx(cx));
637        },
638        Err(e) => {
639            stream.error_native(e, CanGc::from_cx(cx));
640        },
641    }
642}
643
644/// Callback used to close streams as part of FileListener.
645fn stream_handle_eof(cx: &mut js::context::JSContext, stream: &ReadableStream) {
646    stream.controller_close_native(CanGc::from_cx(cx));
647}
648
649impl FileListener {
650    fn handle(&mut self, msg: FileManagerResult<ReadFileProgress>) {
651        match msg {
652            Ok(ReadFileProgress::Meta(blob_buf)) => match self.state.take() {
653                Some(FileListenerState::Empty(target)) => {
654                    let bytes = if let FileListenerTarget::Stream(ref trusted_stream) = target {
655                        let trusted = trusted_stream.clone();
656
657                        let task = task!(enqueue_stream_chunk: move |cx| {
658                            let stream = trusted.root();
659                            stream_handle_incoming(cx, &stream, Ok(blob_buf.bytes));
660                        });
661                        self.task_source.queue(task);
662
663                        Vec::with_capacity(0)
664                    } else {
665                        blob_buf.bytes
666                    };
667
668                    self.state = Some(FileListenerState::Receiving(bytes, target));
669                },
670                _ => panic!(
671                    "Unexpected FileListenerState when receiving ReadFileProgress::Meta msg."
672                ),
673            },
674            Ok(ReadFileProgress::Partial(mut bytes_in)) => match self.state.take() {
675                Some(FileListenerState::Receiving(mut bytes, target)) => {
676                    if let FileListenerTarget::Stream(ref trusted_stream) = target {
677                        let trusted = trusted_stream.clone();
678
679                        let task = task!(enqueue_stream_chunk: move |cx| {
680                            let stream = trusted.root();
681                            stream_handle_incoming(cx, &stream, Ok(bytes_in));
682                        });
683
684                        self.task_source.queue(task);
685                    } else {
686                        bytes.append(&mut bytes_in);
687                    };
688
689                    self.state = Some(FileListenerState::Receiving(bytes, target));
690                },
691                _ => panic!(
692                    "Unexpected FileListenerState when receiving ReadFileProgress::Partial msg."
693                ),
694            },
695            Ok(ReadFileProgress::EOF) => match self.state.take() {
696                Some(FileListenerState::Receiving(bytes, target)) => match target {
697                    FileListenerTarget::Promise(trusted_promise, callback) => {
698                        let task = task!(resolve_promise: move |cx| {
699                            let promise = trusted_promise.root();
700                            let mut realm = enter_auto_realm(cx, &*promise.global());
701                            callback(&mut realm, promise, Ok(bytes));
702                        });
703
704                        self.task_source.queue(task);
705                    },
706                    FileListenerTarget::Stream(trusted_stream) => {
707                        let task = task!(enqueue_stream_chunk: move |cx| {
708                            let stream = trusted_stream.root();
709                            stream_handle_eof(cx, &stream);
710                        });
711
712                        self.task_source.queue(task);
713                    },
714                },
715                _ => {
716                    panic!("Unexpected FileListenerState when receiving ReadFileProgress::EOF msg.")
717                },
718            },
719            Err(_) => match self.state.take() {
720                Some(FileListenerState::Receiving(_, target)) |
721                Some(FileListenerState::Empty(target)) => {
722                    let error = Err(Error::Network(None));
723
724                    match target {
725                        FileListenerTarget::Promise(trusted_promise, callback) => {
726                            self.task_source.queue(task!(reject_promise: move |cx| {
727                                let promise = trusted_promise.root();
728                                let mut realm = enter_auto_realm(cx, &*promise.global());
729                                callback(&mut realm, promise, error);
730                            }));
731                        },
732                        FileListenerTarget::Stream(trusted_stream) => {
733                            self.task_source.queue(task!(error_stream: move |cx| {
734                                let stream = trusted_stream.root();
735                                stream_handle_incoming(cx, &stream, error);
736                            }));
737                        },
738                    }
739                },
740                _ => panic!("Unexpected FileListenerState when receiving Err msg."),
741            },
742        }
743    }
744}
745
746impl GlobalScope {
747    /// A sender to the event loop of this global scope. This either sends to the Worker event loop
748    /// or the ScriptThread event loop in the case of a `Window`. This can be `None` for dedicated
749    /// workers that are not currently handling a message.
750    pub(crate) fn webview_id(&self) -> Option<WebViewId> {
751        if let Some(window) = self.downcast::<Window>() {
752            return Some(window.webview_id());
753        }
754        // If this is a worker only DedicatedWorkerGlobalScope will have a WebViewId, the other are
755        // ServiceWorkerGlobalScope, PaintWorklet, or DissimilarOriginWindow.
756        // TODO: This should only return None for ServiceWorkerGlobalScope.
757        self.downcast::<DedicatedWorkerGlobalScope>()
758            .map(DedicatedWorkerGlobalScope::webview_id)
759    }
760
761    #[allow(clippy::too_many_arguments)]
762    pub(crate) fn new_inherited(
763        pipeline_id: PipelineId,
764        devtools_chan: Option<GenericCallback<ScriptToDevtoolsControlMsg>>,
765        mem_profiler_chan: profile_mem::ProfilerChan,
766        time_profiler_chan: profile_time::ProfilerChan,
767        script_to_constellation_chan: ScriptToConstellationChan,
768        script_to_embedder_chan: ScriptToEmbedderChan,
769        resource_threads: ResourceThreads,
770        storage_threads: StorageThreads,
771        origin: MutableOrigin,
772        creation_url: ServoUrl,
773        top_level_creation_url: Option<ServoUrl>,
774        #[cfg(feature = "webgpu")] gpu_id_hub: Arc<IdentityHub>,
775        inherited_secure_context: Option<bool>,
776        unminify_js: bool,
777        font_context: Option<Arc<FontContext>>,
778        initial_https_state: HttpsState,
779    ) -> Self {
780        Self {
781            task_manager: Default::default(),
782            message_port_state: DomRefCell::new(MessagePortState::UnManaged),
783            broadcast_channel_state: DomRefCell::new(BroadcastChannelState::UnManaged),
784            constellation_interest_counts: RefCell::new(HashMap::new()),
785            blob_state: Default::default(),
786            eventtarget: EventTarget::new_inherited(),
787            registration_map: DomRefCell::new(HashMapTracedValues::new_fx()),
788            indexeddb: Default::default(),
789            worker_map: DomRefCell::new(HashMapTracedValues::new_fx()),
790            pipeline_id,
791
792            console_timers: DomRefCell::new(Default::default()),
793            module_map: DomRefCell::new(Default::default()),
794            devtools_chan,
795            mem_profiler_chan,
796            time_profiler_chan,
797            script_to_constellation_chan,
798            script_to_embedder_chan,
799            in_error_reporting_mode: Default::default(),
800            resource_threads,
801            storage_threads,
802            timers: OnceCell::default(),
803            origin,
804            creation_url: DomRefCell::new(creation_url),
805            top_level_creation_url,
806            permission_state_invocation_results: Default::default(),
807            list_auto_close_worker: Default::default(),
808            event_source_tracker: DOMTracker::new(),
809            abort_signal_dependents: Default::default(),
810            uncaught_rejections: Default::default(),
811            consumed_rejections: Default::default(),
812            #[cfg(feature = "webgpu")]
813            gpu_id_hub,
814            #[cfg(feature = "webgpu")]
815            gpu_devices: DomRefCell::new(HashMapTracedValues::new_fx()),
816            frozen_supported_performance_entry_types: CachedFrozenArray::new(),
817            https_state: Cell::new(initial_https_state),
818            console_group_stack: DomRefCell::new(Vec::new()),
819            console_count_map: Default::default(),
820            inherited_secure_context,
821            unminified_js_dir: unminify_js.then(|| unminified_path("unminified-js")),
822            byte_length_queuing_strategy_size_function: OnceCell::new(),
823            count_queuing_strategy_size_function: OnceCell::new(),
824            notification_permission_request_callback_map: Default::default(),
825            import_map: Default::default(),
826            resolved_module_set: Default::default(),
827            font_context,
828            fetch_group: Default::default(),
829        }
830    }
831
832    /// The message-port router Id of the global, if any
833    fn port_router_id(&self) -> Option<MessagePortRouterId> {
834        if let MessagePortState::Managed(id, _message_ports) = &*self.message_port_state.borrow() {
835            Some(*id)
836        } else {
837            None
838        }
839    }
840
841    /// Is this global managing a given port?
842    fn is_managing_port(&self, port_id: &MessagePortId) -> bool {
843        if let MessagePortState::Managed(_router_id, message_ports) =
844            &*self.message_port_state.borrow()
845        {
846            return message_ports.contains_key(port_id);
847        }
848        false
849    }
850
851    fn timers(&self) -> &OneshotTimers {
852        self.timers.get_or_init(|| OneshotTimers::new(self))
853    }
854
855    pub(crate) fn font_context(&self) -> Option<&Arc<FontContext>> {
856        self.font_context.as_ref()
857    }
858
859    /// <https://w3c.github.io/ServiceWorker/#get-the-service-worker-registration-object>
860    #[allow(clippy::too_many_arguments)]
861    pub(crate) fn get_serviceworker_registration(
862        &self,
863        script_url: &ServoUrl,
864        scope: &ServoUrl,
865        registration_id: ServiceWorkerRegistrationId,
866        installing_worker: Option<ServiceWorkerId>,
867        _waiting_worker: Option<ServiceWorkerId>,
868        _active_worker: Option<ServiceWorkerId>,
869        can_gc: CanGc,
870    ) -> DomRoot<ServiceWorkerRegistration> {
871        // Step 1
872        let mut registrations = self.registration_map.borrow_mut();
873
874        if let Some(registration) = registrations.get(&registration_id) {
875            // Step 3
876            return DomRoot::from_ref(&**registration);
877        }
878
879        // Step 2.1 -> 2.5
880        let new_registration =
881            ServiceWorkerRegistration::new(self, scope.clone(), registration_id, can_gc);
882
883        // Step 2.6
884        if let Some(worker_id) = installing_worker {
885            let worker = self.get_serviceworker(script_url, scope, worker_id, can_gc);
886            new_registration.set_installing(&worker);
887        }
888
889        // TODO: 2.7 (waiting worker)
890
891        // TODO: 2.8 (active worker)
892
893        // Step 2.9
894        registrations.insert(registration_id, Dom::from_ref(&*new_registration));
895
896        // Step 3
897        new_registration
898    }
899
900    /// <https://w3c.github.io/ServiceWorker/#get-the-service-worker-object>
901    pub(crate) fn get_serviceworker(
902        &self,
903        script_url: &ServoUrl,
904        scope: &ServoUrl,
905        worker_id: ServiceWorkerId,
906        can_gc: CanGc,
907    ) -> DomRoot<ServiceWorker> {
908        // Step 1
909        let mut workers = self.worker_map.borrow_mut();
910
911        if let Some(worker) = workers.get(&worker_id) {
912            // Step 3
913            DomRoot::from_ref(&**worker)
914        } else {
915            // Step 2.1
916            // TODO: step 2.2, worker state.
917            let new_worker =
918                ServiceWorker::new(self, script_url.clone(), scope.clone(), worker_id, can_gc);
919
920            // Step 2.3
921            workers.insert(worker_id, Dom::from_ref(&*new_worker));
922
923            // Step 3
924            new_worker
925        }
926    }
927
928    /// Complete the transfer of a message-port.
929    fn complete_port_transfer(
930        &self,
931        cx: &mut js::context::JSContext,
932        port_id: MessagePortId,
933        tasks: VecDeque<PortMessageTask>,
934        disentangled: bool,
935    ) {
936        let should_start = if let MessagePortState::Managed(_id, message_ports) =
937            &mut *self.message_port_state.borrow_mut()
938        {
939            match message_ports.get_mut(&port_id) {
940                None => {
941                    panic!("complete_port_transfer called for an unknown port.");
942                },
943                Some(managed_port) => {
944                    if managed_port.pending {
945                        panic!("CompleteTransfer msg received for a pending port.");
946                    }
947                    if let Some(port_impl) = managed_port.port_impl.as_mut() {
948                        port_impl.complete_transfer(tasks);
949                        if disentangled {
950                            port_impl.disentangle();
951                            managed_port.dom_port.disentangle();
952                        }
953                        port_impl.enabled()
954                    } else {
955                        panic!("managed-port has no port-impl.");
956                    }
957                },
958            }
959        } else {
960            panic!("complete_port_transfer called for an unknown port.");
961        };
962        if should_start {
963            self.start_message_port(cx, &port_id);
964        }
965    }
966
967    /// The closing of `otherPort`, if it is in a different global.
968    /// <https://html.spec.whatwg.org/multipage/#disentangle>
969    fn try_complete_disentanglement(
970        &self,
971        cx: &mut js::context::JSContext,
972        port_id: MessagePortId,
973    ) {
974        let dom_port = if let MessagePortState::Managed(_id, message_ports) =
975            &mut *self.message_port_state.borrow_mut()
976        {
977            if let Some(managed_port) = message_ports.get_mut(&port_id) {
978                if managed_port.pending {
979                    unreachable!("CompleteDisentanglement msg received for a pending port.");
980                }
981                let port_impl = managed_port
982                    .port_impl
983                    .as_mut()
984                    .expect("managed-port has no port-impl.");
985                port_impl.disentangle();
986                managed_port.dom_port.as_rooted()
987            } else {
988                // Note: this, and the other return below,
989                // can happen if the port has already been transferred out of this global,
990                // in which case the disentanglement will complete along with the transfer.
991                return;
992            }
993        } else {
994            return;
995        };
996
997        // Fire an event named close at otherPort.
998        dom_port.upcast().fire_event(cx, atom!("close"));
999
1000        let res = self.script_to_constellation_chan().send(
1001            ScriptToConstellationMessage::DisentanglePorts(port_id, None),
1002        );
1003        if res.is_err() {
1004            warn!("Sending DisentanglePorts failed");
1005        }
1006    }
1007
1008    /// Clean-up DOM related resources
1009    pub(crate) fn perform_a_dom_garbage_collection_checkpoint(&self) {
1010        self.perform_a_message_port_garbage_collection_checkpoint();
1011        self.perform_a_blob_garbage_collection_checkpoint();
1012        self.perform_a_broadcast_channel_garbage_collection_checkpoint();
1013        self.perform_an_abort_signal_garbage_collection_checkpoint();
1014    }
1015
1016    /// Remove the routers for ports and broadcast-channels.
1017    /// Drain the list of workers.
1018    pub(crate) fn remove_web_messaging_and_dedicated_workers_infra(&self) {
1019        self.remove_message_ports_router();
1020        self.remove_broadcast_channel_router();
1021
1022        // Drop each ref to a worker explicitly now,
1023        // which will send a shutdown signal,
1024        // and join on the worker thread.
1025        self.list_auto_close_worker
1026            .borrow_mut()
1027            .drain(0..)
1028            .for_each(drop);
1029    }
1030
1031    /// Update our state to un-managed,
1032    /// and tell the constellation to drop the sender to our message-port router.
1033    fn remove_message_ports_router(&self) {
1034        if let MessagePortState::Managed(router_id, _message_ports) =
1035            &*self.message_port_state.borrow()
1036        {
1037            let _ = self.script_to_constellation_chan().send(
1038                ScriptToConstellationMessage::RemoveMessagePortRouter(*router_id),
1039            );
1040        }
1041        *self.message_port_state.borrow_mut() = MessagePortState::UnManaged;
1042    }
1043
1044    /// Update our state to un-managed,
1045    /// and tell the constellation to drop the sender to our broadcast router.
1046    fn remove_broadcast_channel_router(&self) {
1047        if let BroadcastChannelState::Managed(router_id, _channels) =
1048            &*self.broadcast_channel_state.borrow()
1049        {
1050            let _ = self.script_to_constellation_chan().send(
1051                ScriptToConstellationMessage::RemoveBroadcastChannelRouter(
1052                    *router_id,
1053                    self.origin().immutable().clone(),
1054                ),
1055            );
1056        }
1057        *self.broadcast_channel_state.borrow_mut() = BroadcastChannelState::UnManaged;
1058    }
1059
1060    /// <https://html.spec.whatwg.org/multipage/#disentangle>
1061    pub(crate) fn disentangle_port(&self, cx: &mut js::context::JSContext, port: &MessagePort) {
1062        let initiator_port = port.message_port_id();
1063        // Let otherPort be the MessagePort which initiatorPort was entangled with.
1064        let Some(other_port) = port.disentangle() else {
1065            // Assert: otherPort exists.
1066            // Note: ignoring the assert,
1067            // because the streams spec seems to disentangle ports that are disentangled already.
1068            return;
1069        };
1070
1071        // Disentangle initiatorPort and otherPort, so that they are no longer entangled or associated with each other.
1072        // Note: this is done in part here, and in part at the constellation(if otherPort is in another global).
1073        let dom_port = if let MessagePortState::Managed(_id, message_ports) =
1074            &mut *self.message_port_state.borrow_mut()
1075        {
1076            let mut dom_port = None;
1077            for port_id in &[initiator_port, &other_port] {
1078                match message_ports.get_mut(port_id) {
1079                    None => {
1080                        continue;
1081                    },
1082                    Some(managed_port) => {
1083                        let port_impl = managed_port
1084                            .port_impl
1085                            .as_mut()
1086                            .expect("managed-port has no port-impl.");
1087                        managed_port.dom_port.disentangle();
1088                        port_impl.disentangle();
1089
1090                        if **port_id == other_port {
1091                            dom_port = Some(managed_port.dom_port.as_rooted())
1092                        }
1093                    },
1094                }
1095            }
1096            dom_port
1097        } else {
1098            panic!("disentangle_port called on a global not managing any ports.");
1099        };
1100
1101        // Fire an event named close at `otherPort`.
1102        // Note: done here if the port is managed by the same global as `initialPort`.
1103        if let Some(dom_port) = dom_port {
1104            dom_port.upcast().fire_event(cx, atom!("close"));
1105        }
1106
1107        let chan = self.script_to_constellation_chan().clone();
1108        let initiator_port = *initiator_port;
1109        self.task_manager()
1110            .port_message_queue()
1111            .queue(task!(post_message: move || {
1112                // Note: we do this in a task to ensure it doesn't affect messages that are still to be routed,
1113                // see the task queueing in `post_messageport_msg`.
1114                let res = chan.send(ScriptToConstellationMessage::DisentanglePorts(initiator_port, Some(other_port)));
1115                if res.is_err() {
1116                    warn!("Sending DisentanglePorts failed");
1117                }
1118            }));
1119    }
1120
1121    /// <https://html.spec.whatwg.org/multipage/#entangle>
1122    pub(crate) fn entangle_ports(&self, port1: MessagePortId, port2: MessagePortId) {
1123        if let MessagePortState::Managed(_id, message_ports) =
1124            &mut *self.message_port_state.borrow_mut()
1125        {
1126            for (port_id, entangled_id) in &[(port1, port2), (port2, port1)] {
1127                match message_ports.get_mut(port_id) {
1128                    None => {
1129                        return warn!("entangled_ports called on a global not managing the port.");
1130                    },
1131                    Some(managed_port) => {
1132                        if let Some(port_impl) = managed_port.port_impl.as_mut() {
1133                            managed_port.dom_port.entangle(*entangled_id);
1134                            port_impl.entangle(*entangled_id);
1135                        } else {
1136                            panic!("managed-port has no port-impl.");
1137                        }
1138                    },
1139                }
1140            }
1141        } else {
1142            panic!("entangled_ports called on a global not managing any ports.");
1143        }
1144
1145        let _ = self
1146            .script_to_constellation_chan()
1147            .send(ScriptToConstellationMessage::EntanglePorts(port1, port2));
1148    }
1149
1150    /// Handle the transfer of a port in the current task.
1151    pub(crate) fn mark_port_as_transferred(&self, port_id: &MessagePortId) -> MessagePortImpl {
1152        if let MessagePortState::Managed(_id, message_ports) =
1153            &mut *self.message_port_state.borrow_mut()
1154        {
1155            let mut port_impl = message_ports
1156                .remove(port_id)
1157                .map(|ref mut managed_port| {
1158                    managed_port
1159                        .port_impl
1160                        .take()
1161                        .expect("Managed port doesn't have a port-impl.")
1162                })
1163                .expect("mark_port_as_transferred called on a global not managing the port.");
1164            port_impl.set_has_been_shipped();
1165            let _ = self
1166                .script_to_constellation_chan()
1167                .send(ScriptToConstellationMessage::MessagePortShipped(*port_id));
1168            port_impl
1169        } else {
1170            panic!("mark_port_as_transferred called on a global not managing any ports.");
1171        }
1172    }
1173
1174    /// <https://html.spec.whatwg.org/multipage/#dom-messageport-start>
1175    pub(crate) fn start_message_port(
1176        &self,
1177        cx: &mut js::context::JSContext,
1178        port_id: &MessagePortId,
1179    ) {
1180        let (message_buffer, dom_port) = if let MessagePortState::Managed(_id, message_ports) =
1181            &mut *self.message_port_state.borrow_mut()
1182        {
1183            let (message_buffer, dom_port) = match message_ports.get_mut(port_id) {
1184                None => panic!("start_message_port called on a unknown port."),
1185                Some(managed_port) => {
1186                    if let Some(port_impl) = managed_port.port_impl.as_mut() {
1187                        (port_impl.start(), managed_port.dom_port.as_rooted())
1188                    } else {
1189                        panic!("managed-port has no port-impl.");
1190                    }
1191                },
1192            };
1193            (message_buffer, dom_port)
1194        } else {
1195            return warn!("start_message_port called on a global not managing any ports.");
1196        };
1197        if let Some(message_buffer) = message_buffer {
1198            for task in message_buffer {
1199                self.route_task_to_port(cx, *port_id, task);
1200            }
1201            if dom_port.disentangled() {
1202                // <https://html.spec.whatwg.org/multipage/#disentangle>
1203                // Fire an event named close at otherPort.
1204                dom_port.upcast().fire_event(cx, atom!("close"));
1205
1206                let res = self.script_to_constellation_chan().send(
1207                    ScriptToConstellationMessage::DisentanglePorts(*port_id, None),
1208                );
1209                if res.is_err() {
1210                    warn!("Sending DisentanglePorts failed");
1211                }
1212            }
1213        }
1214    }
1215
1216    /// <https://html.spec.whatwg.org/multipage/#dom-messageport-close>
1217    pub(crate) fn close_message_port(&self, port_id: &MessagePortId) {
1218        if let MessagePortState::Managed(_id, message_ports) =
1219            &mut *self.message_port_state.borrow_mut()
1220        {
1221            match message_ports.get_mut(port_id) {
1222                None => panic!("close_message_port called on an unknown port."),
1223                Some(managed_port) => {
1224                    if let Some(port_impl) = managed_port.port_impl.as_mut() {
1225                        port_impl.close();
1226                        managed_port.explicitly_closed = true;
1227                    } else {
1228                        panic!("managed-port has no port-impl.");
1229                    }
1230                },
1231            };
1232        } else {
1233            warn!("close_message_port called on a global not managing any ports.")
1234        }
1235    }
1236
1237    /// <https://html.spec.whatwg.org/multipage/#message-port-post-message-steps>
1238    // Steps 6 and 7
1239    pub(crate) fn post_messageport_msg(&self, port_id: MessagePortId, task: PortMessageTask) {
1240        if let MessagePortState::Managed(_id, message_ports) =
1241            &mut *self.message_port_state.borrow_mut()
1242        {
1243            let entangled_port = match message_ports.get_mut(&port_id) {
1244                None => panic!("post_messageport_msg called on an unknown port."),
1245                Some(managed_port) => {
1246                    if let Some(port_impl) = managed_port.port_impl.as_mut() {
1247                        port_impl.entangled_port_id()
1248                    } else {
1249                        panic!("managed-port has no port-impl.");
1250                    }
1251                },
1252            };
1253            if let Some(entangled_id) = entangled_port {
1254                // Step 7
1255                let this = Trusted::new(self);
1256                self.task_manager()
1257                    .port_message_queue()
1258                    .queue(task!(post_message: move |cx| {
1259                        let global = this.root();
1260                        // Note: we do this in a task, as this will ensure the global and constellation
1261                        // are aware of any transfer that might still take place in the current task.
1262                        global.route_task_to_port(cx, entangled_id, task);
1263                    }));
1264            }
1265        } else {
1266            warn!("post_messageport_msg called on a global not managing any ports.");
1267        }
1268    }
1269
1270    /// If we don't know about the port,
1271    /// send the message to the constellation for routing.
1272    fn re_route_port_task(&self, port_id: MessagePortId, task: PortMessageTask) {
1273        let _ = self.script_to_constellation_chan().send(
1274            ScriptToConstellationMessage::RerouteMessagePort(port_id, task),
1275        );
1276    }
1277
1278    /// <https://html.spec.whatwg.org/multipage/#dom-broadcastchannel-postmessage>
1279    /// Step 7 and following steps.
1280    pub(crate) fn schedule_broadcast(&self, msg: BroadcastChannelMsg, channel_id: &Uuid) {
1281        // First, broadcast locally.
1282        self.broadcast_message_event(msg.clone(), Some(channel_id));
1283
1284        if let BroadcastChannelState::Managed(router_id, _) =
1285            &*self.broadcast_channel_state.borrow()
1286        {
1287            // Second, broadcast to other globals via the constellation.
1288            //
1289            // Note: for globals in the same script-thread,
1290            // we could skip the hop to the constellation.
1291            let _ = self.script_to_constellation_chan().send(
1292                ScriptToConstellationMessage::ScheduleBroadcast(*router_id, msg),
1293            );
1294        } else {
1295            panic!("Attemps to broadcast a message via global not managing any channels.");
1296        }
1297    }
1298
1299    /// <https://html.spec.whatwg.org/multipage/#dom-broadcastchannel-postmessage>
1300    /// Step 7 and following steps.
1301    pub(crate) fn broadcast_message_event(
1302        &self,
1303        event: BroadcastChannelMsg,
1304        channel_id: Option<&Uuid>,
1305    ) {
1306        if let BroadcastChannelState::Managed(_, channels) = &*self.broadcast_channel_state.borrow()
1307        {
1308            let BroadcastChannelMsg {
1309                data,
1310                origin,
1311                channel_name,
1312            } = event;
1313
1314            // Step 7, a few preliminary steps.
1315
1316            // - Check the worker is not closing.
1317            if let Some(worker) = self.downcast::<WorkerGlobalScope>() {
1318                if worker.is_closing() {
1319                    return;
1320                }
1321            }
1322
1323            // - Check the associated document is fully-active.
1324            if let Some(window) = self.downcast::<Window>() {
1325                if !window.Document().is_fully_active() {
1326                    return;
1327                }
1328            }
1329
1330            // - Check for a case-sensitive match for the name of the channel.
1331            if let Some(channels) = channels.get(&channel_name.into()) {
1332                channels
1333                    .iter()
1334                    .filter(|channel| {
1335                        // Step 8.
1336                        // Filter out the sender.
1337                        if let Some(id) = channel_id {
1338                            channel.id() != id
1339                        } else {
1340                            true
1341                        }
1342                    })
1343                    .map(|channel| DomRoot::from_ref(&**channel))
1344                    // Step 9, sort by creation order,
1345                    // done by using a queue to store channels in creation order.
1346                    .for_each(|channel| {
1347                        let data = data.clone_for_broadcast();
1348                        let origin = origin.clone();
1349
1350                        // Step 10: Queue a task on the DOM manipulation task-source,
1351                        // to fire the message event
1352                        let channel = Trusted::new(&*channel);
1353                        let global = Trusted::new(self);
1354                        self.task_manager().dom_manipulation_task_source().queue(
1355                            task!(process_pending_port_messages: move |cx| {
1356                                let destination = channel.root();
1357                                let global = global.root();
1358
1359                                // 10.1 Check for closed flag.
1360                                if destination.closed() {
1361                                    return;
1362                                }
1363
1364                                rooted!(&in(cx) let mut message = UndefinedValue());
1365
1366                                // Step 10.3 StructuredDeserialize(serialized, targetRealm).
1367                                if let Ok(ports) = structuredclone::read(&global, data, message.handle_mut(), CanGc::from_cx(cx)) {
1368                                    // Step 10.4, Fire an event named message at destination.
1369                                    MessageEvent::dispatch_jsval(
1370                                        destination.upcast(),
1371                                        &global,
1372                                        message.handle(),
1373                                        Some(&origin.ascii_serialization()),
1374                                        None,
1375                                        ports,
1376                                        CanGc::from_cx(cx)
1377                                    );
1378                                } else {
1379                                    // Step 10.3, fire an event named messageerror at destination.
1380                                    MessageEvent::dispatch_error(cx, destination.upcast(), &global);
1381                                }
1382                            })
1383                        );
1384                    });
1385            }
1386        }
1387    }
1388
1389    /// <https://html.spec.whatwg.org/multipage/#encoding-parsing-a-url>
1390    pub(crate) fn encoding_parse_a_url(&self, url: &str) -> Result<ServoUrl, url::ParseError> {
1391        if let Some(window) = self.downcast::<Window>() {
1392            return window.Document().encoding_parse_a_url(url);
1393        }
1394
1395        // encoding parsing for worker environments.
1396        let base = self.api_base_url();
1397        base.join(url)
1398    }
1399
1400    /// <https://streams.spec.whatwg.org/#abstract-opdef-setupcrossrealmtransformreadable>
1401    /// The "Add a handler for port’s message event with the following steps:"
1402    /// and "Add a handler for port’s messageerror event with the following steps:" part.
1403    pub(crate) fn note_cross_realm_transform_readable(
1404        &self,
1405        cross_realm_transform_readable: &CrossRealmTransformReadable,
1406        port_id: &MessagePortId,
1407    ) {
1408        let MessagePortState::Managed(_id, message_ports) =
1409            &mut *self.message_port_state.borrow_mut()
1410        else {
1411            unreachable!(
1412                "Cross realm transform readable must be called on a global managing ports"
1413            );
1414        };
1415
1416        let Some(managed_port) = message_ports.get_mut(port_id) else {
1417            unreachable!("Cross realm transform readable must match a managed port");
1418        };
1419
1420        managed_port.cross_realm_transform = Some(CrossRealmTransform::Readable(
1421            cross_realm_transform_readable.clone(),
1422        ));
1423    }
1424
1425    /// <https://streams.spec.whatwg.org/#abstract-opdef-setupcrossrealmtransformwritable>
1426    /// The "Add a handler for port’s message event with the following steps:"
1427    /// and "Add a handler for port’s messageerror event with the following steps:" part.
1428    pub(crate) fn note_cross_realm_transform_writable(
1429        &self,
1430        cross_realm_transform_writable: &CrossRealmTransformWritable,
1431        port_id: &MessagePortId,
1432    ) {
1433        let MessagePortState::Managed(_id, message_ports) =
1434            &mut *self.message_port_state.borrow_mut()
1435        else {
1436            unreachable!(
1437                "Cross realm transform writable must be called on a global managing ports"
1438            );
1439        };
1440
1441        let Some(managed_port) = message_ports.get_mut(port_id) else {
1442            unreachable!("Cross realm transform writable must match a managed port");
1443        };
1444
1445        managed_port.cross_realm_transform = Some(CrossRealmTransform::Writable(
1446            cross_realm_transform_writable.clone(),
1447        ));
1448    }
1449
1450    /// Custom routing logic, followed by the task steps of
1451    /// <https://html.spec.whatwg.org/multipage/#message-port-post-message-steps>
1452    fn route_task_to_port(
1453        &self,
1454        cx: &mut js::context::JSContext,
1455        port_id: MessagePortId,
1456        task: PortMessageTask,
1457    ) {
1458        rooted!(&in(cx) let mut cross_realm_transform = None);
1459
1460        let should_dispatch = if let MessagePortState::Managed(_id, message_ports) =
1461            &mut *self.message_port_state.borrow_mut()
1462        {
1463            if !message_ports.contains_key(&port_id) {
1464                self.re_route_port_task(port_id, task);
1465                return;
1466            }
1467            match message_ports.get_mut(&port_id) {
1468                None => panic!("route_task_to_port called for an unknown port."),
1469                Some(managed_port) => {
1470                    // If the port is not enabled yet, or if is awaiting the completion of it's transfer,
1471                    // the task will be buffered and dispatched upon enablement or completion of the transfer.
1472                    if let Some(port_impl) = managed_port.port_impl.as_mut() {
1473                        let to_dispatch = port_impl.handle_incoming(task).map(|to_dispatch| {
1474                            (DomRoot::from_ref(&*managed_port.dom_port), to_dispatch)
1475                        });
1476                        cross_realm_transform.set(managed_port.cross_realm_transform.clone());
1477                        to_dispatch
1478                    } else {
1479                        panic!("managed-port has no port-impl.");
1480                    }
1481                },
1482            }
1483        } else {
1484            self.re_route_port_task(port_id, task);
1485            return;
1486        };
1487
1488        // Add a task that runs the following steps to the port message queue of targetPort:
1489        // Note: we are in the task, and running the relevant steps.
1490
1491        // Let finalTargetPort be the MessagePort in whose port message queue the task now finds itself.
1492        if let Some((dom_port, PortMessageTask { origin, data })) = should_dispatch {
1493            // Let messageEventTarget be finalTargetPort's message event target.
1494            let message_event_target = dom_port.upcast();
1495
1496            // Let targetRealm be finalTargetPort's relevant realm.
1497            // Done via the routing logic here and in the constellation: `self` is the target realm.
1498
1499            // Let messageClone be deserializeRecord.[[Deserialized]].
1500            // Re-ordered because we need to pass it to `structuredclone::read`.
1501            rooted!(&in(cx) let mut message_clone = UndefinedValue());
1502
1503            let mut realm = enter_auto_realm(cx, self);
1504            let cx = &mut realm.current_realm();
1505
1506            // Note: this is necessary, on top of entering the realm above,
1507            // for the call to `GlobalScope::incumbent`,
1508            // in `MessagePort::post_message_impl` to succeed.
1509            run_a_script::<DomTypeHolder, _>(self, || {
1510                // Let deserializeRecord be StructuredDeserializeWithTransfer(serializeWithTransferResult, targetRealm).
1511                // Let newPorts be a new frozen array
1512                // consisting of all MessagePort objects in deserializeRecord.[[TransferredValues]],
1513                // if any, maintaining their relative order.
1514                // Note: both done in `structuredclone::read`.
1515                if let Ok(ports) = structuredclone::read(
1516                    self,
1517                    data,
1518                    message_clone.handle_mut(),
1519                    CanGc::from_cx(cx),
1520                ) {
1521                    // Note: if this port is used to transfer a stream, we handle the events in Rust.
1522                    if let Some(transform) = cross_realm_transform.deref().as_ref() {
1523                        match transform {
1524                            // Add a handler for port’s message event with the following steps:
1525                            // from <https://streams.spec.whatwg.org/#abstract-opdef-setupcrossrealmtransformreadable>
1526                            CrossRealmTransform::Readable(readable) => {
1527                                readable.handle_message(
1528                                    cx,
1529                                    self,
1530                                    &dom_port,
1531                                    message_clone.handle(),
1532                                );
1533                            },
1534                            // Add a handler for port’s message event with the following steps:
1535                            // from <https://streams.spec.whatwg.org/#abstract-opdef-setupcrossrealmtransformwritable>
1536                            CrossRealmTransform::Writable(writable) => {
1537                                writable.handle_message(cx, self, message_clone.handle());
1538                            },
1539                        }
1540                    } else {
1541                        // Fire an event named message at messageEventTarget,
1542                        // using MessageEvent,
1543                        // with the data attribute initialized to messageClone
1544                        // and the ports attribute initialized to newPorts.
1545                        MessageEvent::dispatch_jsval(
1546                            message_event_target,
1547                            self,
1548                            message_clone.handle(),
1549                            Some(&origin.ascii_serialization()),
1550                            None,
1551                            ports,
1552                            CanGc::from_cx(cx),
1553                        );
1554                    }
1555                } else if let Some(transform) = cross_realm_transform.deref().as_ref() {
1556                    match transform {
1557                        // Add a handler for port’s messageerror event with the following steps:
1558                        // from <https://streams.spec.whatwg.org/#abstract-opdef-setupcrossrealmtransformreadable>
1559                        CrossRealmTransform::Readable(readable) => {
1560                            readable.handle_error(cx, self, &dom_port);
1561                        },
1562                        // Add a handler for port’s messageerror event with the following steps:
1563                        // from <https://streams.spec.whatwg.org/#abstract-opdef-setupcrossrealmtransformwritable>
1564                        CrossRealmTransform::Writable(writable) => {
1565                            writable.handle_error(cx, self, &dom_port);
1566                        },
1567                    }
1568                } else {
1569                    // If this throws an exception, catch it,
1570                    // fire an event named messageerror at messageEventTarget,
1571                    // using MessageEvent, and then return.
1572                    MessageEvent::dispatch_error(cx, message_event_target, self);
1573                }
1574            });
1575        }
1576    }
1577
1578    /// Check all ports that have been transfer-received in the previous task,
1579    /// and complete their transfer if they haven't been re-transferred.
1580    pub(crate) fn maybe_add_pending_ports(&self) {
1581        if let MessagePortState::Managed(router_id, message_ports) =
1582            &mut *self.message_port_state.borrow_mut()
1583        {
1584            let to_be_added: Vec<MessagePortId> = message_ports
1585                .iter()
1586                .filter_map(|(id, managed_port)| {
1587                    if managed_port.pending {
1588                        Some(*id)
1589                    } else {
1590                        None
1591                    }
1592                })
1593                .collect();
1594            for id in to_be_added.iter() {
1595                let managed_port = message_ports
1596                    .get_mut(id)
1597                    .expect("Collected port-id to match an entry");
1598                if !managed_port.pending {
1599                    panic!("Only pending ports should be found in to_be_added")
1600                }
1601                managed_port.pending = false;
1602            }
1603            let _ = self.script_to_constellation_chan().send(
1604                ScriptToConstellationMessage::CompleteMessagePortTransfer(*router_id, to_be_added),
1605            );
1606        } else {
1607            warn!("maybe_add_pending_ports called on a global not managing any ports.");
1608        }
1609    }
1610
1611    /// <https://html.spec.whatwg.org/multipage/#ports-and-garbage-collection>
1612    pub(crate) fn perform_a_message_port_garbage_collection_checkpoint(&self) {
1613        let is_empty = if let MessagePortState::Managed(_id, message_ports) =
1614            &mut *self.message_port_state.borrow_mut()
1615        {
1616            let to_be_removed: Vec<MessagePortId> = message_ports
1617                .iter()
1618                .filter_map(|(id, managed_port)| {
1619                    if managed_port.explicitly_closed {
1620                        Some(*id)
1621                    } else {
1622                        None
1623                    }
1624                })
1625                .collect();
1626            for id in to_be_removed {
1627                message_ports.remove(&id);
1628            }
1629            // Note: ports are only removed throught explicit closure by script in this global.
1630            // TODO: #25772
1631            // TODO: remove ports when we can be sure their port message queue is empty(via the constellation).
1632            message_ports.is_empty()
1633        } else {
1634            false
1635        };
1636        if is_empty {
1637            self.remove_message_ports_router();
1638        }
1639    }
1640
1641    /// Remove broadcast-channels that are closed.
1642    /// TODO: Also remove them if they do not have an event-listener.
1643    /// see <https://github.com/servo/servo/issues/25772>
1644    pub(crate) fn perform_a_broadcast_channel_garbage_collection_checkpoint(&self) {
1645        let is_empty = if let BroadcastChannelState::Managed(router_id, channels) =
1646            &mut *self.broadcast_channel_state.borrow_mut()
1647        {
1648            channels.retain(|name, ref mut channels| {
1649                channels.retain(|chan| !chan.closed());
1650                if channels.is_empty() {
1651                    let _ = self.script_to_constellation_chan().send(
1652                        ScriptToConstellationMessage::RemoveBroadcastChannelNameInRouter(
1653                            *router_id,
1654                            name.to_string(),
1655                            self.origin().immutable().clone(),
1656                        ),
1657                    );
1658                    false
1659                } else {
1660                    true
1661                }
1662            });
1663            channels.is_empty()
1664        } else {
1665            false
1666        };
1667        if is_empty {
1668            self.remove_broadcast_channel_router();
1669        }
1670    }
1671
1672    /// Register a dependent AbortSignal that may need to be kept alive
1673    /// <https://dom.spec.whatwg.org/#abort-signal-garbage-collection>
1674    pub(crate) fn register_dependent_abort_signal(&self, signal: &AbortSignal) {
1675        self.abort_signal_dependents
1676            .borrow_mut()
1677            .insert(Dom::from_ref(signal));
1678    }
1679
1680    /// Clean up dependent AbortSignals that no longer satisfy the GC predicate.
1681    pub(crate) fn perform_an_abort_signal_garbage_collection_checkpoint(&self) {
1682        let mut set = self.abort_signal_dependents.borrow_mut();
1683
1684        set.retain(|dom_signal| dom_signal.must_keep_alive_for_gc());
1685    }
1686
1687    /// Start tracking a broadcast-channel.
1688    pub(crate) fn track_broadcast_channel(&self, dom_channel: &BroadcastChannel) {
1689        let mut current_state = self.broadcast_channel_state.borrow_mut();
1690
1691        if let BroadcastChannelState::UnManaged = &*current_state {
1692            // Setup a route for IPC, for broadcasts from the constellation to our channels.
1693            let (broadcast_control_sender, broadcast_control_receiver) =
1694                ipc::channel().expect("ipc channel failure");
1695            let context = Trusted::new(self);
1696            let listener = BroadcastListener {
1697                task_source: self.task_manager().dom_manipulation_task_source().into(),
1698                context,
1699            };
1700            ROUTER.add_typed_route(
1701                broadcast_control_receiver,
1702                Box::new(move |message| match message {
1703                    Ok(msg) => listener.handle(msg),
1704                    Err(err) => warn!("Error receiving a BroadcastChannelMsg: {:?}", err),
1705                }),
1706            );
1707            let router_id = BroadcastChannelRouterId::new();
1708            *current_state = BroadcastChannelState::Managed(router_id, HashMap::new());
1709            let _ = self.script_to_constellation_chan().send(
1710                ScriptToConstellationMessage::NewBroadcastChannelRouter(
1711                    router_id,
1712                    broadcast_control_sender,
1713                    self.origin().immutable().clone(),
1714                ),
1715            );
1716        }
1717
1718        if let BroadcastChannelState::Managed(router_id, channels) = &mut *current_state {
1719            let entry = channels.entry(dom_channel.Name()).or_insert_with(|| {
1720                let _ = self.script_to_constellation_chan().send(
1721                    ScriptToConstellationMessage::NewBroadcastChannelNameInRouter(
1722                        *router_id,
1723                        dom_channel.Name().to_string(),
1724                        self.origin().immutable().clone(),
1725                    ),
1726                );
1727                VecDeque::new()
1728            });
1729            entry.push_back(Dom::from_ref(dom_channel));
1730        } else {
1731            panic!("track_broadcast_channel should have first switched the state to managed.");
1732        }
1733    }
1734
1735    /// Start tracking a message-port
1736    pub(crate) fn track_message_port(
1737        &self,
1738        dom_port: &MessagePort,
1739        port_impl: Option<MessagePortImpl>,
1740    ) {
1741        let mut current_state = self.message_port_state.borrow_mut();
1742
1743        if let MessagePortState::UnManaged = &*current_state {
1744            // Setup a route for IPC, for messages from the constellation to our ports.
1745            let context = Trusted::new(self);
1746            let listener = MessageListener {
1747                task_source: self.task_manager().port_message_queue().into(),
1748                context,
1749            };
1750
1751            let port_control_callback = GenericCallback::new(move |message| match message {
1752                Ok(msg) => listener.notify(msg),
1753                Err(err) => warn!("Error receiving a MessagePortMsg: {:?}", err),
1754            })
1755            .expect("Could not create callback");
1756            let router_id = MessagePortRouterId::new();
1757            *current_state = MessagePortState::Managed(router_id, HashMapTracedValues::new_fx());
1758            let _ = self.script_to_constellation_chan().send(
1759                ScriptToConstellationMessage::NewMessagePortRouter(
1760                    router_id,
1761                    port_control_callback,
1762                ),
1763            );
1764        }
1765
1766        if let MessagePortState::Managed(router_id, message_ports) = &mut *current_state {
1767            if let Some(port_impl) = port_impl {
1768                // We keep transfer-received ports as "pending",
1769                // and only ask the constellation to complete the transfer
1770                // if they're not re-shipped in the current task.
1771                message_ports.insert(
1772                    *dom_port.message_port_id(),
1773                    ManagedMessagePort {
1774                        port_impl: Some(port_impl),
1775                        dom_port: Dom::from_ref(dom_port),
1776                        pending: true,
1777                        explicitly_closed: false,
1778                        cross_realm_transform: None,
1779                    },
1780                );
1781
1782                // Queue a task to complete the transfer,
1783                // unless the port is re-transferred in the current task.
1784                let this = Trusted::new(self);
1785                self.task_manager().port_message_queue().queue(
1786                    task!(process_pending_port_messages: move || {
1787                        let target_global = this.root();
1788                        target_global.maybe_add_pending_ports();
1789                    }),
1790                );
1791            } else {
1792                // If this is a newly-created port, let the constellation immediately know.
1793                let port_impl = MessagePortImpl::new(*dom_port.message_port_id());
1794                message_ports.insert(
1795                    *dom_port.message_port_id(),
1796                    ManagedMessagePort {
1797                        port_impl: Some(port_impl),
1798                        dom_port: Dom::from_ref(dom_port),
1799                        pending: false,
1800                        explicitly_closed: false,
1801                        cross_realm_transform: None,
1802                    },
1803                );
1804                let _ = self.script_to_constellation_chan().send(
1805                    ScriptToConstellationMessage::NewMessagePort(
1806                        *router_id,
1807                        *dom_port.message_port_id(),
1808                    ),
1809                );
1810            };
1811        } else {
1812            panic!("track_message_port should have first switched the state to managed.");
1813        }
1814    }
1815
1816    /// <https://html.spec.whatwg.org/multipage/#serialization-steps>
1817    /// defined at <https://w3c.github.io/FileAPI/#blob-section>.
1818    /// Get the snapshot state and underlying bytes of the blob.
1819    pub(crate) fn serialize_blob(&self, blob_id: &BlobId) -> BlobImpl {
1820        // Note: we combine the snapshot state and underlying bytes into one call,
1821        // which seems spec compliant.
1822        // See https://w3c.github.io/FileAPI/#snapshot-state
1823        let bytes = self
1824            .get_blob_bytes(blob_id)
1825            .expect("Could not read bytes from blob as part of serialization steps.");
1826        let type_string = self.get_blob_type_string(blob_id);
1827
1828        // Note: the new BlobImpl is a clone, but with it's own BlobId.
1829        BlobImpl::new_from_bytes(bytes, type_string)
1830    }
1831
1832    fn track_blob_info(&self, blob_info: BlobInfo, blob_id: BlobId) {
1833        self.blob_state.borrow_mut().insert(blob_id, blob_info);
1834    }
1835
1836    /// Start tracking a blob
1837    pub(crate) fn track_blob(&self, dom_blob: &Blob, blob_impl: BlobImpl) {
1838        let blob_id = blob_impl.blob_id();
1839
1840        let blob_info = BlobInfo {
1841            blob_impl,
1842            tracker: BlobTracker::Blob(WeakRef::new(dom_blob)),
1843            has_url: false,
1844        };
1845
1846        self.track_blob_info(blob_info, blob_id);
1847    }
1848
1849    /// Start tracking a file
1850    pub(crate) fn track_file(&self, file: &File, blob_impl: BlobImpl) {
1851        let blob_id = blob_impl.blob_id();
1852
1853        let blob_info = BlobInfo {
1854            blob_impl,
1855            tracker: BlobTracker::File(WeakRef::new(file)),
1856            has_url: false,
1857        };
1858
1859        self.track_blob_info(blob_info, blob_id);
1860    }
1861
1862    /// Clean-up any file or blob that is unreachable from script,
1863    /// unless it has an oustanding blob url.
1864    /// <https://w3c.github.io/FileAPI/#lifeTime>
1865    fn perform_a_blob_garbage_collection_checkpoint(&self) {
1866        let mut blob_state = self.blob_state.borrow_mut();
1867        blob_state.0.retain(|_id, blob_info| {
1868            let garbage_collected = match &blob_info.tracker {
1869                BlobTracker::File(weak) => weak.root().is_none(),
1870                BlobTracker::Blob(weak) => weak.root().is_none(),
1871            };
1872            if garbage_collected && !blob_info.has_url {
1873                if let BlobData::File(f) = blob_info.blob_impl.blob_data() {
1874                    self.decrement_file_ref(f.get_id());
1875                }
1876                false
1877            } else {
1878                true
1879            }
1880        });
1881    }
1882
1883    /// Clean-up all file related resources on document unload.
1884    /// <https://w3c.github.io/FileAPI/#lifeTime>
1885    pub(crate) fn clean_up_all_file_resources(&self) {
1886        self.blob_state
1887            .borrow_mut()
1888            .drain()
1889            .for_each(|(_id, blob_info)| {
1890                if let BlobData::File(f) = blob_info.blob_impl.blob_data() {
1891                    self.decrement_file_ref(f.get_id());
1892                }
1893            });
1894    }
1895
1896    fn decrement_file_ref(&self, id: Uuid) {
1897        let origin = self.origin().immutable();
1898
1899        let (tx, rx) = profile_generic_channel::channel(self.time_profiler_chan().clone()).unwrap();
1900
1901        let msg = FileManagerThreadMsg::DecRef(id, origin.clone(), tx);
1902        self.send_to_file_manager(msg);
1903        let _ = rx.recv();
1904    }
1905
1906    /// Get a slice to the inner data of a Blob,
1907    /// In the case of a File-backed blob, this might incur synchronous read and caching.
1908    pub(crate) fn get_blob_bytes(&self, blob_id: &BlobId) -> Result<Vec<u8>, ()> {
1909        let parent = {
1910            match *self.get_blob_data(blob_id) {
1911                BlobData::Sliced(parent, rel_pos) => Some((parent, rel_pos)),
1912                _ => None,
1913            }
1914        };
1915
1916        match parent {
1917            Some((parent_id, rel_pos)) => self.get_blob_bytes_non_sliced(&parent_id).map(|v| {
1918                let range = rel_pos.to_abs_range(v.len());
1919                v.index(range).to_vec()
1920            }),
1921            None => self.get_blob_bytes_non_sliced(blob_id),
1922        }
1923    }
1924
1925    /// Retrieve information about a specific blob from the blob store
1926    ///
1927    /// # Panics
1928    /// This function panics if there is no blob with the given ID.
1929    pub(crate) fn get_blob_data<'a>(&'a self, blob_id: &BlobId) -> Ref<'a, BlobData> {
1930        Ref::map(self.blob_state.borrow(), |blob_state| {
1931            blob_state
1932                .get(blob_id)
1933                .expect("get_blob_impl called for a unknown blob")
1934                .blob_impl
1935                .blob_data()
1936        })
1937    }
1938
1939    /// Get bytes from a non-sliced blob
1940    fn get_blob_bytes_non_sliced(&self, blob_id: &BlobId) -> Result<Vec<u8>, ()> {
1941        match *self.get_blob_data(blob_id) {
1942            BlobData::File(ref f) => {
1943                let (buffer, is_new_buffer) = match f.get_cache() {
1944                    Some(bytes) => (bytes, false),
1945                    None => {
1946                        let bytes = self.read_file(f.get_id())?;
1947                        (bytes, true)
1948                    },
1949                };
1950
1951                // Cache
1952                if is_new_buffer {
1953                    f.cache_bytes(buffer.clone());
1954                }
1955
1956                Ok(buffer)
1957            },
1958            BlobData::Memory(ref s) => Ok(s.clone()),
1959            BlobData::Sliced(_, _) => panic!("This blob doesn't have a parent."),
1960        }
1961    }
1962
1963    /// Get a slice to the inner data of a Blob,
1964    /// if it's a memory blob, or it's file-id and file-size otherwise.
1965    ///
1966    /// Note: this is almost a duplicate of `get_blob_bytes`,
1967    /// tweaked for integration with streams.
1968    /// TODO: merge with `get_blob_bytes` by way of broader integration with blob streams.
1969    fn get_blob_bytes_or_file_id(&self, blob_id: &BlobId) -> BlobResult {
1970        let parent = {
1971            match *self.get_blob_data(blob_id) {
1972                BlobData::Sliced(parent, rel_pos) => Some((parent, rel_pos)),
1973                _ => None,
1974            }
1975        };
1976
1977        match parent {
1978            Some((parent_id, rel_pos)) => {
1979                match self.get_blob_bytes_non_sliced_or_file_id(&parent_id) {
1980                    BlobResult::Bytes(bytes) => {
1981                        let range = rel_pos.to_abs_range(bytes.len());
1982                        BlobResult::Bytes(bytes.index(range).to_vec())
1983                    },
1984                    res => res,
1985                }
1986            },
1987            None => self.get_blob_bytes_non_sliced_or_file_id(blob_id),
1988        }
1989    }
1990
1991    /// Get bytes from a non-sliced blob if in memory, or it's file-id and file-size.
1992    ///
1993    /// Note: this is almost a duplicate of `get_blob_bytes_non_sliced`,
1994    /// tweaked for integration with streams.
1995    /// TODO: merge with `get_blob_bytes` by way of broader integration with blob streams.
1996    fn get_blob_bytes_non_sliced_or_file_id(&self, blob_id: &BlobId) -> BlobResult {
1997        match *self.get_blob_data(blob_id) {
1998            BlobData::File(ref f) => match f.get_cache() {
1999                Some(bytes) => BlobResult::Bytes(bytes),
2000                None => BlobResult::File(f.get_id(), f.get_size() as usize),
2001            },
2002            BlobData::Memory(ref s) => BlobResult::Bytes(s.clone()),
2003            BlobData::Sliced(_, _) => panic!("This blob doesn't have a parent."),
2004        }
2005    }
2006
2007    /// Get a copy of the type_string of a blob.
2008    pub(crate) fn get_blob_type_string(&self, blob_id: &BlobId) -> String {
2009        let blob_state = self.blob_state.borrow();
2010        let blob_info = blob_state
2011            .get(blob_id)
2012            .expect("get_blob_type_string called for a unknown blob.");
2013        blob_info.blob_impl.type_string()
2014    }
2015
2016    /// <https://w3c.github.io/FileAPI/#dfn-size>
2017    pub(crate) fn get_blob_size(&self, blob_id: &BlobId) -> u64 {
2018        let parent = {
2019            match *self.get_blob_data(blob_id) {
2020                BlobData::Sliced(parent, rel_pos) => Some((parent, rel_pos)),
2021                _ => None,
2022            }
2023        };
2024        match parent {
2025            Some((parent_id, rel_pos)) => {
2026                let parent_size = match *self.get_blob_data(&parent_id) {
2027                    BlobData::File(ref f) => f.get_size(),
2028                    BlobData::Memory(ref v) => v.len() as u64,
2029                    BlobData::Sliced(_, _) => panic!("Blob ancestry should be only one level."),
2030                };
2031                rel_pos.to_abs_range(parent_size as usize).len() as u64
2032            },
2033            None => match *self.get_blob_data(blob_id) {
2034                BlobData::File(ref f) => f.get_size(),
2035                BlobData::Memory(ref v) => v.len() as u64,
2036                BlobData::Sliced(_, _) => {
2037                    panic!("It was previously checked that this blob does not have a parent.")
2038                },
2039            },
2040        }
2041    }
2042
2043    pub(crate) fn get_blob_url_id(&self, blob_id: &BlobId) -> Uuid {
2044        let mut blob_state = self.blob_state.borrow_mut();
2045        let parent = {
2046            let blob_info = blob_state
2047                .get_mut(blob_id)
2048                .expect("get_blob_url_id called for a unknown blob.");
2049
2050            // Keep track of blobs with outstanding URLs.
2051            blob_info.has_url = true;
2052
2053            match blob_info.blob_impl.blob_data() {
2054                BlobData::Sliced(parent, rel_pos) => Some((*parent, *rel_pos)),
2055                _ => None,
2056            }
2057        };
2058        match parent {
2059            Some((parent_id, rel_pos)) => {
2060                let parent_info = blob_state
2061                    .get_mut(&parent_id)
2062                    .expect("Parent of blob whose url is requested is unknown.");
2063                let parent_file_id = self.promote(parent_info, /* set_valid is */ false);
2064                let parent_size = match parent_info.blob_impl.blob_data() {
2065                    BlobData::File(f) => f.get_size(),
2066                    BlobData::Memory(v) => v.len() as u64,
2067                    BlobData::Sliced(_, _) => panic!("Blob ancestry should be only one level."),
2068                };
2069                let parent_size = rel_pos.to_abs_range(parent_size as usize).len() as u64;
2070                let blob_info = blob_state
2071                    .get_mut(blob_id)
2072                    .expect("Blob whose url is requested is unknown.");
2073                self.create_sliced_url_id(blob_info, &parent_file_id, &rel_pos, parent_size)
2074            },
2075            None => {
2076                let blob_info = blob_state
2077                    .get_mut(blob_id)
2078                    .expect("Blob whose url is requested is unknown.");
2079                self.promote(blob_info, /* set_valid is */ true)
2080            },
2081        }
2082    }
2083
2084    /// Get a FileID representing sliced parent-blob content
2085    fn create_sliced_url_id(
2086        &self,
2087        blob_info: &mut BlobInfo,
2088        parent_file_id: &Uuid,
2089        rel_pos: &RelativePos,
2090        parent_len: u64,
2091    ) -> Uuid {
2092        let origin = self.origin().immutable();
2093
2094        let (tx, rx) = profile_generic_channel::channel(self.time_profiler_chan().clone()).unwrap();
2095        let msg =
2096            FileManagerThreadMsg::AddSlicedURLEntry(*parent_file_id, *rel_pos, tx, origin.clone());
2097        self.send_to_file_manager(msg);
2098        match rx.recv().expect("File manager thread is down.") {
2099            Ok(new_id) => {
2100                *blob_info.blob_impl.blob_data_mut() = BlobData::File(FileBlob::new(
2101                    new_id,
2102                    None,
2103                    None,
2104                    rel_pos.to_abs_range(parent_len as usize).len() as u64,
2105                ));
2106
2107                // Return the indirect id reference
2108                new_id
2109            },
2110            Err(_) => {
2111                // Return dummy id
2112                Uuid::new_v4()
2113            },
2114        }
2115    }
2116
2117    /// Promote non-Slice blob:
2118    /// 1. Memory-based: The bytes in data slice will be transferred to file manager thread.
2119    /// 2. File-based: If set_valid, then activate the FileID so it can serve as URL
2120    ///    Depending on set_valid, the returned FileID can be part of
2121    ///    valid or invalid Blob URL.
2122    pub(crate) fn promote(&self, blob_info: &mut BlobInfo, set_valid: bool) -> Uuid {
2123        let mut bytes = vec![];
2124
2125        match blob_info.blob_impl.blob_data_mut() {
2126            BlobData::Sliced(_, _) => {
2127                panic!("Sliced blobs should use create_sliced_url_id instead of promote.");
2128            },
2129            BlobData::File(f) => {
2130                if set_valid {
2131                    let origin = self.origin().immutable();
2132                    let (tx, rx) = profile_ipc::channel(self.time_profiler_chan().clone()).unwrap();
2133
2134                    let msg = FileManagerThreadMsg::ActivateBlobURL(f.get_id(), tx, origin.clone());
2135                    self.send_to_file_manager(msg);
2136
2137                    match rx.recv().unwrap() {
2138                        Ok(_) => return f.get_id(),
2139                        // Return a dummy id on error
2140                        Err(_) => return Uuid::new_v4(),
2141                    }
2142                } else {
2143                    // no need to activate
2144                    return f.get_id();
2145                }
2146            },
2147            BlobData::Memory(bytes_in) => mem::swap(bytes_in, &mut bytes),
2148        };
2149
2150        let origin = self.origin().immutable();
2151
2152        let blob_buf = BlobBuf {
2153            filename: None,
2154            type_string: blob_info.blob_impl.type_string(),
2155            size: bytes.len() as u64,
2156            bytes: bytes.to_vec(),
2157        };
2158
2159        let id = Uuid::new_v4();
2160        let msg = FileManagerThreadMsg::PromoteMemory(id, blob_buf, set_valid, origin.clone());
2161        self.send_to_file_manager(msg);
2162
2163        *blob_info.blob_impl.blob_data_mut() = BlobData::File(FileBlob::new(
2164            id,
2165            None,
2166            Some(bytes.to_vec()),
2167            bytes.len() as u64,
2168        ));
2169
2170        id
2171    }
2172
2173    fn send_to_file_manager(&self, msg: FileManagerThreadMsg) {
2174        let resource_threads = self.resource_threads();
2175        let _ = resource_threads.send(CoreResourceMsg::ToFileManager(msg));
2176    }
2177
2178    fn read_file(&self, id: Uuid) -> Result<Vec<u8>, ()> {
2179        let recv = self.send_msg(id);
2180        GlobalScope::read_msg(recv)
2181    }
2182
2183    /// <https://w3c.github.io/FileAPI/#blob-get-stream>
2184    pub(crate) fn get_blob_stream(
2185        &self,
2186        cx: &mut js::context::JSContext,
2187        blob_id: &BlobId,
2188    ) -> Fallible<DomRoot<ReadableStream>> {
2189        let (file_id, size) = match self.get_blob_bytes_or_file_id(blob_id) {
2190            BlobResult::Bytes(bytes) => {
2191                // If we have all the bytes in memory, queue them and close the stream.
2192                return ReadableStream::new_from_bytes(cx, self, bytes);
2193            },
2194            BlobResult::File(id, size) => (id, size),
2195        };
2196
2197        let stream = ReadableStream::new_with_external_underlying_source(
2198            cx,
2199            self,
2200            UnderlyingSourceType::Blob(size),
2201        )?;
2202
2203        let recv = self.send_msg(file_id);
2204
2205        let trusted_stream = Trusted::new(&*stream);
2206        let mut file_listener = FileListener {
2207            state: Some(FileListenerState::Empty(FileListenerTarget::Stream(
2208                trusted_stream,
2209            ))),
2210            task_source: self.task_manager().file_reading_task_source().into(),
2211        };
2212
2213        ROUTER.add_typed_route(
2214            recv.to_ipc_receiver(),
2215            Box::new(move |msg| {
2216                file_listener.handle(msg.expect("Deserialization of file listener msg failed."));
2217            }),
2218        );
2219
2220        Ok(stream)
2221    }
2222
2223    pub(crate) fn read_file_async(
2224        &self,
2225        id: Uuid,
2226        promise: Rc<Promise>,
2227        callback: FileListenerCallback,
2228    ) {
2229        let recv = self.send_msg(id);
2230
2231        let trusted_promise = TrustedPromise::new(promise);
2232        let mut file_listener = FileListener {
2233            state: Some(FileListenerState::Empty(FileListenerTarget::Promise(
2234                trusted_promise,
2235                callback,
2236            ))),
2237            task_source: self.task_manager().file_reading_task_source().into(),
2238        };
2239
2240        ROUTER.add_typed_route(
2241            recv.to_ipc_receiver(),
2242            Box::new(move |msg| {
2243                file_listener.handle(msg.expect("Deserialization of file listener msg failed."));
2244            }),
2245        );
2246    }
2247
2248    fn send_msg(&self, id: Uuid) -> profile_ipc::IpcReceiver<FileManagerResult<ReadFileProgress>> {
2249        let resource_threads = self.resource_threads();
2250        let (chan, recv) = profile_ipc::channel(self.time_profiler_chan().clone()).unwrap();
2251        let origin = self.origin().immutable();
2252        let msg = FileManagerThreadMsg::ReadFile(chan, id, origin.clone());
2253        let _ = resource_threads.send(CoreResourceMsg::ToFileManager(msg));
2254        recv
2255    }
2256
2257    fn read_msg(
2258        receiver: profile_ipc::IpcReceiver<FileManagerResult<ReadFileProgress>>,
2259    ) -> Result<Vec<u8>, ()> {
2260        let mut bytes = vec![];
2261
2262        loop {
2263            match receiver.recv().unwrap() {
2264                Ok(ReadFileProgress::Meta(mut blob_buf)) => {
2265                    bytes.append(&mut blob_buf.bytes);
2266                },
2267                Ok(ReadFileProgress::Partial(mut bytes_in)) => {
2268                    bytes.append(&mut bytes_in);
2269                },
2270                Ok(ReadFileProgress::EOF) => {
2271                    return Ok(bytes);
2272                },
2273                Err(_) => return Err(()),
2274            }
2275        }
2276    }
2277
2278    pub(crate) fn permission_state_invocation_results(
2279        &self,
2280    ) -> &DomRefCell<HashMap<PermissionName, PermissionState>> {
2281        &self.permission_state_invocation_results
2282    }
2283
2284    pub(crate) fn track_worker(
2285        &self,
2286        closing: Arc<AtomicBool>,
2287        join_handle: JoinHandle<()>,
2288        control_sender: Sender<DedicatedWorkerControlMsg>,
2289        context: ThreadSafeJSContext,
2290    ) {
2291        self.list_auto_close_worker
2292            .borrow_mut()
2293            .push(AutoCloseWorker {
2294                closing,
2295                join_handle: Some(join_handle),
2296                control_sender,
2297                context,
2298            });
2299    }
2300
2301    pub(crate) fn track_event_source(&self, event_source: &EventSource) {
2302        self.event_source_tracker.track(event_source);
2303    }
2304
2305    pub(crate) fn close_event_sources(&self) -> bool {
2306        let mut canceled_any_fetch = false;
2307        self.event_source_tracker
2308            .for_each(
2309                |event_source: DomRoot<EventSource>| match event_source.ReadyState() {
2310                    2 => {},
2311                    _ => {
2312                        event_source.cancel();
2313                        canceled_any_fetch = true;
2314                    },
2315                },
2316            );
2317        canceled_any_fetch
2318    }
2319
2320    /// Returns the global scope of the realm that the given DOM object's reflector
2321    /// was created in.
2322    #[expect(unsafe_code)]
2323    pub(crate) fn from_reflector<T: DomObject>(reflector: &T, _realm: InRealm) -> DomRoot<Self> {
2324        unsafe { GlobalScope::from_object(*reflector.reflector().get_jsobject()) }
2325    }
2326
2327    /// Returns the global scope of the realm that the given JS object was created in.
2328    #[expect(unsafe_code)]
2329    pub(crate) unsafe fn from_object(obj: *mut JSObject) -> DomRoot<Self> {
2330        assert!(!obj.is_null());
2331        let global = unsafe { GetNonCCWObjectGlobal(obj) };
2332        unsafe { global_scope_from_global_static(global) }
2333    }
2334
2335    /// Returns the global scope for the given JSContext
2336    #[expect(unsafe_code)]
2337    pub(crate) unsafe fn from_context(cx: *mut JSContext, _realm: InRealm) -> DomRoot<Self> {
2338        let global = unsafe { CurrentGlobalOrNull(cx) };
2339        assert!(!global.is_null());
2340        unsafe { global_scope_from_global(global, cx) }
2341    }
2342
2343    /// Return global scope asociated with current realm
2344    ///
2345    /// Eventually we could return Handle here as global is already rooted by realm.
2346    #[expect(unsafe_code)]
2347    pub(crate) fn from_current_realm(realm: &'_ CurrentRealm) -> DomRoot<Self> {
2348        let global = realm.global();
2349        unsafe { global_scope_from_global(global.get(), realm.raw_cx_no_gc()) }
2350    }
2351
2352    /// Returns the global scope for the given SafeJSContext
2353    #[expect(unsafe_code)]
2354    pub(crate) fn from_safe_context(cx: SafeJSContext, realm: InRealm) -> DomRoot<Self> {
2355        unsafe { Self::from_context(*cx, realm) }
2356    }
2357
2358    pub(crate) fn add_uncaught_rejection(&self, rejection: HandleObject) {
2359        self.uncaught_rejections
2360            .borrow_mut()
2361            .push(Heap::boxed(rejection.get()));
2362    }
2363
2364    pub(crate) fn remove_uncaught_rejection(&self, rejection: HandleObject) {
2365        let mut uncaught_rejections = self.uncaught_rejections.borrow_mut();
2366
2367        if let Some(index) = uncaught_rejections
2368            .iter()
2369            .position(|promise| *promise == Heap::boxed(rejection.get()))
2370        {
2371            uncaught_rejections.remove(index);
2372        }
2373    }
2374
2375    // `Heap` values must stay boxed, as they need semantics like `Pin`
2376    // (that is, they cannot be moved).
2377    #[allow(clippy::vec_box)]
2378    /// <https://html.spec.whatwg.org/multipage/#about-to-be-notified-rejected-promises-list>
2379    pub(crate) fn get_uncaught_rejections(&self) -> &DomRefCell<Vec<Box<Heap<*mut JSObject>>>> {
2380        &self.uncaught_rejections
2381    }
2382
2383    pub(crate) fn add_consumed_rejection(&self, rejection: HandleObject) {
2384        self.consumed_rejections
2385            .borrow_mut()
2386            .push(Heap::boxed(rejection.get()));
2387    }
2388
2389    pub(crate) fn remove_consumed_rejection(&self, rejection: HandleObject) {
2390        let mut consumed_rejections = self.consumed_rejections.borrow_mut();
2391
2392        if let Some(index) = consumed_rejections
2393            .iter()
2394            .position(|promise| *promise == Heap::boxed(rejection.get()))
2395        {
2396            consumed_rejections.remove(index);
2397        }
2398    }
2399
2400    // `Heap` values must stay boxed, as they need semantics like `Pin`
2401    // (that is, they cannot be moved).
2402    #[allow(clippy::vec_box)]
2403    pub(crate) fn get_consumed_rejections(&self) -> &DomRefCell<Vec<Box<Heap<*mut JSObject>>>> {
2404        &self.consumed_rejections
2405    }
2406
2407    pub(crate) fn set_module_map(&self, request: ModuleRequest, module: ModuleStatus) {
2408        self.module_map.borrow_mut().insert(request, module);
2409    }
2410
2411    pub(crate) fn get_module_map_entry(&self, request: &ModuleRequest) -> Option<ModuleStatus> {
2412        self.module_map.borrow().get(request).cloned()
2413    }
2414
2415    #[expect(unsafe_code)]
2416    pub(crate) fn get_cx() -> SafeJSContext {
2417        let cx = Runtime::get()
2418            .expect("Can't obtain context after runtime shutdown")
2419            .as_ptr();
2420        unsafe { SafeJSContext::from_ptr(cx) }
2421    }
2422
2423    pub(crate) fn time(&self, label: DOMString) -> Result<(), ()> {
2424        let mut timers = self.console_timers.borrow_mut();
2425        if timers.len() >= 10000 {
2426            return Err(());
2427        }
2428        match timers.entry(label) {
2429            Entry::Vacant(entry) => {
2430                entry.insert(Instant::now());
2431                Ok(())
2432            },
2433            Entry::Occupied(_) => Err(()),
2434        }
2435    }
2436
2437    /// Computes the delta time since a label has been created
2438    ///
2439    /// Returns an error if the label does not exist.
2440    pub(crate) fn time_log(&self, label: &DOMString) -> Result<u64, ()> {
2441        self.console_timers
2442            .borrow()
2443            .get(label)
2444            .ok_or(())
2445            .map(|&start| (Instant::now() - start).as_millis() as u64)
2446    }
2447
2448    /// Computes the delta time since a label has been created and stops
2449    /// tracking the label.
2450    ///
2451    /// Returns an error if the label does not exist.
2452    pub(crate) fn time_end(&self, label: &DOMString) -> Result<u64, ()> {
2453        self.console_timers
2454            .borrow_mut()
2455            .remove(label)
2456            .ok_or(())
2457            .map(|start| (Instant::now() - start).as_millis() as u64)
2458    }
2459
2460    /// Get an `&IpcSender<ScriptToDevtoolsControlMsg>` to send messages
2461    /// to the devtools thread when available.
2462    pub(crate) fn devtools_chan(&self) -> Option<&GenericCallback<ScriptToDevtoolsControlMsg>> {
2463        self.devtools_chan.as_ref()
2464    }
2465
2466    /// Get a sender to the memory profiler thread.
2467    pub(crate) fn mem_profiler_chan(&self) -> &profile_mem::ProfilerChan {
2468        &self.mem_profiler_chan
2469    }
2470
2471    /// Get a sender to the time profiler thread.
2472    pub(crate) fn time_profiler_chan(&self) -> &profile_time::ProfilerChan {
2473        &self.time_profiler_chan
2474    }
2475
2476    /// Get a sender to the constellation thread.
2477    pub(crate) fn script_to_constellation_chan(&self) -> &ScriptToConstellationChan {
2478        &self.script_to_constellation_chan
2479    }
2480
2481    pub(crate) fn script_to_embedder_chan(&self) -> &ScriptToEmbedderChan {
2482        &self.script_to_embedder_chan
2483    }
2484
2485    pub(crate) fn send_to_embedder(&self, msg: EmbedderMsg) {
2486        self.script_to_embedder_chan().send(msg).unwrap();
2487    }
2488
2489    /// Get the `PipelineId` for this global scope.
2490    pub(crate) fn pipeline_id(&self) -> PipelineId {
2491        self.pipeline_id
2492    }
2493
2494    /// Register interest in a notification category. Sends a `RegisterInterest`
2495    /// message to the constellation when the first listener is registered.
2496    pub(crate) fn register_interest(&self, interest: ConstellationInterest) {
2497        let mut counts = self.constellation_interest_counts.borrow_mut();
2498        let count = counts.entry(interest).or_insert(0);
2499        *count += 1;
2500        if *count == 1 {
2501            let _ = self
2502                .script_to_constellation_chan()
2503                .send(ScriptToConstellationMessage::RegisterInterest(interest));
2504        }
2505    }
2506
2507    /// Unregister interest in a notification category. Sends an `UnregisterInterest`
2508    /// message to the constellation when the last listener is removed.
2509    pub(crate) fn unregister_interest(&self, interest: ConstellationInterest) {
2510        let mut counts = self.constellation_interest_counts.borrow_mut();
2511        if let Some(count) = counts.get_mut(&interest) {
2512            *count = count.saturating_sub(1);
2513            if *count == 0 {
2514                counts.remove(&interest);
2515                let _ = self
2516                    .script_to_constellation_chan()
2517                    .send(ScriptToConstellationMessage::UnregisterInterest(interest));
2518            }
2519        }
2520    }
2521
2522    /// Get the origin for this global scope
2523    pub(crate) fn origin(&self) -> &MutableOrigin {
2524        &self.origin
2525    }
2526
2527    /// Get the creation_url for this global scope
2528    pub(crate) fn creation_url(&self) -> ServoUrl {
2529        self.creation_url.borrow().clone()
2530    }
2531
2532    pub(crate) fn set_creation_url(&self, creation_url: ServoUrl) {
2533        *self.creation_url.borrow_mut() = creation_url;
2534    }
2535
2536    /// Get the top_level_creation_url for this global scope
2537    pub(crate) fn top_level_creation_url(&self) -> &Option<ServoUrl> {
2538        &self.top_level_creation_url
2539    }
2540
2541    pub(crate) fn image_cache(&self) -> Arc<dyn ImageCache> {
2542        if let Some(window) = self.downcast::<Window>() {
2543            return window.image_cache();
2544        }
2545        if let Some(worker) = self.downcast::<DedicatedWorkerGlobalScope>() {
2546            return worker.image_cache();
2547        }
2548        if let Some(worker) = self.downcast::<PaintWorkletGlobalScope>() {
2549            return worker.image_cache();
2550        }
2551        unreachable!();
2552    }
2553
2554    /// Schedule a [`TimerEventRequest`] on this [`GlobalScope`]'s [`timers::TimerScheduler`].
2555    /// Every Worker has its own scheduler, which handles events in the Worker event loop,
2556    /// but `Window`s use a shared scheduler associated with their [`ScriptThread`].
2557    pub(crate) fn schedule_timer(&self, request: TimerEventRequest) -> Option<TimerId> {
2558        match self.downcast::<WorkerGlobalScope>() {
2559            Some(worker_global) => Some(worker_global.timer_scheduler().schedule_timer(request)),
2560            _ => with_script_thread(|script_thread| Some(script_thread.schedule_timer(request))),
2561        }
2562    }
2563
2564    /// <https://html.spec.whatwg.org/multipage/#nested-browsing-context>
2565    pub(crate) fn is_nested_browsing_context(&self) -> bool {
2566        self.downcast::<Window>()
2567            .is_some_and(|window| !window.is_top_level())
2568    }
2569
2570    /// Obtain the size of in flight keep alive records from the resource thread.
2571    /// If we can't communicate with the thread, we return u64::MAX to ensure
2572    /// the limit is higher than what is allowed. This ensures that whenever
2573    /// we want to initiate a keep alive request and the thread doesn't communicate,
2574    /// we block additional keep alive requests.
2575    pub(crate) fn total_size_of_in_flight_keep_alive_records(&self) -> u64 {
2576        let (sender, receiver) = generic_channel::channel().unwrap();
2577        if self
2578            .core_resource_thread()
2579            .send(CoreResourceMsg::TotalSizeOfInFlightKeepAliveRecords(
2580                self.pipeline_id(),
2581                sender,
2582            ))
2583            .is_err()
2584        {
2585            return u64::MAX;
2586        }
2587        receiver.recv().unwrap_or(u64::MAX)
2588    }
2589
2590    /// Part of <https://fetch.spec.whatwg.org/#populate-request-from-client>
2591    pub(crate) fn request_client(&self) -> RequestClient {
2592        // Step 1.2.2. If global is a Window object and global’s navigable is not null,
2593        // then set request’s traversable for user prompts to global’s navigable’s traversable navigable.
2594        let window = self.downcast::<Window>();
2595        let preloaded_resources = window
2596            .map(|window: &Window| window.Document().preloaded_resources().clone())
2597            .unwrap_or_default();
2598        let is_nested_browsing_context = window.is_some_and(|window| !window.is_top_level());
2599        RequestClient {
2600            preloaded_resources,
2601            policy_container: RequestPolicyContainer::PolicyContainer(self.policy_container()),
2602            origin: RequestOrigin::Origin(self.origin().immutable().clone()),
2603            is_nested_browsing_context,
2604            insecure_requests_policy: self.insecure_requests_policy(),
2605        }
2606    }
2607
2608    /// <https://html.spec.whatwg.org/multipage/#concept-settings-object-policy-container>
2609    pub(crate) fn policy_container(&self) -> PolicyContainer {
2610        if let Some(window) = self.downcast::<Window>() {
2611            return window.Document().policy_container().to_owned();
2612        }
2613        if let Some(worker) = self.downcast::<WorkerGlobalScope>() {
2614            return worker.policy_container().to_owned();
2615        }
2616        unreachable!();
2617    }
2618
2619    /// Get the [base url](https://html.spec.whatwg.org/multipage/#api-base-url)
2620    /// for this global scope.
2621    pub(crate) fn api_base_url(&self) -> ServoUrl {
2622        if let Some(window) = self.downcast::<Window>() {
2623            // https://html.spec.whatwg.org/multipage/#script-settings-for-browsing-contexts:api-base-url
2624            return window.Document().base_url();
2625        }
2626        if let Some(worker) = self.downcast::<WorkerGlobalScope>() {
2627            // https://html.spec.whatwg.org/multipage/#script-settings-for-workers:api-base-url
2628            return worker.get_url().clone();
2629        }
2630        if let Some(worklet) = self.downcast::<WorkletGlobalScope>() {
2631            // https://drafts.css-houdini.org/worklets/#script-settings-for-worklets
2632            return worklet.base_url();
2633        }
2634        if let Some(_debugger_global) = self.downcast::<DebuggerGlobalScope>() {
2635            return self.creation_url();
2636        }
2637        unreachable!();
2638    }
2639
2640    /// Get the URL for this global scope.
2641    pub(crate) fn get_url(&self) -> ServoUrl {
2642        if let Some(window) = self.downcast::<Window>() {
2643            return window.get_url();
2644        }
2645        if let Some(worker) = self.downcast::<WorkerGlobalScope>() {
2646            return worker.get_url().clone();
2647        }
2648        if let Some(worklet) = self.downcast::<WorkletGlobalScope>() {
2649            // TODO: is this the right URL to return?
2650            return worklet.base_url();
2651        }
2652        if let Some(_debugger_global) = self.downcast::<DebuggerGlobalScope>() {
2653            return self.creation_url();
2654        }
2655        unreachable!();
2656    }
2657
2658    /// Get the Referrer Policy for this global scope.
2659    pub(crate) fn get_referrer_policy(&self) -> ReferrerPolicy {
2660        if let Some(window) = self.downcast::<Window>() {
2661            let document = window.Document();
2662
2663            return document.get_referrer_policy();
2664        }
2665        if let Some(worker) = self.downcast::<WorkerGlobalScope>() {
2666            return worker.policy_container().get_referrer_policy();
2667        }
2668        unreachable!();
2669    }
2670
2671    /// Step 3."client" of <https://w3c.github.io/webappsec-referrer-policy/#determine-requests-referrer>
2672    /// Determine the Referrer for a request whose Referrer is "client"
2673    pub(crate) fn get_referrer(&self) -> Referrer {
2674        // Substep 3."client".2. If environment’s global object is a Window object, then
2675        if let Some(window) = self.downcast::<Window>() {
2676            // Substep 3."client".2.1. Let document be the associated Document of environment’s global object.
2677            let mut document = window.Document();
2678
2679            // Substep 3."client".2.2. If document’s origin is an opaque origin, return no referrer.
2680            if let ImmutableOrigin::Opaque(_) = document.origin().immutable() {
2681                return Referrer::NoReferrer;
2682            }
2683
2684            let mut url = document.url();
2685
2686            // Substep 3."client".2.3. While document is an iframe srcdoc document,
2687            // let document be document’s browsing context’s browsing context container’s node document.
2688            while url.as_str() == "about:srcdoc" {
2689                // Return early if we cannot get a parent document. This might happen if
2690                // this iframe was already removed from the parent page.
2691                let Some(parent_document) =
2692                    document.browsing_context().and_then(|browsing_context| {
2693                        browsing_context
2694                            .parent()
2695                            .and_then(|parent| parent.document())
2696                    })
2697                else {
2698                    return Referrer::NoReferrer;
2699                };
2700                document = parent_document;
2701                url = document.url();
2702            }
2703
2704            // Substep 3."client".2.4. Let referrerSource be document’s URL.
2705            Referrer::Client(url)
2706        } else {
2707            // Substep 3."client".3. Otherwise, let referrerSource be environment’s creation URL.
2708            Referrer::Client(self.creation_url())
2709        }
2710    }
2711
2712    /// Extract a `Window`, panic if the global object is not a `Window`.
2713    pub(crate) fn as_window(&self) -> &Window {
2714        self.downcast::<Window>().expect("expected a Window scope")
2715    }
2716
2717    /// Returns a policy that should be used for fetches initiated from this global.
2718    pub(crate) fn insecure_requests_policy(&self) -> InsecureRequestsPolicy {
2719        if let Some(window) = self.downcast::<Window>() {
2720            return window.Document().insecure_requests_policy();
2721        }
2722        if let Some(worker) = self.downcast::<WorkerGlobalScope>() {
2723            return worker.insecure_requests_policy();
2724        }
2725        debug!("unsupported global, defaulting insecure requests policy to DoNotUpgrade");
2726        InsecureRequestsPolicy::DoNotUpgrade
2727    }
2728
2729    /// Whether this document has ancestor navigables that are trustworthy
2730    pub(crate) fn has_trustworthy_ancestor_origin(&self) -> bool {
2731        self.downcast::<Window>()
2732            .is_some_and(|window| window.Document().has_trustworthy_ancestor_origin())
2733    }
2734
2735    // Whether this document has a trustworthy origin or has trustowrthy ancestor navigables
2736    pub(crate) fn has_trustworthy_ancestor_or_current_origin(&self) -> bool {
2737        self.downcast::<Window>().is_some_and(|window| {
2738            window
2739                .Document()
2740                .has_trustworthy_ancestor_or_current_origin()
2741        })
2742    }
2743
2744    /// <https://html.spec.whatwg.org/multipage/#report-an-exception>
2745    pub(crate) fn report_an_exception(&self, cx: SafeJSContext, error: HandleValue, can_gc: CanGc) {
2746        // Step 1. Let notHandled be true.
2747        //
2748        // Handled in `report_an_error`
2749
2750        // Step 2. Let errorInfo be the result of extracting error information from exception.
2751        // Step 3. Let script be a script found in an implementation-defined way, or null.
2752        // This should usually be the running script (most notably during run a classic script).
2753        // Step 4. If script is a classic script and script's muted errors is true, then set errorInfo[error] to null,
2754        // errorInfo[message] to "Script error.", errorInfo[filename] to the empty string,
2755        // errorInfo[lineno] to 0, and errorInfo[colno] to 0.
2756        let error_info = crate::dom::bindings::error::ErrorInfo::from_value(error, cx, can_gc);
2757        // Step 5. If omitError is true, then set errorInfo[error] to null.
2758        //
2759        // `omitError` defaults to `false`
2760
2761        // Steps 6-7
2762        self.report_an_error(error_info, error, can_gc);
2763    }
2764
2765    /// Steps 6-7 of <https://html.spec.whatwg.org/multipage/#report-an-exception>
2766    pub(crate) fn report_an_error(&self, error_info: ErrorInfo, value: HandleValue, can_gc: CanGc) {
2767        #[cfg(feature = "js_backtrace")]
2768        LAST_EXCEPTION_BACKTRACE.with(|backtrace| {
2769            if let Some((js_backtrace, rust_backtrace)) = backtrace.borrow_mut().take() {
2770                if let Some(stack) = js_backtrace {
2771                    error!("JS backtrace:\n{}", stack);
2772                }
2773                error!("Rust backtrace:\n{}", rust_backtrace);
2774            }
2775        });
2776
2777        // Step 6. Early return if global is in error reporting mode,
2778        if self.in_error_reporting_mode.get() {
2779            return;
2780        }
2781
2782        // Step 6.1. Set global's in error reporting mode to true.
2783        self.in_error_reporting_mode.set(true);
2784
2785        // Step 6.2. Set notHandled to the result of firing an event named error at global,
2786        // using ErrorEvent, with the cancelable attribute initialized to true,
2787        // and additional attributes initialized according to errorInfo.
2788
2789        // FIXME(#13195): muted errors.
2790        let event = ErrorEvent::new(
2791            self,
2792            atom!("error"),
2793            EventBubbles::DoesNotBubble,
2794            EventCancelable::Cancelable,
2795            error_info.message.as_str().into(),
2796            error_info.filename.as_str().into(),
2797            error_info.lineno,
2798            error_info.column,
2799            value,
2800            can_gc,
2801        );
2802
2803        let not_handled = event
2804            .upcast::<Event>()
2805            .fire(self.upcast::<EventTarget>(), can_gc);
2806
2807        // Step 6.3. Set global's in error reporting mode to false.
2808        self.in_error_reporting_mode.set(false);
2809
2810        // Step 7. If notHandled is true, then:
2811        if not_handled {
2812            // Step 7.2. If global implements DedicatedWorkerGlobalScope,
2813            // queue a global task on the DOM manipulation task source with the
2814            // global's associated Worker's relevant global object to run these steps:
2815            //
2816            // https://html.spec.whatwg.org/multipage/#runtime-script-errors-2
2817            if let Some(dedicated) = self.downcast::<DedicatedWorkerGlobalScope>() {
2818                dedicated.forward_error_to_worker_object(error_info);
2819            } else if self.is::<Window>() {
2820                // Step 7.3. Otherwise, the user agent may report exception to a developer console.
2821                if let Some(ref chan) = self.devtools_chan {
2822                    let _ = chan.send(ScriptToDevtoolsControlMsg::ReportPageError(
2823                        self.pipeline_id,
2824                        PageError {
2825                            error_message: error_info.message.clone(),
2826                            source_name: error_info.filename.clone(),
2827                            line_number: error_info.lineno,
2828                            column_number: error_info.column,
2829                            time_stamp: get_time_stamp(),
2830                        },
2831                    ));
2832                }
2833                self.send_to_embedder(EmbedderMsg::ShowConsoleApiMessage(
2834                    self.webview_id(),
2835                    ConsoleLogLevel::Error,
2836                    format!(
2837                        "Error at {}:{}:{} {}",
2838                        error_info.filename,
2839                        error_info.lineno,
2840                        error_info.column,
2841                        error_info.message
2842                    ),
2843                ));
2844            }
2845        }
2846    }
2847
2848    /// Get the `&ResourceThreads` for this global scope.
2849    pub(crate) fn resource_threads(&self) -> &ResourceThreads {
2850        &self.resource_threads
2851    }
2852
2853    /// Get the `CoreResourceThread` for this global scope.
2854    pub(crate) fn core_resource_thread(&self) -> CoreResourceThread {
2855        self.resource_threads().sender()
2856    }
2857
2858    /// Get a reference to the [`StorageThreads`] for this [`GlobalScope`].
2859    pub(crate) fn storage_threads(&self) -> &StorageThreads {
2860        &self.storage_threads
2861    }
2862
2863    /// A sender to the event loop of this global scope. This either sends to the Worker event loop
2864    /// or the ScriptThread event loop in the case of a `Window`. This can be `None` for dedicated
2865    /// workers that are not currently handling a message.
2866    pub(crate) fn event_loop_sender(&self) -> Option<ScriptEventLoopSender> {
2867        if let Some(window) = self.downcast::<Window>() {
2868            Some(window.event_loop_sender())
2869        } else if let Some(dedicated) = self.downcast::<DedicatedWorkerGlobalScope>() {
2870            dedicated.event_loop_sender()
2871        } else if let Some(shared_worker) = self.downcast::<SharedWorkerGlobalScope>() {
2872            Some(shared_worker.event_loop_sender())
2873        } else if let Some(service_worker) = self.downcast::<ServiceWorkerGlobalScope>() {
2874            Some(service_worker.event_loop_sender())
2875        } else {
2876            unreachable!(
2877                "Tried to access event loop sender for incompatible \
2878                 GlobalScope (PaintWorklet or DissimilarOriginWindow)"
2879            );
2880        }
2881    }
2882
2883    /// A reference to the [`TaskManager`] used to schedule tasks for this [`GlobalScope`].
2884    pub(crate) fn task_manager(&self) -> &TaskManager {
2885        let shared_canceller = self
2886            .downcast::<WorkerGlobalScope>()
2887            .map(WorkerGlobalScope::shared_task_canceller);
2888        self.task_manager.get_or_init(|| {
2889            TaskManager::new(
2890                self.event_loop_sender(),
2891                self.pipeline_id(),
2892                shared_canceller,
2893            )
2894        })
2895    }
2896
2897    /// Evaluate JS code on this global scope.
2898    pub(crate) fn evaluate_js_on_global(
2899        &self,
2900        cx: &mut CurrentRealm,
2901        code: Cow<'_, str>,
2902        filename: &str,
2903        introduction_type: Option<&'static CStr>,
2904        rval: Option<MutableHandleValue>,
2905    ) -> Result<(), JavaScriptEvaluationError> {
2906        let in_realm_proof = cx.into();
2907        let in_realm = InRealm::Already(&in_realm_proof);
2908
2909        run_a_script::<DomTypeHolder, _>(self, || {
2910            let url = self.api_base_url();
2911            let fetch_options = ScriptFetchOptions::default_classic_script();
2912
2913            let no_script_rval = rval.is_none();
2914
2915            rooted!(&in(cx) let mut compiled_script = std::ptr::null_mut::<JSScript>());
2916            compiled_script.set(compile_script(
2917                cx,
2918                &code,
2919                filename,
2920                1,
2921                introduction_type,
2922                ErrorReporting::Unmuted,
2923                no_script_rval,
2924            ));
2925
2926            let Some(script) = NonNull::new(*compiled_script) else {
2927                debug!("error compiling Dom string");
2928                report_pending_exception(cx.into(), in_realm, CanGc::from_cx(cx));
2929                return Err(JavaScriptEvaluationError::CompilationFailure);
2930            };
2931
2932            rooted!(&in(cx) let mut value = UndefinedValue());
2933            let rval = rval.unwrap_or_else(|| value.handle_mut());
2934
2935            if !evaluate_script(cx, script, url, fetch_options, rval) {
2936                let error_info = take_and_report_pending_exception_for_api(cx);
2937                return Err(JavaScriptEvaluationError::EvaluationFailure(error_info));
2938            }
2939
2940            maybe_resume_unwind();
2941            Ok(())
2942        })
2943    }
2944
2945    /// <https://html.spec.whatwg.org/multipage/#timer-initialisation-steps>
2946    pub(crate) fn schedule_callback(
2947        &self,
2948        callback: OneshotTimerCallback,
2949        duration: Duration,
2950    ) -> OneshotTimerHandle {
2951        self.timers()
2952            .schedule_callback(callback, duration, self.timer_source())
2953    }
2954
2955    pub(crate) fn unschedule_callback(&self, handle: OneshotTimerHandle) {
2956        self.timers().unschedule_callback(handle);
2957    }
2958
2959    /// <https://html.spec.whatwg.org/multipage/#timer-initialisation-steps>
2960    pub(crate) fn set_timeout_or_interval(
2961        &self,
2962        cx: &mut js::context::JSContext,
2963        callback: TimerCallback,
2964        arguments: Vec<HandleValue>,
2965        timeout: Duration,
2966        is_interval: IsInterval,
2967    ) -> Fallible<i32> {
2968        self.timers().set_timeout_or_interval(
2969            cx,
2970            self,
2971            callback,
2972            arguments,
2973            timeout,
2974            is_interval,
2975            self.timer_source(),
2976        )
2977    }
2978
2979    pub(crate) fn clear_timeout_or_interval(&self, handle: i32) {
2980        self.timers().clear_timeout_or_interval(self, handle);
2981    }
2982
2983    pub(crate) fn fire_timer(&self, handle: TimerEventId, cx: &mut js::context::JSContext) {
2984        self.timers().fire_timer(handle, self, cx);
2985    }
2986
2987    pub(crate) fn resume(&self) {
2988        self.timers().resume();
2989    }
2990
2991    pub(crate) fn suspend(&self) {
2992        self.timers().suspend();
2993    }
2994
2995    pub(crate) fn slow_down_timers(&self) {
2996        self.timers().slow_down();
2997    }
2998
2999    pub(crate) fn speed_up_timers(&self) {
3000        self.timers().speed_up();
3001    }
3002
3003    fn timer_source(&self) -> TimerSource {
3004        if self.is::<Window>() {
3005            return TimerSource::FromWindow(self.pipeline_id());
3006        }
3007        if self.is::<WorkerGlobalScope>() {
3008            return TimerSource::FromWorker;
3009        }
3010        unreachable!();
3011    }
3012
3013    /// Returns a boolean indicating whether the event-loop
3014    /// where this global is running on can continue running JS.
3015    pub(crate) fn can_continue_running(&self) -> bool {
3016        if self.is::<Window>() {
3017            return ScriptThread::can_continue_running();
3018        }
3019        if let Some(worker) = self.downcast::<WorkerGlobalScope>() {
3020            return !worker.is_closing();
3021        }
3022
3023        // TODO: plug worklets into this.
3024        true
3025    }
3026
3027    /// Returns the idb factory for this global.
3028    pub(crate) fn get_indexeddb(&self) -> DomRoot<IDBFactory> {
3029        self.indexeddb
3030            .or_init(|| IDBFactory::new(self, CanGc::deprecated_note()))
3031    }
3032
3033    pub(crate) fn get_existing_indexeddb(&self) -> Option<DomRoot<IDBFactory>> {
3034        self.indexeddb.get()
3035    }
3036
3037    /// Perform a microtask checkpoint.
3038    pub(crate) fn perform_a_microtask_checkpoint(&self, cx: &mut js::context::JSContext) {
3039        if let Some(window) = self.downcast::<Window>() {
3040            window.perform_a_microtask_checkpoint(cx);
3041        } else if let Some(worker) = self.downcast::<WorkerGlobalScope>() {
3042            worker.perform_a_microtask_checkpoint(cx);
3043        }
3044    }
3045
3046    /// Enqueue a microtask for subsequent execution.
3047    pub(crate) fn enqueue_microtask(&self, job: Microtask) {
3048        if self.is::<Window>() {
3049            ScriptThread::enqueue_microtask(job);
3050        } else if let Some(worker) = self.downcast::<WorkerGlobalScope>() {
3051            worker.enqueue_microtask(job);
3052        }
3053    }
3054
3055    /// Create a new sender/receiver pair that can be used to implement an on-demand
3056    /// event loop. Used for implementing web APIs that require blocking semantics
3057    /// without resorting to nested event loops.
3058    pub(crate) fn new_script_pair(&self) -> (ScriptEventLoopSender, ScriptEventLoopReceiver) {
3059        if let Some(window) = self.downcast::<Window>() {
3060            return window.new_script_pair();
3061        }
3062        if let Some(worker) = self.downcast::<WorkerGlobalScope>() {
3063            return worker.new_script_pair();
3064        }
3065        unreachable!();
3066    }
3067
3068    /// Process a single event as if it were the next event
3069    /// in the queue for the event-loop where this global scope is running on.
3070    /// Returns a boolean indicating whether further events should be processed.
3071    pub(crate) fn process_event(
3072        &self,
3073        msg: CommonScriptMsg,
3074        cx: &mut js::context::JSContext,
3075    ) -> bool {
3076        if self.is::<Window>() {
3077            return ScriptThread::process_event(msg, cx);
3078        }
3079        if let Some(worker) = self.downcast::<WorkerGlobalScope>() {
3080            return worker.process_event(msg, cx);
3081        }
3082        unreachable!();
3083    }
3084
3085    pub(crate) fn runtime_handle(&self) -> ParentRuntime {
3086        if self.is::<Window>() {
3087            ScriptThread::runtime_handle()
3088        } else if let Some(worker) = self.downcast::<WorkerGlobalScope>() {
3089            worker.runtime_handle()
3090        } else {
3091            unreachable!()
3092        }
3093    }
3094
3095    /// Returns the ["current"] global object.
3096    ///
3097    /// ["current"]: https://html.spec.whatwg.org/multipage/#current
3098    #[expect(unsafe_code)]
3099    pub(crate) fn current() -> Option<DomRoot<Self>> {
3100        let cx = Runtime::get()?;
3101        unsafe {
3102            let global = CurrentGlobalOrNull(cx.as_ptr());
3103            if global.is_null() {
3104                None
3105            } else {
3106                Some(global_scope_from_global(global, cx.as_ptr()))
3107            }
3108        }
3109    }
3110
3111    /// Returns the ["entry"] global object.
3112    ///
3113    /// ["entry"]: https://html.spec.whatwg.org/multipage/#entry
3114    pub(crate) fn entry() -> DomRoot<Self> {
3115        entry_global()
3116    }
3117
3118    /// Returns the ["incumbent"] global object.
3119    ///
3120    /// ["incumbent"]: https://html.spec.whatwg.org/multipage/#incumbent
3121    pub(crate) fn incumbent() -> Option<DomRoot<Self>> {
3122        incumbent_global()
3123    }
3124
3125    pub(crate) fn performance(&self) -> DomRoot<Performance> {
3126        if let Some(window) = self.downcast::<Window>() {
3127            return window.Performance();
3128        }
3129        if let Some(worker) = self.downcast::<WorkerGlobalScope>() {
3130            return worker.Performance();
3131        }
3132        unreachable!();
3133    }
3134
3135    /// <https://w3c.github.io/performance-timeline/#supportedentrytypes-attribute>
3136    pub(crate) fn supported_performance_entry_types(
3137        &self,
3138        cx: SafeJSContext,
3139        retval: MutableHandleValue,
3140        can_gc: CanGc,
3141    ) {
3142        self.frozen_supported_performance_entry_types.get_or_init(
3143            || {
3144                EntryType::VARIANTS
3145                    .iter()
3146                    .map(|t| DOMString::from(t.as_str()))
3147                    .collect()
3148            },
3149            cx,
3150            retval,
3151            can_gc,
3152        );
3153    }
3154
3155    pub(crate) fn get_https_state(&self) -> HttpsState {
3156        self.https_state.get()
3157    }
3158
3159    pub(crate) fn set_https_state(&self, https_state: HttpsState) {
3160        self.https_state.set(https_state);
3161    }
3162
3163    pub(crate) fn inherited_secure_context(&self) -> Option<bool> {
3164        self.inherited_secure_context
3165    }
3166
3167    /// <https://html.spec.whatwg.org/multipage/#secure-context>
3168    pub(crate) fn is_secure_context(&self) -> bool {
3169        // This differs from the specification, but it seems that
3170        // `inherited_secure_context` implements more-or-less the exact same logic, in a
3171        // different manner. Workers inherit whether or not their in a secure context and
3172        // worklets do as well (they can only be created in secure contexts).
3173        if Some(false) == self.inherited_secure_context {
3174            return false;
3175        }
3176        // Step 1. If environment is an environment settings object, then:
3177        // Step 1.1. Let global be environment's global object.
3178        match self.top_level_creation_url() {
3179            None => {
3180                // Workers and worklets don't have a top-level creation URL
3181                assert!(
3182                    self.downcast::<WorkerGlobalScope>().is_some() ||
3183                        self.downcast::<WorkletGlobalScope>().is_some()
3184                );
3185                true
3186            },
3187            Some(top_level_creation_url) => {
3188                assert!(self.downcast::<Window>().is_some());
3189                // Step 2. If the result of Is url potentially trustworthy?
3190                // given environment's top-level creation URL is "Potentially Trustworthy", then return true.
3191                // Step 3. Return false.
3192                if top_level_creation_url.scheme() == "blob" &&
3193                    Some(true) == self.inherited_secure_context
3194                {
3195                    return true;
3196                }
3197                top_level_creation_url.is_potentially_trustworthy()
3198            },
3199        }
3200    }
3201
3202    /// <https://www.w3.org/TR/CSP/#get-csp-of-object>
3203    pub(crate) fn get_csp_list(&self) -> Option<CspList> {
3204        if self.downcast::<Window>().is_some() || self.downcast::<WorkerGlobalScope>().is_some() {
3205            return self.policy_container().csp_list;
3206        }
3207        // TODO: Worklet global scopes.
3208        None
3209    }
3210
3211    pub(crate) fn status_code(&self) -> Option<u16> {
3212        if let Some(window) = self.downcast::<Window>() {
3213            return window.Document().status_code();
3214        }
3215        None
3216    }
3217
3218    #[cfg(feature = "webgpu")]
3219    pub(crate) fn wgpu_id_hub(&self) -> Arc<IdentityHub> {
3220        self.gpu_id_hub.clone()
3221    }
3222
3223    #[cfg(feature = "webgpu")]
3224    pub(crate) fn add_gpu_device(&self, device: &GPUDevice) {
3225        self.gpu_devices
3226            .borrow_mut()
3227            .insert(device.id(), WeakRef::new(device));
3228    }
3229
3230    #[cfg(feature = "webgpu")]
3231    pub(crate) fn remove_gpu_device(&self, device: WebGPUDevice) {
3232        let device = self
3233            .gpu_devices
3234            .borrow_mut()
3235            .remove(&device)
3236            .expect("GPUDevice should still be in devices hashmap");
3237        assert!(device.root().is_none())
3238    }
3239
3240    #[cfg(feature = "webgpu")]
3241    pub(crate) fn gpu_device_lost(
3242        &self,
3243        device: WebGPUDevice,
3244        reason: DeviceLostReason,
3245        msg: String,
3246    ) {
3247        let reason = match reason {
3248            DeviceLostReason::Unknown => GPUDeviceLostReason::Unknown,
3249            DeviceLostReason::Destroyed => GPUDeviceLostReason::Destroyed,
3250        };
3251        let _ac = crate::realms::enter_realm(self);
3252        if let Some(device) = self
3253            .gpu_devices
3254            .borrow_mut()
3255            .get_mut(&device)
3256            .expect("GPUDevice should still be in devices hashmap")
3257            .root()
3258        {
3259            device.lose(reason, msg);
3260        }
3261    }
3262
3263    #[cfg(feature = "webgpu")]
3264    pub(crate) fn handle_uncaptured_gpu_error(
3265        &self,
3266        device: WebGPUDevice,
3267        error: webgpu_traits::Error,
3268    ) {
3269        if let Some(gpu_device) = self
3270            .gpu_devices
3271            .borrow()
3272            .get(&device)
3273            .and_then(|device| device.root())
3274        {
3275            gpu_device.fire_uncaptured_error(error);
3276        } else {
3277            warn!("Recived error for lost GPUDevice!")
3278        }
3279    }
3280
3281    pub(crate) fn current_group_label(&self) -> Option<DOMString> {
3282        self.console_group_stack
3283            .borrow()
3284            .last()
3285            .map(|label| DOMString::from(format!("[{}]", label)))
3286    }
3287
3288    pub(crate) fn push_console_group(&self, group: DOMString) {
3289        self.console_group_stack.borrow_mut().push(group);
3290    }
3291
3292    pub(crate) fn pop_console_group(&self) {
3293        let _ = self.console_group_stack.borrow_mut().pop();
3294    }
3295
3296    pub(crate) fn increment_console_count(&self, label: &DOMString) -> usize {
3297        *self
3298            .console_count_map
3299            .borrow_mut()
3300            .entry(label.clone())
3301            .and_modify(|e| *e += 1)
3302            .or_insert(1)
3303    }
3304
3305    pub(crate) fn reset_console_count(&self, label: &DOMString) -> Result<(), ()> {
3306        match self.console_count_map.borrow_mut().get_mut(label) {
3307            Some(value) => {
3308                *value = 0;
3309                Ok(())
3310            },
3311            None => Err(()),
3312        }
3313    }
3314
3315    pub(crate) fn structured_clone(
3316        &self,
3317        cx: &mut js::context::JSContext,
3318        value: HandleValue,
3319        options: RootedTraceableBox<StructuredSerializeOptions>,
3320        retval: MutableHandleValue,
3321    ) -> Fallible<()> {
3322        let mut rooted = CustomAutoRooter::new(
3323            options
3324                .transfer
3325                .iter()
3326                .map(|js: &RootedTraceableBox<Heap<*mut JSObject>>| js.get())
3327                .collect(),
3328        );
3329
3330        #[expect(unsafe_code)]
3331        let guard = unsafe { CustomAutoRooterGuard::new(cx.raw_cx(), &mut rooted) };
3332
3333        let data = structuredclone::write(cx.into(), value, Some(guard))?;
3334
3335        structuredclone::read(self, data, retval, CanGc::from_cx(cx))?;
3336
3337        Ok(())
3338    }
3339
3340    pub(crate) fn fetch<Listener: FetchResponseListener>(
3341        &self,
3342        request_builder: RequestBuilder,
3343        context: Listener,
3344        task_source: SendableTaskSource,
3345    ) {
3346        let network_listener = NetworkListener::new(context, task_source);
3347        self.fetch_with_network_listener(request_builder, network_listener);
3348    }
3349
3350    pub(crate) fn fetch_with_network_listener<Listener: FetchResponseListener>(
3351        &self,
3352        request_builder: RequestBuilder,
3353        network_listener: NetworkListener<Listener>,
3354    ) {
3355        fetch_async(
3356            &self.core_resource_thread(),
3357            request_builder,
3358            None,
3359            network_listener.into_callback(),
3360        );
3361    }
3362
3363    pub(crate) fn unminify_js(&self) -> bool {
3364        self.unminified_js_dir.is_some()
3365    }
3366
3367    pub(crate) fn unminified_js_dir(&self) -> Option<String> {
3368        self.unminified_js_dir.clone()
3369    }
3370
3371    pub(crate) fn set_byte_length_queuing_strategy_size(&self, function: Rc<Function>) {
3372        if self
3373            .byte_length_queuing_strategy_size_function
3374            .set(function)
3375            .is_err()
3376        {
3377            warn!("byte length queuing strategy size function is set twice.");
3378        };
3379    }
3380
3381    pub(crate) fn get_byte_length_queuing_strategy_size(&self) -> Option<Rc<Function>> {
3382        self.byte_length_queuing_strategy_size_function
3383            .get()
3384            .cloned()
3385    }
3386
3387    pub(crate) fn set_count_queuing_strategy_size(&self, function: Rc<Function>) {
3388        if self
3389            .count_queuing_strategy_size_function
3390            .set(function)
3391            .is_err()
3392        {
3393            warn!("count queuing strategy size function is set twice.");
3394        };
3395    }
3396
3397    pub(crate) fn get_count_queuing_strategy_size(&self) -> Option<Rc<Function>> {
3398        self.count_queuing_strategy_size_function.get().cloned()
3399    }
3400
3401    pub(crate) fn add_notification_permission_request_callback(
3402        &self,
3403        callback_id: String,
3404        callback: Rc<NotificationPermissionCallback>,
3405    ) {
3406        self.notification_permission_request_callback_map
3407            .borrow_mut()
3408            .insert(callback_id, callback);
3409    }
3410
3411    pub(crate) fn remove_notification_permission_request_callback(
3412        &self,
3413        callback_id: String,
3414    ) -> Option<Rc<NotificationPermissionCallback>> {
3415        self.notification_permission_request_callback_map
3416            .borrow_mut()
3417            .remove(&callback_id)
3418    }
3419
3420    pub(crate) fn append_deferred_fetch(
3421        &self,
3422        deferred_fetch: QueuedDeferredFetchRecord,
3423    ) -> DeferredFetchRecordId {
3424        let deferred_record_id = DeferredFetchRecordId::default();
3425        self.fetch_group
3426            .borrow_mut()
3427            .deferred_fetch_records
3428            .insert(deferred_record_id, deferred_fetch);
3429        deferred_record_id
3430    }
3431
3432    pub(crate) fn deferred_fetches(&self) -> Vec<QueuedDeferredFetchRecord> {
3433        self.fetch_group
3434            .borrow()
3435            .deferred_fetch_records
3436            .values()
3437            .cloned()
3438            .collect()
3439    }
3440
3441    pub(crate) fn deferred_fetch_record_for_id(
3442        &self,
3443        deferred_fetch_record_id: &DeferredFetchRecordId,
3444    ) -> QueuedDeferredFetchRecord {
3445        self.fetch_group
3446            .borrow()
3447            .deferred_fetch_records
3448            .get(deferred_fetch_record_id)
3449            .expect("Should always use a generated fetch_record_id instead of passing your own")
3450            .clone()
3451    }
3452
3453    /// <https://fetch.spec.whatwg.org/#process-deferred-fetches>
3454    pub(crate) fn process_deferred_fetches(&self) {
3455        // Step 1. For each deferred fetch record deferredRecord of fetchGroup’s
3456        // deferred fetch records, process a deferred fetch deferredRecord.
3457        for deferred_fetch in self.deferred_fetches() {
3458            deferred_fetch.process(self);
3459        }
3460    }
3461
3462    pub(crate) fn import_map(&self) -> Ref<'_, ImportMap> {
3463        self.import_map.borrow()
3464    }
3465
3466    pub(crate) fn import_map_mut(&self) -> RefMut<'_, ImportMap> {
3467        self.import_map.borrow_mut()
3468    }
3469
3470    pub(crate) fn resolved_module_set(&self) -> Ref<'_, HashSet<ResolvedModule>> {
3471        self.resolved_module_set.borrow()
3472    }
3473
3474    /// <https://html.spec.whatwg.org/multipage/#add-module-to-resolved-module-set>
3475    pub(crate) fn add_module_to_resolved_module_set(
3476        &self,
3477        base_url: &str,
3478        specifier: &str,
3479        specifier_url: Option<ServoUrl>,
3480    ) {
3481        // Step 1. Let global be settingsObject's global object.
3482        // Step 2. If global does not implement Window, then return.
3483        if self.is::<Window>() {
3484            // Step 3. Let record be a new specifier resolution record, with serialized base URL
3485            // set to serializedBaseURL, specifier set to normalizedSpecifier, and specifier as
3486            // a URL set to asURL.
3487            let record =
3488                ResolvedModule::new(base_url.to_owned(), specifier.to_owned(), specifier_url);
3489            // Step 4. Append record to global's resolved module set.
3490            self.resolved_module_set.borrow_mut().insert(record);
3491        }
3492    }
3493
3494    /// <https://html.spec.whatwg.org/multipage/#run-steps-after-a-timeout>
3495    /// TODO: This should end-up being used in the other timer mechanism
3496    /// integrate as per <https://html.spec.whatwg.org/multipage/#timers:run-steps-after-a-timeout?
3497    pub(crate) fn run_steps_after_a_timeout<F>(
3498        &self,
3499        ordering_identifier: DOMString,
3500        milliseconds: i64,
3501        completion_steps: F,
3502    ) -> i32
3503    where
3504        F: 'static + FnOnce(&mut js::context::JSContext, &GlobalScope),
3505    {
3506        let timers = self.timers();
3507
3508        // Step 1. Let timerKey be a new unique internal value.
3509        let timer_key = timers.fresh_runsteps_key();
3510
3511        // Step 2. Let startTime be the current high resolution time given global.
3512        let start_time = timers.now_for_runsteps();
3513
3514        // Step 3. Set global's map of active timers[timerKey] to startTime plus milliseconds.
3515        let ms = milliseconds.max(0) as u64;
3516        let delay = std::time::Duration::from_millis(ms);
3517        let deadline = start_time + delay;
3518        timers.runsteps_set_active(timer_key, deadline);
3519
3520        // Step 4. Run the following steps in parallel:
3521        //   (We schedule a oneshot that will enforce the sub-steps when it fires.)
3522        let callback = crate::timers::OneshotTimerCallback::RunStepsAfterTimeout {
3523            // Step 1. timerKey
3524            timer_key,
3525            // Step 4. orderingIdentifier
3526            ordering_id: ordering_identifier,
3527            // Spec: milliseconds
3528            milliseconds: ms,
3529            // Step 4.4 Perform completionSteps.
3530            completion: Box::new(completion_steps),
3531        };
3532        let _ = self.schedule_callback(callback, delay);
3533
3534        // Step 5. Return timerKey.
3535        timer_key
3536    }
3537}
3538
3539/// Returns the Rust global scope from a JS global object.
3540#[expect(unsafe_code)]
3541unsafe fn global_scope_from_global(
3542    global: *mut JSObject,
3543    cx: *mut JSContext,
3544) -> DomRoot<GlobalScope> {
3545    unsafe {
3546        assert!(!global.is_null());
3547        let clasp = get_object_class(global);
3548        assert_ne!(
3549            ((*clasp).flags & (JSCLASS_IS_DOMJSCLASS | JSCLASS_IS_GLOBAL)),
3550            0
3551        );
3552        root_from_object(global, cx).unwrap()
3553    }
3554}
3555
3556/// Returns the Rust global scope from a JS global object.
3557#[expect(unsafe_code)]
3558unsafe fn global_scope_from_global_static(global: *mut JSObject) -> DomRoot<GlobalScope> {
3559    assert!(!global.is_null());
3560    let clasp = unsafe { get_object_class(global) };
3561
3562    unsafe {
3563        assert_ne!(
3564            ((*clasp).flags & (JSCLASS_IS_DOMJSCLASS | JSCLASS_IS_GLOBAL)),
3565            0
3566        );
3567    }
3568
3569    root_from_object_static(global).unwrap()
3570}
3571
3572#[expect(unsafe_code)]
3573impl GlobalScopeHelpers<crate::DomTypeHolder> for GlobalScope {
3574    unsafe fn from_context(cx: *mut JSContext, realm: InRealm) -> DomRoot<Self> {
3575        unsafe { GlobalScope::from_context(cx, realm) }
3576    }
3577
3578    fn from_current_realm(realm: &'_ CurrentRealm) -> DomRoot<Self> {
3579        GlobalScope::from_current_realm(realm)
3580    }
3581
3582    fn get_cx() -> SafeJSContext {
3583        GlobalScope::get_cx()
3584    }
3585
3586    unsafe fn from_object(obj: *mut JSObject) -> DomRoot<Self> {
3587        unsafe { GlobalScope::from_object(obj) }
3588    }
3589
3590    fn from_reflector(reflector: &impl DomObject, realm: InRealm) -> DomRoot<Self> {
3591        GlobalScope::from_reflector(reflector, realm)
3592    }
3593
3594    fn origin(&self) -> &MutableOrigin {
3595        GlobalScope::origin(self)
3596    }
3597
3598    fn incumbent() -> Option<DomRoot<Self>> {
3599        GlobalScope::incumbent()
3600    }
3601
3602    fn perform_a_microtask_checkpoint(&self, cx: &mut js::context::JSContext) {
3603        GlobalScope::perform_a_microtask_checkpoint(self, cx)
3604    }
3605
3606    fn get_url(&self) -> ServoUrl {
3607        self.get_url()
3608    }
3609
3610    fn is_secure_context(&self) -> bool {
3611        self.is_secure_context()
3612    }
3613}