From 479afcfb8e9882f7b0c6e3b99d148654ea39e576 Mon Sep 17 00:00:00 2001 From: Paul Rouget Date: Fri, 5 Jun 2020 14:03:31 +0200 Subject: [PATCH] Embedding API: prefs r/w --- components/config/opts.rs | 48 ----- components/config/pref_util.rs | 11 ++ components/config/prefs.rs | 41 +---- components/config/tests/opts.rs | 57 +----- ports/libsimpleservo/api/src/lib.rs | 55 +++++- ports/libsimpleservo/capi/src/lib.rs | 20 +- ports/libsimpleservo/capi/src/prefs.rs | 241 +++++++++++++++++++++++++ ports/winit/Cargo.toml | 1 - ports/winit/main2.rs | 16 ++ ports/winit/prefs.rs | 127 +++++++++++++ 10 files changed, 460 insertions(+), 157 deletions(-) create mode 100644 ports/libsimpleservo/capi/src/prefs.rs create mode 100644 ports/winit/prefs.rs diff --git a/components/config/opts.rs b/components/config/opts.rs index 6fd11bea1bf..47d92f986d5 100644 --- a/components/config/opts.rs +++ b/components/config/opts.rs @@ -5,7 +5,6 @@ //! Configuration options for a single run of the servo application. Created //! from command line arguments. -use crate::prefs::{self, PrefValue}; use euclid::Size2D; use getopts::{Matches, Options}; use servo_geometry::DeviceIndependentPixel; @@ -640,18 +639,6 @@ pub fn from_cmdline_args(mut opts: Options, args: &[String]) -> ArgumentParsingR "Run as a content process and connect to the given pipe", "servo-ipc-channel.abcdefg", ); - opts.optmulti( - "", - "pref", - "A preference to set to enable", - "dom.bluetooth.enabled", - ); - opts.optmulti( - "", - "pref", - "A preference to set to enable", - "dom.webgpu.enabled", - ); opts.optflag("b", "no-native-titlebar", "Do not use native titlebar"); opts.optflag("w", "webrender", "Use webrender backend"); opts.optopt("G", "graphics", "Select graphics backend (gl or es2)", "gl"); @@ -926,16 +913,6 @@ pub fn from_cmdline_args(mut opts: Options, args: &[String]) -> ArgumentParsingR set_options(opts); - // These must happen after setting the default options, since the prefs rely on - // on the resource path. - // Note that command line preferences have the highest precedence - - prefs::add_user_prefs(); - - for pref in opt_match.opt_strs("pref").iter() { - parse_pref_from_command_line(pref); - } - if let Some(layout_threads) = layout_threads { set_pref!(layout.threads, layout_threads as i64); } @@ -965,31 +942,6 @@ pub fn get() -> RwLockReadGuard<'static, Opts> { OPTIONS.read().unwrap() } -pub fn parse_pref_from_command_line(pref: &str) { - let split: Vec<&str> = pref.splitn(2, '=').collect(); - let pref_name = split[0]; - let pref_value = parse_cli_pref_value(split.get(1).cloned()); - prefs::pref_map() - .set(pref_name, pref_value) - .expect(format!("Error setting preference: {}", pref).as_str()); -} - -fn parse_cli_pref_value(input: Option<&str>) -> PrefValue { - match input { - Some("true") | None => PrefValue::Bool(true), - Some("false") => PrefValue::Bool(false), - Some(string) => { - if let Some(int) = string.parse::().ok() { - PrefValue::Int(int) - } else if let Some(float) = string.parse::().ok() { - PrefValue::Float(float) - } else { - PrefValue::from(string) - } - }, - } -} - pub fn parse_url_or_filename(cwd: &Path, input: &str) -> Result { match ServoUrl::parse(input) { Ok(url) => Ok(url), diff --git a/components/config/pref_util.rs b/components/config/pref_util.rs index 8a3fde0c99b..ba4dd78f6e8 100644 --- a/components/config/pref_util.rs +++ b/components/config/pref_util.rs @@ -220,6 +220,17 @@ impl<'m, P: Clone> Preferences<'m, P> { } } + /// Has the preference been modified from its original value? + pub fn is_default(&self, key: &str) -> Result { + if let Some(accessor) = self.accessors.get(key) { + let user = (accessor.getter)(&self.default_prefs); + let default = (accessor.getter)(&self.user_prefs.read().unwrap()); + Ok(default == user) + } else { + Err(PrefError::NoSuchPref(String::from(key))) + } + } + /// Creates an iterator over all keys and values pub fn iter<'a>(&'a self) -> impl Iterator + 'a { let prefs = self.user_prefs.read().unwrap(); diff --git a/components/config/prefs.rs b/components/config/prefs.rs index 459f3328324..6de159b91df 100644 --- a/components/config/prefs.rs +++ b/components/config/prefs.rs @@ -2,15 +2,10 @@ * 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 crate::basedir::default_config_dir; -use crate::opts; use embedder_traits::resources::{self, Resource}; use serde_json::{self, Value}; use std::borrow::ToOwned; use std::collections::HashMap; -use std::fs::File; -use std::io::{stderr, Read, Write}; -use std::path::PathBuf; use crate::pref_util::Preferences; pub use crate::pref_util::{PrefError, PrefValue}; @@ -58,42 +53,12 @@ pub fn pref_map() -> &'static Preferences<'static, Prefs> { &PREFS } -pub(crate) fn add_user_prefs() { - if let Some(path) = user_prefs_path() { - init_user_prefs(path); +pub fn add_user_prefs(prefs: HashMap) { + if let Err(error) = PREFS.set_all(prefs.into_iter()) { + panic!("Error setting preference: {:?}", error); } } -fn user_prefs_path() -> Option { - opts::get() - .config_dir - .clone() - .or_else(|| default_config_dir()) - .map(|path| path.join("prefs.json")) - .filter(|path| path.exists()) -} - -fn init_user_prefs(path: PathBuf) { - if let Ok(mut file) = File::open(&path) { - let mut txt = String::new(); - file.read_to_string(&mut txt) - .expect("Can't read user prefs"); - match read_prefs_map(&txt) { - Ok(prefs) => { - if let Err(error) = PREFS.set_all(prefs.into_iter()) { - writeln!(&mut stderr(), "Error setting preference: {:?}", error) - } else { - Ok(()) - } - }, - Err(error) => writeln!(&mut stderr(), "Error parsing prefs.json: {:?}", error), - } - } else { - writeln!(&mut stderr(), "Error opening user prefs from {:?}", path) - } - .expect("failed printing to stderr"); -} - pub fn read_prefs_map(txt: &str) -> Result, PrefError> { let prefs: HashMap = serde_json::from_str(txt).map_err(|e| PrefError::JsonParseErr(e))?; diff --git a/components/config/tests/opts.rs b/components/config/tests/opts.rs index abb1d0a9dfc..e0b36456c90 100644 --- a/components/config/tests/opts.rs +++ b/components/config/tests/opts.rs @@ -2,11 +2,7 @@ * 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/. */ -#[macro_use] -extern crate servo_config; - -use servo_config::opts::{parse_pref_from_command_line, parse_url_or_filename}; -use servo_config::{prefs, prefs::PrefValue}; +use servo_config::opts::parse_url_or_filename; use std::path::Path; #[cfg(not(target_os = "windows"))] @@ -88,54 +84,3 @@ fn test_argument_parsing_special() { assert_eq!(url.query(), None); assert_eq!(url.fragment(), None); } - -#[test] -fn test_invalid_prefs_from_command_line_panics() { - let err_msg = std::panic::catch_unwind(|| { - parse_pref_from_command_line("doesntexist=true"); - }) - .err() - .and_then(|a| a.downcast_ref::().cloned()) - .expect("Should panic"); - assert!( - err_msg.starts_with("Error setting preference"), - "Message should describe the problem" - ); - assert!( - err_msg.contains("doesntexist"), - "Message should mention the name of the preference" - ); -} - -#[test] -fn test_parse_pref_from_command_line() { - // Test with boolean values. - parse_pref_from_command_line("dom.bluetooth.enabled=true"); - assert_eq!( - prefs::pref_map().get("dom.bluetooth.enabled"), - PrefValue::Bool(true) - ); - assert_eq!(pref!(dom.bluetooth.enabled), true); - - parse_pref_from_command_line("dom.bluetooth.enabled=false"); - assert_eq!( - prefs::pref_map().get("dom.bluetooth.enabled"), - PrefValue::Bool(false) - ); - assert_eq!(pref!(dom.bluetooth.enabled), false); - - // Test with numbers - parse_pref_from_command_line("layout.threads=42"); - assert_eq!(pref!(layout.threads), 42); - - // Test string. - parse_pref_from_command_line("shell.homepage=str"); - assert_eq!(pref!(shell.homepage), "str"); - - // Test with no value (defaults to true). - prefs::pref_map() - .set("dom.bluetooth.enabled", false) - .unwrap(); - parse_pref_from_command_line("dom.bluetooth.enabled"); - assert_eq!(pref!(dom.bluetooth.enabled), true); -} diff --git a/ports/libsimpleservo/api/src/lib.rs b/ports/libsimpleservo/api/src/lib.rs index cf133ff0a5b..7dd1881243f 100644 --- a/ports/libsimpleservo/api/src/lib.rs +++ b/ports/libsimpleservo/api/src/lib.rs @@ -7,6 +7,7 @@ extern crate log; pub mod gl_glue; +pub use servo::config::prefs::{add_user_prefs, PrefValue}; pub use servo::embedder_traits::{ ContextMenuResult, MediaSessionPlaybackState, PermissionPrompt, PermissionRequest, PromptResult, }; @@ -19,6 +20,7 @@ use servo::compositing::windowing::{ AnimationState, EmbedderCoordinates, EmbedderMethods, MouseWindowEvent, WindowEvent, WindowMethods, }; +use servo::config::prefs::pref_map; use servo::embedder_traits::resources::{self, Resource, ResourceReaderMethods}; use servo::embedder_traits::{ EmbedderMsg, EmbedderProxy, MediaSessionEvent, PromptDefinition, PromptOrigin, @@ -36,6 +38,7 @@ use servo::webrender_surfman::WebrenderSurfman; use servo::{self, gl, BrowserId, Servo}; use servo_media::player::context as MediaPlayerContext; use std::cell::RefCell; +use std::collections::HashMap; use std::mem; use std::os::raw::c_void; use std::path::PathBuf; @@ -55,7 +58,6 @@ pub use servo::embedder_traits::EventLoopWaker; pub struct InitOptions { pub args: Vec, - pub url: Option, pub coordinates: Coordinates, pub density: f32, pub xr_discovery: Option, @@ -63,6 +65,7 @@ pub struct InitOptions { pub gl_context_pointer: Option<*const c_void>, pub native_display_pointer: Option<*const c_void>, pub native_widget: *mut c_void, + pub prefs: Option>, } #[derive(Clone, Debug)] @@ -173,6 +176,44 @@ pub fn is_uri_valid(url: &str) -> bool { ServoUrl::parse(url).is_ok() } +/// Retrieve a snapshot of the current preferences +pub fn get_prefs() -> HashMap { + pref_map() + .iter() + .map(|(key, value)| { + let is_default = pref_map().is_default(&key).unwrap(); + (key, (value, is_default)) + }) + .collect() +} + +/// Retrieve a preference. +pub fn get_pref(key: &str) -> (PrefValue, bool) { + if let Ok(is_default) = pref_map().is_default(&key) { + (pref_map().get(key), is_default) + } else { + (PrefValue::Missing, false) + } +} + +/// Restore a preference to its default value. +pub fn reset_pref(key: &str) -> bool { + pref_map().reset(key).is_ok() +} + +/// Restore all the preferences to their default values. +pub fn reset_all_prefs() { + pref_map().reset_all(); +} + +/// Change the value of a preference. +pub fn set_pref(key: &str, val: PrefValue) -> Result<(), &'static str> { + pref_map() + .set(key, val) + .map(|_| ()) + .map_err(|_| "Pref set failed") +} + /// Initialize Servo. At that point, we need a valid GL context. /// In the future, this will be done in multiple steps. pub fn init( @@ -195,16 +236,14 @@ pub fn init( opts::from_cmdline_args(Options::new(), &args); } - let embedder_url = init_opts.url.as_ref().and_then(|s| ServoUrl::parse(s).ok()); - let cmdline_url = opts::get().url.clone(); + if let Some(prefs) = init_opts.prefs { + add_user_prefs(prefs); + } + let pref_url = ServoUrl::parse(&pref!(shell.homepage)).ok(); let blank_url = ServoUrl::parse("about:blank").ok(); - let url = embedder_url - .or(cmdline_url) - .or(pref_url) - .or(blank_url) - .unwrap(); + let url = pref_url.or(blank_url).unwrap(); gl.clear_color(1.0, 1.0, 1.0, 1.0); gl.clear(gl::COLOR_BUFFER_BIT); diff --git a/ports/libsimpleservo/capi/src/lib.rs b/ports/libsimpleservo/capi/src/lib.rs index 5bd8bf3dd60..e37ee89178e 100644 --- a/ports/libsimpleservo/capi/src/lib.rs +++ b/ports/libsimpleservo/capi/src/lib.rs @@ -8,6 +8,8 @@ extern crate lazy_static; #[macro_use] extern crate log; +mod prefs; + #[cfg(target_os = "windows")] mod vslogger; @@ -187,7 +189,7 @@ fn do_redirect_stdout_stderr(handler: LogHandlerFn) -> Result<(), ()> { fn call(f: F) -> T where - F: Fn(&mut ServoGlue) -> Result, + F: FnOnce(&mut ServoGlue) -> Result, { match SERVO.with(|s| match s.borrow_mut().as_mut() { Some(ref mut s) => (f)(s), @@ -235,7 +237,6 @@ pub struct CHostCallbacks { #[repr(C)] pub struct CInitOptions { pub args: *const c_char, - pub url: *const c_char, pub width: i32, pub height: i32, pub density: f32, @@ -243,6 +244,7 @@ pub struct CInitOptions { pub vslogger_mod_list: *const *const c_char, pub vslogger_mod_size: u32, pub native_widget: *mut c_void, + pub prefs: *const prefs::CPrefList, } #[repr(C)] @@ -427,15 +429,18 @@ unsafe fn init( warn!("Error redirecting stdout/stderr: {}", reason); } - let url = CStr::from_ptr(opts.url); - let url = url.to_str().map(|s| s.to_string()).ok(); - let coordinates = Coordinates::new(0, 0, opts.width, opts.height, opts.width, opts.height); + let prefs = if opts.prefs.is_null() { + None + } else { + Some((*opts.prefs).convert()) + }; + let opts = InitOptions { args, - url, coordinates, + prefs, density: opts.density, xr_discovery: None, enable_subpixel_text_antialiasing: opts.enable_subpixel_text_antialiasing, @@ -532,6 +537,9 @@ pub extern "C" fn resize(width: i32, height: i32) { pub extern "C" fn perform_updates() { catch_any_panic(|| { debug!("perform_updates"); + // We might have allocated some memory to respond to a potential + // request, from the embedder, for a copy of Servo's preferences. + prefs::free_prefs(); call(|s| s.perform_updates()); }); } diff --git a/ports/libsimpleservo/capi/src/prefs.rs b/ports/libsimpleservo/capi/src/prefs.rs new file mode 100644 index 00000000000..b339636f50d --- /dev/null +++ b/ports/libsimpleservo/capi/src/prefs.rs @@ -0,0 +1,241 @@ +/* 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's internal preferences are not C-compatible. To expose the preference to the embedder, +//! we keep a C-compatible copy of the preferences alive (LOCALCPREFS). The embedder can +//! retrieve an array (CPREFS) of struct of pointers (CPrefs) to the C-compatible preferences +//! (LocalCPref). + +use crate::catch_any_panic; +use crate::simpleservo::{self, PrefValue}; +use std::cell::RefCell; +use std::collections::{BTreeMap, HashMap}; +use std::ffi::{CStr, CString}; +use std::os::raw::{c_char, c_void}; + +thread_local! { + // CPREFS keeps alive a set of CPref that are sent over to the embedder. + // The CPREFS are structs holding pointers to values held alive by LOCALCPREFS. + // This is emptied in free_prefs the next time perform_updates is called. + static CPREFS: RefCell> = RefCell::new(Vec::new()); + static LOCALCPREFS: RefCell> = RefCell::new(BTreeMap::new()); +} + +struct LocalCPref { + key: CString, + value: LocalCPrefValue, + is_default: bool, +} + +#[derive(Debug)] +enum LocalCPrefValue { + Float(f64), + Int(i64), + Str(CString), + Bool(bool), + Missing, +} + +impl LocalCPrefValue { + pub fn new(v: &PrefValue) -> LocalCPrefValue { + match v { + PrefValue::Float(v) => LocalCPrefValue::Float(*v), + PrefValue::Int(v) => LocalCPrefValue::Int(*v), + PrefValue::Str(v) => LocalCPrefValue::Str(CString::new(v.clone()).unwrap()), + PrefValue::Bool(v) => LocalCPrefValue::Bool(*v), + PrefValue::Missing => LocalCPrefValue::Missing, + } + } +} + +#[repr(C)] +pub struct CPrefList { + pub len: usize, + pub list: *const CPref, +} + +impl CPrefList { + pub fn convert(&self) -> HashMap { + let slice = unsafe { std::slice::from_raw_parts(self.list, self.len) }; + slice.iter().map(|cpref| cpref.convert()).collect() + } +} + +#[repr(C)] +pub struct CPref { + pub pref_type: CPrefType, + pub key: *const c_char, + pub value: *const c_void, + pub is_default: bool, +} + +impl CPref { + fn new(local: &LocalCPref) -> CPref { + let (pref_type, value) = match &local.value { + LocalCPrefValue::Float(v) => (CPrefType::Float, v as *const f64 as *const c_void), + LocalCPrefValue::Int(v) => (CPrefType::Int, v as *const i64 as *const c_void), + LocalCPrefValue::Bool(v) => (CPrefType::Bool, v as *const bool as *const c_void), + LocalCPrefValue::Str(v) => (CPrefType::Str, v.as_ptr() as *const c_void), + LocalCPrefValue::Missing => (CPrefType::Missing, std::ptr::null()), + }; + CPref { + key: local.key.as_ptr(), + is_default: local.is_default, + pref_type, + value, + } + } + fn convert(&self) -> (String, PrefValue) { + let key = unsafe { CStr::from_ptr(self.key) }; + let key = key.to_str().expect("Can't read string").to_string(); + let value = unsafe { + match self.pref_type { + CPrefType::Float => PrefValue::Float(*(self.value as *const f64)), + CPrefType::Int => PrefValue::Int(*(self.value as *const i64)), + CPrefType::Bool => PrefValue::Bool(*(self.value as *const bool)), + CPrefType::Str => PrefValue::Str({ + let value = CStr::from_ptr(self.value as *const c_char); + value.to_str().expect("Can't read string").to_string() + }), + CPrefType::Missing => PrefValue::Missing, + } + }; + (key, value) + } +} + +#[repr(C)] +pub enum CPrefType { + Float, + Int, + Str, + Bool, + Missing, +} + +#[no_mangle] +pub extern "C" fn get_pref_as_float(ptr: *const c_void) -> *const f64 { + ptr as *const f64 +} + +#[no_mangle] +pub extern "C" fn get_pref_as_int(ptr: *const c_void) -> *const i64 { + ptr as *const i64 +} + +#[no_mangle] +pub extern "C" fn get_pref_as_str(ptr: *const c_void) -> *const c_char { + ptr as *const c_char +} + +#[no_mangle] +pub extern "C" fn get_pref_as_bool(ptr: *const c_void) -> *const bool { + ptr as *const bool +} + +#[no_mangle] +pub extern "C" fn reset_all_prefs() { + catch_any_panic(|| { + debug!("reset_all_prefs"); + simpleservo::reset_all_prefs() + }) +} + +#[no_mangle] +pub extern "C" fn reset_pref(key: *const c_char) -> bool { + catch_any_panic(|| { + debug!("reset_pref"); + let key = unsafe { CStr::from_ptr(key) }; + let key = key.to_str().expect("Can't read string"); + simpleservo::reset_pref(key) + }) +} + +#[no_mangle] +pub extern "C" fn get_pref(key: *const c_char) -> CPref { + catch_any_panic(|| { + debug!("get_pref"); + LOCALCPREFS.with(|localmap| { + let key = unsafe { CStr::from_ptr(key) }; + let key = key.to_str().expect("Can't read string"); + let (value, is_default) = simpleservo::get_pref(key); + let local = LocalCPref { + key: CString::new(key).unwrap(), + value: LocalCPrefValue::new(&value), + is_default: is_default, + }; + let cpref = CPref::new(&local); + localmap.borrow_mut().insert(key.to_string(), local); + cpref + }) + }) +} + +fn set_pref(key: *const c_char, value: PrefValue) -> bool { + catch_any_panic(|| { + debug!("set_pref"); + let key = unsafe { CStr::from_ptr(key) }; + let key = key.to_str().expect("Can't read string"); + simpleservo::set_pref(key, value).is_ok() + }) +} + +#[no_mangle] +pub extern "C" fn set_float_pref(key: *const c_char, value: f64) -> bool { + set_pref(key, PrefValue::Float(value)) +} + +#[no_mangle] +pub extern "C" fn set_int_pref(key: *const c_char, value: i64) -> bool { + set_pref(key, PrefValue::Int(value)) +} + +#[no_mangle] +pub extern "C" fn set_bool_pref(key: *const c_char, value: bool) -> bool { + set_pref(key, PrefValue::Bool(value)) +} + +#[no_mangle] +pub extern "C" fn set_str_pref(key: *const c_char, value: *const c_char) -> bool { + let value = unsafe { CStr::from_ptr(value) }; + let value = value.to_str().expect("Can't read string").to_string(); + set_pref(key, PrefValue::Str(value)) +} + +#[no_mangle] +pub extern "C" fn get_prefs() -> CPrefList { + // Called from any thread + catch_any_panic(|| { + debug!("get_prefs"); + let map = simpleservo::get_prefs(); + let local: BTreeMap = map + .into_iter() + .map(|(key, (value, is_default))| { + let l = LocalCPref { + key: CString::new(key.clone()).unwrap(), + value: LocalCPrefValue::new(&value), + is_default: is_default, + }; + (key, l) + }) + .collect(); + + let ptrs: Vec = local.iter().map(|(_, local)| CPref::new(&local)).collect(); + + let list = CPrefList { + len: ptrs.len(), + list: ptrs.as_ptr(), + }; + + LOCALCPREFS.with(|p| *p.borrow_mut() = local); + CPREFS.with(|p| *p.borrow_mut() = ptrs); + + list + }) +} + +pub(crate) fn free_prefs() { + LOCALCPREFS.with(|p| p.borrow_mut().clear()); + CPREFS.with(|p| p.borrow_mut().clear()); +} diff --git a/ports/winit/Cargo.toml b/ports/winit/Cargo.toml index 1850f8acbdd..5b6cc3b3c37 100644 --- a/ports/winit/Cargo.toml +++ b/ports/winit/Cargo.toml @@ -11,7 +11,6 @@ publish = false [[bin]] name = "servo" path = "main.rs" -test = false bench = false [target.'cfg(windows)'.build-dependencies] diff --git a/ports/winit/main2.rs b/ports/winit/main2.rs index 707cf1d422c..1319e2cb60d 100644 --- a/ports/winit/main2.rs +++ b/ports/winit/main2.rs @@ -18,6 +18,7 @@ mod events_loop; mod headed_window; mod headless_window; mod keyutils; +mod prefs; mod resources; mod window_trait; @@ -102,9 +103,22 @@ pub fn main() { "Set custom user agent string (or ios / android / desktop for platform default)", "NCSA Mosaic/1.0 (X11;SunOS 4.1.4 sun4m)", ); + opts.optmulti( + "", + "pref", + "A preference to set to enable", + "dom.bluetooth.enabled", + ); + opts.optmulti( + "", + "pref", + "A preference to set to disable", + "dom.webgpu.enabled=false", + ); let opts_matches; let content_process_token; + match opts::from_cmdline_args(opts, &args) { ArgumentParsingResult::ContentProcess(matches, token) => { opts_matches = matches; @@ -119,6 +133,8 @@ pub fn main() { }, }; + prefs::register_user_prefs(&opts_matches); + // TODO: once log-panics is released, can this be replaced by // log_panics::init()? panic::set_hook(Box::new(|info| { diff --git a/ports/winit/prefs.rs b/ports/winit/prefs.rs new file mode 100644 index 00000000000..16f4de85b81 --- /dev/null +++ b/ports/winit/prefs.rs @@ -0,0 +1,127 @@ +/* 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 servo::embedder_traits; +use getopts::{Matches, Options}; +use servo::config::opts::{self, ArgumentParsingResult}; +use servo::config::prefs::{self, PrefValue}; +use std::collections::HashMap; +use std::fs::File; +use std::io::Read; +use servo::servo_config::basedir; + +pub fn register_user_prefs(opts_matches: &Matches) { + // Read user's prefs.json and then parse --pref command line args. + + let user_prefs_path = opts::get() + .config_dir + .clone() + .or_else(|| basedir::default_config_dir()) + .map(|path| path.join("prefs.json")) + .filter(|path| path.exists()); + + let mut userprefs = if let Some(path) = user_prefs_path { + let mut file = File::open(&path).expect("Error opening user prefs"); + let mut txt = String::new(); + file.read_to_string(&mut txt).expect("Can't read user prefs file"); + prefs::read_prefs_map(&txt).expect("Can't parse user prefs file") + } else { + HashMap::new() + }; + + let argprefs: HashMap = opts_matches.opt_strs("pref").iter().map(|pref| { + let split: Vec<&str> = pref.splitn(2, '=').collect(); + let pref_name = split[0]; + let pref_value = match split.get(1).cloned() { + Some("true") | None => PrefValue::Bool(true), + Some("false") => PrefValue::Bool(false), + Some(string) => { + if let Some(int) = string.parse::().ok() { + PrefValue::Int(int) + } else if let Some(float) = string.parse::().ok() { + PrefValue::Float(float) + } else { + PrefValue::from(string) + } + }, + }; + (pref_name.to_string(), pref_value) + }).collect(); + + // --pref overrides user prefs.json + userprefs.extend(argprefs); + + prefs::add_user_prefs(userprefs); +} + +// Use for test +#[allow(dead_code)] +fn test_parse_pref(arg: &str) { + embedder_traits::resources::set_for_tests(); + let mut opts = Options::new(); + opts.optmulti("", "pref", "", ""); + let args = vec![ + "servo".to_string(), + "--pref".to_string(), + arg.to_string() + ]; + let matches = match opts::from_cmdline_args(opts, &args) { + ArgumentParsingResult::ContentProcess(m, _) => m, + ArgumentParsingResult::ChromeProcess(m) => m, + }; + register_user_prefs(&matches); +} + +#[test] +fn test_parse_pref_from_command_line() { + use servo::servo_config::pref; + // Test with boolean values. + test_parse_pref("dom.bluetooth.enabled=true"); + assert_eq!( + prefs::pref_map().get("dom.bluetooth.enabled"), + PrefValue::Bool(true) + ); + assert_eq!(pref!(dom.bluetooth.enabled), true); + + test_parse_pref("dom.bluetooth.enabled=false"); + assert_eq!( + prefs::pref_map().get("dom.bluetooth.enabled"), + PrefValue::Bool(false) + ); + assert_eq!(pref!(dom.bluetooth.enabled), false); + + // Test with numbers + test_parse_pref("layout.threads=42"); + assert_eq!(pref!(layout.threads), 42); + + // Test string. + test_parse_pref("shell.homepage=str"); + assert_eq!(pref!(shell.homepage), "str"); + + // Test with no value (defaults to true). + prefs::pref_map() + .set("dom.bluetooth.enabled", false) + .unwrap(); + test_parse_pref("dom.bluetooth.enabled"); + assert_eq!(pref!(dom.bluetooth.enabled), true); +} + + +#[test] +fn test_invalid_prefs_from_command_line_panics() { + let err_msg = std::panic::catch_unwind(|| { + test_parse_pref("doesntexist=true"); + }) + .err() + .and_then(|a| a.downcast_ref::().cloned()) + .expect("Should panic"); + assert!( + err_msg.starts_with("Error setting preference"), + "Message should describe the problem" + ); + assert!( + err_msg.contains("doesntexist"), + "Message should mention the name of the preference" + ); +}