diff options
author | Tony <68118705+Legend-Master@users.noreply.github.com> | 2025-03-27 11:00:08 +0800 |
---|---|---|
committer | GitHub <noreply@github.com> | 2025-03-27 03:00:08 +0000 |
commit | 5a76906d64a34ebb0608f64dd558e7457675f9c6 (patch) | |
tree | ec570d0a20e591752abe7b63a109139a9b75428f | |
parent | 53a2e61fecd42d4d35b7ff5479095cf86e12c1ae (diff) | |
download | servo-5a76906d64a34ebb0608f64dd558e7457675f9c6.tar.gz servo-5a76906d64a34ebb0608f64dd558e7457675f9c6.zip |
Allow setting userscripts directly without the need of files (#35388)
* Allow settings userscripts through preferences
Signed-off-by: Tony <legendmastertony@gmail.com>
* mach fmt instead of cargo fmt
Signed-off-by: Tony <legendmastertony@gmail.com>
* Fix pref loading not working for array values
Signed-off-by: Tony <legendmastertony@gmail.com>
* Use pref! in userscripts instead
Signed-off-by: Tony <legendmastertony@gmail.com>
* Implement the model jdm suggested
- Remove userscripts from all places and move it to servoshell
- Add in `UserContentManager` struct and passing it through `Servo::new`
all the way down to script thread
Signed-off-by: Tony <legendmastertony@gmail.com>
* Apply suggestions from code review and format
Signed-off-by: Tony <legendmastertony@gmail.com>
* Revert unrelated change
Signed-off-by: Tony <legendmastertony@gmail.com>
---------
Signed-off-by: Tony <legendmastertony@gmail.com>
Signed-off-by: Tony <68118705+Legend-Master@users.noreply.github.com>
-rw-r--r-- | components/config/opts.rs | 6 | ||||
-rw-r--r-- | components/config/pref_util.rs | 12 | ||||
-rw-r--r-- | components/constellation/constellation.rs | 9 | ||||
-rw-r--r-- | components/constellation/pipeline.rs | 7 | ||||
-rw-r--r-- | components/script/dom/userscripts.rs | 30 | ||||
-rw-r--r-- | components/script/dom/window.rs | 16 | ||||
-rw-r--r-- | components/script/script_thread.rs | 12 | ||||
-rw-r--r-- | components/servo/examples/winit_minimal.rs | 1 | ||||
-rw-r--r-- | components/servo/lib.rs | 5 | ||||
-rw-r--r-- | components/shared/embedder/lib.rs | 1 | ||||
-rw-r--r-- | components/shared/embedder/user_content_manager.rs | 55 | ||||
-rw-r--r-- | components/shared/script/lib.rs | 3 | ||||
-rw-r--r-- | ports/servoshell/desktop/app.rs | 27 | ||||
-rw-r--r-- | ports/servoshell/egl/android/simpleservo.rs | 1 | ||||
-rw-r--r-- | ports/servoshell/egl/ohos/simpleservo.rs | 1 | ||||
-rw-r--r-- | ports/servoshell/prefs.rs | 8 |
16 files changed, 143 insertions, 51 deletions
diff --git a/components/config/opts.rs b/components/config/opts.rs index 640c1b910c4..785b43b0acd 100644 --- a/components/config/opts.rs +++ b/components/config/opts.rs @@ -35,11 +35,6 @@ pub struct Opts { /// True to turn off incremental layout. pub nonincremental_layout: bool, - /// Where to load userscripts from, if any. An empty string will load from - /// the resources/user-agent-js directory, and if the option isn't passed userscripts - /// won't be loaded - pub userscripts: Option<String>, - pub user_stylesheets: Vec<(Vec<u8>, ServoUrl)>, /// True to exit on thread failure instead of displaying about:failure. @@ -191,7 +186,6 @@ impl Default for Opts { time_profiling: None, time_profiler_trace_path: None, nonincremental_layout: false, - userscripts: None, user_stylesheets: Vec::new(), hard_fail: true, webdriver_port: None, diff --git a/components/config/pref_util.rs b/components/config/pref_util.rs index 8f1bbca6cf8..42617689394 100644 --- a/components/config/pref_util.rs +++ b/components/config/pref_util.rs @@ -43,13 +43,11 @@ impl TryFrom<&Value> for PrefValue { .unwrap_or(Err("Could not parse number from JSON".into())), Value::String(value) => Ok(value.clone().into()), Value::Array(array) => { - let array = array.iter().map(TryInto::try_into); - if !array.clone().all(|v| v.is_ok()) { - return Err(format!( - "Cannot turn all array avlues into preference: {array:?}" - )); - } - Ok(PrefValue::Array(array.map(Result::unwrap).collect())) + let array = array + .iter() + .map(TryInto::<PrefValue>::try_into) + .collect::<Result<Vec<_>, _>>()?; + Ok(PrefValue::Array(array)) }, Value::Object(_) => Err("Cannot turn object into preference".into()), } diff --git a/components/constellation/constellation.rs b/components/constellation/constellation.rs index 6da1028af40..b8fd7a465aa 100644 --- a/components/constellation/constellation.rs +++ b/components/constellation/constellation.rs @@ -119,6 +119,7 @@ use devtools_traits::{ ScriptToDevtoolsControlMsg, }; use embedder_traits::resources::{self, Resource}; +use embedder_traits::user_content_manager::UserContentManager; use embedder_traits::{ Cursor, EmbedderMsg, EmbedderProxy, ImeEvent, InputEvent, MediaSessionActionType, MediaSessionEvent, MediaSessionPlaybackState, MouseButton, MouseButtonAction, MouseButtonEvent, @@ -471,6 +472,9 @@ pub struct Constellation<STF, SWF> { /// Read during startup and provided to image caches that are created /// on an as-needed basis, rather than retrieving it every time. rippy_data: Vec<u8>, + + /// User content manager + user_content_manager: UserContentManager, } /// State needed to construct a constellation. @@ -523,6 +527,9 @@ pub struct InitialConstellationState { #[cfg(feature = "webgpu")] pub wgpu_image_map: WGPUImageMap, + + /// User content manager + pub user_content_manager: UserContentManager, } /// Data needed for webdriver @@ -739,6 +746,7 @@ where active_media_session: None, user_agent: state.user_agent, rippy_data, + user_content_manager: state.user_content_manager, }; constellation.run(); @@ -982,6 +990,7 @@ where player_context: WindowGLContext::get(), user_agent: self.user_agent.clone(), rippy_data: self.rippy_data.clone(), + user_content_manager: self.user_content_manager.clone(), }); let pipeline = match result { diff --git a/components/constellation/pipeline.rs b/components/constellation/pipeline.rs index b1ff106e353..65f0c9dbfbd 100644 --- a/components/constellation/pipeline.rs +++ b/components/constellation/pipeline.rs @@ -23,6 +23,7 @@ use compositing_traits::{CompositionPipeline, CompositorMsg, CompositorProxy}; use constellation_traits::WindowSizeData; use crossbeam_channel::{Sender, unbounded}; use devtools_traits::{DevtoolsControlMsg, ScriptToDevtoolsControlMsg}; +use embedder_traits::user_content_manager::UserContentManager; use fonts::{SystemFontServiceProxy, SystemFontServiceProxySender}; use ipc_channel::Error; use ipc_channel::ipc::{self, IpcReceiver, IpcSender}; @@ -196,6 +197,9 @@ pub struct InitialPipelineState { /// The image bytes associated with the RippyPNG embedder resource. pub rippy_data: Vec<u8>, + + /// User content manager + pub user_content_manager: UserContentManager, } pub struct NewPipeline { @@ -292,6 +296,7 @@ impl Pipeline { player_context: state.player_context, user_agent: state.user_agent, rippy_data: state.rippy_data, + user_content_manager: state.user_content_manager, }; // Spawn the child process. @@ -498,6 +503,7 @@ pub struct UnprivilegedPipelineContent { player_context: WindowGLContext, user_agent: Cow<'static, str>, rippy_data: Vec<u8>, + user_content_manager: UserContentManager, } impl UnprivilegedPipelineContent { @@ -543,6 +549,7 @@ impl UnprivilegedPipelineContent { compositor_api: self.cross_process_compositor_api.clone(), player_context: self.player_context.clone(), inherited_secure_context: self.load_data.inherited_secure_context, + user_content_manager: self.user_content_manager, }, layout_factory, Arc::new(self.system_font_service.to_proxy()), diff --git a/components/script/dom/userscripts.rs b/components/script/dom/userscripts.rs index f48c8cf7f5e..79c2f58dbec 100644 --- a/components/script/dom/userscripts.rs +++ b/components/script/dom/userscripts.rs @@ -2,9 +2,6 @@ * 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/. */ -use std::fs::{File, read_dir}; -use std::io::Read; -use std::path::PathBuf; use std::rc::Rc; use js::jsval::UndefinedValue; @@ -19,37 +16,24 @@ use crate::script_runtime::CanGc; pub(crate) fn load_script(head: &HTMLHeadElement) { let doc = head.owner_document(); - let path_str = match doc.window().get_userscripts_path() { - Some(p) => p, - None => return, - }; + let userscripts = doc.window().userscripts().to_owned(); + if userscripts.is_empty() { + return; + } let window = Trusted::new(doc.window()); doc.add_delayed_task(task!(UserScriptExecute: move || { let win = window.root(); let cx = win.get_cx(); rooted!(in(*cx) let mut rval = UndefinedValue()); - let path = PathBuf::from(&path_str); - let mut files = read_dir(path) - .expect("Bad path passed to --userscripts") - .filter_map(|e| e.ok()) - .map(|e| e.path()) - .collect::<Vec<_>>(); - - files.sort(); - - for file in files { - let mut f = File::open(&file).unwrap(); - let mut contents = vec![]; - f.read_to_end(&mut contents).unwrap(); + for user_script in userscripts { let script_text = SourceCode::Text( - Rc::new(DOMString::from_string(String::from_utf8_lossy(&contents).to_string())) + Rc::new(DOMString::from_string(user_script.script)) ); - let global_scope = win.as_global_scope(); global_scope.evaluate_script_on_global_with_result( &script_text, - &file.to_string_lossy(), + &user_script.source_file.map(|path| path.to_string_lossy().to_string()).unwrap_or_default(), rval.handle_mut(), 1, ScriptFetchOptions::default_classic_script(global_scope), diff --git a/components/script/dom/window.rs b/components/script/dom/window.rs index ecf7b39cd60..e8ba3e6b307 100644 --- a/components/script/dom/window.rs +++ b/components/script/dom/window.rs @@ -26,6 +26,7 @@ use crossbeam_channel::{Sender, unbounded}; use cssparser::{Parser, ParserInput, SourceLocation}; use devtools_traits::{ScriptToDevtoolsControlMsg, TimelineMarker, TimelineMarkerType}; use dom_struct::dom_struct; +use embedder_traits::user_content_manager::{UserContentManager, UserScript}; use embedder_traits::{ AlertResponse, ConfirmResponse, EmbedderMsg, PromptResponse, SimpleDialog, Theme, WebDriverJSError, WebDriverJSResult, @@ -373,10 +374,9 @@ pub(crate) struct Window { /// Unminify Css. unminify_css: bool, - /// Where to load userscripts from, if any. An empty string will load from - /// the resources/user-agent-js directory, and if the option isn't passed userscripts - /// won't be loaded. - userscripts_path: Option<String>, + /// User content manager + #[no_trace] + user_content_manager: UserContentManager, /// Window's GL context from application #[ignore_malloc_size_of = "defined in script_thread"] @@ -624,8 +624,8 @@ impl Window { &self.compositor_api } - pub(crate) fn get_userscripts_path(&self) -> Option<String> { - self.userscripts_path.clone() + pub(crate) fn userscripts(&self) -> &[UserScript] { + self.user_content_manager.scripts() } pub(crate) fn get_player_context(&self) -> WindowGLContext { @@ -2797,7 +2797,7 @@ impl Window { unminify_js: bool, unminify_css: bool, local_script_source: Option<String>, - userscripts_path: Option<String>, + user_content_manager: UserContentManager, user_agent: Cow<'static, str>, player_context: WindowGLContext, #[cfg(feature = "webgpu")] gpu_id_hub: Arc<IdentityHub>, @@ -2884,7 +2884,7 @@ impl Window { has_sent_idle_message: Cell::new(false), relayout_event, unminify_css, - userscripts_path, + user_content_manager, player_context, throttled: Cell::new(false), layout_marker: DomRefCell::new(Rc::new(Cell::new(true))), diff --git a/components/script/script_thread.rs b/components/script/script_thread.rs index ed0495bfb67..2e934217815 100644 --- a/components/script/script_thread.rs +++ b/components/script/script_thread.rs @@ -43,6 +43,7 @@ use devtools_traits::{ CSSError, DevtoolScriptControlMsg, DevtoolsPageInfo, NavigationState, ScriptToDevtoolsControlMsg, WorkerId, }; +use embedder_traits::user_content_manager::UserContentManager; use embedder_traits::{ EmbedderMsg, InputEvent, MediaSessionActionType, Theme, WebDriverScriptCommand, }; @@ -303,10 +304,9 @@ pub struct ScriptThread { /// Unminify Css. unminify_css: bool, - /// Where to load userscripts from, if any. An empty string will load from - /// the resources/user-agent-js directory, and if the option isn't passed userscripts - /// won't be loaded - userscripts_path: Option<String>, + /// User content manager + #[no_trace] + user_content_manager: UserContentManager, /// An optional string allowing the user agent to be set for testing. user_agent: Cow<'static, str>, @@ -938,8 +938,8 @@ impl ScriptThread { unminify_js: opts.unminify_js, local_script_source: opts.local_script_source.clone(), unminify_css: opts.unminify_css, - userscripts_path: opts.userscripts.clone(), user_agent, + user_content_manager: state.user_content_manager, player_context: state.player_context, node_ids: Default::default(), is_user_interacting: Cell::new(false), @@ -3096,7 +3096,7 @@ impl ScriptThread { self.unminify_js, self.unminify_css, self.local_script_source.clone(), - self.userscripts_path.clone(), + self.user_content_manager.clone(), self.user_agent.clone(), self.player_context.clone(), #[cfg(feature = "webgpu")] diff --git a/components/servo/examples/winit_minimal.rs b/components/servo/examples/winit_minimal.rs index c4995a54224..49d0c96f806 100644 --- a/components/servo/examples/winit_minimal.rs +++ b/components/servo/examples/winit_minimal.rs @@ -102,6 +102,7 @@ impl ApplicationHandler<WakerEvent> for App { }), window_delegate.clone(), Default::default(), + Default::default(), ); servo.setup_logging(); diff --git a/components/servo/lib.rs b/components/servo/lib.rs index 25d12703700..7027fa77972 100644 --- a/components/servo/lib.rs +++ b/components/servo/lib.rs @@ -61,6 +61,7 @@ use constellation::{ }; use constellation_traits::{ConstellationMsg, WindowSizeData}; use crossbeam_channel::{Receiver, Sender, unbounded}; +use embedder_traits::user_content_manager::UserContentManager; pub use embedder_traits::*; use env_logger::Builder as EnvLoggerBuilder; use euclid::Scale; @@ -264,6 +265,7 @@ impl Servo { mut embedder: Box<dyn EmbedderMethods>, window: Rc<dyn WindowMethods>, user_agent: Option<String>, + user_content_manager: UserContentManager, ) -> Self { // Global configuration options, parsed from the command line. opts::set_options(opts); @@ -501,6 +503,7 @@ impl Servo { #[cfg(feature = "webgpu")] wgpu_image_map, protocols, + user_content_manager, ); if cfg!(feature = "webdriver") { @@ -1053,6 +1056,7 @@ fn create_constellation( external_images: Arc<Mutex<WebrenderExternalImageRegistry>>, #[cfg(feature = "webgpu")] wgpu_image_map: WGPUImageMap, protocols: ProtocolRegistry, + user_content_manager: UserContentManager, ) -> Sender<ConstellationMsg> { // Global configuration options, parsed from the command line. let opts = opts::get(); @@ -1105,6 +1109,7 @@ fn create_constellation( webrender_external_images: external_images, #[cfg(feature = "webgpu")] wgpu_image_map, + user_content_manager, }; let layout_factory = Arc::new(layout_thread_2020::LayoutFactoryImpl()); diff --git a/components/shared/embedder/lib.rs b/components/shared/embedder/lib.rs index 638f17edc1c..006420c3c64 100644 --- a/components/shared/embedder/lib.rs +++ b/components/shared/embedder/lib.rs @@ -10,6 +10,7 @@ pub mod input_events; pub mod resources; +pub mod user_content_manager; mod webdriver; use std::fmt::{Debug, Error, Formatter}; diff --git a/components/shared/embedder/user_content_manager.rs b/components/shared/embedder/user_content_manager.rs new file mode 100644 index 00000000000..b8028723bd2 --- /dev/null +++ b/components/shared/embedder/user_content_manager.rs @@ -0,0 +1,55 @@ +/* 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/. */ + +use std::path::PathBuf; + +use malloc_size_of::MallocSizeOfOps; +use malloc_size_of_derive::MallocSizeOf; +use serde::{Deserialize, Serialize}; + +#[derive(Clone, Debug, Default, Deserialize, MallocSizeOf, Serialize)] +pub struct UserContentManager { + user_scripts: Vec<UserScript>, +} + +impl UserContentManager { + pub fn new() -> Self { + UserContentManager::default() + } + + pub fn add_script(&mut self, script: impl Into<UserScript>) { + self.user_scripts.push(script.into()); + } + + pub fn scripts(&self) -> &[UserScript] { + &self.user_scripts + } +} + +#[derive(Clone, Debug, Deserialize, Serialize)] +pub struct UserScript { + pub script: String, + pub source_file: Option<PathBuf>, +} + +// Maybe we should implement `MallocSizeOf` for `PathBuf` in `malloc_size_of` crate? +impl malloc_size_of::MallocSizeOf for UserScript { + fn size_of(&self, ops: &mut MallocSizeOfOps) -> usize { + let mut sum = 0; + sum += self.script.size_of(ops); + if let Some(path) = &self.source_file { + sum += unsafe { ops.malloc_size_of(path.as_path()) }; + } + sum + } +} + +impl<T: Into<String>> From<T> for UserScript { + fn from(script: T) -> Self { + UserScript { + script: script.into(), + source_file: None, + } + } +} diff --git a/components/shared/script/lib.rs b/components/shared/script/lib.rs index 8f3cc4d95e2..e7971962106 100644 --- a/components/shared/script/lib.rs +++ b/components/shared/script/lib.rs @@ -33,6 +33,7 @@ use constellation_traits::{ use crossbeam_channel::{RecvTimeoutError, Sender}; use devtools_traits::{DevtoolScriptControlMsg, ScriptToDevtoolsControlMsg, WorkerId}; use embedder_traits::input_events::InputEvent; +use embedder_traits::user_content_manager::UserContentManager; use embedder_traits::{MediaSessionActionType, Theme, WebDriverScriptCommand}; use euclid::{Rect, Scale, Size2D, UnknownUnit}; use http::{HeaderMap, Method}; @@ -463,6 +464,8 @@ pub struct InitialScriptState { pub compositor_api: CrossProcessCompositorApi, /// Application window's GL Context for Media player pub player_context: WindowGLContext, + /// User content manager + pub user_content_manager: UserContentManager, } /// This trait allows creating a `ServiceWorkerManager` without depending on the `script` diff --git a/ports/servoshell/desktop/app.rs b/ports/servoshell/desktop/app.rs index b8667ec3b4f..6df9945ee7a 100644 --- a/ports/servoshell/desktop/app.rs +++ b/ports/servoshell/desktop/app.rs @@ -6,6 +6,7 @@ use std::cell::Cell; use std::collections::HashMap; +use std::path::Path; use std::rc::Rc; use std::time::Instant; use std::{env, fs}; @@ -16,6 +17,7 @@ use servo::config::opts::Opts; use servo::config::prefs::Preferences; use servo::servo_config::pref; use servo::servo_url::ServoUrl; +use servo::user_content_manager::{UserContentManager, UserScript}; use servo::webxr::glwindow::GlWindowDiscovery; #[cfg(target_os = "windows")] use servo::webxr::openxr::{AppInfo, OpenXrDiscovery}; @@ -146,6 +148,13 @@ impl App { } } + let mut user_content_manager = UserContentManager::new(); + for script in load_userscripts(self.servoshell_preferences.userscripts_directory.as_deref()) + .expect("Loading userscripts failed") + { + user_content_manager.add_script(script); + } + let servo = Servo::new( self.opts.clone(), self.preferences.clone(), @@ -153,6 +162,7 @@ impl App { embedder, Rc::new(UpcastedWindow(window.clone())), self.servoshell_preferences.user_agent.clone(), + user_content_manager, ); servo.setup_logging(); @@ -442,3 +452,20 @@ impl ApplicationHandler<WakerEvent> for App { self.suspended.set(true); } } + +fn load_userscripts(userscripts_directory: Option<&Path>) -> std::io::Result<Vec<UserScript>> { + let mut userscripts = Vec::new(); + if let Some(userscripts_directory) = &userscripts_directory { + let mut files = std::fs::read_dir(userscripts_directory)? + .map(|e| e.map(|entry| entry.path())) + .collect::<Result<Vec<_>, _>>()?; + files.sort(); + for file in files { + userscripts.push(UserScript { + script: std::fs::read_to_string(&file)?, + source_file: Some(file), + }); + } + } + Ok(userscripts) +} diff --git a/ports/servoshell/egl/android/simpleservo.rs b/ports/servoshell/egl/android/simpleservo.rs index 6ef42c95789..348faa8d4e0 100644 --- a/ports/servoshell/egl/android/simpleservo.rs +++ b/ports/servoshell/egl/android/simpleservo.rs @@ -99,6 +99,7 @@ pub fn init( embedder_callbacks, window_callbacks.clone(), None, + Default::default(), ); APP.with(|app| { diff --git a/ports/servoshell/egl/ohos/simpleservo.rs b/ports/servoshell/egl/ohos/simpleservo.rs index 708140e19a8..621b2f446ed 100644 --- a/ports/servoshell/egl/ohos/simpleservo.rs +++ b/ports/servoshell/egl/ohos/simpleservo.rs @@ -131,6 +131,7 @@ pub fn init( embedder_callbacks, window_callbacks.clone(), None, /* user_agent */ + Default::default(), ); let app_state = RunningAppState::new( diff --git a/ports/servoshell/prefs.rs b/ports/servoshell/prefs.rs index 925ea65ba60..5157615f44a 100644 --- a/ports/servoshell/prefs.rs +++ b/ports/servoshell/prefs.rs @@ -56,6 +56,9 @@ pub(crate) struct ServoShellPreferences { pub output_image_path: Option<String>, /// Whether or not to exit after Servo detects a stable output image in all WebViews. pub exit_after_stable_image: bool, + /// Where to load userscripts from, if any. + /// and if the option isn't passed userscripts won't be loaded. + pub userscripts_directory: Option<PathBuf>, } impl Default for ServoShellPreferences { @@ -74,6 +77,7 @@ impl Default for ServoShellPreferences { user_agent: None, output_image_path: None, exit_after_stable_image: false, + userscripts_directory: None, } } } @@ -622,6 +626,9 @@ pub(crate) fn parse_command_line_arguments(args: Vec<String>) -> ArgumentParsing screen_size_override, output_image_path, exit_after_stable_image: exit_after_load, + userscripts_directory: opt_match + .opt_default("userscripts", "resources/user-agent-js") + .map(PathBuf::from), ..Default::default() }; @@ -636,7 +643,6 @@ pub(crate) fn parse_command_line_arguments(args: Vec<String>) -> ArgumentParsing time_profiling, time_profiler_trace_path: opt_match.opt_str("profiler-trace-path"), nonincremental_layout, - userscripts: opt_match.opt_default("userscripts", "resources/user-agent-js"), user_stylesheets, hard_fail: opt_match.opt_present("f") && !opt_match.opt_present("F"), webdriver_port, |