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