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