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