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