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