servo/
lib.rs

1/* This Source Code Form is subject to the terms of the Mozilla Public
2 * License, v. 2.0. If a copy of the MPL was not distributed with this
3 * file, You can obtain one at https://mozilla.org/MPL/2.0/. */
4
5//! Servo, the mighty web browser engine from the future.
6//!
7//! This is a very simple library that wires all of Servo's components
8//! together as type `Servo`, along with a generic client
9//! implementing the `WindowMethods` trait, to create a working web
10//! browser.
11//!
12//! The `Servo` type is responsible for configuring a
13//! `Constellation`, which does the heavy lifting of coordinating all
14//! of Servo's internal subsystems, including the `ScriptThread` and the
15//! `LayoutThread`, as well maintains the navigation context.
16//!
17//! `Servo` is fed events from a generic type that implements the
18//! `WindowMethods` trait.
19
20mod clipboard_delegate;
21mod javascript_evaluator;
22mod proxies;
23mod responders;
24mod servo_delegate;
25mod webview;
26mod webview_delegate;
27
28use std::cell::{Cell, RefCell};
29use std::cmp::max;
30use std::path::PathBuf;
31use std::rc::{Rc, Weak};
32use std::sync::Arc;
33
34use base::generic_channel::{GenericCallback, RoutedReceiver};
35pub use base::id::WebViewId;
36use base::id::{PipelineNamespace, PipelineNamespaceId};
37#[cfg(feature = "bluetooth")]
38use bluetooth::BluetoothThreadFactory;
39#[cfg(feature = "bluetooth")]
40use bluetooth_traits::BluetoothRequest;
41use clipboard_delegate::StringRequest;
42pub use compositing::WebRenderDebugOption;
43use compositing::{IOCompositor, InitialCompositorState};
44pub use compositing_traits::rendering_context::{
45    OffscreenRenderingContext, RenderingContext, SoftwareRenderingContext, WindowRenderingContext,
46};
47use compositing_traits::{CompositorMsg, CompositorProxy, CrossProcessCompositorApi};
48#[cfg(all(
49    not(target_os = "windows"),
50    not(target_os = "ios"),
51    not(target_os = "android"),
52    not(target_arch = "arm"),
53    not(target_arch = "aarch64"),
54    not(target_env = "ohos"),
55))]
56use constellation::content_process_sandbox_profile;
57use constellation::{
58    Constellation, FromEmbedderLogger, FromScriptLogger, InitialConstellationState,
59    UnprivilegedContent,
60};
61pub use constellation_traits::EmbedderToConstellationMessage;
62use constellation_traits::ScriptToConstellationChan;
63use crossbeam_channel::{Receiver, Sender, unbounded};
64use embedder_traits::user_content_manager::UserContentManager;
65pub use embedder_traits::{WebDriverSenders, *};
66use env_logger::Builder as EnvLoggerBuilder;
67use fonts::SystemFontService;
68#[cfg(all(
69    not(target_os = "windows"),
70    not(target_os = "ios"),
71    not(target_os = "android"),
72    not(target_arch = "arm"),
73    not(target_arch = "aarch64"),
74    not(target_env = "ohos"),
75))]
76use gaol::sandbox::{ChildSandbox, ChildSandboxMethods};
77pub use gleam::gl;
78pub use image::RgbaImage;
79use ipc_channel::ipc::{self, IpcSender};
80use javascript_evaluator::JavaScriptEvaluator;
81pub use keyboard_types::{
82    Code, CompositionEvent, CompositionState, Key, KeyState, Location, Modifiers, NamedKey,
83};
84use layout::LayoutFactoryImpl;
85use log::{Log, Metadata, Record, debug, warn};
86use media::{GlApi, NativeDisplay, WindowGLContext};
87use net::protocols::ProtocolRegistry;
88use net::resource_thread::new_resource_threads;
89use net_traits::{exit_fetch_thread, start_fetch_thread};
90use profile::{mem as profile_mem, time as profile_time};
91use profile_traits::mem::MemoryReportResult;
92use profile_traits::{mem, time};
93use rustc_hash::FxHashMap;
94use script::{JSEngineSetup, ServiceWorkerManager};
95use servo_config::opts::Opts;
96pub use servo_config::prefs::PrefValue;
97use servo_config::prefs::Preferences;
98use servo_config::{opts, pref, prefs};
99use servo_delegate::DefaultServoDelegate;
100use servo_geometry::{
101    DeviceIndependentIntRect, convert_rect_to_css_pixel, convert_size_to_css_pixel,
102};
103use servo_media::ServoMedia;
104use servo_media::player::context::GlContext;
105use servo_url::ServoUrl;
106use storage::new_storage_threads;
107use style::global_style_data::StyleThreadPool;
108use webview::WebViewInner;
109#[cfg(feature = "webxr")]
110pub use webxr;
111pub use {
112    background_hang_monitor, base, canvas_traits, devtools, devtools_traits, euclid, fonts,
113    ipc_channel, layout_api, media, net, net_traits, profile, profile_traits, script,
114    script_traits, servo_config as config, servo_config, servo_geometry, servo_url, style,
115    style_traits, webrender_api,
116};
117#[cfg(feature = "bluetooth")]
118pub use {bluetooth, bluetooth_traits};
119
120use crate::proxies::ConstellationProxy;
121use crate::responders::ServoErrorChannel;
122pub use crate::servo_delegate::{ServoDelegate, ServoError};
123use crate::webview::MINIMUM_WEBVIEW_SIZE;
124pub use crate::webview::{WebView, WebViewBuilder};
125pub use crate::webview_delegate::{
126    AllowOrDenyRequest, AuthenticationRequest, ColorPicker, EmbedderControl, FilePicker,
127    InputMethodControl, NavigationRequest, PermissionRequest, SelectElement, WebResourceLoad,
128    WebViewDelegate,
129};
130
131#[cfg(feature = "media-gstreamer")]
132mod media_platform {
133    #[cfg(any(windows, target_os = "macos"))]
134    mod gstreamer_plugins {
135        include!(concat!(env!("OUT_DIR"), "/gstreamer_plugins.rs"));
136    }
137
138    use servo_media_gstreamer::GStreamerBackend;
139
140    use super::ServoMedia;
141
142    #[cfg(any(windows, target_os = "macos"))]
143    pub fn init() {
144        ServoMedia::init_with_backend(|| {
145            let mut plugin_dir = std::env::current_exe().unwrap();
146            plugin_dir.pop();
147
148            if cfg!(target_os = "macos") {
149                plugin_dir.push("lib");
150            }
151
152            match GStreamerBackend::init_with_plugins(
153                plugin_dir,
154                gstreamer_plugins::GSTREAMER_PLUGINS,
155            ) {
156                Ok(b) => b,
157                Err(e) => {
158                    eprintln!("Error initializing GStreamer: {:?}", e);
159                    std::process::exit(1);
160                },
161            }
162        });
163    }
164
165    #[cfg(not(any(windows, target_os = "macos")))]
166    pub fn init() {
167        ServoMedia::init::<GStreamerBackend>();
168    }
169}
170
171#[cfg(not(feature = "media-gstreamer"))]
172mod media_platform {
173    use super::ServoMedia;
174    pub fn init() {
175        ServoMedia::init::<servo_media_dummy::DummyBackend>();
176    }
177}
178
179/// The in-process interface to Servo.
180///
181/// It does everything necessary to render the web, primarily
182/// orchestrating the interaction between JavaScript, CSS layout,
183/// rendering, and the client window.
184///
185// Clients create an event loop to pump messages between the embedding
186// application and various browser components.
187pub struct Servo {
188    delegate: RefCell<Rc<dyn ServoDelegate>>,
189    compositor: Rc<RefCell<IOCompositor>>,
190    constellation_proxy: ConstellationProxy,
191    embedder_receiver: Receiver<EmbedderMsg>,
192    /// A struct that tracks ongoing JavaScript evaluations and is responsible for
193    /// calling the callback when the evaluation is complete.
194    javascript_evaluator: Rc<RefCell<JavaScriptEvaluator>>,
195    /// Tracks whether we are in the process of shutting down, or have shut down.
196    /// This is shared with `WebView`s and the `ServoRenderer`.
197    shutdown_state: Rc<Cell<ShutdownState>>,
198    /// A map  [`WebView`]s that are managed by this [`Servo`] instance. These are stored
199    /// as `Weak` references so that the embedding application can control their lifetime.
200    /// When accessed, `Servo` will be reponsible for cleaning up the invalid `Weak`
201    /// references.
202    webviews: RefCell<FxHashMap<WebViewId, Weak<RefCell<WebViewInner>>>>,
203    servo_errors: ServoErrorChannel,
204    /// For single-process Servo instances, this field controls the initialization
205    /// and deinitialization of the JS Engine. Multiprocess Servo instances have their
206    /// own instance that exists in the content process instead.
207    _js_engine_setup: Option<JSEngineSetup>,
208    /// Whether or not any WebView in this instance is animating or WebXR is enabled.
209    animating: Cell<bool>,
210}
211
212impl Servo {
213    #[servo_tracing::instrument(skip(builder))]
214    fn new(builder: ServoBuilder) -> Self {
215        // Global configuration options, parsed from the command line.
216        let opts = builder.opts.map(|opts| *opts);
217        opts::initialize_options(opts.unwrap_or_default());
218        let opts = opts::get();
219
220        // Set the preferences globally.
221        // TODO: It would be better to make these private to a particular Servo instance.
222        let preferences = builder.preferences.map(|opts| *opts);
223        servo_config::prefs::set(preferences.unwrap_or_default());
224
225        use std::sync::atomic::Ordering;
226
227        style::context::DEFAULT_DISABLE_STYLE_SHARING_CACHE
228            .store(opts.debug.disable_share_style_cache, Ordering::Relaxed);
229        style::context::DEFAULT_DUMP_STYLE_STATISTICS
230            .store(opts.debug.dump_style_statistics, Ordering::Relaxed);
231        style::traversal::IS_SERVO_NONINCREMENTAL_LAYOUT
232            .store(opts.nonincremental_layout, Ordering::Relaxed);
233
234        if !opts.multiprocess {
235            media_platform::init();
236        }
237
238        // Reserving a namespace to create WebViewId.
239        PipelineNamespace::install(PipelineNamespaceId(0));
240
241        // Get both endpoints of a special channel for communication between
242        // the client window and the compositor. This channel is unique because
243        // messages to client may need to pump a platform-specific event loop
244        // to deliver the message.
245        let event_loop_waker = builder.event_loop_waker;
246        let (compositor_proxy, compositor_receiver) =
247            create_compositor_channel(event_loop_waker.clone());
248        let (constellation_proxy, embedder_to_constellation_receiver) = ConstellationProxy::new();
249        let (embedder_proxy, embedder_receiver) = create_embedder_channel(event_loop_waker.clone());
250        let time_profiler_chan = profile_time::Profiler::create(
251            &opts.time_profiling,
252            opts.time_profiler_trace_path.clone(),
253        );
254        let mem_profiler_chan = profile_mem::Profiler::create();
255
256        let devtools_sender = if pref!(devtools_server_enabled) {
257            Some(devtools::start_server(
258                pref!(devtools_server_port) as u16,
259                embedder_proxy.clone(),
260            ))
261        } else {
262            None
263        };
264
265        // Important that this call is done in a single-threaded fashion, we
266        // can't defer it after `create_constellation` has started.
267        let js_engine_setup = if !opts.multiprocess {
268            Some(script::init())
269        } else {
270            None
271        };
272
273        // Create the constellation, which maintains the engine pipelines, including script and
274        // layout, as well as the navigation context.
275        let mut protocols = ProtocolRegistry::with_internal_protocols();
276        protocols.merge(builder.protocol_registry);
277
278        // The compositor coordinates with the client window to create the final
279        // rendered page and display it somewhere.
280        let shutdown_state = Rc::new(Cell::new(ShutdownState::NotShuttingDown));
281        let compositor = IOCompositor::new(InitialCompositorState {
282            compositor_proxy: compositor_proxy.clone(),
283            receiver: compositor_receiver,
284            embedder_to_constellation_sender: constellation_proxy.sender().clone(),
285            time_profiler_chan: time_profiler_chan.clone(),
286            mem_profiler_chan: mem_profiler_chan.clone(),
287            rendering_context: builder.rendering_context,
288            shutdown_state: shutdown_state.clone(),
289            event_loop_waker,
290            refresh_driver: builder.refresh_driver,
291            shaders_path: opts.shaders_path.clone(),
292            #[cfg(feature = "webxr")]
293            webxr_registry: builder.webxr_registry,
294        });
295
296        create_constellation(
297            embedder_to_constellation_receiver,
298            &compositor.borrow(),
299            opts.config_dir.clone(),
300            embedder_proxy,
301            compositor_proxy.clone(),
302            time_profiler_chan,
303            mem_profiler_chan,
304            devtools_sender,
305            protocols,
306            builder.user_content_manager,
307        );
308
309        if opts::get().multiprocess {
310            prefs::add_observer(Box::new(constellation_proxy.clone()));
311        }
312
313        Self {
314            delegate: RefCell::new(Rc::new(DefaultServoDelegate)),
315            compositor,
316            javascript_evaluator: Rc::new(RefCell::new(JavaScriptEvaluator::new(
317                constellation_proxy.clone(),
318            ))),
319            constellation_proxy,
320            embedder_receiver,
321            shutdown_state,
322            webviews: Default::default(),
323            servo_errors: ServoErrorChannel::default(),
324            _js_engine_setup: js_engine_setup,
325            animating: Cell::new(false),
326        }
327    }
328
329    pub fn delegate(&self) -> Rc<dyn ServoDelegate> {
330        self.delegate.borrow().clone()
331    }
332
333    pub fn set_delegate(&self, delegate: Rc<dyn ServoDelegate>) {
334        *self.delegate.borrow_mut() = delegate;
335    }
336
337    /// Whether or not any [`WebView`] of this Servo instance has animating content, such as a CSS
338    /// animation or transition or is running `requestAnimationFrame` callbacks. In addition, this
339    /// returns true if WebXR content is running. This indicates that the embedding application
340    /// should be spinning the Servo event loop on regular intervals in order to trigger animation
341    /// updates.
342    pub fn animating(&self) -> bool {
343        self.animating.get()
344    }
345
346    /// **EXPERIMENTAL:** Intialize GL accelerated media playback. This currently only works on a limited number
347    /// of platforms. This should be run *before* calling [`Servo::new`] and creating the first [`WebView`].
348    pub fn initialize_gl_accelerated_media(display: NativeDisplay, api: GlApi, context: GlContext) {
349        WindowGLContext::initialize(display, api, context)
350    }
351
352    /// Spin the Servo event loop, which:
353    ///
354    ///   - Performs updates in the compositor, such as queued pinch zoom events
355    ///   - Runs delebgate methods on all `WebView`s and `Servo` itself
356    ///   - Maybe update the rendered compositor output, but *without* swapping buffers.
357    ///
358    /// The return value of this method indicates whether or not Servo, false indicates that Servo
359    /// has finished shutting down and you should not spin the event loop any longer.
360    pub fn spin_event_loop(&self) -> bool {
361        if self.shutdown_state.get() == ShutdownState::FinishedShuttingDown {
362            return false;
363        }
364
365        {
366            let compositor = self.compositor.borrow();
367            let mut messages = Vec::new();
368            while let Ok(message) = compositor.receiver().try_recv() {
369                match message {
370                    Ok(message) => messages.push(message),
371                    Err(error) => {
372                        warn!("Router deserialization error: {error}. Ignoring this CompositorMsg.")
373                    },
374                }
375            }
376            compositor.handle_messages(messages);
377        }
378
379        // Only handle incoming embedder messages if the compositor hasn't already started shutting down.
380        while let Ok(message) = self.embedder_receiver.try_recv() {
381            self.handle_embedder_message(message);
382
383            if self.shutdown_state.get() == ShutdownState::FinishedShuttingDown {
384                break;
385            }
386        }
387
388        if self.constellation_proxy.disconnected() {
389            self.delegate()
390                .notify_error(self, ServoError::LostConnectionWithBackend);
391        }
392
393        self.compositor.borrow_mut().perform_updates();
394        self.send_new_frame_ready_messages();
395        self.send_animating_changed_messages();
396        self.handle_delegate_errors();
397        self.clean_up_destroyed_webview_handles();
398
399        if self.shutdown_state.get() == ShutdownState::FinishedShuttingDown {
400            return false;
401        }
402
403        true
404    }
405
406    fn send_new_frame_ready_messages(&self) {
407        let webviews_needing_repaint = self.compositor.borrow().webviews_needing_repaint();
408
409        for webview in webviews_needing_repaint
410            .iter()
411            .filter_map(|webview_id| self.get_webview_handle(*webview_id))
412        {
413            webview.delegate().notify_new_frame_ready(webview);
414        }
415    }
416
417    fn send_animating_changed_messages(&self) {
418        let animating = self.compositor.borrow().webxr_running() ||
419            self.webviews
420                .borrow()
421                .values()
422                .filter_map(WebView::from_weak_handle)
423                .any(|webview| webview.animating());
424        if animating != self.animating.get() {
425            self.animating.set(animating);
426            self.delegate().notify_animating_changed(animating);
427        }
428    }
429
430    fn handle_delegate_errors(&self) {
431        while let Some(error) = self.servo_errors.try_recv() {
432            self.delegate().notify_error(self, error);
433        }
434    }
435
436    fn clean_up_destroyed_webview_handles(&self) {
437        // Remove any webview handles that have been destroyed and would not be upgradable.
438        // Note that `retain` is O(capacity) because it visits empty buckets, so it may be worth
439        // calling `shrink_to_fit` at some point to deal with cases where a long-running Servo
440        // instance goes from many open webviews to only a few.
441        self.webviews
442            .borrow_mut()
443            .retain(|_webview_id, webview| webview.strong_count() > 0);
444    }
445
446    pub fn setup_logging(&self) {
447        let constellation_chan = self.constellation_proxy.sender();
448        let env = env_logger::Env::default();
449        let env_logger = EnvLoggerBuilder::from_env(env).build();
450        let con_logger = FromEmbedderLogger::new(constellation_chan);
451
452        let filter = max(env_logger.filter(), con_logger.filter());
453        let logger = BothLogger(env_logger, con_logger);
454
455        log::set_boxed_logger(Box::new(logger)).expect("Failed to set logger.");
456        log::set_max_level(filter);
457    }
458
459    pub fn create_memory_report(&self, snd: IpcSender<MemoryReportResult>) {
460        self.constellation_proxy
461            .send(EmbedderToConstellationMessage::CreateMemoryReport(snd));
462    }
463
464    pub fn start_shutting_down(&self) {
465        if self.shutdown_state.get() != ShutdownState::NotShuttingDown {
466            warn!("Requested shutdown while already shutting down");
467            return;
468        }
469
470        debug!("Sending Exit message to Constellation");
471        self.constellation_proxy
472            .send(EmbedderToConstellationMessage::Exit);
473        self.shutdown_state.set(ShutdownState::ShuttingDown);
474    }
475
476    fn finish_shutting_down(&self) {
477        debug!("Servo received message that Constellation shutdown is complete");
478        self.shutdown_state.set(ShutdownState::FinishedShuttingDown);
479        self.compositor.borrow_mut().finish_shutting_down();
480    }
481
482    pub fn deinit(&self) {
483        self.compositor.borrow_mut().deinit();
484    }
485
486    fn get_webview_handle(&self, id: WebViewId) -> Option<WebView> {
487        self.webviews
488            .borrow()
489            .get(&id)
490            .and_then(WebView::from_weak_handle)
491    }
492
493    fn handle_embedder_message(&self, message: EmbedderMsg) {
494        match message {
495            EmbedderMsg::ShutdownComplete => self.finish_shutting_down(),
496            EmbedderMsg::Status(webview_id, status_text) => {
497                if let Some(webview) = self.get_webview_handle(webview_id) {
498                    webview.set_status_text(status_text);
499                }
500            },
501            EmbedderMsg::ChangePageTitle(webview_id, title) => {
502                if let Some(webview) = self.get_webview_handle(webview_id) {
503                    webview.set_page_title(title);
504                }
505            },
506            EmbedderMsg::MoveTo(webview_id, position) => {
507                if let Some(webview) = self.get_webview_handle(webview_id) {
508                    webview.delegate().request_move_to(webview, position);
509                }
510            },
511            EmbedderMsg::ResizeTo(webview_id, size) => {
512                if let Some(webview) = self.get_webview_handle(webview_id) {
513                    webview
514                        .delegate()
515                        .request_resize_to(webview, size.max(MINIMUM_WEBVIEW_SIZE));
516                }
517            },
518            EmbedderMsg::ShowSimpleDialog(webview_id, simple_dialog) => {
519                if let Some(webview) = self.get_webview_handle(webview_id) {
520                    webview.delegate().show_embedder_control(
521                        webview,
522                        EmbedderControl::SimpleDialog(simple_dialog),
523                    );
524                }
525            },
526            EmbedderMsg::ShowContextMenu(webview_id, ipc_sender, title, items) => {
527                if let Some(webview) = self.get_webview_handle(webview_id) {
528                    webview
529                        .delegate()
530                        .show_context_menu(webview, ipc_sender, title, items);
531                }
532            },
533            EmbedderMsg::AllowNavigationRequest(webview_id, pipeline_id, servo_url) => {
534                if let Some(webview) = self.get_webview_handle(webview_id) {
535                    let request = NavigationRequest {
536                        url: servo_url.into_url(),
537                        pipeline_id,
538                        constellation_proxy: self.constellation_proxy.clone(),
539                        response_sent: false,
540                    };
541                    webview.delegate().request_navigation(webview, request);
542                }
543            },
544            EmbedderMsg::AllowOpeningWebView(webview_id, response_sender) => {
545                if let Some(webview) = self.get_webview_handle(webview_id) {
546                    let webview_id_and_viewport_details = webview
547                        .delegate()
548                        .request_open_auxiliary_webview(webview)
549                        .map(|webview| (webview.id(), webview.viewport_details()));
550                    let _ = response_sender.send(webview_id_and_viewport_details);
551                }
552            },
553            EmbedderMsg::WebViewClosed(webview_id) => {
554                if let Some(webview) = self.get_webview_handle(webview_id) {
555                    webview.delegate().notify_closed(webview);
556                }
557            },
558            EmbedderMsg::WebViewFocused(webview_id, focus_result) => {
559                if focus_result {
560                    for id in self.webviews.borrow().keys() {
561                        if let Some(webview) = self.get_webview_handle(*id) {
562                            let focused = webview.id() == webview_id;
563                            webview.set_focused(focused);
564                        }
565                    }
566                }
567            },
568            EmbedderMsg::WebViewBlurred => {
569                for id in self.webviews.borrow().keys() {
570                    if let Some(webview) = self.get_webview_handle(*id) {
571                        webview.set_focused(false);
572                    }
573                }
574            },
575            EmbedderMsg::AllowUnload(webview_id, response_sender) => {
576                if let Some(webview) = self.get_webview_handle(webview_id) {
577                    let request = AllowOrDenyRequest::new(
578                        response_sender,
579                        AllowOrDeny::Allow,
580                        self.servo_errors.sender(),
581                    );
582                    webview.delegate().request_unload(webview, request);
583                }
584            },
585            EmbedderMsg::FinishJavaScriptEvaluation(evaluation_id, result) => {
586                self.javascript_evaluator
587                    .borrow_mut()
588                    .finish_evaluation(evaluation_id, result);
589            },
590            EmbedderMsg::InputEventHandled(webview_id, input_event_id, result) => {
591                self.compositor.borrow_mut().notify_input_event_handled(
592                    webview_id,
593                    input_event_id,
594                    result,
595                );
596
597                if let Some(webview) = self.get_webview_handle(webview_id) {
598                    webview
599                        .delegate()
600                        .notify_input_event_handled(webview, input_event_id, result);
601                }
602            },
603            EmbedderMsg::ClearClipboard(webview_id) => {
604                if let Some(webview) = self.get_webview_handle(webview_id) {
605                    webview.clipboard_delegate().clear(webview);
606                }
607            },
608            EmbedderMsg::GetClipboardText(webview_id, result_sender) => {
609                if let Some(webview) = self.get_webview_handle(webview_id) {
610                    webview
611                        .clipboard_delegate()
612                        .get_text(webview, StringRequest::from(result_sender));
613                }
614            },
615            EmbedderMsg::SetClipboardText(webview_id, string) => {
616                if let Some(webview) = self.get_webview_handle(webview_id) {
617                    webview.clipboard_delegate().set_text(webview, string);
618                }
619            },
620            EmbedderMsg::SetCursor(webview_id, cursor) => {
621                if let Some(webview) = self.get_webview_handle(webview_id) {
622                    webview.set_cursor(cursor);
623                }
624            },
625            EmbedderMsg::NewFavicon(webview_id, image) => {
626                if let Some(webview) = self.get_webview_handle(webview_id) {
627                    webview.set_favicon(image);
628                }
629            },
630            EmbedderMsg::NotifyLoadStatusChanged(webview_id, load_status) => {
631                if let Some(webview) = self.get_webview_handle(webview_id) {
632                    webview.set_load_status(load_status);
633                }
634            },
635            EmbedderMsg::HistoryTraversalComplete(webview_id, traversal_id) => {
636                if let Some(webview) = self.get_webview_handle(webview_id) {
637                    webview
638                        .delegate()
639                        .notify_traversal_complete(webview.clone(), traversal_id);
640                }
641            },
642            EmbedderMsg::HistoryChanged(webview_id, urls, current_index) => {
643                if let Some(webview) = self.get_webview_handle(webview_id) {
644                    let urls: Vec<_> = urls.into_iter().map(ServoUrl::into_url).collect();
645                    let current_url = urls[current_index].clone();
646
647                    webview
648                        .delegate()
649                        .notify_history_changed(webview.clone(), urls, current_index);
650                    webview.set_url(current_url);
651                }
652            },
653            EmbedderMsg::NotifyFullscreenStateChanged(webview_id, fullscreen) => {
654                if let Some(webview) = self.get_webview_handle(webview_id) {
655                    webview
656                        .delegate()
657                        .notify_fullscreen_state_changed(webview, fullscreen);
658                }
659            },
660            EmbedderMsg::WebResourceRequested(
661                webview_id,
662                web_resource_request,
663                response_sender,
664            ) => {
665                if let Some(webview) =
666                    webview_id.and_then(|webview_id| self.get_webview_handle(webview_id))
667                {
668                    let web_resource_load = WebResourceLoad::new(
669                        web_resource_request,
670                        response_sender,
671                        self.servo_errors.sender(),
672                    );
673                    webview
674                        .delegate()
675                        .load_web_resource(webview, web_resource_load);
676                } else {
677                    let web_resource_load = WebResourceLoad::new(
678                        web_resource_request,
679                        response_sender,
680                        self.servo_errors.sender(),
681                    );
682                    self.delegate().load_web_resource(web_resource_load);
683                }
684            },
685            EmbedderMsg::Panic(webview_id, reason, backtrace) => {
686                if let Some(webview) = self.get_webview_handle(webview_id) {
687                    webview
688                        .delegate()
689                        .notify_crashed(webview, reason, backtrace);
690                }
691            },
692            EmbedderMsg::GetSelectedBluetoothDevice(webview_id, items, response_sender) => {
693                if let Some(webview) = self.get_webview_handle(webview_id) {
694                    webview.delegate().show_bluetooth_device_dialog(
695                        webview,
696                        items,
697                        response_sender,
698                    );
699                }
700            },
701            EmbedderMsg::SelectFiles(control_id, file_picker_request, response_sender) => {
702                if file_picker_request.accept_current_paths_for_testing {
703                    let _ = response_sender.send(Some(file_picker_request.current_paths));
704                    return;
705                }
706                if let Some(webview) = self.get_webview_handle(control_id.webview_id) {
707                    webview.delegate().show_embedder_control(
708                        webview,
709                        EmbedderControl::FilePicker(FilePicker {
710                            id: control_id,
711                            file_picker_request,
712                            response_sender,
713                            response_sent: false,
714                        }),
715                    );
716                }
717            },
718            EmbedderMsg::RequestAuthentication(webview_id, url, for_proxy, response_sender) => {
719                if let Some(webview) = self.get_webview_handle(webview_id) {
720                    let authentication_request = AuthenticationRequest::new(
721                        url.into_url(),
722                        for_proxy,
723                        response_sender,
724                        self.servo_errors.sender(),
725                    );
726                    webview
727                        .delegate()
728                        .request_authentication(webview, authentication_request);
729                }
730            },
731            EmbedderMsg::PromptPermission(webview_id, requested_feature, response_sender) => {
732                if let Some(webview) = self.get_webview_handle(webview_id) {
733                    let permission_request = PermissionRequest {
734                        requested_feature,
735                        allow_deny_request: AllowOrDenyRequest::new(
736                            response_sender,
737                            AllowOrDeny::Deny,
738                            self.servo_errors.sender(),
739                        ),
740                    };
741                    webview
742                        .delegate()
743                        .request_permission(webview, permission_request);
744                }
745            },
746            EmbedderMsg::ReportProfile(_items) => {},
747            EmbedderMsg::MediaSessionEvent(webview_id, media_session_event) => {
748                if let Some(webview) = self.get_webview_handle(webview_id) {
749                    webview
750                        .delegate()
751                        .notify_media_session_event(webview, media_session_event);
752                }
753            },
754            EmbedderMsg::OnDevtoolsStarted(port, token) => match port {
755                Ok(port) => self
756                    .delegate()
757                    .notify_devtools_server_started(self, port, token),
758                Err(()) => self
759                    .delegate()
760                    .notify_error(self, ServoError::DevtoolsFailedToStart),
761            },
762            EmbedderMsg::RequestDevtoolsConnection(response_sender) => {
763                self.delegate().request_devtools_connection(
764                    self,
765                    AllowOrDenyRequest::new(
766                        response_sender,
767                        AllowOrDeny::Deny,
768                        self.servo_errors.sender(),
769                    ),
770                );
771            },
772            EmbedderMsg::PlayGamepadHapticEffect(
773                webview_id,
774                gamepad_index,
775                gamepad_haptic_effect_type,
776                ipc_sender,
777            ) => {
778                if let Some(webview) = self.get_webview_handle(webview_id) {
779                    webview.delegate().play_gamepad_haptic_effect(
780                        webview,
781                        gamepad_index,
782                        gamepad_haptic_effect_type,
783                        ipc_sender,
784                    );
785                }
786            },
787            EmbedderMsg::StopGamepadHapticEffect(webview_id, gamepad_index, ipc_sender) => {
788                if let Some(webview) = self.get_webview_handle(webview_id) {
789                    webview.delegate().stop_gamepad_haptic_effect(
790                        webview,
791                        gamepad_index,
792                        ipc_sender,
793                    );
794                }
795            },
796            EmbedderMsg::ShowNotification(webview_id, notification) => {
797                match webview_id.and_then(|webview_id| self.get_webview_handle(webview_id)) {
798                    Some(webview) => webview.delegate().show_notification(webview, notification),
799                    None => self.delegate().show_notification(notification),
800                }
801            },
802            EmbedderMsg::ShowEmbedderControl(control_id, position, embedder_control) => {
803                if let Some(webview) = self.get_webview_handle(control_id.webview_id) {
804                    let constellation_proxy = self.constellation_proxy.clone();
805                    let embedder_control = match embedder_control {
806                        EmbedderControlRequest::SelectElement(options, selected_option) => {
807                            EmbedderControl::SelectElement(SelectElement {
808                                id: control_id,
809                                options,
810                                selected_option,
811                                position,
812                                constellation_proxy,
813                                response_sent: false,
814                            })
815                        },
816                        EmbedderControlRequest::ColorPicker(current_color) => {
817                            EmbedderControl::ColorPicker(ColorPicker {
818                                id: control_id,
819                                current_color: Some(current_color),
820                                position,
821                                constellation_proxy,
822                                response_sent: false,
823                            })
824                        },
825                        EmbedderControlRequest::InputMethod(input_method_request) => {
826                            EmbedderControl::InputMethod(InputMethodControl {
827                                id: control_id,
828                                input_method_type: input_method_request.input_method_type,
829                                text: input_method_request.text,
830                                insertion_point: input_method_request.insertion_point,
831                                position,
832                                multiline: input_method_request.multiline,
833                            })
834                        },
835                        EmbedderControlRequest::FilePicker { .. } => unreachable!(
836                            "This message should be routed through the FileManagerThread"
837                        ),
838                    };
839
840                    webview
841                        .delegate()
842                        .show_embedder_control(webview, embedder_control);
843                }
844            },
845            EmbedderMsg::HideEmbedderControl(control_id) => {
846                if let Some(webview) = self.get_webview_handle(control_id.webview_id) {
847                    webview
848                        .delegate()
849                        .hide_embedder_control(webview, control_id);
850                }
851            },
852            EmbedderMsg::GetWindowRect(webview_id, response_sender) => {
853                let window_rect = || {
854                    let Some(webview) = self.get_webview_handle(webview_id) else {
855                        return DeviceIndependentIntRect::default();
856                    };
857                    let hidpi_scale_factor = webview.hidpi_scale_factor();
858                    let Some(screen_geometry) = webview.delegate().screen_geometry(webview) else {
859                        return DeviceIndependentIntRect::default();
860                    };
861
862                    convert_rect_to_css_pixel(screen_geometry.window_rect, hidpi_scale_factor)
863                };
864
865                if let Err(error) = response_sender.send(window_rect()) {
866                    warn!("Failed to respond to GetWindowRect: {error}");
867                }
868            },
869            EmbedderMsg::GetScreenMetrics(webview_id, response_sender) => {
870                let screen_metrics = || {
871                    let Some(webview) = self.get_webview_handle(webview_id) else {
872                        return ScreenMetrics::default();
873                    };
874                    let hidpi_scale_factor = webview.hidpi_scale_factor();
875                    let Some(screen_geometry) = webview.delegate().screen_geometry(webview) else {
876                        return ScreenMetrics::default();
877                    };
878
879                    ScreenMetrics {
880                        screen_size: convert_size_to_css_pixel(
881                            screen_geometry.size,
882                            hidpi_scale_factor,
883                        ),
884                        available_size: convert_size_to_css_pixel(
885                            screen_geometry.available_size,
886                            hidpi_scale_factor,
887                        ),
888                    }
889                };
890                if let Err(error) = response_sender.send(screen_metrics()) {
891                    warn!("Failed to respond to GetScreenMetrics: {error}");
892                }
893            },
894        }
895    }
896
897    pub fn constellation_sender(&self) -> Sender<EmbedderToConstellationMessage> {
898        self.constellation_proxy.sender()
899    }
900
901    pub fn execute_webdriver_command(&self, command: WebDriverCommandMsg) {
902        self.constellation_proxy
903            .send(EmbedderToConstellationMessage::WebDriverCommand(command));
904    }
905
906    pub fn set_preference(&self, name: &str, value: PrefValue) {
907        let mut preferences = prefs::get().clone();
908        preferences.set_value(name, value);
909        prefs::set(preferences);
910    }
911}
912
913fn create_embedder_channel(
914    event_loop_waker: Box<dyn EventLoopWaker>,
915) -> (EmbedderProxy, Receiver<EmbedderMsg>) {
916    let (sender, receiver) = unbounded();
917    (
918        EmbedderProxy {
919            sender,
920            event_loop_waker,
921        },
922        receiver,
923    )
924}
925
926fn create_compositor_channel(
927    event_loop_waker: Box<dyn EventLoopWaker>,
928) -> (CompositorProxy, RoutedReceiver<CompositorMsg>) {
929    let (sender, receiver) = unbounded();
930    let sender_clone = sender.clone();
931    let event_loop_waker_clone = event_loop_waker.clone();
932    // This callback is equivalent to `CompositorProxy::send`
933    let result_callback = move |msg: Result<CompositorMsg, ipc_channel::Error>| {
934        if let Err(err) = sender_clone.send(msg) {
935            warn!("Failed to send response ({:?}).", err);
936        }
937        event_loop_waker_clone.wake();
938    };
939
940    let generic_callback =
941        GenericCallback::new(result_callback).expect("Failed to create callback");
942    let cross_process_compositor_api = CrossProcessCompositorApi::new(generic_callback);
943    let compositor_proxy = CompositorProxy {
944        sender,
945        cross_process_compositor_api,
946        event_loop_waker,
947    };
948
949    (compositor_proxy, receiver)
950}
951
952#[allow(clippy::too_many_arguments)]
953fn create_constellation(
954    embedder_to_constellation_receiver: Receiver<EmbedderToConstellationMessage>,
955    compositor: &IOCompositor,
956    config_dir: Option<PathBuf>,
957    embedder_proxy: EmbedderProxy,
958    compositor_proxy: CompositorProxy,
959    time_profiler_chan: time::ProfilerChan,
960    mem_profiler_chan: mem::ProfilerChan,
961    devtools_sender: Option<Sender<devtools_traits::DevtoolsControlMsg>>,
962    protocols: ProtocolRegistry,
963    user_content_manager: UserContentManager,
964) {
965    // Global configuration options, parsed from the command line.
966    let opts = opts::get();
967
968    #[cfg(feature = "bluetooth")]
969    let bluetooth_thread: IpcSender<BluetoothRequest> =
970        BluetoothThreadFactory::new(embedder_proxy.clone());
971
972    let privileged_urls = protocols.privileged_urls();
973
974    let (public_resource_threads, private_resource_threads, async_runtime) = new_resource_threads(
975        devtools_sender.clone(),
976        time_profiler_chan.clone(),
977        mem_profiler_chan.clone(),
978        embedder_proxy.clone(),
979        config_dir.clone(),
980        opts.certificate_path.clone(),
981        opts.ignore_certificate_errors,
982        Arc::new(protocols),
983    );
984
985    let (private_storage_threads, public_storage_threads) =
986        new_storage_threads(mem_profiler_chan.clone(), config_dir);
987
988    let system_font_service = Arc::new(
989        SystemFontService::spawn(
990            compositor_proxy.cross_process_compositor_api.clone(),
991            mem_profiler_chan.clone(),
992        )
993        .to_proxy(),
994    );
995
996    let initial_state = InitialConstellationState {
997        compositor_proxy,
998        embedder_proxy,
999        devtools_sender,
1000        #[cfg(feature = "bluetooth")]
1001        bluetooth_thread,
1002        system_font_service,
1003        public_resource_threads,
1004        private_resource_threads,
1005        public_storage_threads,
1006        private_storage_threads,
1007        time_profiler_chan,
1008        mem_profiler_chan,
1009        #[cfg(feature = "webxr")]
1010        webxr_registry: Some(compositor.webxr_main_thread_registry()),
1011        #[cfg(not(feature = "webxr"))]
1012        webxr_registry: None,
1013        webgl_threads: Some(compositor.webgl_threads()),
1014        webrender_external_images: compositor.webrender_external_images(),
1015        #[cfg(feature = "webgpu")]
1016        wgpu_image_map: compositor.webgpu_image_map(),
1017        user_content_manager,
1018        async_runtime,
1019        privileged_urls,
1020    };
1021
1022    let layout_factory = Arc::new(LayoutFactoryImpl());
1023
1024    Constellation::<script::ScriptThread, script::ServiceWorkerManager>::start(
1025        embedder_to_constellation_receiver,
1026        initial_state,
1027        layout_factory,
1028        opts.random_pipeline_closure_probability,
1029        opts.random_pipeline_closure_seed,
1030        opts.hard_fail,
1031    );
1032}
1033
1034// A logger that logs to two downstream loggers.
1035// This should probably be in the log crate.
1036struct BothLogger<Log1, Log2>(Log1, Log2);
1037
1038impl<Log1, Log2> Log for BothLogger<Log1, Log2>
1039where
1040    Log1: Log,
1041    Log2: Log,
1042{
1043    fn enabled(&self, metadata: &Metadata) -> bool {
1044        self.0.enabled(metadata) || self.1.enabled(metadata)
1045    }
1046
1047    fn log(&self, record: &Record) {
1048        self.0.log(record);
1049        self.1.log(record);
1050    }
1051
1052    fn flush(&self) {
1053        self.0.flush();
1054        self.1.flush();
1055    }
1056}
1057
1058pub fn set_logger(script_to_constellation_chan: ScriptToConstellationChan) {
1059    let con_logger = FromScriptLogger::new(script_to_constellation_chan);
1060    let env = env_logger::Env::default();
1061    let env_logger = EnvLoggerBuilder::from_env(env).build();
1062
1063    let filter = max(env_logger.filter(), con_logger.filter());
1064    let logger = BothLogger(env_logger, con_logger);
1065
1066    log::set_boxed_logger(Box::new(logger)).expect("Failed to set logger.");
1067    log::set_max_level(filter);
1068}
1069
1070/// Content process entry point.
1071pub fn run_content_process(token: String) {
1072    let (unprivileged_content_sender, unprivileged_content_receiver) =
1073        ipc::channel::<UnprivilegedContent>().unwrap();
1074    let connection_bootstrap: IpcSender<IpcSender<UnprivilegedContent>> =
1075        IpcSender::connect(token).unwrap();
1076    connection_bootstrap
1077        .send(unprivileged_content_sender)
1078        .unwrap();
1079
1080    let unprivileged_content = unprivileged_content_receiver.recv().unwrap();
1081    opts::initialize_options(unprivileged_content.opts());
1082    prefs::set(unprivileged_content.prefs().clone());
1083
1084    // Enter the sandbox if necessary.
1085    if opts::get().sandbox {
1086        create_sandbox();
1087    }
1088
1089    let _js_engine_setup = script::init();
1090
1091    match unprivileged_content {
1092        UnprivilegedContent::Pipeline(mut content) => {
1093            media_platform::init();
1094
1095            // Start the fetch thread for this content process.
1096            let fetch_thread_join_handle = start_fetch_thread();
1097
1098            set_logger(content.script_to_constellation_chan().clone());
1099
1100            let (background_hang_monitor_register, join_handle) =
1101                content.register_with_background_hang_monitor();
1102            let layout_factory = Arc::new(LayoutFactoryImpl());
1103
1104            content.register_system_memory_reporter();
1105
1106            let script_join_handle = content.start_all::<script::ScriptThread>(
1107                true,
1108                layout_factory,
1109                background_hang_monitor_register,
1110                None,
1111            );
1112
1113            // Since wait_for_completion is true,
1114            // here we know that the script-thread
1115            // will exit(or already has),
1116            // and so we can join first on the script, and then on the BHM worker, threads.
1117            script_join_handle
1118                .join()
1119                .expect("Failed to join on the script thread.");
1120            join_handle
1121                .join()
1122                .expect("Failed to join on the BHM background thread.");
1123
1124            StyleThreadPool::shutdown();
1125
1126            // Shut down the fetch thread started above.
1127            exit_fetch_thread();
1128            fetch_thread_join_handle
1129                .join()
1130                .expect("Failed to join on the fetch thread in the constellation");
1131        },
1132        UnprivilegedContent::ServiceWorker(content) => {
1133            content.start::<ServiceWorkerManager>();
1134        },
1135    }
1136}
1137
1138#[cfg(all(
1139    not(target_os = "windows"),
1140    not(target_os = "ios"),
1141    not(target_os = "android"),
1142    not(target_arch = "arm"),
1143    not(target_arch = "aarch64"),
1144    not(target_env = "ohos"),
1145))]
1146fn create_sandbox() {
1147    ChildSandbox::new(content_process_sandbox_profile())
1148        .activate()
1149        .expect("Failed to activate sandbox!");
1150}
1151
1152#[cfg(any(
1153    target_os = "windows",
1154    target_os = "ios",
1155    target_os = "android",
1156    target_arch = "arm",
1157    target_arch = "aarch64",
1158    target_env = "ohos",
1159))]
1160fn create_sandbox() {
1161    panic!("Sandboxing is not supported on Windows, iOS, ARM targets and android.");
1162}
1163
1164struct DefaultEventLoopWaker;
1165
1166impl EventLoopWaker for DefaultEventLoopWaker {
1167    fn clone_box(&self) -> Box<dyn EventLoopWaker> {
1168        Box::new(DefaultEventLoopWaker)
1169    }
1170}
1171
1172#[cfg(feature = "webxr")]
1173struct DefaultWebXrRegistry;
1174#[cfg(feature = "webxr")]
1175impl webxr::WebXrRegistry for DefaultWebXrRegistry {}
1176
1177pub struct ServoBuilder {
1178    rendering_context: Rc<dyn RenderingContext>,
1179    opts: Option<Box<Opts>>,
1180    preferences: Option<Box<Preferences>>,
1181    event_loop_waker: Box<dyn EventLoopWaker>,
1182    user_content_manager: UserContentManager,
1183    protocol_registry: ProtocolRegistry,
1184    refresh_driver: Option<Rc<dyn RefreshDriver>>,
1185    #[cfg(feature = "webxr")]
1186    webxr_registry: Box<dyn webxr::WebXrRegistry>,
1187}
1188
1189impl ServoBuilder {
1190    pub fn new(rendering_context: Rc<dyn RenderingContext>) -> Self {
1191        Self {
1192            rendering_context,
1193            opts: None,
1194            preferences: None,
1195            event_loop_waker: Box::new(DefaultEventLoopWaker),
1196            user_content_manager: UserContentManager::default(),
1197            protocol_registry: ProtocolRegistry::default(),
1198            refresh_driver: None,
1199            #[cfg(feature = "webxr")]
1200            webxr_registry: Box::new(DefaultWebXrRegistry),
1201        }
1202    }
1203
1204    pub fn build(self) -> Servo {
1205        Servo::new(self)
1206    }
1207
1208    pub fn opts(mut self, opts: Opts) -> Self {
1209        self.opts = Some(Box::new(opts));
1210        self
1211    }
1212
1213    pub fn preferences(mut self, preferences: Preferences) -> Self {
1214        self.preferences = Some(Box::new(preferences));
1215        self
1216    }
1217
1218    pub fn event_loop_waker(mut self, event_loop_waker: Box<dyn EventLoopWaker>) -> Self {
1219        self.event_loop_waker = event_loop_waker;
1220        self
1221    }
1222
1223    pub fn refresh_driver(mut self, refresh_driver: Rc<dyn RefreshDriver>) -> Self {
1224        self.refresh_driver = Some(refresh_driver);
1225        self
1226    }
1227
1228    pub fn user_content_manager(mut self, user_content_manager: UserContentManager) -> Self {
1229        self.user_content_manager = user_content_manager;
1230        self
1231    }
1232
1233    pub fn protocol_registry(mut self, protocol_registry: ProtocolRegistry) -> Self {
1234        self.protocol_registry = protocol_registry;
1235        self
1236    }
1237
1238    #[cfg(feature = "webxr")]
1239    pub fn webxr_registry(mut self, webxr_registry: Box<dyn webxr::WebXrRegistry>) -> Self {
1240        self.webxr_registry = webxr_registry;
1241        self
1242    }
1243}