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