servo/components/config/prefs.rs
Nico Burns 6cbd89dbb0
Layout: Implement CSS Grid using taffy (#32619)
* Add layout.grid.enabled pref

Signed-off-by: Nico Burns <nico@nicoburns.com>

* Add taffy dependency

Signed-off-by: Nico Burns <nico@nicoburns.com>

* Import taffy <-> stylo conversion code from taffy_stylo crate

Signed-off-by: Nico Burns <nico@nicoburns.com>

* Add `Grid` variant to DisplayInside

Signed-off-by: Nico Burns <nico@nicoburns.com>

* Implement CSS Grid using Taffy

Signed-off-by: Nico Burns <nico@nicoburns.com>

Import full stylo_taffy crate

Signed-off-by: Nico Burns <nico@nicoburns.com>

Squashed PR feedback changes

Deduplicate is_document_only_whitespace

Signed-off-by: Nico Burns <nico@nicoburns.com>

Import taffy::AvailableSpace

Signed-off-by: Nico Burns <nico@nicoburns.com>

Rename FlexContext to TaffyContainerContext

Signed-off-by: Nico Burns <nico@nicoburns.com>

Eliminate references to flexbox in taffy/layout module

Signed-off-by: Nico Burns <nico@nicoburns.com>

Use constructors for geom types

Signed-off-by: Nico Burns <nico@nicoburns.com>

Remove comment about abspos elements splitting contiguous text runs

Signed-off-by: Nico Burns <nico@nicoburns.com>

Remove reference to flexbox in taffy/construct

Signed-off-by: Nico Burns <nico@nicoburns.com>

Deduplicate construction of flexbox/grid containers

Signed-off-by: Nico Burns <nico@nicoburns.com>

Make anonymous text runs InFlow

Signed-off-by: Nico Burns <nico@nicoburns.com>

Remove commented code

Signed-off-by: Nico Burns <nico@nicoburns.com>

Update comments

Signed-off-by: Nico Burns <nico@nicoburns.com>

Inline/vendor the stylo/taffy interop code

Signed-off-by: Nico Burns <nico@nicoburns.com>

* Update test expectations

Signed-off-by: Nico Burns <nico@nicoburns.com>

* Fix nits from PR review

Signed-off-by: Nico Burns <nico@nicoburns.com>

---------

Signed-off-by: Nico Burns <nico@nicoburns.com>
2024-11-21 20:21:01 +00:00

628 lines
22 KiB
Rust
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/* 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::borrow::ToOwned;
use std::collections::HashMap;
use std::convert::{TryFrom, TryInto};
use std::sync::LazyLock;
use embedder_traits::resources::{self, Resource};
use gen::Prefs;
use log::{error, warn};
use serde_json::{self, Value};
use crate::pref_util::Preferences;
pub use crate::pref_util::{PrefError, PrefValue};
static PREFS: LazyLock<Preferences<'static, Prefs>> = LazyLock::new(|| {
let def_prefs: Prefs = serde_json::from_str(&resources::read_string(Resource::Preferences))
.unwrap_or_else(|_| {
error!("Preference json file is invalid. Setting Preference to default values");
Prefs::default()
});
let result = Preferences::new(def_prefs, &gen::PREF_ACCESSORS);
for (key, value) in result.iter() {
set_stylo_pref_ref(&key, &value);
}
result
});
/// A convenience macro for accessing a preference value using its static path.
/// Passing an invalid path is a compile-time error.
#[macro_export]
macro_rules! pref {
($($segment: ident).+) => {{
let values = $crate::prefs::pref_map().values();
let lock = values.read()
.map(|prefs| prefs $(.$segment)+.clone());
lock.unwrap()
}};
}
/// A convenience macro for updating a preference value using its static path.
/// Passing an invalid path is a compile-time error.
#[macro_export]
macro_rules! set_pref {
($($segment: ident).+, $value: expr) => {{
let value = $value;
$crate::prefs::set_stylo_pref(stringify!($($segment).+), value);
let values = $crate::prefs::pref_map().values();
let mut lock = values.write().unwrap();
lock$ (.$segment)+ = value;
}};
}
/// Access preferences using their `String` keys. Note that the key may be different from the
/// static path because legacy keys contain hyphens, or because a preference name has been renamed.
///
/// When retrieving a preference, the value will always be a `PrefValue`. When setting a value, it
/// may be a `PrefValue` or the type that converts into the correct underlying value; one of `bool`,
/// `i64`, `f64` or `String`.
#[inline]
pub fn pref_map() -> &'static Preferences<'static, Prefs> {
&PREFS
}
pub fn add_user_prefs(prefs: HashMap<String, PrefValue>) {
for (key, value) in prefs.iter() {
set_stylo_pref_ref(key, value);
}
if let Err(error) = PREFS.set_all(prefs) {
panic!("Error setting preference: {:?}", error);
}
}
pub fn set_stylo_pref(key: &str, value: impl Into<PrefValue>) {
set_stylo_pref_ref(key, &value.into());
}
fn set_stylo_pref_ref(key: &str, value: &PrefValue) {
match value.try_into() {
Ok(StyloPrefValue::Bool(value)) => style_config::set_bool(key, value),
Ok(StyloPrefValue::Int(value)) => style_config::set_i32(key, value),
Err(TryFromPrefValueError::IntegerOverflow(value)) => {
// TODO: logging doesnt actually work this early, so we should
// split PrefValue into i32 and i64 variants.
warn!("Pref value too big for Stylo: {} ({})", key, value);
},
Err(TryFromPrefValueError::UnmappedType) => {
// Most of Servos prefs will hit this. When adding a new pref type
// in Stylo, update TryFrom<&PrefValue> for StyloPrefValue as well.
},
}
}
enum StyloPrefValue {
Bool(bool),
Int(i32),
}
enum TryFromPrefValueError {
IntegerOverflow(i64),
UnmappedType,
}
impl TryFrom<&PrefValue> for StyloPrefValue {
type Error = TryFromPrefValueError;
fn try_from(value: &PrefValue) -> Result<Self, Self::Error> {
match *value {
PrefValue::Int(value) => {
if let Ok(value) = value.try_into() {
Ok(Self::Int(value))
} else {
Err(TryFromPrefValueError::IntegerOverflow(value))
}
},
PrefValue::Bool(value) => Ok(Self::Bool(value)),
_ => Err(TryFromPrefValueError::UnmappedType),
}
}
}
pub fn read_prefs_map(txt: &str) -> Result<HashMap<String, PrefValue>, PrefError> {
let prefs: HashMap<String, Value> =
serde_json::from_str(txt).map_err(PrefError::JsonParseErr)?;
prefs
.into_iter()
.map(|(k, pref_value)| {
Ok({
let v = match &pref_value {
Value::Bool(b) => PrefValue::Bool(*b),
Value::Number(n) if n.is_i64() => PrefValue::Int(n.as_i64().unwrap()),
Value::Number(n) if n.is_f64() => PrefValue::Float(n.as_f64().unwrap()),
Value::String(s) => PrefValue::Str(s.to_owned()),
Value::Array(v) => {
let mut array = v.iter().map(PrefValue::from_json_value);
if array.all(|v| v.is_some()) {
PrefValue::Array(array.flatten().collect())
} else {
return Err(PrefError::InvalidValue(format!(
"Invalid value: {}",
pref_value
)));
}
},
_ => {
return Err(PrefError::InvalidValue(format!(
"Invalid value: {}",
pref_value
)));
},
};
(k.to_owned(), v)
})
})
.collect()
}
mod gen {
use serde::{Deserialize, Serialize};
use servo_config_plugins::build_structs;
// The number of layout threads is calculated if it is not present in `prefs.json`.
fn default_layout_threads() -> i64 {
std::cmp::max(num_cpus::get() * 3 / 4, 1) as i64
}
fn default_font_size() -> i64 {
16
}
fn default_monospace_font_size() -> i64 {
13
}
build_structs! {
// type of the accessors
accessor_type = crate::pref_util::Accessor::<Prefs, crate::pref_util::PrefValue>,
// name of the constant, which will hold a HashMap of preference accessors
gen_accessors = PREF_ACCESSORS,
// tree of structs to generate
gen_types = Prefs {
fonts: {
#[serde(default)]
default: String,
#[serde(default)]
serif: String,
#[serde(default)]
#[serde(rename = "fonts.sans-serif")]
sans_serif: String,
#[serde(default)]
monospace: String,
#[serde(default = "default_font_size")]
#[serde(rename = "fonts.default-size")]
default_size: i64,
#[serde(default = "default_monospace_font_size")]
#[serde(rename = "fonts.default-monospace-size")]
default_monospace_size: i64,
},
css: {
animations: {
testing: {
#[serde(default)]
enabled: bool,
},
},
},
devtools: {
server: {
enabled: bool,
port: i64,
},
},
dom: {
webgpu: {
/// Enable WebGPU APIs.
enabled: bool,
/// List of comma-separated backends to be used by wgpu
wgpu_backend: String,
},
bluetooth: {
enabled: bool,
testing: {
enabled: bool,
}
},
allow_scripts_to_close_windows: bool,
canvas_capture: {
enabled: bool,
},
canvas_text: {
enabled: bool,
},
composition_event: {
#[serde(rename = "dom.compositionevent.enabled")]
enabled: bool,
},
crypto: {
subtle: {
enabled: bool,
}
},
custom_elements: {
#[serde(rename = "dom.customelements.enabled")]
enabled: bool,
},
document: {
dblclick_timeout: i64,
dblclick_dist: i64,
},
forcetouch: {
enabled: bool,
},
fullscreen: {
test: bool,
},
gamepad: {
enabled: bool,
},
imagebitmap: {
enabled: bool,
},
intersection_observer: {
enabled: bool,
},
microdata: {
testing: {
enabled: bool,
}
},
mouse_event: {
which: {
#[serde(rename = "dom.mouseevent.which.enabled")]
enabled: bool,
}
},
mutation_observer: {
enabled: bool,
},
offscreen_canvas: {
enabled: bool,
},
permissions: {
enabled: bool,
testing: {
allowed_in_nonsecure_contexts: bool,
}
},
resize_observer: {
enabled: bool,
},
script: {
asynch: bool,
},
serviceworker: {
enabled: bool,
timeout_seconds: i64,
},
servo_helpers: {
enabled: bool,
},
servoparser: {
async_html_tokenizer: {
enabled: bool,
}
},
shadowdom: {
enabled: bool,
},
svg: {
enabled: bool,
},
testable_crash: {
enabled: bool,
},
testbinding: {
enabled: bool,
prefcontrolled: {
#[serde(default)]
enabled: bool,
},
prefcontrolled2: {
#[serde(default)]
enabled: bool,
},
preference_value: {
#[serde(default)]
falsy: bool,
#[serde(default)]
quote_string_test: String,
#[serde(default)]
space_string_test: String,
#[serde(default)]
string_empty: String,
#[serde(default)]
string_test: String,
#[serde(default)]
truthy: bool,
},
},
testing: {
element: {
activation: {
#[serde(default)]
enabled: bool,
}
},
html_input_element: {
select_files: {
#[serde(rename = "dom.testing.htmlinputelement.select_files.enabled")]
enabled: bool,
}
},
},
testperf: {
#[serde(default)]
enabled: bool,
},
webgl2: {
/// Enable WebGL2 APIs.
enabled: bool,
},
webrtc: {
transceiver: {
enabled: bool,
},
#[serde(default)]
enabled: bool,
},
webvtt: {
enabled: bool,
},
webxr: {
#[serde(default)]
enabled: bool,
#[serde(default)]
test: bool,
first_person_observer_view: bool,
glwindow: {
#[serde(default)]
enabled: bool,
#[serde(rename = "dom.webxr.glwindow.left-right")]
left_right: bool,
#[serde(rename = "dom.webxr.glwindow.red-cyan")]
red_cyan: bool,
spherical: bool,
cubemap: bool,
},
hands: {
#[serde(default)]
enabled: bool,
},
layers: {
enabled: bool,
},
openxr: {
enabled: bool,
},
sessionavailable: bool,
#[serde(rename = "dom.webxr.unsafe-assume-user-intent")]
unsafe_assume_user_intent: bool,
},
worklet: {
blockingsleep: {
#[serde(default)]
enabled: bool,
},
#[serde(default)]
enabled: bool,
testing: {
#[serde(default)]
enabled: bool,
},
timeout_ms: i64,
},
},
gfx: {
subpixel_text_antialiasing: {
#[serde(rename = "gfx.subpixel-text-antialiasing.enabled")]
enabled: bool,
},
texture_swizzling: {
#[serde(rename = "gfx.texture-swizzling.enabled")]
enabled: bool,
},
},
js: {
asmjs: {
enabled: bool,
},
asyncstack: {
enabled: bool,
},
baseline_interpreter: {
enabled: bool,
},
/// Whether to disable the jit within SpiderMonkey
disable_jit: bool,
baseline_jit: {
enabled: bool,
unsafe_eager_compilation: {
enabled: bool,
},
},
discard_system_source: {
enabled: bool,
},
dump_stack_on_debuggee_would_run: {
enabled: bool,
},
ion: {
enabled: bool,
offthread_compilation: {
enabled: bool,
},
unsafe_eager_compilation: {
enabled: bool,
},
},
mem: {
gc: {
allocation_threshold_mb: i64,
allocation_threshold_factor: i64,
allocation_threshold_avoid_interrupt_factor: i64,
compacting: {
enabled: bool,
},
decommit_threshold_mb: i64,
dynamic_heap_growth: {
enabled: bool,
},
dynamic_mark_slice: {
enabled: bool,
},
empty_chunk_count_max: i64,
empty_chunk_count_min: i64,
high_frequency_heap_growth_max: i64,
high_frequency_heap_growth_min: i64,
high_frequency_high_limit_mb: i64,
high_frequency_low_limit_mb: i64,
high_frequency_time_limit_ms: i64,
incremental: {
enabled: bool,
slice_ms: i64,
},
low_frequency_heap_growth: i64,
per_zone: {
enabled: bool,
},
zeal: {
frequency: i64,
level: i64,
},
},
max: i64,
},
native_regex: {
enabled: bool,
},
offthread_compilation: {
enabled: bool,
},
parallel_parsing: {
enabled: bool,
},
shared_memory: {
enabled: bool,
},
throw_on_asmjs_validation_failure: {
enabled: bool,
},
throw_on_debuggee_would_run: {
enabled: bool,
},
timers: {
minimum_duration: i64,
},
wasm: {
baseline: {
enabled: bool,
},
enabled: bool,
ion: {
enabled: bool,
}
},
werror: {
enabled: bool,
},
},
layout: {
animations: {
test: {
enabled: bool,
}
},
columns: {
enabled: bool,
},
css: {
transition_behavior: {
#[serde(rename = "layout.css.transition-behavior.enabled")]
enabled: bool,
}
},
flexbox: {
enabled: bool,
},
grid: {
enabled: bool,
},
legacy_layout: bool,
#[serde(default = "default_layout_threads")]
threads: i64,
writing_mode: {
#[serde(rename = "layout.writing-mode.enabled")]
enabled: bool,
}
},
media: {
glvideo: {
/// Enable hardware acceleration for video playback.
enabled: bool,
},
testing: {
/// Enable a non-standard event handler for verifying behavior of media elements during tests.
enabled: bool,
}
},
network: {
enforce_tls: {
enabled: bool,
localhost: bool,
onion: bool,
},
http_cache: {
#[serde(rename = "network.http-cache.disabled")]
disabled: bool,
},
local_directory_listing: {
enabled: bool,
},
mime: {
sniff: bool,
},
tls: {
/// Ignore `std::io::Error` with `ErrorKind::UnexpectedEof` received when a TLS connection
/// is closed without a close_notify.
///
/// Used for tests because WPT server doesn't properly close the TLS connection.
// TODO: remove this when WPT server is updated to use a proper TLS implementation.
ignore_unexpected_eof: bool,
},
},
session_history: {
#[serde(rename = "session-history.max-length")]
max_length: i64,
},
shell: {
background_color: {
/// The background color of shell's viewport. This will be used by OpenGL's `glClearColor`.
#[serde(rename = "shell.background-color.rgba")]
rgba: [f64; 4],
},
crash_reporter: {
enabled: bool,
},
/// URL string of the homepage.
homepage: String,
#[serde(rename = "shell.native-orientation")]
native_orientation: String,
native_titlebar: {
/// Enable native window's titlebar and decorations.
#[serde(rename = "shell.native-titlebar.enabled")]
enabled: bool,
},
/// URL string of the search engine page (for example <https://google.com> or and <https://duckduckgo.com>.
searchpage: String,
},
webgl: {
testing: {
context_creation_error: bool,
}
},
}
}
}