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