mirror of
https://github.com/servo/servo.git
synced 2025-09-05 12:38:21 +01:00
servoshell: Support runtime preference manipulation (#38159)
These changes add a custom servo:preferences URL that allows modifying selected preferences at runtime. The goal of this work is to make it easy to test pages while toggling experimental web platform features, and support quickly changing the User-Agent header. Testing: Manually verified that spacex.com loads correctly after changing the user agent, and that https://polygon.io/ displays grid elements correctly and no console errors with the experimental prefs enabled. Fixes: #35862 <img width="1136" height="880" alt="Screenshot 2025-07-18 at 1 06 23 AM" src="https://github.com/user-attachments/assets/2d27c321-6ca0-43c3-a347-7bc4b55272df" /> --------- Signed-off-by: Josh Matthews <josh@joshmatthews.net>
This commit is contained in:
parent
c97ec1b2fb
commit
6565d982bd
13 changed files with 308 additions and 25 deletions
|
@ -452,7 +452,7 @@ pub enum UserAgentPlatform {
|
||||||
impl UserAgentPlatform {
|
impl UserAgentPlatform {
|
||||||
/// Return the default `UserAgentPlatform` for this platform. This is
|
/// Return the default `UserAgentPlatform` for this platform. This is
|
||||||
/// not an implementation of `Default` so that it can be `const`.
|
/// not an implementation of `Default` so that it can be `const`.
|
||||||
const fn default() -> Self {
|
pub const fn default() -> Self {
|
||||||
if cfg!(target_os = "android") {
|
if cfg!(target_os = "android") {
|
||||||
Self::Android
|
Self::Android
|
||||||
} else if cfg!(target_env = "ohos") {
|
} else if cfg!(target_env = "ohos") {
|
||||||
|
|
|
@ -482,6 +482,9 @@ pub struct Constellation<STF, SWF> {
|
||||||
|
|
||||||
/// When in single-process mode, join handles for script-threads.
|
/// When in single-process mode, join handles for script-threads.
|
||||||
script_join_handles: HashMap<WebViewId, JoinHandle<()>>,
|
script_join_handles: HashMap<WebViewId, JoinHandle<()>>,
|
||||||
|
|
||||||
|
/// A list of URLs that can access privileged internal APIs.
|
||||||
|
privileged_urls: Vec<ServoUrl>,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// State needed to construct a constellation.
|
/// State needed to construct a constellation.
|
||||||
|
@ -535,6 +538,9 @@ pub struct InitialConstellationState {
|
||||||
/// User content manager
|
/// User content manager
|
||||||
pub user_content_manager: UserContentManager,
|
pub user_content_manager: UserContentManager,
|
||||||
|
|
||||||
|
/// A list of URLs that can access privileged internal APIs.
|
||||||
|
pub privileged_urls: Vec<ServoUrl>,
|
||||||
|
|
||||||
/// The async runtime.
|
/// The async runtime.
|
||||||
pub async_runtime: Box<dyn AsyncRuntime>,
|
pub async_runtime: Box<dyn AsyncRuntime>,
|
||||||
}
|
}
|
||||||
|
@ -732,6 +738,7 @@ where
|
||||||
process_manager: ProcessManager::new(state.mem_profiler_chan),
|
process_manager: ProcessManager::new(state.mem_profiler_chan),
|
||||||
async_runtime: state.async_runtime,
|
async_runtime: state.async_runtime,
|
||||||
script_join_handles: Default::default(),
|
script_join_handles: Default::default(),
|
||||||
|
privileged_urls: state.privileged_urls,
|
||||||
};
|
};
|
||||||
|
|
||||||
constellation.run();
|
constellation.run();
|
||||||
|
@ -1003,6 +1010,7 @@ where
|
||||||
player_context: WindowGLContext::get(),
|
player_context: WindowGLContext::get(),
|
||||||
rippy_data: self.rippy_data.clone(),
|
rippy_data: self.rippy_data.clone(),
|
||||||
user_content_manager: self.user_content_manager.clone(),
|
user_content_manager: self.user_content_manager.clone(),
|
||||||
|
privileged_urls: self.privileged_urls.clone(),
|
||||||
});
|
});
|
||||||
|
|
||||||
let pipeline = match result {
|
let pipeline = match result {
|
||||||
|
|
|
@ -204,6 +204,9 @@ pub struct InitialPipelineState {
|
||||||
|
|
||||||
/// User content manager
|
/// User content manager
|
||||||
pub user_content_manager: UserContentManager,
|
pub user_content_manager: UserContentManager,
|
||||||
|
|
||||||
|
/// A list of URLs that can access privileged internal APIs.
|
||||||
|
pub privileged_urls: Vec<ServoUrl>,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct NewPipeline {
|
pub struct NewPipeline {
|
||||||
|
@ -305,6 +308,7 @@ impl Pipeline {
|
||||||
rippy_data: state.rippy_data,
|
rippy_data: state.rippy_data,
|
||||||
user_content_manager: state.user_content_manager,
|
user_content_manager: state.user_content_manager,
|
||||||
lifeline_sender: None,
|
lifeline_sender: None,
|
||||||
|
privileged_urls: state.privileged_urls,
|
||||||
};
|
};
|
||||||
|
|
||||||
// Spawn the child process.
|
// Spawn the child process.
|
||||||
|
@ -497,6 +501,7 @@ pub struct UnprivilegedPipelineContent {
|
||||||
rippy_data: Vec<u8>,
|
rippy_data: Vec<u8>,
|
||||||
user_content_manager: UserContentManager,
|
user_content_manager: UserContentManager,
|
||||||
lifeline_sender: Option<IpcSender<()>>,
|
lifeline_sender: Option<IpcSender<()>>,
|
||||||
|
privileged_urls: Vec<ServoUrl>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl UnprivilegedPipelineContent {
|
impl UnprivilegedPipelineContent {
|
||||||
|
@ -543,6 +548,7 @@ impl UnprivilegedPipelineContent {
|
||||||
player_context: self.player_context.clone(),
|
player_context: self.player_context.clone(),
|
||||||
inherited_secure_context: self.load_data.inherited_secure_context,
|
inherited_secure_context: self.load_data.inherited_secure_context,
|
||||||
user_content_manager: self.user_content_manager,
|
user_content_manager: self.user_content_manager,
|
||||||
|
privileged_urls: self.privileged_urls,
|
||||||
},
|
},
|
||||||
layout_factory,
|
layout_factory,
|
||||||
Arc::new(self.system_font_service.to_proxy()),
|
Arc::new(self.system_font_service.to_proxy()),
|
||||||
|
|
|
@ -514,6 +514,10 @@ impl HttpCache {
|
||||||
request: &Request,
|
request: &Request,
|
||||||
done_chan: &mut DoneChannel,
|
done_chan: &mut DoneChannel,
|
||||||
) -> Option<CachedResponse> {
|
) -> Option<CachedResponse> {
|
||||||
|
if pref!(network_http_cache_disabled) {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
|
||||||
// TODO: generate warning headers as appropriate <https://tools.ietf.org/html/rfc7234#section-5.5>
|
// TODO: generate warning headers as appropriate <https://tools.ietf.org/html/rfc7234#section-5.5>
|
||||||
debug!("trying to construct cache response for {:?}", request.url());
|
debug!("trying to construct cache response for {:?}", request.url());
|
||||||
if request.method != Method::GET {
|
if request.method != Method::GET {
|
||||||
|
|
|
@ -30,6 +30,13 @@ use file::FileProtocolHander;
|
||||||
static FORBIDDEN_SCHEMES: [&str; 4] = ["http", "https", "chrome", "about"];
|
static FORBIDDEN_SCHEMES: [&str; 4] = ["http", "https", "chrome", "about"];
|
||||||
|
|
||||||
pub trait ProtocolHandler: Send + Sync {
|
pub trait ProtocolHandler: Send + Sync {
|
||||||
|
/// A list of schema-less URLs that can be resolved against this handler's
|
||||||
|
/// scheme. These URLs will be granted access to the `navigator.servo`
|
||||||
|
/// interface to perform privileged operations that manipulate Servo internals.
|
||||||
|
fn privileged_paths(&self) -> &'static [&'static str] {
|
||||||
|
&[]
|
||||||
|
}
|
||||||
|
|
||||||
/// Triggers the load of a resource for this protocol and returns a future
|
/// Triggers the load of a resource for this protocol and returns a future
|
||||||
/// that will produce a Response. Even if the protocol is not backed by a
|
/// that will produce a Response. Even if the protocol is not backed by a
|
||||||
/// http endpoint, it is recommended to a least provide:
|
/// http endpoint, it is recommended to a least provide:
|
||||||
|
@ -132,6 +139,18 @@ impl ProtocolRegistry {
|
||||||
.get(scheme)
|
.get(scheme)
|
||||||
.is_some_and(|handler| handler.is_secure())
|
.is_some_and(|handler| handler.is_secure())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn privileged_urls(&self) -> Vec<ServoUrl> {
|
||||||
|
self.handlers
|
||||||
|
.iter()
|
||||||
|
.flat_map(|(scheme, handler)| {
|
||||||
|
let paths = handler.privileged_paths();
|
||||||
|
paths
|
||||||
|
.iter()
|
||||||
|
.filter_map(move |path| ServoUrl::parse(&format!("{scheme}:{path}")).ok())
|
||||||
|
})
|
||||||
|
.collect()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Test if the URL is potentially trustworthy or the custom protocol is registered as secure
|
/// Test if the URL is potentially trustworthy or the custom protocol is registered as secure
|
||||||
|
|
|
@ -8,11 +8,13 @@ use constellation_traits::ScriptToConstellationMessage;
|
||||||
use dom_struct::dom_struct;
|
use dom_struct::dom_struct;
|
||||||
use js::rust::HandleObject;
|
use js::rust::HandleObject;
|
||||||
use profile_traits::mem::MemoryReportResult;
|
use profile_traits::mem::MemoryReportResult;
|
||||||
|
use script_bindings::error::{Error, Fallible};
|
||||||
use script_bindings::interfaces::ServoInternalsHelpers;
|
use script_bindings::interfaces::ServoInternalsHelpers;
|
||||||
use script_bindings::script_runtime::JSContext;
|
use script_bindings::script_runtime::JSContext;
|
||||||
|
use script_bindings::str::USVString;
|
||||||
|
use servo_config::prefs::{self, PrefValue};
|
||||||
|
|
||||||
use crate::dom::bindings::codegen::Bindings::ServoInternalsBinding::ServoInternalsMethods;
|
use crate::dom::bindings::codegen::Bindings::ServoInternalsBinding::ServoInternalsMethods;
|
||||||
use crate::dom::bindings::error::Error;
|
|
||||||
use crate::dom::bindings::reflector::{DomGlobal, Reflector, reflect_dom_object};
|
use crate::dom::bindings::reflector::{DomGlobal, Reflector, reflect_dom_object};
|
||||||
use crate::dom::bindings::root::DomRoot;
|
use crate::dom::bindings::root::DomRoot;
|
||||||
use crate::dom::globalscope::GlobalScope;
|
use crate::dom::globalscope::GlobalScope;
|
||||||
|
@ -20,6 +22,7 @@ use crate::dom::promise::Promise;
|
||||||
use crate::realms::{AlreadyInRealm, InRealm};
|
use crate::realms::{AlreadyInRealm, InRealm};
|
||||||
use crate::routed_promise::{RoutedPromiseListener, route_promise};
|
use crate::routed_promise::{RoutedPromiseListener, route_promise};
|
||||||
use crate::script_runtime::CanGc;
|
use crate::script_runtime::CanGc;
|
||||||
|
use crate::script_thread::ScriptThread;
|
||||||
|
|
||||||
#[dom_struct]
|
#[dom_struct]
|
||||||
pub(crate) struct ServoInternals {
|
pub(crate) struct ServoInternals {
|
||||||
|
@ -55,6 +58,51 @@ impl ServoInternalsMethods<crate::DomTypeHolder> for ServoInternals {
|
||||||
}
|
}
|
||||||
promise
|
promise
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <https://servo.org/internal-no-spec>
|
||||||
|
fn GetBoolPreference(&self, name: USVString) -> Fallible<bool> {
|
||||||
|
if let PrefValue::Bool(b) = prefs::get().get_value(&name) {
|
||||||
|
return Ok(b);
|
||||||
|
}
|
||||||
|
Err(Error::TypeMismatch)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <https://servo.org/internal-no-spec>
|
||||||
|
fn GetIntPreference(&self, name: USVString) -> Fallible<i64> {
|
||||||
|
if let PrefValue::Int(i) = prefs::get().get_value(&name) {
|
||||||
|
return Ok(i);
|
||||||
|
}
|
||||||
|
Err(Error::TypeMismatch)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <https://servo.org/internal-no-spec>
|
||||||
|
fn GetStringPreference(&self, name: USVString) -> Fallible<USVString> {
|
||||||
|
if let PrefValue::Str(s) = prefs::get().get_value(&name) {
|
||||||
|
return Ok(s.into());
|
||||||
|
}
|
||||||
|
Err(Error::TypeMismatch)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <https://servo.org/internal-no-spec>
|
||||||
|
fn SetBoolPreference(&self, name: USVString, value: bool) {
|
||||||
|
let mut current_prefs = prefs::get().clone();
|
||||||
|
current_prefs.set_value(&name, value.into());
|
||||||
|
prefs::set(current_prefs);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <https://servo.org/internal-no-spec>
|
||||||
|
fn SetIntPreference(&self, name: USVString, value: i64) {
|
||||||
|
let mut current_prefs = prefs::get().clone();
|
||||||
|
current_prefs.set_value(&name, value.into());
|
||||||
|
prefs::set(current_prefs);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <https://servo.org/internal-no-spec>
|
||||||
|
fn SetStringPreference(&self, name: USVString, value: USVString) {
|
||||||
|
let mut current_prefs = prefs::get().clone();
|
||||||
|
current_prefs.set_value(&name, value.0.into());
|
||||||
|
prefs::set(current_prefs);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl RoutedPromiseListener<MemoryReportResult> for ServoInternals {
|
impl RoutedPromiseListener<MemoryReportResult> for ServoInternals {
|
||||||
|
@ -66,14 +114,16 @@ impl RoutedPromiseListener<MemoryReportResult> for ServoInternals {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ServoInternalsHelpers for ServoInternals {
|
impl ServoInternalsHelpers for ServoInternals {
|
||||||
/// The navigator.servo api is only exposed to about: pages except about:blank
|
/// The navigator.servo api is exposed to about: pages except about:blank, as
|
||||||
|
/// well as any URLs provided by embedders that register new protocol handlers.
|
||||||
#[allow(unsafe_code)]
|
#[allow(unsafe_code)]
|
||||||
fn is_servo_internal(cx: JSContext, _global: HandleObject) -> bool {
|
fn is_servo_internal(cx: JSContext, _global: HandleObject) -> bool {
|
||||||
unsafe {
|
unsafe {
|
||||||
let in_realm_proof = AlreadyInRealm::assert_for_cx(cx);
|
let in_realm_proof = AlreadyInRealm::assert_for_cx(cx);
|
||||||
let global_scope = GlobalScope::from_context(*cx, InRealm::Already(&in_realm_proof));
|
let global_scope = GlobalScope::from_context(*cx, InRealm::Already(&in_realm_proof));
|
||||||
let url = global_scope.get_url();
|
let url = global_scope.get_url();
|
||||||
url.scheme() == "about" && url.as_str() != "about:blank"
|
(url.scheme() == "about" && url.as_str() != "about:blank") ||
|
||||||
|
ScriptThread::is_servo_privileged(url)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -346,6 +346,10 @@ pub struct ScriptThread {
|
||||||
needs_rendering_update: Arc<AtomicBool>,
|
needs_rendering_update: Arc<AtomicBool>,
|
||||||
|
|
||||||
debugger_global: Dom<DebuggerGlobalScope>,
|
debugger_global: Dom<DebuggerGlobalScope>,
|
||||||
|
|
||||||
|
/// A list of URLs that can access privileged internal APIs.
|
||||||
|
#[no_trace]
|
||||||
|
privileged_urls: Vec<ServoUrl>,
|
||||||
}
|
}
|
||||||
|
|
||||||
struct BHMExitSignal {
|
struct BHMExitSignal {
|
||||||
|
@ -1016,6 +1020,7 @@ impl ScriptThread {
|
||||||
scheduled_update_the_rendering: Default::default(),
|
scheduled_update_the_rendering: Default::default(),
|
||||||
needs_rendering_update: Arc::new(AtomicBool::new(false)),
|
needs_rendering_update: Arc::new(AtomicBool::new(false)),
|
||||||
debugger_global: debugger_global.as_traced(),
|
debugger_global: debugger_global.as_traced(),
|
||||||
|
privileged_urls: state.privileged_urls,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -4030,6 +4035,10 @@ impl ScriptThread {
|
||||||
};
|
};
|
||||||
document.event_handler().handle_refresh_cursor();
|
document.event_handler().handle_refresh_cursor();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub(crate) fn is_servo_privileged(url: ServoUrl) -> bool {
|
||||||
|
with_script_thread(|script_thread| script_thread.privileged_urls.contains(&url))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Drop for ScriptThread {
|
impl Drop for ScriptThread {
|
||||||
|
|
|
@ -11,6 +11,13 @@
|
||||||
Func="ServoInternals::is_servo_internal"]
|
Func="ServoInternals::is_servo_internal"]
|
||||||
interface ServoInternals {
|
interface ServoInternals {
|
||||||
Promise<object> reportMemory();
|
Promise<object> reportMemory();
|
||||||
|
|
||||||
|
[Throws] USVString getStringPreference(USVString name);
|
||||||
|
[Throws] long long getIntPreference(USVString name);
|
||||||
|
[Throws] boolean getBoolPreference(USVString name);
|
||||||
|
undefined setStringPreference(USVString name, USVString value);
|
||||||
|
undefined setIntPreference(USVString name, long long value);
|
||||||
|
undefined setBoolPreference(USVString name, boolean value);
|
||||||
};
|
};
|
||||||
|
|
||||||
partial interface Navigator {
|
partial interface Navigator {
|
||||||
|
|
|
@ -1149,6 +1149,8 @@ fn create_constellation(
|
||||||
let bluetooth_thread: IpcSender<BluetoothRequest> =
|
let bluetooth_thread: IpcSender<BluetoothRequest> =
|
||||||
BluetoothThreadFactory::new(embedder_proxy.clone());
|
BluetoothThreadFactory::new(embedder_proxy.clone());
|
||||||
|
|
||||||
|
let privileged_urls = protocols.privileged_urls();
|
||||||
|
|
||||||
let (public_resource_threads, private_resource_threads, async_runtime) = new_resource_threads(
|
let (public_resource_threads, private_resource_threads, async_runtime) = new_resource_threads(
|
||||||
devtools_sender.clone(),
|
devtools_sender.clone(),
|
||||||
time_profiler_chan.clone(),
|
time_profiler_chan.clone(),
|
||||||
|
@ -1191,6 +1193,7 @@ fn create_constellation(
|
||||||
wgpu_image_map,
|
wgpu_image_map,
|
||||||
user_content_manager,
|
user_content_manager,
|
||||||
async_runtime,
|
async_runtime,
|
||||||
|
privileged_urls,
|
||||||
};
|
};
|
||||||
|
|
||||||
let layout_factory = Arc::new(LayoutFactoryImpl());
|
let layout_factory = Arc::new(LayoutFactoryImpl());
|
||||||
|
|
|
@ -354,6 +354,8 @@ pub struct InitialScriptState {
|
||||||
pub player_context: WindowGLContext,
|
pub player_context: WindowGLContext,
|
||||||
/// User content manager
|
/// User content manager
|
||||||
pub user_content_manager: UserContentManager,
|
pub user_content_manager: UserContentManager,
|
||||||
|
/// A list of URLs that can access privileged internal APIs.
|
||||||
|
pub privileged_urls: Vec<ServoUrl>,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Errors from executing a paint worklet
|
/// Errors from executing a paint worklet
|
||||||
|
|
|
@ -4,22 +4,37 @@
|
||||||
|
|
||||||
//! Loads resources using a mapping from well-known shortcuts to resource: urls.
|
//! Loads resources using a mapping from well-known shortcuts to resource: urls.
|
||||||
//! Recognized shortcuts:
|
//! Recognized shortcuts:
|
||||||
|
//! - servo:default-user-agent
|
||||||
|
//! - servo:experimental-preferences
|
||||||
//! - servo:newtab
|
//! - servo:newtab
|
||||||
|
//! - servo:preferences
|
||||||
|
|
||||||
use std::future::Future;
|
use std::future::Future;
|
||||||
use std::pin::Pin;
|
use std::pin::Pin;
|
||||||
|
|
||||||
|
use headers::{ContentType, HeaderMapExt};
|
||||||
use net::fetch::methods::{DoneChannel, FetchContext};
|
use net::fetch::methods::{DoneChannel, FetchContext};
|
||||||
use net::protocols::ProtocolHandler;
|
use net::protocols::ProtocolHandler;
|
||||||
|
use net_traits::ResourceFetchTiming;
|
||||||
use net_traits::request::Request;
|
use net_traits::request::Request;
|
||||||
use net_traits::response::Response;
|
use net_traits::response::{Response, ResponseBody};
|
||||||
|
use servo::config::prefs::UserAgentPlatform;
|
||||||
|
|
||||||
use crate::desktop::protocols::resource::ResourceProtocolHandler;
|
use crate::desktop::protocols::resource::ResourceProtocolHandler;
|
||||||
|
use crate::prefs::EXPERIMENTAL_PREFS;
|
||||||
|
|
||||||
#[derive(Default)]
|
#[derive(Default)]
|
||||||
pub struct ServoProtocolHandler {}
|
pub struct ServoProtocolHandler {}
|
||||||
|
|
||||||
impl ProtocolHandler for ServoProtocolHandler {
|
impl ProtocolHandler for ServoProtocolHandler {
|
||||||
|
fn privileged_paths(&self) -> &'static [&'static str] {
|
||||||
|
&["preferences"]
|
||||||
|
}
|
||||||
|
|
||||||
|
fn is_fetchable(&self) -> bool {
|
||||||
|
true
|
||||||
|
}
|
||||||
|
|
||||||
fn load(
|
fn load(
|
||||||
&self,
|
&self,
|
||||||
request: &mut Request,
|
request: &mut Request,
|
||||||
|
@ -35,9 +50,44 @@ impl ProtocolHandler for ServoProtocolHandler {
|
||||||
context,
|
context,
|
||||||
"/newtab.html",
|
"/newtab.html",
|
||||||
),
|
),
|
||||||
|
|
||||||
|
"preferences" => ResourceProtocolHandler::response_for_path(
|
||||||
|
request,
|
||||||
|
done_chan,
|
||||||
|
context,
|
||||||
|
"/preferences.html",
|
||||||
|
),
|
||||||
|
|
||||||
|
"experimental-preferences" => {
|
||||||
|
let pref_list = EXPERIMENTAL_PREFS
|
||||||
|
.iter()
|
||||||
|
.map(|pref| format!("\"{pref}\""))
|
||||||
|
.collect::<Vec<String>>()
|
||||||
|
.join(",");
|
||||||
|
json_response(request, format!("[{pref_list}]"))
|
||||||
|
},
|
||||||
|
|
||||||
|
"default-user-agent" => {
|
||||||
|
let user_agent = UserAgentPlatform::default().to_user_agent_string();
|
||||||
|
json_response(request, format!("\"{user_agent}\""))
|
||||||
|
},
|
||||||
|
|
||||||
_ => Box::pin(std::future::ready(Response::network_internal_error(
|
_ => Box::pin(std::future::ready(Response::network_internal_error(
|
||||||
"Invalid shortcut",
|
"Invalid shortcut",
|
||||||
))),
|
))),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn json_response(
|
||||||
|
request: &Request,
|
||||||
|
body: String,
|
||||||
|
) -> Pin<Box<dyn Future<Output = Response> + Send>> {
|
||||||
|
let mut response = Response::new(
|
||||||
|
request.current_url(),
|
||||||
|
ResourceFetchTiming::new(request.timing_type()),
|
||||||
|
);
|
||||||
|
response.headers.typed_insert(ContentType::json());
|
||||||
|
*response.body.lock().unwrap() = ResponseBody::Done(body.into_bytes());
|
||||||
|
Box::pin(std::future::ready(response))
|
||||||
|
}
|
||||||
|
|
|
@ -20,6 +20,25 @@ use servo::servo_geometry::DeviceIndependentPixel;
|
||||||
use servo::servo_url::ServoUrl;
|
use servo::servo_url::ServoUrl;
|
||||||
use url::Url;
|
use url::Url;
|
||||||
|
|
||||||
|
pub(crate) static EXPERIMENTAL_PREFS: &[&str] = &[
|
||||||
|
"dom_async_clipboard_enabled",
|
||||||
|
"dom_fontface_enabled",
|
||||||
|
"dom_intersection_observer_enabled",
|
||||||
|
"dom_mouse_event_which_enabled",
|
||||||
|
"dom_navigator_sendbeacon_enabled",
|
||||||
|
"dom_notification_enabled",
|
||||||
|
"dom_offscreen_canvas_enabled",
|
||||||
|
"dom_permissions_enabled",
|
||||||
|
"dom_resize_observer_enabled",
|
||||||
|
"dom_trusted_types_enabled",
|
||||||
|
"dom_webgl2_enabled",
|
||||||
|
"dom_webgpu_enabled",
|
||||||
|
"dom_xpath_enabled",
|
||||||
|
"layout_columns_enabled",
|
||||||
|
"layout_container_queries_enabled",
|
||||||
|
"layout_grid_enabled",
|
||||||
|
];
|
||||||
|
|
||||||
#[cfg_attr(any(target_os = "android", target_env = "ohos"), allow(dead_code))]
|
#[cfg_attr(any(target_os = "android", target_env = "ohos"), allow(dead_code))]
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
pub(crate) struct ServoShellPreferences {
|
pub(crate) struct ServoShellPreferences {
|
||||||
|
@ -587,26 +606,9 @@ pub(crate) fn parse_command_line_arguments(args: Vec<String>) -> ArgumentParsing
|
||||||
.collect();
|
.collect();
|
||||||
|
|
||||||
if opt_match.opt_present("enable-experimental-web-platform-features") {
|
if opt_match.opt_present("enable-experimental-web-platform-features") {
|
||||||
vec![
|
for pref in EXPERIMENTAL_PREFS {
|
||||||
"dom_async_clipboard_enabled",
|
preferences.set_value(pref, PrefValue::Bool(true));
|
||||||
"dom_fontface_enabled",
|
}
|
||||||
"dom_intersection_observer_enabled",
|
|
||||||
"dom_mouse_event_which_enabled",
|
|
||||||
"dom_navigator_sendbeacon_enabled",
|
|
||||||
"dom_notification_enabled",
|
|
||||||
"dom_offscreen_canvas_enabled",
|
|
||||||
"dom_permissions_enabled",
|
|
||||||
"dom_resize_observer_enabled",
|
|
||||||
"dom_trusted_types_enabled",
|
|
||||||
"dom_webgl2_enabled",
|
|
||||||
"dom_webgpu_enabled",
|
|
||||||
"dom_xpath_enabled",
|
|
||||||
"layout_columns_enabled",
|
|
||||||
"layout_container_queries_enabled",
|
|
||||||
"layout_grid_enabled",
|
|
||||||
]
|
|
||||||
.iter()
|
|
||||||
.for_each(|pref| preferences.set_value(pref, PrefValue::Bool(true)));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Handle all command-line preferences overrides.
|
// Handle all command-line preferences overrides.
|
||||||
|
|
123
resources/resource_protocol/preferences.html
Normal file
123
resources/resource_protocol/preferences.html
Normal file
|
@ -0,0 +1,123 @@
|
||||||
|
<!doctype html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<title>servo:preferences</title>
|
||||||
|
<style>
|
||||||
|
ul {
|
||||||
|
list-style: none;
|
||||||
|
}
|
||||||
|
li {
|
||||||
|
padding-bottom: 1em;
|
||||||
|
}
|
||||||
|
select {
|
||||||
|
font-size: 10pt;
|
||||||
|
}
|
||||||
|
.hidden {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<h2>Preferences</h2>
|
||||||
|
<div id="content" class="hidden">
|
||||||
|
<ul>
|
||||||
|
<li>
|
||||||
|
<input type="checkbox" class="bool-preference" id="experimental">
|
||||||
|
<label for="experimental">Experimental web platform features</label>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<input type="checkbox" class="bool-preference" id="more-experimental">
|
||||||
|
<label for="more-experimental">Even more experimental web platform features</label>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<input type="checkbox" class="bool-preference" id="http-cache">
|
||||||
|
<label for="http-cache">Disable HTTP cache</label>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<div>
|
||||||
|
<label style="vertical-align: baseline" for="user-agent">User agent:</label>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<input class="string-preference" id="user-agent" size=110>
|
||||||
|
<select onchange="updateUserAgent(event)">
|
||||||
|
<option value="servo" selected>Servo</option>
|
||||||
|
<option value="firefox">Firefox</option>
|
||||||
|
<option value="chrome">Chrome</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
<div id="loading">Loading...</div>
|
||||||
|
<script>
|
||||||
|
const prefs = {
|
||||||
|
"experimental": [],
|
||||||
|
"http-cache": ["network_http_cache_disabled"],
|
||||||
|
"more-experimental": ["dom_abort_controller_enabled"],
|
||||||
|
"user-agent": ["user_agent"],
|
||||||
|
};
|
||||||
|
|
||||||
|
const userAgents = {
|
||||||
|
"firefox": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:137.0) Gecko/20100101 Firefox/137.0",
|
||||||
|
"chrome": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/138.0.0.0 Safari/537.36",
|
||||||
|
"servo": "",
|
||||||
|
};
|
||||||
|
|
||||||
|
function initialize() {
|
||||||
|
document.getElementById("loading").classList.toggle("hidden");
|
||||||
|
document.getElementById("content").classList.toggle("hidden");
|
||||||
|
|
||||||
|
const boolPrefInputs = document.getElementsByClassName("bool-preference");
|
||||||
|
for (const pref of boolPrefInputs) {
|
||||||
|
const relatedPrefs = prefs[pref.id];
|
||||||
|
let prefValue = true;
|
||||||
|
for (const boolPref of relatedPrefs) {
|
||||||
|
prefValue &= navigator.servo.getBoolPreference(boolPref);
|
||||||
|
}
|
||||||
|
pref.checked = prefValue;
|
||||||
|
pref.onchange = (ev) => {
|
||||||
|
const relatedPrefs = prefs[ev.target.id];
|
||||||
|
for (const boolPref of relatedPrefs) {
|
||||||
|
navigator.servo.setBoolPreference(boolPref, ev.target.checked);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
const stringPrefInputs = document.getElementsByClassName("string-preference");
|
||||||
|
for (const pref of stringPrefInputs) {
|
||||||
|
const prefValue = navigator.servo.getStringPreference(prefs[pref.id][0]);
|
||||||
|
pref.value = prefValue;
|
||||||
|
pref.oninput = (ev) => updateStringPreference(ev.target);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function updateStringPreference(input) {
|
||||||
|
const relatedPref = prefs[input.id][0];
|
||||||
|
navigator.servo.setStringPreference(relatedPref, input.value);
|
||||||
|
}
|
||||||
|
|
||||||
|
function updateUserAgent(event) {
|
||||||
|
const input = document.getElementById("user-agent");
|
||||||
|
const selection = event.target.value
|
||||||
|
input.value = userAgents[selection];
|
||||||
|
updateStringPreference(input);
|
||||||
|
}
|
||||||
|
|
||||||
|
function initExperimentalPrefs() {
|
||||||
|
return fetch("servo:experimental-preferences")
|
||||||
|
.then(response => response.json())
|
||||||
|
.then(prefList => prefs["experimental"] = prefList)
|
||||||
|
}
|
||||||
|
|
||||||
|
function initDefaultUserAgent() {
|
||||||
|
return fetch("servo:default-user-agent")
|
||||||
|
.then(response => response.json())
|
||||||
|
.then(userAgent => userAgents["servo"] = userAgent);
|
||||||
|
}
|
||||||
|
|
||||||
|
Promise.all([
|
||||||
|
initExperimentalPrefs(),
|
||||||
|
initDefaultUserAgent(),
|
||||||
|
]).then(initialize);
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
</html>
|
Loading…
Add table
Add a link
Reference in a new issue