Skip to main content

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