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