1use 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;
39#[cfg(feature = "devtools")]
40use devtools_traits::{
41 CSSError, DevtoolScriptControlMsg, DevtoolsPageInfo, NavigationState,
42 ScriptToDevtoolsControlMsg,
43};
44use embedder_traits::user_contents::{UserContentManagerId, UserContents, UserScript};
45use embedder_traits::{
46 EmbedderControlId, EmbedderControlResponse, EmbedderMsg, FocusSequenceNumber,
47 InputEventOutcome, JavaScriptEvaluationError, JavaScriptEvaluationId, MediaSessionActionType,
48 ViewportDetails, WebDriverScriptCommand,
49};
50use encoding_rs::Encoding;
51use fonts::{FontContext, SystemFontServiceProxy, WebFontLoadEvent};
52use headers::{HeaderMapExt, LastModified, ReferrerPolicy as ReferrerPolicyHeader};
53use http::header::REFRESH;
54use hyper_serde::Serde;
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::blob_url_store::UrlWithBlobClaim;
65use net_traits::fetch::fetch_controller::FetchController;
66use net_traits::image_cache::{ImageCacheFactory, ImageCacheResponseMessage};
67use net_traits::pub_domains::is_same_site;
68use net_traits::request::{Referrer, RequestId};
69use net_traits::response::ResponseInit;
70use net_traits::{
71 FetchMetadata, FetchResponseMsg, Metadata, NetworkError, ResourceFetchTiming, ResourceThreads,
72 ResourceTimingType,
73};
74use paint_api::{CrossProcessPaintApi, PinchZoomInfos, PipelineExitSource};
75use percent_encoding::percent_decode;
76use profile_traits::mem::{ProcessReports, ReportsChan, perform_memory_report};
77use profile_traits::time::ProfilerCategory;
78use profile_traits::time_profile;
79use rustc_hash::{FxHashMap, FxHashSet};
80use script_bindings::cell::DomRefCell;
81use script_traits::{
82 ConstellationInputEvent, DiscardBrowsingContext, DocumentActivity, InitialScriptState,
83 NewPipelineInfo, Painter, ScriptThreadMessage, UpdatePipelineIdReason, WebViewState,
84};
85use servo_arc::Arc as ServoArc;
86use servo_base::cross_process_instant::CrossProcessInstant;
87use servo_base::generic_channel::GenericSender;
88use servo_base::id::{
89 BrowsingContextId, HistoryStateId, PipelineId, PipelineNamespace, ScriptEventLoopId,
90 ServiceWorkerId, WebViewId, WorkerId,
91};
92use servo_base::threadboost::{BoostAffinity, ThreadPriority};
93use servo_base::{Epoch, generic_channel};
94#[cfg(feature = "webgl")]
95use servo_canvas_traits::webgl::WebGLPipeline;
96use servo_config::opts::{self, DiagnosticsLoggingOption};
97use servo_config::{pref, prefs};
98use servo_constellation_traits::{
99 HistoryTraversalSource, LoadData, LoadOrigin, NavigationHistoryBehavior, PaintMetricEvent,
100 RemoteFocusOperation, ScreenshotReadinessResponse, ScriptToConstellationChan,
101 ScriptToConstellationMessage, ScrollStateUpdate, ServiceWorkerState,
102 SessionHistoryTraversalRequest, StructuredSerializedData, TargetSnapshotParams,
103 TraversalDirection, WindowSizeType,
104};
105use servo_url::{ImmutableOrigin, MutableOrigin, OriginSnapshot, ServoUrl};
106use smallvec::SmallVec;
107use storage_traits::StorageThreads;
108use storage_traits::webstorage_thread::WebStorageType;
109use style::context::QuirksMode;
110use style::error_reporting::RustLogReporter;
111use style::media_queries::MediaList;
112use style::shared_lock::SharedRwLock;
113use style::stylesheets::{AllowImportRules, DocumentStyleSheet, Origin, Stylesheet};
114use style::thread_state::{self, ThreadState};
115use stylo_atoms::Atom;
116use timers::{TimerEventRequest, TimerId, TimerScheduler};
117use url::Position;
118#[cfg(feature = "webgpu")]
119use webgpu_traits::{WebGPUDevice, WebGPUMsg};
120
121use crate::dom::bindings::codegen::Bindings::DocumentBinding::DocumentMethods;
122use crate::dom::bindings::codegen::Bindings::NavigatorBinding::NavigatorMethods;
123use crate::dom::bindings::codegen::Bindings::WindowBinding::WindowMethods;
124use crate::dom::bindings::conversions::{
125 ConversionResult, FromJSValConvertible, StringificationBehavior,
126};
127use crate::dom::bindings::inheritance::Castable;
128use crate::dom::bindings::refcounted::{LivePromiseReferences, Trusted};
129use crate::dom::bindings::root::{Dom, DomRoot};
130use crate::dom::bindings::str::DOMString;
131use crate::dom::csp::{CspReporting, GlobalCspReporting, Violation};
132use crate::dom::customelementregistry::{
133 CallbackReaction, CustomElementDefinition, CustomElementReactionStack,
134};
135use crate::dom::document::focus::FocusableArea;
136use crate::dom::document::{
137 AbortReason, Document, HasBrowsingContext, IsHTMLDocument, RenderingUpdateReason,
138};
139use crate::dom::element::Element;
140use crate::dom::globalscope::GlobalScope;
141use crate::dom::html::htmliframeelement::{HTMLIFrameElement, IframeContext, ProcessingMode};
142use crate::dom::node::{Node, NodeTraits};
143use crate::dom::servoparser::{ParserContext, ServoParser};
144#[cfg(feature = "devtools")]
145use crate::dom::types::DebuggerGlobalScope;
146use crate::dom::types::SharedWorker;
147#[cfg(feature = "webgpu")]
148use crate::dom::webgpu::identityhub::IdentityHub;
149use crate::dom::window::Window;
150use crate::dom::windowproxy::{CreatorBrowsingContextInfo, WindowProxy};
151#[cfg(feature = "devtools")]
152use crate::event_loop::devtools::{self, DevtoolsState};
153use crate::event_loop::document_collection::DocumentCollection;
154use crate::event_loop::document_loader::DocumentLoader;
155use crate::event_loop::script_mutation_observers::ScriptMutationObservers;
156use crate::event_loop::script_window_proxies::ScriptWindowProxies;
157use crate::event_loop::svg_font::SvgFontResolver;
158use crate::event_loop::webdriver_handlers::{self, jsval_to_webdriver};
159use crate::fetch::network_listener::submit_timing;
160use crate::messaging::{
161 CommonScriptMsg, MainThreadScriptMsg, MixedMessage, ScriptEventLoopSender,
162 ScriptThreadReceivers, ScriptThreadSenders,
163};
164use crate::mime::{APPLICATION, CHARSET, MimeExt, TEXT, XML};
165use crate::navigation::{InProgressLoad, NavigationListener};
166use crate::realms::enter_auto_realm;
167use crate::runtime::job_queue::{MicrotaskRunnable, job_queue_microtask_checkpoint};
168use crate::runtime::script_runtime::{
169 IntroductionType, Runtime, ScriptThreadEventCategory, ThreadSafeJSContext, get_reports,
170};
171use crate::scripting::fetching_options::ScriptFetchOptions;
172use crate::scripting::script_execution::{RethrowErrors, ScriptOptions};
173use crate::tasks::task_queue::TaskQueue;
174
175thread_local!(static SCRIPT_THREAD_ROOT: Cell<Option<*const ScriptThread>> = const { Cell::new(None) });
176
177fn with_optional_script_thread<R>(f: impl FnOnce(Option<&ScriptThread>) -> R) -> R {
178 SCRIPT_THREAD_ROOT.with(|root| {
179 f(root
180 .get()
181 .and_then(|script_thread| unsafe { script_thread.as_ref() }))
182 })
183}
184
185pub(crate) fn with_script_thread<R: Default>(f: impl FnOnce(&ScriptThread) -> R) -> R {
186 with_optional_script_thread(|script_thread| script_thread.map(f).unwrap_or_default())
187}
188
189pub(crate) struct IncompleteParserContexts(RefCell<Vec<(PipelineId, ParserContext)>>);
195
196unsafe_no_jsmanaged_fields!(TaskQueue<MainThreadScriptMsg>);
197
198type NodeIdSet = HashSet<String>;
199
200#[derive(Default)]
202pub(crate) struct ScriptUserInteractingGuard {
203 was_interacting: bool,
204 user_interaction_cell: Rc<Cell<bool>>,
205}
206
207impl ScriptUserInteractingGuard {
208 fn new(user_interaction_cell: Rc<Cell<bool>>) -> Self {
209 let was_interacting = user_interaction_cell.get();
210 user_interaction_cell.set(true);
211 Self {
212 was_interacting,
213 user_interaction_cell,
214 }
215 }
216}
217
218impl Drop for ScriptUserInteractingGuard {
219 fn drop(&mut self) {
220 self.user_interaction_cell.set(self.was_interacting)
221 }
222}
223
224struct ScriptThreadUserContents {
227 user_scripts: Rc<Vec<UserScript>>,
228 user_stylesheets: Rc<Vec<DocumentStyleSheet>>,
229}
230
231impl ScriptThreadUserContents {
232 fn new(user_contents: UserContents, shared_locks: &SharedRwLocks) -> Self {
233 let user_stylesheets = user_contents
234 .stylesheets
235 .iter()
236 .map(|user_stylesheet| {
237 DocumentStyleSheet(ServoArc::new(Stylesheet::from_str(
238 user_stylesheet.source(),
239 user_stylesheet.url().into(),
240 Origin::User,
241 ServoArc::new(shared_locks.ua_or_user.wrap(MediaList::empty())),
242 shared_locks.ua_or_user.clone(),
243 None,
244 Some(&RustLogReporter),
245 QuirksMode::NoQuirks,
246 AllowImportRules::Yes,
247 )))
248 })
249 .collect();
250 Self {
251 user_scripts: Rc::new(user_contents.scripts),
252 user_stylesheets: Rc::new(user_stylesheets),
253 }
254 }
255}
256
257#[derive(Clone, MallocSizeOf)]
258pub struct SharedRwLocks {
259 pub author: SharedRwLock,
260 pub ua_or_user: SharedRwLock,
261}
262
263impl Default for SharedRwLocks {
264 fn default() -> Self {
265 Self {
266 author: SharedRwLock::new(),
267 ua_or_user: SharedRwLock::new(),
268 }
269 }
270}
271
272#[derive(JSTraceable)]
273#[cfg_attr(crown, expect(crown::unrooted_must_root))]
275pub struct ScriptThread {
276 #[no_trace]
279 this: Weak<ScriptThread>,
280
281 last_render_opportunity_time: Cell<Option<Instant>>,
283 #[no_trace]
288 webview_states: RefCell<FxHashMap<WebViewId, Weak<WebViewState>>>,
289
290 documents: DomRefCell<DocumentCollection>,
292
293 window_proxies: Rc<ScriptWindowProxies>,
295
296 incomplete_loads: DomRefCell<Vec<InProgressLoad>>,
298
299 incomplete_parser_contexts: IncompleteParserContexts,
301
302 #[no_trace]
305 image_cache_factory: Arc<dyn ImageCacheFactory>,
306
307 receivers: ScriptThreadReceivers,
310
311 senders: ScriptThreadSenders,
314
315 #[no_trace]
318 resource_threads: ResourceThreads,
319
320 #[no_trace]
321 storage_threads: StorageThreads,
322
323 task_queue: TaskQueue<MainThreadScriptMsg>,
325
326 #[no_trace]
328 background_hang_monitor: Box<dyn BackgroundHangMonitor>,
329 closing: Arc<AtomicBool>,
331
332 #[no_trace]
335 timer_scheduler: RefCell<TimerScheduler>,
336
337 #[no_trace]
339 system_font_service: Arc<SystemFontServiceProxy>,
340
341 js_runtime: Rc<Runtime>,
343
344 mutation_observers: Rc<ScriptMutationObservers>,
345
346 #[no_trace]
348 #[cfg(feature = "webgl")]
349 webgl_chan: Option<WebGLPipeline>,
350
351 #[no_trace]
353 #[cfg(feature = "webxr")]
354 webxr_registry: Option<webxr_api::Registry>,
355
356 custom_element_reaction_stack: Rc<CustomElementReactionStack>,
358
359 #[no_trace]
361 paint_api: CrossProcessPaintApi,
362
363 profile_script_events: bool,
365
366 unminify_js: bool,
368
369 local_script_source: Option<String>,
371
372 unminify_css: bool,
374
375 #[no_trace]
377 shared_style_locks: SharedRwLocks,
378
379 #[no_trace]
383 user_contents_for_manager_id:
384 RefCell<FxHashMap<UserContentManagerId, ScriptThreadUserContents>>,
385
386 #[no_trace]
388 player_context: WindowGLContext,
389
390 #[no_trace]
392 pipeline_to_node_ids: DomRefCell<FxHashMap<PipelineId, NodeIdSet>>,
393
394 is_user_interacting: Rc<Cell<bool>>,
396
397 #[no_trace]
399 #[cfg(feature = "webgpu")]
400 gpu_id_hub: Arc<IdentityHub>,
401
402 #[no_trace]
404 layout_factory: Arc<dyn LayoutFactory>,
405
406 #[no_trace]
410 scheduled_update_the_rendering: RefCell<Option<TimerId>>,
411
412 needs_rendering_update: Arc<AtomicBool>,
419
420 #[cfg(feature = "devtools")]
421 debugger_global: Dom<DebuggerGlobalScope>,
422
423 debugger_paused: Cell<bool>,
424
425 #[no_trace]
427 privileged_urls: Vec<ServoUrl>,
428
429 #[cfg(feature = "devtools")]
430 devtools_state: DevtoolsState,
431
432 online: Cell<bool>,
434}
435
436struct BHMExitSignal {
437 closing: Arc<AtomicBool>,
438 js_context: ThreadSafeJSContext,
439}
440
441impl BackgroundHangMonitorExitSignal for BHMExitSignal {
442 fn signal_to_exit(&self) {
443 self.closing.store(true, Ordering::SeqCst);
444 self.js_context.request_interrupt_callback();
445 }
446}
447
448#[expect(unsafe_code)]
449unsafe extern "C" fn interrupt_callback(_cx: *mut UnsafeJSContext) -> bool {
450 let res = ScriptThread::can_continue_running();
451 if !res {
452 ScriptThread::prepare_for_shutdown();
453 }
454 res
455}
456
457struct ScriptMemoryFailsafe<'a> {
462 owner: Option<&'a ScriptThread>,
463}
464
465impl<'a> ScriptMemoryFailsafe<'a> {
466 fn neuter(&mut self) {
467 self.owner = None;
468 }
469
470 fn new(owner: &'a ScriptThread) -> ScriptMemoryFailsafe<'a> {
471 ScriptMemoryFailsafe { owner: Some(owner) }
472 }
473}
474
475impl Drop for ScriptMemoryFailsafe<'_> {
476 fn drop(&mut self) {
477 if let Some(owner) = self.owner {
478 for (_, document) in owner.documents.borrow().iter() {
479 document.window().clear_js_runtime_for_script_deallocation();
480 }
481 }
482 }
483}
484
485impl ScriptThreadFactory for ScriptThread {
486 fn create(
487 state: InitialScriptState,
488 layout_factory: Arc<dyn LayoutFactory>,
489 image_cache_factory: Arc<dyn ImageCacheFactory>,
490 background_hang_monitor_register: Box<dyn BackgroundHangMonitorRegister>,
491 ) -> JoinHandle<()> {
492 PipelineNamespace::set_installer_sender(state.namespace_request_sender.clone());
495
496 let script_thread_id = state.id;
497 thread::Builder::new()
498 .name(format!("Script#{script_thread_id}"))
499 .stack_size(8 * 1024 * 1024) .spawn(move || {
501 profile_traits::debug_event!(
502 "ScriptThread::spawned",
503 script_thread_id = script_thread_id.to_string()
504 );
505 thread_state::initialize(ThreadState::SCRIPT);
506 PipelineNamespace::install(state.pipeline_namespace_id);
507 ScriptEventLoopId::install(state.id);
508 let memory_profiler_sender = state.memory_profiler_sender.clone();
509 let reporter_name = format!("script-reporter-{script_thread_id:?}");
510 let (script_thread, mut cx) = ScriptThread::new(
511 state,
512 layout_factory,
513 image_cache_factory,
514 background_hang_monitor_register,
515 );
516 SCRIPT_THREAD_ROOT.with(|root| {
517 root.set(Some(Rc::as_ptr(&script_thread)));
518 });
519 servo_base::threadboost::boost_thread(
520 ThreadPriority::Critical,
521 BoostAffinity::Boost,
522 );
523 let mut failsafe = ScriptMemoryFailsafe::new(&script_thread);
524
525 memory_profiler_sender.run_with_memory_reporting(
526 || script_thread.start(&mut cx),
527 reporter_name,
528 ScriptEventLoopSender::MainThread(script_thread.senders.self_sender.clone()),
529 CommonScriptMsg::CollectReports,
530 );
531
532 failsafe.neuter();
534 })
535 .expect("Thread spawning failed")
536 }
537}
538
539#[servo_tracing::instrument_all(skip_all)]
540impl ScriptThread {
541 fn shared_webview_state(&self, shared_state: &WebViewState) -> Rc<WebViewState> {
545 let mut webview_states = self.webview_states.borrow_mut();
546
547 webview_states.retain(|_, state| state.upgrade().is_some());
549
550 if let Some(existing_shared_state) =
551 webview_states.get(&shared_state.id).and_then(Weak::upgrade)
552 {
553 return existing_shared_state;
554 }
555
556 let shared_state = Rc::new(shared_state.clone());
557 webview_states.insert(shared_state.id, Rc::downgrade(&shared_state));
558 shared_state
559 }
560
561 pub(crate) fn runtime_handle() -> ParentRuntime {
562 with_optional_script_thread(|script_thread| {
563 script_thread.unwrap().js_runtime.prepare_for_new_child()
564 })
565 }
566
567 pub(crate) fn can_continue_running() -> bool {
568 with_script_thread(|script_thread| script_thread.can_continue_running_inner())
569 }
570
571 pub(crate) fn prepare_for_shutdown() {
572 with_script_thread(|script_thread| {
573 script_thread.prepare_for_shutdown_inner();
574 })
575 }
576
577 pub(crate) fn mutation_observers() -> Rc<ScriptMutationObservers> {
578 with_script_thread(|script_thread| script_thread.mutation_observers.clone())
579 }
580
581 pub(crate) fn shared_style_locks(&self) -> &SharedRwLocks {
582 &self.shared_style_locks
583 }
584
585 pub(crate) fn process_event(cx: &mut JSContext, msg: CommonScriptMsg) -> bool {
589 with_script_thread(|script_thread| {
590 if !script_thread.can_continue_running_inner() {
591 return false;
592 }
593 script_thread.handle_msg_from_script(cx, MainThreadScriptMsg::Common(msg));
594 true
595 })
596 }
597
598 pub(crate) fn schedule_timer(&self, request: TimerEventRequest) -> TimerId {
600 self.timer_scheduler.borrow_mut().schedule_timer(request)
601 }
602
603 pub(crate) fn cancel_timer(&self, timer_id: TimerId) {
606 self.timer_scheduler.borrow_mut().cancel_timer(timer_id)
607 }
608
609 pub(crate) fn await_stable_state(cx: &JSContext, task: Box<dyn MicrotaskRunnable>) {
611 crate::runtime::job_queue::enqueue(cx, task);
612 }
613
614 fn check_load_origin(source: &LoadOrigin, target: &OriginSnapshot) -> bool {
619 match source {
620 LoadOrigin::Constellation | LoadOrigin::WebDriver => {
621 true
623 },
624 LoadOrigin::Script(source_origin) => source_origin.same_origin_domain(target),
625 }
626 }
627
628 pub(crate) fn set_needs_rendering_update(&self) {
632 self.needs_rendering_update.store(true, Ordering::Relaxed);
633 }
634
635 pub(crate) fn can_navigate_to_javascript_url(
637 cx: &mut JSContext,
638 initiator_global: &GlobalScope,
639 target_global: &GlobalScope,
640 load_data: &mut LoadData,
641 container: Option<&Element>,
642 ) -> bool {
643 if !Self::check_load_origin(&load_data.load_origin, &target_global.origin().snapshot()) {
647 return false;
648 }
649
650 if initiator_global
653 .get_csp_list()
654 .should_navigation_request_be_blocked(cx, initiator_global, load_data, container)
655 {
656 return false;
657 }
658
659 true
660 }
661
662 pub(crate) fn navigate_to_javascript_url(
665 cx: &mut JSContext,
666 initiator_global: &GlobalScope,
667 target_global: &GlobalScope,
668 load_data: &mut LoadData,
669 container: Option<&Element>,
670 initial_insertion: Option<bool>,
671 ) -> bool {
672 if !Self::can_navigate_to_javascript_url(
675 cx,
676 initiator_global,
677 target_global,
678 load_data,
679 container,
680 ) {
681 return false;
682 }
683
684 if !Self::evaluate_a_javascript_url(cx, target_global, load_data) {
687 let window_proxy = target_global.as_window().window_proxy();
689 if let Some(frame_element) = window_proxy
690 .frame_element()
691 .and_then(Castable::downcast::<HTMLIFrameElement>)
692 {
693 if initial_insertion == Some(true) && frame_element.is_initial_blank_document() {
697 frame_element.run_iframe_load_event_steps(cx);
698 }
699 }
700 return false;
702 }
703
704 true
705 }
706
707 pub(crate) fn get_top_level_for_browsing_context(
708 sender_webview_id: WebViewId,
709 sender_pipeline_id: PipelineId,
710 browsing_context_id: BrowsingContextId,
711 ) -> Option<WebViewId> {
712 with_script_thread(|script_thread| {
713 script_thread.ask_constellation_for_top_level_info(
714 sender_webview_id,
715 sender_pipeline_id,
716 browsing_context_id,
717 )
718 })
719 }
720
721 pub(crate) fn find_window(id: PipelineId) -> Option<DomRoot<Window>> {
722 with_script_thread(|script_thread| script_thread.documents.borrow().find_window(id))
723 }
724
725 pub(crate) fn find_document(id: PipelineId) -> Option<DomRoot<Document>> {
726 with_script_thread(|script_thread| script_thread.documents.borrow().find_document(id))
727 }
728
729 #[must_use]
733 pub(crate) fn user_interacting_guard() -> ScriptUserInteractingGuard {
734 with_script_thread(|script_thread| {
735 ScriptUserInteractingGuard::new(script_thread.is_user_interacting.clone())
736 })
737 }
738
739 pub(crate) fn is_user_interacting() -> bool {
740 with_script_thread(|script_thread| script_thread.is_user_interacting.get())
741 }
742
743 pub(crate) fn get_fully_active_document_ids(&self) -> FxHashSet<PipelineId> {
744 self.documents
745 .borrow()
746 .iter()
747 .filter_map(|(id, document)| {
748 if document.is_fully_active() {
749 Some(id)
750 } else {
751 None
752 }
753 })
754 .fold(FxHashSet::default(), |mut set, id| {
755 let _ = set.insert(id);
756 set
757 })
758 }
759
760 pub(crate) fn window_proxies() -> Rc<ScriptWindowProxies> {
761 with_script_thread(|script_thread| script_thread.window_proxies.clone())
762 }
763
764 fn handle_register_paint_worklet(
765 &self,
766 pipeline_id: PipelineId,
767 name: Atom,
768 properties: Vec<Atom>,
769 painter: Box<dyn Painter>,
770 ) {
771 let Some(window) = self.documents.borrow().find_window(pipeline_id) else {
772 warn!("Paint worklet registered after pipeline {pipeline_id} closed.");
773 return;
774 };
775
776 window
777 .layout_mut()
778 .register_paint_worklet_modules(name, properties, painter);
779 }
780
781 pub(crate) fn custom_element_reaction_stack() -> Rc<CustomElementReactionStack> {
782 with_optional_script_thread(|script_thread| {
783 script_thread
784 .as_ref()
785 .unwrap()
786 .custom_element_reaction_stack
787 .clone()
788 })
789 }
790
791 pub(crate) fn enqueue_callback_reaction(
792 cx: &mut JSContext,
793 element: &Element,
794 reaction: CallbackReaction,
795 definition: Option<Rc<CustomElementDefinition>>,
796 ) {
797 with_script_thread(|script_thread| {
798 script_thread
799 .custom_element_reaction_stack
800 .enqueue_callback_reaction(cx, element, reaction, definition);
801 })
802 }
803
804 pub(crate) fn enqueue_upgrade_reaction(
805 cx: &js::context::JSContext,
806 element: &Element,
807 definition: Rc<CustomElementDefinition>,
808 ) {
809 with_script_thread(|script_thread| {
810 script_thread
811 .custom_element_reaction_stack
812 .enqueue_upgrade_reaction(cx, element, definition);
813 })
814 }
815
816 pub(crate) fn invoke_backup_element_queue(cx: &mut JSContext) {
817 with_script_thread(|script_thread| {
818 script_thread
819 .custom_element_reaction_stack
820 .invoke_backup_element_queue(cx);
821 })
822 }
823
824 pub(crate) fn save_node_id(pipeline: PipelineId, node_id: String) {
825 with_script_thread(|script_thread| {
826 script_thread
827 .pipeline_to_node_ids
828 .borrow_mut()
829 .entry(pipeline)
830 .or_default()
831 .insert(node_id);
832 })
833 }
834
835 pub(crate) fn has_node_id(pipeline: PipelineId, node_id: &str) -> bool {
836 with_script_thread(|script_thread| {
837 script_thread
838 .pipeline_to_node_ids
839 .borrow()
840 .get(&pipeline)
841 .is_some_and(|node_ids| node_ids.contains(node_id))
842 })
843 }
844
845 #[servo_tracing::instrument(name = "ScripThread::new", level = "debug", skip_all)]
847 pub(crate) fn new(
848 state: InitialScriptState,
849 layout_factory: Arc<dyn LayoutFactory>,
850 image_cache_factory: Arc<dyn ImageCacheFactory>,
851 background_hang_monitor_register: Box<dyn BackgroundHangMonitorRegister>,
852 ) -> (Rc<ScriptThread>, js::context::JSContext) {
853 let (self_sender, self_receiver) = unbounded();
854 let mut runtime =
855 Runtime::new(Some(ScriptEventLoopSender::MainThread(self_sender.clone())));
856
857 let mut cx = unsafe { runtime.cx() };
860
861 unsafe {
862 SetWindowProxyClass(&cx, GetWindowProxyClass());
863 JS_AddInterruptCallback(&cx, Some(interrupt_callback));
864 }
865
866 let constellation_receiver = state
867 .constellation_to_script_receiver
868 .route_preserving_errors();
869
870 #[cfg(feature = "devtools")]
872 let (devtools_server_sender, ipc_devtools_sender, devtools_server_receiver) = {
873 let (ipc_devtools_sender, ipc_devtools_receiver) = generic_channel::channel().unwrap();
874 (
875 state.devtools_server_sender,
876 ipc_devtools_sender,
877 ipc_devtools_receiver.route_preserving_errors(),
878 )
879 };
880
881 let task_queue = TaskQueue::new(self_receiver);
882
883 let closing = Arc::new(AtomicBool::new(false));
884 let background_hang_monitor_exit_signal = BHMExitSignal {
885 closing: closing.clone(),
886 js_context: runtime.thread_safe_js_context(),
887 };
888
889 let background_hang_monitor = background_hang_monitor_register.register_component(
890 MonitoredComponentId(state.id, MonitoredComponentType::Script),
893 Duration::from_millis(1000),
894 Duration::from_millis(5000),
895 Box::new(background_hang_monitor_exit_signal),
896 );
897
898 let (image_cache_sender, image_cache_receiver) = unbounded();
899
900 let receivers = ScriptThreadReceivers {
901 constellation_receiver,
902 image_cache_receiver,
903 #[cfg(feature = "devtools")]
904 devtools_server_receiver,
905 #[cfg(feature = "webgpu")]
907 webgpu_receiver: RefCell::new(crossbeam_channel::never()),
908 };
909
910 let opts = opts::get();
911 let senders = ScriptThreadSenders {
912 self_sender,
913 #[cfg(feature = "bluetooth")]
914 bluetooth_sender: state.bluetooth_sender,
915 constellation_sender: state.constellation_to_script_sender,
916 pipeline_to_constellation_sender: state.script_to_constellation_sender,
917 pipeline_to_embedder_sender: state.script_to_embedder_sender.clone(),
918 image_cache_sender,
919 time_profiler_sender: state.time_profiler_sender,
920 memory_profiler_sender: state.memory_profiler_sender,
921 #[cfg(feature = "devtools")]
922 devtools_server_sender,
923 #[cfg(feature = "devtools")]
924 devtools_client_to_script_thread_sender: ipc_devtools_sender,
925 };
926
927 #[cfg(feature = "webgpu")]
928 let gpu_id_hub = Arc::new(IdentityHub::default());
929
930 #[cfg(feature = "devtools")]
931 let debugger_global = {
932 let debugger_global = DebuggerGlobalScope::new(
933 PipelineId::new(),
934 senders.devtools_server_sender.clone(),
935 senders.devtools_client_to_script_thread_sender.clone(),
936 senders.memory_profiler_sender.clone(),
937 senders.time_profiler_sender.clone(),
938 senders.pipeline_to_constellation_sender.clone(),
939 senders.pipeline_to_embedder_sender.clone(),
940 state.resource_threads.clone(),
941 state.storage_threads.clone(),
942 #[cfg(feature = "webgpu")]
943 gpu_id_hub.clone(),
944 &mut cx,
945 );
946 debugger_global.execute(&mut cx);
947 debugger_global
948 };
949
950 let shared_style_locks = Default::default();
951 let user_contents_for_manager_id =
952 FxHashMap::from_iter(state.user_contents_for_manager_id.into_iter().map(
953 |(user_content_manager_id, user_contents)| {
954 (
955 user_content_manager_id,
956 ScriptThreadUserContents::new(user_contents, &shared_style_locks),
957 )
958 },
959 ));
960
961 (
962 Rc::new_cyclic(|weak_script_thread| {
963 runtime.set_script_thread(weak_script_thread.clone());
964 Self {
965 webview_states: Default::default(),
966 documents: DomRefCell::new(DocumentCollection::default()),
967 last_render_opportunity_time: Default::default(),
968 window_proxies: Default::default(),
969 incomplete_loads: DomRefCell::new(vec![]),
970 incomplete_parser_contexts: IncompleteParserContexts(RefCell::new(vec![])),
971 senders,
972 receivers,
973 image_cache_factory,
974 resource_threads: state.resource_threads,
975 storage_threads: state.storage_threads,
976 task_queue,
977 background_hang_monitor,
978 closing,
979 timer_scheduler: Default::default(),
980 js_runtime: Rc::new(runtime),
981 mutation_observers: Default::default(),
982 system_font_service: Arc::new(state.system_font_service.to_proxy()),
983 #[cfg(feature = "webgl")]
984 webgl_chan: state.webgl_chan,
985 #[cfg(feature = "webxr")]
986 webxr_registry: state.webxr_registry,
987 custom_element_reaction_stack: Rc::new(CustomElementReactionStack::new()),
988 paint_api: state.cross_process_paint_api,
989 profile_script_events: opts
990 .debug
991 .is_enabled(DiagnosticsLoggingOption::ProfileScriptEvents),
992 unminify_js: opts.unminify_js,
993 local_script_source: opts.local_script_source.clone(),
994 unminify_css: opts.unminify_css,
995 shared_style_locks,
996 user_contents_for_manager_id: RefCell::new(user_contents_for_manager_id),
997 player_context: state.player_context,
998 pipeline_to_node_ids: Default::default(),
999 is_user_interacting: Rc::new(Cell::new(false)),
1000 #[cfg(feature = "webgpu")]
1001 gpu_id_hub,
1002 layout_factory,
1003 scheduled_update_the_rendering: Default::default(),
1004 needs_rendering_update: Arc::new(AtomicBool::new(false)),
1005 #[cfg(feature = "devtools")]
1006 debugger_global: debugger_global.as_traced(),
1007 debugger_paused: Cell::new(false),
1008 privileged_urls: state.privileged_urls,
1009 this: weak_script_thread.clone(),
1010 #[cfg(feature = "devtools")]
1011 devtools_state: Default::default(),
1012 online: state.online.into(),
1013 }
1014 }),
1015 cx,
1016 )
1017 }
1018
1019 fn can_continue_running_inner(&self) -> bool {
1021 if self.closing.load(Ordering::SeqCst) {
1022 return false;
1023 }
1024 true
1025 }
1026
1027 fn prepare_for_shutdown_inner(&self) {
1029 let docs = self.documents.borrow();
1030 for (_, document) in docs.iter() {
1031 document
1032 .owner_global()
1033 .task_manager()
1034 .cancel_all_tasks_and_ignore_future_tasks();
1035 }
1036 }
1037
1038 pub(crate) fn start(&self, cx: &mut JSContext) {
1041 debug!("Starting script thread.");
1042 while self.handle_msgs(cx) {
1043 debug!("Running script thread.");
1045 }
1046 debug!("Stopped script thread.");
1047 }
1048
1049 fn process_pending_input_events(&self, cx: &mut JSContext, pipeline_id: PipelineId) {
1051 let Some(document) = self.documents.borrow().find_document(pipeline_id) else {
1052 warn!("Processing pending input events for closed pipeline {pipeline_id}.");
1053 return;
1054 };
1055 if document.window().Closed() {
1057 warn!("Input event sent to a pipeline with a closed window {pipeline_id}.");
1058 return;
1059 }
1060 if !document.event_handler().has_pending_input_events() {
1061 return;
1062 }
1063
1064 let _guard = ScriptUserInteractingGuard::new(self.is_user_interacting.clone());
1065 document.event_handler().handle_pending_input_events(cx);
1066 }
1067
1068 fn cancel_scheduled_update_the_rendering(&self) {
1069 if let Some(timer_id) = self.scheduled_update_the_rendering.borrow_mut().take() {
1070 self.timer_scheduler.borrow_mut().cancel_timer(timer_id);
1071 }
1072 }
1073
1074 fn schedule_update_the_rendering_timer_if_necessary(&self, delay: Duration) {
1075 if self.scheduled_update_the_rendering.borrow().is_some() {
1076 return;
1077 }
1078
1079 debug!("Scheduling ScriptThread animation frame.");
1080 let trigger_script_thread_animation = self.needs_rendering_update.clone();
1081 let timer_id = self.schedule_timer(TimerEventRequest {
1082 callback: Box::new(move || {
1083 trigger_script_thread_animation.store(true, Ordering::Relaxed);
1084 }),
1085 duration: delay,
1086 });
1087
1088 *self.scheduled_update_the_rendering.borrow_mut() = Some(timer_id);
1089 }
1090
1091 pub(crate) fn update_the_rendering(&self, cx: &mut JSContext) -> bool {
1098 self.last_render_opportunity_time.set(Some(Instant::now()));
1099 self.cancel_scheduled_update_the_rendering();
1100 self.needs_rendering_update.store(false, Ordering::Relaxed);
1101
1102 if !self.can_continue_running_inner() {
1103 return false;
1104 }
1105
1106 let documents_in_order = self.documents.borrow().documents_in_order();
1125
1126 let mut painters_generating_frames = FxHashSet::default();
1131 for pipeline_id in documents_in_order.iter() {
1132 let Some(document) = self.documents.borrow().find_document(*pipeline_id) else {
1133 continue;
1134 };
1135
1136 if !document.is_fully_active() {
1137 continue;
1138 }
1139
1140 if document.waiting_on_canvas_image_updates() {
1141 continue;
1142 }
1143
1144 if
1147 document.is_render_blocked()
1149 {
1160 continue;
1161 }
1162
1163 document.clear_rendering_update_reasons();
1166
1167 self.process_pending_input_events(cx, *pipeline_id);
1174
1175 let resized = document.window().run_the_resize_steps(cx);
1177
1178 document.run_the_scroll_steps(cx);
1180
1181 let media_features_changed = document.window().take_pending_media_query_evaluation();
1188 if resized || media_features_changed {
1189 document
1190 .window()
1191 .evaluate_media_queries_and_report_changes(cx);
1192 }
1193 if resized {
1194 document.react_to_environment_changes(cx);
1197 }
1198
1199 let mut realm = enter_auto_realm(cx, &*document);
1200 let cx = &mut realm.current_realm();
1201
1202 if !document.window().throttled() {
1204 document.update_animations_and_send_events(cx);
1208
1209 document.run_the_animation_frame_callbacks(cx);
1219 }
1220
1221 let mut depth = Default::default();
1223 while document.gather_active_resize_observations_at_depth(cx, &depth) {
1224 depth = document.broadcast_active_resize_observations(cx);
1226 }
1227
1228 if document.has_skipped_resize_observations() {
1229 document.deliver_resize_loop_error_notification(cx);
1230 document.add_rendering_update_reason(
1233 RenderingUpdateReason::ResizeObserverStartedObservingTarget,
1234 );
1235 }
1236
1237 document.focus_handler().perform_focus_fixup_rule(cx);
1242
1243 document.update_intersection_observer_steps(cx, CrossProcessInstant::now());
1251
1252 if document.is_render_blocked() {
1257 continue;
1258 }
1259
1260 if document.update_the_rendering(cx).0.needs_frame() {
1266 painters_generating_frames.insert(document.webview_id().into());
1267 }
1268
1269 }
1272
1273 let should_generate_frame = !painters_generating_frames.is_empty();
1274 if should_generate_frame {
1275 self.paint_api
1276 .generate_frame(painters_generating_frames.into_iter().collect());
1277 }
1278
1279 self.perform_a_microtask_checkpoint(cx);
1282 should_generate_frame
1283 }
1284
1285 fn maybe_schedule_rendering_opportunity_after_ipc_message(
1292 &self,
1293 no_gc: &NoGC,
1294 built_any_display_lists: bool,
1295 ) {
1296 let needs_rendering_update = self
1297 .documents
1298 .borrow()
1299 .iter()
1300 .any(|(_, document)| document.needs_rendering_update(no_gc));
1301 let running_animations = self.documents.borrow().iter().any(|(_, document)| {
1302 document.is_fully_active() &&
1303 !document.window().throttled() &&
1304 (document.animation_manager().running_animation_count() != 0 ||
1305 document.has_active_request_animation_frame_callbacks())
1306 });
1307
1308 if !needs_rendering_update && !running_animations {
1312 return;
1313 }
1314
1315 if running_animations && built_any_display_lists {
1319 return;
1320 }
1321
1322 let animation_delay = if running_animations && !needs_rendering_update {
1330 Duration::from_millis(30)
1334 } else {
1335 Duration::from_millis(20)
1338 };
1339
1340 let time_since_last_rendering_opportunity = self
1341 .last_render_opportunity_time
1342 .get()
1343 .map(|last_render_opportunity_time| Instant::now() - last_render_opportunity_time)
1344 .unwrap_or(Duration::MAX)
1345 .min(animation_delay);
1346 self.schedule_update_the_rendering_timer_if_necessary(
1347 animation_delay - time_since_last_rendering_opportunity,
1348 );
1349 }
1350
1351 fn maybe_fulfill_font_ready_promises(&self, cx: &mut JSContext) {
1354 let mut sent_message = false;
1355 for (_, document) in self.documents.borrow().iter() {
1356 sent_message = document.maybe_fulfill_font_ready_promise(cx) || sent_message;
1357 }
1358
1359 if sent_message {
1360 self.perform_a_microtask_checkpoint(cx);
1361 }
1362 }
1363
1364 fn maybe_resolve_pending_screenshot_readiness_requests(&self, cx: &mut JSContext) {
1368 for (_, document) in self.documents.borrow().iter() {
1369 document
1370 .window()
1371 .maybe_resolve_pending_screenshot_readiness_requests(cx);
1372 }
1373 }
1374
1375 fn handle_msgs(&self, cx: &mut JSContext) -> bool {
1377 let mut sequential: SmallVec<[MixedMessage; 10]> = SmallVec::new();
1379
1380 self.background_hang_monitor.notify_wait();
1382
1383 debug!("Waiting for event.");
1385 let fully_active = self.get_fully_active_document_ids();
1386 let mut event = self.receivers.recv(
1387 &self.task_queue,
1388 &self.timer_scheduler.borrow(),
1389 &fully_active,
1390 );
1391
1392 loop {
1393 debug!("Handling event: {event:?}");
1394
1395 self.timer_scheduler
1397 .borrow_mut()
1398 .dispatch_completed_timers();
1399
1400 match event {
1402 MixedMessage::FromConstellation(ScriptThreadMessage::SpawnPipeline(
1406 new_pipeline_info,
1407 )) => {
1408 self.spawn_pipeline(cx, new_pipeline_info);
1409 },
1410 MixedMessage::FromScript(MainThreadScriptMsg::Inactive) => {
1411 },
1414 MixedMessage::FromConstellation(ScriptThreadMessage::ExitFullScreen(id)) => self
1415 .profile_event(ScriptThreadEventCategory::ExitFullscreen, Some(id), || {
1416 self.handle_exit_fullscreen(cx, id);
1417 }),
1418 _ => {
1419 sequential.push(event);
1420 },
1421 }
1422
1423 match self.receivers.try_recv(&self.task_queue, &fully_active) {
1427 Some(new_event) => event = new_event,
1428 None => break,
1429 }
1430 }
1431
1432 debug!("Processing events.");
1434 for msg in sequential.drain(..) {
1435 debug!("Processing event {:?}.", msg);
1436 let category = self.categorize_msg(&msg);
1437 let pipeline_id = msg.pipeline_id();
1438 macro_rules! handle_message(
1443 ( $cx:ident ) => (
1444 if self.closing.load(Ordering::SeqCst) {
1445 match msg {
1447 MixedMessage::FromConstellation(ScriptThreadMessage::ExitScriptThread) => {
1448 self.handle_exit_script_thread_msg($cx);
1449 return false;
1450 },
1451 MixedMessage::FromConstellation(ScriptThreadMessage::ExitPipeline(
1452 webview_id,
1453 pipeline_id,
1454 discard_browsing_context,
1455 )) => {
1456 self.handle_exit_pipeline_msg(
1457 $cx,
1458 webview_id,
1459 pipeline_id,
1460 discard_browsing_context,
1461 );
1462 },
1463 _ => {},
1464 }
1465 continue;
1466 }
1467
1468 let exiting = self.profile_event(category, pipeline_id, || {
1469 match msg {
1470 MixedMessage::FromConstellation(ScriptThreadMessage::ExitScriptThread) => {
1471 self.handle_exit_script_thread_msg($cx);
1472 return true;
1473 },
1474 MixedMessage::FromConstellation(inner_msg) => {
1475 self.handle_msg_from_constellation($cx, inner_msg)
1476 },
1477 MixedMessage::FromScript(inner_msg) => {
1478 self.handle_msg_from_script($cx, inner_msg)
1479 },
1480 #[cfg(feature = "devtools")]
1481 MixedMessage::FromDevtools(inner_msg) => {
1482 self.handle_msg_from_devtools($cx, *inner_msg)
1483 },
1484 MixedMessage::FromImageCache(inner_msg) => {
1485 self.handle_msg_from_image_cache($cx, inner_msg)
1486 },
1487 #[cfg(feature = "webgpu")]
1488 MixedMessage::FromWebGPUServer(inner_msg) => {
1489 self.handle_msg_from_webgpu_server($cx, inner_msg)
1490 },
1491 MixedMessage::TimerFired => {},
1492 }
1493
1494 false
1495 });
1496
1497 if exiting {
1499 return false;
1500 }
1501
1502 self.perform_a_microtask_checkpoint($cx);
1505 )
1506 );
1507
1508 let global = pipeline_id.and_then(|id| self.documents.borrow().find_global(id));
1509 match global {
1510 None => {
1511 handle_message!(cx);
1512 },
1513 Some(global) => {
1514 let mut realm = enter_auto_realm(cx, &*global);
1515 let cx = &mut realm.current_realm();
1516 handle_message!(cx);
1517 },
1518 };
1519 }
1520
1521 for (_, doc) in self.documents.borrow().iter() {
1522 let window = doc.window();
1523 window
1524 .upcast::<GlobalScope>()
1525 .perform_a_dom_garbage_collection_checkpoint();
1526 }
1527
1528 let built_any_display_lists =
1529 self.needs_rendering_update.load(Ordering::Relaxed) && self.update_the_rendering(cx);
1530
1531 self.maybe_fulfill_font_ready_promises(cx);
1532 self.maybe_resolve_pending_screenshot_readiness_requests(cx);
1533
1534 self.maybe_schedule_rendering_opportunity_after_ipc_message(
1536 cx.no_gc(),
1537 built_any_display_lists,
1538 );
1539
1540 true
1541 }
1542
1543 fn categorize_msg(&self, msg: &MixedMessage) -> ScriptThreadEventCategory {
1544 match *msg {
1545 MixedMessage::FromConstellation(ref inner_msg) => match *inner_msg {
1546 ScriptThreadMessage::SendInputEvent(..) => ScriptThreadEventCategory::InputEvent,
1547 _ => ScriptThreadEventCategory::ConstellationMsg,
1548 },
1549 #[cfg(feature = "devtools")]
1550 MixedMessage::FromDevtools(_) => ScriptThreadEventCategory::DevtoolsMsg,
1551 MixedMessage::FromImageCache(_) => ScriptThreadEventCategory::ImageCacheMsg,
1552 MixedMessage::FromScript(ref inner_msg) => match *inner_msg {
1553 MainThreadScriptMsg::Common(CommonScriptMsg::Task(category, ..)) => category,
1554 MainThreadScriptMsg::RegisterPaintWorklet { .. } => {
1555 ScriptThreadEventCategory::WorkletEvent
1556 },
1557 _ => ScriptThreadEventCategory::ScriptEvent,
1558 },
1559 #[cfg(feature = "webgpu")]
1560 MixedMessage::FromWebGPUServer(_) => ScriptThreadEventCategory::WebGPUMsg,
1561 MixedMessage::TimerFired => ScriptThreadEventCategory::TimerEvent,
1562 }
1563 }
1564
1565 fn profile_event<F, R>(
1566 &self,
1567 category: ScriptThreadEventCategory,
1568 pipeline_id: Option<PipelineId>,
1569 f: F,
1570 ) -> R
1571 where
1572 F: FnOnce() -> R,
1573 {
1574 self.background_hang_monitor
1575 .notify_activity(HangAnnotation::Script(category.into()));
1576 let start = Instant::now();
1577 let value = if self.profile_script_events {
1578 let profiler_chan = self.senders.time_profiler_sender.clone();
1579 match category {
1580 ScriptThreadEventCategory::SpawnPipeline => {
1581 time_profile!(
1582 ProfilerCategory::ScriptSpawnPipeline,
1583 None,
1584 profiler_chan,
1585 f
1586 )
1587 },
1588 ScriptThreadEventCategory::ConstellationMsg => time_profile!(
1589 ProfilerCategory::ScriptConstellationMsg,
1590 None,
1591 profiler_chan,
1592 f
1593 ),
1594 ScriptThreadEventCategory::DatabaseAccessEvent => time_profile!(
1595 ProfilerCategory::ScriptDatabaseAccessEvent,
1596 None,
1597 profiler_chan,
1598 f
1599 ),
1600 ScriptThreadEventCategory::DevtoolsMsg => {
1601 time_profile!(ProfilerCategory::ScriptDevtoolsMsg, None, profiler_chan, f)
1602 },
1603 ScriptThreadEventCategory::DocumentEvent => time_profile!(
1604 ProfilerCategory::ScriptDocumentEvent,
1605 None,
1606 profiler_chan,
1607 f
1608 ),
1609 ScriptThreadEventCategory::InputEvent => {
1610 time_profile!(ProfilerCategory::ScriptInputEvent, None, profiler_chan, f)
1611 },
1612 ScriptThreadEventCategory::FileRead => {
1613 time_profile!(ProfilerCategory::ScriptFileRead, None, profiler_chan, f)
1614 },
1615 ScriptThreadEventCategory::FontLoading => {
1616 time_profile!(ProfilerCategory::ScriptFontLoading, None, profiler_chan, f)
1617 },
1618 ScriptThreadEventCategory::FormPlannedNavigation => time_profile!(
1619 ProfilerCategory::ScriptPlannedNavigation,
1620 None,
1621 profiler_chan,
1622 f
1623 ),
1624 ScriptThreadEventCategory::GeolocationEvent => {
1625 time_profile!(
1626 ProfilerCategory::ScriptGeolocationEvent,
1627 None,
1628 profiler_chan,
1629 f
1630 )
1631 },
1632 ScriptThreadEventCategory::NavigationAndTraversalEvent => {
1633 time_profile!(
1634 ProfilerCategory::ScriptNavigationAndTraversalEvent,
1635 None,
1636 profiler_chan,
1637 f
1638 )
1639 },
1640 ScriptThreadEventCategory::ImageCacheMsg => time_profile!(
1641 ProfilerCategory::ScriptImageCacheMsg,
1642 None,
1643 profiler_chan,
1644 f
1645 ),
1646 ScriptThreadEventCategory::NetworkEvent => {
1647 time_profile!(ProfilerCategory::ScriptNetworkEvent, None, profiler_chan, f)
1648 },
1649 ScriptThreadEventCategory::PortMessage => {
1650 time_profile!(ProfilerCategory::ScriptPortMessage, None, profiler_chan, f)
1651 },
1652 ScriptThreadEventCategory::Resize => {
1653 time_profile!(ProfilerCategory::ScriptResize, None, profiler_chan, f)
1654 },
1655 ScriptThreadEventCategory::ScriptEvent => {
1656 time_profile!(ProfilerCategory::ScriptEvent, None, profiler_chan, f)
1657 },
1658 ScriptThreadEventCategory::SetScrollState => time_profile!(
1659 ProfilerCategory::ScriptSetScrollState,
1660 None,
1661 profiler_chan,
1662 f
1663 ),
1664 ScriptThreadEventCategory::UpdateReplacedElement => time_profile!(
1665 ProfilerCategory::ScriptUpdateReplacedElement,
1666 None,
1667 profiler_chan,
1668 f
1669 ),
1670 ScriptThreadEventCategory::StylesheetLoad => time_profile!(
1671 ProfilerCategory::ScriptStylesheetLoad,
1672 None,
1673 profiler_chan,
1674 f
1675 ),
1676 ScriptThreadEventCategory::SetViewport => {
1677 time_profile!(ProfilerCategory::ScriptSetViewport, None, profiler_chan, f)
1678 },
1679 ScriptThreadEventCategory::TimerEvent => {
1680 time_profile!(ProfilerCategory::ScriptTimerEvent, None, profiler_chan, f)
1681 },
1682 ScriptThreadEventCategory::WebSocketEvent => time_profile!(
1683 ProfilerCategory::ScriptWebSocketEvent,
1684 None,
1685 profiler_chan,
1686 f
1687 ),
1688 ScriptThreadEventCategory::WorkerEvent => {
1689 time_profile!(ProfilerCategory::ScriptWorkerEvent, None, profiler_chan, f)
1690 },
1691 ScriptThreadEventCategory::WorkletEvent => {
1692 time_profile!(ProfilerCategory::ScriptWorkletEvent, None, profiler_chan, f)
1693 },
1694 ScriptThreadEventCategory::ServiceWorkerEvent => time_profile!(
1695 ProfilerCategory::ScriptServiceWorkerEvent,
1696 None,
1697 profiler_chan,
1698 f
1699 ),
1700 ScriptThreadEventCategory::EnterFullscreen => time_profile!(
1701 ProfilerCategory::ScriptEnterFullscreen,
1702 None,
1703 profiler_chan,
1704 f
1705 ),
1706 ScriptThreadEventCategory::ExitFullscreen => time_profile!(
1707 ProfilerCategory::ScriptExitFullscreen,
1708 None,
1709 profiler_chan,
1710 f
1711 ),
1712 ScriptThreadEventCategory::PerformanceTimelineTask => time_profile!(
1713 ProfilerCategory::ScriptPerformanceEvent,
1714 None,
1715 profiler_chan,
1716 f
1717 ),
1718 ScriptThreadEventCategory::Rendering => {
1719 time_profile!(ProfilerCategory::ScriptRendering, None, profiler_chan, f)
1720 },
1721 #[cfg(feature = "webgpu")]
1722 ScriptThreadEventCategory::WebGPUMsg => {
1723 time_profile!(ProfilerCategory::ScriptWebGPUMsg, None, profiler_chan, f)
1724 },
1725 }
1726 } else {
1727 f()
1728 };
1729 let task_duration = start.elapsed();
1730 for (doc_id, doc) in self.documents.borrow().iter() {
1731 if let Some(pipeline_id) = pipeline_id &&
1732 pipeline_id == doc_id &&
1733 task_duration.as_nanos() > MAX_TASK_NS
1734 {
1735 if opts::get()
1736 .debug
1737 .is_enabled(DiagnosticsLoggingOption::ProgressiveWebMetrics)
1738 {
1739 println!(
1740 "Task took longer than max allowed ({category:?}) {:?}",
1741 task_duration.as_nanos()
1742 );
1743 }
1744 doc.start_tti();
1745 }
1746 doc.record_tti_if_necessary();
1747 }
1748 value
1749 }
1750
1751 fn handle_update_service_worker_state(
1753 &self,
1754 pipeline_id: PipelineId,
1755 state: ServiceWorkerState,
1756 worker_id: ServiceWorkerId,
1757 ) {
1758 let Some(global) = self.documents.borrow().find_global(pipeline_id) else {
1759 log::warn!(
1760 "Received update sw worker state for unknown pipeline: {:?}",
1761 pipeline_id
1762 );
1763 return;
1764 };
1765
1766 let trusted_global = Trusted::new(&*global);
1767
1768 let task = task!(update_sw_state: move |cx| {
1771 trusted_global.root().update_service_worker_state(cx, state, worker_id);
1772 });
1773 global
1774 .task_manager()
1775 .dom_manipulation_task_source()
1776 .queue(task);
1777 }
1778
1779 fn handle_msg_from_constellation(&self, cx: &mut JSContext, msg: ScriptThreadMessage) {
1780 match msg {
1781 ScriptThreadMessage::UpdateServiceWorkerState {
1782 pipeline_id,
1783 state,
1784 worker_id,
1785 } => {
1786 self.handle_update_service_worker_state(pipeline_id, state, worker_id);
1787 },
1788 ScriptThreadMessage::StopDelayingLoadEventsMode(pipeline_id) => {
1789 self.handle_stop_delaying_load_events_mode(pipeline_id)
1790 },
1791 ScriptThreadMessage::NavigateIframe(
1792 parent_pipeline_id,
1793 browsing_context_id,
1794 load_data,
1795 history_handling,
1796 target_snapshot_params,
1797 ) => self.handle_navigate_iframe(
1798 cx,
1799 parent_pipeline_id,
1800 browsing_context_id,
1801 load_data,
1802 history_handling,
1803 target_snapshot_params,
1804 ),
1805 ScriptThreadMessage::UnloadDocument(pipeline_id) => {
1806 self.handle_unload_document(cx, pipeline_id)
1807 },
1808 ScriptThreadMessage::ResizeInactive(id, new_size) => {
1809 self.handle_resize_inactive_msg(id, new_size)
1810 },
1811 ScriptThreadMessage::UpdateWebViewState(state) => {
1812 self.handle_update_webview_state(cx, state);
1813 },
1814 ScriptThreadMessage::GetDocumentOrigin(pipeline_id, result_sender) => {
1815 self.handle_get_document_origin(pipeline_id, result_sender);
1816 },
1817 ScriptThreadMessage::GetDocumentOriginDetails(pipeline_id, result_sender) => {
1818 self.handle_get_origin_details(pipeline_id, result_sender);
1819 },
1820 ScriptThreadMessage::GetTitle(pipeline_id) => self.handle_get_title_msg(pipeline_id),
1821 ScriptThreadMessage::SetDocumentActivity(pipeline_id, activity) => {
1822 self.handle_set_document_activity_msg(cx, pipeline_id, activity)
1823 },
1824 ScriptThreadMessage::SetThrottled(pipeline_id, throttled) => {
1825 self.handle_set_throttled_msg(pipeline_id, throttled)
1826 },
1827 ScriptThreadMessage::PostMessage {
1828 target: target_pipeline_id,
1829 source_webview,
1830 source_with_ancestry,
1831 target_origin: origin,
1832 source_origin,
1833 data,
1834 } => self.handle_post_message_msg(
1835 cx,
1836 target_pipeline_id,
1837 source_webview,
1838 source_with_ancestry,
1839 origin,
1840 source_origin,
1841 *data,
1842 ),
1843 ScriptThreadMessage::UpdatePipelineId(
1844 parent_pipeline_id,
1845 browsing_context_id,
1846 webview_id,
1847 new_pipeline_id,
1848 reason,
1849 ) => self.handle_update_pipeline_id(
1850 cx,
1851 parent_pipeline_id,
1852 browsing_context_id,
1853 webview_id,
1854 new_pipeline_id,
1855 reason,
1856 ),
1857 ScriptThreadMessage::UpdateHistoryState(pipeline_id, history_state_id, url) => {
1858 self.handle_update_history_state_msg(cx, pipeline_id, history_state_id, url)
1859 },
1860 ScriptThreadMessage::RemoveHistoryStates(pipeline_id, history_states) => {
1861 self.handle_remove_history_states(cx, pipeline_id, history_states)
1862 },
1863 ScriptThreadMessage::FocusDocumentAsPartOfFocusingSteps(
1864 pipeline_id,
1865 sequence,
1866 iframe_browsing_context_id,
1867 ) => self.handle_focus_document_as_part_of_focusing_steps(
1868 cx,
1869 pipeline_id,
1870 sequence,
1871 iframe_browsing_context_id,
1872 ),
1873 ScriptThreadMessage::UnfocusDocumentAsPartOfFocusingSteps(pipeline_id, sequence) => {
1874 self.handle_unfocus_document_as_part_of_focusing_steps(cx, pipeline_id, sequence);
1875 },
1876 ScriptThreadMessage::FocusDocument(pipeline_id, remote_focus_operation) => {
1877 self.handle_focus_document(cx, pipeline_id, remote_focus_operation);
1878 },
1879 ScriptThreadMessage::WebDriverScriptCommand(pipeline_id, msg) => {
1880 self.handle_webdriver_msg(cx, pipeline_id, msg)
1881 },
1882 ScriptThreadMessage::WebFontLoadFinished(pipeline_id, event) => {
1883 if event == WebFontLoadEvent::LoadedSuccessfully {
1887 self.handle_web_font_loaded(cx.no_gc(), pipeline_id)
1888 }
1889 },
1890 ScriptThreadMessage::DispatchIFrameLoadEvent {
1891 target: browsing_context_id,
1892 parent: parent_id,
1893 child: child_id,
1894 } => self.handle_iframe_load_event(cx, parent_id, browsing_context_id, child_id),
1895 ScriptThreadMessage::DispatchStorageEvent(
1896 pipeline_id,
1897 storage,
1898 url,
1899 key,
1900 old_value,
1901 new_value,
1902 ) => {
1903 self.handle_storage_event(cx, pipeline_id, storage, url, key, old_value, new_value)
1904 },
1905 #[cfg(feature = "devtools")]
1906 ScriptThreadMessage::ReportCSSError(pipeline_id, filename, line, column, msg) => {
1907 self.handle_css_error_reporting(pipeline_id, filename, line, column, msg)
1908 },
1909 ScriptThreadMessage::Reload(pipeline_id) => self.handle_reload(cx, pipeline_id),
1910 ScriptThreadMessage::Resize(id, size, size_type) => {
1911 self.handle_resize_message(id, size, size_type);
1912 },
1913 ScriptThreadMessage::ExitPipeline(
1914 webview_id,
1915 pipeline_id,
1916 discard_browsing_context,
1917 ) => {
1918 self.handle_exit_pipeline_msg(cx, webview_id, pipeline_id, discard_browsing_context)
1919 },
1920 ScriptThreadMessage::PaintMetric(pipeline_id, event) => {
1921 self.handle_paint_metric(cx, pipeline_id, event)
1922 },
1923 ScriptThreadMessage::MediaSessionAction(pipeline_id, action) => {
1924 self.handle_media_session_action(cx, pipeline_id, action)
1925 },
1926 ScriptThreadMessage::SendInputEvent(webview_id, id, event) => {
1927 self.handle_input_event(webview_id, id, event)
1928 },
1929 #[cfg(feature = "webgpu")]
1930 ScriptThreadMessage::SetWebGPUPort(port) => {
1931 *self.receivers.webgpu_receiver.borrow_mut() = port.route_preserving_errors();
1932 },
1933 ScriptThreadMessage::TickAllAnimations(_webviews) => {
1934 self.set_needs_rendering_update();
1935 },
1936 ScriptThreadMessage::NoLongerWaitingOnAsychronousImageUpdates(pipeline_id) => {
1937 if let Some(document) = self.documents.borrow().find_document(pipeline_id) {
1938 document.handle_no_longer_waiting_on_asynchronous_image_updates();
1939 }
1940 },
1941 msg @ ScriptThreadMessage::SpawnPipeline(..) |
1942 msg @ ScriptThreadMessage::ExitFullScreen(..) |
1943 msg @ ScriptThreadMessage::ExitScriptThread => {
1944 panic!("should have handled {:?} already", msg)
1945 },
1946 ScriptThreadMessage::SetScrollStates(pipeline_id, scroll_states) => {
1947 self.handle_set_scroll_states(pipeline_id, scroll_states)
1948 },
1949 ScriptThreadMessage::EvaluateJavaScript(
1950 webview_id,
1951 pipeline_id,
1952 evaluation_id,
1953 script,
1954 ) => {
1955 self.handle_evaluate_javascript(cx, webview_id, pipeline_id, evaluation_id, script);
1956 },
1957 ScriptThreadMessage::SendImageKeysBatch(pipeline_id, image_keys) => {
1958 if let Some(window) = self.documents.borrow().find_window(pipeline_id) {
1959 window
1960 .image_cache()
1961 .dispatch_fill_key_cache_with_batch_of_keys(image_keys);
1962 } else {
1963 warn!(
1964 "Could not find window corresponding to an image cache to send image keys to pipeline {:?}",
1965 pipeline_id
1966 );
1967 }
1968 },
1969 ScriptThreadMessage::RefreshCursor(pipeline_id) => {
1970 self.handle_refresh_cursor(pipeline_id);
1971 },
1972 ScriptThreadMessage::PreferencesUpdated(updates) => {
1973 let mut current_preferences = prefs::get().clone();
1974 for (name, value) in updates {
1975 current_preferences.set_value(&name, value);
1976 }
1977 prefs::set(current_preferences);
1978 },
1979 ScriptThreadMessage::ForwardKeyboardScroll(pipeline_id, scroll) => {
1980 if let Some(document) = self.documents.borrow().find_document(pipeline_id) {
1981 document.event_handler().do_keyboard_scroll(cx, scroll);
1982 }
1983 },
1984 ScriptThreadMessage::RequestScreenshotReadiness(webview_id, pipeline_id) => {
1985 self.handle_request_screenshot_readiness(cx, webview_id, pipeline_id);
1986 },
1987 ScriptThreadMessage::EmbedderControlResponse(id, response) => {
1988 self.handle_embedder_control_response(cx, id, response);
1989 },
1990 ScriptThreadMessage::SetUserContents(user_content_manager_id, user_contents) => {
1991 self.user_contents_for_manager_id.borrow_mut().insert(
1992 user_content_manager_id,
1993 ScriptThreadUserContents::new(user_contents, &self.shared_style_locks),
1994 );
1995 },
1996 ScriptThreadMessage::DestroyUserContentManager(user_content_manager_id) => {
1997 self.user_contents_for_manager_id
1998 .borrow_mut()
1999 .remove(&user_content_manager_id);
2000 },
2001 ScriptThreadMessage::UpdatePinchZoomInfos(id, pinch_zoom_infos) => {
2002 self.handle_update_pinch_zoom_infos(cx, id, pinch_zoom_infos);
2003 },
2004 ScriptThreadMessage::SetAccessibilityActive(pipeline_id, active, epoch) => {
2005 self.set_accessibility_active(pipeline_id, active, epoch);
2006 },
2007 ScriptThreadMessage::ForwardAccessibilityAction(pipeline_id, action_request) => {
2008 self.forward_accessibility_action(pipeline_id, action_request);
2009 },
2010 ScriptThreadMessage::TriggerGarbageCollection => unsafe {
2011 JS_GC(cx, GCReason::API);
2012 },
2013 ScriptThreadMessage::SetNetworkOnlineState(online) => {
2014 self.handle_set_network_online_state(online);
2015 },
2016 }
2017 }
2018
2019 fn handle_set_scroll_states(&self, pipeline_id: PipelineId, scroll_states: ScrollStateUpdate) {
2020 let Some(window) = self.documents.borrow().find_window(pipeline_id) else {
2021 warn!("Received scroll states for closed pipeline {pipeline_id}");
2022 return;
2023 };
2024
2025 self.profile_event(
2026 ScriptThreadEventCategory::SetScrollState,
2027 Some(pipeline_id),
2028 || {
2029 window
2030 .layout_mut()
2031 .set_scroll_offsets_from_renderer(&scroll_states.offsets);
2032 },
2033 );
2034
2035 window
2036 .Document()
2037 .event_handler()
2038 .handle_embedder_scroll_event(scroll_states.scrolled_node);
2039 }
2040
2041 #[cfg(feature = "webgpu")]
2042 fn handle_msg_from_webgpu_server(&self, cx: &mut JSContext, msg: WebGPUMsg) {
2043 match msg {
2044 WebGPUMsg::FreeAdapter(id) => self.gpu_id_hub.free_adapter_id(id),
2045 WebGPUMsg::FreeDevice {
2046 device_id,
2047 pipeline_id,
2048 } => {
2049 self.gpu_id_hub.free_device_id(device_id);
2050 if let Some(global) = self.documents.borrow().find_global(pipeline_id) {
2051 global.remove_gpu_device(WebGPUDevice(device_id));
2052 } },
2054 WebGPUMsg::FreeBuffer(id) => self.gpu_id_hub.free_buffer_id(id),
2055 WebGPUMsg::FreePipelineLayout(id) => self.gpu_id_hub.free_pipeline_layout_id(id),
2056 WebGPUMsg::FreeComputePipeline(id) => self.gpu_id_hub.free_compute_pipeline_id(id),
2057 WebGPUMsg::FreeBindGroup(id) => self.gpu_id_hub.free_bind_group_id(id),
2058 WebGPUMsg::FreeBindGroupLayout(id) => self.gpu_id_hub.free_bind_group_layout_id(id),
2059 WebGPUMsg::FreeCommandBuffer(id) => self.gpu_id_hub.free_command_buffer_id(id),
2060 WebGPUMsg::FreeSampler(id) => self.gpu_id_hub.free_sampler_id(id),
2061 WebGPUMsg::FreeShaderModule(id) => self.gpu_id_hub.free_shader_module_id(id),
2062 WebGPUMsg::FreeRenderBundle(id) => self.gpu_id_hub.free_render_bundle_id(id),
2063 WebGPUMsg::FreeRenderPipeline(id) => self.gpu_id_hub.free_render_pipeline_id(id),
2064 WebGPUMsg::FreeTexture(id) => self.gpu_id_hub.free_texture_id(id),
2065 WebGPUMsg::FreeTextureView(id) => self.gpu_id_hub.free_texture_view_id(id),
2066 WebGPUMsg::FreeComputePass(id) => self.gpu_id_hub.free_compute_pass_id(id),
2067 WebGPUMsg::FreeRenderPass(id) => self.gpu_id_hub.free_render_pass_id(id),
2068 WebGPUMsg::Exit => {
2069 *self.receivers.webgpu_receiver.borrow_mut() = crossbeam_channel::never()
2070 },
2071 WebGPUMsg::DeviceLost {
2072 pipeline_id,
2073 device,
2074 reason,
2075 msg,
2076 } => {
2077 let global = self.documents.borrow().find_global(pipeline_id).unwrap();
2078 let _ac = enter_auto_realm(cx, &*global);
2079 global.gpu_device_lost(device, reason, msg);
2080 },
2081 WebGPUMsg::UncapturedError {
2082 device,
2083 pipeline_id,
2084 error,
2085 } => {
2086 let global = self.documents.borrow().find_global(pipeline_id).unwrap();
2087 let _ac = enter_auto_realm(cx, &*global);
2088 global.handle_uncaptured_gpu_error(device, error);
2089 },
2090 _ => {},
2091 }
2092 }
2093
2094 fn handle_msg_from_script(&self, cx: &mut JSContext, msg: MainThreadScriptMsg) {
2095 match msg {
2096 MainThreadScriptMsg::Common(CommonScriptMsg::Task(_, task, pipeline_id, _)) => {
2097 let global = pipeline_id.and_then(|id| self.documents.borrow().find_global(id));
2098 match global {
2099 None => task.run_box(cx),
2100 Some(global) => {
2101 let mut realm = enter_auto_realm(cx, &*global);
2102 let cx = &mut realm.current_realm();
2103 task.run_box(cx)
2104 },
2105 }
2106 },
2107 MainThreadScriptMsg::Common(CommonScriptMsg::CollectReports(chan)) => {
2108 self.collect_reports(cx, chan)
2109 },
2110 MainThreadScriptMsg::Common(CommonScriptMsg::ReportCspViolations(
2111 pipeline_id,
2112 violations,
2113 )) => {
2114 if let Some(global) = self.documents.borrow().find_global(pipeline_id) {
2115 let mut realm = enter_auto_realm(cx, &*global);
2116 let cx = &mut realm.current_realm();
2117 global.run_worker_csp_violation_report_tasks(violations, cx);
2118 }
2119 },
2120 MainThreadScriptMsg::NavigationResponse {
2121 pipeline_id,
2122 message,
2123 } => {
2124 self.handle_navigation_response(cx, pipeline_id, *message);
2125 },
2126 MainThreadScriptMsg::WorkletLoaded(pipeline_id) => {
2127 self.handle_worklet_loaded(pipeline_id)
2128 },
2129 MainThreadScriptMsg::RegisterPaintWorklet {
2130 pipeline_id,
2131 name,
2132 properties,
2133 painter,
2134 } => self.handle_register_paint_worklet(pipeline_id, name, properties, painter),
2135 MainThreadScriptMsg::Inactive => {},
2136 MainThreadScriptMsg::WakeUp => {},
2137 MainThreadScriptMsg::ForwardEmbedderControlResponseFromFileManager(
2138 control_id,
2139 response,
2140 ) => {
2141 self.handle_embedder_control_response(cx, control_id, response);
2142 },
2143 }
2144 }
2145
2146 #[cfg(feature = "devtools")]
2147 fn handle_msg_from_devtools(&self, cx: &mut JSContext, msg: DevtoolScriptControlMsg) {
2148 let documents = self.documents.borrow();
2149 match msg {
2150 DevtoolScriptControlMsg::GetEventListenerInfo(id, node, reply) => {
2151 devtools::handle_get_event_listener_info(&self.devtools_state, id, &node, reply)
2152 },
2153 DevtoolScriptControlMsg::GetRootNode(id, reply) => {
2154 devtools::handle_get_root_node(cx, &self.devtools_state, &documents, id, reply)
2155 },
2156 DevtoolScriptControlMsg::GetDocumentElement(id, reply) => {
2157 devtools::handle_get_document_element(
2158 cx,
2159 &self.devtools_state,
2160 &documents,
2161 id,
2162 reply,
2163 )
2164 },
2165 DevtoolScriptControlMsg::GetStyleSheets(id, reply) => {
2166 devtools::handle_get_stylesheets(cx, &documents, id, reply);
2167 },
2168 DevtoolScriptControlMsg::GetStyleSheetText(id, index, reply) => {
2169 devtools::handle_get_stylesheet_text(cx, &documents, id, index, reply);
2170 },
2171 DevtoolScriptControlMsg::GetChildren(id, node_id, reply) => {
2172 devtools::handle_get_children(cx, &self.devtools_state, id, &node_id, reply)
2173 },
2174 DevtoolScriptControlMsg::GetAttributeStyle(id, node_id, reply) => {
2175 devtools::handle_get_attribute_style(cx, &self.devtools_state, id, &node_id, reply)
2176 },
2177 DevtoolScriptControlMsg::GetStylesheetStyle(id, node_id, matched_rule, reply) => {
2178 devtools::handle_get_stylesheet_style(
2179 cx,
2180 &self.devtools_state,
2181 &documents,
2182 id,
2183 &node_id,
2184 matched_rule,
2185 reply,
2186 )
2187 },
2188 DevtoolScriptControlMsg::GetSelectors(id, node_id, reply) => {
2189 devtools::handle_get_selectors(
2190 cx,
2191 &self.devtools_state,
2192 &documents,
2193 id,
2194 &node_id,
2195 reply,
2196 )
2197 },
2198 DevtoolScriptControlMsg::GetComputedStyle(id, node_id, reply) => {
2199 devtools::handle_get_computed_style(cx, &self.devtools_state, id, &node_id, reply)
2200 },
2201 DevtoolScriptControlMsg::GetLayout(id, node_id, reply) => {
2202 devtools::handle_get_layout(cx, &self.devtools_state, id, &node_id, reply)
2203 },
2204 DevtoolScriptControlMsg::GetXPath(id, node_id, reply) => {
2205 devtools::handle_get_xpath(&self.devtools_state, id, &node_id, reply)
2206 },
2207 DevtoolScriptControlMsg::GetInnerOrOuterHTML(id, node_id, reply, html_type) => {
2208 devtools::handle_get_inner_or_outer_html(
2209 cx,
2210 &self.devtools_state,
2211 id,
2212 &node_id,
2213 reply,
2214 html_type,
2215 )
2216 },
2217 DevtoolScriptControlMsg::ModifyAttribute(id, node_id, modifications) => {
2218 devtools::handle_modify_attribute(
2219 cx,
2220 &self.devtools_state,
2221 &documents,
2222 id,
2223 &node_id,
2224 modifications,
2225 )
2226 },
2227 DevtoolScriptControlMsg::ModifyRule(id, node_id, modifications) => {
2228 devtools::handle_modify_rule(
2229 cx,
2230 &self.devtools_state,
2231 &documents,
2232 id,
2233 &node_id,
2234 modifications,
2235 )
2236 },
2237 DevtoolScriptControlMsg::WantsLiveNotifications(id, to_send) => {
2238 match documents.find_window(id) {
2239 Some(window) => {
2240 window.set_devtools_wants_updates(to_send);
2241 },
2242 None => warn!("Message sent to closed pipeline {}.", id),
2243 }
2244 },
2245 DevtoolScriptControlMsg::SetTimelineMarkers(id, marker_types, reply) => {
2246 devtools::handle_set_timeline_markers(&documents, id, marker_types, reply)
2247 },
2248 DevtoolScriptControlMsg::DropTimelineMarkers(id, marker_types) => {
2249 devtools::handle_drop_timeline_markers(&documents, id, marker_types)
2250 },
2251 DevtoolScriptControlMsg::RequestAnimationFrame(id, name) => {
2252 devtools::handle_request_animation_frame(&documents, id, name)
2253 },
2254 DevtoolScriptControlMsg::NavigateTo(pipeline_id, url) => {
2255 self.handle_navigate_to(pipeline_id, url)
2256 },
2257 DevtoolScriptControlMsg::GoBack(pipeline_id) => {
2258 self.handle_traverse_history(pipeline_id, TraversalDirection::Back(1))
2259 },
2260 DevtoolScriptControlMsg::GoForward(pipeline_id) => {
2261 self.handle_traverse_history(pipeline_id, TraversalDirection::Forward(1))
2262 },
2263 DevtoolScriptControlMsg::Reload(id) => self.handle_reload(cx, id),
2264 DevtoolScriptControlMsg::GetCssDatabase(reply) => {
2265 devtools::handle_get_css_database(reply)
2266 },
2267 DevtoolScriptControlMsg::SimulateColorScheme(id, theme) => {
2268 let Some(document) = documents.find_document(id) else {
2269 return warn!("Message sent to closed pipeline {id}.");
2270 };
2271 document.set_theme_override(theme);
2275 },
2276 DevtoolScriptControlMsg::HighlightDomNode(id, node_id) => {
2277 devtools::handle_highlight_dom_node(
2278 &self.devtools_state,
2279 &documents,
2280 id,
2281 node_id.as_deref(),
2282 )
2283 },
2284 DevtoolScriptControlMsg::Eval(code, id, frame_actor_id, eager, reply) => {
2285 self.debugger_global.fire_eval(
2286 cx,
2287 code.into(),
2288 id,
2289 None,
2290 frame_actor_id,
2291 eager,
2292 reply,
2293 );
2294 },
2295 DevtoolScriptControlMsg::GetPossibleBreakpoints(spidermonkey_id, result_sender) => {
2296 self.debugger_global.fire_get_possible_breakpoints(
2297 cx,
2298 spidermonkey_id,
2299 result_sender,
2300 );
2301 },
2302 DevtoolScriptControlMsg::SetBreakpoint(spidermonkey_id, script_id, offset) => {
2303 self.debugger_global
2304 .fire_set_breakpoint(cx, spidermonkey_id, script_id, offset);
2305 },
2306 DevtoolScriptControlMsg::ClearBreakpoint(spidermonkey_id, script_id, offset) => {
2307 self.debugger_global
2308 .fire_clear_breakpoint(cx, spidermonkey_id, script_id, offset);
2309 },
2310 DevtoolScriptControlMsg::Interrupt => {
2311 self.debugger_global.fire_interrupt(cx);
2312 },
2313 DevtoolScriptControlMsg::ListFrames(pipeline_id, start, count, result_sender) => {
2314 self.debugger_global
2315 .fire_list_frames(cx, pipeline_id, start, count, result_sender);
2316 },
2317 DevtoolScriptControlMsg::GetEnvironment(request, result_sender) => {
2318 self.debugger_global
2319 .fire_get_environment(cx, request, result_sender);
2320 },
2321 DevtoolScriptControlMsg::Resume(resume_limit_type, frame_actor_id) => {
2322 self.debugger_global
2323 .fire_resume(cx, resume_limit_type, frame_actor_id);
2324 self.debugger_paused.set(false);
2325 },
2326 DevtoolScriptControlMsg::Blackbox(spidermonkey_id, coverage) => {
2327 self.debugger_global
2328 .fire_blackbox(cx, spidermonkey_id, coverage);
2329 },
2330 DevtoolScriptControlMsg::Unblackbox(spidermonkey_id, coverage) => {
2331 self.debugger_global
2332 .fire_unblackbox(cx, spidermonkey_id, coverage);
2333 },
2334 }
2335 }
2336
2337 #[cfg(feature = "devtools")]
2340 pub(crate) fn enter_debugger_pause_loop(&self) {
2341 self.debugger_paused.set(true);
2342
2343 #[allow(unsafe_code)]
2344 let mut cx = unsafe { js::context::JSContext::from_ptr(js::rust::Runtime::get().unwrap()) };
2345
2346 while self.debugger_paused.get() {
2347 match self.receivers.devtools_server_receiver.recv() {
2348 Ok(Ok(msg)) => self.handle_msg_from_devtools(&mut cx, msg),
2349 _ => {
2350 self.debugger_paused.set(false);
2351 break;
2352 },
2353 }
2354 }
2355 }
2356
2357 fn handle_msg_from_image_cache(&self, cx: &mut JSContext, response: ImageCacheResponseMessage) {
2358 match response {
2359 ImageCacheResponseMessage::NotifyPendingImageLoadStatus(pending_image_response) => {
2360 let window = self
2361 .documents
2362 .borrow()
2363 .find_window(pending_image_response.pipeline_id);
2364 if let Some(ref window) = window {
2365 window.pending_image_notification(pending_image_response, cx);
2366 }
2367 },
2368 ImageCacheResponseMessage::VectorImageRasterizationComplete(response) => {
2369 let window = self.documents.borrow().find_window(response.pipeline_id);
2370 if let Some(ref window) = window {
2371 window.handle_image_rasterization_complete_notification(cx.no_gc(), response);
2372 }
2373 },
2374 };
2375 }
2376
2377 fn handle_webdriver_msg(
2378 &self,
2379 cx: &mut JSContext,
2380 pipeline_id: PipelineId,
2381 msg: WebDriverScriptCommand,
2382 ) {
2383 let documents = self.documents.borrow();
2384 match msg {
2385 WebDriverScriptCommand::AddCookie(params, reply) => {
2386 webdriver_handlers::handle_add_cookie(&documents, pipeline_id, params, reply)
2387 },
2388 WebDriverScriptCommand::DeleteCookies(reply) => {
2389 webdriver_handlers::handle_delete_cookies(&documents, pipeline_id, reply)
2390 },
2391 WebDriverScriptCommand::DeleteCookie(name, reply) => {
2392 webdriver_handlers::handle_delete_cookie(&documents, pipeline_id, name, reply)
2393 },
2394 WebDriverScriptCommand::ElementClear(element_id, reply) => {
2395 webdriver_handlers::handle_element_clear(
2396 cx,
2397 &documents,
2398 pipeline_id,
2399 element_id,
2400 reply,
2401 )
2402 },
2403 WebDriverScriptCommand::FindElementsCSSSelector(selector, reply) => {
2404 webdriver_handlers::handle_find_elements_css_selector(
2405 cx,
2406 &documents,
2407 pipeline_id,
2408 selector,
2409 reply,
2410 )
2411 },
2412 WebDriverScriptCommand::FindElementsLinkText(selector, partial, reply) => {
2413 webdriver_handlers::handle_find_elements_link_text(
2414 cx,
2415 &documents,
2416 pipeline_id,
2417 selector,
2418 partial,
2419 reply,
2420 )
2421 },
2422 WebDriverScriptCommand::FindElementsTagName(selector, reply) => {
2423 webdriver_handlers::handle_find_elements_tag_name(
2424 cx,
2425 &documents,
2426 pipeline_id,
2427 selector,
2428 reply,
2429 )
2430 },
2431 WebDriverScriptCommand::FindElementsXpathSelector(selector, reply) => {
2432 webdriver_handlers::handle_find_elements_xpath_selector(
2433 cx,
2434 &documents,
2435 pipeline_id,
2436 selector,
2437 reply,
2438 )
2439 },
2440 WebDriverScriptCommand::FindElementElementsCSSSelector(selector, element_id, reply) => {
2441 webdriver_handlers::handle_find_element_elements_css_selector(
2442 cx,
2443 &documents,
2444 pipeline_id,
2445 element_id,
2446 selector,
2447 reply,
2448 )
2449 },
2450 WebDriverScriptCommand::FindElementElementsLinkText(
2451 selector,
2452 element_id,
2453 partial,
2454 reply,
2455 ) => webdriver_handlers::handle_find_element_elements_link_text(
2456 cx,
2457 &documents,
2458 pipeline_id,
2459 element_id,
2460 selector,
2461 partial,
2462 reply,
2463 ),
2464 WebDriverScriptCommand::FindElementElementsTagName(selector, element_id, reply) => {
2465 webdriver_handlers::handle_find_element_elements_tag_name(
2466 cx,
2467 &documents,
2468 pipeline_id,
2469 element_id,
2470 selector,
2471 reply,
2472 )
2473 },
2474 WebDriverScriptCommand::FindElementElementsXPathSelector(
2475 selector,
2476 element_id,
2477 reply,
2478 ) => webdriver_handlers::handle_find_element_elements_xpath_selector(
2479 cx,
2480 &documents,
2481 pipeline_id,
2482 element_id,
2483 selector,
2484 reply,
2485 ),
2486 WebDriverScriptCommand::FindShadowElementsCSSSelector(
2487 selector,
2488 shadow_root_id,
2489 reply,
2490 ) => webdriver_handlers::handle_find_shadow_elements_css_selector(
2491 cx,
2492 &documents,
2493 pipeline_id,
2494 shadow_root_id,
2495 selector,
2496 reply,
2497 ),
2498 WebDriverScriptCommand::FindShadowElementsLinkText(
2499 selector,
2500 shadow_root_id,
2501 partial,
2502 reply,
2503 ) => webdriver_handlers::handle_find_shadow_elements_link_text(
2504 cx,
2505 &documents,
2506 pipeline_id,
2507 shadow_root_id,
2508 selector,
2509 partial,
2510 reply,
2511 ),
2512 WebDriverScriptCommand::FindShadowElementsTagName(selector, shadow_root_id, reply) => {
2513 webdriver_handlers::handle_find_shadow_elements_tag_name(
2514 cx,
2515 &documents,
2516 pipeline_id,
2517 shadow_root_id,
2518 selector,
2519 reply,
2520 )
2521 },
2522 WebDriverScriptCommand::FindShadowElementsXPathSelector(
2523 selector,
2524 shadow_root_id,
2525 reply,
2526 ) => webdriver_handlers::handle_find_shadow_elements_xpath_selector(
2527 cx,
2528 &documents,
2529 pipeline_id,
2530 shadow_root_id,
2531 selector,
2532 reply,
2533 ),
2534 WebDriverScriptCommand::GetElementShadowRoot(element_id, reply) => {
2535 webdriver_handlers::handle_get_element_shadow_root(
2536 &documents,
2537 pipeline_id,
2538 element_id,
2539 reply,
2540 )
2541 },
2542 WebDriverScriptCommand::ElementClick(element_id, reply) => {
2543 webdriver_handlers::handle_element_click(
2544 cx,
2545 &documents,
2546 pipeline_id,
2547 element_id,
2548 reply,
2549 )
2550 },
2551 WebDriverScriptCommand::GetKnownElement(element_id, reply) => {
2552 webdriver_handlers::handle_get_known_element(
2553 &documents,
2554 pipeline_id,
2555 element_id,
2556 reply,
2557 )
2558 },
2559 WebDriverScriptCommand::GetKnownWindow(webview_id, reply) => {
2560 webdriver_handlers::handle_get_known_window(
2561 &documents,
2562 pipeline_id,
2563 webview_id,
2564 reply,
2565 )
2566 },
2567 WebDriverScriptCommand::GetKnownShadowRoot(element_id, reply) => {
2568 webdriver_handlers::handle_get_known_shadow_root(
2569 &documents,
2570 pipeline_id,
2571 element_id,
2572 reply,
2573 )
2574 },
2575 WebDriverScriptCommand::GetActiveElement(reply) => {
2576 webdriver_handlers::handle_get_active_element(&documents, pipeline_id, reply)
2577 },
2578 WebDriverScriptCommand::GetComputedRole(node_id, reply) => {
2579 webdriver_handlers::handle_get_computed_role(
2580 &documents,
2581 pipeline_id,
2582 node_id,
2583 reply,
2584 )
2585 },
2586 WebDriverScriptCommand::GetPageSource(reply) => {
2587 webdriver_handlers::handle_get_page_source(cx, &documents, pipeline_id, reply)
2588 },
2589 WebDriverScriptCommand::GetCookies(reply) => {
2590 webdriver_handlers::handle_get_cookies(&documents, pipeline_id, reply)
2591 },
2592 WebDriverScriptCommand::GetCookie(name, reply) => {
2593 webdriver_handlers::handle_get_cookie(&documents, pipeline_id, name, reply)
2594 },
2595 WebDriverScriptCommand::GetElementTagName(node_id, reply) => {
2596 webdriver_handlers::handle_get_name(&documents, pipeline_id, node_id, reply)
2597 },
2598 WebDriverScriptCommand::GetElementAttribute(node_id, name, reply) => {
2599 webdriver_handlers::handle_get_attribute(
2600 cx,
2601 &documents,
2602 pipeline_id,
2603 node_id,
2604 name,
2605 reply,
2606 )
2607 },
2608 WebDriverScriptCommand::GetElementProperty(node_id, name, reply) => {
2609 webdriver_handlers::handle_get_property(
2610 cx,
2611 &documents,
2612 pipeline_id,
2613 node_id,
2614 name,
2615 reply,
2616 )
2617 },
2618 WebDriverScriptCommand::GetElementCSS(node_id, name, reply) => {
2619 webdriver_handlers::handle_get_css(
2620 cx,
2621 &documents,
2622 pipeline_id,
2623 node_id,
2624 name,
2625 reply,
2626 )
2627 },
2628 WebDriverScriptCommand::GetElementRect(node_id, reply) => {
2629 webdriver_handlers::handle_get_rect(cx, &documents, pipeline_id, node_id, reply)
2630 },
2631 WebDriverScriptCommand::ScrollAndGetBoundingClientRect(node_id, reply) => {
2632 webdriver_handlers::handle_scroll_and_get_bounding_client_rect(
2633 cx,
2634 &documents,
2635 pipeline_id,
2636 node_id,
2637 reply,
2638 )
2639 },
2640 WebDriverScriptCommand::GetElementText(node_id, reply) => {
2641 webdriver_handlers::handle_get_text(&documents, pipeline_id, node_id, reply)
2642 },
2643 WebDriverScriptCommand::GetElementInViewCenterPoint(node_id, reply) => {
2644 webdriver_handlers::handle_get_element_in_view_center_point(
2645 cx,
2646 &documents,
2647 pipeline_id,
2648 node_id,
2649 reply,
2650 )
2651 },
2652 WebDriverScriptCommand::GetParentFrameId(reply) => {
2653 webdriver_handlers::handle_get_parent_frame_id(&documents, pipeline_id, reply)
2654 },
2655 WebDriverScriptCommand::GetBrowsingContextId(webdriver_frame_id, reply) => {
2656 webdriver_handlers::handle_get_browsing_context_id(
2657 &documents,
2658 pipeline_id,
2659 webdriver_frame_id,
2660 reply,
2661 )
2662 },
2663 WebDriverScriptCommand::GetUrl(reply) => {
2664 webdriver_handlers::handle_get_url(&documents, pipeline_id, reply)
2665 },
2666 WebDriverScriptCommand::IsEnabled(element_id, reply) => {
2667 webdriver_handlers::handle_is_enabled(&documents, pipeline_id, element_id, reply)
2668 },
2669 WebDriverScriptCommand::IsSelected(element_id, reply) => {
2670 webdriver_handlers::handle_is_selected(&documents, pipeline_id, element_id, reply)
2671 },
2672 WebDriverScriptCommand::GetTitle(reply) => {
2673 webdriver_handlers::handle_get_title(&documents, pipeline_id, reply)
2674 },
2675 WebDriverScriptCommand::WillSendKeys(
2676 element_id,
2677 text,
2678 strict_file_interactability,
2679 reply,
2680 ) => webdriver_handlers::handle_will_send_keys(
2681 cx,
2682 &documents,
2683 pipeline_id,
2684 element_id,
2685 text,
2686 strict_file_interactability,
2687 reply,
2688 ),
2689 WebDriverScriptCommand::AddLoadStatusSender(_, response_sender) => {
2690 webdriver_handlers::handle_add_load_status_sender(
2691 &documents,
2692 pipeline_id,
2693 response_sender,
2694 )
2695 },
2696 WebDriverScriptCommand::RemoveLoadStatusSender(_) => {
2697 webdriver_handlers::handle_remove_load_status_sender(&documents, pipeline_id)
2698 },
2699 WebDriverScriptCommand::ExecuteScriptWithCallback(script, reply) => {
2706 let window = documents.find_window(pipeline_id);
2707 drop(documents);
2708 webdriver_handlers::handle_execute_script(cx, window, script, reply);
2709 },
2710 WebDriverScriptCommand::SetProtocolHandlerAutomationMode(mode) => {
2711 webdriver_handlers::set_protocol_handler_automation_mode(
2712 &documents,
2713 pipeline_id,
2714 mode,
2715 )
2716 },
2717 WebDriverScriptCommand::SetPermission(name, state, reply) => {
2718 webdriver_handlers::set_permission(&documents, pipeline_id, name, state, reply)
2719 },
2720 }
2721 }
2722
2723 pub(crate) fn handle_resize_message(
2726 &self,
2727 id: PipelineId,
2728 viewport_details: ViewportDetails,
2729 size_type: WindowSizeType,
2730 ) {
2731 self.profile_event(ScriptThreadEventCategory::Resize, Some(id), || {
2732 let window = self.documents.borrow().find_window(id);
2733 if let Some(ref window) = window {
2734 window.add_resize_event(viewport_details, size_type);
2735 return;
2736 }
2737 let mut loads = self.incomplete_loads.borrow_mut();
2738 if let Some(ref mut load) = loads.iter_mut().find(|load| load.pipeline_id == id) {
2739 load.viewport_details = viewport_details;
2740 }
2741 })
2742 }
2743
2744 fn handle_update_webview_state(&self, cx: &mut JSContext, new_state: WebViewState) {
2746 let Some(webview_state) = self
2747 .webview_states
2748 .borrow()
2749 .get(&new_state.id)
2750 .and_then(Weak::upgrade)
2751 else {
2752 return;
2753 };
2754
2755 let new_theme = new_state.theme.get();
2756 let theme_changed = webview_state.theme.replace(new_theme) != new_theme;
2757 let new_has_system_focus = new_state.has_system_focus.get();
2758 let system_focus_changed =
2759 webview_state.has_system_focus.replace(new_has_system_focus) != new_has_system_focus;
2760
2761 if !theme_changed && !system_focus_changed {
2762 return;
2763 }
2764
2765 let documents: Vec<_> = self
2769 .documents
2770 .borrow()
2771 .iter()
2772 .map(|(_, document)| document)
2773 .collect();
2774
2775 for document in documents {
2776 if document.webview_id() != new_state.id {
2777 continue;
2778 }
2779 if theme_changed {
2780 document.window().refresh_theme();
2781 }
2782 if system_focus_changed {
2783 document.gained_or_lost_system_focus(cx, new_has_system_focus);
2784 }
2785 }
2786 }
2787
2788 fn handle_get_document_origin(
2789 &self,
2790 id: PipelineId,
2791 result_sender: GenericSender<Option<OriginSnapshot>>,
2792 ) {
2793 let _ = result_sender.send(
2794 self.documents
2795 .borrow()
2796 .find_document(id)
2797 .map(|document| document.origin().snapshot()),
2798 );
2799 }
2800
2801 fn handle_get_origin_details(
2802 &self,
2803 id: PipelineId,
2804 result_sender: GenericSender<Option<(OriginSnapshot, Vec<ImmutableOrigin>)>>,
2805 ) {
2806 let origin_details = self.documents.borrow().find_document(id).map(|document| {
2807 (
2808 document.origin().snapshot(),
2809 document
2810 .internal_ancestor_origin_objects_list()
2811 .clone()
2812 .unwrap_or_default(),
2813 )
2814 });
2815 let _ = result_sender.send(origin_details);
2816 }
2817
2818 fn handle_exit_fullscreen(&self, cx: &mut JSContext, id: PipelineId) {
2820 let document = self.documents.borrow().find_document(id);
2821 if let Some(document) = document {
2822 let mut realm = enter_auto_realm(cx, &*document);
2823 document.exit_fullscreen(&mut realm);
2824 }
2825 }
2826
2827 pub(crate) fn spawn_pipeline(&self, cx: &mut JSContext, new_pipeline_info: NewPipelineInfo) {
2828 self.profile_event(
2829 ScriptThreadEventCategory::SpawnPipeline,
2830 Some(new_pipeline_info.new_pipeline_id),
2831 || {
2832 #[cfg(feature = "devtools")]
2833 self.devtools_state
2834 .notify_pipeline_created(new_pipeline_info.new_pipeline_id);
2835
2836 let webview_state = self.shared_webview_state(&new_pipeline_info.webview_state);
2838 self.pre_page_load(cx, InProgressLoad::new(new_pipeline_info, webview_state));
2839 },
2840 );
2841 }
2842
2843 fn collect_reports(&self, cx: &mut JSContext, reports_chan: ReportsChan) {
2844 let documents = self.documents.borrow();
2845 let urls = itertools::join(documents.iter().map(|(_, d)| d.url().to_string()), ", ");
2846
2847 let mut reports = vec![];
2848 let mut computed_objects = HashSet::new();
2849 perform_memory_report(|ops| {
2850 for (_, document) in documents.iter() {
2851 document
2852 .window()
2853 .layout()
2854 .collect_reports(&mut reports, ops);
2855
2856 computed_objects.extend(document.collect_reports(&mut reports, ops));
2857 }
2858
2859 let prefix = format!("url({urls})");
2860 reports.extend(get_reports(cx, prefix, ops, computed_objects));
2861 });
2862
2863 reports_chan.send(ProcessReports::new(reports));
2864 }
2865
2866 fn handle_set_throttled_msg(&self, pipeline_id: PipelineId, throttled: bool) {
2867 let window = self.documents.borrow().find_window(pipeline_id);
2868 match window {
2869 Some(window) => {
2870 window.set_throttled(throttled);
2871 return;
2872 },
2873 None => {
2874 let mut loads = self.incomplete_loads.borrow_mut();
2875 if let Some(ref mut load) = loads
2876 .iter_mut()
2877 .find(|load| load.pipeline_id == pipeline_id)
2878 {
2879 load.throttled = throttled;
2880 return;
2881 }
2882 },
2883 }
2884
2885 warn!("SetThrottled sent to nonexistent pipeline");
2886 }
2887
2888 fn handle_set_document_activity_msg(
2890 &self,
2891 cx: &mut JSContext,
2892 id: PipelineId,
2893 activity: DocumentActivity,
2894 ) {
2895 debug!(
2896 "Setting activity of {} to be {:?} in {:?}.",
2897 id,
2898 activity,
2899 thread::current().name()
2900 );
2901
2902 let _ = self.senders.self_sender.send(MainThreadScriptMsg::Inactive);
2907
2908 let document = self.documents.borrow().find_document(id);
2909 if let Some(document) = document {
2910 document.set_activity(cx, activity);
2911 return;
2912 }
2913 let mut loads = self.incomplete_loads.borrow_mut();
2914 if let Some(ref mut load) = loads.iter_mut().find(|load| load.pipeline_id == id) {
2915 load.activity = activity;
2916 return;
2917 }
2918 warn!("change of activity sent to nonexistent pipeline");
2919 }
2920
2921 fn handle_focus_document_as_part_of_focusing_steps(
2922 &self,
2923 cx: &mut JSContext,
2924 pipeline_id: PipelineId,
2925 sequence: FocusSequenceNumber,
2926 browsing_context_id: Option<BrowsingContextId>,
2927 ) {
2928 let Some(document) = self.documents.borrow().find_document(pipeline_id) else {
2929 warn!("Unknown {pipeline_id:?} for FocusDocumentAsPartOfFocusingSteps message.");
2930 return;
2931 };
2932
2933 let focus_handler = document.focus_handler();
2934 if focus_handler.focus_sequence() > sequence {
2935 debug!(
2936 "Disregarding the FocusDocumentAsPartOfFocusingSteps message because \
2937 the contained sequence number is too old ({sequence:?} < {:?})",
2938 focus_handler.focus_sequence()
2939 );
2940 return;
2941 }
2942
2943 let iframe_element = browsing_context_id
2946 .and_then(|browsing_context_id| document.iframes().element(browsing_context_id));
2947
2948 rooted!(&in(cx) let focusable_area = iframe_element
2949 .map(|iframe_element| FocusableArea::IFrameViewport {
2950 iframe_element: iframe_element.as_traced(),
2951 kind: iframe_element
2952 .upcast::<Element>()
2953 .focusable_area_kind(cx.no_gc()),
2954 })
2955 .unwrap_or(FocusableArea::Viewport)
2956 );
2957
2958 rooted!(&in(cx) let new_focus_chain = focusable_area.focus_chain());
2959 rooted!(&in(cx) let old_focus_chain = focus_handler.current_focus_chain());
2960
2961 focus_handler.focus_update_steps(
2962 cx,
2963 new_focus_chain,
2964 old_focus_chain,
2965 &focusable_area,
2966 false, );
2968
2969 document.refresh_focus_rendering();
2970 }
2971
2972 fn handle_focus_document(
2973 &self,
2974 cx: &mut JSContext,
2975 pipeline_id: PipelineId,
2976 remote_focus_operation: RemoteFocusOperation,
2977 ) {
2978 let Some(document) = self.documents.borrow().find_document(pipeline_id) else {
2979 warn!("Unknown {pipeline_id:?} for FocusDocument message.");
2980 return;
2981 };
2982 match remote_focus_operation {
2983 RemoteFocusOperation::Viewport => document.window().Focus(cx),
2984 RemoteFocusOperation::Sequential(direction, iframe_browsing_context_id) => document
2985 .focus_handler()
2986 .sequential_focus_from_another_document(cx, iframe_browsing_context_id, direction),
2987 }
2988 }
2989
2990 fn handle_unfocus_document_as_part_of_focusing_steps(
2991 &self,
2992 cx: &mut JSContext,
2993 pipeline_id: PipelineId,
2994 sequence: FocusSequenceNumber,
2995 ) {
2996 let Some(document) = self.documents.borrow().find_document(pipeline_id) else {
2997 warn!("Unknown {pipeline_id:?} for UnfocusDocumentAsPartOfFocusingSteps");
2998 return;
2999 };
3000
3001 let window = document.window();
3004 if window.is_top_level() {
3005 return;
3006 }
3007
3008 let focus_handler = document.focus_handler();
3009 if focus_handler.focus_sequence() > sequence {
3010 debug!(
3011 "Disregarding the Unfocus message because the contained sequence number is \
3012 too old ({:?} < {:?})",
3013 sequence,
3014 focus_handler.focus_sequence()
3015 );
3016 return;
3017 }
3018
3019 rooted!(&in(cx) let new_focus_chain = vec![]);
3020 rooted!(&in(cx) let old_focus_chain = focus_handler.current_focus_chain());
3021
3022 focus_handler.focus_update_steps(
3023 cx,
3024 new_focus_chain,
3025 old_focus_chain,
3026 &FocusableArea::Viewport,
3027 false, );
3029
3030 document.refresh_focus_rendering();
3031 }
3032
3033 #[expect(clippy::too_many_arguments)]
3034 fn handle_post_message_msg(
3036 &self,
3037 cx: &mut JSContext,
3038 pipeline_id: PipelineId,
3039 source_webview: WebViewId,
3040 source_with_ancestry: Vec<BrowsingContextId>,
3041 origin: Option<ImmutableOrigin>,
3042 source_origin: ImmutableOrigin,
3043 data: StructuredSerializedData,
3044 ) {
3045 let window = self.documents.borrow().find_window(pipeline_id);
3046 match window {
3047 None => warn!("postMessage after target pipeline {} closed.", pipeline_id),
3048 Some(window) => {
3049 let mut last = None;
3050 for browsing_context_id in source_with_ancestry.into_iter().rev() {
3051 if let Some(window_proxy) =
3052 self.window_proxies.find_window_proxy(browsing_context_id)
3053 {
3054 last = Some(window_proxy);
3055 continue;
3056 }
3057 let window_proxy = WindowProxy::new_dissimilar_origin(
3058 cx,
3059 window.upcast::<GlobalScope>(),
3060 browsing_context_id,
3061 source_webview,
3062 last.as_deref(),
3063 None,
3064 CreatorBrowsingContextInfo::from(last.as_deref(), None),
3065 );
3066 self.window_proxies
3067 .insert(browsing_context_id, &window_proxy);
3068 last = Some(window_proxy);
3069 }
3070
3071 let source = last.expect("Source with ancestry should contain at least one bc.");
3074
3075 window.post_message(origin, source_origin, &source, data)
3077 },
3078 }
3079 }
3080
3081 fn handle_stop_delaying_load_events_mode(&self, pipeline_id: PipelineId) {
3082 let window = self.documents.borrow().find_window(pipeline_id);
3083 if let Some(window) = window {
3084 match window.undiscarded_window_proxy() {
3085 Some(window_proxy) => window_proxy.stop_delaying_load_events_mode(),
3086 None => warn!(
3087 "Attempted to take {} of 'delaying-load-events-mode' after having been discarded.",
3088 pipeline_id
3089 ),
3090 };
3091 }
3092 }
3093
3094 fn handle_unload_document(&self, cx: &mut JSContext, pipeline_id: PipelineId) {
3095 let document = self.documents.borrow().find_document(pipeline_id);
3096 if let Some(document) = document {
3097 document.unload(cx, false);
3098 }
3099 }
3100
3101 fn handle_update_pipeline_id(
3102 &self,
3103 cx: &mut JSContext,
3104 parent_pipeline_id: PipelineId,
3105 browsing_context_id: BrowsingContextId,
3106 webview_id: WebViewId,
3107 new_pipeline_id: PipelineId,
3108 reason: UpdatePipelineIdReason,
3109 ) {
3110 let frame_element = self
3111 .documents
3112 .borrow()
3113 .find_iframe(parent_pipeline_id, browsing_context_id);
3114 let Some(frame_element) = frame_element else {
3115 return;
3116 };
3117 if !frame_element.update_pipeline_id(cx, new_pipeline_id, reason) {
3118 return;
3119 };
3120
3121 let Some(window) = self.documents.borrow().find_window(new_pipeline_id) else {
3122 return;
3123 };
3124 let _ = self.window_proxies.local_window_proxy(
3127 cx,
3128 &self.senders,
3129 &window,
3130 browsing_context_id,
3131 webview_id,
3132 Some(parent_pipeline_id),
3133 Some(frame_element),
3134 None,
3138 );
3139 }
3140
3141 fn handle_update_history_state_msg(
3142 &self,
3143 cx: &mut JSContext,
3144 pipeline_id: PipelineId,
3145 history_state_id: Option<HistoryStateId>,
3146 url: ServoUrl,
3147 ) {
3148 let Some(window) = self.documents.borrow().find_window(pipeline_id) else {
3149 return warn!("update history state after pipeline {pipeline_id} closed.",);
3150 };
3151 window.History(cx).activate_state(cx, history_state_id, url);
3152 }
3153
3154 fn handle_remove_history_states(
3155 &self,
3156 cx: &mut JSContext,
3157 pipeline_id: PipelineId,
3158 history_states: Vec<HistoryStateId>,
3159 ) {
3160 let Some(window) = self.documents.borrow().find_window(pipeline_id) else {
3161 return warn!("update history state after pipeline {pipeline_id} closed.",);
3162 };
3163 window.History(cx).remove_states(history_states);
3164 }
3165
3166 fn handle_resize_inactive_msg(&self, id: PipelineId, new_viewport_details: ViewportDetails) {
3168 let window = self.documents.borrow().find_window(id)
3169 .expect("ScriptThread: received a resize msg for a pipeline not in this script thread. This is a bug.");
3170 window.set_viewport_details(new_viewport_details);
3171 }
3172
3173 pub(crate) fn handle_page_headers_available(
3176 &self,
3177 cx: &mut JSContext,
3178 webview_id: WebViewId,
3179 pipeline_id: PipelineId,
3180 metadata: Option<&Metadata>,
3181 origin: MutableOrigin,
3182 ) -> Option<DomRoot<Document>> {
3183 if self.task_queue.is_pipeline_closed(&pipeline_id) {
3184 return None;
3186 }
3187
3188 let Some(idx) = self
3189 .incomplete_loads
3190 .borrow()
3191 .iter()
3192 .position(|load| load.pipeline_id == pipeline_id)
3193 else {
3194 unreachable!("Pipeline shouldn't have finished loading.");
3195 };
3196
3197 let is_204_205 = match metadata {
3202 Some(metadata) => metadata.status.in_range(204..=205),
3203 _ => false,
3204 };
3205
3206 if is_204_205 {
3207 if let Some(window) = self.documents.borrow().find_window(pipeline_id) {
3209 let window_proxy = window.window_proxy();
3210 if window_proxy.parent().is_some() {
3213 window_proxy.stop_delaying_load_events_mode();
3219 }
3220 }
3221 self.senders
3222 .pipeline_to_constellation_sender
3223 .send((
3224 webview_id,
3225 pipeline_id,
3226 ScriptToConstellationMessage::AbortLoadUrl,
3227 ))
3228 .unwrap();
3229 return None;
3230 };
3231
3232 let load = self.incomplete_loads.borrow_mut().remove(idx);
3233 metadata.map(|meta| self.load(cx, meta, load, origin))
3234 }
3235
3236 fn handle_get_title_msg(&self, pipeline_id: PipelineId) {
3238 let Some(document) = self.documents.borrow().find_document(pipeline_id) else {
3239 return warn!("Message sent to closed pipeline {pipeline_id}.");
3240 };
3241 document.send_title_to_embedder();
3242 }
3243
3244 fn handle_exit_pipeline_msg(
3246 &self,
3247 cx: &mut JSContext,
3248 webview_id: WebViewId,
3249 pipeline_id: PipelineId,
3250 discard_browsing_context: DiscardBrowsingContext,
3251 ) {
3252 debug!("{pipeline_id}: Starting pipeline exit.");
3253
3254 let document = self.documents.borrow_mut().remove(pipeline_id);
3257 if let Some(document) = document {
3258 debug_assert!(
3260 !self
3261 .incomplete_loads
3262 .borrow()
3263 .iter()
3264 .any(|load| load.pipeline_id == pipeline_id)
3265 );
3266
3267 if let Some(parser) = document.get_current_parser() {
3268 parser.abort(cx, AbortReason::PipelineExited);
3269 }
3270
3271 if !document.window_detached() {
3272 debug!("{pipeline_id}: Shutting down layout");
3273 document.window().layout_mut().exit_now();
3274 }
3275
3276 debug!("{pipeline_id}: Clearing animations");
3278 document.animation_manager().clear();
3279
3280 if !document.window_detached() {
3281 let window = document.window();
3284 if discard_browsing_context == DiscardBrowsingContext::Yes {
3285 let browsing_context_id = window.window_proxy().browsing_context_id();
3286 window.discard_browsing_context();
3287 self.window_proxies.remove(browsing_context_id);
3288 }
3289
3290 window.image_cache().clear();
3293
3294 window.as_global_scope().tear_down();
3297
3298 debug!("{pipeline_id}: Clearing JavaScript runtime");
3299 window.clear_js_runtime();
3300 }
3301 }
3302
3303 self.task_queue
3305 .remove_tasks_for_exiting_pipeline(&pipeline_id);
3306
3307 debug!("{pipeline_id}: Sending PipelineExited message to constellation");
3308 self.senders
3309 .pipeline_to_constellation_sender
3310 .send((
3311 webview_id,
3312 pipeline_id,
3313 ScriptToConstellationMessage::PipelineExited,
3314 ))
3315 .ok();
3316
3317 self.paint_api
3318 .pipeline_exited(webview_id, pipeline_id, PipelineExitSource::Script);
3319
3320 #[cfg(feature = "devtools")]
3321 self.devtools_state.notify_pipeline_exited(pipeline_id);
3322 self.pipeline_to_node_ids.borrow_mut().remove(&pipeline_id);
3323
3324 LivePromiseReferences::expensive_prune_dropped_promises();
3327
3328 debug!("{pipeline_id}: Finished pipeline exit");
3329 }
3330
3331 fn handle_exit_script_thread_msg(&self, cx: &mut JSContext) {
3333 debug!("Exiting script thread.");
3334
3335 let mut webview_and_pipeline_ids = Vec::new();
3336 webview_and_pipeline_ids.extend(
3337 self.incomplete_loads
3338 .borrow()
3339 .iter()
3340 .next()
3341 .map(|load| (load.webview_id(), load.pipeline_id)),
3342 );
3343 webview_and_pipeline_ids.extend(
3344 self.documents
3345 .borrow()
3346 .iter()
3347 .next()
3348 .map(|(pipeline_id, document)| (document.webview_id(), pipeline_id)),
3349 );
3350
3351 for (webview_id, pipeline_id) in webview_and_pipeline_ids {
3352 self.handle_exit_pipeline_msg(cx, webview_id, pipeline_id, DiscardBrowsingContext::Yes);
3353 }
3354
3355 self.background_hang_monitor.unregister();
3356
3357 if opts::get().multiprocess {
3360 debug!("Exiting IPC router thread in script thread.");
3361 ipc_channel::router::ROUTER.shutdown();
3362 }
3363
3364 debug!("Exited script thread.");
3365 }
3366
3367 pub(crate) fn handle_tick_all_animations_for_testing(no_gc: &NoGC, id: PipelineId) {
3369 with_script_thread(|script_thread| {
3370 let Some(document) = script_thread.documents.borrow().find_document(id) else {
3371 warn!("Animation tick for tests for closed pipeline {id}.");
3372 return;
3373 };
3374 document.maybe_mark_animating_nodes_as_dirty(no_gc);
3375 });
3376 }
3377
3378 fn handle_web_font_loaded(&self, no_gc: &NoGC, pipeline_id: PipelineId) {
3380 let Some(document) = self.documents.borrow().find_document(pipeline_id) else {
3381 warn!("Web font loaded in closed pipeline {}.", pipeline_id);
3382 return;
3383 };
3384
3385 document.dirty_all_nodes(no_gc);
3387
3388 document
3389 .window()
3390 .font_context()
3391 .decrement_count_of_loading_fonts_by_one();
3392 }
3393
3394 fn handle_worklet_loaded(&self, pipeline_id: PipelineId) {
3397 if let Some(document) = self.documents.borrow().find_document(pipeline_id) {
3398 document.add_restyle_reason(RestyleReason::PaintWorkletLoaded);
3399 }
3400 }
3401
3402 #[allow(clippy::too_many_arguments)]
3404 fn handle_storage_event(
3405 &self,
3406 cx: &mut JSContext,
3407 pipeline_id: PipelineId,
3408 storage_type: WebStorageType,
3409 url: ServoUrl,
3410 key: Option<String>,
3411 old_value: Option<String>,
3412 new_value: Option<String>,
3413 ) {
3414 let Some(window) = self.documents.borrow().find_window(pipeline_id) else {
3415 return warn!("Storage event sent to closed pipeline {pipeline_id}.");
3416 };
3417
3418 let storage = match storage_type {
3419 WebStorageType::Local => window.GetLocalStorage(cx),
3420 WebStorageType::Session => window.GetSessionStorage(cx),
3421 };
3422 let Ok(storage) = storage else {
3423 return;
3424 };
3425
3426 storage.queue_storage_event(url, key, old_value, new_value);
3427 }
3428
3429 fn handle_iframe_load_event(
3431 &self,
3432 cx: &mut JSContext,
3433 parent_id: PipelineId,
3434 browsing_context_id: BrowsingContextId,
3435 child_id: PipelineId,
3436 ) {
3437 let iframe = self
3438 .documents
3439 .borrow()
3440 .find_iframe(parent_id, browsing_context_id);
3441 match iframe {
3442 Some(iframe) => iframe.iframe_load_event_steps(cx, child_id),
3443 None => warn!("Message sent to closed pipeline {}.", parent_id),
3444 }
3445 }
3446
3447 fn ask_constellation_for_top_level_info(
3448 &self,
3449 sender_webview_id: WebViewId,
3450 sender_pipeline_id: PipelineId,
3451 browsing_context_id: BrowsingContextId,
3452 ) -> Option<WebViewId> {
3453 let (result_sender, result_receiver) = generic_channel::channel().unwrap();
3454 let msg = ScriptToConstellationMessage::GetTopForBrowsingContext(
3455 browsing_context_id,
3456 result_sender,
3457 );
3458 self.senders
3459 .pipeline_to_constellation_sender
3460 .send((sender_webview_id, sender_pipeline_id, msg))
3461 .expect("Failed to send to constellation.");
3462 result_receiver
3463 .recv()
3464 .expect("Failed to get top-level id from constellation.")
3465 }
3466
3467 fn load(
3470 &self,
3471 cx: &mut JSContext,
3472 metadata: &Metadata,
3473 incomplete: InProgressLoad,
3474 origin: MutableOrigin,
3475 ) -> DomRoot<Document> {
3476 let webview_id = incomplete.webview_id();
3477 let script_to_constellation_chan = ScriptToConstellationChan {
3478 sender: self.senders.pipeline_to_constellation_sender.clone(),
3479 webview_id,
3480 pipeline_id: incomplete.pipeline_id,
3481 };
3482
3483 let final_url = metadata.final_url.clone();
3484 let _ = script_to_constellation_chan
3485 .send(ScriptToConstellationMessage::SetFinalUrl(final_url.clone()));
3486
3487 debug!(
3488 "ScriptThread: loading {} on pipeline {:?}",
3489 incomplete.load_data.url, incomplete.pipeline_id
3490 );
3491
3492 let font_context = Arc::new(FontContext::new(
3493 self.system_font_service.clone(),
3494 self.paint_api.clone(),
3495 self.resource_threads.clone(),
3496 ));
3497
3498 let font_resolver = Arc::new(SvgFontResolver::new(font_context.clone()));
3499
3500 let image_cache = self.image_cache_factory.create(
3501 webview_id,
3502 incomplete.pipeline_id,
3503 &self.paint_api,
3504 font_resolver,
3505 );
3506
3507 let (user_contents, user_stylesheets) = incomplete
3508 .user_content_manager_id
3509 .and_then(|user_content_manager_id| {
3510 self.user_contents_for_manager_id
3511 .borrow()
3512 .get(&user_content_manager_id)
3513 .map(|script_thread_user_contents| {
3514 (
3515 script_thread_user_contents.user_scripts.clone(),
3516 script_thread_user_contents.user_stylesheets.clone(),
3517 )
3518 })
3519 })
3520 .unwrap_or_default();
3521
3522 let layout_config = LayoutConfig {
3523 id: incomplete.pipeline_id,
3524 webview_id,
3525 url: final_url.clone(),
3526 is_iframe: incomplete.parent_info.is_some(),
3527 script_chan: self.senders.constellation_sender.clone(),
3528 image_cache: image_cache.clone(),
3529 font_context,
3530 time_profiler_chan: self.senders.time_profiler_sender.clone(),
3531 paint_api: self.paint_api.clone(),
3532 viewport_details: incomplete.viewport_details,
3533 user_stylesheets,
3534 theme: incomplete.webview_state.theme.get(),
3535 embedder_chan: self.senders.pipeline_to_embedder_sender.clone(),
3536 };
3537
3538 let creation_url = final_url.clone();
3543 let window = match window_for_replacement(
3544 &self.window_proxies,
3545 incomplete.browsing_context_id,
3546 &origin,
3547 ) {
3548 Some(window) => {
3549 window.set_up_a_window_environment_settings_object(
3550 self.layout_factory.create(layout_config),
3551 creation_url,
3552 final_url.clone(),
3554 incomplete.navigation_start,
3555 incomplete.viewport_details,
3556 );
3557 window
3558 },
3559 None => {
3560 Window::new(
3561 cx,
3562 incomplete.webview_state,
3563 self.js_runtime.clone(),
3564 self.senders.self_sender.clone(),
3565 self.layout_factory.create(layout_config),
3566 self.senders.image_cache_sender.clone(),
3567 self.resource_threads.clone(),
3568 self.storage_threads.clone(),
3569 #[cfg(feature = "bluetooth")]
3570 self.senders.bluetooth_sender.clone(),
3571 self.senders.memory_profiler_sender.clone(),
3572 self.senders.time_profiler_sender.clone(),
3573 #[cfg(feature = "devtools")]
3574 self.senders.devtools_server_sender.clone(),
3575 self.senders.pipeline_to_constellation_sender.clone(),
3576 self.senders.pipeline_to_embedder_sender.clone(),
3577 self.senders.constellation_sender.clone(),
3578 incomplete.pipeline_id,
3579 incomplete.parent_info,
3580 incomplete.viewport_details,
3581 origin.clone(),
3582 creation_url,
3583 final_url.clone(),
3588 incomplete.navigation_start,
3589 #[cfg(feature = "webgl")]
3590 self.webgl_chan.as_ref().map(|chan| chan.channel()),
3591 #[cfg(feature = "webxr")]
3592 self.webxr_registry.clone(),
3593 self.paint_api.clone(),
3594 self.unminify_js,
3595 self.unminify_css,
3596 self.local_script_source.clone(),
3597 user_contents,
3598 self.player_context.clone(),
3599 #[cfg(feature = "webgpu")]
3600 self.gpu_id_hub.clone(),
3601 incomplete.load_data.inherited_secure_context,
3602 self.this.clone(),
3603 )
3604 },
3605 };
3606 #[cfg(feature = "devtools")]
3607 if self.senders.devtools_server_sender.is_some() {
3608 self.debugger_global.fire_add_debuggee(
3609 cx,
3610 window.upcast(),
3611 incomplete.pipeline_id,
3612 None,
3613 );
3614 }
3615
3616 let mut realm = enter_auto_realm(cx, &*window);
3617 let cx = &mut realm;
3618
3619 let last_modified = metadata.headers.as_ref().and_then(|headers| {
3627 headers.typed_get::<LastModified>().map(|tm| {
3628 let tm: SystemTime = tm.into();
3629 let local_time: DateTime<Local> = tm.into();
3630 local_time.format("%m/%d/%Y %H:%M:%S").to_string()
3631 })
3632 });
3633
3634 let loader = DocumentLoader::new_with_threads(
3635 self.resource_threads.clone(),
3636 Some(final_url.clone()),
3637 );
3638
3639 let content_type: Option<Mime> = metadata
3640 .content_type
3641 .clone()
3642 .map(Serde::into_inner)
3643 .map(Mime::from_ct);
3644 let encoding_hint_from_content_type = content_type
3645 .as_ref()
3646 .and_then(|mime| mime.get_parameter(CHARSET))
3647 .and_then(|charset| Encoding::for_label(charset.as_bytes()));
3648
3649 let is_html_document = match content_type {
3650 Some(ref mime) if mime.type_ == APPLICATION && mime.has_suffix("xml") => {
3651 IsHTMLDocument::NonHTMLDocument
3652 },
3653
3654 Some(ref mime) if mime.matches(TEXT, XML) || mime.matches(APPLICATION, XML) => {
3655 IsHTMLDocument::NonHTMLDocument
3656 },
3657 _ => IsHTMLDocument::HTMLDocument,
3658 };
3659
3660 let referrer = metadata
3667 .referrer
3668 .as_ref()
3669 .map(|referrer| referrer.clone().into_string());
3670
3671 let document = Document::new(
3680 cx,
3681 &window,
3682 HasBrowsingContext::Yes,
3683 Some(final_url.clone()),
3684 incomplete.load_data.about_base_url,
3685 origin,
3686 is_html_document,
3687 content_type,
3688 last_modified,
3689 incomplete.activity,
3690 loader,
3691 referrer,
3692 Some(metadata.status.raw_code()),
3693 incomplete.load_data.is_initial_about_blank,
3694 true,
3695 incomplete.load_data.inherited_insecure_requests_policy,
3696 incomplete.load_data.has_trustworthy_ancestor_origin,
3697 self.custom_element_reaction_stack.clone(),
3698 incomplete.load_data.creation_sandboxing_flag_set,
3699 incomplete.pipeline_id,
3700 image_cache,
3701 );
3702
3703 document.set_navigation_start(incomplete.navigation_start);
3707
3708 let referrer_policy = metadata
3709 .headers
3710 .as_deref()
3711 .and_then(|h| h.typed_get::<ReferrerPolicyHeader>())
3712 .into();
3713 document.set_referrer_policy(referrer_policy);
3714
3715 self.documents
3716 .borrow_mut()
3717 .insert(incomplete.pipeline_id, &document);
3718
3719 window.init_document(&document);
3721
3722 let iframe = incomplete.parent_info.and_then(|parent_id| {
3723 self.documents
3724 .borrow()
3725 .find_iframe(parent_id, incomplete.browsing_context_id)
3726 });
3727 let iframe_in_this_script_thread = iframe.is_some();
3741 let parent_info = incomplete.parent_info.filter(|parent_id| {
3742 iframe_in_this_script_thread ||
3743 self.documents.borrow().find_document(*parent_id).is_none()
3744 });
3745
3746 let window_proxy = self.window_proxies.local_window_proxy(
3748 cx,
3749 &self.senders,
3750 &window,
3751 incomplete.browsing_context_id,
3752 webview_id,
3753 parent_info,
3754 iframe,
3755 incomplete.opener,
3756 );
3757 if let Some(name) = incomplete.frame_name {
3758 window_proxy.set_name(DOMString::from(name));
3759 }
3760 if window_proxy.parent().is_some() {
3761 window_proxy.stop_delaying_load_events_mode();
3766 }
3767 window.init_window_proxy(&window_proxy);
3768
3769 if let Some(parent_pipeline) = parent_info {
3770 if iframe_in_this_script_thread {
3771 self.handle_update_pipeline_id(
3774 cx,
3775 parent_pipeline,
3776 window_proxy.browsing_context_id(),
3777 webview_id,
3778 incomplete.pipeline_id,
3779 UpdatePipelineIdReason::Navigation,
3780 );
3781 } else {
3782 let (result_sender, result_receiver) = generic_channel::channel().unwrap();
3787 let msg = ScriptToConstellationMessage::IsCurrentlyFullyActive(
3788 parent_pipeline,
3789 result_sender,
3790 );
3791 self.senders
3792 .pipeline_to_constellation_sender
3793 .send((webview_id, incomplete.pipeline_id, msg))
3794 .expect("Failed to send to constellation.");
3795 let is_parent_fully_active = result_receiver
3796 .recv()
3797 .expect("Failed to get top-level id from constellation.");
3798 if is_parent_fully_active {
3799 window_proxy
3800 .parent()
3801 .expect("Must either have a frame element or remote proxy")
3802 .set_pipeline_id(parent_pipeline);
3803 }
3804 }
3805 }
3806
3807 let refresh_header = metadata.headers.as_deref().and_then(|h| h.get(REFRESH));
3808 if let Some(refresh_val) = refresh_header {
3810 document.shared_declarative_refresh_steps(
3815 refresh_val.as_bytes(),
3816 false,
3817 );
3818 }
3819
3820 self.senders
3821 .pipeline_to_constellation_sender
3822 .send((
3823 webview_id,
3824 incomplete.pipeline_id,
3825 ScriptToConstellationMessage::ActivateDocument,
3826 ))
3827 .unwrap();
3828
3829 let incomplete_browsing_context_id: BrowsingContextId = webview_id.into();
3831 let is_top_level_global = incomplete_browsing_context_id == incomplete.browsing_context_id;
3832 #[cfg(feature = "devtools")]
3833 self.notify_devtools(
3834 document.Title(),
3835 final_url.clone(),
3836 is_top_level_global,
3837 (
3838 incomplete.browsing_context_id,
3839 incomplete.pipeline_id,
3840 None,
3841 webview_id,
3842 ),
3843 );
3844
3845 if !incomplete.load_data.is_initial_about_blank {
3846 if is_html_document == IsHTMLDocument::NonHTMLDocument {
3847 ServoParser::parse_xml_document(
3848 cx,
3849 &document,
3850 None,
3851 final_url,
3852 encoding_hint_from_content_type,
3853 );
3854 } else {
3855 ServoParser::parse_html_document(
3856 cx,
3857 &document,
3858 None,
3859 final_url,
3860 encoding_hint_from_content_type,
3861 incomplete.load_data.container_document_encoding,
3862 );
3863 }
3864 }
3865
3866 if incomplete.activity == DocumentActivity::FullyActive {
3867 window.resume(cx);
3868 } else {
3869 window.suspend(cx);
3870 }
3871
3872 if incomplete.throttled {
3873 window.set_throttled(true);
3874 }
3875
3876 document
3877 }
3878
3879 #[cfg(feature = "devtools")]
3880 fn notify_devtools(
3881 &self,
3882 title: DOMString,
3883 url: ServoUrl,
3884 is_top_level_global: bool,
3885 (browsing_context_id, pipeline_id, worker_id, webview_id): (
3886 BrowsingContextId,
3887 PipelineId,
3888 Option<WorkerId>,
3889 WebViewId,
3890 ),
3891 ) {
3892 if let Some(ref chan) = self.senders.devtools_server_sender {
3893 let page_info = DevtoolsPageInfo {
3894 title: String::from(title),
3895 url,
3896 is_top_level_global,
3897 is_service_worker: false,
3898 };
3899 chan.send(ScriptToDevtoolsControlMsg::NewGlobal(
3900 (browsing_context_id, pipeline_id, worker_id, webview_id),
3901 self.senders.devtools_client_to_script_thread_sender.clone(),
3902 page_info.clone(),
3903 ))
3904 .unwrap();
3905
3906 let state = NavigationState::Stop(pipeline_id, page_info);
3907 let _ = chan.send(ScriptToDevtoolsControlMsg::Navigate(
3908 browsing_context_id,
3909 state,
3910 ));
3911 }
3912 }
3913
3914 fn handle_input_event(
3916 &self,
3917 webview_id: WebViewId,
3918 pipeline_id: PipelineId,
3919 event: ConstellationInputEvent,
3920 ) {
3921 let Some(document) = self.documents.borrow().find_document(pipeline_id) else {
3922 warn!("Input event sent to closed pipeline {pipeline_id}.");
3923 let _ = self
3924 .senders
3925 .pipeline_to_embedder_sender
3926 .send(EmbedderMsg::InputEventsHandled(
3927 webview_id,
3928 vec![InputEventOutcome {
3929 id: event.event.id,
3930 result: Default::default(),
3931 }],
3932 ));
3933 return;
3934 };
3935 document.event_handler().note_pending_input_event(event);
3936 }
3937
3938 fn set_accessibility_active(&self, pipeline_id: PipelineId, active: bool, epoch: Epoch) {
3940 if !(pref!(accessibility_enabled)) {
3941 return;
3942 }
3943
3944 let Some(document) = self.documents.borrow().find_document(pipeline_id) else {
3945 if active {
3946 error!("Trying to set accessibility active on stale document: {pipeline_id}");
3947 }
3948 return;
3949 };
3950
3951 document
3952 .window()
3953 .layout()
3954 .set_accessibility_active(active, epoch);
3955 }
3956
3957 fn forward_accessibility_action(
3959 &self,
3960 pipeline_id: PipelineId,
3961 action_request: accesskit::ActionRequest,
3962 ) {
3963 if !(pref!(accessibility_enabled)) {
3964 return;
3965 }
3966
3967 let Some(document) = self.documents.borrow().find_document(pipeline_id) else {
3968 error!("Trying to forward accessibility action to stale document: {pipeline_id}");
3969 return;
3970 };
3971
3972 document
3973 .window()
3974 .layout()
3975 .handle_accessibility_action(action_request);
3976 }
3977
3978 fn handle_navigate_iframe(
3980 &self,
3981 cx: &mut JSContext,
3982 parent_pipeline_id: PipelineId,
3983 browsing_context_id: BrowsingContextId,
3984 load_data: LoadData,
3985 history_handling: NavigationHistoryBehavior,
3986 target_snapshot_params: TargetSnapshotParams,
3987 ) {
3988 let iframe = self
3989 .documents
3990 .borrow()
3991 .find_iframe(parent_pipeline_id, browsing_context_id);
3992 if let Some(iframe) = iframe {
3993 iframe.navigate_or_reload_child_browsing_context(
3994 cx,
3995 load_data,
3996 history_handling,
3997 ProcessingMode::NotFirstTime,
3998 target_snapshot_params,
3999 );
4000 }
4001 }
4002
4003 fn evaluate_a_javascript_url(
4008 cx: &mut JSContext,
4009 global_scope: &GlobalScope,
4010 load_data: &mut LoadData,
4011 ) -> bool {
4012 let encoded = &load_data.url[Position::AfterScheme..][1..];
4016
4017 let script_source = percent_decode(encoded.as_bytes()).decode_utf8_lossy();
4020
4021 let base_url = global_scope.api_base_url();
4025
4026 let mut realm = enter_auto_realm(cx, global_scope);
4029 let cx = &mut realm.current_realm();
4030 let classic_script = global_scope.create_a_classic_script(
4031 cx,
4032 script_source,
4033 base_url,
4034 ScriptOptions::ReturnsAValue,
4035 ScriptFetchOptions::default_classic_script(),
4036 Some(IntroductionType::JAVASCRIPT_URL),
4037 1, );
4039
4040 rooted!(&in(cx) let mut return_value = UndefinedValue());
4042 let evaluation_status = global_scope.run_a_classic_script(
4043 cx,
4044 classic_script,
4045 RethrowErrors::No,
4046 Some(return_value.handle_mut()),
4047 );
4048
4049 if evaluation_status.is_err() || !return_value.get().is_string() {
4054 return false;
4055 }
4056 let Ok(ConversionResult::Success(body)) =
4057 DOMString::from_jsval(cx, return_value.handle(), StringificationBehavior::Empty)
4058 else {
4059 return false;
4060 };
4061
4062 load_data.url = global_scope.get_url();
4067 load_data
4068 .headers
4069 .typed_insert(headers::ContentType::from(mime::TEXT_HTML_UTF_8));
4070 load_data.js_eval_result = Some(body.into());
4071
4072 let policy_container = global_scope.policy_container();
4075 load_data.policy_container = Some(policy_container);
4076
4077 load_data.about_base_url = global_scope.as_window().Document().about_base_url();
4110 true
4111 }
4112
4113 #[servo_tracing::instrument(skip_all)]
4116 fn pre_page_load(&self, cx: &mut JSContext, mut incomplete: InProgressLoad) {
4117 let origin_from_snapshot = || -> Option<MutableOrigin> {
4118 match incomplete.load_data.load_origin {
4119 LoadOrigin::Script(ref snapshot) => {
4120 Some(MutableOrigin::from_snapshot(snapshot.clone()))
4121 },
4122 _ => None,
4123 }
4124 };
4125
4126 let preserved_origin = || -> Option<MutableOrigin> {
4127 let creator_pipeline_id = incomplete.load_data.creator_pipeline_id?;
4137 Some(
4138 ScriptThread::find_document(creator_pipeline_id)?
4139 .origin()
4140 .clone(),
4141 )
4142 };
4143
4144 let url_str = incomplete.load_data.url.as_str();
4145 if url_str == "about:blank" || incomplete.load_data.js_eval_result.is_some() {
4146 let source_origin = preserved_origin().or(origin_from_snapshot());
4147 self.start_synchronous_page_load(cx, incomplete, source_origin);
4148 return;
4149 }
4150 if url_str == "about:srcdoc" {
4151 let source_origin = preserved_origin().or(origin_from_snapshot());
4152 self.page_load_about_srcdoc(cx, incomplete, source_origin);
4153 return;
4154 }
4155
4156 let source_origin = origin_from_snapshot();
4157 let context = ParserContext::new(
4158 incomplete.webview_id(),
4159 incomplete.pipeline_id,
4160 incomplete.load_data.url.clone(),
4161 incomplete.load_data.creation_sandboxing_flag_set,
4162 incomplete.parent_info,
4163 incomplete.target_snapshot_params,
4164 source_origin,
4165 );
4166 self.incomplete_parser_contexts
4167 .0
4168 .borrow_mut()
4169 .push((incomplete.pipeline_id, context));
4170
4171 let request_builder = incomplete.request_builder();
4172 incomplete.controller = FetchController::new(
4173 request_builder.id,
4174 false,
4175 self.resource_threads.core_thread.clone(),
4176 );
4177 NavigationListener::new(request_builder, self.senders.self_sender.clone())
4178 .initiate_fetch(&self.resource_threads.core_thread, None);
4179 self.incomplete_loads.borrow_mut().push(incomplete);
4180 }
4181
4182 fn handle_navigation_response(
4183 &self,
4184 cx: &mut JSContext,
4185 pipeline_id: PipelineId,
4186 message: FetchResponseMsg,
4187 ) {
4188 if let Some(metadata) = NavigationListener::http_redirect_metadata(&message) {
4189 self.handle_navigation_redirect(pipeline_id, metadata);
4190 return;
4191 };
4192
4193 match message {
4194 FetchResponseMsg::ProcessResponse(_request_id, metadata) => {
4195 self.handle_fetch_metadata(cx, pipeline_id, metadata)
4196 },
4197 FetchResponseMsg::ProcessResponseChunk(_request_id, chunk) => {
4198 self.handle_fetch_chunk(cx, pipeline_id, chunk)
4199 },
4200 FetchResponseMsg::ProcessResponseEOF(_request_id, eof, timing) => {
4201 self.handle_fetch_eof(cx, pipeline_id, eof, timing)
4202 },
4203 FetchResponseMsg::ProcessCspViolations(request_id, violations) => {
4204 self.handle_csp_violations(cx, pipeline_id, request_id, violations)
4205 },
4206 FetchResponseMsg::ProcessRequestBody(..) => {},
4207 FetchResponseMsg::ProcessContentLength(_request_id, _size) => {},
4208 }
4209 }
4210
4211 fn handle_fetch_metadata(
4212 &self,
4213 cx: &mut JSContext,
4214 id: PipelineId,
4215 fetch_metadata: Result<FetchMetadata, NetworkError>,
4216 ) {
4217 match fetch_metadata {
4218 Ok(_) => (),
4219 Err(NetworkError::Crash(..)) => (),
4220 Err(ref e) => {
4221 warn!("Network error: {:?}", e);
4222 },
4223 };
4224
4225 let mut incomplete_parser_contexts = self.incomplete_parser_contexts.0.borrow_mut();
4226 let parser = incomplete_parser_contexts
4227 .iter_mut()
4228 .find(|&&mut (pipeline_id, _)| pipeline_id == id);
4229 if let Some(&mut (_, ref mut ctxt)) = parser {
4230 ctxt.process_response(self, cx, fetch_metadata);
4231 }
4232 }
4233
4234 fn handle_fetch_chunk(&self, cx: &mut JSContext, pipeline_id: PipelineId, chunk: Bytes) {
4235 let mut incomplete_parser_contexts = self.incomplete_parser_contexts.0.borrow_mut();
4236 let parser = incomplete_parser_contexts
4237 .iter_mut()
4238 .find(|&&mut (parser_pipeline_id, _)| parser_pipeline_id == pipeline_id);
4239 if let Some(&mut (_, ref mut ctxt)) = parser {
4240 ctxt.process_response_chunk(cx, chunk);
4241 }
4242 }
4243
4244 #[expect(clippy::redundant_clone, reason = "False positive")]
4245 fn handle_fetch_eof(
4246 &self,
4247 cx: &mut JSContext,
4248 id: PipelineId,
4249 eof: Result<(), NetworkError>,
4250 timing: ResourceFetchTiming,
4251 ) {
4252 let idx = self
4253 .incomplete_parser_contexts
4254 .0
4255 .borrow()
4256 .iter()
4257 .position(|&(pipeline_id, _)| pipeline_id == id);
4258
4259 if let Some(idx) = idx {
4260 let (_, context) = self.incomplete_parser_contexts.0.borrow_mut().remove(idx);
4261
4262 if let Some(window_proxy) = context
4264 .get_document()
4265 .and_then(|document| document.browsing_context()) &&
4266 let Some(frame_element) = window_proxy.frame_element()
4267 {
4268 let iframe_ctx = IframeContext::new(
4269 frame_element
4270 .downcast::<HTMLIFrameElement>()
4271 .expect("WindowProxy::frame_element should be an HTMLIFrameElement"),
4272 );
4273
4274 let mut resource_timing = timing.clone();
4276 resource_timing.timing_type = ResourceTimingType::Resource;
4277 submit_timing(
4278 cx,
4279 &frame_element.owner_global(),
4280 &iframe_ctx,
4281 &eof,
4282 &resource_timing,
4283 );
4284 }
4285
4286 context.process_response_eof(cx, eof, timing);
4287 }
4288 }
4289
4290 fn handle_csp_violations(
4291 &self,
4292 cx: &mut JSContext,
4293 pipeline_id: PipelineId,
4294 _request_id: RequestId,
4295 violations: Vec<Violation>,
4296 ) {
4297 let mut incomplete_parser_contexts = self.incomplete_parser_contexts.0.borrow_mut();
4298 let parser = incomplete_parser_contexts
4299 .iter_mut()
4300 .find(|&&mut (parser_pipeline_id, _)| parser_pipeline_id == pipeline_id);
4301 let Some(&mut (_, ref mut ctxt)) = parser else {
4302 return;
4303 };
4304 let pipeline_id = ctxt.parent_info().unwrap_or(pipeline_id);
4306 if let Some(global) = self.documents.borrow().find_global(pipeline_id) {
4307 global.report_csp_violations(cx, violations, None, None);
4308 }
4309 }
4310
4311 fn handle_navigation_redirect(&self, id: PipelineId, metadata: &Metadata) {
4312 assert!(metadata.location_url.is_some());
4316
4317 let mut incomplete_loads = self.incomplete_loads.borrow_mut();
4318 let Some(incomplete_load) = incomplete_loads
4319 .iter_mut()
4320 .find(|incomplete_load| incomplete_load.pipeline_id == id)
4321 else {
4322 return;
4323 };
4324
4325 incomplete_load
4328 .url_list
4329 .push(UrlWithBlobClaim::from_url_without_having_claimed_blob(
4330 metadata.final_url.clone(),
4331 ));
4332
4333 let mut request_builder = incomplete_load.request_builder();
4334 request_builder.referrer = metadata
4335 .referrer
4336 .clone()
4337 .map(Referrer::ReferrerUrl)
4338 .unwrap_or(Referrer::NoReferrer);
4339 request_builder.referrer_policy = metadata.referrer_policy;
4340 request_builder.origin = request_builder
4341 .client
4342 .as_ref()
4343 .expect("Must have a client during redirect")
4344 .origin
4345 .clone();
4346
4347 let headers = metadata
4348 .headers
4349 .as_ref()
4350 .map(|headers| headers.clone().into_inner())
4351 .unwrap_or_default();
4352
4353 let response_init = Some(ResponseInit {
4354 url: metadata.final_url.clone(),
4355 location_url: metadata.location_url.clone(),
4356 headers,
4357 referrer: metadata.referrer.clone(),
4358 status_code: metadata
4359 .status
4360 .try_code()
4361 .map(|code| code.as_u16())
4362 .unwrap_or(200),
4363 });
4364
4365 incomplete_load.controller = FetchController::new(
4366 request_builder.id,
4367 false,
4368 self.resource_threads.core_thread.clone(),
4369 );
4370 NavigationListener::new(request_builder, self.senders.self_sender.clone())
4371 .initiate_fetch(&self.resource_threads.core_thread, response_init);
4372 }
4373
4374 fn start_synchronous_page_load(
4377 &self,
4378 cx: &mut JSContext,
4379 mut incomplete: InProgressLoad,
4380 source_origin: Option<MutableOrigin>,
4381 ) {
4382 let mut context = ParserContext::new(
4383 incomplete.webview_id(),
4384 incomplete.pipeline_id,
4385 incomplete.load_data.url.clone(),
4386 incomplete.load_data.creation_sandboxing_flag_set,
4387 incomplete.parent_info,
4388 incomplete.target_snapshot_params,
4389 source_origin,
4390 );
4391
4392 let mut meta = Metadata::default(incomplete.load_data.url.clone());
4393 meta.set_content_type(Some(&mime::TEXT_HTML));
4394 meta.set_referrer_policy(incomplete.load_data.referrer_policy);
4395
4396 let chunk = match incomplete.load_data.js_eval_result {
4399 Some(ref mut content) => std::mem::take(content),
4400 None => String::new(),
4401 };
4402
4403 let policy_container = incomplete.load_data.policy_container.clone();
4404 let about_base_url = incomplete.load_data.about_base_url.clone();
4405 self.incomplete_loads.borrow_mut().push(incomplete);
4406
4407 context.process_response(self, cx, Ok(FetchMetadata::Unfiltered(meta)));
4408 context.set_policy_container(policy_container);
4409 context.set_about_base_url(about_base_url);
4410 context.process_response_chunk(cx, chunk.into());
4411 context.process_response_eof(
4412 cx,
4413 Ok(()),
4414 ResourceFetchTiming::new(ResourceTimingType::None),
4415 );
4416 }
4417
4418 fn page_load_about_srcdoc(
4420 &self,
4421 cx: &mut JSContext,
4422 mut incomplete: InProgressLoad,
4423 source_origin: Option<MutableOrigin>,
4424 ) {
4425 let url = ServoUrl::parse("about:srcdoc").unwrap();
4426 let mut meta = Metadata::default(url.clone());
4427 meta.set_content_type(Some(&mime::TEXT_HTML));
4428 meta.set_referrer_policy(incomplete.load_data.referrer_policy);
4429
4430 let srcdoc = std::mem::take(&mut incomplete.load_data.srcdoc);
4431 let chunk = srcdoc.into_bytes();
4432
4433 let policy_container = incomplete.load_data.policy_container.clone();
4434 let creation_sandboxing_flag_set = incomplete.load_data.creation_sandboxing_flag_set;
4435
4436 let webview_id = incomplete.webview_id();
4437 let pipeline_id = incomplete.pipeline_id;
4438 let parent_info = incomplete.parent_info;
4439 let about_base_url = incomplete.load_data.about_base_url.clone();
4440 let target_snapshot_params = incomplete.target_snapshot_params;
4441 self.incomplete_loads.borrow_mut().push(incomplete);
4442
4443 let mut context = ParserContext::new(
4444 webview_id,
4445 pipeline_id,
4446 url,
4447 creation_sandboxing_flag_set,
4448 parent_info,
4449 target_snapshot_params,
4450 source_origin,
4451 );
4452 context.process_response(self, cx, Ok(FetchMetadata::Unfiltered(meta)));
4453 context.set_policy_container(policy_container);
4454 context.set_about_base_url(about_base_url);
4455 context.process_response_chunk(cx, Bytes::copy_from_slice(&chunk));
4456 context.process_response_eof(
4457 cx,
4458 Ok(()),
4459 ResourceFetchTiming::new(ResourceTimingType::None),
4460 );
4461 }
4462
4463 #[cfg(feature = "devtools")]
4464 fn handle_css_error_reporting(
4465 &self,
4466 pipeline_id: PipelineId,
4467 filename: String,
4468 line: u32,
4469 column: u32,
4470 msg: String,
4471 ) {
4472 let Some(ref sender) = self.senders.devtools_server_sender else {
4473 return;
4474 };
4475
4476 if let Some(window) = self.documents.borrow().find_window(pipeline_id) &&
4477 window.live_devtools_updates()
4478 {
4479 let css_error = CSSError {
4480 filename,
4481 line,
4482 column,
4483 msg,
4484 };
4485 let message = ScriptToDevtoolsControlMsg::ReportCSSError(pipeline_id, css_error);
4486 sender.send(message).unwrap();
4487 }
4488 }
4489
4490 fn handle_navigate_to(&self, pipeline_id: PipelineId, url: ServoUrl) {
4491 if let Some(document) = self.documents.borrow().find_document(pipeline_id) {
4494 self.senders
4495 .pipeline_to_constellation_sender
4496 .send((
4497 document.webview_id(),
4498 pipeline_id,
4499 ScriptToConstellationMessage::LoadUrl(
4500 LoadData::new_for_new_unrelated_webview(url),
4501 NavigationHistoryBehavior::Push,
4502 TargetSnapshotParams::default(),
4503 ),
4504 ))
4505 .unwrap();
4506 }
4507 }
4508
4509 fn handle_traverse_history(&self, pipeline_id: PipelineId, direction: TraversalDirection) {
4510 if let Some(document) = self.documents.borrow().find_document(pipeline_id) {
4513 let webview_id = document.webview_id();
4514 self.senders
4515 .pipeline_to_constellation_sender
4516 .send((
4517 webview_id,
4518 pipeline_id,
4519 ScriptToConstellationMessage::TraverseHistory(
4520 SessionHistoryTraversalRequest::new(
4521 webview_id,
4522 direction,
4523 HistoryTraversalSource::Script,
4524 ),
4525 ),
4526 ))
4527 .unwrap();
4528 }
4529 }
4530
4531 fn handle_reload(&self, cx: &mut JSContext, pipeline_id: PipelineId) {
4532 let window = self.documents.borrow().find_window(pipeline_id);
4533 if let Some(window) = window {
4534 window.Location(cx).reload_without_origin_check(cx, &window);
4535 }
4536 }
4537
4538 fn handle_paint_metric(
4539 &self,
4540 cx: &mut JSContext,
4541 pipeline_id: PipelineId,
4542 event: PaintMetricEvent,
4543 ) {
4544 match self.documents.borrow().find_document(pipeline_id) {
4545 Some(document) => document.handle_paint_metric(cx, event),
4546 None => {
4547 warn!("Received paint metric ({event:?}) for unknown document: {pipeline_id:?}")
4548 },
4549 }
4550 }
4551
4552 fn handle_media_session_action(
4553 &self,
4554 cx: &mut JSContext,
4555 pipeline_id: PipelineId,
4556 action: MediaSessionActionType,
4557 ) {
4558 if let Some(window) = self.documents.borrow().find_window(pipeline_id) {
4559 let media_session = window.Navigator(cx).MediaSession(cx);
4560 media_session.handle_action(cx, action);
4561 } else {
4562 warn!("No MediaSession for this pipeline ID");
4563 };
4564 }
4565
4566 fn handle_set_network_online_state(&self, online: bool) {
4567 let previous = self.online.replace(online);
4568 if previous == online {
4569 return;
4570 }
4571
4572 for (_, document) in self.documents.borrow().iter() {
4573 document.queue_online_offline_events(online);
4574 }
4575
4576 SharedWorker::set_network_online_state(online);
4577 }
4578
4579 pub(crate) fn enqueue_microtask(cx: &js::context::JSContext, job: Box<dyn MicrotaskRunnable>) {
4580 crate::runtime::job_queue::enqueue(cx, job);
4581 }
4582
4583 pub(crate) fn perform_a_microtask_checkpoint(&self, cx: &mut JSContext) {
4584 if self.can_continue_running_inner() {
4586 let globals = self
4587 .documents
4588 .borrow()
4589 .iter()
4590 .map(|(_id, document)| DomRoot::from_ref(document.window().upcast()))
4591 .collect();
4592
4593 job_queue_microtask_checkpoint(cx, globals)
4594 }
4595 }
4596
4597 fn handle_evaluate_javascript(
4598 &self,
4599 cx: &mut JSContext,
4600 webview_id: WebViewId,
4601 pipeline_id: PipelineId,
4602 evaluation_id: JavaScriptEvaluationId,
4603 script: String,
4604 ) {
4605 let Some(window) = self.documents.borrow().find_window(pipeline_id) else {
4606 let _ = self.senders.pipeline_to_constellation_sender.send((
4607 webview_id,
4608 pipeline_id,
4609 ScriptToConstellationMessage::FinishJavaScriptEvaluation(
4610 evaluation_id,
4611 Err(JavaScriptEvaluationError::WebViewNotReady),
4612 ),
4613 ));
4614 return;
4615 };
4616
4617 let global_scope = window.as_global_scope();
4618 let mut realm = enter_auto_realm(cx, global_scope);
4619 let cx = &mut realm.current_realm();
4620
4621 rooted!(&in(cx) let mut return_value = UndefinedValue());
4622 if let Err(err) = global_scope.evaluate_js_on_global(
4623 cx,
4624 script.into(),
4625 "",
4626 None, Some(return_value.handle_mut()),
4628 ) {
4629 _ = self.senders.pipeline_to_constellation_sender.send((
4630 webview_id,
4631 pipeline_id,
4632 ScriptToConstellationMessage::FinishJavaScriptEvaluation(evaluation_id, Err(err)),
4633 ));
4634 return;
4635 };
4636
4637 let result = jsval_to_webdriver(cx, global_scope, return_value.handle());
4638 let _ = self.senders.pipeline_to_constellation_sender.send((
4639 webview_id,
4640 pipeline_id,
4641 ScriptToConstellationMessage::FinishJavaScriptEvaluation(evaluation_id, result),
4642 ));
4643 }
4644
4645 fn handle_refresh_cursor(&self, pipeline_id: PipelineId) {
4646 let Some(document) = self.documents.borrow().find_document(pipeline_id) else {
4647 return;
4648 };
4649 document.event_handler().handle_refresh_cursor();
4650 }
4651
4652 pub(crate) fn is_servo_privileged(url: ServoUrl) -> bool {
4653 with_script_thread(|script_thread| script_thread.privileged_urls.contains(&url))
4654 }
4655
4656 fn handle_request_screenshot_readiness(
4657 &self,
4658 cx: &mut JSContext,
4659 webview_id: WebViewId,
4660 pipeline_id: PipelineId,
4661 ) {
4662 let Some(window) = self.documents.borrow().find_window(pipeline_id) else {
4663 let _ = self.senders.pipeline_to_constellation_sender.send((
4664 webview_id,
4665 pipeline_id,
4666 ScriptToConstellationMessage::RespondToScreenshotReadinessRequest(
4667 ScreenshotReadinessResponse::NoLongerActive,
4668 ),
4669 ));
4670 return;
4671 };
4672 window.request_screenshot_readiness(cx);
4673 }
4674
4675 fn handle_embedder_control_response(
4676 &self,
4677 cx: &mut JSContext,
4678 id: EmbedderControlId,
4679 response: EmbedderControlResponse,
4680 ) {
4681 let Some(document) = self.documents.borrow().find_document(id.pipeline_id) else {
4682 return;
4683 };
4684 document
4685 .embedder_controls()
4686 .handle_embedder_control_response(cx, id, response);
4687 }
4688
4689 pub(crate) fn handle_update_pinch_zoom_infos(
4690 &self,
4691 cx: &mut JSContext,
4692 pipeline_id: PipelineId,
4693 pinch_zoom_infos: PinchZoomInfos,
4694 ) {
4695 let Some(window) = self.documents.borrow().find_window(pipeline_id) else {
4696 warn!("Visual viewport update for closed pipeline {pipeline_id}.");
4697 return;
4698 };
4699
4700 window.maybe_update_visual_viewport(cx, pinch_zoom_infos);
4701 }
4702
4703 #[cfg(feature = "devtools")]
4704 pub(crate) fn devtools_want_updates_for_node(pipeline: PipelineId, node: &Node) -> bool {
4705 with_script_thread(|script_thread| {
4706 script_thread
4707 .devtools_state
4708 .wants_updates_for_node(pipeline, node)
4709 })
4710 }
4711
4712 pub(crate) fn online() -> bool {
4713 with_script_thread(|script_thread| script_thread.online.get())
4714 }
4715}
4716
4717impl Drop for ScriptThread {
4718 fn drop(&mut self) {
4719 SCRIPT_THREAD_ROOT.with(|root| {
4720 root.set(None);
4721 });
4722 }
4723}
4724
4725fn window_for_replacement(
4727 script_window_proxies: &ScriptWindowProxies,
4728 id: BrowsingContextId,
4729 origin: &MutableOrigin,
4730) -> Option<DomRoot<Window>> {
4731 let browsing_context = obtain_a_browsing_context(script_window_proxies, id, origin);
4734
4735 browsing_context
4740 .and_then(|window_proxy| window_proxy.document())
4741 .filter(|document| {
4742 document.is_initial_about_blank() && document.origin().same_origin_domain(origin)
4743 })
4744 .map(|document| DomRoot::from_ref(document.window()))
4745}
4746
4747fn obtain_a_browsing_context(
4749 script_window_proxies: &ScriptWindowProxies,
4750 id: BrowsingContextId,
4751 destination_origin: &MutableOrigin,
4752) -> Option<DomRoot<WindowProxy>> {
4753 let browsing_context = script_window_proxies.find_window_proxy(id)?;
4755 if browsing_context.parent().is_none() {
4757 return Some(browsing_context);
4758 }
4759 let swap_group = false;
4764 let document = browsing_context.document()?;
4766 let source_origin = document.origin();
4767 if !is_same_site(source_origin.immutable(), destination_origin.immutable()) {
4771 }
4780 if !swap_group {
4785 return Some(browsing_context);
4791 }
4792 Some(browsing_context)
4805}