1use std::borrow::ToOwned;
88use std::cell::{Cell, OnceCell, RefCell};
89use std::collections::hash_map::Entry;
90use std::collections::{HashMap, HashSet, VecDeque};
91use std::marker::PhantomData;
92use std::mem::replace;
93use std::rc::{Rc, Weak};
94use std::sync::Arc;
95use std::thread::JoinHandle;
96use std::{process, thread};
97
98use background_hang_monitor_api::{
99 BackgroundHangMonitorControlMsg, BackgroundHangMonitorRegister, HangMonitorAlert,
100};
101use content_security_policy::sandboxing_directive::SandboxingFlagSet;
102use crossbeam_channel::{Receiver, Select, Sender, unbounded};
103use devtools_traits::{
104 ChromeToDevtoolsControlMsg, DevtoolsControlMsg, DevtoolsPageInfo, NavigationState,
105 ScriptToDevtoolsControlMsg,
106};
107use embedder_traits::resources::{self, Resource};
108use embedder_traits::user_contents::{UserContentManagerId, UserContents};
109use embedder_traits::{
110 AnimationState, EmbedderControlId, EmbedderControlResponse, EmbedderProxy, FocusSequenceNumber,
111 GenericEmbedderProxy, InputEvent, InputEventAndId, InputEventOutcome, JSValue,
112 JavaScriptEvaluationError, JavaScriptEvaluationId, KeyboardEvent, MediaSessionActionType,
113 MediaSessionEvent, MediaSessionPlaybackState, MouseButton, MouseButtonAction, MouseButtonEvent,
114 NewWebViewDetails, PaintHitTestResult, Theme, ViewportDetails, WakeLockDelegate, WakeLockType,
115 WebDriverCommandMsg, WebDriverLoadStatus, WebDriverScriptCommand,
116};
117use euclid::Size2D;
118use euclid::default::Size2D as UntypedSize2D;
119use fonts::SystemFontServiceProxy;
120use ipc_channel::IpcError;
121use ipc_channel::router::ROUTER;
122use keyboard_types::{Key, KeyState, Modifiers, NamedKey};
123use layout_api::{LayoutFactory, ScriptThreadFactory};
124use log::{debug, error, info, trace, warn};
125use media::WindowGLContext;
126use net::image_cache::ImageCacheFactoryImpl;
127use net_traits::pub_domains::registered_domain_name;
128use net_traits::{self, AsyncRuntime, ResourceThreads, exit_fetch_thread, start_fetch_thread};
129use paint_api::{
130 PaintMessage, PaintProxy, PinchZoomInfos, PipelineExitSource, SendableFrameTree,
131 WebRenderExternalImageIdManager,
132};
133use profile_traits::mem::ProfilerMsg;
134use profile_traits::{mem, time};
135use rand::rngs::SmallRng;
136use rand::seq::IndexedRandom;
137use rand::{Rng, SeedableRng};
138use rustc_hash::{FxHashMap, FxHashSet};
139use script_traits::{
140 ConstellationInputEvent, DiscardBrowsingContext, DocumentActivity, NewPipelineInfo,
141 ProgressiveWebMetricType, ScriptThreadMessage, UpdatePipelineIdReason,
142};
143use servo_background_hang_monitor::HangMonitorRegister;
144use servo_base::generic_channel::{
145 GenericCallback, GenericSend, GenericSender, RoutedReceiver, SendError,
146};
147use servo_base::id::{
148 BrowsingContextGroupId, BrowsingContextId, CONSTELLATION_PIPELINE_NAMESPACE_ID,
149 FIRST_CONTENT_PIPELINE_NAMESPACE_ID, HistoryStateId, MessagePortId, MessagePortRouterId,
150 PainterId, PipelineId, PipelineNamespace, PipelineNamespaceId, PipelineNamespaceRequest,
151 ScriptEventLoopId, WebViewId,
152};
153use servo_base::{Epoch, generic_channel};
154#[cfg(feature = "bluetooth")]
155use servo_bluetooth_traits::BluetoothRequest;
156use servo_canvas::canvas_paint_thread::CanvasPaintThread;
157use servo_canvas_traits::ConstellationCanvasMsg;
158use servo_canvas_traits::canvas::{CanvasId, CanvasMsg};
159use servo_canvas_traits::webgl::WebGLThreads;
160use servo_config::{opts, pref};
161use servo_constellation_traits::{
162 AuxiliaryWebViewCreationRequest, AuxiliaryWebViewCreationResponse, ConstellationInterest,
163 DocumentState, EmbedderToConstellationMessage, IFrameLoadInfo, IFrameLoadInfoWithData,
164 IFrameSizeMsg, Job, LoadData, LogEntry, MessagePortMsg, NavigationHistoryBehavior,
165 PaintMetricEvent, PortMessageTask, PortTransferInfo, RemoteFocusOperation, SWManagerMsg,
166 SWManagerSenders, ScreenshotReadinessResponse, ScriptToConstellationMessage, ScrollStateUpdate,
167 ServiceWorkerManagerFactory, ServiceWorkerMsg, StructuredSerializedData, TargetSnapshotParams,
168 TraversalDirection, UserContentManagerAction, WindowSizeType,
169};
170use servo_url::{Host, ImmutableOrigin, ServoUrl};
171use storage_traits::StorageThreads;
172use storage_traits::client_storage::ClientStorageThreadMessage;
173use storage_traits::indexeddb::{IndexedDBThreadMsg, SyncOperation};
174use storage_traits::webstorage_thread::{WebStorageThreadMsg, WebStorageType};
175use style::global_style_data::StyleThreadPool;
176#[cfg(feature = "webgpu")]
177use webgpu::canvas_context::WebGpuExternalImageMap;
178#[cfg(feature = "webgpu")]
179use webgpu_traits::{WebGPU, WebGPURequest};
180
181use super::embedder::ConstellationToEmbedderMsg;
182use crate::broadcastchannel::BroadcastChannels;
183use crate::browsingcontext::{
184 AllBrowsingContextsIterator, BrowsingContext, FullyActiveBrowsingContextsIterator,
185 NewBrowsingContextInfo,
186};
187use crate::constellation_webview::ConstellationWebView;
188use crate::event_loop::EventLoop;
189use crate::pipeline::Pipeline;
190use crate::process_manager::ProcessManager;
191use crate::serviceworker::ServiceWorkerUnprivilegedContent;
192use crate::session_history::{NeedsToReload, SessionHistoryChange, SessionHistoryDiff};
193
194struct PendingApprovalNavigation {
195 load_data: LoadData,
196 history_behaviour: NavigationHistoryBehavior,
197 target_snapshot_params: TargetSnapshotParams,
198}
199
200type PendingApprovalNavigations = FxHashMap<PipelineId, PendingApprovalNavigation>;
201
202#[derive(Debug)]
203enum TransferState {
205 Managed(MessagePortRouterId),
208 TransferInProgress(VecDeque<PortMessageTask>),
211 CompletionInProgress(MessagePortRouterId),
214 CompletionFailed(VecDeque<PortMessageTask>),
220 CompletionRequested(MessagePortRouterId, VecDeque<PortMessageTask>),
223}
224
225#[derive(Debug)]
226struct MessagePortInfo {
228 state: TransferState,
230
231 entangled_with: Option<MessagePortId>,
233}
234
235#[cfg(feature = "webgpu")]
236struct WebRenderWGPU {
238 webrender_external_image_id_manager: WebRenderExternalImageIdManager,
240
241 wgpu_image_map: WebGpuExternalImageMap,
243}
244
245#[derive(Clone, Default)]
249struct BrowsingContextGroup {
250 top_level_browsing_context_set: FxHashSet<WebViewId>,
252
253 event_loops: HashMap<Host, Weak<EventLoop>>,
261
262 #[cfg(feature = "webgpu")]
264 webgpus: HashMap<Host, WebGPU>,
265}
266
267pub struct Constellation<STF, SWF> {
281 namespace_receiver: RoutedReceiver<PipelineNamespaceRequest>,
285 pub(crate) namespace_ipc_sender: GenericSender<PipelineNamespaceRequest>,
286
287 event_loops: Vec<Weak<EventLoop>>,
291
292 pub(crate) script_sender: GenericSender<(WebViewId, PipelineId, ScriptToConstellationMessage)>,
295
296 script_receiver:
299 Receiver<Result<(WebViewId, PipelineId, ScriptToConstellationMessage), IpcError>>,
300
301 pub(crate) background_monitor_register: Option<Box<dyn BackgroundHangMonitorRegister>>,
304
305 background_monitor_register_join_handle: Option<JoinHandle<()>>,
307
308 background_monitor_control_sender: Option<GenericSender<BackgroundHangMonitorControlMsg>>,
311
312 pub(crate) background_hang_monitor_sender: GenericSender<HangMonitorAlert>,
315
316 background_hang_monitor_receiver: RoutedReceiver<HangMonitorAlert>,
319
320 pub(crate) layout_factory: Arc<dyn LayoutFactory>,
324
325 embedder_to_constellation_receiver: Receiver<EmbedderToConstellationMessage>,
327
328 pub(crate) embedder_proxy: EmbedderProxy,
332
333 pub(crate) constellation_to_embedder_proxy: GenericEmbedderProxy<ConstellationToEmbedderMsg>,
335
336 pub(crate) paint_proxy: PaintProxy,
339
340 webviews: FxHashMap<WebViewId, ConstellationWebView>,
342
343 pub(crate) public_resource_threads: ResourceThreads,
347
348 pub(crate) private_resource_threads: ResourceThreads,
353
354 pub(crate) public_storage_threads: StorageThreads,
358
359 pub(crate) private_storage_threads: StorageThreads,
364
365 pub(crate) system_font_service: Arc<SystemFontServiceProxy>,
368
369 pub(crate) devtools_sender: Option<Sender<DevtoolsControlMsg>>,
372
373 pub script_to_devtools_callback: OnceCell<Option<GenericCallback<ScriptToDevtoolsControlMsg>>>,
376
377 #[cfg(feature = "bluetooth")]
380 pub(crate) bluetooth_ipc_sender: GenericSender<BluetoothRequest>,
381
382 sw_managers: HashMap<ImmutableOrigin, GenericSender<ServiceWorkerMsg>>,
384
385 swmanager_ipc_sender: GenericSender<SWManagerMsg>,
389
390 swmanager_receiver: RoutedReceiver<SWManagerMsg>,
394
395 pub(crate) time_profiler_chan: time::ProfilerChan,
398
399 pub(crate) mem_profiler_chan: mem::ProfilerChan,
402
403 #[cfg(feature = "webgpu")]
405 webrender_wgpu: WebRenderWGPU,
406
407 message_ports: FxHashMap<MessagePortId, MessagePortInfo>,
409
410 message_port_routers: FxHashMap<MessagePortRouterId, GenericCallback<MessagePortMsg>>,
412
413 broadcast_channels: BroadcastChannels,
415
416 pipeline_interests: FxHashMap<ConstellationInterest, FxHashSet<PipelineId>>,
418
419 pipelines: FxHashMap<PipelineId, Pipeline>,
422
423 browsing_contexts: FxHashMap<BrowsingContextId, BrowsingContext>,
425
426 browsing_context_group_set: FxHashMap<BrowsingContextGroupId, BrowsingContextGroup>,
430
431 browsing_context_group_next_id: u32,
433
434 pending_changes: Vec<SessionHistoryChange>,
440
441 next_pipeline_namespace_id: Cell<PipelineNamespaceId>,
444
445 webdriver_load_status_sender: Option<(GenericSender<WebDriverLoadStatus>, PipelineId)>,
447
448 document_states: FxHashMap<PipelineId, DocumentState>,
450
451 shutting_down: bool,
453
454 handled_warnings: VecDeque<(Option<String>, String)>,
457
458 random_pipeline_closure: Option<(SmallRng, f32)>,
461
462 phantom: PhantomData<(STF, SWF)>,
464
465 pub(crate) webgl_threads: Option<WebGLThreads>,
467
468 pub(crate) webxr_registry: Option<webxr_api::Registry>,
470
471 canvas: OnceCell<(Sender<ConstellationCanvasMsg>, GenericSender<CanvasMsg>)>,
473
474 pending_approval_navigations: PendingApprovalNavigations,
476
477 pressed_mouse_buttons: u16,
480
481 active_keyboard_modifiers: Modifiers,
483
484 hard_fail: bool,
486
487 active_media_session: Option<PipelineId>,
489
490 screen_wake_lock_count: u32,
493
494 wake_lock_provider: Box<dyn WakeLockDelegate>,
496
497 pub(crate) broken_image_icon_data: Vec<u8>,
501
502 pub(crate) process_manager: ProcessManager,
504
505 async_runtime: Box<dyn AsyncRuntime>,
507
508 event_loop_join_handles: Vec<JoinHandle<()>>,
511
512 pub(crate) privileged_urls: Vec<ServoUrl>,
514
515 pub(crate) image_cache_factory: Arc<ImageCacheFactoryImpl>,
519
520 pending_viewport_changes: HashMap<BrowsingContextId, ViewportDetails>,
523
524 screenshot_readiness_requests: Vec<ScreenshotReadinessRequest>,
528
529 pub(crate) user_contents_for_manager_id: FxHashMap<UserContentManagerId, UserContents>,
534}
535
536pub struct InitialConstellationState {
538 pub embedder_proxy: EmbedderProxy,
542
543 pub constellation_to_embedder_proxy: GenericEmbedderProxy<ConstellationToEmbedderMsg>,
545
546 pub paint_proxy: PaintProxy,
548
549 pub devtools_sender: Option<Sender<DevtoolsControlMsg>>,
551
552 #[cfg(feature = "bluetooth")]
554 pub bluetooth_thread: GenericSender<BluetoothRequest>,
555
556 pub system_font_service: Arc<SystemFontServiceProxy>,
558
559 pub public_resource_threads: ResourceThreads,
561
562 pub private_resource_threads: ResourceThreads,
564
565 pub public_storage_threads: StorageThreads,
567
568 pub private_storage_threads: StorageThreads,
570
571 pub time_profiler_chan: time::ProfilerChan,
573
574 pub mem_profiler_chan: mem::ProfilerChan,
576
577 pub webrender_external_image_id_manager: WebRenderExternalImageIdManager,
579
580 pub webgl_threads: Option<WebGLThreads>,
582
583 pub webxr_registry: Option<webxr_api::Registry>,
585
586 #[cfg(feature = "webgpu")]
587 pub wgpu_image_map: WebGpuExternalImageMap,
588
589 pub privileged_urls: Vec<ServoUrl>,
591
592 pub async_runtime: Box<dyn AsyncRuntime>,
594
595 pub wake_lock_provider: Box<dyn WakeLockDelegate>,
597}
598
599#[derive(Clone, Copy, Debug)]
604enum ExitPipelineMode {
605 Normal,
606 Force,
607}
608
609const WARNINGS_BUFFER_SIZE: usize = 32;
611
612impl<STF, SWF> Constellation<STF, SWF>
613where
614 STF: ScriptThreadFactory,
615 SWF: ServiceWorkerManagerFactory,
616{
617 #[servo_tracing::instrument(skip(state, layout_factory))]
619 pub fn start(
620 embedder_to_constellation_receiver: Receiver<EmbedderToConstellationMessage>,
621 state: InitialConstellationState,
622 layout_factory: Arc<dyn LayoutFactory>,
623 random_pipeline_closure_probability: Option<f32>,
624 random_pipeline_closure_seed: Option<usize>,
625 hard_fail: bool,
626 ) {
627 let (swmanager_ipc_sender, swmanager_ipc_receiver) =
629 generic_channel::channel().expect("ipc channel failure");
630
631 thread::Builder::new()
632 .name("Constellation".to_owned())
633 .spawn(move || {
634 let (script_ipc_sender, script_ipc_receiver) =
635 generic_channel::channel().expect("ipc channel failure");
636 let script_receiver = script_ipc_receiver.route_preserving_errors();
637
638 let (namespace_ipc_sender, namespace_ipc_receiver) =
639 generic_channel::channel().expect("ipc channel failure");
640 let namespace_receiver = namespace_ipc_receiver.route_preserving_errors();
641
642 let (background_hang_monitor_ipc_sender, background_hang_monitor_ipc_receiver) =
643 generic_channel::channel().expect("ipc channel failure");
644 let background_hang_monitor_receiver =
645 background_hang_monitor_ipc_receiver.route_preserving_errors();
646
647 let (
651 background_monitor_register,
652 background_monitor_register_join_handle,
653 background_monitor_control_sender
654 ) = if opts::get().multiprocess {
655 (None, None, None)
656 } else {
657 let (
658 background_hang_monitor_control_ipc_sender,
659 background_hang_monitor_control_ipc_receiver,
660 ) = generic_channel::channel().expect("ipc channel failure");
661 let (register, join_handle) = HangMonitorRegister::init(
662 background_hang_monitor_ipc_sender.clone(),
663 background_hang_monitor_control_ipc_receiver,
664 opts::get().background_hang_monitor,
665 );
666 (
667 Some(register),
668 Some(join_handle),
669 Some(background_hang_monitor_control_ipc_sender),
670 )
671 };
672
673 let swmanager_receiver = swmanager_ipc_receiver.route_preserving_errors();
674
675 PipelineNamespace::install(CONSTELLATION_PIPELINE_NAMESPACE_ID);
676
677 #[cfg(feature = "webgpu")]
678 let webrender_wgpu = WebRenderWGPU {
679 webrender_external_image_id_manager: state.webrender_external_image_id_manager,
680 wgpu_image_map: state.wgpu_image_map,
681 };
682
683 let broken_image_icon_data = resources::read_bytes(Resource::BrokenImageIcon);
684
685 let mut constellation: Constellation<STF, SWF> = Constellation {
686 event_loops: Default::default(),
687 namespace_receiver,
688 namespace_ipc_sender,
689 script_sender: script_ipc_sender,
690 background_hang_monitor_sender: background_hang_monitor_ipc_sender,
691 background_hang_monitor_receiver,
692 background_monitor_register,
693 background_monitor_register_join_handle,
694 background_monitor_control_sender,
695 script_receiver,
696 embedder_to_constellation_receiver,
697 layout_factory,
698 embedder_proxy: state.embedder_proxy,
699 constellation_to_embedder_proxy: state.constellation_to_embedder_proxy,
700 paint_proxy: state.paint_proxy,
701 webviews: Default::default(),
702 devtools_sender: state.devtools_sender,
703 script_to_devtools_callback: Default::default(),
704 #[cfg(feature = "bluetooth")]
705 bluetooth_ipc_sender: state.bluetooth_thread,
706 public_resource_threads: state.public_resource_threads,
707 private_resource_threads: state.private_resource_threads,
708 public_storage_threads: state.public_storage_threads,
709 private_storage_threads: state.private_storage_threads,
710 system_font_service: state.system_font_service,
711 sw_managers: Default::default(),
712 swmanager_receiver,
713 swmanager_ipc_sender,
714 browsing_context_group_set: Default::default(),
715 browsing_context_group_next_id: Default::default(),
716 message_ports: Default::default(),
717 message_port_routers: Default::default(),
718 broadcast_channels: Default::default(),
719 pipeline_interests: Default::default(),
720 pipelines: Default::default(),
721 browsing_contexts: Default::default(),
722 pending_changes: vec![],
723 next_pipeline_namespace_id: Cell::new(FIRST_CONTENT_PIPELINE_NAMESPACE_ID),
724 time_profiler_chan: state.time_profiler_chan,
725 mem_profiler_chan: state.mem_profiler_chan.clone(),
726 phantom: PhantomData,
727 webdriver_load_status_sender: None,
728 document_states: Default::default(),
729 #[cfg(feature = "webgpu")]
730 webrender_wgpu,
731 shutting_down: false,
732 handled_warnings: VecDeque::new(),
733 random_pipeline_closure: random_pipeline_closure_probability.map(|probability| {
734 let rng = random_pipeline_closure_seed
735 .map(|seed| SmallRng::seed_from_u64(seed as u64))
736 .unwrap_or_else(SmallRng::from_os_rng);
737 warn!("Randomly closing pipelines using seed {random_pipeline_closure_seed:?}.");
738 (rng, probability)
739 }),
740 webgl_threads: state.webgl_threads,
741 webxr_registry: state.webxr_registry,
742 canvas: OnceCell::new(),
743 pending_approval_navigations: Default::default(),
744 pressed_mouse_buttons: 0,
745 active_keyboard_modifiers: Modifiers::empty(),
746 hard_fail,
747 active_media_session: None,
748 screen_wake_lock_count: 0,
749 wake_lock_provider: state.wake_lock_provider,
750 broken_image_icon_data: broken_image_icon_data.clone(),
751 process_manager: ProcessManager::new(state.mem_profiler_chan),
752 async_runtime: state.async_runtime,
753 event_loop_join_handles: Default::default(),
754 privileged_urls: state.privileged_urls,
755 image_cache_factory: Arc::new(ImageCacheFactoryImpl::new(
756 broken_image_icon_data,
757 )),
758 pending_viewport_changes: Default::default(),
759 screenshot_readiness_requests: Vec::new(),
760 user_contents_for_manager_id: Default::default(),
761 };
762
763 constellation.run();
764 })
765 .expect("Thread spawning failed");
766 }
767
768 fn event_loops(&self) -> Vec<Rc<EventLoop>> {
769 self.event_loops
770 .iter()
771 .filter_map(|weak_event_loop| weak_event_loop.upgrade())
772 .collect()
773 }
774
775 pub(crate) fn add_event_loop(&mut self, event_loop: &Rc<EventLoop>) {
776 self.event_loops.push(Rc::downgrade(event_loop));
777 }
778
779 pub(crate) fn add_event_loop_join_handle(&mut self, join_handle: JoinHandle<()>) {
780 self.event_loop_join_handles.push(join_handle);
781 }
782
783 fn clean_up_finished_script_event_loops(&mut self) {
784 self.event_loop_join_handles
785 .retain(|join_handle| !join_handle.is_finished());
786 self.event_loops
787 .retain(|event_loop| event_loop.upgrade().is_some());
788 }
789
790 fn run(&mut self) {
792 let join_handle = start_fetch_thread();
796
797 while !self.shutting_down || !self.pipelines.is_empty() {
798 self.maybe_close_random_pipeline();
801 self.handle_request();
802 self.clean_up_finished_script_event_loops();
803 }
804 self.handle_shutdown();
805
806 if !opts::get().multiprocess {
807 StyleThreadPool::shutdown();
808 }
809
810 exit_fetch_thread();
812 join_handle
813 .join()
814 .expect("Failed to join on the fetch thread in the constellation");
815
816 debug!("Asking embedding layer to complete shutdown.");
820 self.constellation_to_embedder_proxy
821 .send(ConstellationToEmbedderMsg::ShutdownComplete);
822 }
823
824 fn send_message_to_pipeline(
827 &mut self,
828 pipeline_id: PipelineId,
829 message: ScriptThreadMessage,
830 failure_message: &str,
831 ) -> bool {
832 let result = match self.pipelines.get(&pipeline_id) {
833 Some(pipeline) => pipeline.event_loop.send(message),
834 None => {
835 warn!("{pipeline_id}: {failure_message}");
836 return false;
837 },
838 };
839 if let Err(err) = result {
840 self.handle_send_error(pipeline_id, err);
841 }
842 true
843 }
844
845 pub(crate) fn next_pipeline_namespace_id(&self) -> PipelineNamespaceId {
847 let pipeline_namespace_id = self.next_pipeline_namespace_id.get();
848 self.next_pipeline_namespace_id
849 .set(PipelineNamespaceId(pipeline_namespace_id.0 + 1));
850 pipeline_namespace_id
851 }
852
853 fn next_browsing_context_group_id(&mut self) -> BrowsingContextGroupId {
854 let id = self.browsing_context_group_next_id;
855 self.browsing_context_group_next_id += 1;
856 BrowsingContextGroupId(id)
857 }
858
859 fn get_event_loop(
860 &self,
861 host: &Host,
862 webview_id: &WebViewId,
863 opener: &Option<BrowsingContextId>,
864 ) -> Result<Weak<EventLoop>, &'static str> {
865 let bc_group = match opener {
866 Some(browsing_context_id) => {
867 let opener = self
868 .browsing_contexts
869 .get(browsing_context_id)
870 .ok_or("Opener was closed before the openee started")?;
871 self.browsing_context_group_set
872 .get(&opener.bc_group_id)
873 .ok_or("Opener belongs to an unknown browsing context group")?
874 },
875 None => self
876 .browsing_context_group_set
877 .values()
878 .filter(|bc_group| {
879 bc_group
880 .top_level_browsing_context_set
881 .contains(webview_id)
882 })
883 .last()
884 .ok_or(
885 "Trying to get an event-loop for a top-level belonging to an unknown browsing context group",
886 )?,
887 };
888 bc_group
889 .event_loops
890 .get(host)
891 .ok_or("Trying to get an event-loop from an unknown browsing context group")
892 .cloned()
893 }
894
895 fn set_event_loop(
896 &mut self,
897 event_loop: &Rc<EventLoop>,
898 host: Host,
899 webview_id: WebViewId,
900 opener: Option<BrowsingContextId>,
901 ) {
902 let relevant_top_level = if let Some(opener) = opener {
903 match self.browsing_contexts.get(&opener) {
904 Some(opener) => opener.webview_id,
905 None => {
906 warn!("Setting event-loop for an unknown auxiliary");
907 return;
908 },
909 }
910 } else {
911 webview_id
912 };
913 let maybe_bc_group_id = self
914 .browsing_context_group_set
915 .iter()
916 .filter_map(|(id, bc_group)| {
917 if bc_group
918 .top_level_browsing_context_set
919 .contains(&webview_id)
920 {
921 Some(*id)
922 } else {
923 None
924 }
925 })
926 .last();
927 let Some(bc_group_id) = maybe_bc_group_id else {
928 return warn!("Trying to add an event-loop to an unknown browsing context group");
929 };
930 if let Some(bc_group) = self.browsing_context_group_set.get_mut(&bc_group_id) {
931 if bc_group
932 .event_loops
933 .insert(host.clone(), Rc::downgrade(event_loop))
934 .is_some()
935 {
936 warn!(
937 "Double-setting an event-loop for {:?} at {:?}",
938 host, relevant_top_level
939 );
940 }
941 }
942 }
943
944 fn get_event_loop_for_new_pipeline(
945 &self,
946 load_data: &LoadData,
947 webview_id: WebViewId,
948 opener: Option<BrowsingContextId>,
949 parent_pipeline_id: Option<PipelineId>,
950 registered_domain_name: &Option<Host>,
951 ) -> Option<Rc<EventLoop>> {
952 if load_data
954 .creation_sandboxing_flag_set
955 .contains(SandboxingFlagSet::SANDBOXED_ORIGIN_BROWSING_CONTEXT_FLAG)
956 {
957 return None;
958 }
959
960 if load_data.url.as_str() == "about:blank" || load_data.url.as_str() == "about:srcdoc" {
964 if let Some(parent) =
965 parent_pipeline_id.and_then(|pipeline_id| self.pipelines.get(&pipeline_id))
966 {
967 return Some(parent.event_loop.clone());
968 }
969
970 if let Some(creator) = load_data
971 .creator_pipeline_id
972 .and_then(|pipeline_id| self.pipelines.get(&pipeline_id))
973 {
974 return Some(creator.event_loop.clone());
975 }
976
977 return None;
980 }
981
982 let Some(registered_domain_name) = registered_domain_name else {
983 return None;
984 };
985
986 self.get_event_loop(registered_domain_name, &webview_id, &opener)
987 .ok()?
988 .upgrade()
989 }
990
991 fn get_or_create_event_loop_for_new_pipeline(
992 &mut self,
993 webview_id: WebViewId,
994 opener: Option<BrowsingContextId>,
995 parent_pipeline_id: Option<PipelineId>,
996 load_data: &LoadData,
997 is_private: bool,
998 ) -> Result<Rc<EventLoop>, IpcError> {
999 let registered_domain_name = if load_data
1000 .creation_sandboxing_flag_set
1001 .contains(SandboxingFlagSet::SANDBOXED_ORIGIN_BROWSING_CONTEXT_FLAG)
1002 {
1003 None
1004 } else {
1005 registered_domain_name(&load_data.url)
1006 };
1007
1008 if let Some(event_loop) = self.get_event_loop_for_new_pipeline(
1009 load_data,
1010 webview_id,
1011 opener,
1012 parent_pipeline_id,
1013 ®istered_domain_name,
1014 ) {
1015 return Ok(event_loop);
1016 }
1017
1018 let event_loop = EventLoop::spawn(self, is_private)?;
1019 if let Some(registered_domain_name) = registered_domain_name {
1020 self.set_event_loop(&event_loop, registered_domain_name, webview_id, opener);
1021 }
1022 Ok(event_loop)
1023 }
1024
1025 #[expect(clippy::too_many_arguments)]
1027 fn new_pipeline(
1028 &mut self,
1029 new_pipeline_id: PipelineId,
1030 browsing_context_id: BrowsingContextId,
1031 webview_id: WebViewId,
1032 parent_pipeline_id: Option<PipelineId>,
1033 opener: Option<BrowsingContextId>,
1034 initial_viewport_details: ViewportDetails,
1035 load_data: LoadData,
1040 is_private: bool,
1041 throttled: bool,
1042 target_snapshot_params: TargetSnapshotParams,
1043 ) {
1044 if self.shutting_down {
1045 return;
1046 }
1047
1048 debug!("Creating new pipeline ({new_pipeline_id:?}) in {browsing_context_id}");
1049 let Some(theme) = self
1050 .webviews
1051 .get(&webview_id)
1052 .map(ConstellationWebView::theme)
1053 else {
1054 warn!("Tried to create Pipeline for uknown WebViewId: {webview_id:?}");
1055 return;
1056 };
1057
1058 let event_loop = match self.get_or_create_event_loop_for_new_pipeline(
1059 webview_id,
1060 opener,
1061 parent_pipeline_id,
1062 &load_data,
1063 is_private,
1064 ) {
1065 Ok(event_loop) => event_loop,
1066 Err(error) => return self.handle_send_error(new_pipeline_id, error.into()),
1067 };
1068
1069 let user_content_manager_id = self
1070 .webviews
1071 .get(&webview_id)
1072 .and_then(|webview| webview.user_content_manager_id);
1073
1074 let new_pipeline_info = NewPipelineInfo {
1075 parent_info: parent_pipeline_id,
1076 new_pipeline_id,
1077 browsing_context_id,
1078 webview_id,
1079 opener,
1080 load_data,
1081 viewport_details: initial_viewport_details,
1082 user_content_manager_id,
1083 theme,
1084 target_snapshot_params,
1085 };
1086 let pipeline = match Pipeline::spawn(new_pipeline_info, event_loop, self, throttled) {
1087 Ok(pipeline) => pipeline,
1088 Err(error) => return self.handle_send_error(new_pipeline_id, error),
1089 };
1090
1091 assert!(!self.pipelines.contains_key(&new_pipeline_id));
1092 self.pipelines.insert(new_pipeline_id, pipeline);
1093 }
1094
1095 fn fully_active_descendant_browsing_contexts_iter(
1097 &self,
1098 browsing_context_id: BrowsingContextId,
1099 ) -> FullyActiveBrowsingContextsIterator<'_> {
1100 FullyActiveBrowsingContextsIterator {
1101 stack: vec![browsing_context_id],
1102 pipelines: &self.pipelines,
1103 browsing_contexts: &self.browsing_contexts,
1104 }
1105 }
1106
1107 fn fully_active_browsing_contexts_iter(
1109 &self,
1110 webview_id: WebViewId,
1111 ) -> FullyActiveBrowsingContextsIterator<'_> {
1112 self.fully_active_descendant_browsing_contexts_iter(BrowsingContextId::from(webview_id))
1113 }
1114
1115 fn all_descendant_browsing_contexts_iter(
1117 &self,
1118 browsing_context_id: BrowsingContextId,
1119 ) -> AllBrowsingContextsIterator<'_> {
1120 AllBrowsingContextsIterator {
1121 stack: vec![browsing_context_id],
1122 pipelines: &self.pipelines,
1123 browsing_contexts: &self.browsing_contexts,
1124 }
1125 }
1126
1127 fn ancestor_pipelines_of_browsing_context_iter(
1130 &self,
1131 browsing_context_id: BrowsingContextId,
1132 ) -> impl Iterator<Item = &Pipeline> + '_ {
1133 let mut state: Option<PipelineId> = self
1134 .browsing_contexts
1135 .get(&browsing_context_id)
1136 .and_then(|browsing_context| browsing_context.parent_pipeline_id);
1137 std::iter::from_fn(move || {
1138 if let Some(pipeline_id) = state {
1139 let pipeline = self.pipelines.get(&pipeline_id)?;
1140 let browsing_context = self.browsing_contexts.get(&pipeline.browsing_context_id)?;
1141 state = browsing_context.parent_pipeline_id;
1142 Some(pipeline)
1143 } else {
1144 None
1145 }
1146 })
1147 }
1148
1149 fn ancestor_or_self_pipelines_of_browsing_context_iter(
1152 &self,
1153 browsing_context_id: BrowsingContextId,
1154 ) -> impl Iterator<Item = &Pipeline> + '_ {
1155 let this_pipeline = self
1156 .browsing_contexts
1157 .get(&browsing_context_id)
1158 .map(|browsing_context| browsing_context.pipeline_id)
1159 .and_then(|pipeline_id| self.pipelines.get(&pipeline_id));
1160 this_pipeline
1161 .into_iter()
1162 .chain(self.ancestor_pipelines_of_browsing_context_iter(browsing_context_id))
1163 }
1164
1165 #[expect(clippy::too_many_arguments)]
1167 fn new_browsing_context(
1168 &mut self,
1169 browsing_context_id: BrowsingContextId,
1170 webview_id: WebViewId,
1171 pipeline_id: PipelineId,
1172 parent_pipeline_id: Option<PipelineId>,
1173 viewport_details: ViewportDetails,
1174 is_private: bool,
1175 inherited_secure_context: Option<bool>,
1176 throttled: bool,
1177 ) {
1178 debug!("{browsing_context_id}: Creating new browsing context");
1179 let bc_group_id = match self
1180 .browsing_context_group_set
1181 .iter_mut()
1182 .filter_map(|(id, bc_group)| {
1183 if bc_group
1184 .top_level_browsing_context_set
1185 .contains(&webview_id)
1186 {
1187 Some(id)
1188 } else {
1189 None
1190 }
1191 })
1192 .last()
1193 {
1194 Some(id) => *id,
1195 None => {
1196 warn!("Top-level was unexpectedly removed from its top_level_browsing_context_set");
1197 return;
1198 },
1199 };
1200
1201 let viewport_details = self
1203 .pending_viewport_changes
1204 .remove(&browsing_context_id)
1205 .unwrap_or(viewport_details);
1206 let browsing_context = BrowsingContext::new(
1207 bc_group_id,
1208 browsing_context_id,
1209 webview_id,
1210 pipeline_id,
1211 parent_pipeline_id,
1212 viewport_details,
1213 is_private,
1214 inherited_secure_context,
1215 throttled,
1216 );
1217 self.browsing_contexts
1218 .insert(browsing_context_id, browsing_context);
1219
1220 if let Some(parent_pipeline_id) = parent_pipeline_id {
1222 if let Some(parent) = self.pipelines.get_mut(&parent_pipeline_id) {
1223 parent.add_child(browsing_context_id);
1224 }
1225 }
1226 }
1227
1228 fn add_pending_change(&mut self, change: SessionHistoryChange) {
1229 debug!(
1230 "adding pending session history change with {}",
1231 if change.replace.is_some() {
1232 "replacement"
1233 } else {
1234 "no replacement"
1235 },
1236 );
1237 self.pending_changes.push(change);
1238 }
1239
1240 #[servo_tracing::instrument(skip_all)]
1242 fn handle_request(&mut self) {
1243 #[expect(clippy::large_enum_variant)]
1244 #[derive(Debug)]
1245 enum Request {
1246 PipelineNamespace(PipelineNamespaceRequest),
1247 Script((WebViewId, PipelineId, ScriptToConstellationMessage)),
1248 BackgroundHangMonitor(HangMonitorAlert),
1249 Embedder(EmbedderToConstellationMessage),
1250 FromSWManager(SWManagerMsg),
1251 RemoveProcess(usize),
1252 }
1253 let mut sel = Select::new();
1265 sel.recv(&self.namespace_receiver);
1266 sel.recv(&self.script_receiver);
1267 sel.recv(&self.background_hang_monitor_receiver);
1268 sel.recv(&self.embedder_to_constellation_receiver);
1269 sel.recv(&self.swmanager_receiver);
1270
1271 self.process_manager.register(&mut sel);
1272
1273 let request = {
1274 let oper = {
1275 let _span = profile_traits::trace_span!("handle_request::select").entered();
1276 sel.select()
1277 };
1278 let index = oper.index();
1279
1280 match index {
1281 0 => oper
1282 .recv(&self.namespace_receiver)
1283 .expect("Unexpected script channel panic in constellation")
1284 .map(Request::PipelineNamespace),
1285 1 => oper
1286 .recv(&self.script_receiver)
1287 .expect("Unexpected script channel panic in constellation")
1288 .map(Request::Script),
1289 2 => oper
1290 .recv(&self.background_hang_monitor_receiver)
1291 .expect("Unexpected BHM channel panic in constellation")
1292 .map(Request::BackgroundHangMonitor),
1293 3 => Ok(Request::Embedder(
1294 oper.recv(&self.embedder_to_constellation_receiver)
1295 .expect("Unexpected embedder channel panic in constellation"),
1296 )),
1297 4 => oper
1298 .recv(&self.swmanager_receiver)
1299 .expect("Unexpected SW channel panic in constellation")
1300 .map(Request::FromSWManager),
1301 _ => {
1302 let process_index = index - 5;
1304 let _ = oper.recv(self.process_manager.receiver_at(process_index));
1305 Ok(Request::RemoveProcess(process_index))
1306 },
1307 }
1308 };
1309
1310 let request = match request {
1311 Ok(request) => request,
1312 Err(err) => return error!("Deserialization failed ({}).", err),
1313 };
1314
1315 match request {
1316 Request::PipelineNamespace(message) => {
1317 self.handle_request_for_pipeline_namespace(message)
1318 },
1319 Request::Embedder(message) => self.handle_request_from_embedder(message),
1320 Request::Script(message) => {
1321 self.handle_request_from_script(message);
1322 },
1323 Request::BackgroundHangMonitor(message) => {
1324 self.handle_request_from_background_hang_monitor(message);
1325 },
1326 Request::FromSWManager(message) => {
1327 self.handle_request_from_swmanager(message);
1328 },
1329 Request::RemoveProcess(index) => self.process_manager.remove(index),
1330 }
1331 }
1332
1333 #[servo_tracing::instrument(skip_all)]
1334 fn handle_request_for_pipeline_namespace(&mut self, request: PipelineNamespaceRequest) {
1335 let PipelineNamespaceRequest(sender) = request;
1336 let _ = sender.send(self.next_pipeline_namespace_id());
1337 }
1338
1339 #[servo_tracing::instrument(skip_all)]
1340 fn handle_request_from_background_hang_monitor(&self, message: HangMonitorAlert) {
1341 match message {
1342 HangMonitorAlert::Profile(bytes) => self
1343 .constellation_to_embedder_proxy
1344 .send(ConstellationToEmbedderMsg::ReportProfile(bytes)),
1345 HangMonitorAlert::Hang(hang) => {
1346 warn!("Component hang alert: {:?}", hang);
1349 },
1350 }
1351 }
1352
1353 #[servo_tracing::instrument(skip_all)]
1354 fn handle_request_from_swmanager(&mut self, message: SWManagerMsg) {
1355 match message {
1356 SWManagerMsg::PostMessageToClient => {
1357 },
1360 }
1361 }
1362
1363 #[servo_tracing::instrument(skip_all)]
1364 fn handle_request_from_embedder(&mut self, message: EmbedderToConstellationMessage) {
1365 trace_msg_from_embedder!(message, "{message:?}");
1366 match message {
1367 EmbedderToConstellationMessage::Exit => {
1368 self.handle_exit();
1369 },
1370 EmbedderToConstellationMessage::AllowNavigationResponse(pipeline_id, allowed) => {
1374 let pending = self.pending_approval_navigations.remove(&pipeline_id);
1375
1376 let webview_id = match self.pipelines.get(&pipeline_id) {
1377 Some(pipeline) => pipeline.webview_id,
1378 None => return warn!("{}: Attempted to navigate after closure", pipeline_id),
1379 };
1380
1381 match pending {
1382 Some(pending) => {
1383 if allowed {
1384 self.load_url(
1385 webview_id,
1386 pipeline_id,
1387 pending.load_data,
1388 pending.history_behaviour,
1389 pending.target_snapshot_params,
1390 );
1391 } else {
1392 if let Some((sender, id)) = &self.webdriver_load_status_sender {
1393 if pipeline_id == *id {
1394 let _ = sender.send(WebDriverLoadStatus::NavigationStop);
1395 }
1396 }
1397
1398 let pipeline_is_top_level_pipeline = self
1399 .browsing_contexts
1400 .get(&BrowsingContextId::from(webview_id))
1401 .is_some_and(|ctx| ctx.pipeline_id == pipeline_id);
1402 if !pipeline_is_top_level_pipeline {
1406 self.send_message_to_pipeline(
1407 pipeline_id,
1408 ScriptThreadMessage::StopDelayingLoadEventsMode(pipeline_id),
1409 "Attempted to navigate after closure",
1410 );
1411 }
1412 }
1413 },
1414 None => {
1415 warn!(
1416 "{}: AllowNavigationResponse for unknown request",
1417 pipeline_id
1418 )
1419 },
1420 }
1421 },
1422 EmbedderToConstellationMessage::LoadUrl(webview_id, url_request) => {
1426 let mut load_data = LoadData::new_for_new_unrelated_webview(url_request.url);
1427
1428 if !url_request.headers.is_empty() {
1429 load_data.headers.extend(url_request.headers);
1430 }
1431
1432 let ctx_id = BrowsingContextId::from(webview_id);
1433 let pipeline_id = match self.browsing_contexts.get(&ctx_id) {
1434 Some(ctx) => ctx.pipeline_id,
1435 None => {
1436 return warn!("{}: LoadUrl for unknown browsing context", webview_id);
1437 },
1438 };
1439 self.load_url(
1442 webview_id,
1443 pipeline_id,
1444 load_data,
1445 NavigationHistoryBehavior::Push,
1446 TargetSnapshotParams::default(),
1447 );
1448 },
1449 EmbedderToConstellationMessage::NewWebView(url, new_webview_details) => {
1452 self.handle_new_top_level_browsing_context(url, new_webview_details);
1453 },
1454 EmbedderToConstellationMessage::CloseWebView(webview_id) => {
1456 self.handle_close_top_level_browsing_context(webview_id);
1457 },
1458 EmbedderToConstellationMessage::SendError(webview_id, error) => {
1460 warn!("Constellation got a SendError message from WebView {webview_id:?}: {error}");
1461 let Some(webview_id) = webview_id else {
1462 return;
1463 };
1464 self.handle_panic_in_webview(webview_id, &error, &None);
1465 },
1466 EmbedderToConstellationMessage::FocusWebView(webview_id) => {
1467 self.handle_focus_web_view(webview_id);
1468 },
1469 EmbedderToConstellationMessage::BlurWebView => {
1470 self.constellation_to_embedder_proxy
1471 .send(ConstellationToEmbedderMsg::WebViewBlurred);
1472 },
1473 EmbedderToConstellationMessage::TraverseHistory(
1475 webview_id,
1476 direction,
1477 traversal_id,
1478 ) => {
1479 self.handle_traverse_history_msg(webview_id, direction);
1480 self.constellation_to_embedder_proxy.send(
1481 ConstellationToEmbedderMsg::HistoryTraversalComplete(webview_id, traversal_id),
1482 );
1483 },
1484 EmbedderToConstellationMessage::ChangeViewportDetails(
1485 webview_id,
1486 new_viewport_details,
1487 size_type,
1488 ) => {
1489 self.handle_change_viewport_details_msg(
1490 webview_id,
1491 new_viewport_details,
1492 size_type,
1493 );
1494 },
1495 EmbedderToConstellationMessage::ThemeChange(webview_id, theme) => {
1496 self.handle_theme_change(webview_id, theme);
1497 },
1498 EmbedderToConstellationMessage::TickAnimation(webview_ids) => {
1499 self.handle_tick_animation(webview_ids)
1500 },
1501 EmbedderToConstellationMessage::NoLongerWaitingOnAsynchronousImageUpdates(
1502 pipeline_ids,
1503 ) => self.handle_no_longer_waiting_on_asynchronous_image_updates(pipeline_ids),
1504 EmbedderToConstellationMessage::WebDriverCommand(command) => {
1505 self.handle_webdriver_msg(command);
1506 },
1507 EmbedderToConstellationMessage::Reload(webview_id) => {
1508 self.handle_reload_msg(webview_id);
1509 },
1510 EmbedderToConstellationMessage::LogEntry(event_loop_id, thread_name, entry) => {
1511 self.handle_log_entry(event_loop_id, thread_name, entry);
1512 },
1513 EmbedderToConstellationMessage::ForwardInputEvent(webview_id, event, hit_test) => {
1514 self.forward_input_event(webview_id, event, hit_test);
1515 },
1516 EmbedderToConstellationMessage::RefreshCursor(pipeline_id) => {
1517 self.handle_refresh_cursor(pipeline_id)
1518 },
1519 EmbedderToConstellationMessage::ToggleProfiler(rate, max_duration) => {
1520 self.send_message_to_all_background_hang_monitors(
1521 BackgroundHangMonitorControlMsg::ToggleSampler(rate, max_duration),
1522 );
1523 },
1524 EmbedderToConstellationMessage::ExitFullScreen(webview_id) => {
1525 self.handle_exit_fullscreen_msg(webview_id);
1526 },
1527 EmbedderToConstellationMessage::MediaSessionAction(action) => {
1528 self.handle_media_session_action_msg(action);
1529 },
1530 EmbedderToConstellationMessage::SetWebViewThrottled(webview_id, throttled) => {
1531 self.set_webview_throttled(webview_id, throttled);
1532 },
1533 EmbedderToConstellationMessage::SetScrollStates(pipeline_id, scroll_states) => {
1534 self.handle_set_scroll_states(pipeline_id, scroll_states)
1535 },
1536 EmbedderToConstellationMessage::PaintMetric(pipeline_id, paint_metric_event) => {
1537 self.handle_paint_metric(pipeline_id, paint_metric_event);
1538 },
1539 EmbedderToConstellationMessage::EvaluateJavaScript(
1540 webview_id,
1541 evaluation_id,
1542 script,
1543 ) => {
1544 self.handle_evaluate_javascript(webview_id, evaluation_id, script);
1545 },
1546 EmbedderToConstellationMessage::CreateMemoryReport(sender) => {
1547 self.mem_profiler_chan.send(ProfilerMsg::Report(sender));
1548 },
1549 EmbedderToConstellationMessage::SendImageKeysForPipeline(pipeline_id, image_keys) => {
1550 if let Some(pipeline) = self.pipelines.get(&pipeline_id) {
1551 if pipeline
1552 .event_loop
1553 .send(ScriptThreadMessage::SendImageKeysBatch(
1554 pipeline_id,
1555 image_keys,
1556 ))
1557 .is_err()
1558 {
1559 warn!("Could not send image keys to pipeline {:?}", pipeline_id);
1560 }
1561 } else {
1562 warn!(
1563 "Keys were generated for a pipeline ({:?}) that was
1564 closed before the request could be fulfilled.",
1565 pipeline_id
1566 )
1567 }
1568 },
1569 EmbedderToConstellationMessage::PreferencesUpdated(updates) => {
1570 let event_loops = self
1571 .pipelines
1572 .values()
1573 .map(|pipeline| pipeline.event_loop.clone());
1574 for event_loop in event_loops {
1575 let _ = event_loop.send(ScriptThreadMessage::PreferencesUpdated(
1576 updates
1577 .iter()
1578 .map(|(name, value)| (String::from(*name), value.clone()))
1579 .collect(),
1580 ));
1581 }
1582 },
1583 EmbedderToConstellationMessage::RequestScreenshotReadiness(webview_id) => {
1584 self.handle_request_screenshot_readiness(webview_id)
1585 },
1586 EmbedderToConstellationMessage::EmbedderControlResponse(id, response) => {
1587 self.handle_embedder_control_response(id, response);
1588 },
1589 EmbedderToConstellationMessage::UserContentManagerAction(
1590 user_content_manager_id,
1591 action,
1592 ) => {
1593 self.handle_user_content_manager_action(user_content_manager_id, action);
1594 },
1595 EmbedderToConstellationMessage::UpdatePinchZoomInfos(pipeline_id, pinch_zoom) => {
1596 self.handle_update_pinch_zoom_infos(pipeline_id, pinch_zoom);
1597 },
1598 EmbedderToConstellationMessage::SetAccessibilityActive(webview_id, active) => {
1599 self.set_accessibility_active(webview_id, active);
1600 },
1601 }
1602 }
1603
1604 fn mutate_user_contents_for_manager_id_and_notify_script_threads(
1605 &mut self,
1606 user_content_manager_id: UserContentManagerId,
1607 callback: impl FnOnce(&mut UserContents),
1608 ) {
1609 let event_loops = self.event_loops();
1610 let user_contents = self
1611 .user_contents_for_manager_id
1612 .entry(user_content_manager_id)
1613 .or_default();
1614
1615 callback(user_contents);
1616
1617 for event_loop in event_loops {
1618 let _ = event_loop.send(ScriptThreadMessage::SetUserContents(
1619 user_content_manager_id,
1620 user_contents.clone(),
1621 ));
1622 }
1623 }
1624
1625 fn handle_user_content_manager_action(
1626 &mut self,
1627 user_content_manager_id: UserContentManagerId,
1628 action: UserContentManagerAction,
1629 ) {
1630 match action {
1631 UserContentManagerAction::AddUserScript(user_script) => {
1632 self.mutate_user_contents_for_manager_id_and_notify_script_threads(
1633 user_content_manager_id,
1634 |user_contents| {
1635 user_contents.scripts.push(user_script);
1636 },
1637 );
1638 },
1639 UserContentManagerAction::RemoveUserScript(user_script_id) => {
1640 self.mutate_user_contents_for_manager_id_and_notify_script_threads(
1641 user_content_manager_id,
1642 |user_contents| {
1643 user_contents
1644 .scripts
1645 .retain(|user_script| user_script.id() != user_script_id);
1646 },
1647 );
1648 },
1649 UserContentManagerAction::AddUserStyleSheet(user_stylesheet) => {
1650 self.mutate_user_contents_for_manager_id_and_notify_script_threads(
1651 user_content_manager_id,
1652 |user_contents| {
1653 user_contents.stylesheets.push(user_stylesheet);
1654 },
1655 );
1656 },
1657 UserContentManagerAction::RemoveUserStyleSheet(user_stylesheet_id) => {
1658 self.mutate_user_contents_for_manager_id_and_notify_script_threads(
1659 user_content_manager_id,
1660 |user_contents| {
1661 user_contents
1662 .stylesheets
1663 .retain(|user_stylesheet| user_stylesheet.id() != user_stylesheet_id);
1664 },
1665 );
1666 },
1667 UserContentManagerAction::DestroyUserContentManager => {
1668 self.user_contents_for_manager_id
1669 .remove(&user_content_manager_id);
1670
1671 for event_loop in self.event_loops() {
1672 let _ = event_loop.send(ScriptThreadMessage::DestroyUserContentManager(
1673 user_content_manager_id,
1674 ));
1675 }
1676 },
1677 }
1678 }
1679
1680 fn send_message_to_all_background_hang_monitors(
1681 &self,
1682 message: BackgroundHangMonitorControlMsg,
1683 ) {
1684 if let Some(background_monitor_control_sender) = &self.background_monitor_control_sender {
1685 if let Err(error) = background_monitor_control_sender.send(message.clone()) {
1686 error!("Could not send message ({message:?}) to BHM: {error}");
1687 }
1688 }
1689 for event_loop in self.event_loops() {
1690 event_loop.send_message_to_background_hang_monitor(&message);
1691 }
1692 }
1693
1694 #[servo_tracing::instrument(skip_all)]
1695 fn handle_evaluate_javascript(
1696 &mut self,
1697 webview_id: WebViewId,
1698 evaluation_id: JavaScriptEvaluationId,
1699 script: String,
1700 ) {
1701 let browsing_context_id = BrowsingContextId::from(webview_id);
1702 let Some(pipeline) = self
1703 .browsing_contexts
1704 .get(&browsing_context_id)
1705 .and_then(|browsing_context| self.pipelines.get(&browsing_context.pipeline_id))
1706 else {
1707 self.handle_finish_javascript_evaluation(
1708 evaluation_id,
1709 Err(JavaScriptEvaluationError::InternalError),
1710 );
1711 return;
1712 };
1713
1714 if pipeline
1715 .event_loop
1716 .send(ScriptThreadMessage::EvaluateJavaScript(
1717 webview_id,
1718 pipeline.id,
1719 evaluation_id,
1720 script,
1721 ))
1722 .is_err()
1723 {
1724 self.handle_finish_javascript_evaluation(
1725 evaluation_id,
1726 Err(JavaScriptEvaluationError::InternalError),
1727 );
1728 }
1729 }
1730
1731 #[servo_tracing::instrument(skip_all)]
1732 fn handle_request_from_script(
1733 &mut self,
1734 message: (WebViewId, PipelineId, ScriptToConstellationMessage),
1735 ) {
1736 let (webview_id, source_pipeline_id, content) = message;
1737 trace_script_msg!(content, "{source_pipeline_id}: {content:?}");
1738
1739 match content {
1740 ScriptToConstellationMessage::CompleteMessagePortTransfer(router_id, ports) => {
1741 self.handle_complete_message_port_transfer(router_id, ports);
1742 },
1743 ScriptToConstellationMessage::MessagePortTransferResult(
1744 router_id,
1745 succeeded,
1746 failed,
1747 ) => {
1748 self.handle_message_port_transfer_completed(router_id, succeeded);
1749 self.handle_message_port_transfer_failed(failed);
1750 },
1751 ScriptToConstellationMessage::RerouteMessagePort(port_id, task) => {
1752 self.handle_reroute_messageport(port_id, task);
1753 },
1754 ScriptToConstellationMessage::MessagePortShipped(port_id) => {
1755 self.handle_messageport_shipped(port_id);
1756 },
1757 ScriptToConstellationMessage::NewMessagePortRouter(router_id, callback) => {
1758 self.handle_new_messageport_router(router_id, callback);
1759 },
1760 ScriptToConstellationMessage::RemoveMessagePortRouter(router_id) => {
1761 self.handle_remove_messageport_router(router_id);
1762 },
1763 ScriptToConstellationMessage::NewMessagePort(router_id, port_id) => {
1764 self.handle_new_messageport(router_id, port_id);
1765 },
1766 ScriptToConstellationMessage::EntanglePorts(port1, port2) => {
1767 self.handle_entangle_messageports(port1, port2);
1768 },
1769 ScriptToConstellationMessage::DisentanglePorts(port1, port2) => {
1770 self.handle_disentangle_messageports(port1, port2);
1771 },
1772 ScriptToConstellationMessage::NewBroadcastChannelRouter(
1773 router_id,
1774 response_sender,
1775 origin,
1776 ) => {
1777 if self
1778 .check_origin_against_pipeline(&source_pipeline_id, &origin)
1779 .is_err()
1780 {
1781 return warn!("Attempt to add broadcast router from an unexpected origin.");
1782 }
1783 self.broadcast_channels
1784 .new_broadcast_channel_router(router_id, response_sender);
1785 },
1786 ScriptToConstellationMessage::NewBroadcastChannelNameInRouter(
1787 router_id,
1788 channel_name,
1789 origin,
1790 ) => {
1791 if self
1792 .check_origin_against_pipeline(&source_pipeline_id, &origin)
1793 .is_err()
1794 {
1795 return warn!("Attempt to add channel name from an unexpected origin.");
1796 }
1797 self.broadcast_channels
1798 .new_broadcast_channel_name_in_router(router_id, channel_name, origin);
1799 },
1800 ScriptToConstellationMessage::RemoveBroadcastChannelNameInRouter(
1801 router_id,
1802 channel_name,
1803 origin,
1804 ) => {
1805 if self
1806 .check_origin_against_pipeline(&source_pipeline_id, &origin)
1807 .is_err()
1808 {
1809 return warn!("Attempt to remove channel name from an unexpected origin.");
1810 }
1811 self.broadcast_channels
1812 .remove_broadcast_channel_name_in_router(router_id, channel_name, origin);
1813 },
1814 ScriptToConstellationMessage::RemoveBroadcastChannelRouter(router_id, origin) => {
1815 if self
1816 .check_origin_against_pipeline(&source_pipeline_id, &origin)
1817 .is_err()
1818 {
1819 return warn!("Attempt to remove broadcast router from an unexpected origin.");
1820 }
1821 self.broadcast_channels
1822 .remove_broadcast_channel_router(router_id);
1823 },
1824 ScriptToConstellationMessage::ScheduleBroadcast(router_id, message) => {
1825 if self
1826 .check_origin_against_pipeline(&source_pipeline_id, &message.origin)
1827 .is_err()
1828 {
1829 return warn!(
1830 "Attempt to schedule broadcast from an origin not matching the origin of the msg."
1831 );
1832 }
1833 self.broadcast_channels
1834 .schedule_broadcast(router_id, message);
1835 },
1836 ScriptToConstellationMessage::PipelineExited => {
1837 self.handle_pipeline_exited(source_pipeline_id);
1838 },
1839 ScriptToConstellationMessage::DiscardDocument => {
1840 self.handle_discard_document(webview_id, source_pipeline_id);
1841 },
1842 ScriptToConstellationMessage::DiscardTopLevelBrowsingContext => {
1843 self.handle_close_top_level_browsing_context(webview_id);
1844 },
1845 ScriptToConstellationMessage::ScriptLoadedURLInIFrame(load_info) => {
1846 self.handle_script_loaded_url_in_iframe_msg(load_info);
1847 },
1848 ScriptToConstellationMessage::ScriptNewIFrame(load_info) => {
1849 self.handle_script_new_iframe(load_info);
1850 },
1851 ScriptToConstellationMessage::CreateAuxiliaryWebView(load_info) => {
1852 self.handle_script_new_auxiliary(load_info);
1853 },
1854 ScriptToConstellationMessage::ChangeRunningAnimationsState(animation_state) => {
1855 self.handle_change_running_animations_state(source_pipeline_id, animation_state)
1856 },
1857 ScriptToConstellationMessage::LoadUrl(
1859 load_data,
1860 history_handling,
1861 target_snapshot_params,
1862 ) => {
1863 self.schedule_navigation(
1864 webview_id,
1865 source_pipeline_id,
1866 load_data,
1867 history_handling,
1868 target_snapshot_params,
1869 );
1870 },
1871 ScriptToConstellationMessage::AbortLoadUrl => {
1872 self.handle_abort_load_url_msg(source_pipeline_id);
1873 },
1874 ScriptToConstellationMessage::LoadComplete => {
1876 self.handle_load_complete_msg(webview_id, source_pipeline_id)
1877 },
1878 ScriptToConstellationMessage::NavigatedToFragment(new_url, replacement_enabled) => {
1880 self.handle_navigated_to_fragment(source_pipeline_id, new_url, replacement_enabled);
1881 },
1882 ScriptToConstellationMessage::TraverseHistory(direction) => {
1884 self.handle_traverse_history_msg(webview_id, direction);
1885 },
1886 ScriptToConstellationMessage::PushHistoryState(history_state_id, url) => {
1888 self.handle_push_history_state_msg(source_pipeline_id, history_state_id, url);
1889 },
1890 ScriptToConstellationMessage::ReplaceHistoryState(history_state_id, url) => {
1891 self.handle_replace_history_state_msg(source_pipeline_id, history_state_id, url);
1892 },
1893 ScriptToConstellationMessage::JointSessionHistoryLength(response_sender) => {
1895 self.handle_joint_session_history_length(webview_id, response_sender);
1896 },
1897 ScriptToConstellationMessage::ActivateDocument => {
1899 self.handle_activate_document_msg(source_pipeline_id);
1900 },
1901 ScriptToConstellationMessage::SetFinalUrl(final_url) => {
1903 if let Some(ref mut pipeline) = self.pipelines.get_mut(&source_pipeline_id) {
1905 pipeline.url = final_url;
1906 } else {
1907 warn!("constellation got set final url message for dead pipeline");
1908 }
1909 },
1910 ScriptToConstellationMessage::PostMessage {
1911 target: browsing_context_id,
1912 source: source_pipeline_id,
1913 target_origin: origin,
1914 source_origin,
1915 data,
1916 } => {
1917 self.handle_post_message_msg(
1918 browsing_context_id,
1919 source_pipeline_id,
1920 origin,
1921 source_origin,
1922 data,
1923 );
1924 },
1925 ScriptToConstellationMessage::FocusAncestorBrowsingContextsForFocusingSteps(
1926 focused_child_browsing_context_id,
1927 sequence,
1928 ) => {
1929 self.handle_focus_ancestor_browsing_contexts_for_focusing_steps(
1930 source_pipeline_id,
1931 focused_child_browsing_context_id,
1932 sequence,
1933 );
1934 },
1935 ScriptToConstellationMessage::FocusRemoteBrowsingContext(
1936 focused_browsing_context_id,
1937 remote_focus_operation,
1938 ) => {
1939 self.handle_focus_remote_browsing_context(
1940 focused_browsing_context_id,
1941 remote_focus_operation,
1942 );
1943 },
1944 ScriptToConstellationMessage::SetThrottledComplete(throttled) => {
1945 self.handle_set_throttled_complete(source_pipeline_id, throttled);
1946 },
1947 ScriptToConstellationMessage::RemoveIFrame(browsing_context_id, response_sender) => {
1948 let removed_pipeline_ids = self.handle_remove_iframe_msg(browsing_context_id);
1949 if let Err(e) = response_sender.send(removed_pipeline_ids) {
1950 warn!("Error replying to remove iframe ({})", e);
1951 }
1952 },
1953 ScriptToConstellationMessage::CreateCanvasPaintThread(size, response_sender) => {
1954 self.handle_create_canvas_paint_thread_msg(size, response_sender)
1955 },
1956 ScriptToConstellationMessage::SetDocumentState(state) => {
1957 self.document_states.insert(source_pipeline_id, state);
1958 },
1959 ScriptToConstellationMessage::LogEntry(event_loop_id, thread_name, entry) => {
1960 self.handle_log_entry(event_loop_id, thread_name, entry);
1961 },
1962 ScriptToConstellationMessage::GetBrowsingContextInfo(pipeline_id, response_sender) => {
1963 let result = self
1964 .pipelines
1965 .get(&pipeline_id)
1966 .and_then(|pipeline| self.browsing_contexts.get(&pipeline.browsing_context_id))
1967 .map(|ctx| (ctx.id, ctx.parent_pipeline_id));
1968 if let Err(e) = response_sender.send(result) {
1969 warn!(
1970 "Sending reply to get browsing context info failed ({:?}).",
1971 e
1972 );
1973 }
1974 },
1975 ScriptToConstellationMessage::GetTopForBrowsingContext(
1976 browsing_context_id,
1977 response_sender,
1978 ) => {
1979 let result = self
1980 .browsing_contexts
1981 .get(&browsing_context_id)
1982 .map(|bc| bc.webview_id);
1983 if let Err(e) = response_sender.send(result) {
1984 warn!(
1985 "Sending reply to get top for browsing context info failed ({:?}).",
1986 e
1987 );
1988 }
1989 },
1990 ScriptToConstellationMessage::GetChildBrowsingContextId(
1991 browsing_context_id,
1992 index,
1993 response_sender,
1994 ) => {
1995 let result = self
1996 .browsing_contexts
1997 .get(&browsing_context_id)
1998 .and_then(|bc| self.pipelines.get(&bc.pipeline_id))
1999 .and_then(|pipeline| pipeline.children.get(index))
2000 .copied();
2001 if let Err(e) = response_sender.send(result) {
2002 warn!(
2003 "Sending reply to get child browsing context ID failed ({:?}).",
2004 e
2005 );
2006 }
2007 },
2008 ScriptToConstellationMessage::GetDocumentOrigin(pipeline_id, response_sender) => {
2009 self.send_message_to_pipeline(
2010 pipeline_id,
2011 ScriptThreadMessage::GetDocumentOrigin(pipeline_id, response_sender),
2012 "Document origin retrieval after closure",
2013 );
2014 },
2015 ScriptToConstellationMessage::ScheduleJob(job) => {
2016 self.handle_schedule_serviceworker_job(source_pipeline_id, job);
2017 },
2018 ScriptToConstellationMessage::ForwardDOMMessage(msg_vec, scope_url) => {
2019 if let Some(mgr) = self.sw_managers.get(&scope_url.origin()) {
2020 let _ = mgr.send(ServiceWorkerMsg::ForwardDOMMessage(msg_vec, scope_url));
2021 } else {
2022 warn!("Unable to forward DOMMessage for postMessage call");
2023 }
2024 },
2025 ScriptToConstellationMessage::RegisterInterest(interest) => {
2026 self.pipeline_interests
2027 .entry(interest)
2028 .or_default()
2029 .insert(source_pipeline_id);
2030 },
2031 ScriptToConstellationMessage::UnregisterInterest(interest) => {
2032 if let Some(set) = self.pipeline_interests.get_mut(&interest) {
2033 set.remove(&source_pipeline_id);
2034 if set.is_empty() {
2035 self.pipeline_interests.remove(&interest);
2036 }
2037 }
2038 },
2039 ScriptToConstellationMessage::BroadcastStorageEvent(
2040 storage,
2041 url,
2042 key,
2043 old_value,
2044 new_value,
2045 ) => {
2046 self.handle_broadcast_storage_event(
2047 source_pipeline_id,
2048 storage,
2049 url,
2050 key,
2051 old_value,
2052 new_value,
2053 );
2054 },
2055 ScriptToConstellationMessage::MediaSessionEvent(pipeline_id, event) => {
2056 if self.active_media_session.is_some() {
2063 if let MediaSessionEvent::PlaybackStateChange(ref state) = event {
2064 if !matches!(
2065 state,
2066 MediaSessionPlaybackState::Playing | MediaSessionPlaybackState::Paused
2067 ) {
2068 return;
2069 }
2070 };
2071 }
2072 self.active_media_session = Some(pipeline_id);
2073 self.constellation_to_embedder_proxy.send(
2074 ConstellationToEmbedderMsg::MediaSessionEvent(webview_id, event),
2075 );
2076 },
2077 #[cfg(feature = "webgpu")]
2078 ScriptToConstellationMessage::RequestAdapter(response_sender, options, ids) => self
2079 .handle_wgpu_request(
2080 source_pipeline_id,
2081 BrowsingContextId::from(webview_id),
2082 ScriptToConstellationMessage::RequestAdapter(response_sender, options, ids),
2083 ),
2084 #[cfg(feature = "webgpu")]
2085 ScriptToConstellationMessage::GetWebGPUChan(response_sender) => self
2086 .handle_wgpu_request(
2087 source_pipeline_id,
2088 BrowsingContextId::from(webview_id),
2089 ScriptToConstellationMessage::GetWebGPUChan(response_sender),
2090 ),
2091 ScriptToConstellationMessage::TitleChanged(pipeline, title) => {
2092 if let Some(pipeline) = self.pipelines.get_mut(&pipeline) {
2093 pipeline.title = title;
2094 }
2095 },
2096 ScriptToConstellationMessage::IFrameSizes(iframe_sizes) => {
2097 self.handle_iframe_size_msg(iframe_sizes)
2098 },
2099 ScriptToConstellationMessage::ReportMemory(sender) => {
2100 self.mem_profiler_chan
2102 .send(mem::ProfilerMsg::Report(sender));
2103 },
2104 ScriptToConstellationMessage::FinishJavaScriptEvaluation(evaluation_id, result) => {
2105 self.handle_finish_javascript_evaluation(evaluation_id, result)
2106 },
2107 ScriptToConstellationMessage::ForwardKeyboardScroll(pipeline_id, scroll) => {
2108 if let Some(pipeline) = self.pipelines.get(&pipeline_id) {
2109 if let Err(error) =
2110 pipeline
2111 .event_loop
2112 .send(ScriptThreadMessage::ForwardKeyboardScroll(
2113 pipeline_id,
2114 scroll,
2115 ))
2116 {
2117 warn!("Could not forward {scroll:?} to {pipeline_id}: {error:?}");
2118 }
2119 }
2120 },
2121 ScriptToConstellationMessage::RespondToScreenshotReadinessRequest(response) => {
2122 self.handle_screenshot_readiness_response(source_pipeline_id, response);
2123 },
2124 ScriptToConstellationMessage::TriggerGarbageCollection => {
2125 for event_loop in self.event_loops() {
2126 let _ = event_loop.send(ScriptThreadMessage::TriggerGarbageCollection);
2127 }
2128 },
2129 ScriptToConstellationMessage::AcquireWakeLock(type_) => match type_ {
2130 WakeLockType::Screen => {
2131 self.screen_wake_lock_count += 1;
2132 if self.screen_wake_lock_count == 1 {
2133 if let Err(e) = self.wake_lock_provider.acquire(type_) {
2134 warn!("Failed to acquire screen wake lock: {e}");
2135 }
2136 }
2137 },
2138 },
2139 ScriptToConstellationMessage::ReleaseWakeLock(type_) => match type_ {
2140 WakeLockType::Screen => {
2141 self.screen_wake_lock_count = self.screen_wake_lock_count.saturating_sub(1);
2142 if self.screen_wake_lock_count == 0 {
2143 if let Err(e) = self.wake_lock_provider.release(type_) {
2144 warn!("Failed to release screen wake lock: {e}");
2145 }
2146 }
2147 },
2148 },
2149 }
2150 }
2151
2152 fn check_origin_against_pipeline(
2156 &self,
2157 pipeline_id: &PipelineId,
2158 origin: &ImmutableOrigin,
2159 ) -> Result<(), ()> {
2160 let pipeline_origin = match self.pipelines.get(pipeline_id) {
2161 Some(pipeline) => pipeline.load_data.url.origin(),
2162 None => {
2163 warn!("Received message from closed or unknown pipeline.");
2164 return Err(());
2165 },
2166 };
2167 if &pipeline_origin == origin {
2168 return Ok(());
2169 }
2170 Err(())
2171 }
2172
2173 #[servo_tracing::instrument(skip_all)]
2174 #[cfg(feature = "webgpu")]
2175 fn handle_wgpu_request(
2176 &mut self,
2177 source_pipeline_id: PipelineId,
2178 browsing_context_id: BrowsingContextId,
2179 request: ScriptToConstellationMessage,
2180 ) {
2181 use webgpu::start_webgpu_thread;
2182
2183 let browsing_context_group_id = match self.browsing_contexts.get(&browsing_context_id) {
2184 Some(bc) => &bc.bc_group_id,
2185 None => return warn!("Browsing context not found"),
2186 };
2187 let Some(source_pipeline) = self.pipelines.get(&source_pipeline_id) else {
2188 return warn!("{source_pipeline_id}: ScriptMsg from closed pipeline");
2189 };
2190 let Some(host) = registered_domain_name(&source_pipeline.url) else {
2191 return warn!("Invalid host url");
2192 };
2193 let browsing_context_group = if let Some(bcg) = self
2194 .browsing_context_group_set
2195 .get_mut(browsing_context_group_id)
2196 {
2197 bcg
2198 } else {
2199 return warn!("Browsing context group not found");
2200 };
2201 let webgpu_chan = match browsing_context_group.webgpus.entry(host) {
2202 Entry::Vacant(v) => start_webgpu_thread(
2203 self.paint_proxy.cross_process_paint_api.clone(),
2204 self.webrender_wgpu
2205 .webrender_external_image_id_manager
2206 .clone(),
2207 self.webrender_wgpu.wgpu_image_map.clone(),
2208 )
2209 .map(|webgpu| {
2210 let msg = ScriptThreadMessage::SetWebGPUPort(webgpu.1);
2211 if let Err(e) = source_pipeline.event_loop.send(msg) {
2212 warn!(
2213 "{}: Failed to send SetWebGPUPort to pipeline ({:?})",
2214 source_pipeline_id, e
2215 );
2216 }
2217 v.insert(webgpu.0).clone()
2218 }),
2219 Entry::Occupied(o) => Some(o.get().clone()),
2220 };
2221 match request {
2222 ScriptToConstellationMessage::RequestAdapter(response_sender, options, adapter_id) => {
2223 match webgpu_chan {
2224 None => {
2225 if let Err(e) = response_sender.send(None) {
2226 warn!("Failed to send request adapter message: {}", e)
2227 }
2228 },
2229 Some(webgpu_chan) => {
2230 let adapter_request = WebGPURequest::RequestAdapter {
2231 sender: response_sender,
2232 options,
2233 adapter_id,
2234 };
2235 if webgpu_chan.0.send(adapter_request).is_err() {
2236 warn!("Failed to send request adapter message on WebGPU channel");
2237 }
2238 },
2239 }
2240 },
2241 ScriptToConstellationMessage::GetWebGPUChan(response_sender) => {
2242 if response_sender.send(webgpu_chan).is_err() {
2243 warn!(
2244 "{}: Failed to send WebGPU channel to pipeline",
2245 source_pipeline_id
2246 )
2247 }
2248 },
2249 _ => warn!("Wrong message type in handle_wgpu_request"),
2250 }
2251 }
2252
2253 #[servo_tracing::instrument(skip_all)]
2254 fn handle_message_port_transfer_completed(
2255 &mut self,
2256 router_id: Option<MessagePortRouterId>,
2257 ports: Vec<MessagePortId>,
2258 ) {
2259 let Some(router_id) = router_id else {
2260 if !ports.is_empty() {
2261 warn!(
2262 "Constellation unable to process port transfer successes, since no router id was received"
2263 );
2264 }
2265 return;
2266 };
2267 for port_id in ports.into_iter() {
2268 let mut entry = match self.message_ports.entry(port_id) {
2269 Entry::Vacant(_) => {
2270 warn!(
2271 "Constellation received a port transfer completed msg for unknown messageport {port_id:?}",
2272 );
2273 continue;
2274 },
2275 Entry::Occupied(entry) => entry,
2276 };
2277 match entry.get().state {
2278 TransferState::CompletionInProgress(expected_router_id) => {
2279 if expected_router_id != router_id {
2282 return warn!(
2283 "Transfer completed by an unexpected router: {:?}",
2284 router_id
2285 );
2286 }
2287 let new_info = MessagePortInfo {
2289 state: TransferState::Managed(router_id),
2290 entangled_with: entry.get().entangled_with,
2291 };
2292 entry.insert(new_info);
2293 },
2294 _ => warn!("Constellation received unexpected port transfer completed message"),
2295 }
2296 }
2297 }
2298
2299 fn handle_message_port_transfer_failed(
2300 &mut self,
2301 ports: FxHashMap<MessagePortId, PortTransferInfo>,
2302 ) {
2303 for (port_id, mut transfer_info) in ports.into_iter() {
2304 let Some(entry) = self.message_ports.remove(&port_id) else {
2305 warn!(
2306 "Constellation received a port transfer completed msg for unknown messageport {port_id:?}",
2307 );
2308 continue;
2309 };
2310 let new_info = match entry.state {
2311 TransferState::CompletionFailed(mut current_buffer) => {
2312 while let Some(task) = transfer_info.port_message_queue.pop_back() {
2319 current_buffer.push_front(task);
2320 }
2321 MessagePortInfo {
2323 state: TransferState::TransferInProgress(current_buffer),
2324 entangled_with: entry.entangled_with,
2325 }
2326 },
2327 TransferState::CompletionRequested(target_router_id, mut current_buffer) => {
2328 while let Some(task) = transfer_info.port_message_queue.pop_back() {
2338 current_buffer.push_front(task);
2339 }
2340 if let Some(ipc_sender) = self.message_port_routers.get(&target_router_id) {
2342 if ipc_sender
2343 .send(MessagePortMsg::CompletePendingTransfer(
2344 port_id,
2345 PortTransferInfo {
2346 port_message_queue: current_buffer,
2347 disentangled: entry.entangled_with.is_none(),
2348 },
2349 ))
2350 .is_err()
2351 {
2352 warn!("Constellation failed to send complete port transfer response.");
2353 }
2354 } else {
2355 warn!("No message-port sender for {:?}", target_router_id);
2356 }
2357
2358 MessagePortInfo {
2360 state: TransferState::CompletionInProgress(target_router_id),
2361 entangled_with: entry.entangled_with,
2362 }
2363 },
2364 _ => {
2365 warn!("Unexpected port transfer failed message received");
2366 continue;
2367 },
2368 };
2369 self.message_ports.insert(port_id, new_info);
2370 }
2371 }
2372
2373 #[servo_tracing::instrument(skip_all)]
2374 fn handle_complete_message_port_transfer(
2375 &mut self,
2376 router_id: MessagePortRouterId,
2377 ports: Vec<MessagePortId>,
2378 ) {
2379 let mut response = FxHashMap::default();
2380 for port_id in ports.into_iter() {
2381 let Some(entry) = self.message_ports.remove(&port_id) else {
2382 warn!(
2383 "Constellation asked to complete transfer for unknown messageport {port_id:?}",
2384 );
2385 continue;
2386 };
2387 let new_info = match entry.state {
2388 TransferState::TransferInProgress(buffer) => {
2389 response.insert(
2390 port_id,
2391 PortTransferInfo {
2392 port_message_queue: buffer,
2393 disentangled: entry.entangled_with.is_none(),
2394 },
2395 );
2396
2397 MessagePortInfo {
2400 state: TransferState::CompletionInProgress(router_id),
2401 entangled_with: entry.entangled_with,
2402 }
2403 },
2404 TransferState::CompletionFailed(buffer) |
2405 TransferState::CompletionRequested(_, buffer) => {
2406 MessagePortInfo {
2419 state: TransferState::CompletionRequested(router_id, buffer),
2420 entangled_with: entry.entangled_with,
2421 }
2422 },
2423 _ => {
2424 warn!("Unexpected complete port transfer message received");
2425 continue;
2426 },
2427 };
2428 self.message_ports.insert(port_id, new_info);
2429 }
2430
2431 if !response.is_empty() {
2432 if let Some(ipc_sender) = self.message_port_routers.get(&router_id) {
2434 if ipc_sender
2435 .send(MessagePortMsg::CompleteTransfer(response))
2436 .is_err()
2437 {
2438 warn!("Constellation failed to send complete port transfer response.");
2439 }
2440 } else {
2441 warn!("No message-port sender for {:?}", router_id);
2442 }
2443 }
2444 }
2445
2446 #[servo_tracing::instrument(skip_all)]
2447 fn handle_reroute_messageport(&mut self, port_id: MessagePortId, task: PortMessageTask) {
2448 let Some(info) = self.message_ports.get_mut(&port_id) else {
2449 return warn!(
2450 "Constellation asked to re-route msg to unknown messageport {:?}",
2451 port_id
2452 );
2453 };
2454 match &mut info.state {
2455 TransferState::Managed(router_id) | TransferState::CompletionInProgress(router_id) => {
2456 if let Some(ipc_sender) = self.message_port_routers.get(router_id) {
2460 let _ = ipc_sender.send(MessagePortMsg::NewTask(port_id, task));
2461 } else {
2462 warn!("No message-port sender for {:?}", router_id);
2463 }
2464 },
2465 TransferState::TransferInProgress(queue) => queue.push_back(task),
2466 TransferState::CompletionFailed(queue) => queue.push_back(task),
2467 TransferState::CompletionRequested(_, queue) => queue.push_back(task),
2468 }
2469 }
2470
2471 #[servo_tracing::instrument(skip_all)]
2472 fn handle_messageport_shipped(&mut self, port_id: MessagePortId) {
2473 if let Some(info) = self.message_ports.get_mut(&port_id) {
2474 match info.state {
2475 TransferState::Managed(_) => {
2476 info.state = TransferState::TransferInProgress(VecDeque::new());
2478 },
2479 TransferState::CompletionInProgress(_) => {
2480 info.state = TransferState::CompletionFailed(VecDeque::new());
2485 },
2486 _ => warn!("Unexpected messageport shipped received"),
2487 }
2488 } else {
2489 warn!(
2490 "Constellation asked to mark unknown messageport as shipped {:?}",
2491 port_id
2492 );
2493 }
2494 }
2495
2496 fn handle_new_messageport_router(
2497 &mut self,
2498 router_id: MessagePortRouterId,
2499 message_port_callbacks: GenericCallback<MessagePortMsg>,
2500 ) {
2501 self.message_port_routers
2502 .insert(router_id, message_port_callbacks);
2503 }
2504
2505 fn handle_remove_messageport_router(&mut self, router_id: MessagePortRouterId) {
2506 self.message_port_routers.remove(&router_id);
2507 }
2508
2509 fn handle_new_messageport(&mut self, router_id: MessagePortRouterId, port_id: MessagePortId) {
2510 match self.message_ports.entry(port_id) {
2511 Entry::Occupied(_) => warn!(
2513 "Constellation asked to start tracking an existing messageport {:?}",
2514 port_id
2515 ),
2516 Entry::Vacant(entry) => {
2517 let info = MessagePortInfo {
2518 state: TransferState::Managed(router_id),
2519 entangled_with: None,
2520 };
2521 entry.insert(info);
2522 },
2523 }
2524 }
2525
2526 #[servo_tracing::instrument(skip_all)]
2527 fn handle_entangle_messageports(&mut self, port1: MessagePortId, port2: MessagePortId) {
2528 if let Some(info) = self.message_ports.get_mut(&port1) {
2529 info.entangled_with = Some(port2);
2530 } else {
2531 warn!(
2532 "Constellation asked to entangle unknown messageport: {:?}",
2533 port1
2534 );
2535 }
2536 if let Some(info) = self.message_ports.get_mut(&port2) {
2537 info.entangled_with = Some(port1);
2538 } else {
2539 warn!(
2540 "Constellation asked to entangle unknown messageport: {:?}",
2541 port2
2542 );
2543 }
2544 }
2545
2546 #[servo_tracing::instrument(skip_all)]
2547 fn handle_disentangle_messageports(
2549 &mut self,
2550 port1: MessagePortId,
2551 port2: Option<MessagePortId>,
2552 ) {
2553 let _ = self.message_ports.remove(&port1);
2559
2560 let Some(port2) = port2 else {
2563 return;
2564 };
2565
2566 if let Some(info) = self.message_ports.get_mut(&port2) {
2568 info.entangled_with = None;
2569 match &mut info.state {
2570 TransferState::Managed(router_id) |
2571 TransferState::CompletionInProgress(router_id) => {
2572 if let Some(ipc_sender) = self.message_port_routers.get(router_id) {
2577 let _ = ipc_sender.send(MessagePortMsg::CompleteDisentanglement(port2));
2578 } else {
2579 warn!("No message-port sender for {:?}", router_id);
2580 }
2581 },
2582 _ => {
2583 },
2585 }
2586 } else {
2587 warn!(
2588 "Constellation asked to disentangle unknown messageport: {:?}",
2589 port2
2590 );
2591 }
2592 }
2593
2594 #[servo_tracing::instrument(skip_all)]
2601 fn handle_schedule_serviceworker_job(&mut self, pipeline_id: PipelineId, job: Job) {
2602 let origin = job.scope_url.origin();
2603
2604 if self
2605 .check_origin_against_pipeline(&pipeline_id, &origin)
2606 .is_err()
2607 {
2608 return warn!(
2609 "Attempt to schedule a serviceworker job from an origin not matching the origin of the job."
2610 );
2611 }
2612
2613 let sw_manager = match self.sw_managers.entry(origin.clone()) {
2615 Entry::Occupied(entry) => entry.into_mut(),
2616 Entry::Vacant(entry) => {
2617 let (own_sender, receiver) =
2618 generic_channel::channel().expect("Failed to create IPC channel!");
2619
2620 let sw_senders = SWManagerSenders {
2621 swmanager_sender: self.swmanager_ipc_sender.clone(),
2622 resource_threads: self.public_resource_threads.clone(),
2623 own_sender: own_sender.clone(),
2624 receiver,
2625 paint_api: self.paint_proxy.cross_process_paint_api.clone(),
2626 system_font_service_sender: self.system_font_service.to_sender(),
2627 };
2628
2629 if opts::get().multiprocess {
2630 let (sender, receiver) = generic_channel::channel()
2631 .expect("Failed to create lifeline channel for sw");
2632 let content =
2633 ServiceWorkerUnprivilegedContent::new(sw_senders, origin, Some(sender));
2634
2635 if let Ok(process) = content.spawn_multiprocess() {
2636 let crossbeam_receiver = receiver.route_preserving_errors();
2637 self.process_manager.add(crossbeam_receiver, process);
2638 } else {
2639 return warn!("Failed to spawn process for SW manager.");
2640 }
2641 } else {
2642 let content = ServiceWorkerUnprivilegedContent::new(sw_senders, origin, None);
2643 content.start::<SWF>();
2644 }
2645 entry.insert(own_sender)
2646 },
2647 };
2648 let _ = sw_manager.send(ServiceWorkerMsg::ScheduleJob(job));
2649 }
2650
2651 #[servo_tracing::instrument(skip_all)]
2652 fn handle_broadcast_storage_event(
2653 &self,
2654 pipeline_id: PipelineId,
2655 storage: WebStorageType,
2656 url: ServoUrl,
2657 key: Option<String>,
2658 old_value: Option<String>,
2659 new_value: Option<String>,
2660 ) {
2661 let origin = url.origin();
2662 let Some(source_pipeline) = self.pipelines.get(&pipeline_id) else {
2663 warn!("Received storage event broadcast request from closed pipeline.");
2664 return;
2665 };
2666
2667 if source_pipeline.url.origin() != origin {
2668 return warn!(
2669 "Attempt to broadcast storage event from an origin not matching the source pipeline origin."
2670 );
2671 }
2672
2673 let interested = match self
2674 .pipeline_interests
2675 .get(&ConstellationInterest::StorageEvent)
2676 {
2677 Some(set) => set,
2678 None => return,
2679 }
2680 .iter()
2681 .filter_map(|interested_id| self.pipelines.get(interested_id));
2682
2683 for pipeline in interested {
2684 if pipeline.id == pipeline_id || pipeline.url.origin() != origin {
2685 continue;
2686 }
2687
2688 if storage == WebStorageType::Session &&
2696 pipeline.webview_id != source_pipeline.webview_id
2697 {
2698 continue;
2699 }
2700
2701 let msg = ScriptThreadMessage::DispatchStorageEvent(
2702 pipeline.id,
2703 storage,
2704 url.clone(),
2705 key.clone(),
2706 old_value.clone(),
2707 new_value.clone(),
2708 );
2709 if let Err(err) = pipeline.event_loop.send(msg) {
2710 warn!(
2711 "{}: Failed to broadcast storage event to pipeline ({:?}).",
2712 pipeline.id, err
2713 );
2714 }
2715 }
2716 }
2717
2718 #[servo_tracing::instrument(skip_all)]
2719 fn handle_exit(&mut self) {
2720 debug!("Handling exit.");
2721
2722 if self.shutting_down {
2724 return;
2725 }
2726 self.shutting_down = true;
2727
2728 self.mem_profiler_chan.send(mem::ProfilerMsg::Exit);
2729
2730 self.send_message_to_all_background_hang_monitors(BackgroundHangMonitorControlMsg::Exit);
2734
2735 let browsing_context_ids: Vec<BrowsingContextId> = self
2737 .browsing_contexts
2738 .values()
2739 .filter(|browsing_context| browsing_context.is_top_level())
2740 .map(|browsing_context| browsing_context.id)
2741 .collect();
2742 for browsing_context_id in browsing_context_ids {
2743 debug!(
2744 "{}: Removing top-level browsing context",
2745 browsing_context_id
2746 );
2747 self.close_browsing_context(browsing_context_id, ExitPipelineMode::Normal);
2748 }
2749
2750 while let Some(pending) = self.pending_changes.pop() {
2752 debug!(
2753 "{}: Removing pending browsing context",
2754 pending.browsing_context_id
2755 );
2756 self.close_browsing_context(pending.browsing_context_id, ExitPipelineMode::Normal);
2757 debug!("{}: Removing pending pipeline", pending.new_pipeline_id);
2758 self.close_pipeline(
2759 pending.new_pipeline_id,
2760 DiscardBrowsingContext::Yes,
2761 ExitPipelineMode::Normal,
2762 );
2763 }
2764
2765 let browsing_context_ids: Vec<BrowsingContextId> =
2767 self.browsing_contexts.keys().cloned().collect();
2768 for browsing_context_id in browsing_context_ids {
2769 debug!(
2770 "{}: Removing detached browsing context",
2771 browsing_context_id
2772 );
2773 self.close_browsing_context(browsing_context_id, ExitPipelineMode::Normal);
2774 }
2775
2776 let pipeline_ids: Vec<PipelineId> = self.pipelines.keys().cloned().collect();
2778 for pipeline_id in pipeline_ids {
2779 debug!("{}: Removing detached pipeline", pipeline_id);
2780 self.close_pipeline(
2781 pipeline_id,
2782 DiscardBrowsingContext::Yes,
2783 ExitPipelineMode::Normal,
2784 );
2785 }
2786 }
2787
2788 #[servo_tracing::instrument(skip_all)]
2789 fn handle_shutdown(&mut self) {
2790 debug!("Handling shutdown.");
2791
2792 for join_handle in self.event_loop_join_handles.drain(..) {
2793 if join_handle.join().is_err() {
2794 error!("Failed to join on a script-thread.");
2795 }
2796 }
2797
2798 drop(self.background_monitor_register.take());
2800 if let Some(join_handle) = self.background_monitor_register_join_handle.take() {
2801 if join_handle.join().is_err() {
2802 error!("Failed to join on the bhm background thread.");
2803 }
2804 }
2805
2806 let (core_ipc_sender, core_ipc_receiver) =
2810 generic_channel::oneshot().expect("Failed to create IPC channel!");
2811 let (public_client_storage_generic_sender, public_client_storage_generic_receiver) =
2812 generic_channel::channel().expect("Failed to create generic channel!");
2813 let (private_client_storage_generic_sender, private_client_storage_generic_receiver) =
2814 generic_channel::channel().expect("Failed to create generic channel!");
2815 let (public_indexeddb_ipc_sender, public_indexeddb_ipc_receiver) =
2816 generic_channel::channel().expect("Failed to create generic channel!");
2817 let (private_indexeddb_ipc_sender, private_indexeddb_ipc_receiver) =
2818 generic_channel::channel().expect("Failed to create generic channel!");
2819 let (public_web_storage_generic_sender, public_web_storage_generic_receiver) =
2820 generic_channel::channel().expect("Failed to create generic channel!");
2821 let (private_web_storage_generic_sender, private_web_storage_generic_receiver) =
2822 generic_channel::channel().expect("Failed to create generic channel!");
2823
2824 debug!("Exiting core resource threads.");
2825 if let Err(e) = self
2826 .public_resource_threads
2827 .send(net_traits::CoreResourceMsg::Exit(core_ipc_sender))
2828 {
2829 warn!("Exit resource thread failed ({})", e);
2830 }
2831
2832 if let Some(ref chan) = self.devtools_sender {
2833 debug!("Exiting devtools.");
2834 let msg = DevtoolsControlMsg::FromChrome(ChromeToDevtoolsControlMsg::ServerExitMsg);
2835 if let Err(e) = chan.send(msg) {
2836 warn!("Exit devtools failed ({:?})", e);
2837 }
2838 }
2839
2840 debug!("Exiting public client storage thread.");
2841 if let Err(e) = generic_channel::GenericSend::send(
2842 &self.public_storage_threads,
2843 ClientStorageThreadMessage::Exit(public_client_storage_generic_sender),
2844 ) {
2845 warn!("Exit public client storage thread failed ({})", e);
2846 }
2847 debug!("Exiting private client storage thread.");
2848 if let Err(e) = generic_channel::GenericSend::send(
2849 &self.private_storage_threads,
2850 ClientStorageThreadMessage::Exit(private_client_storage_generic_sender),
2851 ) {
2852 warn!("Exit private client storage thread failed ({})", e);
2853 }
2854
2855 debug!("Exiting public indexeddb resource threads.");
2856 if let Err(e) =
2857 self.public_storage_threads
2858 .send(IndexedDBThreadMsg::Sync(SyncOperation::Exit(
2859 public_indexeddb_ipc_sender,
2860 )))
2861 {
2862 warn!("Exit public indexeddb thread failed ({})", e);
2863 }
2864
2865 debug!("Exiting private indexeddb resource threads.");
2866 if let Err(e) =
2867 self.private_storage_threads
2868 .send(IndexedDBThreadMsg::Sync(SyncOperation::Exit(
2869 private_indexeddb_ipc_sender,
2870 )))
2871 {
2872 warn!("Exit private indexeddb thread failed ({})", e);
2873 }
2874
2875 debug!("Exiting public web storage thread.");
2876 if let Err(e) = generic_channel::GenericSend::send(
2877 &self.public_storage_threads,
2878 WebStorageThreadMsg::Exit(public_web_storage_generic_sender),
2879 ) {
2880 warn!("Exit public web storage thread failed ({})", e);
2881 }
2882
2883 debug!("Exiting private web storage thread.");
2884 if let Err(e) = generic_channel::GenericSend::send(
2885 &self.private_storage_threads,
2886 WebStorageThreadMsg::Exit(private_web_storage_generic_sender),
2887 ) {
2888 warn!("Exit private web storage thread failed ({})", e);
2889 }
2890
2891 #[cfg(feature = "bluetooth")]
2892 {
2893 debug!("Exiting bluetooth thread.");
2894 if let Err(e) = self.bluetooth_ipc_sender.send(BluetoothRequest::Exit) {
2895 warn!("Exit bluetooth thread failed ({})", e);
2896 }
2897 }
2898
2899 debug!("Exiting service worker manager thread.");
2900 for (_, mgr) in self.sw_managers.drain() {
2901 if let Err(e) = mgr.send(ServiceWorkerMsg::Exit) {
2902 warn!("Exit service worker manager failed ({})", e);
2903 }
2904 }
2905
2906 let canvas_exit_receiver = if let Some((canvas_sender, _)) = self.canvas.get() {
2907 debug!("Exiting Canvas Paint thread.");
2908 let (canvas_exit_sender, canvas_exit_receiver) = unbounded();
2909 if let Err(e) = canvas_sender.send(ConstellationCanvasMsg::Exit(canvas_exit_sender)) {
2910 warn!("Exit Canvas Paint thread failed ({})", e);
2911 }
2912 Some(canvas_exit_receiver)
2913 } else {
2914 None
2915 };
2916
2917 debug!("Exiting WebGPU threads.");
2918 #[cfg(feature = "webgpu")]
2919 let receivers = self
2920 .browsing_context_group_set
2921 .values()
2922 .flat_map(|browsing_context_group| {
2923 browsing_context_group.webgpus.values().map(|webgpu| {
2924 let (sender, receiver) =
2925 generic_channel::oneshot().expect("Failed to create IPC channel!");
2926 if let Err(e) = webgpu.exit(sender) {
2927 warn!("Exit WebGPU Thread failed ({})", e);
2928 None
2929 } else {
2930 Some(receiver)
2931 }
2932 })
2933 })
2934 .flatten();
2935
2936 #[cfg(feature = "webgpu")]
2937 for receiver in receivers {
2938 if let Err(e) = receiver.recv() {
2939 warn!("Failed to receive exit response from WebGPU ({:?})", e);
2940 }
2941 }
2942
2943 debug!("Exiting GLPlayer thread.");
2944 WindowGLContext::get().exit();
2945
2946 if let Some(canvas_exit_receiver) = canvas_exit_receiver {
2949 let _ = canvas_exit_receiver.recv();
2950 }
2951
2952 debug!("Exiting the system font service thread.");
2953 self.system_font_service.exit();
2954
2955 if let Err(e) = core_ipc_receiver.recv() {
2957 warn!("Exit resource thread failed ({:?})", e);
2958 }
2959 if let Err(e) = public_client_storage_generic_receiver.recv() {
2960 warn!("Exit public client storage thread failed ({:?})", e);
2961 }
2962 if let Err(e) = private_client_storage_generic_receiver.recv() {
2963 warn!("Exit private client storage thread failed ({:?})", e);
2964 }
2965 if let Err(e) = public_indexeddb_ipc_receiver.recv() {
2966 warn!("Exit public indexeddb thread failed ({:?})", e);
2967 }
2968 if let Err(e) = private_indexeddb_ipc_receiver.recv() {
2969 warn!("Exit private indexeddb thread failed ({:?})", e);
2970 }
2971 if let Err(e) = public_web_storage_generic_receiver.recv() {
2972 warn!("Exit public web storage thread failed ({:?})", e);
2973 }
2974 if let Err(e) = private_web_storage_generic_receiver.recv() {
2975 warn!("Exit private web storage thread failed ({:?})", e);
2976 }
2977
2978 debug!("Shutting-down IPC router thread in constellation.");
2979 ROUTER.shutdown();
2980
2981 debug!("Shutting-down the async runtime in constellation.");
2982 self.async_runtime.shutdown();
2983 }
2984
2985 fn handle_pipeline_exited(&mut self, pipeline_id: PipelineId) {
2986 debug!("{}: Exited", pipeline_id);
2987 let Some(pipeline) = self.pipelines.remove(&pipeline_id) else {
2988 return;
2989 };
2990
2991 self.pipeline_interests.retain(|_, set| {
2993 set.remove(&pipeline_id);
2994 !set.is_empty()
2995 });
2996
2997 self.paint_proxy.send(PaintMessage::PipelineExited(
3001 pipeline.webview_id,
3002 pipeline.id,
3003 PipelineExitSource::Constellation,
3004 ));
3005 }
3006
3007 #[servo_tracing::instrument(skip_all)]
3008 fn handle_send_error(&mut self, pipeline_id: PipelineId, error: SendError) {
3009 error!("Error sending message to {pipeline_id:?}: {error}",);
3010
3011 let Some(pipeline) = self.pipelines.get(&pipeline_id) else {
3013 return;
3014 };
3015
3016 self.handle_panic_in_webview(
3018 pipeline.webview_id,
3019 &format!("Send failed ({error})"),
3020 &None,
3021 );
3022 }
3023
3024 #[servo_tracing::instrument(skip_all)]
3025 fn handle_panic(
3026 &mut self,
3027 event_loop_id: Option<ScriptEventLoopId>,
3028 reason: String,
3029 backtrace: Option<String>,
3030 ) {
3031 if self.hard_fail {
3032 error!("Pipeline failed in hard-fail mode. Crashing!");
3035 process::exit(1);
3036 }
3037
3038 let Some(event_loop_id) = event_loop_id else {
3039 return;
3040 };
3041 debug!("Panic handler for {event_loop_id:?}: {reason:?}",);
3042
3043 let mut webview_ids = HashSet::new();
3044 for pipeline in self.pipelines.values() {
3045 if pipeline.event_loop.id() == event_loop_id {
3046 webview_ids.insert(pipeline.webview_id);
3047 }
3048 }
3049 for webview_id in webview_ids {
3050 self.handle_panic_in_webview(webview_id, &reason, &backtrace);
3051 }
3052 }
3053
3054 fn handle_panic_in_webview(
3055 &mut self,
3056 webview_id: WebViewId,
3057 reason: &String,
3058 backtrace: &Option<String>,
3059 ) {
3060 let browsing_context_id = BrowsingContextId::from(webview_id);
3061 self.constellation_to_embedder_proxy
3062 .send(ConstellationToEmbedderMsg::Panic(
3063 webview_id,
3064 reason.clone(),
3065 backtrace.clone(),
3066 ));
3067
3068 let Some(browsing_context) = self.browsing_contexts.get(&browsing_context_id) else {
3069 return warn!("failed browsing context is missing");
3070 };
3071 let viewport_details = browsing_context.viewport_details;
3072 let pipeline_id = browsing_context.pipeline_id;
3073 let throttled = browsing_context.throttled;
3074
3075 let Some(pipeline) = self.pipelines.get(&pipeline_id) else {
3076 return warn!("failed pipeline is missing");
3077 };
3078 let opener = pipeline.opener;
3079
3080 self.close_browsing_context_children(
3081 browsing_context_id,
3082 DiscardBrowsingContext::No,
3083 ExitPipelineMode::Force,
3084 );
3085
3086 let old_pipeline_id = pipeline_id;
3087 let Some(old_load_data) = self.refresh_load_data(pipeline_id) else {
3088 return warn!("failed pipeline is missing");
3089 };
3090 if old_load_data.crash.is_some() {
3091 return error!("crash page crashed");
3092 }
3093
3094 warn!("creating replacement pipeline for crash page");
3095
3096 let new_pipeline_id = PipelineId::new();
3097 let new_load_data = LoadData {
3098 crash: Some(
3099 backtrace
3100 .clone()
3101 .map(|backtrace| format!("{reason}\n{backtrace}"))
3102 .unwrap_or_else(|| reason.clone()),
3103 ),
3104 creation_sandboxing_flag_set: SandboxingFlagSet::all(),
3105 ..old_load_data.clone()
3106 };
3107
3108 let is_private = false;
3109 self.new_pipeline(
3110 new_pipeline_id,
3111 browsing_context_id,
3112 webview_id,
3113 None,
3114 opener,
3115 viewport_details,
3116 new_load_data,
3117 is_private,
3118 throttled,
3119 TargetSnapshotParams::default(),
3120 );
3121 self.add_pending_change(SessionHistoryChange {
3122 webview_id,
3123 browsing_context_id,
3124 new_pipeline_id,
3125 replace: Some(NeedsToReload::Yes(old_pipeline_id, old_load_data)),
3128 new_browsing_context_info: None,
3129 viewport_details,
3130 });
3131 }
3132
3133 #[servo_tracing::instrument(skip_all)]
3134 fn handle_focus_web_view(&mut self, webview_id: WebViewId) {
3135 self.constellation_to_embedder_proxy
3136 .send(ConstellationToEmbedderMsg::WebViewFocused(webview_id, true));
3137 }
3138
3139 #[servo_tracing::instrument(skip_all)]
3140 fn handle_log_entry(
3141 &mut self,
3142 event_loop_id: Option<ScriptEventLoopId>,
3143 thread_name: Option<String>,
3144 entry: LogEntry,
3145 ) {
3146 if let LogEntry::Panic(ref reason, ref backtrace) = entry {
3147 self.handle_panic(event_loop_id, reason.clone(), Some(backtrace.clone()));
3148 }
3149
3150 match entry {
3151 LogEntry::Panic(reason, _) | LogEntry::Error(reason) | LogEntry::Warn(reason) => {
3152 if WARNINGS_BUFFER_SIZE <= self.handled_warnings.len() {
3154 self.handled_warnings.pop_front();
3155 }
3156 self.handled_warnings.push_back((thread_name, reason));
3157 },
3158 }
3159 }
3160
3161 fn update_pressed_mouse_buttons(&mut self, event: &MouseButtonEvent) {
3162 let button_as_bitmask = match event.button {
3166 MouseButton::Left => 1,
3167 MouseButton::Right => 2,
3168 MouseButton::Middle => 4,
3169 MouseButton::Back => 8,
3170 MouseButton::Forward => 16,
3171 MouseButton::Other(_) => return,
3172 };
3173
3174 match event.action {
3175 MouseButtonAction::Down => {
3176 self.pressed_mouse_buttons |= button_as_bitmask;
3177 },
3178 MouseButtonAction::Up => {
3179 self.pressed_mouse_buttons &= !(button_as_bitmask);
3180 },
3181 }
3182 }
3183
3184 #[expect(deprecated)]
3185 fn update_active_keybord_modifiers(&mut self, event: &KeyboardEvent) {
3186 self.active_keyboard_modifiers = event.event.modifiers;
3187
3188 let Key::Named(named_key) = event.event.key else {
3193 return;
3194 };
3195
3196 let modified_modifier = match named_key {
3197 NamedKey::Alt => Modifiers::ALT,
3198 NamedKey::AltGraph => Modifiers::ALT_GRAPH,
3199 NamedKey::CapsLock => Modifiers::CAPS_LOCK,
3200 NamedKey::Control => Modifiers::CONTROL,
3201 NamedKey::Fn => Modifiers::FN,
3202 NamedKey::FnLock => Modifiers::FN_LOCK,
3203 NamedKey::Meta => Modifiers::META,
3204 NamedKey::NumLock => Modifiers::NUM_LOCK,
3205 NamedKey::ScrollLock => Modifiers::SCROLL_LOCK,
3206 NamedKey::Shift => Modifiers::SHIFT,
3207 NamedKey::Symbol => Modifiers::SYMBOL,
3208 NamedKey::SymbolLock => Modifiers::SYMBOL_LOCK,
3209 NamedKey::Hyper => Modifiers::HYPER,
3210 NamedKey::Super => Modifiers::META,
3213 _ => return,
3214 };
3215 match event.event.state {
3216 KeyState::Down => self.active_keyboard_modifiers.insert(modified_modifier),
3217 KeyState::Up => self.active_keyboard_modifiers.remove(modified_modifier),
3218 }
3219 }
3220
3221 fn set_accessibility_active(&mut self, webview_id: WebViewId, active: bool) {
3222 if !(pref!(accessibility_enabled)) {
3223 return;
3224 }
3225
3226 let Some(webview) = self.webviews.get_mut(&webview_id) else {
3227 return;
3228 };
3229
3230 webview.accessibility_active = active;
3231 let Some(pipeline_id) = webview.active_top_level_pipeline_id else {
3232 return;
3233 };
3234 let epoch = webview.active_top_level_pipeline_epoch;
3235 self.send_message_to_pipeline(
3240 pipeline_id,
3241 ScriptThreadMessage::SetAccessibilityActive(pipeline_id, active, epoch),
3242 "Set accessibility active after closure",
3243 );
3244 }
3245
3246 fn forward_input_event(
3247 &mut self,
3248 webview_id: WebViewId,
3249 event: InputEventAndId,
3250 hit_test_result: Option<PaintHitTestResult>,
3251 ) {
3252 if let InputEvent::MouseButton(event) = &event.event {
3253 self.update_pressed_mouse_buttons(event);
3254 }
3255
3256 if let InputEvent::Keyboard(event) = &event.event {
3257 self.update_active_keybord_modifiers(event);
3258 }
3259
3260 let pressed_mouse_buttons = self.pressed_mouse_buttons;
3263 let active_keyboard_modifiers = self.active_keyboard_modifiers;
3264
3265 let event_id = event.id;
3266 let Some(webview) = self.webviews.get_mut(&webview_id) else {
3267 warn!("Got input event for unknown WebViewId: {webview_id:?}");
3268 self.constellation_to_embedder_proxy.send(
3269 ConstellationToEmbedderMsg::InputEventsHandled(
3270 webview_id,
3271 vec![InputEventOutcome {
3272 id: event_id,
3273 result: Default::default(),
3274 }],
3275 ),
3276 );
3277 return;
3278 };
3279
3280 let event = ConstellationInputEvent {
3281 hit_test_result,
3282 pressed_mouse_buttons,
3283 active_keyboard_modifiers,
3284 event,
3285 };
3286
3287 if !webview.forward_input_event(event, &self.pipelines, &self.browsing_contexts) {
3288 self.constellation_to_embedder_proxy.send(
3289 ConstellationToEmbedderMsg::InputEventsHandled(
3290 webview_id,
3291 vec![InputEventOutcome {
3292 id: event_id,
3293 result: Default::default(),
3294 }],
3295 ),
3296 );
3297 }
3298 }
3299
3300 #[servo_tracing::instrument(skip_all)]
3301 fn handle_new_top_level_browsing_context(
3302 &mut self,
3303 url: ServoUrl,
3304 NewWebViewDetails {
3305 webview_id,
3306 viewport_details,
3307 user_content_manager_id,
3308 }: NewWebViewDetails,
3309 ) {
3310 let pipeline_id = PipelineId::new();
3311 let browsing_context_id = BrowsingContextId::from(webview_id);
3312 let load_data = LoadData::new_for_new_unrelated_webview(url);
3313 let is_private = false;
3314 let throttled = false;
3315
3316 self.webviews.insert(
3319 webview_id,
3320 ConstellationWebView::new(webview_id, browsing_context_id, user_content_manager_id),
3321 );
3322
3323 let mut new_bc_group: BrowsingContextGroup = Default::default();
3325 let new_bc_group_id = self.next_browsing_context_group_id();
3326 new_bc_group
3327 .top_level_browsing_context_set
3328 .insert(webview_id);
3329 self.browsing_context_group_set
3330 .insert(new_bc_group_id, new_bc_group);
3331
3332 self.new_pipeline(
3333 pipeline_id,
3334 browsing_context_id,
3335 webview_id,
3336 None,
3337 None,
3338 viewport_details,
3339 load_data,
3340 is_private,
3341 throttled,
3342 TargetSnapshotParams::default(),
3343 );
3344 self.add_pending_change(SessionHistoryChange {
3345 webview_id,
3346 browsing_context_id,
3347 new_pipeline_id: pipeline_id,
3348 replace: None,
3349 new_browsing_context_info: Some(NewBrowsingContextInfo {
3350 parent_pipeline_id: None,
3351 is_private,
3352 inherited_secure_context: None,
3353 throttled,
3354 }),
3355 viewport_details,
3356 });
3357
3358 let painter_id = PainterId::from(webview_id);
3359 self.system_font_service
3360 .prefetch_font_keys_for_painter(painter_id);
3361 }
3362
3363 #[servo_tracing::instrument(skip_all)]
3364 fn handle_close_top_level_browsing_context(&mut self, webview_id: WebViewId) {
3366 debug!("{webview_id}: Closing");
3367 let browsing_context_id = BrowsingContextId::from(webview_id);
3368 let browsing_context =
3370 self.close_browsing_context(browsing_context_id, ExitPipelineMode::Normal);
3371 self.webviews.remove(&webview_id);
3373 self.constellation_to_embedder_proxy
3374 .send(ConstellationToEmbedderMsg::WebViewClosed(webview_id));
3375
3376 let Some(browsing_context) = browsing_context else {
3377 return warn!(
3378 "fn handle_close_top_level_browsing_context {}: Closing twice",
3379 browsing_context_id
3380 );
3381 };
3382 let bc_group_id = browsing_context.bc_group_id;
3386 let Some(bc_group) = self.browsing_context_group_set.get_mut(&bc_group_id) else {
3388 warn!("{}: Browsing context group not found!", bc_group_id);
3390 return;
3391 };
3392 if !bc_group.top_level_browsing_context_set.remove(&webview_id) {
3394 warn!("{webview_id}: Top-level browsing context not found in {bc_group_id}",);
3395 }
3396 if bc_group.top_level_browsing_context_set.is_empty() {
3399 self.browsing_context_group_set
3400 .remove(&browsing_context.bc_group_id);
3401 }
3402
3403 debug!("{webview_id}: Closed");
3404 }
3405
3406 #[servo_tracing::instrument(skip_all)]
3407 fn handle_iframe_size_msg(&mut self, iframe_sizes: Vec<IFrameSizeMsg>) {
3408 for IFrameSizeMsg {
3409 browsing_context_id,
3410 size,
3411 type_,
3412 } in iframe_sizes
3413 {
3414 self.resize_browsing_context(size, type_, browsing_context_id);
3415 }
3416 }
3417
3418 #[servo_tracing::instrument(skip_all)]
3419 fn handle_finish_javascript_evaluation(
3420 &mut self,
3421 evaluation_id: JavaScriptEvaluationId,
3422 result: Result<JSValue, JavaScriptEvaluationError>,
3423 ) {
3424 self.constellation_to_embedder_proxy.send(
3425 ConstellationToEmbedderMsg::FinishJavaScriptEvaluation(evaluation_id, result),
3426 );
3427 }
3428
3429 #[servo_tracing::instrument(skip_all)]
3430 fn handle_subframe_loaded(&mut self, pipeline_id: PipelineId) {
3431 let browsing_context_id = match self.pipelines.get(&pipeline_id) {
3432 Some(pipeline) => pipeline.browsing_context_id,
3433 None => return warn!("{}: Subframe loaded after closure", pipeline_id),
3434 };
3435 let parent_pipeline_id = match self.browsing_contexts.get(&browsing_context_id) {
3436 Some(browsing_context) => browsing_context.parent_pipeline_id,
3437 None => {
3438 return warn!(
3439 "{}: Subframe loaded in closed {}",
3440 pipeline_id, browsing_context_id,
3441 );
3442 },
3443 };
3444 let Some(parent_pipeline_id) = parent_pipeline_id else {
3445 return warn!("{}: Subframe has no parent", pipeline_id);
3446 };
3447 let msg = ScriptThreadMessage::DispatchIFrameLoadEvent {
3451 target: browsing_context_id,
3452 parent: parent_pipeline_id,
3453 child: pipeline_id,
3454 };
3455 let result = match self.pipelines.get(&parent_pipeline_id) {
3456 Some(parent) => parent.event_loop.send(msg),
3457 None => {
3458 return warn!(
3459 "{}: Parent pipeline browsing context loaded after closure",
3460 parent_pipeline_id
3461 );
3462 },
3463 };
3464 if let Err(e) = result {
3465 self.handle_send_error(parent_pipeline_id, e);
3466 }
3467 }
3468
3469 #[servo_tracing::instrument(skip_all)]
3474 fn handle_script_loaded_url_in_iframe_msg(&mut self, load_info: IFrameLoadInfoWithData) {
3475 let IFrameLoadInfo {
3476 parent_pipeline_id,
3477 browsing_context_id,
3478 webview_id,
3479 new_pipeline_id,
3480 is_private,
3481 mut history_handling,
3482 target_snapshot_params,
3483 ..
3484 } = load_info.info;
3485
3486 let old_pipeline = load_info
3488 .old_pipeline_id
3489 .and_then(|id| self.pipelines.get(&id));
3490
3491 if let Some(old_pipeline) = old_pipeline {
3494 if !old_pipeline.completely_loaded {
3495 history_handling = NavigationHistoryBehavior::Replace;
3496 }
3497 debug!(
3498 "{:?}: Old pipeline is {}completely loaded",
3499 load_info.old_pipeline_id,
3500 if old_pipeline.completely_loaded {
3501 ""
3502 } else {
3503 "not "
3504 }
3505 );
3506 }
3507
3508 let is_parent_private = {
3509 let parent_browsing_context_id = match self.pipelines.get(&parent_pipeline_id) {
3510 Some(pipeline) => pipeline.browsing_context_id,
3511 None => {
3512 return warn!(
3513 "{parent_pipeline_id}: Script loaded url in iframe \
3514 {browsing_context_id} in closed parent pipeline",
3515 );
3516 },
3517 };
3518
3519 let Some(ctx) = self.browsing_contexts.get(&parent_browsing_context_id) else {
3520 return warn!(
3521 "{parent_browsing_context_id}: Script loaded url in \
3522 iframe {browsing_context_id} in closed parent browsing context",
3523 );
3524 };
3525 ctx.is_private
3526 };
3527 let is_private = is_private || is_parent_private;
3528
3529 let Some(browsing_context) = self.browsing_contexts.get(&browsing_context_id) else {
3530 return warn!(
3531 "{browsing_context_id}: Script loaded url in iframe with closed browsing context",
3532 );
3533 };
3534
3535 let replace = if history_handling == NavigationHistoryBehavior::Replace {
3536 Some(NeedsToReload::No(browsing_context.pipeline_id))
3537 } else {
3538 None
3539 };
3540
3541 let browsing_context_size = browsing_context.viewport_details;
3542 let browsing_context_throttled = browsing_context.throttled;
3543 #[cfg(debug_assertions)]
3545 if !(browsing_context_size == load_info.viewport_details) {
3546 log::warn!(
3547 "debug assertion failed! browsing_context_size == load_info.viewport_details.initial_viewport"
3548 );
3549 }
3550
3551 self.new_pipeline(
3553 new_pipeline_id,
3554 browsing_context_id,
3555 webview_id,
3556 Some(parent_pipeline_id),
3557 None,
3558 browsing_context_size,
3559 load_info.load_data,
3560 is_private,
3561 browsing_context_throttled,
3562 target_snapshot_params,
3563 );
3564 self.add_pending_change(SessionHistoryChange {
3565 webview_id,
3566 browsing_context_id,
3567 new_pipeline_id,
3568 replace,
3569 new_browsing_context_info: None,
3571 viewport_details: load_info.viewport_details,
3572 });
3573 }
3574
3575 #[servo_tracing::instrument(skip_all)]
3576 fn handle_script_new_iframe(&mut self, load_info: IFrameLoadInfoWithData) {
3577 let IFrameLoadInfo {
3578 parent_pipeline_id,
3579 new_pipeline_id,
3580 browsing_context_id,
3581 webview_id,
3582 is_private,
3583 ..
3584 } = load_info.info;
3585
3586 let (script_sender, parent_browsing_context_id) =
3587 match self.pipelines.get(&parent_pipeline_id) {
3588 Some(pipeline) => (pipeline.event_loop.clone(), pipeline.browsing_context_id),
3589 None => {
3590 return warn!(
3591 "{}: Script loaded url in closed iframe pipeline",
3592 parent_pipeline_id
3593 );
3594 },
3595 };
3596 let (is_parent_private, is_parent_throttled, is_parent_secure) =
3597 match self.browsing_contexts.get(&parent_browsing_context_id) {
3598 Some(ctx) => (ctx.is_private, ctx.throttled, ctx.inherited_secure_context),
3599 None => {
3600 return warn!(
3601 "{}: New iframe {} loaded in closed parent browsing context",
3602 parent_browsing_context_id, browsing_context_id,
3603 );
3604 },
3605 };
3606 let is_private = is_private || is_parent_private;
3607 let pipeline = Pipeline::new_already_spawned(
3608 new_pipeline_id,
3609 browsing_context_id,
3610 webview_id,
3611 None,
3612 script_sender,
3613 self.paint_proxy.clone(),
3614 is_parent_throttled,
3615 load_info.load_data,
3616 );
3617
3618 assert!(!self.pipelines.contains_key(&new_pipeline_id));
3619 self.pipelines.insert(new_pipeline_id, pipeline);
3620 self.add_pending_change(SessionHistoryChange {
3621 webview_id,
3622 browsing_context_id,
3623 new_pipeline_id,
3624 replace: None,
3625 new_browsing_context_info: Some(NewBrowsingContextInfo {
3627 parent_pipeline_id: Some(parent_pipeline_id),
3628 is_private,
3629 inherited_secure_context: is_parent_secure,
3630 throttled: is_parent_throttled,
3631 }),
3632 viewport_details: load_info.viewport_details,
3633 });
3634 }
3635
3636 #[servo_tracing::instrument(skip_all)]
3637 fn handle_script_new_auxiliary(&mut self, load_info: AuxiliaryWebViewCreationRequest) {
3638 let AuxiliaryWebViewCreationRequest {
3639 load_data,
3640 opener_webview_id,
3641 opener_pipeline_id,
3642 response_sender,
3643 } = load_info;
3644
3645 let Some((webview_id_sender, webview_id_receiver)) = generic_channel::channel() else {
3646 warn!("Failed to create channel");
3647 let _ = response_sender.send(None);
3648 return;
3649 };
3650 self.constellation_to_embedder_proxy
3651 .send(ConstellationToEmbedderMsg::AllowOpeningWebView(
3652 opener_webview_id,
3653 webview_id_sender,
3654 ));
3655 let NewWebViewDetails {
3656 webview_id: new_webview_id,
3657 viewport_details,
3658 user_content_manager_id,
3659 } = match webview_id_receiver.recv() {
3660 Ok(Some(new_webview_details)) => new_webview_details,
3661 Ok(None) | Err(_) => {
3662 let _ = response_sender.send(None);
3663 return;
3664 },
3665 };
3666 let new_browsing_context_id = BrowsingContextId::from(new_webview_id);
3667
3668 let (script_sender, opener_browsing_context_id) =
3669 match self.pipelines.get(&opener_pipeline_id) {
3670 Some(pipeline) => (pipeline.event_loop.clone(), pipeline.browsing_context_id),
3671 None => {
3672 return warn!(
3673 "{}: Auxiliary loaded url in closed iframe pipeline",
3674 opener_pipeline_id
3675 );
3676 },
3677 };
3678 let (is_opener_private, is_opener_throttled, is_opener_secure) =
3679 match self.browsing_contexts.get(&opener_browsing_context_id) {
3680 Some(ctx) => (ctx.is_private, ctx.throttled, ctx.inherited_secure_context),
3681 None => {
3682 return warn!(
3683 "{}: New auxiliary {} loaded in closed opener browsing context",
3684 opener_browsing_context_id, new_browsing_context_id,
3685 );
3686 },
3687 };
3688 let new_pipeline_id = PipelineId::new();
3689 let pipeline = Pipeline::new_already_spawned(
3690 new_pipeline_id,
3691 new_browsing_context_id,
3692 new_webview_id,
3693 Some(opener_browsing_context_id),
3694 script_sender,
3695 self.paint_proxy.clone(),
3696 is_opener_throttled,
3697 load_data,
3698 );
3699 let _ = response_sender.send(Some(AuxiliaryWebViewCreationResponse {
3700 new_webview_id,
3701 new_pipeline_id,
3702 user_content_manager_id,
3703 }));
3704
3705 assert!(!self.pipelines.contains_key(&new_pipeline_id));
3706 self.pipelines.insert(new_pipeline_id, pipeline);
3707 self.webviews.insert(
3708 new_webview_id,
3709 ConstellationWebView::new(
3710 new_webview_id,
3711 new_browsing_context_id,
3712 user_content_manager_id,
3713 ),
3714 );
3715
3716 let Some(opener) = self.browsing_contexts.get(&opener_browsing_context_id) else {
3718 return warn!("Trying to append an unknown auxiliary to a browsing context group");
3719 };
3720 let Some(bc_group) = self.browsing_context_group_set.get_mut(&opener.bc_group_id) else {
3721 return warn!("Trying to add a top-level to an unknown group.");
3722 };
3723 bc_group
3724 .top_level_browsing_context_set
3725 .insert(new_webview_id);
3726
3727 self.add_pending_change(SessionHistoryChange {
3728 webview_id: new_webview_id,
3729 browsing_context_id: new_browsing_context_id,
3730 new_pipeline_id,
3731 replace: None,
3732 new_browsing_context_info: Some(NewBrowsingContextInfo {
3733 parent_pipeline_id: None,
3735 is_private: is_opener_private,
3736 inherited_secure_context: is_opener_secure,
3737 throttled: is_opener_throttled,
3738 }),
3739 viewport_details,
3740 });
3741 }
3742
3743 #[servo_tracing::instrument(skip_all)]
3744 fn handle_refresh_cursor(&self, pipeline_id: PipelineId) {
3745 let Some(pipeline) = self.pipelines.get(&pipeline_id) else {
3746 return;
3747 };
3748
3749 if let Err(error) = pipeline
3750 .event_loop
3751 .send(ScriptThreadMessage::RefreshCursor(pipeline_id))
3752 {
3753 warn!("Could not send RefreshCursor message to pipeline: {error:?}");
3754 }
3755 }
3756
3757 #[servo_tracing::instrument(skip_all)]
3758 fn handle_change_running_animations_state(
3759 &mut self,
3760 pipeline_id: PipelineId,
3761 animation_state: AnimationState,
3762 ) {
3763 if let Some(pipeline) = self.pipelines.get_mut(&pipeline_id) {
3764 if pipeline.animation_state != animation_state {
3765 pipeline.animation_state = animation_state;
3766 self.paint_proxy
3767 .send(PaintMessage::ChangeRunningAnimationsState(
3768 pipeline.webview_id,
3769 pipeline_id,
3770 animation_state,
3771 ))
3772 }
3773 }
3774 }
3775
3776 #[servo_tracing::instrument(skip_all)]
3777 fn handle_tick_animation(&mut self, webview_ids: Vec<WebViewId>) {
3778 let mut animating_event_loops = HashSet::new();
3779
3780 for webview_id in webview_ids.iter() {
3781 for browsing_context in self.fully_active_browsing_contexts_iter(*webview_id) {
3782 let Some(pipeline) = self.pipelines.get(&browsing_context.pipeline_id) else {
3783 continue;
3784 };
3785
3786 let event_loop = &pipeline.event_loop;
3787 if !animating_event_loops.contains(&event_loop.id()) {
3788 let _ = event_loop
3793 .send(ScriptThreadMessage::TickAllAnimations(webview_ids.clone()));
3794 animating_event_loops.insert(event_loop.id());
3795 }
3796 }
3797 }
3798 }
3799
3800 #[servo_tracing::instrument(skip_all)]
3801 fn handle_no_longer_waiting_on_asynchronous_image_updates(
3802 &mut self,
3803 pipeline_ids: Vec<PipelineId>,
3804 ) {
3805 for pipeline_id in pipeline_ids.into_iter() {
3806 if let Some(pipeline) = self.pipelines.get(&pipeline_id) {
3807 let _ = pipeline.event_loop.send(
3808 ScriptThreadMessage::NoLongerWaitingOnAsychronousImageUpdates(pipeline_id),
3809 );
3810 }
3811 }
3812 }
3813
3814 #[servo_tracing::instrument(skip_all)]
3818 fn schedule_navigation(
3819 &mut self,
3820 webview_id: WebViewId,
3821 source_id: PipelineId,
3822 load_data: LoadData,
3823 history_handling: NavigationHistoryBehavior,
3824 target_snapshot_params: TargetSnapshotParams,
3825 ) {
3826 match self.pending_approval_navigations.entry(source_id) {
3827 Entry::Occupied(_) => {
3828 return warn!(
3829 "{}: Tried to schedule a navigation while one is already pending",
3830 source_id
3831 );
3832 },
3833 Entry::Vacant(entry) => {
3834 let _ = entry.insert(PendingApprovalNavigation {
3835 load_data: load_data.clone(),
3836 history_behaviour: history_handling,
3837 target_snapshot_params,
3838 });
3839 },
3840 };
3841 self.constellation_to_embedder_proxy.send(
3843 ConstellationToEmbedderMsg::AllowNavigationRequest(
3844 webview_id,
3845 source_id,
3846 load_data.url,
3847 ),
3848 );
3849 }
3850
3851 #[servo_tracing::instrument(skip_all)]
3852 fn load_url(
3853 &mut self,
3854 webview_id: WebViewId,
3855 source_id: PipelineId,
3856 load_data: LoadData,
3857 history_handling: NavigationHistoryBehavior,
3858 target_snapshot_params: TargetSnapshotParams,
3859 ) -> Option<PipelineId> {
3860 debug!(
3861 "{}: Loading ({}replacing): {}",
3862 source_id,
3863 match history_handling {
3864 NavigationHistoryBehavior::Push => "not ",
3865 NavigationHistoryBehavior::Replace => "",
3866 NavigationHistoryBehavior::Auto => "unsure if ",
3867 },
3868 load_data.url,
3869 );
3870 let (browsing_context_id, opener) = match self.pipelines.get(&source_id) {
3877 Some(pipeline) => (pipeline.browsing_context_id, pipeline.opener),
3878 None => {
3879 warn!("{}: Loaded after closure", source_id);
3880 return None;
3881 },
3882 };
3883 let (viewport_details, pipeline_id, parent_pipeline_id, is_private, is_throttled) =
3884 match self.browsing_contexts.get(&browsing_context_id) {
3885 Some(ctx) => (
3886 ctx.viewport_details,
3887 ctx.pipeline_id,
3888 ctx.parent_pipeline_id,
3889 ctx.is_private,
3890 ctx.throttled,
3891 ),
3892 None => {
3893 warn!(
3898 "{}: Loaded url in closed {}",
3899 source_id, browsing_context_id,
3900 );
3901 return None;
3902 },
3903 };
3904
3905 if let Some(ref chan) = self.devtools_sender {
3906 let state = NavigationState::Start(load_data.url.clone());
3907 let _ = chan.send(DevtoolsControlMsg::FromScript(
3908 ScriptToDevtoolsControlMsg::Navigate(browsing_context_id, state),
3909 ));
3910 }
3911
3912 match parent_pipeline_id {
3913 Some(parent_pipeline_id) => {
3914 let msg = ScriptThreadMessage::NavigateIframe(
3917 parent_pipeline_id,
3918 browsing_context_id,
3919 load_data,
3920 history_handling,
3921 target_snapshot_params,
3922 );
3923 let result = match self.pipelines.get(&parent_pipeline_id) {
3924 Some(parent_pipeline) => parent_pipeline.event_loop.send(msg),
3925 None => {
3926 warn!("{}: Child loaded after closure", parent_pipeline_id);
3927 return None;
3928 },
3929 };
3930 if let Err(e) = result {
3931 self.handle_send_error(parent_pipeline_id, e);
3932 } else if let Some((sender, id)) = &self.webdriver_load_status_sender {
3933 if source_id == *id {
3934 let _ = sender.send(WebDriverLoadStatus::NavigationStop);
3935 }
3936 }
3937
3938 None
3939 },
3940 None => {
3941 for change in &self.pending_changes {
3943 if change.browsing_context_id == browsing_context_id {
3944 return None;
3946 }
3947 }
3948
3949 if self.get_activity(source_id) == DocumentActivity::Inactive {
3950 return None;
3955 }
3956
3957 let replace = if history_handling == NavigationHistoryBehavior::Replace {
3963 Some(NeedsToReload::No(pipeline_id))
3964 } else {
3965 None
3966 };
3967
3968 let new_pipeline_id = PipelineId::new();
3969 self.new_pipeline(
3970 new_pipeline_id,
3971 browsing_context_id,
3972 webview_id,
3973 None,
3974 opener,
3975 viewport_details,
3976 load_data,
3977 is_private,
3978 is_throttled,
3979 target_snapshot_params,
3980 );
3981 self.add_pending_change(SessionHistoryChange {
3982 webview_id,
3983 browsing_context_id,
3984 new_pipeline_id,
3985 replace,
3986 new_browsing_context_info: None,
3988 viewport_details,
3989 });
3990 self.paint_proxy
3991 .send(PaintMessage::EnableLCPCalculation(webview_id));
3992 Some(new_pipeline_id)
3993 },
3994 }
3995 }
3996
3997 #[servo_tracing::instrument(skip_all)]
3998 fn handle_abort_load_url_msg(&mut self, new_pipeline_id: PipelineId) {
3999 let pending_index = self
4000 .pending_changes
4001 .iter()
4002 .rposition(|change| change.new_pipeline_id == new_pipeline_id);
4003
4004 if let Some(pending_index) = pending_index {
4006 self.pending_changes.remove(pending_index);
4007 self.close_pipeline(
4008 new_pipeline_id,
4009 DiscardBrowsingContext::No,
4010 ExitPipelineMode::Normal,
4011 );
4012 }
4013
4014 self.send_screenshot_readiness_requests_to_pipelines();
4015 }
4016
4017 #[servo_tracing::instrument(skip_all)]
4018 fn handle_load_complete_msg(&mut self, webview_id: WebViewId, pipeline_id: PipelineId) {
4019 if let Some(pipeline) = self.pipelines.get_mut(&pipeline_id) {
4020 debug!("{}: Marking as loaded", pipeline_id);
4021 pipeline.completely_loaded = true;
4022 }
4023
4024 let pipeline_is_top_level_pipeline = self
4029 .browsing_contexts
4030 .get(&BrowsingContextId::from(webview_id))
4031 .is_some_and(|ctx| ctx.pipeline_id == pipeline_id);
4032 if !pipeline_is_top_level_pipeline {
4033 self.handle_subframe_loaded(pipeline_id);
4034 }
4035 }
4036
4037 #[servo_tracing::instrument(skip_all)]
4038 fn handle_navigated_to_fragment(
4039 &mut self,
4040 pipeline_id: PipelineId,
4041 new_url: ServoUrl,
4042 history_handling: NavigationHistoryBehavior,
4043 ) {
4044 let (webview_id, old_url) = match self.pipelines.get_mut(&pipeline_id) {
4045 Some(pipeline) => {
4046 let old_url = replace(&mut pipeline.url, new_url.clone());
4047 (pipeline.webview_id, old_url)
4048 },
4049 None => {
4050 return warn!("{}: Navigated to fragment after closure", pipeline_id);
4051 },
4052 };
4053
4054 let Some(webview) = self.webviews.get_mut(&webview_id) else {
4055 return warn!("Ignoring navigation in non-existent WebView ({webview_id:?}).");
4056 };
4057
4058 match history_handling {
4059 NavigationHistoryBehavior::Replace => {},
4060 _ => {
4061 let diff = SessionHistoryDiff::Hash {
4062 pipeline_reloader: NeedsToReload::No(pipeline_id),
4063 new_url,
4064 old_url,
4065 };
4066
4067 webview.session_history.push_diff(diff);
4068 self.notify_history_changed(webview_id);
4069 },
4070 }
4071 }
4072
4073 #[servo_tracing::instrument(skip_all)]
4074 fn handle_traverse_history_msg(
4075 &mut self,
4076 webview_id: WebViewId,
4077 direction: TraversalDirection,
4078 ) {
4079 let mut browsing_context_changes = FxHashMap::<BrowsingContextId, NeedsToReload>::default();
4080 let mut pipeline_changes =
4081 FxHashMap::<PipelineId, (Option<HistoryStateId>, ServoUrl)>::default();
4082 let mut url_to_load = FxHashMap::<PipelineId, ServoUrl>::default();
4083 {
4084 let Some(webview) = self.webviews.get_mut(&webview_id) else {
4085 return warn!(
4086 "Ignoring history traversal in non-existent WebView ({webview_id:?})."
4087 );
4088 };
4089
4090 match direction {
4091 TraversalDirection::Forward(forward) => {
4092 let future_length = webview.session_history.future.len();
4093
4094 if future_length < forward {
4095 return warn!("Cannot traverse that far into the future.");
4096 }
4097
4098 for diff in webview
4099 .session_history
4100 .future
4101 .drain(future_length - forward..)
4102 .rev()
4103 {
4104 match diff {
4105 SessionHistoryDiff::BrowsingContext {
4106 browsing_context_id,
4107 ref new_reloader,
4108 ..
4109 } => {
4110 browsing_context_changes
4111 .insert(browsing_context_id, new_reloader.clone());
4112 },
4113 SessionHistoryDiff::Pipeline {
4114 ref pipeline_reloader,
4115 new_history_state_id,
4116 ref new_url,
4117 ..
4118 } => match *pipeline_reloader {
4119 NeedsToReload::No(pipeline_id) => {
4120 pipeline_changes.insert(
4121 pipeline_id,
4122 (Some(new_history_state_id), new_url.clone()),
4123 );
4124 },
4125 NeedsToReload::Yes(pipeline_id, ..) => {
4126 url_to_load.insert(pipeline_id, new_url.clone());
4127 },
4128 },
4129 SessionHistoryDiff::Hash {
4130 ref pipeline_reloader,
4131 ref new_url,
4132 ..
4133 } => match *pipeline_reloader {
4134 NeedsToReload::No(pipeline_id) => {
4135 let state = pipeline_changes
4136 .get(&pipeline_id)
4137 .and_then(|change| change.0);
4138 pipeline_changes.insert(pipeline_id, (state, new_url.clone()));
4139 },
4140 NeedsToReload::Yes(pipeline_id, ..) => {
4141 url_to_load.insert(pipeline_id, new_url.clone());
4142 },
4143 },
4144 }
4145 webview.session_history.past.push(diff);
4146 }
4147 },
4148 TraversalDirection::Back(back) => {
4149 let past_length = webview.session_history.past.len();
4150
4151 if past_length < back {
4152 return warn!("Cannot traverse that far into the past.");
4153 }
4154
4155 for diff in webview
4156 .session_history
4157 .past
4158 .drain(past_length - back..)
4159 .rev()
4160 {
4161 match diff {
4162 SessionHistoryDiff::BrowsingContext {
4163 browsing_context_id,
4164 ref old_reloader,
4165 ..
4166 } => {
4167 browsing_context_changes
4168 .insert(browsing_context_id, old_reloader.clone());
4169 },
4170 SessionHistoryDiff::Pipeline {
4171 ref pipeline_reloader,
4172 old_history_state_id,
4173 ref old_url,
4174 ..
4175 } => match *pipeline_reloader {
4176 NeedsToReload::No(pipeline_id) => {
4177 pipeline_changes.insert(
4178 pipeline_id,
4179 (old_history_state_id, old_url.clone()),
4180 );
4181 },
4182 NeedsToReload::Yes(pipeline_id, ..) => {
4183 url_to_load.insert(pipeline_id, old_url.clone());
4184 },
4185 },
4186 SessionHistoryDiff::Hash {
4187 ref pipeline_reloader,
4188 ref old_url,
4189 ..
4190 } => match *pipeline_reloader {
4191 NeedsToReload::No(pipeline_id) => {
4192 let state = pipeline_changes
4193 .get(&pipeline_id)
4194 .and_then(|change| change.0);
4195 pipeline_changes.insert(pipeline_id, (state, old_url.clone()));
4196 },
4197 NeedsToReload::Yes(pipeline_id, ..) => {
4198 url_to_load.insert(pipeline_id, old_url.clone());
4199 },
4200 },
4201 }
4202 webview.session_history.future.push(diff);
4203 }
4204 },
4205 }
4206 }
4207
4208 for (browsing_context_id, mut pipeline_reloader) in browsing_context_changes.drain() {
4209 if let NeedsToReload::Yes(pipeline_id, ref mut load_data) = pipeline_reloader {
4210 if let Some(url) = url_to_load.get(&pipeline_id) {
4211 load_data.url = url.clone();
4212 }
4213 }
4214 self.update_browsing_context(browsing_context_id, pipeline_reloader);
4215 }
4216
4217 for (pipeline_id, (history_state_id, url)) in pipeline_changes.drain() {
4218 self.update_pipeline(pipeline_id, history_state_id, url);
4219 }
4220
4221 self.notify_history_changed(webview_id);
4222
4223 self.trim_history(webview_id);
4224 self.set_frame_tree_for_webview(webview_id);
4225 }
4226
4227 #[servo_tracing::instrument(skip_all)]
4228 fn update_browsing_context(
4229 &mut self,
4230 browsing_context_id: BrowsingContextId,
4231 new_reloader: NeedsToReload,
4232 ) {
4233 let new_pipeline_id = match new_reloader {
4234 NeedsToReload::No(pipeline_id) => pipeline_id,
4235 NeedsToReload::Yes(pipeline_id, load_data) => {
4236 debug!(
4237 "{}: Reloading document {}",
4238 browsing_context_id, pipeline_id,
4239 );
4240
4241 let (
4242 webview_id,
4243 old_pipeline_id,
4244 parent_pipeline_id,
4245 viewport_details,
4246 is_private,
4247 throttled,
4248 ) = match self.browsing_contexts.get(&browsing_context_id) {
4249 Some(ctx) => (
4250 ctx.webview_id,
4251 ctx.pipeline_id,
4252 ctx.parent_pipeline_id,
4253 ctx.viewport_details,
4254 ctx.is_private,
4255 ctx.throttled,
4256 ),
4257 None => return warn!("No browsing context to traverse!"),
4258 };
4259 let opener = match self.pipelines.get(&old_pipeline_id) {
4260 Some(pipeline) => pipeline.opener,
4261 None => None,
4262 };
4263 let new_pipeline_id = PipelineId::new();
4264 self.new_pipeline(
4265 new_pipeline_id,
4266 browsing_context_id,
4267 webview_id,
4268 parent_pipeline_id,
4269 opener,
4270 viewport_details,
4271 load_data.clone(),
4272 is_private,
4273 throttled,
4274 TargetSnapshotParams::default(),
4278 );
4279 self.add_pending_change(SessionHistoryChange {
4280 webview_id,
4281 browsing_context_id,
4282 new_pipeline_id,
4283 replace: Some(NeedsToReload::Yes(pipeline_id, load_data)),
4284 new_browsing_context_info: None,
4286 viewport_details,
4287 });
4288 return;
4289 },
4290 };
4291
4292 let (old_pipeline_id, parent_pipeline_id, webview_id) =
4293 match self.browsing_contexts.get_mut(&browsing_context_id) {
4294 Some(browsing_context) => {
4295 let old_pipeline_id = browsing_context.pipeline_id;
4296 browsing_context.update_current_entry(new_pipeline_id);
4297 (
4298 old_pipeline_id,
4299 browsing_context.parent_pipeline_id,
4300 browsing_context.webview_id,
4301 )
4302 },
4303 None => {
4304 return warn!("{}: Closed during traversal", browsing_context_id);
4305 },
4306 };
4307
4308 self.unload_document(old_pipeline_id);
4309
4310 if let Some(new_pipeline) = self.pipelines.get(&new_pipeline_id) {
4311 if let Some(ref chan) = self.devtools_sender {
4312 let state = NavigationState::Start(new_pipeline.url.clone());
4313 let _ = chan.send(DevtoolsControlMsg::FromScript(
4314 ScriptToDevtoolsControlMsg::Navigate(browsing_context_id, state),
4315 ));
4316 let page_info = DevtoolsPageInfo {
4317 title: new_pipeline.title.clone(),
4318 url: new_pipeline.url.clone(),
4319 is_top_level_global: webview_id == browsing_context_id,
4320 is_service_worker: false,
4321 };
4322 let state = NavigationState::Stop(new_pipeline.id, page_info);
4323 let _ = chan.send(DevtoolsControlMsg::FromScript(
4324 ScriptToDevtoolsControlMsg::Navigate(browsing_context_id, state),
4325 ));
4326 }
4327
4328 new_pipeline.set_throttled(false);
4329 self.notify_focus_state(new_pipeline_id);
4330 }
4331
4332 self.update_activity(old_pipeline_id);
4333 self.update_activity(new_pipeline_id);
4334
4335 if let Some(parent_pipeline_id) = parent_pipeline_id {
4336 let msg = ScriptThreadMessage::UpdatePipelineId(
4337 parent_pipeline_id,
4338 browsing_context_id,
4339 webview_id,
4340 new_pipeline_id,
4341 UpdatePipelineIdReason::Traversal,
4342 );
4343 self.send_message_to_pipeline(parent_pipeline_id, msg, "Child traversed after closure");
4344 }
4345 }
4346
4347 #[servo_tracing::instrument(skip_all)]
4348 fn update_pipeline(
4349 &mut self,
4350 pipeline_id: PipelineId,
4351 history_state_id: Option<HistoryStateId>,
4352 url: ServoUrl,
4353 ) {
4354 let msg = ScriptThreadMessage::UpdateHistoryState(pipeline_id, history_state_id, url);
4355 self.send_message_to_pipeline(pipeline_id, msg, "History state updated after closure");
4356 }
4357
4358 #[servo_tracing::instrument(skip_all)]
4359 fn handle_joint_session_history_length(
4360 &self,
4361 webview_id: WebViewId,
4362 response_sender: GenericSender<u32>,
4363 ) {
4364 let length = self
4365 .webviews
4366 .get(&webview_id)
4367 .map(|webview| webview.session_history.history_length())
4368 .unwrap_or(1);
4369 let _ = response_sender.send(length as u32);
4370 }
4371
4372 #[servo_tracing::instrument(skip_all)]
4373 fn handle_push_history_state_msg(
4374 &mut self,
4375 pipeline_id: PipelineId,
4376 history_state_id: HistoryStateId,
4377 url: ServoUrl,
4378 ) {
4379 let (webview_id, old_state_id, old_url) = match self.pipelines.get_mut(&pipeline_id) {
4380 Some(pipeline) => {
4381 let old_history_state_id = pipeline.history_state_id;
4382 let old_url = replace(&mut pipeline.url, url.clone());
4383 pipeline.history_state_id = Some(history_state_id);
4384 pipeline.history_states.insert(history_state_id);
4385 (pipeline.webview_id, old_history_state_id, old_url)
4386 },
4387 None => {
4388 return warn!(
4389 "{}: Push history state {} for closed pipeline",
4390 pipeline_id, history_state_id,
4391 );
4392 },
4393 };
4394
4395 let Some(webview) = self.webviews.get_mut(&webview_id) else {
4396 return warn!("Ignoring history change in non-existent WebView ({webview_id:?}).");
4397 };
4398
4399 let diff = SessionHistoryDiff::Pipeline {
4400 pipeline_reloader: NeedsToReload::No(pipeline_id),
4401 new_history_state_id: history_state_id,
4402 new_url: url,
4403 old_history_state_id: old_state_id,
4404 old_url,
4405 };
4406 webview.session_history.push_diff(diff);
4407 self.notify_history_changed(webview_id);
4408 }
4409
4410 #[servo_tracing::instrument(skip_all)]
4411 fn handle_replace_history_state_msg(
4412 &mut self,
4413 pipeline_id: PipelineId,
4414 history_state_id: HistoryStateId,
4415 url: ServoUrl,
4416 ) {
4417 let webview_id = match self.pipelines.get_mut(&pipeline_id) {
4418 Some(pipeline) => {
4419 pipeline.history_state_id = Some(history_state_id);
4420 pipeline.url = url.clone();
4421 pipeline.webview_id
4422 },
4423 None => {
4424 return warn!(
4425 "{}: Replace history state {} for closed pipeline",
4426 history_state_id, pipeline_id
4427 );
4428 },
4429 };
4430
4431 let Some(webview) = self.webviews.get_mut(&webview_id) else {
4432 return warn!("Ignoring history change in non-existent WebView ({webview_id:?}).");
4433 };
4434
4435 webview
4436 .session_history
4437 .replace_history_state(pipeline_id, history_state_id, url);
4438 self.notify_history_changed(webview_id);
4439 }
4440
4441 #[servo_tracing::instrument(skip_all)]
4442 fn handle_reload_msg(&mut self, webview_id: WebViewId) {
4443 let browsing_context_id = BrowsingContextId::from(webview_id);
4444 let pipeline_id = match self.browsing_contexts.get(&browsing_context_id) {
4445 Some(browsing_context) => browsing_context.pipeline_id,
4446 None => {
4447 return warn!("{}: Got reload event after closure", browsing_context_id);
4448 },
4449 };
4450 self.send_message_to_pipeline(
4451 pipeline_id,
4452 ScriptThreadMessage::Reload(pipeline_id),
4453 "Got reload event after closure",
4454 );
4455 self.paint_proxy
4456 .send(PaintMessage::EnableLCPCalculation(webview_id));
4457 }
4458
4459 #[servo_tracing::instrument(skip_all)]
4461 fn handle_post_message_msg(
4462 &mut self,
4463 browsing_context_id: BrowsingContextId,
4464 source_pipeline: PipelineId,
4465 origin: Option<ImmutableOrigin>,
4466 source_origin: ImmutableOrigin,
4467 data: StructuredSerializedData,
4468 ) {
4469 let pipeline_id = match self.browsing_contexts.get(&browsing_context_id) {
4470 None => {
4471 return warn!(
4472 "{}: PostMessage to closed browsing context",
4473 browsing_context_id
4474 );
4475 },
4476 Some(browsing_context) => browsing_context.pipeline_id,
4477 };
4478 let source_webview = match self.pipelines.get(&source_pipeline) {
4479 Some(pipeline) => pipeline.webview_id,
4480 None => return warn!("{}: PostMessage from closed pipeline", source_pipeline),
4481 };
4482
4483 let browsing_context_for_pipeline = |pipeline_id| {
4484 self.pipelines
4485 .get(&pipeline_id)
4486 .and_then(|pipeline| self.browsing_contexts.get(&pipeline.browsing_context_id))
4487 };
4488 let mut maybe_browsing_context = browsing_context_for_pipeline(source_pipeline);
4489 if maybe_browsing_context.is_none() {
4490 return warn!("{source_pipeline}: PostMessage from pipeline with closed parent");
4491 }
4492
4493 let mut source_with_ancestry = vec![];
4499 while let Some(browsing_context) = maybe_browsing_context {
4500 source_with_ancestry.push(browsing_context.id);
4501 maybe_browsing_context = browsing_context
4502 .parent_pipeline_id
4503 .and_then(browsing_context_for_pipeline);
4504 }
4505 let msg = ScriptThreadMessage::PostMessage {
4506 target: pipeline_id,
4507 source_webview,
4508 source_with_ancestry,
4509 target_origin: origin,
4510 source_origin,
4511 data: Box::new(data),
4512 };
4513 self.send_message_to_pipeline(pipeline_id, msg, "PostMessage to closed pipeline");
4514 }
4515
4516 #[servo_tracing::instrument(skip_all)]
4517 fn handle_focus_ancestor_browsing_contexts_for_focusing_steps(
4518 &mut self,
4519 pipeline_id: PipelineId,
4520 focused_child_browsing_context_id: Option<BrowsingContextId>,
4521 sequence: FocusSequenceNumber,
4522 ) {
4523 let (browsing_context_id, webview_id) = match self.pipelines.get_mut(&pipeline_id) {
4524 Some(pipeline) => {
4525 pipeline.focus_sequence = sequence;
4526 (pipeline.browsing_context_id, pipeline.webview_id)
4527 },
4528 None => return warn!("{}: Focus parent after closure", pipeline_id),
4529 };
4530
4531 if self.get_activity(pipeline_id) != DocumentActivity::FullyActive {
4533 debug!(
4534 "Ignoring the focus request because pipeline {} is not \
4535 fully active",
4536 pipeline_id
4537 );
4538 return;
4539 }
4540
4541 self.constellation_to_embedder_proxy
4543 .send(ConstellationToEmbedderMsg::WebViewFocused(webview_id, true));
4544
4545 let focused_browsing_context_id =
4549 focused_child_browsing_context_id.unwrap_or(browsing_context_id);
4550
4551 self.focus_browsing_context(Some(pipeline_id), focused_browsing_context_id);
4555 }
4556
4557 fn handle_focus_remote_browsing_context(
4558 &mut self,
4559 target: BrowsingContextId,
4560 operation: RemoteFocusOperation,
4561 ) {
4562 let Some(browsing_context) = self.browsing_contexts.get(&target) else {
4563 return warn!("{target:?} not found for focus message");
4564 };
4565 let pipeline_id = browsing_context.pipeline_id;
4566 let Some(pipeline) = self.pipelines.get(&pipeline_id) else {
4567 return warn!("{pipeline_id:?} not found for focus message");
4568 };
4569 if let Err(error) = pipeline
4570 .event_loop
4571 .send(ScriptThreadMessage::FocusDocument(pipeline_id, operation))
4572 {
4573 self.handle_send_error(pipeline_id, error);
4574 }
4575 }
4576
4577 #[servo_tracing::instrument(skip_all)]
4588 fn focus_browsing_context(
4589 &mut self,
4590 initiator_pipeline_id: Option<PipelineId>,
4591 focused_browsing_context_id: BrowsingContextId,
4592 ) {
4593 let webview_id = match self.browsing_contexts.get(&focused_browsing_context_id) {
4594 Some(browsing_context) => browsing_context.webview_id,
4595 None => return warn!("Browsing context {} not found", focused_browsing_context_id),
4596 };
4597
4598 let old_focused_browsing_context_id = match self.webviews.get_mut(&webview_id) {
4600 Some(browser) => replace(
4601 &mut browser.focused_browsing_context_id,
4602 focused_browsing_context_id,
4603 ),
4604 None => {
4605 return warn!(
4606 "{}: Browsing context for focus msg does not exist",
4607 webview_id
4608 );
4609 },
4610 };
4611
4612 let mut old_focus_chain_pipelines: Vec<&Pipeline> = self
4619 .ancestor_or_self_pipelines_of_browsing_context_iter(old_focused_browsing_context_id)
4620 .collect();
4621 let mut new_focus_chain_pipelines: Vec<&Pipeline> = self
4622 .ancestor_or_self_pipelines_of_browsing_context_iter(focused_browsing_context_id)
4623 .collect();
4624
4625 debug!(
4626 "old_focus_chain_pipelines = {:?}",
4627 old_focus_chain_pipelines
4628 .iter()
4629 .map(|p| p.id.to_string())
4630 .collect::<Vec<_>>()
4631 );
4632 debug!(
4633 "new_focus_chain_pipelines = {:?}",
4634 new_focus_chain_pipelines
4635 .iter()
4636 .map(|p| p.id.to_string())
4637 .collect::<Vec<_>>()
4638 );
4639
4640 match (
4644 &old_focus_chain_pipelines[..],
4645 &new_focus_chain_pipelines[..],
4646 ) {
4647 ([.., p1], [.., p2]) if p1.id == p2.id => {},
4648 _ => {
4649 warn!("Aborting the focus operation - focus chain sanity check failed");
4650 return;
4651 },
4652 }
4653
4654 let mut first_common_pipeline_in_chain = None;
4658 while let ([.., p1], [.., p2]) = (
4659 &old_focus_chain_pipelines[..],
4660 &new_focus_chain_pipelines[..],
4661 ) {
4662 if p1.id != p2.id {
4663 break;
4664 }
4665 old_focus_chain_pipelines.pop();
4666 first_common_pipeline_in_chain = new_focus_chain_pipelines.pop();
4667 }
4668
4669 let mut send_errors = Vec::new();
4670
4671 for &pipeline in old_focus_chain_pipelines.iter() {
4674 if Some(pipeline.id) != initiator_pipeline_id {
4675 let msg = ScriptThreadMessage::UnfocusDocumentAsPartOfFocusingSteps(
4676 pipeline.id,
4677 pipeline.focus_sequence,
4678 );
4679 trace!("Sending {:?} to {}", msg, pipeline.id);
4680 if let Err(e) = pipeline.event_loop.send(msg) {
4681 send_errors.push((pipeline.id, e));
4682 }
4683 } else {
4684 trace!(
4685 "Not notifying {} - it's the initiator of this focus operation",
4686 pipeline.id
4687 );
4688 }
4689 }
4690
4691 let mut child_browsing_context_id = None;
4694 for &pipeline in new_focus_chain_pipelines.iter().rev() {
4695 if Some(pipeline.id) != initiator_pipeline_id {
4698 if let Err(error) = pipeline.event_loop.send(
4699 ScriptThreadMessage::FocusDocumentAsPartOfFocusingSteps(
4700 pipeline.id,
4701 pipeline.focus_sequence,
4702 child_browsing_context_id,
4703 ),
4704 ) {
4705 send_errors.push((pipeline.id, error));
4706 }
4707 }
4708 child_browsing_context_id = Some(pipeline.browsing_context_id);
4709 }
4710
4711 if let Some(pipeline) = first_common_pipeline_in_chain {
4712 if Some(pipeline.id) != initiator_pipeline_id {
4713 if let Err(error) = pipeline.event_loop.send(
4714 ScriptThreadMessage::FocusDocumentAsPartOfFocusingSteps(
4715 pipeline.id,
4716 pipeline.focus_sequence,
4717 child_browsing_context_id,
4718 ),
4719 ) {
4720 send_errors.push((pipeline.id, error));
4721 }
4722 }
4723 }
4724
4725 for (pipeline_id, error) in send_errors {
4726 self.handle_send_error(pipeline_id, error);
4727 }
4728 }
4729
4730 #[servo_tracing::instrument(skip_all)]
4731 fn handle_remove_iframe_msg(
4732 &mut self,
4733 browsing_context_id: BrowsingContextId,
4734 ) -> Vec<PipelineId> {
4735 let result = self
4736 .all_descendant_browsing_contexts_iter(browsing_context_id)
4737 .flat_map(|browsing_context| browsing_context.pipelines.iter().cloned())
4738 .collect();
4739 self.close_browsing_context(browsing_context_id, ExitPipelineMode::Normal);
4740 result
4741 }
4742
4743 #[servo_tracing::instrument(skip_all)]
4744 fn handle_set_throttled_complete(&mut self, pipeline_id: PipelineId, throttled: bool) {
4745 let Some(pipeline) = self.pipelines.get(&pipeline_id) else {
4746 return warn!("{pipeline_id}: Visibility change for closed browsing context",);
4747 };
4748 let Some(browsing_context) = self.browsing_contexts.get(&pipeline.browsing_context_id)
4749 else {
4750 return warn!("{}: Visibility change for closed pipeline", pipeline_id);
4751 };
4752 let Some(parent_pipeline_id) = browsing_context.parent_pipeline_id else {
4753 return;
4754 };
4755
4756 let msg = ScriptThreadMessage::SetThrottledInContainingIframe(
4757 pipeline.webview_id,
4758 parent_pipeline_id,
4759 browsing_context.id,
4760 throttled,
4761 );
4762 self.send_message_to_pipeline(parent_pipeline_id, msg, "Parent pipeline closed");
4763 }
4764
4765 #[servo_tracing::instrument(skip_all)]
4766 fn handle_create_canvas_paint_thread_msg(
4767 &mut self,
4768 size: UntypedSize2D<u64>,
4769 response_sender: GenericSender<Option<(GenericSender<CanvasMsg>, CanvasId)>>,
4770 ) {
4771 let (canvas_data_sender, canvas_data_receiver) = unbounded();
4772 let (canvas_sender, canvas_ipc_sender) = self
4773 .canvas
4774 .get_or_init(|| self.create_canvas_paint_thread());
4775
4776 let response = if let Err(e) = canvas_sender.send(ConstellationCanvasMsg::Create {
4777 sender: canvas_data_sender,
4778 size,
4779 }) {
4780 warn!("Create canvas paint thread failed ({})", e);
4781 None
4782 } else {
4783 match canvas_data_receiver.recv() {
4784 Ok(Some(canvas_id)) => Some((canvas_ipc_sender.clone(), canvas_id)),
4785 Ok(None) => None,
4786 Err(e) => {
4787 warn!("Create canvas paint thread id response failed ({})", e);
4788 None
4789 },
4790 }
4791 };
4792 if let Err(e) = response_sender.send(response) {
4793 warn!("Create canvas paint thread response failed ({})", e);
4794 }
4795 }
4796
4797 #[servo_tracing::instrument(skip_all)]
4798 fn handle_webdriver_msg(&mut self, msg: WebDriverCommandMsg) {
4799 match msg {
4802 WebDriverCommandMsg::IsBrowsingContextOpen(browsing_context_id, response_sender) => {
4803 let is_open = self.browsing_contexts.contains_key(&browsing_context_id);
4804 let _ = response_sender.send(is_open);
4805 },
4806 WebDriverCommandMsg::FocusBrowsingContext(browsing_context_id) => {
4807 self.handle_focus_remote_browsing_context(
4808 browsing_context_id,
4809 RemoteFocusOperation::Viewport,
4810 );
4811 },
4812 WebDriverCommandMsg::ScriptCommand(browsing_context_id, cmd) => {
4814 let pipeline_id = if let Some(browsing_context) =
4815 self.browsing_contexts.get(&browsing_context_id)
4816 {
4817 browsing_context.pipeline_id
4818 } else {
4819 return warn!("{}: Browsing context is not ready", browsing_context_id);
4820 };
4821
4822 match &cmd {
4823 WebDriverScriptCommand::AddLoadStatusSender(_, sender) => {
4824 self.webdriver_load_status_sender = Some((sender.clone(), pipeline_id));
4825 },
4826 WebDriverScriptCommand::RemoveLoadStatusSender(_) => {
4827 self.webdriver_load_status_sender = None;
4828 },
4829 _ => {},
4830 };
4831
4832 let control_msg = ScriptThreadMessage::WebDriverScriptCommand(pipeline_id, cmd);
4833 self.send_message_to_pipeline(
4834 pipeline_id,
4835 control_msg,
4836 "ScriptCommand after closure",
4837 );
4838 },
4839 WebDriverCommandMsg::CloseWebView(..) |
4840 WebDriverCommandMsg::NewWindow(..) |
4841 WebDriverCommandMsg::FocusWebView(..) |
4842 WebDriverCommandMsg::IsWebViewOpen(..) |
4843 WebDriverCommandMsg::GetWindowRect(..) |
4844 WebDriverCommandMsg::GetViewportSize(..) |
4845 WebDriverCommandMsg::SetWindowRect(..) |
4846 WebDriverCommandMsg::MaximizeWebView(..) |
4847 WebDriverCommandMsg::LoadUrl(..) |
4848 WebDriverCommandMsg::Refresh(..) |
4849 WebDriverCommandMsg::InputEvent(..) |
4850 WebDriverCommandMsg::TakeScreenshot(..) => {
4851 unreachable!("This command should be send directly to the embedder.");
4852 },
4853 _ => {
4854 warn!("Unhandled WebDriver command: {:?}", msg);
4855 },
4856 }
4857 }
4858
4859 #[servo_tracing::instrument(skip_all)]
4860 fn set_webview_throttled(&mut self, webview_id: WebViewId, throttled: bool) {
4861 let browsing_context_id = BrowsingContextId::from(webview_id);
4862 let pipeline_id = match self.browsing_contexts.get(&browsing_context_id) {
4863 Some(browsing_context) => browsing_context.pipeline_id,
4864 None => {
4865 return warn!("{browsing_context_id}: Tried to SetWebViewThrottled after closure");
4866 },
4867 };
4868 match self.pipelines.get(&pipeline_id) {
4869 None => warn!("{pipeline_id}: Tried to SetWebViewThrottled after closure"),
4870 Some(pipeline) => pipeline.set_throttled(throttled),
4871 }
4872 }
4873
4874 #[servo_tracing::instrument(skip_all)]
4875 fn notify_history_changed(&self, webview_id: WebViewId) {
4876 let session_history = match self.webviews.get(&webview_id) {
4883 Some(webview) => &webview.session_history,
4884 None => {
4885 return warn!(
4886 "{}: Session history does not exist for browsing context",
4887 webview_id
4888 );
4889 },
4890 };
4891
4892 let browsing_context_id = BrowsingContextId::from(webview_id);
4893 let Some(browsing_context) = self.browsing_contexts.get(&browsing_context_id) else {
4894 return warn!("notify_history_changed error after top-level browsing context closed.");
4895 };
4896
4897 let current_url = match self.pipelines.get(&browsing_context.pipeline_id) {
4898 Some(pipeline) => pipeline.url.clone(),
4899 None => {
4900 return warn!("{}: Refresh after closure", browsing_context.pipeline_id);
4901 },
4902 };
4903
4904 let resolve_url_future =
4907 |previous_url: &mut ServoUrl, diff: &SessionHistoryDiff| match *diff {
4908 SessionHistoryDiff::BrowsingContext {
4909 browsing_context_id,
4910 ref new_reloader,
4911 ..
4912 } => {
4913 if browsing_context_id == webview_id {
4914 let url = match *new_reloader {
4915 NeedsToReload::No(pipeline_id) => {
4916 match self.pipelines.get(&pipeline_id) {
4917 Some(pipeline) => pipeline.url.clone(),
4918 None => previous_url.clone(),
4919 }
4920 },
4921 NeedsToReload::Yes(_, ref load_data) => load_data.url.clone(),
4922 };
4923 *previous_url = url.clone();
4924 Some(url)
4925 } else {
4926 Some(previous_url.clone())
4927 }
4928 },
4929 _ => Some(previous_url.clone()),
4930 };
4931
4932 let resolve_url_past = |previous_url: &mut ServoUrl, diff: &SessionHistoryDiff| match *diff
4933 {
4934 SessionHistoryDiff::BrowsingContext {
4935 browsing_context_id,
4936 ref old_reloader,
4937 ..
4938 } => {
4939 if browsing_context_id == webview_id {
4940 let url = match *old_reloader {
4941 NeedsToReload::No(pipeline_id) => match self.pipelines.get(&pipeline_id) {
4942 Some(pipeline) => pipeline.url.clone(),
4943 None => previous_url.clone(),
4944 },
4945 NeedsToReload::Yes(_, ref load_data) => load_data.url.clone(),
4946 };
4947 *previous_url = url.clone();
4948 Some(url)
4949 } else {
4950 Some(previous_url.clone())
4951 }
4952 },
4953 _ => Some(previous_url.clone()),
4954 };
4955
4956 let mut entries: Vec<ServoUrl> = session_history
4957 .past
4958 .iter()
4959 .rev()
4960 .scan(current_url.clone(), &resolve_url_past)
4961 .collect();
4962
4963 entries.reverse();
4964
4965 let current_index = entries.len();
4966
4967 entries.push(current_url.clone());
4968
4969 entries.extend(
4970 session_history
4971 .future
4972 .iter()
4973 .rev()
4974 .scan(current_url, &resolve_url_future),
4975 );
4976 self.constellation_to_embedder_proxy
4977 .send(ConstellationToEmbedderMsg::HistoryChanged(
4978 webview_id,
4979 entries,
4980 current_index,
4981 ));
4982 }
4983
4984 #[servo_tracing::instrument(skip_all)]
4985 fn change_session_history(&mut self, change: SessionHistoryChange) {
4986 debug!(
4987 "{}: Setting to {}",
4988 change.browsing_context_id, change.new_pipeline_id
4989 );
4990
4991 if self.focused_browsing_context_is_descendant_of(&change) {
4995 if let Some(webview) = self.webviews.get_mut(&change.webview_id) {
4996 webview.focused_browsing_context_id = change.browsing_context_id;
4997 }
4998 }
4999
5000 let (old_pipeline_id, webview_id) =
5001 match self.browsing_contexts.get_mut(&change.browsing_context_id) {
5002 Some(browsing_context) => {
5003 debug!("Adding pipeline to existing browsing context.");
5004 let old_pipeline_id = browsing_context.pipeline_id;
5005 browsing_context.pipelines.insert(change.new_pipeline_id);
5006 browsing_context.update_current_entry(change.new_pipeline_id);
5007 (Some(old_pipeline_id), Some(browsing_context.webview_id))
5008 },
5009 None => {
5010 debug!("Adding pipeline to new browsing context.");
5011 (None, None)
5012 },
5013 };
5014
5015 if let Some(old_pipeline_id) = old_pipeline_id {
5016 self.unload_document(old_pipeline_id);
5017 }
5018
5019 let Some(webview) = self.webviews.get_mut(&change.webview_id) else {
5020 return warn!("Ignoring history change in non-existent WebView ({webview_id:?}).");
5021 };
5022
5023 match old_pipeline_id {
5024 None => {
5025 let Some(new_context_info) = change.new_browsing_context_info else {
5026 return warn!(
5027 "{}: No NewBrowsingContextInfo for browsing context",
5028 change.browsing_context_id,
5029 );
5030 };
5031 self.new_browsing_context(
5032 change.browsing_context_id,
5033 change.webview_id,
5034 change.new_pipeline_id,
5035 new_context_info.parent_pipeline_id,
5036 change.viewport_details,
5037 new_context_info.is_private,
5038 new_context_info.inherited_secure_context,
5039 new_context_info.throttled,
5040 );
5041 self.update_activity(change.new_pipeline_id);
5042 },
5043 Some(old_pipeline_id) => {
5044 let (pipelines_to_close, states_to_close) = if let Some(replace_reloader) =
5046 change.replace
5047 {
5048 webview.session_history.replace_reloader(
5049 replace_reloader.clone(),
5050 NeedsToReload::No(change.new_pipeline_id),
5051 );
5052
5053 match replace_reloader {
5054 NeedsToReload::No(pipeline_id) => (Some(vec![pipeline_id]), None),
5055 NeedsToReload::Yes(..) => (None, None),
5056 }
5057 } else {
5058 let diff = SessionHistoryDiff::BrowsingContext {
5059 browsing_context_id: change.browsing_context_id,
5060 new_reloader: NeedsToReload::No(change.new_pipeline_id),
5061 old_reloader: NeedsToReload::No(old_pipeline_id),
5062 };
5063
5064 let mut pipelines_to_close = vec![];
5065 let mut states_to_close = FxHashMap::default();
5066
5067 let diffs_to_close = webview.session_history.push_diff(diff);
5068 for diff in diffs_to_close {
5069 match diff {
5070 SessionHistoryDiff::BrowsingContext { new_reloader, .. } => {
5071 if let Some(pipeline_id) = new_reloader.alive_pipeline_id() {
5072 pipelines_to_close.push(pipeline_id);
5073 }
5074 },
5075 SessionHistoryDiff::Pipeline {
5076 pipeline_reloader,
5077 new_history_state_id,
5078 ..
5079 } => {
5080 if let Some(pipeline_id) = pipeline_reloader.alive_pipeline_id() {
5081 let states =
5082 states_to_close.entry(pipeline_id).or_insert(Vec::new());
5083 states.push(new_history_state_id);
5084 }
5085 },
5086 _ => {},
5087 }
5088 }
5089
5090 (Some(pipelines_to_close), Some(states_to_close))
5091 };
5092
5093 self.update_activity(old_pipeline_id);
5094 self.update_activity(change.new_pipeline_id);
5095
5096 if let Some(states_to_close) = states_to_close {
5097 for (pipeline_id, states) in states_to_close {
5098 let msg = ScriptThreadMessage::RemoveHistoryStates(pipeline_id, states);
5099 if !self.send_message_to_pipeline(
5100 pipeline_id,
5101 msg,
5102 "Removed history states after closure",
5103 ) {
5104 return;
5105 }
5106 }
5107 }
5108
5109 if let Some(pipelines_to_close) = pipelines_to_close {
5110 for pipeline_id in pipelines_to_close {
5111 self.close_pipeline(
5112 pipeline_id,
5113 DiscardBrowsingContext::No,
5114 ExitPipelineMode::Normal,
5115 );
5116 }
5117 }
5118 },
5119 }
5120
5121 if let Some(webview_id) = webview_id {
5122 self.trim_history(webview_id);
5123 }
5124
5125 self.notify_focus_state(change.new_pipeline_id);
5126
5127 self.notify_history_changed(change.webview_id);
5128 self.set_frame_tree_for_webview(change.webview_id);
5129 }
5130
5131 fn notify_focus_state(&mut self, pipeline_id: PipelineId) {
5135 let Some(pipeline) = self.pipelines.get(&pipeline_id) else {
5136 return warn!("Pipeline {pipeline_id} is closed");
5137 };
5138
5139 let is_focused = match self.webviews.get(&pipeline.webview_id) {
5140 Some(webview) => webview.focused_browsing_context_id == pipeline.browsing_context_id,
5141 None => {
5142 return warn!(
5143 "Pipeline {pipeline_id}'s top-level browsing context {} is closed",
5144 pipeline.webview_id
5145 );
5146 },
5147 };
5148
5149 let msg = if is_focused {
5151 ScriptThreadMessage::FocusDocumentAsPartOfFocusingSteps(
5152 pipeline_id,
5153 pipeline.focus_sequence,
5154 None,
5155 )
5156 } else {
5157 ScriptThreadMessage::UnfocusDocumentAsPartOfFocusingSteps(
5158 pipeline_id,
5159 pipeline.focus_sequence,
5160 )
5161 };
5162 if let Err(e) = pipeline.event_loop.send(msg) {
5163 self.handle_send_error(pipeline_id, e);
5164 }
5165 }
5166
5167 #[servo_tracing::instrument(skip_all)]
5168 fn focused_browsing_context_is_descendant_of(&self, change: &SessionHistoryChange) -> bool {
5169 let focused_browsing_context_id = self
5170 .webviews
5171 .get(&change.webview_id)
5172 .map(|webview| webview.focused_browsing_context_id);
5173 focused_browsing_context_id.is_some_and(|focused_browsing_context_id| {
5174 focused_browsing_context_id == change.browsing_context_id ||
5175 self.fully_active_descendant_browsing_contexts_iter(change.browsing_context_id)
5176 .any(|nested_ctx| nested_ctx.id == focused_browsing_context_id)
5177 })
5178 }
5179
5180 #[servo_tracing::instrument(skip_all)]
5181 fn trim_history(&mut self, webview_id: WebViewId) {
5182 let pipelines_to_evict = {
5183 let Some(webview) = self.webviews.get_mut(&webview_id) else {
5184 return warn!("Not trimming history for non-existent WebView ({webview_id:}");
5185 };
5186 let history_length = pref!(session_history_max_length) as usize;
5187
5188 let past_trim = webview
5192 .session_history
5193 .past
5194 .iter()
5195 .rev()
5196 .map(|diff| diff.alive_old_pipeline())
5197 .skip(history_length)
5198 .flatten();
5199
5200 let future_trim = webview
5203 .session_history
5204 .future
5205 .iter()
5206 .rev()
5207 .map(|diff| diff.alive_new_pipeline())
5208 .skip(history_length)
5209 .flatten();
5210
5211 past_trim.chain(future_trim).collect::<Vec<_>>()
5212 };
5213
5214 let mut dead_pipelines = vec![];
5215 for evicted_id in pipelines_to_evict {
5216 let Some(load_data) = self.refresh_load_data(evicted_id) else {
5217 continue;
5218 };
5219
5220 dead_pipelines.push((evicted_id, NeedsToReload::Yes(evicted_id, load_data)));
5221 self.close_pipeline(
5222 evicted_id,
5223 DiscardBrowsingContext::No,
5224 ExitPipelineMode::Normal,
5225 );
5226 }
5227
5228 if let Some(webview) = self.webviews.get_mut(&webview_id) {
5229 for (alive_id, dead) in dead_pipelines {
5230 webview
5231 .session_history
5232 .replace_reloader(NeedsToReload::No(alive_id), dead);
5233 }
5234 };
5235 }
5236
5237 #[servo_tracing::instrument(skip_all)]
5238 fn handle_activate_document_msg(&mut self, pipeline_id: PipelineId) {
5239 debug!("{}: Document ready to activate", pipeline_id);
5240
5241 let Some(pending_index) = self
5243 .pending_changes
5244 .iter()
5245 .rposition(|change| change.new_pipeline_id == pipeline_id)
5246 else {
5247 return;
5248 };
5249
5250 let change = self.pending_changes.swap_remove(pending_index);
5253
5254 self.send_screenshot_readiness_requests_to_pipelines();
5255
5256 let parent_pipeline_id = match change.new_browsing_context_info {
5258 Some(ref info) => info.parent_pipeline_id,
5260 None => match self.browsing_contexts.get(&change.browsing_context_id) {
5262 Some(ctx) => ctx.parent_pipeline_id,
5263 None => {
5264 return warn!(
5265 "{}: Activated document after closure of {}",
5266 change.new_pipeline_id, change.browsing_context_id,
5267 );
5268 },
5269 },
5270 };
5271 if let Some(parent_pipeline_id) = parent_pipeline_id {
5272 if let Some(parent_pipeline) = self.pipelines.get(&parent_pipeline_id) {
5273 let msg = ScriptThreadMessage::UpdatePipelineId(
5274 parent_pipeline_id,
5275 change.browsing_context_id,
5276 change.webview_id,
5277 pipeline_id,
5278 UpdatePipelineIdReason::Navigation,
5279 );
5280 let _ = parent_pipeline.event_loop.send(msg);
5281 }
5282 }
5283 self.change_session_history(change);
5284 }
5285
5286 #[servo_tracing::instrument(skip_all)]
5288 fn handle_change_viewport_details_msg(
5289 &mut self,
5290 webview_id: WebViewId,
5291 new_viewport_details: ViewportDetails,
5292 size_type: WindowSizeType,
5293 ) {
5294 debug!(
5295 "handle_change_viewport_details_msg: {:?}",
5296 new_viewport_details
5297 );
5298
5299 let browsing_context_id = BrowsingContextId::from(webview_id);
5300 self.resize_browsing_context(new_viewport_details, size_type, browsing_context_id);
5301 }
5302
5303 #[servo_tracing::instrument(skip_all)]
5305 fn handle_exit_fullscreen_msg(&mut self, webview_id: WebViewId) {
5306 let browsing_context_id = BrowsingContextId::from(webview_id);
5307 self.switch_fullscreen_mode(browsing_context_id);
5308 }
5309
5310 #[servo_tracing::instrument(skip_all)]
5311 fn handle_request_screenshot_readiness(&mut self, webview_id: WebViewId) {
5312 self.screenshot_readiness_requests
5313 .push(ScreenshotReadinessRequest {
5314 webview_id,
5315 pipeline_states: Default::default(),
5316 state: Default::default(),
5317 });
5318 self.send_screenshot_readiness_requests_to_pipelines();
5319 }
5320
5321 fn send_screenshot_readiness_requests_to_pipelines(&mut self) {
5322 if !self.pending_changes.is_empty() {
5324 return;
5325 }
5326
5327 for screenshot_request in &self.screenshot_readiness_requests {
5328 if screenshot_request.state.get() != ScreenshotRequestState::Pending {
5330 return;
5331 }
5332
5333 *screenshot_request.pipeline_states.borrow_mut() =
5334 self.fully_active_browsing_contexts_iter(screenshot_request.webview_id)
5335 .filter_map(|browsing_context| {
5336 let pipeline_id = browsing_context.pipeline_id;
5337 let Some(pipeline) = self.pipelines.get(&pipeline_id) else {
5338 return None;
5340 };
5341 if browsing_context.viewport_details.size == Size2D::zero() {
5345 return None;
5346 }
5347 let _ = pipeline.event_loop.send(
5348 ScriptThreadMessage::RequestScreenshotReadiness(
5349 pipeline.webview_id,
5350 pipeline_id,
5351 ),
5352 );
5353 Some((pipeline_id, None))
5354 })
5355 .collect();
5356 screenshot_request
5357 .state
5358 .set(ScreenshotRequestState::WaitingOnScript);
5359 }
5360 }
5361
5362 #[servo_tracing::instrument(skip_all)]
5363 fn handle_screenshot_readiness_response(
5364 &mut self,
5365 updated_pipeline_id: PipelineId,
5366 response: ScreenshotReadinessResponse,
5367 ) {
5368 if self.screenshot_readiness_requests.is_empty() {
5369 return;
5370 }
5371
5372 self.screenshot_readiness_requests
5373 .retain(|screenshot_request| {
5374 if screenshot_request.state.get() != ScreenshotRequestState::WaitingOnScript {
5375 return true;
5376 }
5377
5378 let mut has_pending_pipeline = false;
5379 let mut pipeline_states = screenshot_request.pipeline_states.borrow_mut();
5380 pipeline_states.retain(|pipeline_id, state| {
5381 if *pipeline_id != updated_pipeline_id {
5382 has_pending_pipeline |= state.is_none();
5383 return true;
5384 }
5385 match response {
5386 ScreenshotReadinessResponse::Ready(epoch) => {
5387 *state = Some(epoch);
5388 true
5389 },
5390 ScreenshotReadinessResponse::NoLongerActive => false,
5391 }
5392 });
5393
5394 if has_pending_pipeline {
5395 return true;
5396 }
5397
5398 let pipelines_and_epochs = pipeline_states
5399 .iter()
5400 .map(|(pipeline_id, epoch)| {
5401 (
5402 *pipeline_id,
5403 epoch.expect("Should have an epoch when pipeline is ready."),
5404 )
5405 })
5406 .collect();
5407 self.paint_proxy
5408 .send(PaintMessage::ScreenshotReadinessReponse(
5409 screenshot_request.webview_id,
5410 pipelines_and_epochs,
5411 ));
5412
5413 false
5414 });
5415 }
5416
5417 #[servo_tracing::instrument(skip_all)]
5419 fn get_activity(&self, pipeline_id: PipelineId) -> DocumentActivity {
5420 let mut ancestor_id = pipeline_id;
5421 loop {
5422 if let Some(ancestor) = self.pipelines.get(&ancestor_id) {
5423 if let Some(browsing_context) =
5424 self.browsing_contexts.get(&ancestor.browsing_context_id)
5425 {
5426 if browsing_context.pipeline_id == ancestor_id {
5427 if let Some(parent_pipeline_id) = browsing_context.parent_pipeline_id {
5428 ancestor_id = parent_pipeline_id;
5429 continue;
5430 } else {
5431 return DocumentActivity::FullyActive;
5432 }
5433 }
5434 }
5435 }
5436 if pipeline_id == ancestor_id {
5437 return DocumentActivity::Inactive;
5438 } else {
5439 return DocumentActivity::Active;
5440 }
5441 }
5442 }
5443
5444 #[servo_tracing::instrument(skip_all)]
5446 fn set_activity(&self, pipeline_id: PipelineId, activity: DocumentActivity) {
5447 debug!("{}: Setting activity to {:?}", pipeline_id, activity);
5448 if let Some(pipeline) = self.pipelines.get(&pipeline_id) {
5449 pipeline.set_activity(activity);
5450 let child_activity = if activity == DocumentActivity::Inactive {
5451 DocumentActivity::Active
5452 } else {
5453 activity
5454 };
5455 for child_id in &pipeline.children {
5456 if let Some(child) = self.browsing_contexts.get(child_id) {
5457 self.set_activity(child.pipeline_id, child_activity);
5458 }
5459 }
5460 }
5461 }
5462
5463 #[servo_tracing::instrument(skip_all)]
5465 fn update_activity(&self, pipeline_id: PipelineId) {
5466 self.set_activity(pipeline_id, self.get_activity(pipeline_id));
5467 }
5468
5469 #[servo_tracing::instrument(skip_all)]
5472 fn resize_browsing_context(
5473 &mut self,
5474 new_viewport_details: ViewportDetails,
5475 size_type: WindowSizeType,
5476 browsing_context_id: BrowsingContextId,
5477 ) {
5478 if let Some(browsing_context) = self.browsing_contexts.get_mut(&browsing_context_id) {
5479 browsing_context.viewport_details = new_viewport_details;
5480 let pipeline_id = browsing_context.pipeline_id;
5482 let Some(pipeline) = self.pipelines.get(&pipeline_id) else {
5483 return warn!("{}: Resized after closing", pipeline_id);
5484 };
5485 let _ = pipeline.event_loop.send(ScriptThreadMessage::Resize(
5486 pipeline.id,
5487 new_viewport_details,
5488 size_type,
5489 ));
5490 let pipeline_ids = browsing_context
5491 .pipelines
5492 .iter()
5493 .filter(|pipeline_id| **pipeline_id != pipeline.id);
5494 for id in pipeline_ids {
5495 if let Some(pipeline) = self.pipelines.get(id) {
5496 let _ = pipeline
5497 .event_loop
5498 .send(ScriptThreadMessage::ResizeInactive(
5499 pipeline.id,
5500 new_viewport_details,
5501 ));
5502 }
5503 }
5504 } else {
5505 self.pending_viewport_changes
5506 .insert(browsing_context_id, new_viewport_details);
5507 }
5508
5509 for change in &self.pending_changes {
5511 let pipeline_id = change.new_pipeline_id;
5512 let Some(pipeline) = self.pipelines.get(&pipeline_id) else {
5513 warn!("{}: Pending pipeline is closed", pipeline_id);
5514 continue;
5515 };
5516 if pipeline.browsing_context_id == browsing_context_id {
5517 let _ = pipeline.event_loop.send(ScriptThreadMessage::Resize(
5518 pipeline.id,
5519 new_viewport_details,
5520 size_type,
5521 ));
5522 }
5523 }
5524 }
5525
5526 #[servo_tracing::instrument(skip_all)]
5528 fn handle_theme_change(&mut self, webview_id: WebViewId, theme: Theme) {
5529 let Some(webview) = self.webviews.get_mut(&webview_id) else {
5530 warn!("Received theme change request for uknown WebViewId: {webview_id:?}");
5531 return;
5532 };
5533 if !webview.set_theme(theme) {
5534 return;
5535 }
5536
5537 for pipeline in self.pipelines.values() {
5538 if pipeline.webview_id != webview_id {
5539 continue;
5540 }
5541 if let Err(error) = pipeline
5542 .event_loop
5543 .send(ScriptThreadMessage::ThemeChange(pipeline.id, theme))
5544 {
5545 warn!(
5546 "{}: Failed to send theme change event to pipeline ({error:?}).",
5547 pipeline.id,
5548 );
5549 }
5550 }
5551 }
5552
5553 #[servo_tracing::instrument(skip_all)]
5555 fn switch_fullscreen_mode(&mut self, browsing_context_id: BrowsingContextId) {
5556 if let Some(browsing_context) = self.browsing_contexts.get(&browsing_context_id) {
5557 let pipeline_id = browsing_context.pipeline_id;
5558 let Some(pipeline) = self.pipelines.get(&pipeline_id) else {
5559 return warn!("{pipeline_id}: Switched from fullscreen mode after closing",);
5560 };
5561 let _ = pipeline
5562 .event_loop
5563 .send(ScriptThreadMessage::ExitFullScreen(pipeline.id));
5564 }
5565 }
5566
5567 #[servo_tracing::instrument(skip_all)]
5569 fn close_browsing_context(
5570 &mut self,
5571 browsing_context_id: BrowsingContextId,
5572 exit_mode: ExitPipelineMode,
5573 ) -> Option<BrowsingContext> {
5574 debug!("{}: Closing", browsing_context_id);
5575
5576 self.close_browsing_context_children(
5577 browsing_context_id,
5578 DiscardBrowsingContext::Yes,
5579 exit_mode,
5580 );
5581
5582 let _ = self.pending_viewport_changes.remove(&browsing_context_id);
5583
5584 let Some(browsing_context) = self.browsing_contexts.remove(&browsing_context_id) else {
5585 warn!("fn close_browsing_context: {browsing_context_id}: Closing twice");
5586 return None;
5587 };
5588
5589 if let Some(webview) = self.webviews.get_mut(&browsing_context.webview_id) {
5590 webview
5591 .session_history
5592 .remove_entries_for_browsing_context(browsing_context_id);
5593 }
5594
5595 if let Some(parent_pipeline_id) = browsing_context.parent_pipeline_id {
5596 match self.pipelines.get_mut(&parent_pipeline_id) {
5597 None => {
5598 warn!("{parent_pipeline_id}: Child closed after parent");
5599 },
5600 Some(parent_pipeline) => {
5601 parent_pipeline.remove_child(browsing_context_id);
5602
5603 if let Some(webview) = self.webviews.get_mut(&browsing_context.webview_id) {
5606 if webview.focused_browsing_context_id == browsing_context_id {
5607 trace!(
5608 "About-to-be-closed browsing context {} is currently focused, so \
5609 focusing its parent {}",
5610 browsing_context_id, parent_pipeline.browsing_context_id
5611 );
5612 webview.focused_browsing_context_id =
5613 parent_pipeline.browsing_context_id;
5614 }
5615 } else {
5616 warn!(
5617 "Browsing context {} contains a reference to \
5618 a non-existent top-level browsing context {}",
5619 browsing_context_id, browsing_context.webview_id
5620 );
5621 }
5622 },
5623 };
5624 }
5625 debug!("{}: Closed", browsing_context_id);
5626 Some(browsing_context)
5627 }
5628
5629 #[servo_tracing::instrument(skip_all)]
5631 fn close_browsing_context_children(
5632 &mut self,
5633 browsing_context_id: BrowsingContextId,
5634 dbc: DiscardBrowsingContext,
5635 exit_mode: ExitPipelineMode,
5636 ) {
5637 debug!("{}: Closing browsing context children", browsing_context_id);
5638 let mut pipelines_to_close: Vec<PipelineId> = self
5643 .pending_changes
5644 .iter()
5645 .filter(|change| change.browsing_context_id == browsing_context_id)
5646 .map(|change| change.new_pipeline_id)
5647 .collect();
5648
5649 if let Some(browsing_context) = self.browsing_contexts.get(&browsing_context_id) {
5650 pipelines_to_close.extend(&browsing_context.pipelines)
5651 }
5652
5653 for pipeline_id in pipelines_to_close {
5654 self.close_pipeline(pipeline_id, dbc, exit_mode);
5655 }
5656
5657 debug!("{}: Closed browsing context children", browsing_context_id);
5658 }
5659
5660 fn refresh_load_data(&self, pipeline_id: PipelineId) -> Option<LoadData> {
5663 self.pipelines.get(&pipeline_id).map(|pipeline| {
5664 let mut load_data = pipeline.load_data.clone();
5665 load_data.url = pipeline.url.clone();
5666 load_data
5667 })
5668 }
5669
5670 #[servo_tracing::instrument(skip_all)]
5672 fn handle_discard_document(&mut self, webview_id: WebViewId, pipeline_id: PipelineId) {
5673 let Some(load_data) = self.refresh_load_data(pipeline_id) else {
5674 return warn!("{}: Discarding closed pipeline", pipeline_id);
5675 };
5676 match self.webviews.get_mut(&webview_id) {
5677 Some(webview) => {
5678 webview.session_history.replace_reloader(
5679 NeedsToReload::No(pipeline_id),
5680 NeedsToReload::Yes(pipeline_id, load_data),
5681 );
5682 },
5683 None => {
5684 return warn!("{pipeline_id}: Discarding after closure of {webview_id}",);
5685 },
5686 };
5687 self.close_pipeline(
5688 pipeline_id,
5689 DiscardBrowsingContext::No,
5690 ExitPipelineMode::Normal,
5691 );
5692 }
5693
5694 #[servo_tracing::instrument(skip_all)]
5696 fn unload_document(&self, pipeline_id: PipelineId) {
5697 if let Some(pipeline) = self.pipelines.get(&pipeline_id) {
5698 pipeline.set_throttled(true);
5699 let msg = ScriptThreadMessage::UnloadDocument(pipeline_id);
5700 let _ = pipeline.event_loop.send(msg);
5701 }
5702 }
5703
5704 #[servo_tracing::instrument(skip_all)]
5706 fn close_pipeline(
5707 &mut self,
5708 pipeline_id: PipelineId,
5709 dbc: DiscardBrowsingContext,
5710 exit_mode: ExitPipelineMode,
5711 ) {
5712 debug!("{}: Closing", pipeline_id);
5713
5714 let browsing_context_id = self
5716 .pipelines
5717 .get(&pipeline_id)
5718 .map(|pipeline| pipeline.browsing_context_id);
5719 if let Some(browsing_context) = browsing_context_id
5720 .and_then(|browsing_context_id| self.browsing_contexts.get_mut(&browsing_context_id))
5721 {
5722 browsing_context.pipelines.remove(&pipeline_id);
5723 }
5724
5725 let browsing_contexts_to_close = {
5730 let mut browsing_contexts_to_close = vec![];
5731
5732 if let Some(pipeline) = self.pipelines.get(&pipeline_id) {
5733 browsing_contexts_to_close.extend_from_slice(&pipeline.children);
5734 }
5735
5736 browsing_contexts_to_close
5737 };
5738
5739 for child_browsing_context in &browsing_contexts_to_close {
5741 self.close_browsing_context(*child_browsing_context, exit_mode);
5742 }
5743
5744 let Some(pipeline) = self.pipelines.get(&pipeline_id) else {
5747 return warn!("fn close_pipeline: {pipeline_id}: Closing twice");
5748 };
5749
5750 let pending_index = self
5752 .pending_changes
5753 .iter()
5754 .position(|change| change.new_pipeline_id == pipeline_id);
5755 if let Some(pending_index) = pending_index {
5756 self.pending_changes.remove(pending_index);
5757 }
5758
5759 pipeline.send_exit_message_to_script(dbc);
5761
5762 self.send_screenshot_readiness_requests_to_pipelines();
5763 self.handle_screenshot_readiness_response(
5764 pipeline_id,
5765 ScreenshotReadinessResponse::NoLongerActive,
5766 );
5767
5768 debug!("{}: Closed", pipeline_id);
5769 }
5770
5771 fn maybe_close_random_pipeline(&mut self) {
5773 match self.random_pipeline_closure {
5774 Some((ref mut rng, probability)) => {
5775 if probability <= rng.random::<f32>() {
5776 return;
5777 }
5778 },
5779 _ => return,
5780 };
5781 let mut pipeline_ids: Vec<&PipelineId> = self.pipelines.keys().collect();
5783 pipeline_ids.sort_unstable();
5784 if let Some((ref mut rng, probability)) = self.random_pipeline_closure {
5785 if let Some(pipeline_id) = pipeline_ids.choose(rng) {
5786 if let Some(pipeline) = self.pipelines.get(pipeline_id) {
5787 if self
5788 .pending_changes
5789 .iter()
5790 .any(|change| change.new_pipeline_id == pipeline.id) &&
5791 probability <= rng.random::<f32>()
5792 {
5793 info!("{}: Not closing pending pipeline", pipeline_id);
5798 } else {
5799 warn!("{}: Randomly closing pipeline", pipeline_id);
5802 pipeline.send_exit_message_to_script(DiscardBrowsingContext::No);
5803 }
5804 }
5805 }
5806 }
5807 }
5808
5809 #[servo_tracing::instrument(skip_all)]
5811 fn browsing_context_to_sendable(
5812 &self,
5813 browsing_context_id: BrowsingContextId,
5814 ) -> Option<SendableFrameTree> {
5815 self.browsing_contexts
5816 .get(&browsing_context_id)
5817 .and_then(|browsing_context| {
5818 self.pipelines
5819 .get(&browsing_context.pipeline_id)
5820 .map(|pipeline| {
5821 let mut frame_tree = SendableFrameTree {
5822 pipeline: pipeline.to_sendable(),
5823 children: vec![],
5824 };
5825
5826 for child_browsing_context_id in &pipeline.children {
5827 if let Some(child) =
5828 self.browsing_context_to_sendable(*child_browsing_context_id)
5829 {
5830 frame_tree.children.push(child);
5831 }
5832 }
5833
5834 frame_tree
5835 })
5836 })
5837 }
5838
5839 #[servo_tracing::instrument(skip_all)]
5841 fn set_frame_tree_for_webview(&mut self, webview_id: WebViewId) {
5842 let browsing_context_id = BrowsingContextId::from(webview_id);
5846 let Some(frame_tree) = self.browsing_context_to_sendable(browsing_context_id) else {
5847 return;
5848 };
5849
5850 let new_pipeline_id = frame_tree.pipeline.id;
5851
5852 debug!("{}: Sending frame tree", browsing_context_id);
5853 self.paint_proxy
5854 .send(PaintMessage::SetFrameTreeForWebView(webview_id, frame_tree));
5855
5856 let Some(webview) = self.webviews.get_mut(&webview_id) else {
5857 return;
5858 };
5859 if webview.active_top_level_pipeline_id == Some(new_pipeline_id) {
5860 return;
5861 }
5862
5863 let old_pipeline_id = webview.active_top_level_pipeline_id;
5864 let old_epoch = webview.active_top_level_pipeline_epoch;
5865 let new_epoch = old_epoch.next();
5866
5867 let accessibility_active = webview.accessibility_active;
5868
5869 webview.active_top_level_pipeline_id = Some(new_pipeline_id);
5870 webview.active_top_level_pipeline_epoch = new_epoch;
5871
5872 if let Some(old_pipeline_id) = old_pipeline_id {
5877 self.send_message_to_pipeline(
5878 old_pipeline_id,
5879 ScriptThreadMessage::SetAccessibilityActive(old_pipeline_id, false, old_epoch),
5880 "Set accessibility active after closure",
5881 );
5882 }
5883
5884 self.send_message_to_pipeline(
5887 new_pipeline_id,
5888 ScriptThreadMessage::SetAccessibilityActive(
5889 new_pipeline_id,
5890 accessibility_active,
5891 new_epoch,
5892 ),
5893 "Set accessibility active after closure",
5894 );
5895 }
5896
5897 #[servo_tracing::instrument(skip_all)]
5898 fn handle_media_session_action_msg(&mut self, action: MediaSessionActionType) {
5899 if let Some(media_session_pipeline_id) = self.active_media_session {
5900 self.send_message_to_pipeline(
5901 media_session_pipeline_id,
5902 ScriptThreadMessage::MediaSessionAction(media_session_pipeline_id, action),
5903 "Got media session action request after closure",
5904 );
5905 } else {
5906 error!("Got a media session action but no active media session is registered");
5907 }
5908 }
5909
5910 #[servo_tracing::instrument(skip_all)]
5911 fn handle_set_scroll_states(&self, pipeline_id: PipelineId, scroll_states: ScrollStateUpdate) {
5912 let Some(pipeline) = self.pipelines.get(&pipeline_id) else {
5913 warn!("Discarding scroll offset update for unknown pipeline");
5914 return;
5915 };
5916 if let Err(error) = pipeline
5917 .event_loop
5918 .send(ScriptThreadMessage::SetScrollStates(
5919 pipeline_id,
5920 scroll_states,
5921 ))
5922 {
5923 warn!("Could not send scroll offsets to pipeline: {pipeline_id:?}: {error:?}");
5924 }
5925 }
5926
5927 #[servo_tracing::instrument(skip_all)]
5928 fn handle_paint_metric(&mut self, pipeline_id: PipelineId, event: PaintMetricEvent) {
5929 let Some(pipeline) = self.pipelines.get(&pipeline_id) else {
5930 warn!("Discarding paint metric event for unknown pipeline");
5931 return;
5932 };
5933 let (metric_type, metric_value, first_reflow) = match event {
5934 PaintMetricEvent::FirstPaint(metric_value, first_reflow) => (
5935 ProgressiveWebMetricType::FirstPaint,
5936 metric_value,
5937 first_reflow,
5938 ),
5939 PaintMetricEvent::FirstContentfulPaint(metric_value, first_reflow) => (
5940 ProgressiveWebMetricType::FirstContentfulPaint,
5941 metric_value,
5942 first_reflow,
5943 ),
5944 PaintMetricEvent::LargestContentfulPaint(metric_value, area, url) => (
5945 ProgressiveWebMetricType::LargestContentfulPaint { area, url },
5946 metric_value,
5947 false, ),
5949 };
5950 if let Err(error) = pipeline.event_loop.send(ScriptThreadMessage::PaintMetric(
5951 pipeline_id,
5952 metric_type,
5953 metric_value,
5954 first_reflow,
5955 )) {
5956 warn!("Could not sent paint metric event to pipeline: {pipeline_id:?}: {error:?}");
5957 }
5958 }
5959
5960 fn create_canvas_paint_thread(
5961 &self,
5962 ) -> (Sender<ConstellationCanvasMsg>, GenericSender<CanvasMsg>) {
5963 CanvasPaintThread::start(self.paint_proxy.cross_process_paint_api.clone())
5964 }
5965
5966 fn handle_embedder_control_response(
5967 &self,
5968 id: EmbedderControlId,
5969 response: EmbedderControlResponse,
5970 ) {
5971 let pipeline_id = id.pipeline_id;
5972 let Some(pipeline) = self.pipelines.get(&pipeline_id) else {
5973 warn!("Not sending embedder control response for unknown pipeline {pipeline_id:?}");
5974 return;
5975 };
5976
5977 if let Err(error) = pipeline
5978 .event_loop
5979 .send(ScriptThreadMessage::EmbedderControlResponse(id, response))
5980 {
5981 warn!(
5982 "Could not send embedder control response to pipeline {pipeline_id:?}: {error:?}"
5983 );
5984 }
5985 }
5986
5987 fn handle_update_pinch_zoom_infos(
5988 &self,
5989 pipeline_id: PipelineId,
5990 pinch_zoom_infos: PinchZoomInfos,
5991 ) {
5992 let Some(pipeline) = self.pipelines.get(&pipeline_id) else {
5993 warn!("Discarding pinch zoom update for unknown pipeline");
5994 return;
5995 };
5996 if let Err(error) = pipeline
5997 .event_loop
5998 .send(ScriptThreadMessage::UpdatePinchZoomInfos(
5999 pipeline_id,
6000 pinch_zoom_infos,
6001 ))
6002 {
6003 warn!("Could not send pinch zoom update to pipeline: {pipeline_id:?}: {error:?}");
6004 }
6005 }
6006
6007 pub(crate) fn script_to_devtools_callback(
6008 &self,
6009 ) -> Option<GenericCallback<ScriptToDevtoolsControlMsg>> {
6010 self.script_to_devtools_callback
6011 .get_or_init(|| {
6012 self.devtools_sender.as_ref().and_then(|devtools_sender| {
6013 let devtools_sender = devtools_sender.clone();
6014 let callback = GenericCallback::new(move |message| match message {
6015 Err(error) => {
6016 error!("Cast to ScriptToDevtoolsControlMsg failed ({error}).")
6017 },
6018 Ok(message) => {
6019 if let Err(error) =
6020 devtools_sender.send(DevtoolsControlMsg::FromScript(message))
6021 {
6022 warn!("Sending to devtools failed ({error:?})")
6023 }
6024 },
6025 });
6026 match callback {
6027 Ok(callback) => Some(callback),
6028 Err(error) => {
6029 error!("Could not create Devtools communication channel: {error}");
6030 None
6031 },
6032 }
6033 })
6034 })
6035 .clone()
6036 }
6037}
6038
6039#[derive(Clone, Copy, Default, PartialEq)]
6042enum ScreenshotRequestState {
6043 #[default]
6048 Pending,
6049 WaitingOnScript,
6055}
6056
6057struct ScreenshotReadinessRequest {
6058 webview_id: WebViewId,
6059 state: Cell<ScreenshotRequestState>,
6060 pipeline_states: RefCell<FxHashMap<PipelineId, Option<Epoch>>>,
6061}