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