/* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ //! Servo, the mighty web browser engine from the future. //! //! This is a very simple library that wires all of Servo's components //! together as type `Servo`, along with a generic client //! implementing the `WindowMethods` trait, to create a working web //! browser. //! //! The `Servo` type is responsible for configuring a //! `Constellation`, which does the heavy lifting of coordinating all //! of Servo's internal subsystems, including the `ScriptThread` and the //! `LayoutThread`, as well maintains the navigation context. //! //! `Servo` is fed events from a generic type that implements the //! `WindowMethods` trait. mod proxies; mod webview; use std::borrow::Cow; use std::cell::RefCell; use std::cmp::max; use std::path::PathBuf; use std::rc::Rc; use std::sync::{Arc, Mutex}; use std::thread; use std::vec::Drain; pub use base::id::TopLevelBrowsingContextId; use base::id::{PipelineId, PipelineNamespace, PipelineNamespaceId}; use bluetooth::BluetoothThreadFactory; use bluetooth_traits::BluetoothRequest; use canvas::canvas_paint_thread::CanvasPaintThread; use canvas::WebGLComm; use canvas_traits::webgl::{GlType, WebGLThreads}; use compositing::webview::UnknownWebView; use compositing::windowing::{EmbedderEvent, EmbedderMethods, WindowMethods}; use compositing::{CompositeTarget, IOCompositor, InitialCompositorState, ShutdownState}; use compositing_traits::{CompositorMsg, CompositorProxy, CompositorReceiver, ConstellationMsg}; #[cfg(all( not(target_os = "windows"), not(target_os = "ios"), not(target_os = "android"), not(target_arch = "arm"), not(target_arch = "aarch64"), not(target_env = "ohos"), ))] use constellation::content_process_sandbox_profile; use constellation::{ Constellation, FromCompositorLogger, FromScriptLogger, InitialConstellationState, UnprivilegedContent, }; use crossbeam_channel::{unbounded, Receiver, Sender}; pub use embedder_traits::*; use env_logger::Builder as EnvLoggerBuilder; use euclid::Scale; use fonts::SystemFontService; #[cfg(all( not(target_os = "windows"), not(target_os = "ios"), not(target_os = "android"), not(target_arch = "arm"), not(target_arch = "aarch64"), not(target_env = "ohos"), ))] use gaol::sandbox::{ChildSandbox, ChildSandboxMethods}; pub use gleam::gl; use gleam::gl::RENDERER; use ipc_channel::ipc::{self, IpcSender}; use ipc_channel::router::ROUTER; pub use keyboard_types::*; #[cfg(feature = "layout_2013")] pub use layout_thread_2013; use log::{error, trace, warn, Log, Metadata, Record}; use media::{GLPlayerThreads, GlApi, NativeDisplay, WindowGLContext}; use net::protocols::ProtocolRegistry; use net::resource_thread::new_resource_threads; use profile::{mem as profile_mem, time as profile_time}; use profile_traits::{mem, time}; use script::{JSEngineSetup, ServiceWorkerManager}; use script_layout_interface::LayoutFactory; use script_traits::{ScriptToConstellationChan, WindowSizeData}; use servo_config::opts::Opts; use servo_config::prefs::Preferences; use servo_config::{opts, pref, prefs}; use servo_media::player::context::GlContext; use servo_media::ServoMedia; #[cfg(all(target_os = "linux", not(target_env = "ohos")))] use surfman::platform::generic::multi::connection::NativeConnection as LinuxNativeConnection; #[cfg(all(target_os = "linux", not(target_env = "ohos")))] use surfman::platform::generic::multi::context::NativeContext as LinuxNativeContext; use surfman::{GLApi, GLVersion}; #[cfg(all(target_os = "linux", not(target_env = "ohos")))] use surfman::{NativeConnection, NativeContext}; #[cfg(feature = "webgpu")] pub use webgpu; #[cfg(feature = "webgpu")] use webgpu::swapchain::WGPUImageMap; use webrender::{RenderApiSender, ShaderPrecacheFlags, UploadMethod, ONE_TIME_USAGE_HINT}; use webrender_api::{ColorF, DocumentId, FramePublishId}; use webrender_traits::rendering_context::RenderingContext; use webrender_traits::{ CrossProcessCompositorApi, WebrenderExternalImageHandlers, WebrenderExternalImageRegistry, WebrenderImageHandlerType, }; #[cfg(feature = "webxr")] pub use webxr; pub use { background_hang_monitor, base, bluetooth, bluetooth_traits, canvas, canvas_traits, compositing, devtools, devtools_traits, euclid, fonts, ipc_channel, layout_thread_2020, media, net, net_traits, profile, profile_traits, script, script_layout_interface, script_traits, servo_config as config, servo_config, servo_geometry, servo_url, style, style_traits, webrender_api, webrender_traits, }; use crate::proxies::ConstellationProxy; pub use crate::webview::WebView; #[cfg(feature = "webdriver")] fn webdriver(port: u16, constellation: Sender) { webdriver_server::start_server(port, constellation); } #[cfg(not(feature = "webdriver"))] fn webdriver(_port: u16, _constellation: Sender) {} #[cfg(feature = "media-gstreamer")] mod media_platform { #[cfg(any(windows, target_os = "macos"))] mod gstreamer_plugins { include!(concat!(env!("OUT_DIR"), "/gstreamer_plugins.rs")); } use servo_media_gstreamer::GStreamerBackend; use super::ServoMedia; #[cfg(any(windows, target_os = "macos"))] pub fn init() { ServoMedia::init_with_backend(|| { let mut plugin_dir = std::env::current_exe().unwrap(); plugin_dir.pop(); if cfg!(target_os = "macos") { plugin_dir.push("lib"); } match GStreamerBackend::init_with_plugins( plugin_dir, gstreamer_plugins::GSTREAMER_PLUGINS, ) { Ok(b) => b, Err(e) => { eprintln!("Error initializing GStreamer: {:?}", e); std::process::exit(1); }, } }); } #[cfg(not(any(windows, target_os = "macos")))] pub fn init() { ServoMedia::init::(); } } #[cfg(not(feature = "media-gstreamer"))] mod media_platform { use super::ServoMedia; pub fn init() { ServoMedia::init::(); } } /// The in-process interface to Servo. /// /// It does everything necessary to render the web, primarily /// orchestrating the interaction between JavaScript, CSS layout, /// rendering, and the client window. /// /// Clients create a `Servo` instance for a given reference-counted type /// implementing `WindowMethods`, which is the bridge to whatever /// application Servo is embedded in. Clients then create an event /// loop to pump messages between the embedding application and /// various browser components. pub struct Servo { compositor: Rc>, constellation_proxy: ConstellationProxy, embedder_receiver: Receiver, messages_for_embedder: Vec, /// For single-process Servo instances, this field controls the initialization /// and deinitialization of the JS Engine. Multiprocess Servo instances have their /// own instance that exists in the content process instead. _js_engine_setup: Option, } #[derive(Clone)] struct RenderNotifier { compositor_proxy: CompositorProxy, } impl RenderNotifier { pub fn new(compositor_proxy: CompositorProxy) -> RenderNotifier { RenderNotifier { compositor_proxy } } } impl webrender_api::RenderNotifier for RenderNotifier { fn clone(&self) -> Box { Box::new(RenderNotifier::new(self.compositor_proxy.clone())) } fn wake_up(&self, _composite_needed: bool) {} fn new_frame_ready( &self, document_id: DocumentId, _scrolled: bool, composite_needed: bool, _frame_publish_id: FramePublishId, ) { self.compositor_proxy .send(CompositorMsg::NewWebRenderFrameReady( document_id, composite_needed, )); } } impl Servo { #[cfg_attr( feature = "tracing", tracing::instrument( skip(preferences, rendering_context, embedder, window), fields(servo_profiling = true), level = "trace", ) )] pub fn new( opts: Opts, preferences: Preferences, rendering_context: Rc, mut embedder: Box, window: Rc, user_agent: Option, composite_target: CompositeTarget, ) -> Self { // Global configuration options, parsed from the command line. opts::set_options(opts); let opts = opts::get(); // Set the preferences globally. // TODO: It would be better to make these private to a particular Servo instance. servo_config::prefs::set(preferences); use std::sync::atomic::Ordering; style::context::DEFAULT_DISABLE_STYLE_SHARING_CACHE .store(opts.debug.disable_share_style_cache, Ordering::Relaxed); style::context::DEFAULT_DUMP_STYLE_STATISTICS .store(opts.debug.dump_style_statistics, Ordering::Relaxed); style::traversal::IS_SERVO_NONINCREMENTAL_LAYOUT .store(opts.nonincremental_layout, Ordering::Relaxed); if !opts.multiprocess { media_platform::init(); } let user_agent = match user_agent { Some(ref ua) if ua == "ios" => default_user_agent_string_for(UserAgent::iOS).into(), Some(ref ua) if ua == "android" => { default_user_agent_string_for(UserAgent::Android).into() }, Some(ref ua) if ua == "desktop" => { default_user_agent_string_for(UserAgent::Desktop).into() }, Some(ref ua) if ua == "ohos" => { default_user_agent_string_for(UserAgent::OpenHarmony).into() }, Some(ua) => ua.into(), None => embedder .get_user_agent_string() .map(Into::into) .unwrap_or(default_user_agent_string_for(DEFAULT_USER_AGENT).into()), }; // Get GL bindings let webrender_gl = rendering_context.gl_api(); // Make sure the gl context is made current. if let Err(err) = rendering_context.make_current() { warn!("Failed to make the rendering context current: {:?}", err); } debug_assert_eq!(webrender_gl.get_error(), gleam::gl::NO_ERROR,); // Bind the webrender framebuffer let framebuffer_object = rendering_context.framebuffer_object(); webrender_gl.bind_framebuffer(gleam::gl::FRAMEBUFFER, framebuffer_object); // Reserving a namespace to create TopLevelBrowsingContextId. PipelineNamespace::install(PipelineNamespaceId(0)); // Get both endpoints of a special channel for communication between // the client window and the compositor. This channel is unique because // messages to client may need to pump a platform-specific event loop // to deliver the message. let event_loop_waker = embedder.create_event_loop_waker(); let (compositor_proxy, compositor_receiver) = create_compositor_channel(event_loop_waker.clone()); let (embedder_proxy, embedder_receiver) = create_embedder_channel(event_loop_waker.clone()); let time_profiler_chan = profile_time::Profiler::create( &opts.time_profiling, opts.time_profiler_trace_path.clone(), ); let mem_profiler_chan = profile_mem::Profiler::create(opts.mem_profiler_period); let devtools_sender = if pref!(devtools_server_enabled) { Some(devtools::start_server( pref!(devtools_server_port) as u16, embedder_proxy.clone(), )) } else { None }; let coordinates: compositing::windowing::EmbedderCoordinates = window.get_coordinates(); let device_pixel_ratio = coordinates.hidpi_factor.get(); let viewport_size = coordinates.viewport.size().to_f32() / device_pixel_ratio; let (mut webrender, webrender_api_sender) = { let mut debug_flags = webrender::DebugFlags::empty(); debug_flags.set( webrender::DebugFlags::PROFILER_DBG, opts.debug.webrender_stats, ); let render_notifier = Box::new(RenderNotifier::new(compositor_proxy.clone())); let clear_color = servo_config::pref!(shell_background_color_rgba); let clear_color = ColorF::new( clear_color[0] as f32, clear_color[1] as f32, clear_color[2] as f32, clear_color[3] as f32, ); // Use same texture upload method as Gecko with ANGLE: // https://searchfox.org/mozilla-central/source/gfx/webrender_bindings/src/bindings.rs#1215-1219 let upload_method = if webrender_gl.get_string(RENDERER).starts_with("ANGLE") { UploadMethod::Immediate } else { UploadMethod::PixelBuffer(ONE_TIME_USAGE_HINT) }; let worker_threads = thread::available_parallelism() .map(|i| i.get()) .unwrap_or(pref!(threadpools_fallback_worker_num) as usize) .min(pref!(threadpools_webrender_workers_max).max(1) as usize); let workers = Some(Arc::new( rayon::ThreadPoolBuilder::new() .num_threads(worker_threads) .thread_name(|idx| format!("WRWorker#{}", idx)) .build() .unwrap(), )); webrender::create_webrender_instance( webrender_gl.clone(), render_notifier, webrender::WebRenderOptions { // We force the use of optimized shaders here because rendering is broken // on Android emulators with unoptimized shaders. This is due to a known // issue in the emulator's OpenGL emulation layer. // See: https://github.com/servo/servo/issues/31726 use_optimized_shaders: true, resource_override_path: opts.shaders_dir.clone(), debug_flags, precache_flags: if pref!(gfx_precache_shaders) { ShaderPrecacheFlags::FULL_COMPILE } else { ShaderPrecacheFlags::empty() }, enable_aa: pref!(gfx_text_antialiasing_enabled), enable_subpixel_aa: pref!(gfx_subpixel_text_antialiasing_enabled), allow_texture_swizzling: pref!(gfx_texture_swizzling_enabled), clear_color, upload_method, workers, ..Default::default() }, None, ) .expect("Unable to initialize webrender!") }; let webrender_api = webrender_api_sender.create_api(); let webrender_document = webrender_api.add_document(coordinates.get_viewport().size()); // Important that this call is done in a single-threaded fashion, we // can't defer it after `create_constellation` has started. let js_engine_setup = if !opts.multiprocess { Some(script::init()) } else { None }; // Create the webgl thread let gl_type = match webrender_gl.get_type() { gleam::gl::GlType::Gl => GlType::Gl, gleam::gl::GlType::Gles => GlType::Gles, }; let (external_image_handlers, external_images) = WebrenderExternalImageHandlers::new(); let mut external_image_handlers = Box::new(external_image_handlers); let WebGLComm { webgl_threads, #[cfg(feature = "webxr")] webxr_layer_grand_manager, image_handler, } = WebGLComm::new( rendering_context.clone(), webrender_api.create_sender(), webrender_document, external_images.clone(), gl_type, ); // Set webrender external image handler for WebGL textures external_image_handlers.set_handler(image_handler, WebrenderImageHandlerType::WebGL); // Create the WebXR main thread #[cfg(feature = "webxr")] let mut webxr_main_thread = webxr::MainThreadRegistry::new(event_loop_waker, webxr_layer_grand_manager) .expect("Failed to create WebXR device registry"); #[cfg(feature = "webxr")] if pref!(dom_webxr_enabled) { embedder.register_webxr(&mut webxr_main_thread, embedder_proxy.clone()); } #[cfg(feature = "webgpu")] let wgpu_image_handler = webgpu::WGPUExternalImages::default(); #[cfg(feature = "webgpu")] let wgpu_image_map = wgpu_image_handler.images.clone(); #[cfg(feature = "webgpu")] external_image_handlers.set_handler( Box::new(wgpu_image_handler), WebrenderImageHandlerType::WebGPU, ); let (player_context, glplayer_threads) = Self::create_media_window_gl_context( &mut external_image_handlers, external_images.clone(), &rendering_context, ); webrender.set_external_image_handler(external_image_handlers); // The division by 1 represents the page's default zoom of 100%, // and gives us the appropriate CSSPixel type for the viewport. let window_size = WindowSizeData { initial_viewport: viewport_size / Scale::new(1.0), device_pixel_ratio: Scale::new(device_pixel_ratio), }; // Create the constellation, which maintains the engine pipelines, including script and // layout, as well as the navigation context. let mut protocols = ProtocolRegistry::with_internal_protocols(); protocols.merge(embedder.get_protocol_handlers()); let constellation_chan = create_constellation( user_agent, opts.config_dir.clone(), embedder_proxy, compositor_proxy.clone(), time_profiler_chan.clone(), mem_profiler_chan.clone(), devtools_sender, webrender_document, webrender_api_sender, #[cfg(feature = "webxr")] webxr_main_thread.registry(), player_context, Some(webgl_threads), glplayer_threads, window_size, external_images, #[cfg(feature = "webgpu")] wgpu_image_map, protocols, ); if cfg!(feature = "webdriver") { if let Some(port) = opts.webdriver_port { webdriver(port, constellation_chan.clone()); } } let composite_target = if let Some(path) = opts.output_file.clone() { CompositeTarget::PngFile(path.into()) } else { composite_target }; // The compositor coordinates with the client window to create the final // rendered page and display it somewhere. let compositor = IOCompositor::new( window, InitialCompositorState { sender: compositor_proxy, receiver: compositor_receiver, constellation_chan: constellation_chan.clone(), time_profiler_chan, mem_profiler_chan, webrender, webrender_document, webrender_api, rendering_context, webrender_gl, #[cfg(feature = "webxr")] webxr_main_thread, }, composite_target, opts.exit_after_load, opts.debug.convert_mouse_to_touch, embedder.get_version_string().unwrap_or_default(), ); Servo { compositor: Rc::new(RefCell::new(compositor)), constellation_proxy: ConstellationProxy::new(constellation_chan), embedder_receiver, messages_for_embedder: Vec::new(), _js_engine_setup: js_engine_setup, } } #[cfg(all(target_os = "linux", not(target_env = "ohos")))] fn get_native_media_display_and_gl_context( rendering_context: &Rc, ) -> Option<(NativeDisplay, GlContext)> { let gl_context = match rendering_context.context() { NativeContext::Default(LinuxNativeContext::Default(native_context)) => { GlContext::Egl(native_context.egl_context as usize) }, NativeContext::Default(LinuxNativeContext::Alternate(native_context)) => { GlContext::Egl(native_context.egl_context as usize) }, NativeContext::Alternate(_) => return None, }; let native_display = match rendering_context.connection().native_connection() { NativeConnection::Default(LinuxNativeConnection::Default(connection)) => { NativeDisplay::Egl(connection.0 as usize) }, NativeConnection::Default(LinuxNativeConnection::Alternate(connection)) => { NativeDisplay::X11(connection.x11_display as usize) }, NativeConnection::Alternate(_) => return None, }; Some((native_display, gl_context)) } // @TODO(victor): https://github.com/servo/media/pull/315 #[cfg(target_os = "windows")] fn get_native_media_display_and_gl_context( rendering_context: &Rc, ) -> Option<(NativeDisplay, GlContext)> { #[cfg(feature = "no-wgl")] { let gl_context = GlContext::Egl(rendering_context.context().egl_context as usize); let native_display = NativeDisplay::Egl(rendering_context.device().egl_display as usize); Some((native_display, gl_context)) } #[cfg(not(feature = "no-wgl"))] None } #[cfg(not(any( target_os = "windows", all(target_os = "linux", not(target_env = "ohos")) )))] fn get_native_media_display_and_gl_context( _rendering_context: &Rc, ) -> Option<(NativeDisplay, GlContext)> { None } fn create_media_window_gl_context( external_image_handlers: &mut WebrenderExternalImageHandlers, external_images: Arc>, rendering_context: &Rc, ) -> (WindowGLContext, Option) { if !pref!(media_glvideo_enabled) { return ( WindowGLContext { gl_context: GlContext::Unknown, gl_api: GlApi::None, native_display: NativeDisplay::Unknown, glplayer_chan: None, }, None, ); } let (native_display, gl_context) = match Self::get_native_media_display_and_gl_context(rendering_context) { Some((native_display, gl_context)) => (native_display, gl_context), None => { return ( WindowGLContext { gl_context: GlContext::Unknown, gl_api: GlApi::None, native_display: NativeDisplay::Unknown, glplayer_chan: None, }, None, ); }, }; let api = rendering_context.connection().gl_api(); let GLVersion { major, minor } = rendering_context.gl_version(); let gl_api = match api { GLApi::GL if major >= 3 && minor >= 2 => GlApi::OpenGL3, GLApi::GL => GlApi::OpenGL, GLApi::GLES if major > 1 => GlApi::Gles2, GLApi::GLES => GlApi::Gles1, }; assert!(!matches!(gl_context, GlContext::Unknown)); let (glplayer_threads, image_handler) = GLPlayerThreads::new(external_images.clone()); external_image_handlers.set_handler(image_handler, WebrenderImageHandlerType::Media); ( WindowGLContext { gl_context, native_display, gl_api, glplayer_chan: Some(GLPlayerThreads::pipeline(&glplayer_threads)), }, Some(glplayer_threads), ) } fn handle_window_event(&mut self, event: EmbedderEvent) { match event { EmbedderEvent::Idle => {}, EmbedderEvent::Refresh => { self.compositor.borrow_mut().composite(); }, EmbedderEvent::ThemeChange(theme) => { let msg = ConstellationMsg::ThemeChange(theme); if let Err(e) = self.constellation_proxy.try_send(msg) { warn!( "Sending platform theme change to constellation failed ({:?}).", e ) } }, EmbedderEvent::InvalidateNativeSurface => { self.compositor.borrow_mut().invalidate_native_surface(); }, EmbedderEvent::ReplaceNativeSurface(native_widget, coords) => { self.compositor .borrow_mut() .replace_native_surface(native_widget, coords); self.compositor.borrow_mut().composite(); }, EmbedderEvent::AllowNavigationResponse(pipeline_id, allowed) => { let msg = ConstellationMsg::AllowNavigationResponse(pipeline_id, allowed); if let Err(e) = self.constellation_proxy.try_send(msg) { warn!( "Sending allow navigation to constellation failed ({:?}).", e ); } }, EmbedderEvent::LoadUrl(top_level_browsing_context_id, url) => { let msg = ConstellationMsg::LoadUrl(top_level_browsing_context_id, url); if let Err(e) = self.constellation_proxy.try_send(msg) { warn!("Sending load url to constellation failed ({:?}).", e); } }, EmbedderEvent::ClearCache => { let msg = ConstellationMsg::ClearCache; if let Err(e) = self.constellation_proxy.try_send(msg) { warn!("Sending clear cache to constellation failed ({:?}).", e); } }, EmbedderEvent::MouseWindowEventClass(mouse_window_event) => { self.compositor .borrow_mut() .on_mouse_window_event_class(mouse_window_event); }, EmbedderEvent::MouseWindowMoveEventClass(cursor) => { self.compositor .borrow_mut() .on_mouse_window_move_event_class(cursor); }, EmbedderEvent::Touch(event_type, identifier, location) => { self.compositor .borrow_mut() .on_touch_event(event_type, identifier, location); }, EmbedderEvent::Wheel(delta, location) => { self.compositor.borrow_mut().on_wheel_event(delta, location); }, EmbedderEvent::Scroll(scroll_location, cursor, phase) => { self.compositor .borrow_mut() .on_scroll_event(scroll_location, cursor, phase); }, EmbedderEvent::Zoom(magnification) => { self.compositor .borrow_mut() .on_zoom_window_event(magnification); }, EmbedderEvent::ResetZoom => { self.compositor.borrow_mut().on_zoom_reset_window_event(); }, EmbedderEvent::PinchZoom(zoom) => { self.compositor .borrow_mut() .on_pinch_zoom_window_event(zoom); }, EmbedderEvent::Navigation(webview_id, direction) => { let msg = ConstellationMsg::TraverseHistory(webview_id, direction); if let Err(e) = self.constellation_proxy.try_send(msg) { warn!("Sending navigation to constellation failed ({:?}).", e); } self.messages_for_embedder .push(EmbedderMsg::Status(webview_id, None)); }, EmbedderEvent::Keyboard(webview_id, key_event) => { let msg = ConstellationMsg::Keyboard(webview_id, key_event); if let Err(e) = self.constellation_proxy.try_send(msg) { warn!("Sending keyboard event to constellation failed ({:?}).", e); } }, EmbedderEvent::IMEComposition(ime_event) => { let msg = ConstellationMsg::IMECompositionEvent(ime_event); if let Err(e) = self.constellation_proxy.try_send(msg) { warn!( "Sending composition event to constellation failed ({:?}).", e ); } }, EmbedderEvent::IMEDismissed => { let msg = ConstellationMsg::IMEDismissed; if let Err(e) = self.constellation_proxy.try_send(msg) { warn!( "Sending IMEDismissed event to constellation failed ({:?}).", e ); } }, EmbedderEvent::Quit => { self.compositor.borrow_mut().maybe_start_shutting_down(); }, EmbedderEvent::ExitFullScreen(top_level_browsing_context_id) => { let msg = ConstellationMsg::ExitFullScreen(top_level_browsing_context_id); if let Err(e) = self.constellation_proxy.try_send(msg) { warn!("Sending exit fullscreen to constellation failed ({:?}).", e); } }, EmbedderEvent::Reload(top_level_browsing_context_id) => { let msg = ConstellationMsg::Reload(top_level_browsing_context_id); if let Err(e) = self.constellation_proxy.try_send(msg) { warn!("Sending reload to constellation failed ({:?}).", e); } }, EmbedderEvent::ToggleSamplingProfiler(rate, max_duration) => { if let Err(e) = self .constellation_proxy .try_send(ConstellationMsg::ToggleProfiler(rate, max_duration)) { warn!("Sending profiler toggle to constellation failed ({:?}).", e); } }, EmbedderEvent::ToggleWebRenderDebug(option) => { self.compositor.borrow_mut().toggle_webrender_debug(option); }, EmbedderEvent::CaptureWebRender => { self.compositor.borrow_mut().capture_webrender(); }, EmbedderEvent::NewWebView(url, top_level_browsing_context_id) => { let msg = ConstellationMsg::NewWebView(url, top_level_browsing_context_id); if let Err(e) = self.constellation_proxy.try_send(msg) { warn!( "Sending NewBrowser message to constellation failed ({:?}).", e ); } }, EmbedderEvent::FocusWebView(top_level_browsing_context_id) => { let msg = ConstellationMsg::FocusWebView(top_level_browsing_context_id); if let Err(e) = self.constellation_proxy.try_send(msg) { warn!( "Sending FocusBrowser message to constellation failed ({:?}).", e ); } }, EmbedderEvent::CloseWebView(top_level_browsing_context_id) => { let msg = ConstellationMsg::CloseWebView(top_level_browsing_context_id); if let Err(e) = self.constellation_proxy.try_send(msg) { warn!( "Sending CloseBrowser message to constellation failed ({:?}).", e ); } }, EmbedderEvent::MoveResizeWebView(webview_id, rect) => { self.compositor .borrow_mut() .move_resize_webview(webview_id, rect); }, EmbedderEvent::ShowWebView(webview_id, hide_others) => { if let Err(UnknownWebView(webview_id)) = self .compositor .borrow_mut() .show_webview(webview_id, hide_others) { warn!("{webview_id}: ShowWebView on unknown webview id"); } }, EmbedderEvent::HideWebView(webview_id) => { if let Err(UnknownWebView(webview_id)) = self.compositor.borrow_mut().hide_webview(webview_id) { warn!("{webview_id}: HideWebView on unknown webview id"); } }, EmbedderEvent::RaiseWebViewToTop(webview_id, hide_others) => { if let Err(UnknownWebView(webview_id)) = self .compositor .borrow_mut() .raise_webview_to_top(webview_id, hide_others) { warn!("{webview_id}: RaiseWebViewToTop on unknown webview id"); } }, EmbedderEvent::BlurWebView => { self.send_to_constellation(ConstellationMsg::BlurWebView); }, EmbedderEvent::SendError(top_level_browsing_context_id, e) => { let msg = ConstellationMsg::SendError(top_level_browsing_context_id, e); if let Err(e) = self.constellation_proxy.try_send(msg) { warn!( "Sending SendError message to constellation failed ({:?}).", e ); } }, EmbedderEvent::MediaSessionAction(a) => { let msg = ConstellationMsg::MediaSessionAction(a); if let Err(e) = self.constellation_proxy.try_send(msg) { warn!( "Sending MediaSessionAction message to constellation failed ({:?}).", e ); } }, EmbedderEvent::SetWebViewThrottled(webview_id, throttled) => { let msg = ConstellationMsg::SetWebViewThrottled(webview_id, throttled); if let Err(e) = self.constellation_proxy.try_send(msg) { warn!( "Sending SetWebViewThrottled to constellation failed ({:?}).", e ); } }, EmbedderEvent::Gamepad(gamepad_event) => { let msg = ConstellationMsg::Gamepad(gamepad_event); if let Err(e) = self.constellation_proxy.try_send(msg) { warn!("Sending Gamepad event to constellation failed ({:?}).", e); } }, EmbedderEvent::Vsync => { self.compositor.borrow_mut().on_vsync(); }, EmbedderEvent::ClipboardAction(clipboard_event) => { self.send_to_constellation(ConstellationMsg::Clipboard(clipboard_event)); }, } } fn send_to_constellation(&self, msg: ConstellationMsg) { let variant_name = msg.variant_name(); if let Err(e) = self.constellation_proxy.try_send(msg) { warn!("Sending {variant_name} to constellation failed: {e:?}"); } } fn receive_messages(&mut self) { while let Ok(message) = self.embedder_receiver.try_recv() { match (message, self.compositor.borrow().shutdown_state) { (_, ShutdownState::FinishedShuttingDown) => { error!( "embedder shouldn't be handling messages after compositor has shut down" ); }, (_, ShutdownState::ShuttingDown) => {}, (EmbedderMsg::Keyboard(webview_id, key_event), ShutdownState::NotShuttingDown) => { self.messages_for_embedder .push(EmbedderMsg::Keyboard(webview_id, key_event)); }, (message, ShutdownState::NotShuttingDown) => { self.messages_for_embedder.push(message); }, } } } pub fn get_events(&mut self) -> Drain<'_, EmbedderMsg> { self.messages_for_embedder.drain(..) } pub fn handle_events(&mut self, events: impl IntoIterator) { if self.compositor.borrow_mut().receive_messages() { self.receive_messages(); } for event in events { trace!("servo <- embedder EmbedderEvent {:?}", event); self.handle_window_event(event); } if self.compositor.borrow().shutdown_state != ShutdownState::FinishedShuttingDown { self.compositor.borrow_mut().perform_updates(); } else { self.messages_for_embedder.push(EmbedderMsg::Shutdown); } } pub fn pinch_zoom_level(&self) -> f32 { self.compositor.borrow_mut().pinch_zoom_level().get() } pub fn setup_logging(&self) { let constellation_chan = self.constellation_proxy.sender(); let env = env_logger::Env::default(); let env_logger = EnvLoggerBuilder::from_env(env).build(); let con_logger = FromCompositorLogger::new(constellation_chan); let filter = max(env_logger.filter(), con_logger.filter()); let logger = BothLogger(env_logger, con_logger); log::set_boxed_logger(Box::new(logger)).expect("Failed to set logger."); log::set_max_level(filter); } pub fn start_shutting_down(&self) { self.compositor.borrow_mut().maybe_start_shutting_down(); } pub fn allow_navigation_response(&self, pipeline_id: PipelineId, allow: bool) { let msg = ConstellationMsg::AllowNavigationResponse(pipeline_id, allow); if let Err(e) = self.constellation_proxy.try_send(msg) { warn!( "Sending allow navigation to constellation failed ({:?}).", e ); } } pub fn deinit(self) { self.compositor.borrow_mut().deinit(); } pub fn present(&mut self) { self.compositor.borrow_mut().present(); } /// Return the OpenGL framebuffer name of the most-recently-completed frame when compositing to /// [`CompositeTarget::OffscreenFbo`], or None otherwise. pub fn offscreen_framebuffer_id(&self) -> Option { self.compositor.borrow().offscreen_framebuffer_id() } pub fn new_webview(&self, url: url::Url) -> WebView { WebView::new(&self.constellation_proxy, self.compositor.clone(), url) } /// FIXME: Remove this once we have a webview delegate. pub fn new_auxiliary_webview(&self) -> WebView { WebView::new_auxiliary(&self.constellation_proxy, self.compositor.clone()) } } fn create_embedder_channel( event_loop_waker: Box, ) -> (EmbedderProxy, Receiver) { let (sender, receiver) = unbounded(); ( EmbedderProxy { sender, event_loop_waker, }, receiver, ) } fn create_compositor_channel( event_loop_waker: Box, ) -> (CompositorProxy, CompositorReceiver) { let (sender, receiver) = unbounded(); let (compositor_ipc_sender, compositor_ipc_receiver) = ipc::channel().expect("ipc channel failure"); let cross_process_compositor_api = CrossProcessCompositorApi(compositor_ipc_sender); let compositor_proxy = CompositorProxy { sender, cross_process_compositor_api, event_loop_waker, }; let compositor_proxy_clone = compositor_proxy.clone(); ROUTER.add_typed_route( compositor_ipc_receiver, Box::new(move |message| { compositor_proxy_clone.send(CompositorMsg::CrossProcess( message.expect("Could not convert Compositor message"), )); }), ); (compositor_proxy, CompositorReceiver { receiver }) } fn get_layout_factory(legacy_layout: bool) -> Arc { cfg_if::cfg_if! { if #[cfg(feature = "layout_2013")] { if legacy_layout { return Arc::new(layout_thread_2013::LayoutFactoryImpl()); } } else { if legacy_layout { panic!("Runtime option `legacy_layout` was enabled, but the `layout_2013` \ feature was not enabled at compile time! "); } } } Arc::new(layout_thread_2020::LayoutFactoryImpl()) } #[allow(clippy::too_many_arguments)] fn create_constellation( user_agent: Cow<'static, str>, config_dir: Option, embedder_proxy: EmbedderProxy, compositor_proxy: CompositorProxy, time_profiler_chan: time::ProfilerChan, mem_profiler_chan: mem::ProfilerChan, devtools_sender: Option>, webrender_document: DocumentId, webrender_api_sender: RenderApiSender, #[cfg(feature = "webxr")] webxr_registry: webxr_api::Registry, player_context: WindowGLContext, webgl_threads: Option, glplayer_threads: Option, initial_window_size: WindowSizeData, external_images: Arc>, #[cfg(feature = "webgpu")] wgpu_image_map: WGPUImageMap, protocols: ProtocolRegistry, ) -> Sender { // Global configuration options, parsed from the command line. let opts = opts::get(); let bluetooth_thread: IpcSender = BluetoothThreadFactory::new(embedder_proxy.clone()); let (public_resource_threads, private_resource_threads) = new_resource_threads( user_agent.clone(), devtools_sender.clone(), time_profiler_chan.clone(), mem_profiler_chan.clone(), embedder_proxy.clone(), config_dir, opts.certificate_path.clone(), opts.ignore_certificate_errors, Arc::new(protocols), ); let system_font_service = Arc::new( SystemFontService::spawn(compositor_proxy.cross_process_compositor_api.clone()).to_proxy(), ); let (canvas_create_sender, canvas_ipc_sender) = CanvasPaintThread::start( compositor_proxy.cross_process_compositor_api.clone(), system_font_service.clone(), public_resource_threads.clone(), ); let initial_state = InitialConstellationState { compositor_proxy, embedder_proxy, devtools_sender, bluetooth_thread, system_font_service, public_resource_threads, private_resource_threads, time_profiler_chan, mem_profiler_chan, webrender_document, webrender_api_sender, #[cfg(feature = "webxr")] webxr_registry: Some(webxr_registry), #[cfg(not(feature = "webxr"))] webxr_registry: None, webgl_threads, glplayer_threads, player_context, user_agent, webrender_external_images: external_images, #[cfg(feature = "webgpu")] wgpu_image_map, }; let layout_factory: Arc = get_layout_factory(opts::get().legacy_layout); Constellation::::start( initial_state, layout_factory, initial_window_size, opts.random_pipeline_closure_probability, opts.random_pipeline_closure_seed, opts.hard_fail, canvas_create_sender, canvas_ipc_sender, ) } // A logger that logs to two downstream loggers. // This should probably be in the log crate. struct BothLogger(Log1, Log2); impl Log for BothLogger where Log1: Log, Log2: Log, { fn enabled(&self, metadata: &Metadata) -> bool { self.0.enabled(metadata) || self.1.enabled(metadata) } fn log(&self, record: &Record) { self.0.log(record); self.1.log(record); } fn flush(&self) { self.0.flush(); self.1.flush(); } } pub fn set_logger(script_to_constellation_chan: ScriptToConstellationChan) { let con_logger = FromScriptLogger::new(script_to_constellation_chan); let env = env_logger::Env::default(); let env_logger = EnvLoggerBuilder::from_env(env).build(); let filter = max(env_logger.filter(), con_logger.filter()); let logger = BothLogger(env_logger, con_logger); log::set_boxed_logger(Box::new(logger)).expect("Failed to set logger."); log::set_max_level(filter); } /// Content process entry point. pub fn run_content_process(token: String) { let (unprivileged_content_sender, unprivileged_content_receiver) = ipc::channel::().unwrap(); let connection_bootstrap: IpcSender> = IpcSender::connect(token).unwrap(); connection_bootstrap .send(unprivileged_content_sender) .unwrap(); let unprivileged_content = unprivileged_content_receiver.recv().unwrap(); opts::set_options(unprivileged_content.opts()); prefs::set(unprivileged_content.prefs().clone()); // Enter the sandbox if necessary. if opts::get().sandbox { create_sandbox(); } let _js_engine_setup = script::init(); match unprivileged_content { UnprivilegedContent::Pipeline(mut content) => { media_platform::init(); set_logger(content.script_to_constellation_chan().clone()); let background_hang_monitor_register = content.register_with_background_hang_monitor(); let layout_factory: Arc = get_layout_factory(opts::get().legacy_layout); content.start_all::( true, layout_factory, background_hang_monitor_register, ); }, UnprivilegedContent::ServiceWorker(content) => { content.start::(); }, } } #[cfg(all( not(target_os = "windows"), not(target_os = "ios"), not(target_os = "android"), not(target_arch = "arm"), not(target_arch = "aarch64"), not(target_env = "ohos"), ))] fn create_sandbox() { ChildSandbox::new(content_process_sandbox_profile()) .activate() .expect("Failed to activate sandbox!"); } #[cfg(any( target_os = "windows", target_os = "ios", target_os = "android", target_arch = "arm", target_arch = "aarch64", target_env = "ohos", ))] fn create_sandbox() { panic!("Sandboxing is not supported on Windows, iOS, ARM targets and android."); } enum UserAgent { Desktop, Android, OpenHarmony, #[allow(non_camel_case_types)] iOS, } fn get_servo_version() -> &'static str { env!("CARGO_PKG_VERSION") } fn default_user_agent_string_for(agent: UserAgent) -> String { let servo_version = get_servo_version(); #[cfg(all(target_os = "linux", target_arch = "x86_64", not(target_env = "ohos")))] let desktop_ua_string = format!("Mozilla/5.0 (X11; Linux x86_64; rv:128.0) Servo/{servo_version} Firefox/128.0"); #[cfg(all( target_os = "linux", not(target_arch = "x86_64"), not(target_env = "ohos") ))] let desktop_ua_string = format!("Mozilla/5.0 (X11; Linux i686; rv:128.0) Servo/{servo_version} Firefox/128.0"); #[cfg(all(target_os = "windows", target_arch = "x86_64"))] let desktop_ua_string = format!( "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:128.0) Servo/{servo_version} Firefox/128.0" ); #[cfg(all(target_os = "windows", not(target_arch = "x86_64")))] let desktop_ua_string = format!("Mozilla/5.0 (Windows NT 10.0; rv:128.0) Servo/{servo_version} Firefox/128.0"); #[cfg(target_os = "macos")] let desktop_ua_string = format!( "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:128.0) Servo/{servo_version} Firefox/128.0" ); #[cfg(any(target_os = "android", target_env = "ohos"))] let desktop_ua_string = "".to_string(); match agent { UserAgent::Desktop => desktop_ua_string, UserAgent::Android => format!( "Mozilla/5.0 (Android; Mobile; rv:128.0) Servo/{servo_version} Firefox/128.0" ), UserAgent::OpenHarmony => format!( "Mozilla/5.0 (OpenHarmony; Mobile; rv:128.0) Servo/{servo_version} Firefox/128.0" ), UserAgent::iOS => format!( "Mozilla/5.0 (iPhone; CPU iPhone OS 18_0 like Mac OS X; rv:128.0) Servo/{servo_version} Firefox/128.0" ), } } #[cfg(target_os = "android")] const DEFAULT_USER_AGENT: UserAgent = UserAgent::Android; #[cfg(target_env = "ohos")] const DEFAULT_USER_AGENT: UserAgent = UserAgent::OpenHarmony; #[cfg(target_os = "ios")] const DEFAULT_USER_AGENT: UserAgent = UserAgent::iOS; #[cfg(not(any(target_os = "android", target_os = "ios", target_env = "ohos")))] const DEFAULT_USER_AGENT: UserAgent = UserAgent::Desktop;