script/
script_thread.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
5//! The script thread is the thread that owns the DOM in memory, runs JavaScript, and triggers
6//! layout. It's in charge of processing events for all same-origin pages in a frame
7//! tree, and manages the entire lifetime of pages in the frame tree from initial request to
8//! teardown.
9//!
10//! Page loads follow a two-step process. When a request for a new page load is received, the
11//! network request is initiated and the relevant data pertaining to the new page is stashed.
12//! While the non-blocking request is ongoing, the script thread is free to process further events,
13//! noting when they pertain to ongoing loads (such as resizes/viewport adjustments). When the
14//! initial response is received for an ongoing load, the second phase starts - the frame tree
15//! entry is created, along with the Window and Document objects, and the appropriate parser
16//! takes over the response body. Once parsing is complete, the document lifecycle for loading
17//! a page runs its course and the script thread returns to processing events in the main event
18//! loop.
19
20use std::cell::{Cell, RefCell};
21use std::collections::HashSet;
22use std::default::Default;
23use std::option::Option;
24use std::rc::{Rc, Weak};
25use std::result::Result;
26use std::sync::Arc;
27use std::sync::atomic::{AtomicBool, Ordering};
28use std::thread::{self, JoinHandle};
29use std::time::{Duration, Instant, SystemTime};
30
31use background_hang_monitor_api::{
32    BackgroundHangMonitor, BackgroundHangMonitorExitSignal, BackgroundHangMonitorRegister,
33    HangAnnotation, MonitoredComponentId, MonitoredComponentType,
34};
35use base::cross_process_instant::CrossProcessInstant;
36use base::generic_channel;
37use base::id::{
38    BrowsingContextId, HistoryStateId, PipelineId, PipelineNamespace, ScriptEventLoopId,
39    TEST_WEBVIEW_ID, WebViewId,
40};
41use canvas_traits::webgl::WebGLPipeline;
42use chrono::{DateTime, Local};
43use constellation_traits::{
44    JsEvalResult, LoadData, LoadOrigin, NavigationHistoryBehavior, ScreenshotReadinessResponse,
45    ScriptToConstellationChan, ScriptToConstellationMessage, StructuredSerializedData,
46    WindowSizeType,
47};
48use crossbeam_channel::unbounded;
49use data_url::mime::Mime;
50use devtools_traits::{
51    CSSError, DevtoolScriptControlMsg, DevtoolsPageInfo, NavigationState,
52    ScriptToDevtoolsControlMsg, WorkerId,
53};
54use embedder_traits::user_contents::{UserContentManagerId, UserContents, UserScript};
55use embedder_traits::{
56    EmbedderControlId, EmbedderControlResponse, EmbedderMsg, FocusSequenceNumber,
57    JavaScriptEvaluationError, JavaScriptEvaluationId, MediaSessionActionType, Theme,
58    ViewportDetails, WebDriverScriptCommand,
59};
60use encoding_rs::Encoding;
61use fonts::{FontContext, SystemFontServiceProxy};
62use headers::{HeaderMapExt, LastModified, ReferrerPolicy as ReferrerPolicyHeader};
63use http::header::REFRESH;
64use hyper_serde::Serde;
65use ipc_channel::ipc;
66use ipc_channel::router::ROUTER;
67use js::glue::GetWindowProxyClass;
68use js::jsapi::JSContext as UnsafeJSContext;
69use js::jsval::UndefinedValue;
70use js::rust::ParentRuntime;
71use js::rust::wrappers2::{JS_AddInterruptCallback, SetWindowProxyClass};
72use layout_api::{LayoutConfig, LayoutFactory, RestyleReason, ScriptThreadFactory};
73use media::WindowGLContext;
74use metrics::MAX_TASK_NS;
75use net_traits::image_cache::{ImageCache, ImageCacheFactory, ImageCacheResponseMessage};
76use net_traits::request::{Referrer, RequestId};
77use net_traits::response::ResponseInit;
78use net_traits::{
79    FetchMetadata, FetchResponseMsg, Metadata, NetworkError, ResourceFetchTiming, ResourceThreads,
80    ResourceTimingType,
81};
82use paint_api::{CrossProcessPaintApi, PinchZoomInfos, PipelineExitSource};
83use percent_encoding::percent_decode;
84use profile_traits::mem::{ProcessReports, ReportsChan, perform_memory_report};
85use profile_traits::time::ProfilerCategory;
86use profile_traits::time_profile;
87use rustc_hash::{FxHashMap, FxHashSet};
88use script_bindings::script_runtime::{JSContext, temp_cx};
89use script_traits::{
90    ConstellationInputEvent, DiscardBrowsingContext, DocumentActivity, InitialScriptState,
91    NewPipelineInfo, Painter, ProgressiveWebMetricType, ScriptThreadMessage,
92    UpdatePipelineIdReason,
93};
94use servo_arc::Arc as ServoArc;
95use servo_config::{opts, pref, prefs};
96use servo_url::{ImmutableOrigin, MutableOrigin, OriginSnapshot, ServoUrl};
97use storage_traits::StorageThreads;
98use storage_traits::webstorage_thread::WebStorageType;
99use style::context::QuirksMode;
100use style::error_reporting::RustLogReporter;
101use style::global_style_data::GLOBAL_STYLE_DATA;
102use style::media_queries::MediaList;
103use style::stylesheets::{AllowImportRules, DocumentStyleSheet, Origin, Stylesheet};
104use style::thread_state::{self, ThreadState};
105use stylo_atoms::Atom;
106use timers::{TimerEventRequest, TimerId, TimerScheduler};
107use url::Position;
108#[cfg(feature = "webgpu")]
109use webgpu_traits::{WebGPUDevice, WebGPUMsg};
110use webrender_api::ExternalScrollId;
111use webrender_api::units::LayoutVector2D;
112
113use crate::document_collection::DocumentCollection;
114use crate::document_loader::DocumentLoader;
115use crate::dom::bindings::cell::DomRefCell;
116use crate::dom::bindings::codegen::Bindings::DocumentBinding::{
117    DocumentMethods, DocumentReadyState,
118};
119use crate::dom::bindings::codegen::Bindings::NavigatorBinding::NavigatorMethods;
120use crate::dom::bindings::codegen::Bindings::WindowBinding::WindowMethods;
121use crate::dom::bindings::conversions::{
122    ConversionResult, SafeFromJSValConvertible, StringificationBehavior,
123};
124use crate::dom::bindings::inheritance::Castable;
125use crate::dom::bindings::refcounted::Trusted;
126use crate::dom::bindings::reflector::DomGlobal;
127use crate::dom::bindings::root::{Dom, DomRoot};
128use crate::dom::bindings::settings_stack::AutoEntryScript;
129use crate::dom::bindings::str::DOMString;
130use crate::dom::csp::{CspReporting, GlobalCspReporting, Violation};
131use crate::dom::customelementregistry::{
132    CallbackReaction, CustomElementDefinition, CustomElementReactionStack,
133};
134use crate::dom::document::{
135    Document, DocumentSource, FocusInitiator, HasBrowsingContext, IsHTMLDocument,
136    RenderingUpdateReason,
137};
138use crate::dom::element::Element;
139use crate::dom::globalscope::GlobalScope;
140use crate::dom::html::htmliframeelement::{HTMLIFrameElement, IframeContext};
141use crate::dom::node::NodeTraits;
142use crate::dom::servoparser::{ParserContext, ServoParser};
143use crate::dom::types::DebuggerGlobalScope;
144#[cfg(feature = "webgpu")]
145use crate::dom::webgpu::identityhub::IdentityHub;
146use crate::dom::window::Window;
147use crate::dom::windowproxy::{CreatorBrowsingContextInfo, WindowProxy};
148use crate::dom::worklet::WorkletThreadPool;
149use crate::dom::workletglobalscope::WorkletGlobalScopeInit;
150use crate::fetch::FetchCanceller;
151use crate::messaging::{
152    CommonScriptMsg, MainThreadScriptMsg, MixedMessage, ScriptEventLoopSender,
153    ScriptThreadReceivers, ScriptThreadSenders,
154};
155use crate::microtask::{Microtask, MicrotaskQueue};
156use crate::mime::{APPLICATION, CHARSET, MimeExt, TEXT, XML};
157use crate::navigation::{InProgressLoad, NavigationListener};
158use crate::network_listener::{FetchResponseListener, submit_timing};
159use crate::realms::{enter_auto_realm, enter_realm};
160use crate::script_mutation_observers::ScriptMutationObservers;
161use crate::script_runtime::{
162    CanGc, IntroductionType, JSContextHelper, Runtime, ScriptThreadEventCategory,
163    ThreadSafeJSContext,
164};
165use crate::script_window_proxies::ScriptWindowProxies;
166use crate::task_queue::TaskQueue;
167use crate::webdriver_handlers::jsval_to_webdriver;
168use crate::{devtools, webdriver_handlers};
169
170thread_local!(static SCRIPT_THREAD_ROOT: Cell<Option<*const ScriptThread>> = const { Cell::new(None) });
171
172fn with_optional_script_thread<R>(f: impl FnOnce(Option<&ScriptThread>) -> R) -> R {
173    SCRIPT_THREAD_ROOT.with(|root| {
174        f(root
175            .get()
176            .and_then(|script_thread| unsafe { script_thread.as_ref() }))
177    })
178}
179
180pub(crate) fn with_script_thread<R: Default>(f: impl FnOnce(&ScriptThread) -> R) -> R {
181    with_optional_script_thread(|script_thread| script_thread.map(f).unwrap_or_default())
182}
183
184// We borrow the incomplete parser contexts mutably during parsing,
185// which is fine except that parsing can trigger evaluation,
186// which can trigger GC, and so we can end up tracing the script
187// thread during parsing. For this reason, we don't trace the
188// incomplete parser contexts during GC.
189pub(crate) struct IncompleteParserContexts(RefCell<Vec<(PipelineId, ParserContext)>>);
190
191unsafe_no_jsmanaged_fields!(TaskQueue<MainThreadScriptMsg>);
192
193type NodeIdSet = HashSet<String>;
194
195/// A simple guard structure that restore the user interacting state when dropped
196#[derive(Default)]
197pub(crate) struct ScriptUserInteractingGuard {
198    was_interacting: bool,
199    user_interaction_cell: Rc<Cell<bool>>,
200}
201
202impl ScriptUserInteractingGuard {
203    fn new(user_interaction_cell: Rc<Cell<bool>>) -> Self {
204        let was_interacting = user_interaction_cell.get();
205        user_interaction_cell.set(true);
206        Self {
207            was_interacting,
208            user_interaction_cell,
209        }
210    }
211}
212
213impl Drop for ScriptUserInteractingGuard {
214    fn drop(&mut self) {
215        self.user_interaction_cell.set(self.was_interacting)
216    }
217}
218
219/// This is the `ScriptThread`'s version of [`UserContents`] with the difference that user
220/// stylesheets are represented as parsed `DocumentStyleSheet`s instead of simple source strings.
221struct ScriptThreadUserContents {
222    user_scripts: Rc<Vec<UserScript>>,
223    user_stylesheets: Rc<Vec<DocumentStyleSheet>>,
224}
225
226impl From<UserContents> for ScriptThreadUserContents {
227    fn from(user_contents: UserContents) -> Self {
228        let shared_lock = &GLOBAL_STYLE_DATA.shared_lock;
229        let user_stylesheets = user_contents
230            .stylesheets
231            .iter()
232            .map(|user_stylesheet| {
233                DocumentStyleSheet(ServoArc::new(Stylesheet::from_str(
234                    user_stylesheet.source(),
235                    user_stylesheet.url().into(),
236                    Origin::User,
237                    ServoArc::new(shared_lock.wrap(MediaList::empty())),
238                    shared_lock.clone(),
239                    None,
240                    Some(&RustLogReporter),
241                    QuirksMode::NoQuirks,
242                    AllowImportRules::Yes,
243                )))
244            })
245            .collect();
246        Self {
247            user_scripts: Rc::new(user_contents.scripts),
248            user_stylesheets: Rc::new(user_stylesheets),
249        }
250    }
251}
252
253#[derive(JSTraceable)]
254// ScriptThread instances are rooted on creation, so this is okay
255#[cfg_attr(crown, expect(crown::unrooted_must_root))]
256pub struct ScriptThread {
257    /// A reference to the currently operating `ScriptThread`. This should always be
258    /// upgradable to an `Rc` as long as the `ScriptThread` is running.
259    #[no_trace]
260    this: Weak<ScriptThread>,
261
262    /// <https://html.spec.whatwg.org/multipage/#last-render-opportunity-time>
263    last_render_opportunity_time: Cell<Option<Instant>>,
264
265    /// The documents for pipelines managed by this thread
266    documents: DomRefCell<DocumentCollection>,
267    /// The window proxies known by this thread
268    window_proxies: Rc<ScriptWindowProxies>,
269    /// A list of data pertaining to loads that have not yet received a network response
270    incomplete_loads: DomRefCell<Vec<InProgressLoad>>,
271    /// A vector containing parser contexts which have not yet been fully processed
272    incomplete_parser_contexts: IncompleteParserContexts,
273    /// An [`ImageCacheFactory`] to use for creating [`ImageCache`]s for all of the
274    /// child `Pipeline`s.
275    #[no_trace]
276    image_cache_factory: Arc<dyn ImageCacheFactory>,
277
278    /// A [`ScriptThreadReceivers`] holding all of the incoming `Receiver`s for messages
279    /// to this [`ScriptThread`].
280    receivers: ScriptThreadReceivers,
281
282    /// A [`ScriptThreadSenders`] that holds all outgoing sending channels necessary to communicate
283    /// to other parts of Servo.
284    senders: ScriptThreadSenders,
285
286    /// A handle to the resource thread. This is an `Arc` to avoid running out of file descriptors if
287    /// there are many iframes.
288    #[no_trace]
289    resource_threads: ResourceThreads,
290
291    #[no_trace]
292    storage_threads: StorageThreads,
293
294    /// A queue of tasks to be executed in this script-thread.
295    task_queue: TaskQueue<MainThreadScriptMsg>,
296
297    /// The dedicated means of communication with the background-hang-monitor for this script-thread.
298    #[no_trace]
299    background_hang_monitor: Box<dyn BackgroundHangMonitor>,
300    /// A flag set to `true` by the BHM on exit, and checked from within the interrupt handler.
301    closing: Arc<AtomicBool>,
302
303    /// A [`TimerScheduler`] used to schedule timers for this [`ScriptThread`]. Timers are handled
304    /// in the [`ScriptThread`] event loop.
305    #[no_trace]
306    timer_scheduler: RefCell<TimerScheduler>,
307
308    /// A proxy to the `SystemFontService` to use for accessing system font lists.
309    #[no_trace]
310    system_font_service: Arc<SystemFontServiceProxy>,
311
312    /// The JavaScript runtime.
313    js_runtime: Rc<Runtime>,
314
315    /// List of pipelines that have been owned and closed by this script thread.
316    #[no_trace]
317    closed_pipelines: DomRefCell<FxHashSet<PipelineId>>,
318
319    /// <https://html.spec.whatwg.org/multipage/#microtask-queue>
320    microtask_queue: Rc<MicrotaskQueue>,
321
322    mutation_observers: Rc<ScriptMutationObservers>,
323
324    /// A handle to the WebGL thread
325    #[no_trace]
326    webgl_chan: Option<WebGLPipeline>,
327
328    /// The WebXR device registry
329    #[no_trace]
330    #[cfg(feature = "webxr")]
331    webxr_registry: Option<webxr_api::Registry>,
332
333    /// The worklet thread pool
334    worklet_thread_pool: DomRefCell<Option<Rc<WorkletThreadPool>>>,
335
336    /// A list of pipelines containing documents that finished loading all their blocking
337    /// resources during a turn of the event loop.
338    docs_with_no_blocking_loads: DomRefCell<FxHashSet<Dom<Document>>>,
339
340    /// <https://html.spec.whatwg.org/multipage/#custom-element-reactions-stack>
341    custom_element_reaction_stack: Rc<CustomElementReactionStack>,
342
343    /// Cross-process access to `Paint`'s API.
344    #[no_trace]
345    paint_api: CrossProcessPaintApi,
346
347    /// Periodically print out on which events script threads spend their processing time.
348    profile_script_events: bool,
349
350    /// Print Progressive Web Metrics to console.
351    print_pwm: bool,
352
353    /// Unminify Javascript.
354    unminify_js: bool,
355
356    /// Directory with stored unminified scripts
357    local_script_source: Option<String>,
358
359    /// Unminify Css.
360    unminify_css: bool,
361
362    /// A map from [`UserContentManagerId`] to its [`UserContents`]. This is initialized
363    /// with a copy of the map in constellation (via the `InitialScriptState`). After that,
364    /// the constellation forwards any mutations to this `ScriptThread` using messages.
365    #[no_trace]
366    user_contents_for_manager_id:
367        RefCell<FxHashMap<UserContentManagerId, ScriptThreadUserContents>>,
368
369    /// Application window's GL Context for Media player
370    #[no_trace]
371    player_context: WindowGLContext,
372
373    /// A map from pipelines to all owned nodes ever created in this script thread
374    #[no_trace]
375    pipeline_to_node_ids: DomRefCell<FxHashMap<PipelineId, NodeIdSet>>,
376
377    /// Code is running as a consequence of a user interaction
378    is_user_interacting: Rc<Cell<bool>>,
379
380    /// Identity manager for WebGPU resources
381    #[no_trace]
382    #[cfg(feature = "webgpu")]
383    gpu_id_hub: Arc<IdentityHub>,
384
385    /// A factory for making new layouts. This allows layout to depend on script.
386    #[no_trace]
387    layout_factory: Arc<dyn LayoutFactory>,
388
389    /// The [`TimerId`] of a ScriptThread-scheduled "update the rendering" call, if any.
390    /// The ScriptThread schedules calls to "update the rendering," but the renderer can
391    /// also do this when animating. Renderer-based calls always take precedence.
392    #[no_trace]
393    scheduled_update_the_rendering: RefCell<Option<TimerId>>,
394
395    /// Whether an animation tick or ScriptThread-triggered rendering update is pending. This might
396    /// either be because the Servo renderer is managing animations and the [`ScriptThread`] has
397    /// received a [`ScriptThreadMessage::TickAllAnimations`] message, because the [`ScriptThread`]
398    /// itself is managing animations the timer fired triggering a [`ScriptThread`]-based
399    /// animation tick, or if there are no animations running and the [`ScriptThread`] has noticed a
400    /// change that requires a rendering update.
401    needs_rendering_update: Arc<AtomicBool>,
402
403    debugger_global: Dom<DebuggerGlobalScope>,
404
405    debugger_paused: Cell<bool>,
406
407    /// A list of URLs that can access privileged internal APIs.
408    #[no_trace]
409    privileged_urls: Vec<ServoUrl>,
410
411    /// Whether accessibility is active. If true, each Layout will maintain an accessibility tree
412    /// and send accessibility updates to the embedder.
413    accessibility_active: Cell<bool>,
414}
415
416struct BHMExitSignal {
417    closing: Arc<AtomicBool>,
418    js_context: ThreadSafeJSContext,
419}
420
421impl BackgroundHangMonitorExitSignal for BHMExitSignal {
422    fn signal_to_exit(&self) {
423        self.closing.store(true, Ordering::SeqCst);
424        self.js_context.request_interrupt_callback();
425    }
426}
427
428#[expect(unsafe_code)]
429unsafe extern "C" fn interrupt_callback(_cx: *mut UnsafeJSContext) -> bool {
430    let res = ScriptThread::can_continue_running();
431    if !res {
432        ScriptThread::prepare_for_shutdown();
433    }
434    res
435}
436
437/// In the event of thread panic, all data on the stack runs its destructor. However, there
438/// are no reachable, owning pointers to the DOM memory, so it never gets freed by default
439/// when the script thread fails. The ScriptMemoryFailsafe uses the destructor bomb pattern
440/// to forcibly tear down the JS realms for pages associated with the failing ScriptThread.
441struct ScriptMemoryFailsafe<'a> {
442    owner: Option<&'a ScriptThread>,
443}
444
445impl<'a> ScriptMemoryFailsafe<'a> {
446    fn neuter(&mut self) {
447        self.owner = None;
448    }
449
450    fn new(owner: &'a ScriptThread) -> ScriptMemoryFailsafe<'a> {
451        ScriptMemoryFailsafe { owner: Some(owner) }
452    }
453}
454
455impl Drop for ScriptMemoryFailsafe<'_> {
456    fn drop(&mut self) {
457        if let Some(owner) = self.owner {
458            for (_, document) in owner.documents.borrow().iter() {
459                document.window().clear_js_runtime_for_script_deallocation();
460            }
461        }
462    }
463}
464
465impl ScriptThreadFactory for ScriptThread {
466    fn create(
467        state: InitialScriptState,
468        layout_factory: Arc<dyn LayoutFactory>,
469        image_cache_factory: Arc<dyn ImageCacheFactory>,
470        background_hang_monitor_register: Box<dyn BackgroundHangMonitorRegister>,
471    ) -> JoinHandle<()> {
472        // Setup pipeline-namespace-installing for all threads in this process.
473        // Idempotent in single-process mode.
474        PipelineNamespace::set_installer_sender(state.namespace_request_sender.clone());
475
476        let script_thread_id = state.id;
477        thread::Builder::new()
478            .name(format!("Script#{script_thread_id}"))
479            .spawn(move || {
480                thread_state::initialize(ThreadState::SCRIPT | ThreadState::LAYOUT);
481                PipelineNamespace::install(state.pipeline_namespace_id);
482                ScriptEventLoopId::install(state.id);
483                let memory_profiler_sender = state.memory_profiler_sender.clone();
484                let reporter_name = format!("script-reporter-{script_thread_id:?}");
485                let (script_thread, mut cx) = ScriptThread::new(
486                    state,
487                    layout_factory,
488                    image_cache_factory,
489                    background_hang_monitor_register,
490                );
491                SCRIPT_THREAD_ROOT.with(|root| {
492                    root.set(Some(Rc::as_ptr(&script_thread)));
493                });
494                let mut failsafe = ScriptMemoryFailsafe::new(&script_thread);
495
496                memory_profiler_sender.run_with_memory_reporting(
497                    || script_thread.start(&mut cx),
498                    reporter_name,
499                    ScriptEventLoopSender::MainThread(script_thread.senders.self_sender.clone()),
500                    CommonScriptMsg::CollectReports,
501                );
502
503                // This must always be the very last operation performed before the thread completes
504                failsafe.neuter();
505            })
506            .expect("Thread spawning failed")
507    }
508}
509
510impl ScriptThread {
511    pub(crate) fn runtime_handle() -> ParentRuntime {
512        with_optional_script_thread(|script_thread| {
513            script_thread.unwrap().js_runtime.prepare_for_new_child()
514        })
515    }
516
517    pub(crate) fn can_continue_running() -> bool {
518        with_script_thread(|script_thread| script_thread.can_continue_running_inner())
519    }
520
521    pub(crate) fn prepare_for_shutdown() {
522        with_script_thread(|script_thread| {
523            script_thread.prepare_for_shutdown_inner();
524        })
525    }
526
527    pub(crate) fn mutation_observers() -> Rc<ScriptMutationObservers> {
528        with_script_thread(|script_thread| script_thread.mutation_observers.clone())
529    }
530
531    pub(crate) fn microtask_queue() -> Rc<MicrotaskQueue> {
532        with_script_thread(|script_thread| script_thread.microtask_queue.clone())
533    }
534
535    pub(crate) fn mark_document_with_no_blocked_loads(doc: &Document) {
536        with_script_thread(|script_thread| {
537            script_thread
538                .docs_with_no_blocking_loads
539                .borrow_mut()
540                .insert(Dom::from_ref(doc));
541        })
542    }
543
544    pub(crate) fn page_headers_available(
545        webview_id: WebViewId,
546        pipeline_id: PipelineId,
547        metadata: Option<Metadata>,
548        can_gc: CanGc,
549    ) -> Option<DomRoot<ServoParser>> {
550        with_script_thread(|script_thread| {
551            script_thread.handle_page_headers_available(webview_id, pipeline_id, metadata, can_gc)
552        })
553    }
554
555    /// Process a single event as if it were the next event
556    /// in the queue for this window event-loop.
557    /// Returns a boolean indicating whether further events should be processed.
558    pub(crate) fn process_event(msg: CommonScriptMsg, cx: &mut js::context::JSContext) -> bool {
559        with_script_thread(|script_thread| {
560            if !script_thread.can_continue_running_inner() {
561                return false;
562            }
563            script_thread.handle_msg_from_script(MainThreadScriptMsg::Common(msg), cx);
564            true
565        })
566    }
567
568    /// Schedule a [`TimerEventRequest`] on this [`ScriptThread`]'s [`TimerScheduler`].
569    pub(crate) fn schedule_timer(&self, request: TimerEventRequest) -> TimerId {
570        self.timer_scheduler.borrow_mut().schedule_timer(request)
571    }
572
573    /// Cancel a the [`TimerEventRequest`] for the given [`TimerId`] on this
574    /// [`ScriptThread`]'s [`TimerScheduler`].
575    pub(crate) fn cancel_timer(&self, timer_id: TimerId) {
576        self.timer_scheduler.borrow_mut().cancel_timer(timer_id)
577    }
578
579    // https://html.spec.whatwg.org/multipage/#await-a-stable-state
580    pub(crate) fn await_stable_state(task: Microtask) {
581        with_script_thread(|script_thread| {
582            script_thread
583                .microtask_queue
584                .enqueue(task, script_thread.get_cx());
585        });
586    }
587
588    /// Check that two origins are "similar enough",
589    /// for now only used to prevent cross-origin JS url evaluation.
590    ///
591    /// <https://github.com/whatwg/html/issues/2591>
592    fn check_load_origin(source: &LoadOrigin, target: &OriginSnapshot) -> bool {
593        match (source, target.immutable()) {
594            (LoadOrigin::Constellation, _) | (LoadOrigin::WebDriver, _) => {
595                // Always allow loads initiated by the constellation or webdriver.
596                true
597            },
598            (_, ImmutableOrigin::Opaque(_)) => {
599                // If the target is opaque, allow.
600                // This covers newly created about:blank auxiliaries, and iframe with no src.
601                // TODO: https://github.com/servo/servo/issues/22879
602                true
603            },
604            (LoadOrigin::Script(source_origin), _) => source_origin.same_origin_domain(target),
605        }
606    }
607
608    /// Inform the `ScriptThread` that it should make a call to
609    /// [`ScriptThread::update_the_rendering`] as soon as possible, as the rendering
610    /// update timer has fired or the renderer has asked us for a new rendering update.
611    pub(crate) fn set_needs_rendering_update(&self) {
612        self.needs_rendering_update.store(true, Ordering::Relaxed);
613    }
614
615    /// Step 13 of <https://html.spec.whatwg.org/multipage/#navigate>
616    pub(crate) fn navigate(
617        webview_id: WebViewId,
618        pipeline_id: PipelineId,
619        mut load_data: LoadData,
620        history_handling: NavigationHistoryBehavior,
621    ) {
622        with_script_thread(|script_thread| {
623            let is_javascript = load_data.url.scheme() == "javascript";
624            // If resource is a request whose url's scheme is "javascript"
625            // https://html.spec.whatwg.org/multipage/#navigate-to-a-javascript:-url
626            if is_javascript {
627                let Some(window) = script_thread.documents.borrow().find_window(pipeline_id) else {
628                    return;
629                };
630                let global = window.as_global_scope();
631                let trusted_global = Trusted::new(global);
632                let sender = script_thread
633                    .senders
634                    .pipeline_to_constellation_sender
635                    .clone();
636                load_data.about_base_url = window.Document().about_base_url();
637                let task = task!(navigate_javascript: move || {
638                    // Important re security. See https://github.com/servo/servo/issues/23373
639                    if trusted_global.root().is::<Window>() {
640                        let global = &trusted_global.root();
641                        if Self::navigate_to_javascript_url(global, global, &mut load_data, None, CanGc::note()) {
642                            sender
643                                .send((webview_id, pipeline_id, ScriptToConstellationMessage::LoadUrl(load_data, history_handling)))
644                                .unwrap();
645                        }
646                    }
647                });
648                // Step 19 of <https://html.spec.whatwg.org/multipage/#navigate>
649                global
650                    .task_manager()
651                    .dom_manipulation_task_source()
652                    .queue(task);
653            } else {
654                script_thread
655                    .senders
656                    .pipeline_to_constellation_sender
657                    .send((
658                        webview_id,
659                        pipeline_id,
660                        ScriptToConstellationMessage::LoadUrl(load_data, history_handling),
661                    ))
662                    .expect("Sending a LoadUrl message to the constellation failed");
663            }
664        });
665    }
666
667    /// <https://html.spec.whatwg.org/multipage/#navigate-to-a-javascript:-url>
668    pub(crate) fn can_navigate_to_javascript_url(
669        initiator_global: &GlobalScope,
670        target_global: &GlobalScope,
671        load_data: &mut LoadData,
672        container: Option<&Element>,
673        can_gc: CanGc,
674    ) -> bool {
675        // Step 3. If initiatorOrigin is not same origin-domain with targetNavigable's active document's origin, then return.
676        //
677        // Important re security. See https://github.com/servo/servo/issues/23373
678        if !Self::check_load_origin(&load_data.load_origin, &target_global.origin().snapshot()) {
679            return false;
680        }
681
682        // Step 5: If the result of should navigation request of type be blocked by
683        // Content Security Policy? given request and cspNavigationType is "Blocked", then return. [CSP]
684        if initiator_global
685            .get_csp_list()
686            .should_navigation_request_be_blocked(initiator_global, load_data, container, can_gc)
687        {
688            return false;
689        }
690
691        true
692    }
693
694    pub(crate) fn navigate_to_javascript_url(
695        initiator_global: &GlobalScope,
696        target_global: &GlobalScope,
697        load_data: &mut LoadData,
698        container: Option<&Element>,
699        can_gc: CanGc,
700    ) -> bool {
701        if !Self::can_navigate_to_javascript_url(
702            initiator_global,
703            target_global,
704            load_data,
705            container,
706            can_gc,
707        ) {
708            return false;
709        }
710
711        // Step 6. Let newDocument be the result of evaluating a javascript: URL given targetNavigable,
712        // url, initiatorOrigin, and userInvolvement.
713        Self::eval_js_url(target_global, load_data, can_gc);
714        true
715    }
716
717    pub(crate) fn get_top_level_for_browsing_context(
718        sender_webview_id: WebViewId,
719        sender_pipeline_id: PipelineId,
720        browsing_context_id: BrowsingContextId,
721    ) -> Option<WebViewId> {
722        with_script_thread(|script_thread| {
723            script_thread.ask_constellation_for_top_level_info(
724                sender_webview_id,
725                sender_pipeline_id,
726                browsing_context_id,
727            )
728        })
729    }
730
731    pub(crate) fn find_document(id: PipelineId) -> Option<DomRoot<Document>> {
732        with_script_thread(|script_thread| script_thread.documents.borrow().find_document(id))
733    }
734
735    /// Creates a guard that sets user_is_interacting to true and returns the
736    /// state of user_is_interacting on drop of the guard.
737    /// Notice that you need to use `let _guard = ...` as `let _ = ...` is not enough
738    #[must_use]
739    pub(crate) fn user_interacting_guard() -> ScriptUserInteractingGuard {
740        with_script_thread(|script_thread| {
741            ScriptUserInteractingGuard::new(script_thread.is_user_interacting.clone())
742        })
743    }
744
745    pub(crate) fn is_user_interacting() -> bool {
746        with_script_thread(|script_thread| script_thread.is_user_interacting.get())
747    }
748
749    pub(crate) fn get_fully_active_document_ids(&self) -> FxHashSet<PipelineId> {
750        self.documents
751            .borrow()
752            .iter()
753            .filter_map(|(id, document)| {
754                if document.is_fully_active() {
755                    Some(id)
756                } else {
757                    None
758                }
759            })
760            .fold(FxHashSet::default(), |mut set, id| {
761                let _ = set.insert(id);
762                set
763            })
764    }
765
766    pub(crate) fn window_proxies() -> Rc<ScriptWindowProxies> {
767        with_script_thread(|script_thread| script_thread.window_proxies.clone())
768    }
769
770    pub(crate) fn find_window_proxy_by_name(name: &DOMString) -> Option<DomRoot<WindowProxy>> {
771        with_script_thread(|script_thread| {
772            script_thread.window_proxies.find_window_proxy_by_name(name)
773        })
774    }
775
776    /// The worklet will use the given `ImageCache`.
777    pub(crate) fn worklet_thread_pool(image_cache: Arc<dyn ImageCache>) -> Rc<WorkletThreadPool> {
778        with_optional_script_thread(|script_thread| {
779            let script_thread = script_thread.unwrap();
780            script_thread
781                .worklet_thread_pool
782                .borrow_mut()
783                .get_or_insert_with(|| {
784                    let init = WorkletGlobalScopeInit {
785                        to_script_thread_sender: script_thread.senders.self_sender.clone(),
786                        resource_threads: script_thread.resource_threads.clone(),
787                        storage_threads: script_thread.storage_threads.clone(),
788                        mem_profiler_chan: script_thread.senders.memory_profiler_sender.clone(),
789                        time_profiler_chan: script_thread.senders.time_profiler_sender.clone(),
790                        devtools_chan: script_thread.senders.devtools_server_sender.clone(),
791                        to_constellation_sender: script_thread
792                            .senders
793                            .pipeline_to_constellation_sender
794                            .clone(),
795                        to_embedder_sender: script_thread
796                            .senders
797                            .pipeline_to_embedder_sender
798                            .clone(),
799                        image_cache,
800                        #[cfg(feature = "webgpu")]
801                        gpu_id_hub: script_thread.gpu_id_hub.clone(),
802                    };
803                    Rc::new(WorkletThreadPool::spawn(init))
804                })
805                .clone()
806        })
807    }
808
809    fn handle_register_paint_worklet(
810        &self,
811        pipeline_id: PipelineId,
812        name: Atom,
813        properties: Vec<Atom>,
814        painter: Box<dyn Painter>,
815    ) {
816        let Some(window) = self.documents.borrow().find_window(pipeline_id) else {
817            warn!("Paint worklet registered after pipeline {pipeline_id} closed.");
818            return;
819        };
820
821        window
822            .layout_mut()
823            .register_paint_worklet_modules(name, properties, painter);
824    }
825
826    pub(crate) fn custom_element_reaction_stack() -> Rc<CustomElementReactionStack> {
827        with_optional_script_thread(|script_thread| {
828            script_thread
829                .as_ref()
830                .unwrap()
831                .custom_element_reaction_stack
832                .clone()
833        })
834    }
835
836    pub(crate) fn enqueue_callback_reaction(
837        element: &Element,
838        reaction: CallbackReaction,
839        definition: Option<Rc<CustomElementDefinition>>,
840    ) {
841        with_script_thread(|script_thread| {
842            script_thread
843                .custom_element_reaction_stack
844                .enqueue_callback_reaction(element, reaction, definition);
845        })
846    }
847
848    pub(crate) fn enqueue_upgrade_reaction(
849        element: &Element,
850        definition: Rc<CustomElementDefinition>,
851    ) {
852        with_script_thread(|script_thread| {
853            script_thread
854                .custom_element_reaction_stack
855                .enqueue_upgrade_reaction(element, definition);
856        })
857    }
858
859    pub(crate) fn invoke_backup_element_queue(can_gc: CanGc) {
860        with_script_thread(|script_thread| {
861            script_thread
862                .custom_element_reaction_stack
863                .invoke_backup_element_queue(can_gc);
864        })
865    }
866
867    pub(crate) fn save_node_id(pipeline: PipelineId, node_id: String) {
868        with_script_thread(|script_thread| {
869            script_thread
870                .pipeline_to_node_ids
871                .borrow_mut()
872                .entry(pipeline)
873                .or_default()
874                .insert(node_id);
875        })
876    }
877
878    pub(crate) fn has_node_id(pipeline: PipelineId, node_id: &str) -> bool {
879        with_script_thread(|script_thread| {
880            script_thread
881                .pipeline_to_node_ids
882                .borrow()
883                .get(&pipeline)
884                .is_some_and(|node_ids| node_ids.contains(node_id))
885        })
886    }
887
888    /// Creates a new script thread.
889    pub(crate) fn new(
890        state: InitialScriptState,
891        layout_factory: Arc<dyn LayoutFactory>,
892        image_cache_factory: Arc<dyn ImageCacheFactory>,
893        background_hang_monitor_register: Box<dyn BackgroundHangMonitorRegister>,
894    ) -> (Rc<ScriptThread>, js::context::JSContext) {
895        let (self_sender, self_receiver) = unbounded();
896        let mut runtime =
897            Runtime::new(Some(ScriptEventLoopSender::MainThread(self_sender.clone())));
898
899        // SAFETY: We ensure that only one JSContext exists in this thread.
900        // This is the first one and the only one
901        let mut cx = unsafe { runtime.cx() };
902
903        unsafe {
904            SetWindowProxyClass(&cx, GetWindowProxyClass());
905            JS_AddInterruptCallback(&cx, Some(interrupt_callback));
906        }
907
908        let constellation_receiver = state
909            .constellation_to_script_receiver
910            .route_preserving_errors();
911
912        // Ask the router to proxy IPC messages from the devtools to us.
913        let devtools_server_sender = state.devtools_server_sender;
914        let (ipc_devtools_sender, ipc_devtools_receiver) = generic_channel::channel().unwrap();
915        let devtools_server_receiver = ipc_devtools_receiver.route_preserving_errors();
916
917        let task_queue = TaskQueue::new(self_receiver, self_sender.clone());
918
919        let closing = Arc::new(AtomicBool::new(false));
920        let background_hang_monitor_exit_signal = BHMExitSignal {
921            closing: closing.clone(),
922            js_context: runtime.thread_safe_js_context(),
923        };
924
925        let background_hang_monitor = background_hang_monitor_register.register_component(
926            // TODO: We shouldn't rely on this PipelineId as a ScriptThread can have multiple
927            // Pipelines and any of them might disappear at any time.
928            MonitoredComponentId(state.id, MonitoredComponentType::Script),
929            Duration::from_millis(1000),
930            Duration::from_millis(5000),
931            Box::new(background_hang_monitor_exit_signal),
932        );
933
934        let (image_cache_sender, image_cache_receiver) = unbounded();
935
936        let receivers = ScriptThreadReceivers {
937            constellation_receiver,
938            image_cache_receiver,
939            devtools_server_receiver,
940            // Initialized to `never` until WebGPU is initialized.
941            #[cfg(feature = "webgpu")]
942            webgpu_receiver: RefCell::new(crossbeam_channel::never()),
943        };
944
945        let opts = opts::get();
946        let senders = ScriptThreadSenders {
947            self_sender,
948            #[cfg(feature = "bluetooth")]
949            bluetooth_sender: state.bluetooth_sender,
950            constellation_sender: state.constellation_to_script_sender,
951            pipeline_to_constellation_sender: state.script_to_constellation_sender,
952            pipeline_to_embedder_sender: state.script_to_embedder_sender.clone(),
953            image_cache_sender,
954            time_profiler_sender: state.time_profiler_sender,
955            memory_profiler_sender: state.memory_profiler_sender,
956            devtools_server_sender,
957            devtools_client_to_script_thread_sender: ipc_devtools_sender,
958        };
959
960        let microtask_queue = runtime.microtask_queue.clone();
961        #[cfg(feature = "webgpu")]
962        let gpu_id_hub = Arc::new(IdentityHub::default());
963
964        let debugger_pipeline_id = PipelineId::new();
965        let script_to_constellation_chan = ScriptToConstellationChan {
966            sender: senders.pipeline_to_constellation_sender.clone(),
967            // This channel is not expected to be used, so the `WebViewId` that we set here
968            // does not matter.
969            // TODO: Look at ways of removing the channel entirely for debugger globals.
970            webview_id: TEST_WEBVIEW_ID,
971            pipeline_id: debugger_pipeline_id,
972        };
973        let debugger_global = DebuggerGlobalScope::new(
974            PipelineId::new(),
975            senders.devtools_server_sender.clone(),
976            senders.devtools_client_to_script_thread_sender.clone(),
977            senders.memory_profiler_sender.clone(),
978            senders.time_profiler_sender.clone(),
979            script_to_constellation_chan,
980            senders.pipeline_to_embedder_sender.clone(),
981            state.resource_threads.clone(),
982            state.storage_threads.clone(),
983            #[cfg(feature = "webgpu")]
984            gpu_id_hub.clone(),
985            &mut cx,
986        );
987
988        debugger_global.execute(&mut cx);
989
990        let user_contents_for_manager_id =
991            FxHashMap::from_iter(state.user_contents_for_manager_id.into_iter().map(
992                |(user_content_manager_id, user_contents)| {
993                    (user_content_manager_id, user_contents.into())
994                },
995            ));
996
997        (
998            Rc::new_cyclic(|weak_script_thread| {
999                runtime.set_script_thread(weak_script_thread.clone());
1000                Self {
1001                    documents: DomRefCell::new(DocumentCollection::default()),
1002                    last_render_opportunity_time: Default::default(),
1003                    window_proxies: Default::default(),
1004                    incomplete_loads: DomRefCell::new(vec![]),
1005                    incomplete_parser_contexts: IncompleteParserContexts(RefCell::new(vec![])),
1006                    senders,
1007                    receivers,
1008                    image_cache_factory,
1009                    resource_threads: state.resource_threads,
1010                    storage_threads: state.storage_threads,
1011                    task_queue,
1012                    background_hang_monitor,
1013                    closing,
1014                    timer_scheduler: Default::default(),
1015                    microtask_queue,
1016                    js_runtime: Rc::new(runtime),
1017                    closed_pipelines: DomRefCell::new(FxHashSet::default()),
1018                    mutation_observers: Default::default(),
1019                    system_font_service: Arc::new(state.system_font_service.to_proxy()),
1020                    webgl_chan: state.webgl_chan,
1021                    #[cfg(feature = "webxr")]
1022                    webxr_registry: state.webxr_registry,
1023                    worklet_thread_pool: Default::default(),
1024                    docs_with_no_blocking_loads: Default::default(),
1025                    custom_element_reaction_stack: Rc::new(CustomElementReactionStack::new()),
1026                    paint_api: state.cross_process_paint_api,
1027                    profile_script_events: opts.debug.profile_script_events,
1028                    print_pwm: opts.print_pwm,
1029                    unminify_js: opts.unminify_js,
1030                    local_script_source: opts.local_script_source.clone(),
1031                    unminify_css: opts.unminify_css,
1032                    user_contents_for_manager_id: RefCell::new(user_contents_for_manager_id),
1033                    player_context: state.player_context,
1034                    pipeline_to_node_ids: Default::default(),
1035                    is_user_interacting: Rc::new(Cell::new(false)),
1036                    #[cfg(feature = "webgpu")]
1037                    gpu_id_hub,
1038                    layout_factory,
1039                    scheduled_update_the_rendering: Default::default(),
1040                    needs_rendering_update: Arc::new(AtomicBool::new(false)),
1041                    debugger_global: debugger_global.as_traced(),
1042                    debugger_paused: Cell::new(false),
1043                    privileged_urls: state.privileged_urls,
1044                    this: weak_script_thread.clone(),
1045                    accessibility_active: Cell::new(state.accessibility_active),
1046                }
1047            }),
1048            cx,
1049        )
1050    }
1051
1052    #[expect(unsafe_code)]
1053    pub(crate) fn get_cx(&self) -> JSContext {
1054        unsafe { JSContext::from_ptr(js::rust::Runtime::get().unwrap().as_ptr()) }
1055    }
1056
1057    /// Check if we are closing.
1058    fn can_continue_running_inner(&self) -> bool {
1059        if self.closing.load(Ordering::SeqCst) {
1060            return false;
1061        }
1062        true
1063    }
1064
1065    /// We are closing, ensure no script can run and potentially hang.
1066    fn prepare_for_shutdown_inner(&self) {
1067        let docs = self.documents.borrow();
1068        for (_, document) in docs.iter() {
1069            document
1070                .owner_global()
1071                .task_manager()
1072                .cancel_all_tasks_and_ignore_future_tasks();
1073        }
1074    }
1075
1076    /// Starts the script thread. After calling this method, the script thread will loop receiving
1077    /// messages on its port.
1078    pub(crate) fn start(&self, cx: &mut js::context::JSContext) {
1079        debug!("Starting script thread.");
1080        while self.handle_msgs(cx) {
1081            // Go on...
1082            debug!("Running script thread.");
1083        }
1084        debug!("Stopped script thread.");
1085    }
1086
1087    /// Process input events as part of a "update the rendering task".
1088    fn process_pending_input_events(&self, pipeline_id: PipelineId, can_gc: CanGc) {
1089        let Some(document) = self.documents.borrow().find_document(pipeline_id) else {
1090            warn!("Processing pending input events for closed pipeline {pipeline_id}.");
1091            return;
1092        };
1093        // Do not handle events if the BC has been, or is being, discarded
1094        if document.window().Closed() {
1095            warn!("Input event sent to a pipeline with a closed window {pipeline_id}.");
1096            return;
1097        }
1098
1099        let _guard = ScriptUserInteractingGuard::new(self.is_user_interacting.clone());
1100        document.event_handler().handle_pending_input_events(can_gc);
1101    }
1102
1103    fn cancel_scheduled_update_the_rendering(&self) {
1104        if let Some(timer_id) = self.scheduled_update_the_rendering.borrow_mut().take() {
1105            self.timer_scheduler.borrow_mut().cancel_timer(timer_id);
1106        }
1107    }
1108
1109    fn schedule_update_the_rendering_timer_if_necessary(&self, delay: Duration) {
1110        if self.scheduled_update_the_rendering.borrow().is_some() {
1111            return;
1112        }
1113
1114        debug!("Scheduling ScriptThread animation frame.");
1115        let trigger_script_thread_animation = self.needs_rendering_update.clone();
1116        let timer_id = self.schedule_timer(TimerEventRequest {
1117            callback: Box::new(move || {
1118                trigger_script_thread_animation.store(true, Ordering::Relaxed);
1119            }),
1120            duration: delay,
1121        });
1122
1123        *self.scheduled_update_the_rendering.borrow_mut() = Some(timer_id);
1124    }
1125
1126    /// <https://html.spec.whatwg.org/multipage/#update-the-rendering>
1127    ///
1128    /// Attempt to update the rendering and then do a microtask checkpoint if rendering was
1129    /// actually updated.
1130    ///
1131    /// Returns true if any reflows produced a new display list.
1132    pub(crate) fn update_the_rendering(&self, can_gc: CanGc) -> bool {
1133        self.last_render_opportunity_time.set(Some(Instant::now()));
1134        self.cancel_scheduled_update_the_rendering();
1135        self.needs_rendering_update.store(false, Ordering::Relaxed);
1136
1137        if !self.can_continue_running_inner() {
1138            return false;
1139        }
1140
1141        // TODO: The specification says to filter out non-renderable documents,
1142        // as well as those for which a rendering update would be unnecessary,
1143        // but this isn't happening here.
1144
1145        // TODO(#31242): the filtering of docs is extended to not exclude the ones that
1146        // has pending initial observation targets
1147        // https://w3c.github.io/IntersectionObserver/#pending-initial-observation
1148
1149        // > 2. Let docs be all fully active Document objects whose relevant agent's event loop
1150        // > is eventLoop, sorted arbitrarily except that the following conditions must be
1151        // > met:
1152        //
1153        // > Any Document B whose container document is A must be listed after A in the
1154        // > list.
1155        //
1156        // > If there are two documents A and B that both have the same non-null container
1157        // > document C, then the order of A and B in the list must match the
1158        // > shadow-including tree order of their respective navigable containers in C's
1159        // > node tree.
1160        //
1161        // > In the steps below that iterate over docs, each Document must be processed in
1162        // > the order it is found in the list.
1163        let documents_in_order = self.documents.borrow().documents_in_order();
1164
1165        // TODO: The specification reads: "for doc in docs" at each step whereas this runs all
1166        // steps per doc in docs. Currently `<iframe>` resizing depends on a parent being able to
1167        // queue resize events on a child and have those run in the same call to this method, so
1168        // that needs to be sorted out to fix this.
1169        let mut painters_generating_frames = FxHashSet::default();
1170        for pipeline_id in documents_in_order.iter() {
1171            let document = self
1172                .documents
1173                .borrow()
1174                .find_document(*pipeline_id)
1175                .expect("Got pipeline for Document not managed by this ScriptThread.");
1176
1177            if !document.is_fully_active() {
1178                continue;
1179            }
1180
1181            if document.waiting_on_canvas_image_updates() {
1182                continue;
1183            }
1184
1185            // Clear this as early as possible so that any callbacks that
1186            // trigger new reasons for updating the rendering don't get lost.
1187            document.clear_rendering_update_reasons();
1188
1189            // TODO(#31581): The steps in the "Revealing the document" section need to be implemented
1190            // `process_pending_input_events` handles the focusing steps as well as other events
1191            // from `Paint`.
1192
1193            // TODO: Should this be broken and to match the specification more closely? For instance see
1194            // https://html.spec.whatwg.org/multipage/#flush-autofocus-candidates.
1195            self.process_pending_input_events(*pipeline_id, can_gc);
1196
1197            // > 8. For each doc of docs, run the resize steps for doc. [CSSOMVIEW]
1198            let resized = document.window().run_the_resize_steps(can_gc);
1199
1200            // > 9. For each doc of docs, run the scroll steps for doc.
1201            document.run_the_scroll_steps(can_gc);
1202
1203            // Media queries is only relevant when there are resizing.
1204            if resized {
1205                // 10. For each doc of docs, evaluate media queries and report changes for doc.
1206                document
1207                    .window()
1208                    .evaluate_media_queries_and_report_changes(can_gc);
1209
1210                // https://html.spec.whatwg.org/multipage/#img-environment-changes
1211                // As per the spec, this can be run at any time.
1212                document.react_to_environment_changes()
1213            }
1214
1215            // > 11. For each doc of docs, update animations and send events for doc, passing
1216            // > in relative high resolution time given frameTimestamp and doc's relevant
1217            // > global object as the timestamp [WEBANIMATIONS]
1218            document.update_animations_and_send_events(can_gc);
1219
1220            // TODO(#31866): Implement "run the fullscreen steps" from
1221            // https://fullscreen.spec.whatwg.org/multipage/#run-the-fullscreen-steps.
1222
1223            // TODO(#31868): Implement the "context lost steps" from
1224            // https://html.spec.whatwg.org/multipage/#context-lost-steps.
1225
1226            // > 14. For each doc of docs, run the animation frame callbacks for doc, passing
1227            // > in the relative high resolution time given frameTimestamp and doc's
1228            // > relevant global object as the timestamp.
1229            document.run_the_animation_frame_callbacks(can_gc);
1230
1231            // Run the resize observer steps.
1232            let _realm = enter_realm(&*document);
1233            let mut depth = Default::default();
1234            while document.gather_active_resize_observations_at_depth(&depth) {
1235                // Note: this will reflow the doc.
1236                depth = document.broadcast_active_resize_observations(can_gc);
1237            }
1238
1239            if document.has_skipped_resize_observations() {
1240                document.deliver_resize_loop_error_notification(can_gc);
1241                // Ensure that another turn of the event loop occurs to process
1242                // the skipped observations.
1243                document.add_rendering_update_reason(
1244                    RenderingUpdateReason::ResizeObserverStartedObservingTarget,
1245                );
1246            }
1247
1248            // TODO(#31870): Implement step 17: if the focused area of doc is not a focusable area,
1249            // then run the focusing steps for document's viewport.
1250
1251            // TODO: Perform pending transition operations from
1252            // https://drafts.csswg.org/css-view-transitions/#perform-pending-transition-operations.
1253
1254            // > 19. For each doc of docs, run the update intersection observations steps for doc,
1255            // > passing in the relative high resolution time given now and
1256            // > doc's relevant global object as the timestamp. [INTERSECTIONOBSERVER]
1257            // TODO(stevennovaryo): The time attribute should be relative to the time origin of the global object
1258            document.update_intersection_observer_steps(CrossProcessInstant::now(), can_gc);
1259
1260            // TODO: Mark paint timing from https://w3c.github.io/paint-timing.
1261
1262            // > Step 22: For each doc of docs, update the rendering or user interface of
1263            // > doc and its node navigable to reflect the current state.
1264            if document.update_the_rendering().needs_frame() {
1265                painters_generating_frames.insert(document.webview_id().into());
1266            }
1267
1268            // TODO: Process top layer removals according to
1269            // https://drafts.csswg.org/css-position-4/#process-top-layer-removals.
1270        }
1271
1272        let should_generate_frame = !painters_generating_frames.is_empty();
1273        if should_generate_frame {
1274            self.paint_api
1275                .generate_frame(painters_generating_frames.into_iter().collect());
1276        }
1277
1278        // Perform a microtask checkpoint as the specifications says that *update the rendering*
1279        // should be run in a task and a microtask checkpoint is always done when running tasks.
1280        self.perform_a_microtask_checkpoint(can_gc);
1281        should_generate_frame
1282    }
1283
1284    /// Schedule a rendering update ("update the rendering"), if necessary. This
1285    /// can be necessary for a couple reasons. For instance, when the DOM
1286    /// changes a scheduled rendering update becomes necessary if one isn't
1287    /// scheduled already. Another example is if rAFs are running but no display
1288    /// lists are being produced. In that case the [`ScriptThread`] is
1289    /// responsible for scheduling animation ticks.
1290    fn maybe_schedule_rendering_opportunity_after_ipc_message(
1291        &self,
1292        built_any_display_lists: bool,
1293    ) {
1294        let needs_rendering_update = self
1295            .documents
1296            .borrow()
1297            .iter()
1298            .any(|(_, document)| document.needs_rendering_update());
1299        let running_animations = self.documents.borrow().iter().any(|(_, document)| {
1300            document.is_fully_active() &&
1301                !document.window().throttled() &&
1302                (document.animations().running_animation_count() != 0 ||
1303                    document.has_active_request_animation_frame_callbacks())
1304        });
1305
1306        // If we are not running animations and no rendering update is
1307        // necessary, just exit early and schedule the next rendering update
1308        // when it becomes necessary.
1309        if !needs_rendering_update && !running_animations {
1310            return;
1311        }
1312
1313        // If animations are running and a reflow in this event loop iteration
1314        // produced a display list, rely on the renderer to inform us of the
1315        // next animation tick / rendering opportunity.
1316        if running_animations && built_any_display_lists {
1317            return;
1318        }
1319
1320        // There are two possibilities: rendering needs to be updated or we are
1321        // scheduling a new animation tick because animations are running, but
1322        // not changing the DOM. In the later case we can wait a bit longer
1323        // until the next "update the rendering" call as it's more efficient to
1324        // slow down rAFs that don't change the DOM.
1325        //
1326        // TODO: Should either of these delays be reduced to also reduce update latency?
1327        let animation_delay = if running_animations && !needs_rendering_update {
1328            // 30 milliseconds (33 FPS) is used here as the rendering isn't changing
1329            // so it isn't a problem to slow down rAF callback calls. In addition, this allows
1330            // renderer-based ticks to arrive first.
1331            Duration::from_millis(30)
1332        } else {
1333            // 20 milliseconds (50 FPS) is used here in order to allow any renderer-based
1334            // animation ticks to arrive first.
1335            Duration::from_millis(20)
1336        };
1337
1338        let time_since_last_rendering_opportunity = self
1339            .last_render_opportunity_time
1340            .get()
1341            .map(|last_render_opportunity_time| Instant::now() - last_render_opportunity_time)
1342            .unwrap_or(Duration::MAX)
1343            .min(animation_delay);
1344        self.schedule_update_the_rendering_timer_if_necessary(
1345            animation_delay - time_since_last_rendering_opportunity,
1346        );
1347    }
1348
1349    /// Fulfill the possibly-pending pending `document.fonts.ready` promise if
1350    /// all web fonts have loaded.
1351    fn maybe_fulfill_font_ready_promises(&self, can_gc: CanGc) {
1352        let mut sent_message = false;
1353        for (_, document) in self.documents.borrow().iter() {
1354            sent_message = document.maybe_fulfill_font_ready_promise(can_gc) || sent_message;
1355        }
1356
1357        if sent_message {
1358            self.perform_a_microtask_checkpoint(can_gc);
1359        }
1360    }
1361
1362    /// If any `Pipeline`s are waiting to become ready for the purpose of taking a
1363    /// screenshot, check to see if the `Pipeline` is now ready and send a message to the
1364    /// Constellation, if so.
1365    fn maybe_resolve_pending_screenshot_readiness_requests(&self, can_gc: CanGc) {
1366        for (_, document) in self.documents.borrow().iter() {
1367            document
1368                .window()
1369                .maybe_resolve_pending_screenshot_readiness_requests(can_gc);
1370        }
1371    }
1372
1373    /// Handle incoming messages from other tasks and the task queue.
1374    fn handle_msgs(&self, cx: &mut js::context::JSContext) -> bool {
1375        // Proritize rendering tasks and others, and gather all other events as `sequential`.
1376        let mut sequential = vec![];
1377
1378        // Notify the background-hang-monitor we are waiting for an event.
1379        self.background_hang_monitor.notify_wait();
1380
1381        // Receive at least one message so we don't spinloop.
1382        debug!("Waiting for event.");
1383        let fully_active = self.get_fully_active_document_ids();
1384        let mut event = self.receivers.recv(
1385            &self.task_queue,
1386            &self.timer_scheduler.borrow(),
1387            &fully_active,
1388        );
1389
1390        loop {
1391            debug!("Handling event: {event:?}");
1392
1393            // Dispatch any completed timers, so that their tasks can be run below.
1394            self.timer_scheduler
1395                .borrow_mut()
1396                .dispatch_completed_timers();
1397
1398            // https://html.spec.whatwg.org/multipage/#event-loop-processing-model step 7
1399            match event {
1400                // This has to be handled before the ResizeMsg below,
1401                // otherwise the page may not have been added to the
1402                // child list yet, causing the find() to fail.
1403                MixedMessage::FromConstellation(ScriptThreadMessage::SpawnPipeline(
1404                    new_pipeline_info,
1405                )) => {
1406                    self.spawn_pipeline(new_pipeline_info);
1407                },
1408                MixedMessage::FromScript(MainThreadScriptMsg::Inactive) => {
1409                    // An event came-in from a document that is not fully-active, it has been stored by the task-queue.
1410                    // Continue without adding it to "sequential".
1411                },
1412                MixedMessage::FromConstellation(ScriptThreadMessage::ExitFullScreen(id)) => self
1413                    .profile_event(ScriptThreadEventCategory::ExitFullscreen, Some(id), || {
1414                        self.handle_exit_fullscreen(id, cx);
1415                    }),
1416                _ => {
1417                    sequential.push(event);
1418                },
1419            }
1420
1421            // If any of our input sources has an event pending, we'll perform another
1422            // iteration and check for events. If there are no events pending, we'll move
1423            // on and execute the sequential events.
1424            match self.receivers.try_recv(&self.task_queue, &fully_active) {
1425                Some(new_event) => event = new_event,
1426                None => break,
1427            }
1428        }
1429
1430        // Process the gathered events.
1431        debug!("Processing events.");
1432        for msg in sequential {
1433            debug!("Processing event {:?}.", msg);
1434            let category = self.categorize_msg(&msg);
1435            let pipeline_id = msg.pipeline_id();
1436            let _realm = pipeline_id.and_then(|id| {
1437                let global = self.documents.borrow().find_global(id);
1438                global.map(|global| enter_realm(&*global))
1439            });
1440
1441            if self.closing.load(Ordering::SeqCst) {
1442                // If we've received the closed signal from the BHM, only handle exit messages.
1443                match msg {
1444                    MixedMessage::FromConstellation(ScriptThreadMessage::ExitScriptThread) => {
1445                        self.handle_exit_script_thread_msg(CanGc::from_cx(cx));
1446                        return false;
1447                    },
1448                    MixedMessage::FromConstellation(ScriptThreadMessage::ExitPipeline(
1449                        webview_id,
1450                        pipeline_id,
1451                        discard_browsing_context,
1452                    )) => {
1453                        self.handle_exit_pipeline_msg(
1454                            webview_id,
1455                            pipeline_id,
1456                            discard_browsing_context,
1457                            CanGc::from_cx(cx),
1458                        );
1459                    },
1460                    _ => {},
1461                }
1462                continue;
1463            }
1464
1465            let exiting = self.profile_event(category, pipeline_id, || {
1466                match msg {
1467                    MixedMessage::FromConstellation(ScriptThreadMessage::ExitScriptThread) => {
1468                        self.handle_exit_script_thread_msg(CanGc::from_cx(cx));
1469                        return true;
1470                    },
1471                    MixedMessage::FromConstellation(inner_msg) => {
1472                        self.handle_msg_from_constellation(inner_msg, cx)
1473                    },
1474                    MixedMessage::FromScript(inner_msg) => {
1475                        self.handle_msg_from_script(inner_msg, cx)
1476                    },
1477                    MixedMessage::FromDevtools(inner_msg) => {
1478                        self.handle_msg_from_devtools(inner_msg, cx)
1479                    },
1480                    MixedMessage::FromImageCache(inner_msg) => {
1481                        self.handle_msg_from_image_cache(inner_msg)
1482                    },
1483                    #[cfg(feature = "webgpu")]
1484                    MixedMessage::FromWebGPUServer(inner_msg) => {
1485                        self.handle_msg_from_webgpu_server(inner_msg, cx)
1486                    },
1487                    MixedMessage::TimerFired => {},
1488                }
1489
1490                false
1491            });
1492
1493            // If an `ExitScriptThread` message was handled above, bail out now.
1494            if exiting {
1495                return false;
1496            }
1497
1498            // https://html.spec.whatwg.org/multipage/#event-loop-processing-model step 6
1499            // TODO(#32003): A microtask checkpoint is only supposed to be performed after running a task.
1500            self.perform_a_microtask_checkpoint(CanGc::from_cx(cx));
1501        }
1502
1503        for (_, doc) in self.documents.borrow().iter() {
1504            let window = doc.window();
1505            window
1506                .upcast::<GlobalScope>()
1507                .perform_a_dom_garbage_collection_checkpoint();
1508        }
1509
1510        {
1511            // https://html.spec.whatwg.org/multipage/#the-end step 6
1512            let mut docs = self.docs_with_no_blocking_loads.borrow_mut();
1513            for document in docs.iter() {
1514                let _realm = enter_auto_realm(cx, &**document);
1515                document.maybe_queue_document_completion();
1516            }
1517            docs.clear();
1518        }
1519
1520        let built_any_display_lists = self.needs_rendering_update.load(Ordering::Relaxed) &&
1521            self.update_the_rendering(CanGc::from_cx(cx));
1522
1523        self.maybe_fulfill_font_ready_promises(CanGc::from_cx(cx));
1524        self.maybe_resolve_pending_screenshot_readiness_requests(CanGc::from_cx(cx));
1525
1526        // This must happen last to detect if any change above makes a rendering update necessary.
1527        self.maybe_schedule_rendering_opportunity_after_ipc_message(built_any_display_lists);
1528
1529        true
1530    }
1531
1532    fn categorize_msg(&self, msg: &MixedMessage) -> ScriptThreadEventCategory {
1533        match *msg {
1534            MixedMessage::FromConstellation(ref inner_msg) => match *inner_msg {
1535                ScriptThreadMessage::SendInputEvent(..) => ScriptThreadEventCategory::InputEvent,
1536                _ => ScriptThreadEventCategory::ConstellationMsg,
1537            },
1538            MixedMessage::FromDevtools(_) => ScriptThreadEventCategory::DevtoolsMsg,
1539            MixedMessage::FromImageCache(_) => ScriptThreadEventCategory::ImageCacheMsg,
1540            MixedMessage::FromScript(ref inner_msg) => match *inner_msg {
1541                MainThreadScriptMsg::Common(CommonScriptMsg::Task(category, ..)) => category,
1542                MainThreadScriptMsg::RegisterPaintWorklet { .. } => {
1543                    ScriptThreadEventCategory::WorkletEvent
1544                },
1545                _ => ScriptThreadEventCategory::ScriptEvent,
1546            },
1547            #[cfg(feature = "webgpu")]
1548            MixedMessage::FromWebGPUServer(_) => ScriptThreadEventCategory::WebGPUMsg,
1549            MixedMessage::TimerFired => ScriptThreadEventCategory::TimerEvent,
1550        }
1551    }
1552
1553    fn profile_event<F, R>(
1554        &self,
1555        category: ScriptThreadEventCategory,
1556        pipeline_id: Option<PipelineId>,
1557        f: F,
1558    ) -> R
1559    where
1560        F: FnOnce() -> R,
1561    {
1562        self.background_hang_monitor
1563            .notify_activity(HangAnnotation::Script(category.into()));
1564        let start = Instant::now();
1565        let value = if self.profile_script_events {
1566            let profiler_chan = self.senders.time_profiler_sender.clone();
1567            match category {
1568                ScriptThreadEventCategory::SpawnPipeline => {
1569                    time_profile!(
1570                        ProfilerCategory::ScriptSpawnPipeline,
1571                        None,
1572                        profiler_chan,
1573                        f
1574                    )
1575                },
1576                ScriptThreadEventCategory::ConstellationMsg => time_profile!(
1577                    ProfilerCategory::ScriptConstellationMsg,
1578                    None,
1579                    profiler_chan,
1580                    f
1581                ),
1582                ScriptThreadEventCategory::DatabaseAccessEvent => time_profile!(
1583                    ProfilerCategory::ScriptDatabaseAccessEvent,
1584                    None,
1585                    profiler_chan,
1586                    f
1587                ),
1588                ScriptThreadEventCategory::DevtoolsMsg => {
1589                    time_profile!(ProfilerCategory::ScriptDevtoolsMsg, None, profiler_chan, f)
1590                },
1591                ScriptThreadEventCategory::DocumentEvent => time_profile!(
1592                    ProfilerCategory::ScriptDocumentEvent,
1593                    None,
1594                    profiler_chan,
1595                    f
1596                ),
1597                ScriptThreadEventCategory::InputEvent => {
1598                    time_profile!(ProfilerCategory::ScriptInputEvent, None, profiler_chan, f)
1599                },
1600                ScriptThreadEventCategory::FileRead => {
1601                    time_profile!(ProfilerCategory::ScriptFileRead, None, profiler_chan, f)
1602                },
1603                ScriptThreadEventCategory::FontLoading => {
1604                    time_profile!(ProfilerCategory::ScriptFontLoading, None, profiler_chan, f)
1605                },
1606                ScriptThreadEventCategory::FormPlannedNavigation => time_profile!(
1607                    ProfilerCategory::ScriptPlannedNavigation,
1608                    None,
1609                    profiler_chan,
1610                    f
1611                ),
1612                ScriptThreadEventCategory::GeolocationEvent => {
1613                    time_profile!(
1614                        ProfilerCategory::ScriptGeolocationEvent,
1615                        None,
1616                        profiler_chan,
1617                        f
1618                    )
1619                },
1620                ScriptThreadEventCategory::HistoryEvent => {
1621                    time_profile!(ProfilerCategory::ScriptHistoryEvent, None, profiler_chan, f)
1622                },
1623                ScriptThreadEventCategory::ImageCacheMsg => time_profile!(
1624                    ProfilerCategory::ScriptImageCacheMsg,
1625                    None,
1626                    profiler_chan,
1627                    f
1628                ),
1629                ScriptThreadEventCategory::NetworkEvent => {
1630                    time_profile!(ProfilerCategory::ScriptNetworkEvent, None, profiler_chan, f)
1631                },
1632                ScriptThreadEventCategory::PortMessage => {
1633                    time_profile!(ProfilerCategory::ScriptPortMessage, None, profiler_chan, f)
1634                },
1635                ScriptThreadEventCategory::Resize => {
1636                    time_profile!(ProfilerCategory::ScriptResize, None, profiler_chan, f)
1637                },
1638                ScriptThreadEventCategory::ScriptEvent => {
1639                    time_profile!(ProfilerCategory::ScriptEvent, None, profiler_chan, f)
1640                },
1641                ScriptThreadEventCategory::SetScrollState => time_profile!(
1642                    ProfilerCategory::ScriptSetScrollState,
1643                    None,
1644                    profiler_chan,
1645                    f
1646                ),
1647                ScriptThreadEventCategory::UpdateReplacedElement => time_profile!(
1648                    ProfilerCategory::ScriptUpdateReplacedElement,
1649                    None,
1650                    profiler_chan,
1651                    f
1652                ),
1653                ScriptThreadEventCategory::StylesheetLoad => time_profile!(
1654                    ProfilerCategory::ScriptStylesheetLoad,
1655                    None,
1656                    profiler_chan,
1657                    f
1658                ),
1659                ScriptThreadEventCategory::SetViewport => {
1660                    time_profile!(ProfilerCategory::ScriptSetViewport, None, profiler_chan, f)
1661                },
1662                ScriptThreadEventCategory::TimerEvent => {
1663                    time_profile!(ProfilerCategory::ScriptTimerEvent, None, profiler_chan, f)
1664                },
1665                ScriptThreadEventCategory::WebSocketEvent => time_profile!(
1666                    ProfilerCategory::ScriptWebSocketEvent,
1667                    None,
1668                    profiler_chan,
1669                    f
1670                ),
1671                ScriptThreadEventCategory::WorkerEvent => {
1672                    time_profile!(ProfilerCategory::ScriptWorkerEvent, None, profiler_chan, f)
1673                },
1674                ScriptThreadEventCategory::WorkletEvent => {
1675                    time_profile!(ProfilerCategory::ScriptWorkletEvent, None, profiler_chan, f)
1676                },
1677                ScriptThreadEventCategory::ServiceWorkerEvent => time_profile!(
1678                    ProfilerCategory::ScriptServiceWorkerEvent,
1679                    None,
1680                    profiler_chan,
1681                    f
1682                ),
1683                ScriptThreadEventCategory::EnterFullscreen => time_profile!(
1684                    ProfilerCategory::ScriptEnterFullscreen,
1685                    None,
1686                    profiler_chan,
1687                    f
1688                ),
1689                ScriptThreadEventCategory::ExitFullscreen => time_profile!(
1690                    ProfilerCategory::ScriptExitFullscreen,
1691                    None,
1692                    profiler_chan,
1693                    f
1694                ),
1695                ScriptThreadEventCategory::PerformanceTimelineTask => time_profile!(
1696                    ProfilerCategory::ScriptPerformanceEvent,
1697                    None,
1698                    profiler_chan,
1699                    f
1700                ),
1701                ScriptThreadEventCategory::Rendering => {
1702                    time_profile!(ProfilerCategory::ScriptRendering, None, profiler_chan, f)
1703                },
1704                #[cfg(feature = "webgpu")]
1705                ScriptThreadEventCategory::WebGPUMsg => {
1706                    time_profile!(ProfilerCategory::ScriptWebGPUMsg, None, profiler_chan, f)
1707                },
1708            }
1709        } else {
1710            f()
1711        };
1712        let task_duration = start.elapsed();
1713        for (doc_id, doc) in self.documents.borrow().iter() {
1714            if let Some(pipeline_id) = pipeline_id {
1715                if pipeline_id == doc_id && task_duration.as_nanos() > MAX_TASK_NS {
1716                    if self.print_pwm {
1717                        println!(
1718                            "Task took longer than max allowed ({:?}) {:?}",
1719                            category,
1720                            task_duration.as_nanos()
1721                        );
1722                    }
1723                    doc.start_tti();
1724                }
1725            }
1726            doc.record_tti_if_necessary();
1727        }
1728        value
1729    }
1730
1731    fn handle_msg_from_constellation(
1732        &self,
1733        msg: ScriptThreadMessage,
1734        cx: &mut js::context::JSContext,
1735    ) {
1736        match msg {
1737            ScriptThreadMessage::StopDelayingLoadEventsMode(pipeline_id) => {
1738                self.handle_stop_delaying_load_events_mode(pipeline_id)
1739            },
1740            ScriptThreadMessage::NavigateIframe(
1741                parent_pipeline_id,
1742                browsing_context_id,
1743                load_data,
1744                history_handling,
1745            ) => self.handle_navigate_iframe(
1746                parent_pipeline_id,
1747                browsing_context_id,
1748                load_data,
1749                history_handling,
1750                CanGc::from_cx(cx),
1751            ),
1752            ScriptThreadMessage::UnloadDocument(pipeline_id) => {
1753                self.handle_unload_document(pipeline_id, CanGc::from_cx(cx))
1754            },
1755            ScriptThreadMessage::ResizeInactive(id, new_size) => {
1756                self.handle_resize_inactive_msg(id, new_size)
1757            },
1758            ScriptThreadMessage::ThemeChange(_, theme) => {
1759                self.handle_theme_change_msg(theme);
1760            },
1761            ScriptThreadMessage::GetTitle(pipeline_id) => self.handle_get_title_msg(pipeline_id),
1762            ScriptThreadMessage::SetDocumentActivity(pipeline_id, activity) => {
1763                self.handle_set_document_activity_msg(pipeline_id, activity, CanGc::from_cx(cx))
1764            },
1765            ScriptThreadMessage::SetThrottled(webview_id, pipeline_id, throttled) => {
1766                self.handle_set_throttled_msg(webview_id, pipeline_id, throttled)
1767            },
1768            ScriptThreadMessage::SetThrottledInContainingIframe(
1769                _,
1770                parent_pipeline_id,
1771                browsing_context_id,
1772                throttled,
1773            ) => self.handle_set_throttled_in_containing_iframe_msg(
1774                parent_pipeline_id,
1775                browsing_context_id,
1776                throttled,
1777            ),
1778            ScriptThreadMessage::PostMessage {
1779                target: target_pipeline_id,
1780                source_webview,
1781                source_with_ancestry,
1782                target_origin: origin,
1783                source_origin,
1784                data,
1785            } => self.handle_post_message_msg(
1786                target_pipeline_id,
1787                source_webview,
1788                source_with_ancestry,
1789                origin,
1790                source_origin,
1791                *data,
1792            ),
1793            ScriptThreadMessage::UpdatePipelineId(
1794                parent_pipeline_id,
1795                browsing_context_id,
1796                webview_id,
1797                new_pipeline_id,
1798                reason,
1799            ) => self.handle_update_pipeline_id(
1800                parent_pipeline_id,
1801                browsing_context_id,
1802                webview_id,
1803                new_pipeline_id,
1804                reason,
1805                CanGc::from_cx(cx),
1806            ),
1807            ScriptThreadMessage::UpdateHistoryState(pipeline_id, history_state_id, url) => self
1808                .handle_update_history_state_msg(
1809                    pipeline_id,
1810                    history_state_id,
1811                    url,
1812                    CanGc::from_cx(cx),
1813                ),
1814            ScriptThreadMessage::RemoveHistoryStates(pipeline_id, history_states) => {
1815                self.handle_remove_history_states(pipeline_id, history_states)
1816            },
1817            ScriptThreadMessage::FocusIFrame(parent_pipeline_id, frame_id, sequence) => self
1818                .handle_focus_iframe_msg(
1819                    parent_pipeline_id,
1820                    frame_id,
1821                    sequence,
1822                    CanGc::from_cx(cx),
1823                ),
1824            ScriptThreadMessage::FocusDocument(pipeline_id, sequence) => {
1825                self.handle_focus_document_msg(pipeline_id, sequence, CanGc::from_cx(cx))
1826            },
1827            ScriptThreadMessage::Unfocus(pipeline_id, sequence) => {
1828                self.handle_unfocus_msg(pipeline_id, sequence, CanGc::from_cx(cx))
1829            },
1830            ScriptThreadMessage::WebDriverScriptCommand(pipeline_id, msg) => {
1831                self.handle_webdriver_msg(pipeline_id, msg, cx)
1832            },
1833            ScriptThreadMessage::WebFontLoaded(pipeline_id, success) => {
1834                self.handle_web_font_loaded(pipeline_id, success)
1835            },
1836            ScriptThreadMessage::DispatchIFrameLoadEvent {
1837                target: browsing_context_id,
1838                parent: parent_id,
1839                child: child_id,
1840            } => self.handle_iframe_load_event(
1841                parent_id,
1842                browsing_context_id,
1843                child_id,
1844                CanGc::from_cx(cx),
1845            ),
1846            ScriptThreadMessage::DispatchStorageEvent(
1847                pipeline_id,
1848                storage,
1849                url,
1850                key,
1851                old_value,
1852                new_value,
1853            ) => self.handle_storage_event(pipeline_id, storage, url, key, old_value, new_value),
1854            ScriptThreadMessage::ReportCSSError(pipeline_id, filename, line, column, msg) => {
1855                self.handle_css_error_reporting(pipeline_id, filename, line, column, msg)
1856            },
1857            ScriptThreadMessage::Reload(pipeline_id) => {
1858                self.handle_reload(pipeline_id, CanGc::from_cx(cx))
1859            },
1860            ScriptThreadMessage::Resize(id, size, size_type) => {
1861                self.handle_resize_message(id, size, size_type);
1862            },
1863            ScriptThreadMessage::ExitPipeline(
1864                webview_id,
1865                pipeline_id,
1866                discard_browsing_context,
1867            ) => self.handle_exit_pipeline_msg(
1868                webview_id,
1869                pipeline_id,
1870                discard_browsing_context,
1871                CanGc::from_cx(cx),
1872            ),
1873            ScriptThreadMessage::PaintMetric(
1874                pipeline_id,
1875                metric_type,
1876                metric_value,
1877                first_reflow,
1878            ) => self.handle_paint_metric(
1879                pipeline_id,
1880                metric_type,
1881                metric_value,
1882                first_reflow,
1883                CanGc::from_cx(cx),
1884            ),
1885            ScriptThreadMessage::MediaSessionAction(pipeline_id, action) => {
1886                self.handle_media_session_action(pipeline_id, action, CanGc::from_cx(cx))
1887            },
1888            ScriptThreadMessage::SendInputEvent(webview_id, id, event) => {
1889                self.handle_input_event(webview_id, id, event)
1890            },
1891            #[cfg(feature = "webgpu")]
1892            ScriptThreadMessage::SetWebGPUPort(port) => {
1893                *self.receivers.webgpu_receiver.borrow_mut() = port.route_preserving_errors();
1894            },
1895            ScriptThreadMessage::TickAllAnimations(_webviews) => {
1896                self.set_needs_rendering_update();
1897            },
1898            ScriptThreadMessage::NoLongerWaitingOnAsychronousImageUpdates(pipeline_id) => {
1899                if let Some(document) = self.documents.borrow().find_document(pipeline_id) {
1900                    document.handle_no_longer_waiting_on_asynchronous_image_updates();
1901                }
1902            },
1903            msg @ ScriptThreadMessage::SpawnPipeline(..) |
1904            msg @ ScriptThreadMessage::ExitFullScreen(..) |
1905            msg @ ScriptThreadMessage::ExitScriptThread => {
1906                panic!("should have handled {:?} already", msg)
1907            },
1908            ScriptThreadMessage::SetScrollStates(pipeline_id, scroll_states) => {
1909                self.handle_set_scroll_states(pipeline_id, scroll_states)
1910            },
1911            ScriptThreadMessage::EvaluateJavaScript(
1912                webview_id,
1913                pipeline_id,
1914                evaluation_id,
1915                script,
1916            ) => {
1917                self.handle_evaluate_javascript(webview_id, pipeline_id, evaluation_id, script, cx);
1918            },
1919            ScriptThreadMessage::SendImageKeysBatch(pipeline_id, image_keys) => {
1920                if let Some(window) = self.documents.borrow().find_window(pipeline_id) {
1921                    window
1922                        .image_cache()
1923                        .fill_key_cache_with_batch_of_keys(image_keys);
1924                } else {
1925                    warn!(
1926                        "Could not find window corresponding to an image cache to send image keys to pipeline {:?}",
1927                        pipeline_id
1928                    );
1929                }
1930            },
1931            ScriptThreadMessage::RefreshCursor(pipeline_id) => {
1932                self.handle_refresh_cursor(pipeline_id);
1933            },
1934            ScriptThreadMessage::PreferencesUpdated(updates) => {
1935                let mut current_preferences = prefs::get().clone();
1936                for (name, value) in updates {
1937                    current_preferences.set_value(&name, value);
1938                }
1939                prefs::set(current_preferences);
1940            },
1941            ScriptThreadMessage::ForwardKeyboardScroll(pipeline_id, scroll) => {
1942                if let Some(document) = self.documents.borrow().find_document(pipeline_id) {
1943                    document.event_handler().do_keyboard_scroll(scroll);
1944                }
1945            },
1946            ScriptThreadMessage::RequestScreenshotReadiness(webview_id, pipeline_id) => {
1947                self.handle_request_screenshot_readiness(
1948                    webview_id,
1949                    pipeline_id,
1950                    CanGc::from_cx(cx),
1951                );
1952            },
1953            ScriptThreadMessage::EmbedderControlResponse(id, response) => {
1954                self.handle_embedder_control_response(id, response, CanGc::from_cx(cx));
1955            },
1956            ScriptThreadMessage::SetUserContents(user_content_manager_id, user_contents) => {
1957                self.user_contents_for_manager_id
1958                    .borrow_mut()
1959                    .insert(user_content_manager_id, user_contents.into());
1960            },
1961            ScriptThreadMessage::DestroyUserContentManager(user_content_manager_id) => {
1962                self.user_contents_for_manager_id
1963                    .borrow_mut()
1964                    .remove(&user_content_manager_id);
1965            },
1966            ScriptThreadMessage::AccessibilityTreeUpdate(webview_id, tree_update) => {
1967                let _ = self.senders.pipeline_to_embedder_sender.send(
1968                    EmbedderMsg::AccessibilityTreeUpdate(webview_id, tree_update),
1969                );
1970            },
1971            ScriptThreadMessage::UpdatePinchZoomInfos(id, pinch_zoom_infos) => {
1972                self.handle_update_pinch_zoom_infos(id, pinch_zoom_infos, CanGc::from_cx(cx));
1973            },
1974            ScriptThreadMessage::SetAccessibilityActive(active) => {
1975                self.set_accessibility_active(active);
1976            },
1977        }
1978    }
1979
1980    fn handle_set_scroll_states(
1981        &self,
1982        pipeline_id: PipelineId,
1983        scroll_states: FxHashMap<ExternalScrollId, LayoutVector2D>,
1984    ) {
1985        let Some(window) = self.documents.borrow().find_window(pipeline_id) else {
1986            warn!("Received scroll states for closed pipeline {pipeline_id}");
1987            return;
1988        };
1989
1990        self.profile_event(
1991            ScriptThreadEventCategory::SetScrollState,
1992            Some(pipeline_id),
1993            || {
1994                window
1995                    .layout_mut()
1996                    .set_scroll_offsets_from_renderer(&scroll_states);
1997            },
1998        )
1999    }
2000
2001    #[cfg(feature = "webgpu")]
2002    fn handle_msg_from_webgpu_server(&self, msg: WebGPUMsg, cx: &mut js::context::JSContext) {
2003        match msg {
2004            WebGPUMsg::FreeAdapter(id) => self.gpu_id_hub.free_adapter_id(id),
2005            WebGPUMsg::FreeDevice {
2006                device_id,
2007                pipeline_id,
2008            } => {
2009                self.gpu_id_hub.free_device_id(device_id);
2010                if let Some(global) = self.documents.borrow().find_global(pipeline_id) {
2011                    global.remove_gpu_device(WebGPUDevice(device_id));
2012                } // page can already be destroyed
2013            },
2014            WebGPUMsg::FreeBuffer(id) => self.gpu_id_hub.free_buffer_id(id),
2015            WebGPUMsg::FreePipelineLayout(id) => self.gpu_id_hub.free_pipeline_layout_id(id),
2016            WebGPUMsg::FreeComputePipeline(id) => self.gpu_id_hub.free_compute_pipeline_id(id),
2017            WebGPUMsg::FreeBindGroup(id) => self.gpu_id_hub.free_bind_group_id(id),
2018            WebGPUMsg::FreeBindGroupLayout(id) => self.gpu_id_hub.free_bind_group_layout_id(id),
2019            WebGPUMsg::FreeCommandBuffer(id) => self
2020                .gpu_id_hub
2021                .free_command_buffer_id(id.into_command_encoder_id()),
2022            WebGPUMsg::FreeSampler(id) => self.gpu_id_hub.free_sampler_id(id),
2023            WebGPUMsg::FreeShaderModule(id) => self.gpu_id_hub.free_shader_module_id(id),
2024            WebGPUMsg::FreeRenderBundle(id) => self.gpu_id_hub.free_render_bundle_id(id),
2025            WebGPUMsg::FreeRenderPipeline(id) => self.gpu_id_hub.free_render_pipeline_id(id),
2026            WebGPUMsg::FreeTexture(id) => self.gpu_id_hub.free_texture_id(id),
2027            WebGPUMsg::FreeTextureView(id) => self.gpu_id_hub.free_texture_view_id(id),
2028            WebGPUMsg::FreeComputePass(id) => self.gpu_id_hub.free_compute_pass_id(id),
2029            WebGPUMsg::FreeRenderPass(id) => self.gpu_id_hub.free_render_pass_id(id),
2030            WebGPUMsg::Exit => {
2031                *self.receivers.webgpu_receiver.borrow_mut() = crossbeam_channel::never()
2032            },
2033            WebGPUMsg::DeviceLost {
2034                pipeline_id,
2035                device,
2036                reason,
2037                msg,
2038            } => {
2039                let global = self.documents.borrow().find_global(pipeline_id).unwrap();
2040                global.gpu_device_lost(device, reason, msg);
2041            },
2042            WebGPUMsg::UncapturedError {
2043                device,
2044                pipeline_id,
2045                error,
2046            } => {
2047                let global = self.documents.borrow().find_global(pipeline_id).unwrap();
2048                let _ac = enter_auto_realm(cx, &*global);
2049                global.handle_uncaptured_gpu_error(device, error);
2050            },
2051            _ => {},
2052        }
2053    }
2054
2055    fn handle_msg_from_script(&self, msg: MainThreadScriptMsg, cx: &mut js::context::JSContext) {
2056        match msg {
2057            MainThreadScriptMsg::Common(CommonScriptMsg::Task(_, task, pipeline_id, _)) => {
2058                let _realm = pipeline_id.and_then(|id| {
2059                    let global = self.documents.borrow().find_global(id);
2060                    global.map(|global| enter_realm(&*global))
2061                });
2062                task.run_box(cx)
2063            },
2064            MainThreadScriptMsg::Common(CommonScriptMsg::CollectReports(chan)) => {
2065                self.collect_reports(chan)
2066            },
2067            MainThreadScriptMsg::Common(CommonScriptMsg::ReportCspViolations(
2068                pipeline_id,
2069                violations,
2070            )) => {
2071                if let Some(global) = self.documents.borrow().find_global(pipeline_id) {
2072                    global.report_csp_violations(violations, None, None);
2073                }
2074            },
2075            MainThreadScriptMsg::NavigationResponse {
2076                pipeline_id,
2077                message,
2078            } => {
2079                self.handle_navigation_response(cx, pipeline_id, *message);
2080            },
2081            MainThreadScriptMsg::WorkletLoaded(pipeline_id) => {
2082                self.handle_worklet_loaded(pipeline_id)
2083            },
2084            MainThreadScriptMsg::RegisterPaintWorklet {
2085                pipeline_id,
2086                name,
2087                properties,
2088                painter,
2089            } => self.handle_register_paint_worklet(pipeline_id, name, properties, painter),
2090            MainThreadScriptMsg::Inactive => {},
2091            MainThreadScriptMsg::WakeUp => {},
2092            MainThreadScriptMsg::ForwardEmbedderControlResponseFromFileManager(
2093                control_id,
2094                response,
2095            ) => {
2096                self.handle_embedder_control_response(control_id, response, CanGc::from_cx(cx));
2097            },
2098        }
2099    }
2100
2101    fn handle_msg_from_devtools(
2102        &self,
2103        msg: DevtoolScriptControlMsg,
2104        cx: &mut js::context::JSContext,
2105    ) {
2106        let documents = self.documents.borrow();
2107        match msg {
2108            DevtoolScriptControlMsg::EvaluateJS(id, s, reply) => match documents.find_window(id) {
2109                Some(window) => {
2110                    let global = window.as_global_scope();
2111                    let _aes = AutoEntryScript::new(global);
2112                    devtools::handle_evaluate_js(global, s, reply, cx)
2113                },
2114                None => warn!("Message sent to closed pipeline {}.", id),
2115            },
2116            DevtoolScriptControlMsg::GetEventListenerInfo(id, node, reply) => {
2117                devtools::handle_get_event_listener_info(&documents, id, &node, reply)
2118            },
2119            DevtoolScriptControlMsg::GetRootNode(id, reply) => {
2120                devtools::handle_get_root_node(&documents, id, reply, CanGc::from_cx(cx))
2121            },
2122            DevtoolScriptControlMsg::GetDocumentElement(id, reply) => {
2123                devtools::handle_get_document_element(&documents, id, reply, CanGc::from_cx(cx))
2124            },
2125            DevtoolScriptControlMsg::GetChildren(id, node_id, reply) => {
2126                devtools::handle_get_children(&documents, id, node_id, reply, CanGc::from_cx(cx))
2127            },
2128            DevtoolScriptControlMsg::GetAttributeStyle(id, node_id, reply) => {
2129                devtools::handle_get_attribute_style(
2130                    &documents,
2131                    id,
2132                    node_id,
2133                    reply,
2134                    CanGc::from_cx(cx),
2135                )
2136            },
2137            DevtoolScriptControlMsg::GetStylesheetStyle(
2138                id,
2139                node_id,
2140                selector,
2141                stylesheet,
2142                reply,
2143            ) => devtools::handle_get_stylesheet_style(
2144                &documents,
2145                id,
2146                node_id,
2147                selector,
2148                stylesheet,
2149                reply,
2150                CanGc::from_cx(cx),
2151            ),
2152            DevtoolScriptControlMsg::GetSelectors(id, node_id, reply) => {
2153                devtools::handle_get_selectors(&documents, id, node_id, reply, CanGc::from_cx(cx))
2154            },
2155            DevtoolScriptControlMsg::GetComputedStyle(id, node_id, reply) => {
2156                devtools::handle_get_computed_style(&documents, id, node_id, reply)
2157            },
2158            DevtoolScriptControlMsg::GetLayout(id, node_id, reply) => {
2159                devtools::handle_get_layout(&documents, id, node_id, reply, CanGc::from_cx(cx))
2160            },
2161            DevtoolScriptControlMsg::GetXPath(id, node_id, reply) => {
2162                devtools::handle_get_xpath(&documents, id, node_id, reply)
2163            },
2164            DevtoolScriptControlMsg::ModifyAttribute(id, node_id, modifications) => {
2165                devtools::handle_modify_attribute(
2166                    &documents,
2167                    id,
2168                    node_id,
2169                    modifications,
2170                    CanGc::from_cx(cx),
2171                )
2172            },
2173            DevtoolScriptControlMsg::ModifyRule(id, node_id, modifications) => {
2174                devtools::handle_modify_rule(
2175                    &documents,
2176                    id,
2177                    node_id,
2178                    modifications,
2179                    CanGc::from_cx(cx),
2180                )
2181            },
2182            DevtoolScriptControlMsg::WantsLiveNotifications(id, to_send) => match documents
2183                .find_window(id)
2184            {
2185                Some(window) => devtools::handle_wants_live_notifications(window.upcast(), to_send),
2186                None => warn!("Message sent to closed pipeline {}.", id),
2187            },
2188            DevtoolScriptControlMsg::SetTimelineMarkers(id, marker_types, reply) => {
2189                devtools::handle_set_timeline_markers(&documents, id, marker_types, reply)
2190            },
2191            DevtoolScriptControlMsg::DropTimelineMarkers(id, marker_types) => {
2192                devtools::handle_drop_timeline_markers(&documents, id, marker_types)
2193            },
2194            DevtoolScriptControlMsg::RequestAnimationFrame(id, name) => {
2195                devtools::handle_request_animation_frame(&documents, id, name)
2196            },
2197            DevtoolScriptControlMsg::Reload(id) => self.handle_reload(id, CanGc::from_cx(cx)),
2198            DevtoolScriptControlMsg::GetCssDatabase(reply) => {
2199                devtools::handle_get_css_database(reply)
2200            },
2201            DevtoolScriptControlMsg::SimulateColorScheme(id, theme) => {
2202                match documents.find_window(id) {
2203                    Some(window) => {
2204                        window.set_theme(theme);
2205                    },
2206                    None => warn!("Message sent to closed pipeline {}.", id),
2207                }
2208            },
2209            DevtoolScriptControlMsg::HighlightDomNode(id, node_id) => {
2210                devtools::handle_highlight_dom_node(&documents, id, node_id)
2211            },
2212            DevtoolScriptControlMsg::Eval(code, id, reply) => {
2213                self.debugger_global
2214                    .fire_eval(CanGc::from_cx(cx), code.into(), id, None, reply);
2215            },
2216            DevtoolScriptControlMsg::GetPossibleBreakpoints(spidermonkey_id, result_sender) => {
2217                self.debugger_global.fire_get_possible_breakpoints(
2218                    CanGc::from_cx(cx),
2219                    spidermonkey_id,
2220                    result_sender,
2221                );
2222            },
2223            DevtoolScriptControlMsg::SetBreakpoint(spidermonkey_id, script_id, offset) => {
2224                self.debugger_global.fire_set_breakpoint(
2225                    CanGc::from_cx(cx),
2226                    spidermonkey_id,
2227                    script_id,
2228                    offset,
2229                );
2230            },
2231            DevtoolScriptControlMsg::ClearBreakpoint(spidermonkey_id, script_id, offset) => {
2232                self.debugger_global.fire_clear_breakpoint(
2233                    CanGc::from_cx(cx),
2234                    spidermonkey_id,
2235                    script_id,
2236                    offset,
2237                );
2238            },
2239            DevtoolScriptControlMsg::Pause(result_sender) => {
2240                self.debugger_global
2241                    .fire_pause(CanGc::from_cx(cx), result_sender);
2242            },
2243            DevtoolScriptControlMsg::Resume => {
2244                self.debugger_paused.set(false);
2245            },
2246        }
2247    }
2248
2249    /// Enter a nested event loop for debugger pause.
2250    /// TODO: This should also be called when manual pause is triggered.
2251    pub(crate) fn enter_debugger_pause_loop(&self) {
2252        self.debugger_paused.set(true);
2253
2254        #[allow(unsafe_code)]
2255        let mut cx = unsafe { js::context::JSContext::from_ptr(js::rust::Runtime::get().unwrap()) };
2256
2257        while self.debugger_paused.get() {
2258            match self.receivers.devtools_server_receiver.recv() {
2259                Ok(Ok(msg)) => self.handle_msg_from_devtools(msg, &mut cx),
2260                _ => {
2261                    self.debugger_paused.set(false);
2262                    break;
2263                },
2264            }
2265        }
2266    }
2267
2268    fn handle_msg_from_image_cache(&self, response: ImageCacheResponseMessage) {
2269        match response {
2270            ImageCacheResponseMessage::NotifyPendingImageLoadStatus(pending_image_response) => {
2271                let window = self
2272                    .documents
2273                    .borrow()
2274                    .find_window(pending_image_response.pipeline_id);
2275                if let Some(ref window) = window {
2276                    window.pending_image_notification(pending_image_response);
2277                }
2278            },
2279            ImageCacheResponseMessage::VectorImageRasterizationComplete(response) => {
2280                let window = self.documents.borrow().find_window(response.pipeline_id);
2281                if let Some(ref window) = window {
2282                    window.handle_image_rasterization_complete_notification(response);
2283                }
2284            },
2285        };
2286    }
2287
2288    fn handle_webdriver_msg(
2289        &self,
2290        pipeline_id: PipelineId,
2291        msg: WebDriverScriptCommand,
2292        cx: &mut js::context::JSContext,
2293    ) {
2294        let documents = self.documents.borrow();
2295        match msg {
2296            WebDriverScriptCommand::AddCookie(params, reply) => {
2297                webdriver_handlers::handle_add_cookie(&documents, pipeline_id, params, reply)
2298            },
2299            WebDriverScriptCommand::DeleteCookies(reply) => {
2300                webdriver_handlers::handle_delete_cookies(&documents, pipeline_id, reply)
2301            },
2302            WebDriverScriptCommand::DeleteCookie(name, reply) => {
2303                webdriver_handlers::handle_delete_cookie(&documents, pipeline_id, name, reply)
2304            },
2305            WebDriverScriptCommand::ElementClear(element_id, reply) => {
2306                webdriver_handlers::handle_element_clear(
2307                    &documents,
2308                    pipeline_id,
2309                    element_id,
2310                    reply,
2311                    CanGc::from_cx(cx),
2312                )
2313            },
2314            WebDriverScriptCommand::FindElementsCSSSelector(selector, reply) => {
2315                webdriver_handlers::handle_find_elements_css_selector(
2316                    &documents,
2317                    pipeline_id,
2318                    selector,
2319                    reply,
2320                )
2321            },
2322            WebDriverScriptCommand::FindElementsLinkText(selector, partial, reply) => {
2323                webdriver_handlers::handle_find_elements_link_text(
2324                    &documents,
2325                    pipeline_id,
2326                    selector,
2327                    partial,
2328                    reply,
2329                )
2330            },
2331            WebDriverScriptCommand::FindElementsTagName(selector, reply) => {
2332                webdriver_handlers::handle_find_elements_tag_name(
2333                    &documents,
2334                    pipeline_id,
2335                    selector,
2336                    reply,
2337                    CanGc::from_cx(cx),
2338                )
2339            },
2340            WebDriverScriptCommand::FindElementsXpathSelector(selector, reply) => {
2341                webdriver_handlers::handle_find_elements_xpath_selector(
2342                    &documents,
2343                    pipeline_id,
2344                    selector,
2345                    reply,
2346                    CanGc::from_cx(cx),
2347                )
2348            },
2349            WebDriverScriptCommand::FindElementElementsCSSSelector(selector, element_id, reply) => {
2350                webdriver_handlers::handle_find_element_elements_css_selector(
2351                    &documents,
2352                    pipeline_id,
2353                    element_id,
2354                    selector,
2355                    reply,
2356                )
2357            },
2358            WebDriverScriptCommand::FindElementElementsLinkText(
2359                selector,
2360                element_id,
2361                partial,
2362                reply,
2363            ) => webdriver_handlers::handle_find_element_elements_link_text(
2364                &documents,
2365                pipeline_id,
2366                element_id,
2367                selector,
2368                partial,
2369                reply,
2370            ),
2371            WebDriverScriptCommand::FindElementElementsTagName(selector, element_id, reply) => {
2372                webdriver_handlers::handle_find_element_elements_tag_name(
2373                    &documents,
2374                    pipeline_id,
2375                    element_id,
2376                    selector,
2377                    reply,
2378                    CanGc::from_cx(cx),
2379                )
2380            },
2381            WebDriverScriptCommand::FindElementElementsXPathSelector(
2382                selector,
2383                element_id,
2384                reply,
2385            ) => webdriver_handlers::handle_find_element_elements_xpath_selector(
2386                &documents,
2387                pipeline_id,
2388                element_id,
2389                selector,
2390                reply,
2391                CanGc::from_cx(cx),
2392            ),
2393            WebDriverScriptCommand::FindShadowElementsCSSSelector(
2394                selector,
2395                shadow_root_id,
2396                reply,
2397            ) => webdriver_handlers::handle_find_shadow_elements_css_selector(
2398                &documents,
2399                pipeline_id,
2400                shadow_root_id,
2401                selector,
2402                reply,
2403            ),
2404            WebDriverScriptCommand::FindShadowElementsLinkText(
2405                selector,
2406                shadow_root_id,
2407                partial,
2408                reply,
2409            ) => webdriver_handlers::handle_find_shadow_elements_link_text(
2410                &documents,
2411                pipeline_id,
2412                shadow_root_id,
2413                selector,
2414                partial,
2415                reply,
2416            ),
2417            WebDriverScriptCommand::FindShadowElementsTagName(selector, shadow_root_id, reply) => {
2418                webdriver_handlers::handle_find_shadow_elements_tag_name(
2419                    &documents,
2420                    pipeline_id,
2421                    shadow_root_id,
2422                    selector,
2423                    reply,
2424                )
2425            },
2426            WebDriverScriptCommand::FindShadowElementsXPathSelector(
2427                selector,
2428                shadow_root_id,
2429                reply,
2430            ) => webdriver_handlers::handle_find_shadow_elements_xpath_selector(
2431                &documents,
2432                pipeline_id,
2433                shadow_root_id,
2434                selector,
2435                reply,
2436                CanGc::from_cx(cx),
2437            ),
2438            WebDriverScriptCommand::GetElementShadowRoot(element_id, reply) => {
2439                webdriver_handlers::handle_get_element_shadow_root(
2440                    &documents,
2441                    pipeline_id,
2442                    element_id,
2443                    reply,
2444                )
2445            },
2446            WebDriverScriptCommand::ElementClick(element_id, reply) => {
2447                webdriver_handlers::handle_element_click(
2448                    &documents,
2449                    pipeline_id,
2450                    element_id,
2451                    reply,
2452                    CanGc::from_cx(cx),
2453                )
2454            },
2455            WebDriverScriptCommand::GetKnownElement(element_id, reply) => {
2456                webdriver_handlers::handle_get_known_element(
2457                    &documents,
2458                    pipeline_id,
2459                    element_id,
2460                    reply,
2461                )
2462            },
2463            WebDriverScriptCommand::GetKnownWindow(webview_id, reply) => {
2464                webdriver_handlers::handle_get_known_window(
2465                    &documents,
2466                    pipeline_id,
2467                    webview_id,
2468                    reply,
2469                )
2470            },
2471            WebDriverScriptCommand::GetKnownShadowRoot(element_id, reply) => {
2472                webdriver_handlers::handle_get_known_shadow_root(
2473                    &documents,
2474                    pipeline_id,
2475                    element_id,
2476                    reply,
2477                )
2478            },
2479            WebDriverScriptCommand::GetActiveElement(reply) => {
2480                webdriver_handlers::handle_get_active_element(&documents, pipeline_id, reply)
2481            },
2482            WebDriverScriptCommand::GetComputedRole(node_id, reply) => {
2483                webdriver_handlers::handle_get_computed_role(
2484                    &documents,
2485                    pipeline_id,
2486                    node_id,
2487                    reply,
2488                )
2489            },
2490            WebDriverScriptCommand::GetPageSource(reply) => {
2491                webdriver_handlers::handle_get_page_source(
2492                    &documents,
2493                    pipeline_id,
2494                    reply,
2495                    CanGc::from_cx(cx),
2496                )
2497            },
2498            WebDriverScriptCommand::GetCookies(reply) => {
2499                webdriver_handlers::handle_get_cookies(&documents, pipeline_id, reply)
2500            },
2501            WebDriverScriptCommand::GetCookie(name, reply) => {
2502                webdriver_handlers::handle_get_cookie(&documents, pipeline_id, name, reply)
2503            },
2504            WebDriverScriptCommand::GetElementTagName(node_id, reply) => {
2505                webdriver_handlers::handle_get_name(&documents, pipeline_id, node_id, reply)
2506            },
2507            WebDriverScriptCommand::GetElementAttribute(node_id, name, reply) => {
2508                webdriver_handlers::handle_get_attribute(
2509                    &documents,
2510                    pipeline_id,
2511                    node_id,
2512                    name,
2513                    reply,
2514                )
2515            },
2516            WebDriverScriptCommand::GetElementProperty(node_id, name, reply) => {
2517                webdriver_handlers::handle_get_property(
2518                    &documents,
2519                    pipeline_id,
2520                    node_id,
2521                    name,
2522                    reply,
2523                    cx,
2524                )
2525            },
2526            WebDriverScriptCommand::GetElementCSS(node_id, name, reply) => {
2527                webdriver_handlers::handle_get_css(&documents, pipeline_id, node_id, name, reply)
2528            },
2529            WebDriverScriptCommand::GetElementRect(node_id, reply) => {
2530                webdriver_handlers::handle_get_rect(
2531                    &documents,
2532                    pipeline_id,
2533                    node_id,
2534                    reply,
2535                    CanGc::from_cx(cx),
2536                )
2537            },
2538            WebDriverScriptCommand::ScrollAndGetBoundingClientRect(node_id, reply) => {
2539                webdriver_handlers::handle_scroll_and_get_bounding_client_rect(
2540                    &documents,
2541                    pipeline_id,
2542                    node_id,
2543                    reply,
2544                    CanGc::from_cx(cx),
2545                )
2546            },
2547            WebDriverScriptCommand::GetElementText(node_id, reply) => {
2548                webdriver_handlers::handle_get_text(&documents, pipeline_id, node_id, reply)
2549            },
2550            WebDriverScriptCommand::GetElementInViewCenterPoint(node_id, reply) => {
2551                webdriver_handlers::handle_get_element_in_view_center_point(
2552                    &documents,
2553                    pipeline_id,
2554                    node_id,
2555                    reply,
2556                    CanGc::from_cx(cx),
2557                )
2558            },
2559            WebDriverScriptCommand::GetParentFrameId(reply) => {
2560                webdriver_handlers::handle_get_parent_frame_id(&documents, pipeline_id, reply)
2561            },
2562            WebDriverScriptCommand::GetBrowsingContextId(webdriver_frame_id, reply) => {
2563                webdriver_handlers::handle_get_browsing_context_id(
2564                    &documents,
2565                    pipeline_id,
2566                    webdriver_frame_id,
2567                    reply,
2568                )
2569            },
2570            WebDriverScriptCommand::GetUrl(reply) => webdriver_handlers::handle_get_url(
2571                &documents,
2572                pipeline_id,
2573                reply,
2574                CanGc::from_cx(cx),
2575            ),
2576            WebDriverScriptCommand::IsEnabled(element_id, reply) => {
2577                webdriver_handlers::handle_is_enabled(&documents, pipeline_id, element_id, reply)
2578            },
2579            WebDriverScriptCommand::IsSelected(element_id, reply) => {
2580                webdriver_handlers::handle_is_selected(&documents, pipeline_id, element_id, reply)
2581            },
2582            WebDriverScriptCommand::GetTitle(reply) => {
2583                webdriver_handlers::handle_get_title(&documents, pipeline_id, reply)
2584            },
2585            WebDriverScriptCommand::WillSendKeys(
2586                element_id,
2587                text,
2588                strict_file_interactability,
2589                reply,
2590            ) => webdriver_handlers::handle_will_send_keys(
2591                &documents,
2592                pipeline_id,
2593                element_id,
2594                text,
2595                strict_file_interactability,
2596                reply,
2597                CanGc::from_cx(cx),
2598            ),
2599            WebDriverScriptCommand::AddLoadStatusSender(_, response_sender) => {
2600                webdriver_handlers::handle_add_load_status_sender(
2601                    &documents,
2602                    pipeline_id,
2603                    response_sender,
2604                )
2605            },
2606            WebDriverScriptCommand::RemoveLoadStatusSender(_) => {
2607                webdriver_handlers::handle_remove_load_status_sender(&documents, pipeline_id)
2608            },
2609            // https://github.com/servo/servo/issues/23535
2610            // The Script messages need different treatment since the JS script might mutate
2611            // `self.documents`, which would conflict with the immutable borrow of it that
2612            // occurs for the rest of the messages.
2613            // We manually drop the immutable borrow first, and quickly
2614            // end the borrow of documents to avoid runtime error.
2615            WebDriverScriptCommand::ExecuteScriptWithCallback(script, reply) => {
2616                let window = documents.find_window(pipeline_id);
2617                drop(documents);
2618                webdriver_handlers::handle_execute_async_script(window, script, reply, cx);
2619            },
2620            WebDriverScriptCommand::SetProtocolHandlerAutomationMode(mode) => {
2621                webdriver_handlers::set_protocol_handler_automation_mode(
2622                    &documents,
2623                    pipeline_id,
2624                    mode,
2625                )
2626            },
2627        }
2628    }
2629
2630    /// Batch window resize operations into a single "update the rendering" task,
2631    /// or, if a load is in progress, set the window size directly.
2632    pub(crate) fn handle_resize_message(
2633        &self,
2634        id: PipelineId,
2635        viewport_details: ViewportDetails,
2636        size_type: WindowSizeType,
2637    ) {
2638        self.profile_event(ScriptThreadEventCategory::Resize, Some(id), || {
2639            let window = self.documents.borrow().find_window(id);
2640            if let Some(ref window) = window {
2641                window.add_resize_event(viewport_details, size_type);
2642                return;
2643            }
2644            let mut loads = self.incomplete_loads.borrow_mut();
2645            if let Some(ref mut load) = loads.iter_mut().find(|load| load.pipeline_id == id) {
2646                load.viewport_details = viewport_details;
2647            }
2648        })
2649    }
2650
2651    /// Handle changes to the theme, triggering reflow if the theme actually changed.
2652    fn handle_theme_change_msg(&self, theme: Theme) {
2653        for (_, document) in self.documents.borrow().iter() {
2654            document.window().set_theme(theme);
2655        }
2656        let mut loads = self.incomplete_loads.borrow_mut();
2657        for load in loads.iter_mut() {
2658            load.theme = theme;
2659        }
2660    }
2661
2662    // exit_fullscreen creates a new JS promise object, so we need to have entered a realm
2663    fn handle_exit_fullscreen(&self, id: PipelineId, cx: &mut js::context::JSContext) {
2664        let document = self.documents.borrow().find_document(id);
2665        if let Some(document) = document {
2666            let mut realm = enter_auto_realm(cx, &*document);
2667            document.exit_fullscreen(CanGc::from_cx(&mut realm));
2668        }
2669    }
2670
2671    #[expect(unsafe_code)]
2672    pub(crate) fn spawn_pipeline(&self, new_pipeline_info: NewPipelineInfo) {
2673        let mut cx = unsafe { temp_cx() };
2674        let cx = &mut cx;
2675        self.profile_event(
2676            ScriptThreadEventCategory::SpawnPipeline,
2677            Some(new_pipeline_info.new_pipeline_id),
2678            || {
2679                // If this is an about:blank or about:srcdoc load, it must share the
2680                // creator's origin. This must match the logic in the constellation
2681                // when creating a new pipeline
2682                let not_an_about_blank_and_about_srcdoc_load =
2683                    new_pipeline_info.load_data.url.as_str() != "about:blank" &&
2684                        new_pipeline_info.load_data.url.as_str() != "about:srcdoc";
2685                let origin = if not_an_about_blank_and_about_srcdoc_load {
2686                    MutableOrigin::new(new_pipeline_info.load_data.url.origin())
2687                } else if let Some(parent) = new_pipeline_info
2688                    .parent_info
2689                    .and_then(|pipeline_id| self.documents.borrow().find_document(pipeline_id))
2690                {
2691                    parent.origin().clone()
2692                } else if let Some(creator) = new_pipeline_info
2693                    .load_data
2694                    .creator_pipeline_id
2695                    .and_then(|pipeline_id| self.documents.borrow().find_document(pipeline_id))
2696                {
2697                    creator.origin().clone()
2698                } else {
2699                    MutableOrigin::new(ImmutableOrigin::new_opaque())
2700                };
2701
2702                // Kick off the fetch for the new resource.
2703                self.pre_page_load(cx, InProgressLoad::new(new_pipeline_info, origin));
2704            },
2705        );
2706    }
2707
2708    fn collect_reports(&self, reports_chan: ReportsChan) {
2709        let documents = self.documents.borrow();
2710        let urls = itertools::join(documents.iter().map(|(_, d)| d.url().to_string()), ", ");
2711
2712        let mut reports = vec![];
2713        perform_memory_report(|ops| {
2714            for (_, document) in documents.iter() {
2715                document
2716                    .window()
2717                    .layout()
2718                    .collect_reports(&mut reports, ops);
2719            }
2720
2721            let prefix = format!("url({urls})");
2722            reports.extend(self.get_cx().get_reports(prefix.clone(), ops));
2723        });
2724
2725        reports_chan.send(ProcessReports::new(reports));
2726    }
2727
2728    /// Updates iframe element after a change in visibility
2729    fn handle_set_throttled_in_containing_iframe_msg(
2730        &self,
2731        parent_pipeline_id: PipelineId,
2732        browsing_context_id: BrowsingContextId,
2733        throttled: bool,
2734    ) {
2735        let iframe = self
2736            .documents
2737            .borrow()
2738            .find_iframe(parent_pipeline_id, browsing_context_id);
2739        if let Some(iframe) = iframe {
2740            iframe.set_throttled(throttled);
2741        }
2742    }
2743
2744    fn handle_set_throttled_msg(
2745        &self,
2746        webview_id: WebViewId,
2747        pipeline_id: PipelineId,
2748        throttled: bool,
2749    ) {
2750        // Separate message sent since parent script thread could be different (Iframe of different
2751        // domain)
2752        self.senders
2753            .pipeline_to_constellation_sender
2754            .send((
2755                webview_id,
2756                pipeline_id,
2757                ScriptToConstellationMessage::SetThrottledComplete(throttled),
2758            ))
2759            .unwrap();
2760
2761        let window = self.documents.borrow().find_window(pipeline_id);
2762        match window {
2763            Some(window) => {
2764                window.set_throttled(throttled);
2765                return;
2766            },
2767            None => {
2768                let mut loads = self.incomplete_loads.borrow_mut();
2769                if let Some(ref mut load) = loads
2770                    .iter_mut()
2771                    .find(|load| load.pipeline_id == pipeline_id)
2772                {
2773                    load.throttled = throttled;
2774                    return;
2775                }
2776            },
2777        }
2778
2779        warn!("SetThrottled sent to nonexistent pipeline");
2780    }
2781
2782    /// Handles activity change message
2783    fn handle_set_document_activity_msg(
2784        &self,
2785        id: PipelineId,
2786        activity: DocumentActivity,
2787        can_gc: CanGc,
2788    ) {
2789        debug!(
2790            "Setting activity of {} to be {:?} in {:?}.",
2791            id,
2792            activity,
2793            thread::current().name()
2794        );
2795        let document = self.documents.borrow().find_document(id);
2796        if let Some(document) = document {
2797            document.set_activity(activity, can_gc);
2798            return;
2799        }
2800        let mut loads = self.incomplete_loads.borrow_mut();
2801        if let Some(ref mut load) = loads.iter_mut().find(|load| load.pipeline_id == id) {
2802            load.activity = activity;
2803            return;
2804        }
2805        warn!("change of activity sent to nonexistent pipeline");
2806    }
2807
2808    fn handle_focus_iframe_msg(
2809        &self,
2810        parent_pipeline_id: PipelineId,
2811        browsing_context_id: BrowsingContextId,
2812        sequence: FocusSequenceNumber,
2813        can_gc: CanGc,
2814    ) {
2815        let document = self
2816            .documents
2817            .borrow()
2818            .find_document(parent_pipeline_id)
2819            .unwrap();
2820
2821        let Some(iframe_element_root) = ({
2822            // Enclose `iframes()` call and create a new root to avoid retaining
2823            // borrow.
2824            let iframes = document.iframes();
2825            iframes
2826                .get(browsing_context_id)
2827                .map(|iframe| DomRoot::from_ref(iframe.element.upcast()))
2828        }) else {
2829            return;
2830        };
2831
2832        if document.get_focus_sequence() > sequence {
2833            debug!(
2834                "Disregarding the FocusIFrame message because the contained sequence number is \
2835                too old ({:?} < {:?})",
2836                sequence,
2837                document.get_focus_sequence()
2838            );
2839            return;
2840        }
2841
2842        document.request_focus(Some(&iframe_element_root), FocusInitiator::Remote, can_gc);
2843    }
2844
2845    fn handle_focus_document_msg(
2846        &self,
2847        pipeline_id: PipelineId,
2848        sequence: FocusSequenceNumber,
2849        can_gc: CanGc,
2850    ) {
2851        if let Some(doc) = self.documents.borrow().find_document(pipeline_id) {
2852            if doc.get_focus_sequence() > sequence {
2853                debug!(
2854                    "Disregarding the FocusDocument message because the contained sequence number is \
2855                    too old ({:?} < {:?})",
2856                    sequence,
2857                    doc.get_focus_sequence()
2858                );
2859                return;
2860            }
2861            doc.request_focus(None, FocusInitiator::Remote, can_gc);
2862        } else {
2863            warn!(
2864                "Couldn't find document by pipleline_id:{pipeline_id:?} when handle_focus_document_msg."
2865            );
2866        }
2867    }
2868
2869    fn handle_unfocus_msg(
2870        &self,
2871        pipeline_id: PipelineId,
2872        sequence: FocusSequenceNumber,
2873        can_gc: CanGc,
2874    ) {
2875        if let Some(doc) = self.documents.borrow().find_document(pipeline_id) {
2876            if doc.get_focus_sequence() > sequence {
2877                debug!(
2878                    "Disregarding the Unfocus message because the contained sequence number is \
2879                    too old ({:?} < {:?})",
2880                    sequence,
2881                    doc.get_focus_sequence()
2882                );
2883                return;
2884            }
2885            doc.handle_container_unfocus(can_gc);
2886        } else {
2887            warn!(
2888                "Couldn't find document by pipleline_id:{pipeline_id:?} when handle_unfocus_msg."
2889            );
2890        }
2891    }
2892
2893    /// <https://html.spec.whatwg.org/multipage/#window-post-message-steps>
2894    fn handle_post_message_msg(
2895        &self,
2896        pipeline_id: PipelineId,
2897        source_webview: WebViewId,
2898        source_with_ancestry: Vec<BrowsingContextId>,
2899        origin: Option<ImmutableOrigin>,
2900        source_origin: ImmutableOrigin,
2901        data: StructuredSerializedData,
2902    ) {
2903        let window = self.documents.borrow().find_window(pipeline_id);
2904        match window {
2905            None => warn!("postMessage after target pipeline {} closed.", pipeline_id),
2906            Some(window) => {
2907                let mut last = None;
2908                for browsing_context_id in source_with_ancestry.into_iter().rev() {
2909                    if let Some(window_proxy) = self.window_proxies.get(browsing_context_id) {
2910                        last = Some(window_proxy);
2911                        continue;
2912                    }
2913                    let window_proxy = WindowProxy::new_dissimilar_origin(
2914                        window.upcast::<GlobalScope>(),
2915                        browsing_context_id,
2916                        source_webview,
2917                        last.as_deref(),
2918                        None,
2919                        CreatorBrowsingContextInfo::from(last.as_deref(), None),
2920                    );
2921                    self.window_proxies
2922                        .insert(browsing_context_id, window_proxy.clone());
2923                    last = Some(window_proxy);
2924                }
2925
2926                // Step 8.3: Let source be the WindowProxy object corresponding to
2927                // incumbentSettings's global object (a Window object).
2928                let source = last.expect("Source with ancestry should contain at least one bc.");
2929
2930                // FIXME(#22512): enqueues a task; unnecessary delay.
2931                window.post_message(origin, source_origin, &source, data)
2932            },
2933        }
2934    }
2935
2936    fn handle_stop_delaying_load_events_mode(&self, pipeline_id: PipelineId) {
2937        let window = self.documents.borrow().find_window(pipeline_id);
2938        if let Some(window) = window {
2939            match window.undiscarded_window_proxy() {
2940                Some(window_proxy) => window_proxy.stop_delaying_load_events_mode(),
2941                None => warn!(
2942                    "Attempted to take {} of 'delaying-load-events-mode' after having been discarded.",
2943                    pipeline_id
2944                ),
2945            };
2946        }
2947    }
2948
2949    fn handle_unload_document(&self, pipeline_id: PipelineId, can_gc: CanGc) {
2950        let document = self.documents.borrow().find_document(pipeline_id);
2951        if let Some(document) = document {
2952            document.unload(false, can_gc);
2953        }
2954    }
2955
2956    fn handle_update_pipeline_id(
2957        &self,
2958        parent_pipeline_id: PipelineId,
2959        browsing_context_id: BrowsingContextId,
2960        webview_id: WebViewId,
2961        new_pipeline_id: PipelineId,
2962        reason: UpdatePipelineIdReason,
2963        can_gc: CanGc,
2964    ) {
2965        let frame_element = self
2966            .documents
2967            .borrow()
2968            .find_iframe(parent_pipeline_id, browsing_context_id);
2969        if let Some(frame_element) = frame_element {
2970            frame_element.update_pipeline_id(new_pipeline_id, reason, can_gc);
2971        }
2972
2973        if let Some(window) = self.documents.borrow().find_window(new_pipeline_id) {
2974            // Ensure that the state of any local window proxies accurately reflects
2975            // the new pipeline.
2976            let _ = self.window_proxies.local_window_proxy(
2977                &self.senders,
2978                &self.documents,
2979                &window,
2980                browsing_context_id,
2981                webview_id,
2982                Some(parent_pipeline_id),
2983                // Any local window proxy has already been created, so there
2984                // is no need to pass along existing opener information that
2985                // will be discarded.
2986                None,
2987            );
2988        }
2989    }
2990
2991    fn handle_update_history_state_msg(
2992        &self,
2993        pipeline_id: PipelineId,
2994        history_state_id: Option<HistoryStateId>,
2995        url: ServoUrl,
2996        can_gc: CanGc,
2997    ) {
2998        let Some(window) = self.documents.borrow().find_window(pipeline_id) else {
2999            return warn!("update history state after pipeline {pipeline_id} closed.",);
3000        };
3001        window
3002            .History()
3003            .activate_state(history_state_id, url, can_gc);
3004    }
3005
3006    fn handle_remove_history_states(
3007        &self,
3008        pipeline_id: PipelineId,
3009        history_states: Vec<HistoryStateId>,
3010    ) {
3011        let Some(window) = self.documents.borrow().find_window(pipeline_id) else {
3012            return warn!("update history state after pipeline {pipeline_id} closed.",);
3013        };
3014        window.History().remove_states(history_states);
3015    }
3016
3017    /// Window was resized, but this script was not active, so don't reflow yet
3018    fn handle_resize_inactive_msg(&self, id: PipelineId, new_viewport_details: ViewportDetails) {
3019        let window = self.documents.borrow().find_window(id)
3020            .expect("ScriptThread: received a resize msg for a pipeline not in this script thread. This is a bug.");
3021        window.set_viewport_details(new_viewport_details);
3022    }
3023
3024    /// We have received notification that the response associated with a load has completed.
3025    /// Kick off the document and frame tree creation process using the result.
3026    fn handle_page_headers_available(
3027        &self,
3028        webview_id: WebViewId,
3029        pipeline_id: PipelineId,
3030        metadata: Option<Metadata>,
3031        can_gc: CanGc,
3032    ) -> Option<DomRoot<ServoParser>> {
3033        if self.closed_pipelines.borrow().contains(&pipeline_id) {
3034            // If the pipeline closed, do not process the headers.
3035            return None;
3036        }
3037
3038        let Some(idx) = self
3039            .incomplete_loads
3040            .borrow()
3041            .iter()
3042            .position(|load| load.pipeline_id == pipeline_id)
3043        else {
3044            unreachable!("Pipeline shouldn't have finished loading.");
3045        };
3046
3047        // https://html.spec.whatwg.org/multipage/#process-a-navigate-response
3048        // 2. If response's status is 204 or 205, then abort these steps.
3049        let is_204_205 = match metadata {
3050            Some(ref metadata) => metadata.status.in_range(204..=205),
3051            _ => false,
3052        };
3053
3054        if is_204_205 {
3055            // If we have an existing window that is being navigated:
3056            if let Some(window) = self.documents.borrow().find_window(pipeline_id) {
3057                let window_proxy = window.window_proxy();
3058                // https://html.spec.whatwg.org/multipage/
3059                // #navigating-across-documents:delaying-load-events-mode-2
3060                if window_proxy.parent().is_some() {
3061                    // The user agent must take this nested browsing context
3062                    // out of the delaying load events mode
3063                    // when this navigation algorithm later matures,
3064                    // or when it terminates (whether due to having run all the steps,
3065                    // or being canceled, or being aborted), whichever happens first.
3066                    window_proxy.stop_delaying_load_events_mode();
3067                }
3068            }
3069            self.senders
3070                .pipeline_to_constellation_sender
3071                .send((
3072                    webview_id,
3073                    pipeline_id,
3074                    ScriptToConstellationMessage::AbortLoadUrl,
3075                ))
3076                .unwrap();
3077            return None;
3078        };
3079
3080        let load = self.incomplete_loads.borrow_mut().remove(idx);
3081        metadata.map(|meta| self.load(meta, load, can_gc))
3082    }
3083
3084    /// Handles a request for the window title.
3085    fn handle_get_title_msg(&self, pipeline_id: PipelineId) {
3086        let Some(document) = self.documents.borrow().find_document(pipeline_id) else {
3087            return warn!("Message sent to closed pipeline {pipeline_id}.");
3088        };
3089        document.send_title_to_embedder();
3090    }
3091
3092    /// Handles a request to exit a pipeline and shut down layout.
3093    fn handle_exit_pipeline_msg(
3094        &self,
3095        webview_id: WebViewId,
3096        pipeline_id: PipelineId,
3097        discard_bc: DiscardBrowsingContext,
3098        can_gc: CanGc,
3099    ) {
3100        debug!("{pipeline_id}: Starting pipeline exit.");
3101
3102        // Abort the parser, if any,
3103        // to prevent any further incoming networking messages from being handled.
3104        let document = self.documents.borrow_mut().remove(pipeline_id);
3105        if let Some(document) = document {
3106            // We should never have a pipeline that's still an incomplete load, but also has a Document.
3107            debug_assert!(
3108                !self
3109                    .incomplete_loads
3110                    .borrow()
3111                    .iter()
3112                    .any(|load| load.pipeline_id == pipeline_id)
3113            );
3114
3115            if let Some(parser) = document.get_current_parser() {
3116                parser.abort(can_gc);
3117            }
3118
3119            debug!("{pipeline_id}: Shutting down layout");
3120            document.window().layout_mut().exit_now();
3121
3122            // Clear any active animations and unroot all of the associated DOM objects.
3123            debug!("{pipeline_id}: Clearing animations");
3124            document.animations().clear();
3125
3126            // We discard the browsing context after requesting layout shut down,
3127            // to avoid running layout on detached iframes.
3128            let window = document.window();
3129            if discard_bc == DiscardBrowsingContext::Yes {
3130                window.discard_browsing_context();
3131            }
3132
3133            debug!("{pipeline_id}: Clearing JavaScript runtime");
3134            window.clear_js_runtime();
3135        }
3136
3137        // Prevent any further work for this Pipeline.
3138        self.closed_pipelines.borrow_mut().insert(pipeline_id);
3139
3140        debug!("{pipeline_id}: Sending PipelineExited message to constellation");
3141        self.senders
3142            .pipeline_to_constellation_sender
3143            .send((
3144                webview_id,
3145                pipeline_id,
3146                ScriptToConstellationMessage::PipelineExited,
3147            ))
3148            .ok();
3149
3150        self.paint_api
3151            .pipeline_exited(webview_id, pipeline_id, PipelineExitSource::Script);
3152
3153        debug!("{pipeline_id}: Finished pipeline exit");
3154    }
3155
3156    /// Handles a request to exit the script thread and shut down layout.
3157    fn handle_exit_script_thread_msg(&self, can_gc: CanGc) {
3158        debug!("Exiting script thread.");
3159
3160        let mut webview_and_pipeline_ids = Vec::new();
3161        webview_and_pipeline_ids.extend(
3162            self.incomplete_loads
3163                .borrow()
3164                .iter()
3165                .next()
3166                .map(|load| (load.webview_id, load.pipeline_id)),
3167        );
3168        webview_and_pipeline_ids.extend(
3169            self.documents
3170                .borrow()
3171                .iter()
3172                .next()
3173                .map(|(pipeline_id, document)| (document.webview_id(), pipeline_id)),
3174        );
3175
3176        for (webview_id, pipeline_id) in webview_and_pipeline_ids {
3177            self.handle_exit_pipeline_msg(
3178                webview_id,
3179                pipeline_id,
3180                DiscardBrowsingContext::Yes,
3181                can_gc,
3182            );
3183        }
3184
3185        self.background_hang_monitor.unregister();
3186
3187        // If we're in multiprocess mode, shut-down the IPC router for this process.
3188        if opts::get().multiprocess {
3189            debug!("Exiting IPC router thread in script thread.");
3190            ROUTER.shutdown();
3191        }
3192
3193        debug!("Exited script thread.");
3194    }
3195
3196    /// Handles animation tick requested during testing.
3197    pub(crate) fn handle_tick_all_animations_for_testing(id: PipelineId) {
3198        with_script_thread(|script_thread| {
3199            let Some(document) = script_thread.documents.borrow().find_document(id) else {
3200                warn!("Animation tick for tests for closed pipeline {id}.");
3201                return;
3202            };
3203            document.maybe_mark_animating_nodes_as_dirty();
3204        });
3205    }
3206
3207    /// Handles a Web font being loaded. Does nothing if the page no longer exists.
3208    fn handle_web_font_loaded(&self, pipeline_id: PipelineId, _success: bool) {
3209        let Some(document) = self.documents.borrow().find_document(pipeline_id) else {
3210            warn!("Web font loaded in closed pipeline {}.", pipeline_id);
3211            return;
3212        };
3213
3214        // TODO: This should only dirty nodes that are waiting for a web font to finish loading!
3215        document.dirty_all_nodes();
3216    }
3217
3218    /// Handles a worklet being loaded by triggering a relayout of the page. Does nothing if the
3219    /// page no longer exists.
3220    fn handle_worklet_loaded(&self, pipeline_id: PipelineId) {
3221        if let Some(document) = self.documents.borrow().find_document(pipeline_id) {
3222            document.add_restyle_reason(RestyleReason::PaintWorkletLoaded);
3223        }
3224    }
3225
3226    /// Notify a window of a storage event
3227    fn handle_storage_event(
3228        &self,
3229        pipeline_id: PipelineId,
3230        storage_type: WebStorageType,
3231        url: ServoUrl,
3232        key: Option<String>,
3233        old_value: Option<String>,
3234        new_value: Option<String>,
3235    ) {
3236        let Some(window) = self.documents.borrow().find_window(pipeline_id) else {
3237            return warn!("Storage event sent to closed pipeline {pipeline_id}.");
3238        };
3239
3240        let storage = match storage_type {
3241            WebStorageType::Local => window.LocalStorage(),
3242            WebStorageType::Session => window.SessionStorage(),
3243        };
3244
3245        storage.queue_storage_event(url, key, old_value, new_value);
3246    }
3247
3248    /// Notify the containing document of a child iframe that has completed loading.
3249    fn handle_iframe_load_event(
3250        &self,
3251        parent_id: PipelineId,
3252        browsing_context_id: BrowsingContextId,
3253        child_id: PipelineId,
3254        can_gc: CanGc,
3255    ) {
3256        let iframe = self
3257            .documents
3258            .borrow()
3259            .find_iframe(parent_id, browsing_context_id);
3260        match iframe {
3261            Some(iframe) => iframe.iframe_load_event_steps(child_id, can_gc),
3262            None => warn!("Message sent to closed pipeline {}.", parent_id),
3263        }
3264    }
3265
3266    fn ask_constellation_for_top_level_info(
3267        &self,
3268        sender_webview_id: WebViewId,
3269        sender_pipeline_id: PipelineId,
3270        browsing_context_id: BrowsingContextId,
3271    ) -> Option<WebViewId> {
3272        let (result_sender, result_receiver) = ipc::channel().unwrap();
3273        let msg = ScriptToConstellationMessage::GetTopForBrowsingContext(
3274            browsing_context_id,
3275            result_sender,
3276        );
3277        self.senders
3278            .pipeline_to_constellation_sender
3279            .send((sender_webview_id, sender_pipeline_id, msg))
3280            .expect("Failed to send to constellation.");
3281        result_receiver
3282            .recv()
3283            .expect("Failed to get top-level id from constellation.")
3284    }
3285
3286    /// The entry point to document loading. Defines bindings, sets up the window and document
3287    /// objects, parses HTML and CSS, and kicks off initial layout.
3288    fn load(
3289        &self,
3290        metadata: Metadata,
3291        incomplete: InProgressLoad,
3292        can_gc: CanGc,
3293    ) -> DomRoot<ServoParser> {
3294        let script_to_constellation_chan = ScriptToConstellationChan {
3295            sender: self.senders.pipeline_to_constellation_sender.clone(),
3296            webview_id: incomplete.webview_id,
3297            pipeline_id: incomplete.pipeline_id,
3298        };
3299
3300        let final_url = metadata.final_url.clone();
3301        let _ = script_to_constellation_chan
3302            .send(ScriptToConstellationMessage::SetFinalUrl(final_url.clone()));
3303
3304        debug!(
3305            "ScriptThread: loading {} on pipeline {:?}",
3306            incomplete.load_data.url, incomplete.pipeline_id
3307        );
3308
3309        let origin = if final_url.as_str() == "about:blank" || final_url.as_str() == "about:srcdoc"
3310        {
3311            incomplete.origin.clone()
3312        } else {
3313            MutableOrigin::new(final_url.origin())
3314        };
3315
3316        let font_context = Arc::new(FontContext::new(
3317            self.system_font_service.clone(),
3318            self.paint_api.clone(),
3319            self.resource_threads.clone(),
3320        ));
3321
3322        let image_cache = self.image_cache_factory.create(
3323            incomplete.webview_id,
3324            incomplete.pipeline_id,
3325            &self.paint_api,
3326        );
3327
3328        let (user_contents, user_stylesheets) = incomplete
3329            .user_content_manager_id
3330            .and_then(|user_content_manager_id| {
3331                self.user_contents_for_manager_id
3332                    .borrow()
3333                    .get(&user_content_manager_id)
3334                    .map(|script_thread_user_contents| {
3335                        (
3336                            script_thread_user_contents.user_scripts.clone(),
3337                            script_thread_user_contents.user_stylesheets.clone(),
3338                        )
3339                    })
3340            })
3341            .unwrap_or_default();
3342
3343        let layout_config = LayoutConfig {
3344            id: incomplete.pipeline_id,
3345            webview_id: incomplete.webview_id,
3346            url: final_url.clone(),
3347            is_iframe: incomplete.parent_info.is_some(),
3348            script_chan: self.senders.constellation_sender.clone(),
3349            image_cache: image_cache.clone(),
3350            font_context: font_context.clone(),
3351            time_profiler_chan: self.senders.time_profiler_sender.clone(),
3352            paint_api: self.paint_api.clone(),
3353            viewport_details: incomplete.viewport_details,
3354            user_stylesheets,
3355            theme: incomplete.theme,
3356            accessibility_active: self.accessibility_active.get(),
3357        };
3358
3359        // Create the window and document objects.
3360        let window = Window::new(
3361            incomplete.webview_id,
3362            self.js_runtime.clone(),
3363            self.senders.self_sender.clone(),
3364            self.layout_factory.create(layout_config),
3365            font_context,
3366            self.senders.image_cache_sender.clone(),
3367            image_cache.clone(),
3368            self.resource_threads.clone(),
3369            self.storage_threads.clone(),
3370            #[cfg(feature = "bluetooth")]
3371            self.senders.bluetooth_sender.clone(),
3372            self.senders.memory_profiler_sender.clone(),
3373            self.senders.time_profiler_sender.clone(),
3374            self.senders.devtools_server_sender.clone(),
3375            script_to_constellation_chan,
3376            self.senders.pipeline_to_embedder_sender.clone(),
3377            self.senders.constellation_sender.clone(),
3378            incomplete.pipeline_id,
3379            incomplete.parent_info,
3380            incomplete.viewport_details,
3381            origin.clone(),
3382            final_url.clone(),
3383            // TODO(37417): Set correct top-level URL here. Currently, we only specify the
3384            // url of the current window. However, in case this is an iframe, we should
3385            // pass in the URL from the frame that includes the iframe (which potentially
3386            // is another nested iframe in a frame).
3387            final_url.clone(),
3388            incomplete.navigation_start,
3389            self.webgl_chan.as_ref().map(|chan| chan.channel()),
3390            #[cfg(feature = "webxr")]
3391            self.webxr_registry.clone(),
3392            self.paint_api.clone(),
3393            self.unminify_js,
3394            self.unminify_css,
3395            self.local_script_source.clone(),
3396            user_contents,
3397            self.player_context.clone(),
3398            #[cfg(feature = "webgpu")]
3399            self.gpu_id_hub.clone(),
3400            incomplete.load_data.inherited_secure_context,
3401            incomplete.theme,
3402            self.this.clone(),
3403        );
3404        self.debugger_global.fire_add_debuggee(
3405            can_gc,
3406            window.upcast(),
3407            incomplete.pipeline_id,
3408            None,
3409        );
3410
3411        let _realm = enter_realm(&*window);
3412
3413        // Initialize the browsing context for the window.
3414        let window_proxy = self.window_proxies.local_window_proxy(
3415            &self.senders,
3416            &self.documents,
3417            &window,
3418            incomplete.browsing_context_id,
3419            incomplete.webview_id,
3420            incomplete.parent_info,
3421            incomplete.opener,
3422        );
3423        if window_proxy.parent().is_some() {
3424            // https://html.spec.whatwg.org/multipage/#navigating-across-documents:delaying-load-events-mode-2
3425            // The user agent must take this nested browsing context
3426            // out of the delaying load events mode
3427            // when this navigation algorithm later matures.
3428            window_proxy.stop_delaying_load_events_mode();
3429        }
3430        window.init_window_proxy(&window_proxy);
3431
3432        let last_modified = metadata.headers.as_ref().and_then(|headers| {
3433            headers.typed_get::<LastModified>().map(|tm| {
3434                let tm: SystemTime = tm.into();
3435                let local_time: DateTime<Local> = tm.into();
3436                local_time.format("%m/%d/%Y %H:%M:%S").to_string()
3437            })
3438        });
3439
3440        let loader = DocumentLoader::new_with_threads(
3441            self.resource_threads.clone(),
3442            Some(final_url.clone()),
3443        );
3444
3445        let content_type: Option<Mime> = metadata
3446            .content_type
3447            .map(Serde::into_inner)
3448            .map(Mime::from_ct);
3449        let encoding_hint_from_content_type = content_type
3450            .as_ref()
3451            .and_then(|mime| mime.get_parameter(CHARSET))
3452            .and_then(|charset| Encoding::for_label(charset.as_bytes()));
3453
3454        let is_html_document = match content_type {
3455            Some(ref mime) if mime.type_ == APPLICATION && mime.has_suffix("xml") => {
3456                IsHTMLDocument::NonHTMLDocument
3457            },
3458
3459            Some(ref mime) if mime.matches(TEXT, XML) || mime.matches(APPLICATION, XML) => {
3460                IsHTMLDocument::NonHTMLDocument
3461            },
3462            _ => IsHTMLDocument::HTMLDocument,
3463        };
3464
3465        let referrer = metadata
3466            .referrer
3467            .as_ref()
3468            .map(|referrer| referrer.clone().into_string());
3469
3470        let is_initial_about_blank = final_url.as_str() == "about:blank";
3471
3472        let document = Document::new(
3473            &window,
3474            HasBrowsingContext::Yes,
3475            Some(final_url.clone()),
3476            incomplete.load_data.about_base_url,
3477            origin,
3478            is_html_document,
3479            content_type,
3480            last_modified,
3481            incomplete.activity,
3482            DocumentSource::FromParser,
3483            loader,
3484            referrer,
3485            Some(metadata.status.raw_code()),
3486            incomplete.canceller,
3487            is_initial_about_blank,
3488            true,
3489            incomplete.load_data.inherited_insecure_requests_policy,
3490            incomplete.load_data.has_trustworthy_ancestor_origin,
3491            self.custom_element_reaction_stack.clone(),
3492            incomplete.load_data.creation_sandboxing_flag_set,
3493            can_gc,
3494        );
3495
3496        let referrer_policy = metadata
3497            .headers
3498            .as_deref()
3499            .and_then(|h| h.typed_get::<ReferrerPolicyHeader>())
3500            .into();
3501        document.set_referrer_policy(referrer_policy);
3502
3503        let refresh_header = metadata.headers.as_deref().and_then(|h| h.get(REFRESH));
3504        if let Some(refresh_val) = refresh_header {
3505            // There are tests that this header handles Unicode code points
3506            document.shared_declarative_refresh_steps(refresh_val.as_bytes());
3507        }
3508
3509        document.set_ready_state(DocumentReadyState::Loading, can_gc);
3510
3511        self.documents
3512            .borrow_mut()
3513            .insert(incomplete.pipeline_id, &document);
3514
3515        window.init_document(&document);
3516
3517        // For any similar-origin iframe, ensure that the contentWindow/contentDocument
3518        // APIs resolve to the new window/document as soon as parsing starts.
3519        if let Some(frame) = window_proxy
3520            .frame_element()
3521            .and_then(|e| e.downcast::<HTMLIFrameElement>())
3522        {
3523            let parent_pipeline = frame.global().pipeline_id();
3524            self.handle_update_pipeline_id(
3525                parent_pipeline,
3526                window_proxy.browsing_context_id(),
3527                window_proxy.webview_id(),
3528                incomplete.pipeline_id,
3529                UpdatePipelineIdReason::Navigation,
3530                can_gc,
3531            );
3532        }
3533
3534        self.senders
3535            .pipeline_to_constellation_sender
3536            .send((
3537                incomplete.webview_id,
3538                incomplete.pipeline_id,
3539                ScriptToConstellationMessage::ActivateDocument,
3540            ))
3541            .unwrap();
3542
3543        // Notify devtools that a new script global exists.
3544        let incomplete_browsing_context_id: BrowsingContextId = incomplete.webview_id.into();
3545        let is_top_level_global = incomplete_browsing_context_id == incomplete.browsing_context_id;
3546        self.notify_devtools(
3547            document.Title(),
3548            final_url.clone(),
3549            is_top_level_global,
3550            (
3551                incomplete.browsing_context_id,
3552                incomplete.pipeline_id,
3553                None,
3554                incomplete.webview_id,
3555            ),
3556        );
3557
3558        document.set_https_state(metadata.https_state);
3559        document.set_navigation_start(incomplete.navigation_start);
3560
3561        if is_html_document == IsHTMLDocument::NonHTMLDocument {
3562            ServoParser::parse_xml_document(
3563                &document,
3564                None,
3565                final_url,
3566                encoding_hint_from_content_type,
3567                can_gc,
3568            );
3569        } else {
3570            ServoParser::parse_html_document(
3571                &document,
3572                None,
3573                final_url,
3574                encoding_hint_from_content_type,
3575                incomplete.load_data.container_document_encoding,
3576                can_gc,
3577            );
3578        }
3579
3580        if incomplete.activity == DocumentActivity::FullyActive {
3581            window.resume(can_gc);
3582        } else {
3583            window.suspend(can_gc);
3584        }
3585
3586        if incomplete.throttled {
3587            window.set_throttled(true);
3588        }
3589
3590        document.get_current_parser().unwrap()
3591    }
3592
3593    fn notify_devtools(
3594        &self,
3595        title: DOMString,
3596        url: ServoUrl,
3597        is_top_level_global: bool,
3598        (browsing_context_id, pipeline_id, worker_id, webview_id): (
3599            BrowsingContextId,
3600            PipelineId,
3601            Option<WorkerId>,
3602            WebViewId,
3603        ),
3604    ) {
3605        if let Some(ref chan) = self.senders.devtools_server_sender {
3606            let page_info = DevtoolsPageInfo {
3607                title: String::from(title),
3608                url,
3609                is_top_level_global,
3610            };
3611            chan.send(ScriptToDevtoolsControlMsg::NewGlobal(
3612                (browsing_context_id, pipeline_id, worker_id, webview_id),
3613                self.senders.devtools_client_to_script_thread_sender.clone(),
3614                page_info.clone(),
3615            ))
3616            .unwrap();
3617
3618            let state = NavigationState::Stop(pipeline_id, page_info);
3619            let _ = chan.send(ScriptToDevtoolsControlMsg::Navigate(
3620                browsing_context_id,
3621                state,
3622            ));
3623        }
3624    }
3625
3626    /// Queue input events for later dispatching as part of a `update_the_rendering` task.
3627    fn handle_input_event(
3628        &self,
3629        webview_id: WebViewId,
3630        pipeline_id: PipelineId,
3631        event: ConstellationInputEvent,
3632    ) {
3633        let Some(document) = self.documents.borrow().find_document(pipeline_id) else {
3634            warn!("Input event sent to closed pipeline {pipeline_id}.");
3635            let _ = self
3636                .senders
3637                .pipeline_to_embedder_sender
3638                .send(EmbedderMsg::InputEventHandled(
3639                    webview_id,
3640                    event.event.id,
3641                    Default::default(),
3642                ));
3643            return;
3644        };
3645        document.event_handler().note_pending_input_event(event);
3646    }
3647
3648    fn set_accessibility_active(&self, active: bool) {
3649        if !(pref!(accessibility_enabled)) {
3650            return;
3651        }
3652
3653        let old_value = self.accessibility_active.replace(active);
3654        if active == old_value {
3655            return;
3656        }
3657
3658        for (_, document) in self.documents.borrow().iter() {
3659            document.window().layout().set_accessibility_active(active);
3660        }
3661    }
3662
3663    /// Handle a "navigate an iframe" message from the constellation.
3664    fn handle_navigate_iframe(
3665        &self,
3666        parent_pipeline_id: PipelineId,
3667        browsing_context_id: BrowsingContextId,
3668        load_data: LoadData,
3669        history_handling: NavigationHistoryBehavior,
3670        can_gc: CanGc,
3671    ) {
3672        let iframe = self
3673            .documents
3674            .borrow()
3675            .find_iframe(parent_pipeline_id, browsing_context_id);
3676        if let Some(iframe) = iframe {
3677            iframe.navigate_or_reload_child_browsing_context(load_data, history_handling, can_gc);
3678        }
3679    }
3680
3681    /// Turn javascript: URL into JS code to eval, according to the steps in
3682    /// <https://html.spec.whatwg.org/multipage/#javascript-protocol>
3683    pub(crate) fn eval_js_url(global_scope: &GlobalScope, load_data: &mut LoadData, can_gc: CanGc) {
3684        // This slice of the URL’s serialization is equivalent to (5.) to (7.):
3685        // Start with the scheme data of the parsed URL;
3686        // append question mark and query component, if any;
3687        // append number sign and fragment component if any.
3688        let encoded = &load_data.url[Position::AfterScheme..][1..];
3689
3690        // Percent-decode (8.) and UTF-8 decode (9.)
3691        let script_source = percent_decode(encoded.as_bytes()).decode_utf8_lossy();
3692
3693        // Script source is ready to be evaluated (11.)
3694        let _ac = enter_realm(global_scope);
3695        rooted!(in(*GlobalScope::get_cx()) let mut jsval = UndefinedValue());
3696        _ = global_scope.evaluate_js_on_global(
3697            script_source,
3698            "",
3699            Some(IntroductionType::JAVASCRIPT_URL),
3700            jsval.handle_mut(),
3701            can_gc,
3702        );
3703
3704        load_data.js_eval_result = if jsval.get().is_string() {
3705            let strval = DOMString::safe_from_jsval(
3706                GlobalScope::get_cx(),
3707                jsval.handle(),
3708                StringificationBehavior::Empty,
3709                can_gc,
3710            );
3711            match strval {
3712                Ok(ConversionResult::Success(s)) => {
3713                    Some(JsEvalResult::Ok(String::from(s).as_bytes().to_vec()))
3714                },
3715                _ => None,
3716            }
3717        } else {
3718            Some(JsEvalResult::NoContent)
3719        };
3720
3721        load_data.url = ServoUrl::parse("about:blank").unwrap();
3722    }
3723
3724    /// Instructs the constellation to fetch the document that will be loaded. Stores the InProgressLoad
3725    /// argument until a notification is received that the fetch is complete.
3726    fn pre_page_load(&self, cx: &mut js::context::JSContext, mut incomplete: InProgressLoad) {
3727        let url_str = incomplete.load_data.url.as_str();
3728        if url_str == "about:blank" {
3729            self.start_page_load_about_blank(cx, incomplete);
3730            return;
3731        }
3732        if url_str == "about:srcdoc" {
3733            self.page_load_about_srcdoc(cx, incomplete);
3734            return;
3735        }
3736
3737        let context = ParserContext::new(
3738            incomplete.webview_id,
3739            incomplete.pipeline_id,
3740            incomplete.load_data.url.clone(),
3741            incomplete.load_data.creation_sandboxing_flag_set,
3742        );
3743        self.incomplete_parser_contexts
3744            .0
3745            .borrow_mut()
3746            .push((incomplete.pipeline_id, context));
3747
3748        let request_builder = incomplete.request_builder();
3749        incomplete.canceller = FetchCanceller::new(
3750            request_builder.id,
3751            false,
3752            self.resource_threads.core_thread.clone(),
3753        );
3754        NavigationListener::new(request_builder, self.senders.self_sender.clone())
3755            .initiate_fetch(&self.resource_threads.core_thread, None);
3756        self.incomplete_loads.borrow_mut().push(incomplete);
3757    }
3758
3759    fn handle_navigation_response(
3760        &self,
3761        cx: &mut js::context::JSContext,
3762        pipeline_id: PipelineId,
3763        message: FetchResponseMsg,
3764    ) {
3765        if let Some(metadata) = NavigationListener::http_redirect_metadata(&message) {
3766            self.handle_navigation_redirect(pipeline_id, metadata);
3767            return;
3768        };
3769
3770        match message {
3771            FetchResponseMsg::ProcessResponse(request_id, metadata) => {
3772                self.handle_fetch_metadata(pipeline_id, request_id, metadata)
3773            },
3774            FetchResponseMsg::ProcessResponseChunk(request_id, chunk) => {
3775                self.handle_fetch_chunk(pipeline_id, request_id, chunk.0)
3776            },
3777            FetchResponseMsg::ProcessResponseEOF(request_id, eof, timing) => {
3778                self.handle_fetch_eof(cx, pipeline_id, request_id, eof, timing)
3779            },
3780            FetchResponseMsg::ProcessCspViolations(request_id, violations) => {
3781                self.handle_csp_violations(pipeline_id, request_id, violations)
3782            },
3783            FetchResponseMsg::ProcessRequestBody(..) | FetchResponseMsg::ProcessRequestEOF(..) => {
3784            },
3785        }
3786    }
3787
3788    fn handle_fetch_metadata(
3789        &self,
3790        id: PipelineId,
3791        request_id: RequestId,
3792        fetch_metadata: Result<FetchMetadata, NetworkError>,
3793    ) {
3794        match fetch_metadata {
3795            Ok(_) => (),
3796            Err(NetworkError::Crash(..)) => (),
3797            Err(ref e) => {
3798                warn!("Network error: {:?}", e);
3799            },
3800        };
3801
3802        let mut incomplete_parser_contexts = self.incomplete_parser_contexts.0.borrow_mut();
3803        let parser = incomplete_parser_contexts
3804            .iter_mut()
3805            .find(|&&mut (pipeline_id, _)| pipeline_id == id);
3806        if let Some(&mut (_, ref mut ctxt)) = parser {
3807            ctxt.process_response(request_id, fetch_metadata);
3808        }
3809    }
3810
3811    fn handle_fetch_chunk(&self, pipeline_id: PipelineId, request_id: RequestId, chunk: Vec<u8>) {
3812        let mut incomplete_parser_contexts = self.incomplete_parser_contexts.0.borrow_mut();
3813        let parser = incomplete_parser_contexts
3814            .iter_mut()
3815            .find(|&&mut (parser_pipeline_id, _)| parser_pipeline_id == pipeline_id);
3816        if let Some(&mut (_, ref mut ctxt)) = parser {
3817            ctxt.process_response_chunk(request_id, chunk);
3818        }
3819    }
3820
3821    fn handle_fetch_eof(
3822        &self,
3823        cx: &mut js::context::JSContext,
3824        id: PipelineId,
3825        request_id: RequestId,
3826        eof: Result<(), NetworkError>,
3827        timing: ResourceFetchTiming,
3828    ) {
3829        let idx = self
3830            .incomplete_parser_contexts
3831            .0
3832            .borrow()
3833            .iter()
3834            .position(|&(pipeline_id, _)| pipeline_id == id);
3835
3836        if let Some(idx) = idx {
3837            let (_, context) = self.incomplete_parser_contexts.0.borrow_mut().remove(idx);
3838
3839            // we need to register an iframe entry to the performance timeline if present
3840            if let Some(window_proxy) = context
3841                .get_document()
3842                .and_then(|document| document.browsing_context())
3843            {
3844                if let Some(frame_element) = window_proxy.frame_element() {
3845                    let iframe_ctx = IframeContext::new(
3846                        frame_element
3847                            .downcast::<HTMLIFrameElement>()
3848                            .expect("WindowProxy::frame_element should be an HTMLIFrameElement"),
3849                    );
3850
3851                    // submit_timing will only accept timing that is of type ResourceTimingType::Resource
3852                    let mut resource_timing = timing.clone();
3853                    resource_timing.timing_type = ResourceTimingType::Resource;
3854                    submit_timing(&iframe_ctx, &eof, &resource_timing, CanGc::from_cx(cx));
3855                }
3856            }
3857
3858            context.process_response_eof(cx, request_id, eof, timing);
3859        }
3860    }
3861
3862    fn handle_csp_violations(&self, id: PipelineId, _: RequestId, violations: Vec<Violation>) {
3863        if let Some(global) = self.documents.borrow().find_global(id) {
3864            // TODO(https://github.com/w3c/webappsec-csp/issues/687): Update after spec is resolved
3865            global.report_csp_violations(violations, None, None);
3866        }
3867    }
3868
3869    fn handle_navigation_redirect(&self, id: PipelineId, metadata: &Metadata) {
3870        // TODO(mrobinson): This tries to accomplish some steps from
3871        // <https://html.spec.whatwg.org/multipage/#process-a-navigate-fetch>, but it's
3872        // very out of sync with the specification.
3873        assert!(metadata.location_url.is_some());
3874
3875        let mut incomplete_loads = self.incomplete_loads.borrow_mut();
3876        let Some(incomplete_load) = incomplete_loads
3877            .iter_mut()
3878            .find(|incomplete_load| incomplete_load.pipeline_id == id)
3879        else {
3880            return;
3881        };
3882
3883        // Update the `url_list` of the incomplete load to track all redirects. This will be reflected
3884        // in the new `RequestBuilder` as well.
3885        incomplete_load.url_list.push(metadata.final_url.clone());
3886
3887        let mut request_builder = incomplete_load.request_builder();
3888        request_builder.referrer = metadata
3889            .referrer
3890            .clone()
3891            .map(Referrer::ReferrerUrl)
3892            .unwrap_or(Referrer::NoReferrer);
3893        request_builder.referrer_policy = metadata.referrer_policy;
3894
3895        let headers = metadata
3896            .headers
3897            .as_ref()
3898            .map(|headers| headers.clone().into_inner())
3899            .unwrap_or_default();
3900
3901        let response_init = Some(ResponseInit {
3902            url: metadata.final_url.clone(),
3903            location_url: metadata.location_url.clone(),
3904            headers,
3905            referrer: metadata.referrer.clone(),
3906            status_code: metadata
3907                .status
3908                .try_code()
3909                .map(|code| code.as_u16())
3910                .unwrap_or(200),
3911        });
3912
3913        incomplete_load.canceller = FetchCanceller::new(
3914            request_builder.id,
3915            false,
3916            self.resource_threads.core_thread.clone(),
3917        );
3918        NavigationListener::new(request_builder, self.senders.self_sender.clone())
3919            .initiate_fetch(&self.resource_threads.core_thread, response_init);
3920    }
3921
3922    /// Synchronously fetch `about:blank`. Stores the `InProgressLoad`
3923    /// argument until a notification is received that the fetch is complete.
3924    fn start_page_load_about_blank(
3925        &self,
3926        cx: &mut js::context::JSContext,
3927        mut incomplete: InProgressLoad,
3928    ) {
3929        let url = ServoUrl::parse("about:blank").unwrap();
3930        let mut context = ParserContext::new(
3931            incomplete.webview_id,
3932            incomplete.pipeline_id,
3933            url.clone(),
3934            incomplete.load_data.creation_sandboxing_flag_set,
3935        );
3936
3937        let mut meta = Metadata::default(url);
3938        meta.set_content_type(Some(&mime::TEXT_HTML));
3939        meta.set_referrer_policy(incomplete.load_data.referrer_policy);
3940
3941        // If this page load is the result of a javascript scheme url, map
3942        // the evaluation result into a response.
3943        let chunk = match incomplete.load_data.js_eval_result {
3944            Some(JsEvalResult::Ok(ref mut content)) => std::mem::take(content),
3945            Some(JsEvalResult::NoContent) => {
3946                meta.status = http::StatusCode::NO_CONTENT.into();
3947                vec![]
3948            },
3949            None => vec![],
3950        };
3951
3952        let policy_container = incomplete.load_data.policy_container.clone();
3953        let about_base_url = incomplete.load_data.about_base_url.clone();
3954        self.incomplete_loads.borrow_mut().push(incomplete);
3955
3956        let dummy_request_id = RequestId::default();
3957        context.process_response(dummy_request_id, Ok(FetchMetadata::Unfiltered(meta)));
3958        context.set_policy_container(policy_container.as_ref());
3959        context.set_about_base_url(about_base_url);
3960        context.process_response_chunk(dummy_request_id, chunk);
3961        context.process_response_eof(
3962            cx,
3963            dummy_request_id,
3964            Ok(()),
3965            ResourceFetchTiming::new(ResourceTimingType::None),
3966        );
3967    }
3968
3969    /// Synchronously parse a srcdoc document from a giving HTML string.
3970    fn page_load_about_srcdoc(
3971        &self,
3972        cx: &mut js::context::JSContext,
3973        mut incomplete: InProgressLoad,
3974    ) {
3975        let url = ServoUrl::parse("about:srcdoc").unwrap();
3976        let mut meta = Metadata::default(url.clone());
3977        meta.set_content_type(Some(&mime::TEXT_HTML));
3978        meta.set_referrer_policy(incomplete.load_data.referrer_policy);
3979
3980        let srcdoc = std::mem::take(&mut incomplete.load_data.srcdoc);
3981        let chunk = srcdoc.into_bytes();
3982
3983        let policy_container = incomplete.load_data.policy_container.clone();
3984        let creation_sandboxing_flag_set = incomplete.load_data.creation_sandboxing_flag_set;
3985
3986        let webview_id = incomplete.webview_id;
3987        let pipeline_id = incomplete.pipeline_id;
3988        let about_base_url = incomplete.load_data.about_base_url.clone();
3989        self.incomplete_loads.borrow_mut().push(incomplete);
3990
3991        let mut context =
3992            ParserContext::new(webview_id, pipeline_id, url, creation_sandboxing_flag_set);
3993        let dummy_request_id = RequestId::default();
3994
3995        context.process_response(dummy_request_id, Ok(FetchMetadata::Unfiltered(meta)));
3996        context.set_policy_container(policy_container.as_ref());
3997        context.set_about_base_url(about_base_url);
3998        context.process_response_chunk(dummy_request_id, chunk);
3999        context.process_response_eof(
4000            cx,
4001            dummy_request_id,
4002            Ok(()),
4003            ResourceFetchTiming::new(ResourceTimingType::None),
4004        );
4005    }
4006
4007    fn handle_css_error_reporting(
4008        &self,
4009        pipeline_id: PipelineId,
4010        filename: String,
4011        line: u32,
4012        column: u32,
4013        msg: String,
4014    ) {
4015        let Some(ref sender) = self.senders.devtools_server_sender else {
4016            return;
4017        };
4018
4019        if let Some(global) = self.documents.borrow().find_global(pipeline_id) {
4020            if global.live_devtools_updates() {
4021                let css_error = CSSError {
4022                    filename,
4023                    line,
4024                    column,
4025                    msg,
4026                };
4027                let message = ScriptToDevtoolsControlMsg::ReportCSSError(pipeline_id, css_error);
4028                sender.send(message).unwrap();
4029            }
4030        }
4031    }
4032
4033    fn handle_reload(&self, pipeline_id: PipelineId, can_gc: CanGc) {
4034        let window = self.documents.borrow().find_window(pipeline_id);
4035        if let Some(window) = window {
4036            window.Location().reload_without_origin_check(can_gc);
4037        }
4038    }
4039
4040    fn handle_paint_metric(
4041        &self,
4042        pipeline_id: PipelineId,
4043        metric_type: ProgressiveWebMetricType,
4044        metric_value: CrossProcessInstant,
4045        first_reflow: bool,
4046        can_gc: CanGc,
4047    ) {
4048        match self.documents.borrow().find_document(pipeline_id) {
4049            Some(document) => {
4050                document.handle_paint_metric(metric_type, metric_value, first_reflow, can_gc)
4051            },
4052            None => warn!(
4053                "Received paint metric ({metric_type:?}) for unknown document: {pipeline_id:?}"
4054            ),
4055        }
4056    }
4057
4058    fn handle_media_session_action(
4059        &self,
4060        pipeline_id: PipelineId,
4061        action: MediaSessionActionType,
4062        can_gc: CanGc,
4063    ) {
4064        if let Some(window) = self.documents.borrow().find_window(pipeline_id) {
4065            let media_session = window.Navigator().MediaSession();
4066            media_session.handle_action(action, can_gc);
4067        } else {
4068            warn!("No MediaSession for this pipeline ID");
4069        };
4070    }
4071
4072    pub(crate) fn enqueue_microtask(job: Microtask) {
4073        with_script_thread(|script_thread| {
4074            script_thread
4075                .microtask_queue
4076                .enqueue(job, script_thread.get_cx());
4077        });
4078    }
4079
4080    pub(crate) fn perform_a_microtask_checkpoint(&self, can_gc: CanGc) {
4081        // Only perform the checkpoint if we're not shutting down.
4082        if self.can_continue_running_inner() {
4083            let globals = self
4084                .documents
4085                .borrow()
4086                .iter()
4087                .map(|(_id, document)| DomRoot::from_ref(document.window().upcast()))
4088                .collect();
4089
4090            self.microtask_queue.checkpoint(
4091                self.get_cx(),
4092                |id| self.documents.borrow().find_global(id),
4093                globals,
4094                can_gc,
4095            )
4096        }
4097    }
4098
4099    fn handle_evaluate_javascript(
4100        &self,
4101        webview_id: WebViewId,
4102        pipeline_id: PipelineId,
4103        evaluation_id: JavaScriptEvaluationId,
4104        script: String,
4105        cx: &mut js::context::JSContext,
4106    ) {
4107        let Some(window) = self.documents.borrow().find_window(pipeline_id) else {
4108            let _ = self.senders.pipeline_to_constellation_sender.send((
4109                webview_id,
4110                pipeline_id,
4111                ScriptToConstellationMessage::FinishJavaScriptEvaluation(
4112                    evaluation_id,
4113                    Err(JavaScriptEvaluationError::WebViewNotReady),
4114                ),
4115            ));
4116            return;
4117        };
4118
4119        let global_scope = window.as_global_scope();
4120        let mut realm = enter_auto_realm(cx, global_scope);
4121        let cx = &mut realm.current_realm();
4122
4123        rooted!(&in(cx) let mut return_value = UndefinedValue());
4124        if let Err(err) = global_scope.evaluate_js_on_global(
4125            script.into(),
4126            "",
4127            None, // No known `introductionType` for JS code from embedder
4128            return_value.handle_mut(),
4129            CanGc::from_cx(cx),
4130        ) {
4131            _ = self.senders.pipeline_to_constellation_sender.send((
4132                webview_id,
4133                pipeline_id,
4134                ScriptToConstellationMessage::FinishJavaScriptEvaluation(evaluation_id, Err(err)),
4135            ));
4136            return;
4137        };
4138
4139        let result = jsval_to_webdriver(cx, global_scope, return_value.handle());
4140        let _ = self.senders.pipeline_to_constellation_sender.send((
4141            webview_id,
4142            pipeline_id,
4143            ScriptToConstellationMessage::FinishJavaScriptEvaluation(evaluation_id, result),
4144        ));
4145    }
4146
4147    fn handle_refresh_cursor(&self, pipeline_id: PipelineId) {
4148        let Some(document) = self.documents.borrow().find_document(pipeline_id) else {
4149            return;
4150        };
4151        document.event_handler().handle_refresh_cursor();
4152    }
4153
4154    pub(crate) fn is_servo_privileged(url: ServoUrl) -> bool {
4155        with_script_thread(|script_thread| script_thread.privileged_urls.contains(&url))
4156    }
4157
4158    fn handle_request_screenshot_readiness(
4159        &self,
4160        webview_id: WebViewId,
4161        pipeline_id: PipelineId,
4162        can_gc: CanGc,
4163    ) {
4164        let Some(window) = self.documents.borrow().find_window(pipeline_id) else {
4165            let _ = self.senders.pipeline_to_constellation_sender.send((
4166                webview_id,
4167                pipeline_id,
4168                ScriptToConstellationMessage::RespondToScreenshotReadinessRequest(
4169                    ScreenshotReadinessResponse::NoLongerActive,
4170                ),
4171            ));
4172            return;
4173        };
4174        window.request_screenshot_readiness(can_gc);
4175    }
4176
4177    fn handle_embedder_control_response(
4178        &self,
4179        id: EmbedderControlId,
4180        response: EmbedderControlResponse,
4181        can_gc: CanGc,
4182    ) {
4183        let Some(document) = self.documents.borrow().find_document(id.pipeline_id) else {
4184            return;
4185        };
4186        document
4187            .embedder_controls()
4188            .handle_embedder_control_response(id, response, can_gc);
4189    }
4190
4191    pub(crate) fn handle_update_pinch_zoom_infos(
4192        &self,
4193        pipeline_id: PipelineId,
4194        pinch_zoom_infos: PinchZoomInfos,
4195        can_gc: CanGc,
4196    ) {
4197        let Some(window) = self.documents.borrow().find_window(pipeline_id) else {
4198            warn!("Visual viewport update for closed pipeline {pipeline_id}.");
4199            return;
4200        };
4201
4202        window.maybe_update_visual_viewport(pinch_zoom_infos, can_gc);
4203    }
4204}
4205
4206impl Drop for ScriptThread {
4207    fn drop(&mut self) {
4208        SCRIPT_THREAD_ROOT.with(|root| {
4209            root.set(None);
4210        });
4211    }
4212}