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