Embed default resources in Servo applications using a servo-default-resources crate (#43182)

This PR considers the following constraints:

- Resources must be available when building servo via a published
crates.io package (i.e. no `../../../resources/<file>` file references).
- Minimal setup when writing tests (`nextest` spawns each test in its
own process, so we don't want to explicitly initialize the resource
handler for every `#[test]` fn)
- Use local resources when developing locally
- Support loading the resources from a proper resource directory if the
embedder wishes so, including via a custom mechanism, not necessarily as
files

(File) Resources that are only accessed from servoshell are out of scope
of this PR, since it mainly focusses on unblocking publishing `libservo`
to crates.io.

Baking the resources into the binary by default simplifies the setup a
lot. We already supported that before, but only for testing purposes and
explicitly not for production builds.

Using [`inventory`](https://crates.io/crates/inventory) adds a simple
way for the embedder to replace the default baked in resources, while
also keeping the test usage of baked in resources simple.

rippy.png is also referenced from image_cache - We simply duplicate it,
since the image is small, to avoid adding unnecessarily complex
solutions like adding a dedicated crate.


Testing: Covered by existing tests. [mach try
full](https://github.com/jschwe/servo/actions/runs/23811669469)
Fixes: Part of #43145

---------

Signed-off-by: Jonathan Schwender <schwenderjonathan@gmail.com>
This commit is contained in:
Jonathan Schwender
2026-04-01 09:16:28 +02:00
committed by GitHub
parent 05946163f2
commit f4877c190e
38 changed files with 234 additions and 200 deletions

21
Cargo.lock generated
View File

@@ -4438,6 +4438,15 @@ dependencies = [
"syn",
]
[[package]]
name = "inventory"
version = "0.3.24"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a4f0c30c76f2f4ccee3fe55a2435f691ca00c0e4bd87abe4f4a851b1d4dac39b"
dependencies = [
"rustversion",
]
[[package]]
name = "ipc-channel"
version = "0.21.0"
@@ -7436,6 +7445,7 @@ dependencies = [
"servo-config",
"servo-constellation",
"servo-constellation-traits",
"servo-default-resources",
"servo-devtools",
"servo-devtools-traits",
"servo-embedder-traits",
@@ -7712,6 +7722,13 @@ dependencies = [
"wgpu-core",
]
[[package]]
name = "servo-default-resources"
version = "0.0.6"
dependencies = [
"servo-embedder-traits",
]
[[package]]
name = "servo-deny-public-fields"
version = "0.0.6"
@@ -7784,6 +7801,7 @@ dependencies = [
"euclid",
"http 1.4.0",
"image",
"inventory",
"keyboard-types",
"log",
"malloc_size_of_derive",
@@ -8304,6 +8322,7 @@ dependencies = [
"serde",
"servo-base",
"servo-config",
"servo-default-resources",
"servo-devtools-traits",
"servo-embedder-traits",
"servo-hyper-serde",
@@ -8353,6 +8372,7 @@ dependencies = [
"serde",
"servo-base",
"servo-config",
"servo-default-resources",
"servo-embedder-traits",
"servo-hyper-serde",
"servo-malloc-size-of",
@@ -8720,6 +8740,7 @@ dependencies = [
"serde",
"servo-base",
"servo-config",
"servo-default-resources",
"servo-malloc-size-of",
"servo-net-traits",
"servo-profile",

View File

@@ -106,6 +106,7 @@ icu_segmenter = "1.5.0"
image = { version = "0.25", default-features = false, features = ["avif", "rayon", "bmp", "gif", "ico", "jpeg", "png", "webp"] }
imsz = "0.4"
indexmap = { version = "2.11.4", features = ["std"] }
inventory = { version = "0.3.24" }
ipc-channel = "0.21"
itertools = "0.14"
js = { package = "mozjs", version = "=0.15.7", default-features = false, features = ["libz-sys", "intl"] }
@@ -265,6 +266,7 @@ servo-config = { version = "0.0.6", path = "components/config" }
servo-config-macro = { version = "0.0.6", path = "components/config/macro" }
servo-constellation = { version = "0.0.6", path = "components/constellation" }
servo-constellation-traits = { version = "0.0.6", path = "components/shared/constellation" }
servo-default-resources = { version = "0.0.6", path = "components/default-resources" }
servo-geometry = { version = "0.0.6", path = "components/geometry" }
servo-media = { version = "0.0.6", path = "components/media/servo-media" }
servo-media-audio = { version = "0.0.6", path = "components/media/audio" }

View File

@@ -0,0 +1,16 @@
[package]
name = "servo-default-resources"
version.workspace = true
authors.workspace = true
repository.workspace = true
description.workspace = true
license.workspace = true
edition.workspace = true
publish.workspace = true
rust-version.workspace = true
[lib]
path = "lib.rs"
[dependencies]
embedder_traits = { workspace = true }

View File

@@ -0,0 +1,38 @@
/* 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 embedder_traits::resources::{Resource, ResourceReaderMethods};
/// A default resource reader that provides baked in resources.
pub struct DefaultResourceReader;
impl ResourceReaderMethods for DefaultResourceReader {
fn sandbox_access_files(&self) -> Vec<PathBuf> {
vec![]
}
fn sandbox_access_files_dirs(&self) -> Vec<PathBuf> {
vec![]
}
fn read(&self, file: Resource) -> Vec<u8> {
match file {
Resource::BluetoothBlocklist => &include_bytes!("resources/gatt_blocklist.txt")[..],
Resource::DomainList => &include_bytes!("resources/public_domains.txt")[..],
Resource::HstsPreloadList => &include_bytes!("resources/hsts_preload.fstmap")[..],
Resource::BadCertHTML => &include_bytes!("resources/badcert.html")[..],
Resource::NetErrorHTML => &include_bytes!("resources/neterror.html")[..],
Resource::BrokenImageIcon => &include_bytes!("resources/rippy.png")[..],
Resource::CrashHTML => &include_bytes!("resources/crash.html")[..],
Resource::DirectoryListingHTML => {
&include_bytes!("resources/directory-listing.html")[..]
},
Resource::AboutMemoryHTML => &include_bytes!("resources/about-memory.html")[..],
Resource::DebuggerJS => &include_bytes!("resources/debugger.js")[..],
}
.to_owned()
}
}
embedder_traits::submit_resource_reader!(&DefaultResourceReader);

View File

Before

Width:  |  Height:  |  Size: 144 B

After

Width:  |  Height:  |  Size: 144 B

View File

@@ -17,7 +17,7 @@ test = false
doctest = false
[features]
test-util = ["hyper-util/server-graceful"]
test-util = ["hyper-util/server-graceful", "dep:servo-default-resources"]
[dependencies]
async-compression = { version = "0.4.12", default-features = false, features = ["brotli", "gzip", "tokio", "zlib", "zstd"] }
@@ -69,6 +69,7 @@ serde = { workspace = true }
servo_arc = { workspace = true }
servo-base = { workspace = true }
servo-config = { workspace = true }
servo-default-resources = { workspace = true, optional = true }
servo-url = { workspace = true }
sha2 = { workspace = true }
time = { workspace = true }
@@ -84,7 +85,7 @@ webpki-roots = { workspace = true }
webrender_api = { workspace = true }
[dev-dependencies]
embedder_traits = { workspace = true, features = ["baked-default-resources"] }
servo-default-resources = { workspace = true }
flate2 = { workspace = true }
fst = "0.4"
hyper = { workspace = true, features = ["full"] }

View File

@@ -39,7 +39,7 @@ use webrender_api::units::DeviceIntSize;
// We bake in rippy.png as a fallback, in case the embedder does not provide a broken
// image icon resource. This version is 229 bytes, so don't exchange it against
// something of higher resolution.
const FALLBACK_RIPPY: &[u8] = include_bytes!("../../resources/rippy.png");
const FALLBACK_RIPPY: &[u8] = include_bytes!("resources/rippy.png");
/// The current SVG stack relies on `resvg` to provide the natural dimensions of
/// the SVG, which it automatically infers from the width/height/viewBox properties

Binary file not shown.

After

Width:  |  Height:  |  Size: 144 B

View File

@@ -22,6 +22,7 @@ use hyper_util::rt::tokio::TokioIo;
use net_traits::AsyncRuntime;
use rustls_pki_types::pem::PemObject;
use rustls_pki_types::{CertificateDer, PrivateKeyDer, PrivatePkcs8KeyDer};
use servo_default_resources as _;
use servo_url::ServoUrl;
use tokio::net::{TcpListener, TcpStream};
use tokio_rustls::{self, TlsAcceptor};

View File

@@ -18,8 +18,11 @@ crate-type = ["rlib"]
[features]
# Note: Servoshell does not use the default features of servo, so you also
# need to add features that should be default to ports/servoshell/Cargo.toml
default = ["clipboard", "js_jit"]
default = ["baked-in-resources", "clipboard", "js_jit"]
background_hang_monitor = ["servo-background-hang-monitor/sampler"]
# This is slightly magical, but this will also work on ohos, where `servo-default-resource` is not added,
# which makes `baked-in-resources a no-op (as intended) on ohos (and perhaps other platforms in the future).
baked-in-resources = ["dep:servo-default-resources"]
bluetooth = [
"servo-bluetooth-traits",
"dep:servo-bluetooth",
@@ -135,6 +138,12 @@ webrender = { workspace = true }
webrender_api = { workspace = true }
webxr-api = { workspace = true, optional = true }
# On most platforms we bake in the resources for simplicity. On ohos we load resources via the filesystem.
# In the future we might want to use this approach also for the other platforms.
# If the feature is turned off, a resource reader must be injected into the build via `inventory::submit!()`.
[target.'cfg(not(target_env = "ohos"))'.dependencies]
servo-default-resources = { workspace = true, optional = true }
[target.'cfg(any(target_os = "android", target_env = "ohos"))'.dependencies]
webxr = { workspace = true, optional = true }

View File

@@ -31,7 +31,7 @@ mod webview_delegate;
// should be exported at the root. See <https://github.com/servo/servo/issues/18475>.
pub use accesskit;
pub use embedder_traits::user_contents::UserScript;
pub use embedder_traits::*;
pub use embedder_traits::{submit_resource_reader, *};
pub use image::RgbaImage;
pub use keyboard_types::{
Code, CompositionEvent, CompositionState, Key, KeyState, Location, Modifiers, NamedKey,
@@ -106,3 +106,7 @@ pub mod protocol_handler {
pub use crate::webview_delegate::ProtocolHandlerRegistration;
}
// We need to reference this crate, in order for the linker not to remove it.
#[cfg(all(feature = "baked-in-resources", not(target_env = "ohos")))]
use servo_default_resources as _;

View File

@@ -14,9 +14,6 @@ name = "embedder_traits"
path = "lib.rs"
[features]
# bakes default resources into the library.
# This feature is mainly intended for testing purposes.
baked-default-resources = []
gamepad = []
[dependencies]
@@ -29,6 +26,7 @@ http = { workspace = true }
hyper_serde = { workspace = true }
image = { workspace = true }
keyboard-types = { workspace = true }
inventory = { workspace = true }
log = { workspace = true }
malloc_size_of = { workspace = true }
malloc_size_of_derive = { workspace = true }

View File

@@ -3,47 +3,82 @@
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
use std::path::PathBuf;
use std::sync::RwLock;
use std::sync::LazyLock;
static RES: RwLock<Option<Box<dyn ResourceReaderMethods + Sync + Send>>> = RwLock::new(None);
#[doc(hidden)]
pub use inventory as _inventory;
#[cfg(feature = "baked-default-resources")]
static INIT_TEST_RESOURCES: std::sync::Once = std::sync::Once::new();
/// A static reference to a ResourceReader
///
/// If you need to initialize the resource reader at runtime, use interior mutability.
///
/// # Examples
///
/// ```
/// pub(crate) struct ResourceReaderImpl {
/// resource_dir: OnceLock<PathBuf>,
/// }
/// static RESOURCE_READER: ResourceReaderImpl = ResourceReaderImpl {
/// resource_dir: OnceLock::new(),
/// };
///
/// servo::submit_resource_reader!(&RESOURCE_READER);
///
/// /// This can be called during initialization, e.g. after parsing commandline flags.
/// pub(crate) fn set_resource_dir(resource_dir: PathBuf) {
/// RESOURCE_READER.resource_dir.set(resource_dir).expect("Already initialized.")
/// }
/// impl ResourceReaderMethods for ResourceReaderImpl {
/// //
/// }
/// ```
pub type ResourceReader = &'static (dyn ResourceReaderMethods + Sync + Send);
#[cfg(all(feature = "baked-default-resources", servo_production))]
const _: () = assert!(
false,
"baked-default-resources should not be used in production"
);
/// The Embedder should initialize the ResourceReader early.
pub fn set(reader: Box<dyn ResourceReaderMethods + Sync + Send>) {
*RES.write().unwrap() = Some(reader);
}
#[cfg(not(feature = "baked-default-resources"))]
pub fn read_bytes(res: Resource) -> Vec<u8> {
if let Some(reader) = RES.read().unwrap().as_ref() {
reader.read(res)
} else {
log::error!("Resource reader not set.");
vec![]
}
}
#[cfg(feature = "baked-default-resources")]
pub fn read_bytes(res: Resource) -> Vec<u8> {
INIT_TEST_RESOURCES.call_once(|| {
let mut reader = RES.write().unwrap();
if reader.is_none() {
*reader = Some(resources_for_tests())
/// Register the [`ResourceReader`] implementation.
///
/// This should be added at most once in the whole project.
/// In particular this means you should make sure to disable the (default)
/// `baked-in-resources` feature of servo if you want to override the default reader.
///
/// # Examples
///
/// Put `submit_resource_reader` invocations **outside** any function body:
/// ```
/// servo_embedder_traits::submit_resource_reader!(my_resource_reader);
/// ```
#[macro_export]
macro_rules! submit_resource_reader {
($resource_reader:expr) => {
$crate::resources::_inventory::submit! {
$resource_reader as $crate::resources::ResourceReader
}
});
RES.read()
.unwrap()
.as_ref()
.expect("Resource reader not set.")
.read(res)
};
}
// The embedder may register a resource reader via `submit_resource_reader!()`
// Note: A weak symbol would perhaps be preferable, but that isn't available in stable rust yet.
inventory::collect!(ResourceReader);
static RESOURCE_READER: LazyLock<ResourceReader> = {
LazyLock::new(|| {
let mut resource_reader_iterator = inventory::iter::<ResourceReader>.into_iter();
let Some(resource_reader) = resource_reader_iterator.next() else {
panic!("No resource reader registered");
};
if resource_reader_iterator.next().is_some() {
log::error!(
"Multiple resource readers registered. Taking the first implementation \
(random, non deterministic order). This is a bug! Check usages of \
`submit_resource_reader!()`. Perhaps you meant to disable the default resource reader \
(selected by depending on the `servo-default-resources` crate) ?"
);
}
*resource_reader
})
};
pub fn read_bytes(res: Resource) -> Vec<u8> {
RESOURCE_READER.read(res)
}
pub fn read_string(res: Resource) -> String {
@@ -51,19 +86,11 @@ pub fn read_string(res: Resource) -> String {
}
pub fn sandbox_access_files() -> Vec<PathBuf> {
RES.read()
.unwrap()
.as_ref()
.map(|reader| reader.sandbox_access_files())
.unwrap_or_default()
RESOURCE_READER.sandbox_access_files()
}
pub fn sandbox_access_files_dirs() -> Vec<PathBuf> {
RES.read()
.unwrap()
.as_ref()
.map(|reader| reader.sandbox_access_files_dirs())
.unwrap_or_default()
RESOURCE_READER.sandbox_access_files_dirs()
}
pub enum Resource {
@@ -146,45 +173,3 @@ pub trait ResourceReaderMethods {
/// here to ensure the content process can access the files.
fn sandbox_access_files_dirs(&self) -> Vec<PathBuf>;
}
/// Provides baked in resources for tests.
///
/// Embedder builds (e.g. servoshell) should use [`set`] and ship the resources themselves.
#[cfg(feature = "baked-default-resources")]
fn resources_for_tests() -> Box<dyn ResourceReaderMethods + Sync + Send> {
struct ResourceReader;
impl ResourceReaderMethods for ResourceReader {
fn sandbox_access_files(&self) -> Vec<PathBuf> {
vec![]
}
fn sandbox_access_files_dirs(&self) -> Vec<PathBuf> {
vec![]
}
fn read(&self, file: Resource) -> Vec<u8> {
match file {
Resource::BluetoothBlocklist => {
&include_bytes!("../../../resources/gatt_blocklist.txt")[..]
},
Resource::DomainList => {
&include_bytes!("../../../resources/public_domains.txt")[..]
},
Resource::HstsPreloadList => {
&include_bytes!("../../../resources/hsts_preload.fstmap")[..]
},
Resource::BadCertHTML => &include_bytes!("../../../resources/badcert.html")[..],
Resource::NetErrorHTML => &include_bytes!("../../../resources/neterror.html")[..],
Resource::BrokenImageIcon => &include_bytes!("../../../resources/rippy.png")[..],
Resource::CrashHTML => &include_bytes!("../../../resources/crash.html")[..],
Resource::DirectoryListingHTML => {
&include_bytes!("../../../resources/directory-listing.html")[..]
},
Resource::AboutMemoryHTML => {
&include_bytes!("../../../resources/about-memory.html")[..]
},
Resource::DebuggerJS => &include_bytes!("../../../resources/debugger.js")[..],
}
.to_owned()
}
}
Box::new(ResourceReader)
}

View File

@@ -51,4 +51,4 @@ uuid = { workspace = true }
webrender_api = { workspace = true }
[dev-dependencies]
embedder_traits = { workspace = true, features = ["baked-default-resources"] }
servo-default-resources = { workspace = true }

View File

@@ -3,6 +3,7 @@
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
use net_traits::pub_domains::{is_pub_domain, is_reg_domain, pub_suffix, reg_suffix};
use servo_default_resources as _;
// These tests may need to be updated if the PSL changes.

View File

@@ -36,6 +36,7 @@ uuid = { workspace = true }
[dev-dependencies]
profile = { workspace = true }
servo-default-resources = { workspace = true }
url = { workspace = true }
[[test]]

View File

@@ -6,6 +6,7 @@ use profile::mem as profile_mem;
use servo_base::generic_channel as base_channel;
use servo_base::generic_channel::GenericSend;
use servo_base::id::TEST_WEBVIEW_ID;
use servo_default_resources as _;
use servo_url::ServoUrl;
use storage_traits::StorageThreads;
use storage_traits::webstorage_thread::{WebStorageThreadMsg, WebStorageType};

View File

@@ -41,7 +41,8 @@ OriginalFilename = "servoshell.exe"
ProductName = "ServoShell"
[features]
default = ["gamepad", "servo/clipboard", "js_jit", "max_log_level", "webgpu", "webxr"]
default = ["baked-in-resources", "gamepad", "servo/clipboard", "js_jit", "max_log_level", "webgpu", "webxr"]
baked-in-resources = ["servo/baked-in-resources"]
crown = ["servo/crown"]
debugmozjs = ["servo/debugmozjs"]
gamepad = ["servo/gamepad"]

View File

@@ -12,7 +12,6 @@ use crate::prefs::{ArgumentParsingResult, parse_command_line_arguments};
pub fn main() {
crate::crash_handler::install();
crate::init_crypto();
crate::resources::init();
// TODO: once log-panics is released, can this be replaced by
// log_panics::init()?

View File

@@ -43,9 +43,7 @@ impl ResourceProtocolHandler {
)));
};
let file_path = crate::resources::resources_dir_path()
.join("resource_protocol")
.join(path);
let file_path = crate::resources::resource_protocol_dir_path().join(path);
if !file_path.exists() || file_path.is_dir() {
return Box::pin(std::future::ready(Response::network_error(

View File

@@ -4,8 +4,6 @@
#![allow(non_snake_case)]
mod resources;
use std::cell::RefCell;
use std::os::raw::{c_char, c_int, c_void};
use std::ptr::NonNull;
@@ -23,7 +21,6 @@ use raw_window_handle::{
AndroidDisplayHandle, AndroidNdkWindowHandle, DisplayHandle, RawDisplayHandle, RawWindowHandle,
WindowHandle,
};
use resources::ResourceReaderInstance;
pub use servo::MediaSessionPlaybackState;
use servo::{
self, DevicePixel, EventLoopWaker, InputMethodControl, LoadStatus, MediaSessionActionType,
@@ -165,7 +162,6 @@ pub extern "C" fn Java_org_servo_servoview_JNIServo_init<'local>(
let host = Rc::new(HostCallbacks::new(callbacks_ref, &env));
crate::init_crypto();
servo::resources::set(Box::new(ResourceReaderInstance::new()));
let (opts, mut preferences, servoshell_preferences) =
match parse_command_line_arguments(init_opts.args.as_slice()) {

View File

@@ -1,47 +0,0 @@
/* 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 servo::resources::{Resource, ResourceReaderMethods};
pub(crate) struct ResourceReaderInstance;
impl ResourceReaderInstance {
pub(crate) fn new() -> ResourceReaderInstance {
ResourceReaderInstance
}
}
impl ResourceReaderMethods for ResourceReaderInstance {
fn read(&self, res: Resource) -> Vec<u8> {
Vec::from(match res {
Resource::HstsPreloadList => {
&include_bytes!("../../../../resources/hsts_preload.fstmap")[..]
},
Resource::BadCertHTML => &include_bytes!("../../../../resources/badcert.html")[..],
Resource::NetErrorHTML => &include_bytes!("../../../../resources/neterror.html")[..],
Resource::BrokenImageIcon => &include_bytes!("../../../../resources/rippy.png")[..],
Resource::DomainList => &include_bytes!("../../../../resources/public_domains.txt")[..],
Resource::BluetoothBlocklist => {
&include_bytes!("../../../../resources/gatt_blocklist.txt")[..]
},
Resource::CrashHTML => &include_bytes!("../../../../resources/crash.html")[..],
Resource::DirectoryListingHTML => {
&include_bytes!("../../../../resources/directory-listing.html")[..]
},
Resource::AboutMemoryHTML => {
&include_bytes!("../../../../resources/about-memory.html")[..]
},
Resource::DebuggerJS => &include_bytes!("../../../../resources/debugger.js")[..],
})
}
fn sandbox_access_files(&self) -> Vec<PathBuf> {
vec![]
}
fn sandbox_access_files_dirs(&self) -> Vec<PathBuf> {
vec![]
}
}

View File

@@ -102,7 +102,6 @@ use xcomponent_sys::{
use super::app::{App, AppInitOptions, EmbeddedPlatformWindow, VsyncRefreshDriver};
use super::host_trait::HostTrait;
use crate::egl::ohos::resources::ResourceReaderInstance;
use crate::prefs::{ArgumentParsingResult, parse_command_line_arguments};
/// Queue length for the thread-safe function to submit URL updates to ArkTS
@@ -209,7 +208,7 @@ fn init_app(
let resource_dir = PathBuf::from(&options.resource_dir).join("servo");
debug!("Resources are located at: {:?}", resource_dir);
servo::resources::set(Box::new(ResourceReaderInstance::new(resource_dir.clone())));
resources::set_resource_dir(resource_dir.clone());
let args = options
.commandline_args

View File

@@ -3,24 +3,37 @@
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
use std::fs;
use std::path::PathBuf;
use std::sync::OnceLock;
use servo::resources::{Resource, ResourceReaderMethods};
pub(crate) struct ResourceReaderInstance {
resource_dir: PathBuf,
pub(crate) struct ResourceReaderImpl {
resource_dir: OnceLock<PathBuf>,
}
impl ResourceReaderInstance {
pub(crate) fn new(resource_dir: PathBuf) -> Self {
assert!(resource_dir.is_dir());
Self { resource_dir }
static RESOURCE_READER: ResourceReaderImpl = ResourceReaderImpl {
resource_dir: OnceLock::new(),
};
servo::submit_resource_reader!(&RESOURCE_READER);
pub(crate) fn set_resource_dir(resource_dir: PathBuf) {
if let Err(e) = RESOURCE_READER.resource_dir.set(resource_dir) {
log::warn!("Failed to set resource dir: {:?}", e);
}
}
impl ResourceReaderMethods for ResourceReaderInstance {
impl ResourceReaderMethods for ResourceReaderImpl {
fn read(&self, res: Resource) -> Vec<u8> {
let file_path = self.resource_dir.join(res.filename());
fs::read(&file_path).expect("failed to read resource file")
let file_path = RESOURCE_READER
.resource_dir
.get()
.expect("Attempted to read resources before reader initialized")
.join("named_resources")
.join(res.filename());
fs::read(&file_path).unwrap_or_else(|e| {
panic!("Failed to read resource file: {:?}: {:?}", file_path, e);
})
}
fn sandbox_access_files(&self) -> Vec<PathBuf> {

View File

@@ -19,7 +19,7 @@ mod egl;
mod panic_hook;
mod parser;
mod prefs;
#[cfg(not(target_os = "android"))]
#[cfg(not(any(target_os = "android", target_env = "ohos")))]
mod resources;
mod running_app_state;
mod webdriver;

View File

@@ -2,22 +2,19 @@
* 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::env;
use std::path::PathBuf;
use std::sync::Mutex;
use std::{env, fs};
use cfg_if::cfg_if;
use servo::resources::{self, Resource};
static CMD_RESOURCE_DIR: Mutex<Option<PathBuf>> = Mutex::new(None);
struct ResourceReader;
pub fn init() {
resources::set(Box::new(ResourceReader));
pub(crate) fn resource_protocol_dir_path() -> PathBuf {
resource_root_dir_path().join("resource_protocol")
}
pub(crate) fn resources_dir_path() -> PathBuf {
fn resource_root_dir_path() -> PathBuf {
// This needs to be called before the process is sandboxed
// as we only give permission to read inside the resources directory,
// not the permissions the "search" for the resources directory.
@@ -27,7 +24,7 @@ pub(crate) fn resources_dir_path() -> PathBuf {
}
// Try ./resources and ./Resources relative to the directory containing the
// canonicalised executable path, then each of its ancestors.
// canonicalized executable path, then each of its ancestors.
let mut path = env::current_exe().unwrap().canonicalize().unwrap();
while path.pop() {
path.push("resources");
@@ -51,7 +48,7 @@ pub(crate) fn resources_dir_path() -> PathBuf {
panic!("Can't find resources directory")
} else {
// Static assert that this is really a non-production build, rather
// than a failure of the build scripts production check.
// than a failure of the build script's production check.
const _: () = assert!(cfg!(servo_do_not_use_in_production));
// Try ./resources in the current directory, then each of its ancestors.
@@ -72,17 +69,3 @@ pub(crate) fn resources_dir_path() -> PathBuf {
}
}
}
impl resources::ResourceReaderMethods for ResourceReader {
fn read(&self, file: Resource) -> Vec<u8> {
let mut path = resources_dir_path();
path.push(file.filename());
fs::read(path).expect("Can't read file")
}
fn sandbox_access_files_dirs(&self) -> Vec<PathBuf> {
vec![resources_dir_path()]
}
fn sandbox_access_files(&self) -> Vec<PathBuf> {
vec![]
}
}

View File

@@ -75,7 +75,7 @@ class MachCommands(CommandBase):
@Command("update-hsts-preload", description="Download the HSTS preload list", category="bootstrap")
def bootstrap_hsts_preload(self, force: bool = False) -> None:
preload_filename = "hsts_preload.fstmap"
preload_path = path.join(self.context.topdir, "resources")
preload_path = path.join(self.context.topdir, "components", "default-resources", "resources")
chromium_hsts_url = (
"https://chromium.googlesource.com/chromium/src"
@@ -108,12 +108,18 @@ class MachCommands(CommandBase):
@Command(
"update-pub-domains",
description="Download the public domains list and update resources/public_domains.txt",
description="Download the public domains list and update the copy in servo-default-resources",
category="bootstrap",
)
def bootstrap_pub_suffix(self, force: bool = False) -> None:
list_url = "https://publicsuffix.org/list/public_suffix_list.dat"
dst_filename = path.join(self.context.topdir, "resources", "public_domains.txt")
dst_filename = path.join(
self.context.topdir,
"components",
"default-resources",
"resources",
"public_domains.txt",
)
not_implemented_case = re.compile(r"^[^*]+\*")
try:

View File

@@ -69,6 +69,16 @@ def check_call_with_randomized_backoff(args: list[str], retries: int) -> int:
return check_call_with_randomized_backoff(args, retries - 1)
def copy_packaged_resources(top_dir: str, destination: str) -> None:
os.makedirs(destination, exist_ok=True)
shutil.copytree(path.join(top_dir, "resources"), destination, dirs_exist_ok=True)
shutil.copytree(
path.join(top_dir, "components", "default-resources", "resources"),
path.join(destination, "named_resources"),
dirs_exist_ok=True,
)
@CommandProvider
class PackageCommands(CommandBase):
@staticmethod
@@ -176,7 +186,7 @@ class PackageCommands(CommandBase):
if path.exists(dir_to_resources):
delete(dir_to_resources)
shutil.copytree(path.join(dir_to_root, "resources"), dir_to_resources)
copy_packaged_resources(dir_to_root, dir_to_resources)
variant = ":assemble" + flavor_name + arch_string + build_type_string
apk_task_name = ":servoapp" + variant
@@ -198,10 +208,8 @@ class PackageCommands(CommandBase):
print("Cleaning up from previous packaging")
delete(ohos_target_dir)
shutil.copytree(ohos_app_dir, ohos_target_dir)
resources_src_dir = path.join(self.get_top_dir(), "resources")
resources_app_dir = path.join(ohos_target_dir, "AppScope", "resources", "resfile", "servo")
os.makedirs(resources_app_dir, exist_ok=True)
shutil.copytree(resources_src_dir, resources_app_dir, dirs_exist_ok=True)
copy_packaged_resources(self.get_top_dir(), str(resources_app_dir))
# Map non-debug profiles to 'release' buildMode HAP.
if build_type.is_custom():
@@ -273,7 +281,7 @@ class PackageCommands(CommandBase):
delete(dir_to_dmg)
print("Copying files")
shutil.copytree(path.join(dir_to_root, "resources"), dir_to_resources)
copy_packaged_resources(dir_to_root, dir_to_resources)
shutil.copy2(
path.join(dir_to_root, "ports/servoshell/platform/macos/Info.plist"),
path.join(dir_to_app, "Contents", "Info.plist"),
@@ -349,7 +357,7 @@ class PackageCommands(CommandBase):
print("Copying files")
dir_to_temp = path.join(dir_to_msi, "temp")
dir_to_resources = path.join(dir_to_temp, "resources")
shutil.copytree(path.join(dir_to_root, "resources"), dir_to_resources)
copy_packaged_resources(dir_to_root, dir_to_resources)
shutil.copy(binary_path, dir_to_temp)
copy_windows_dependencies(target_dir, dir_to_temp)
@@ -417,7 +425,7 @@ class PackageCommands(CommandBase):
print("Copying files")
dir_to_resources = path.join(dir_to_temp, "resources")
shutil.copytree(path.join(dir_to_root, "resources"), dir_to_resources)
copy_packaged_resources(dir_to_root, dir_to_resources)
shutil.copy(binary_path, dir_to_temp)
print("Creating tarball")

View File

@@ -5,7 +5,7 @@ skip-check-licenses = false
# Files that are ignored for all tidy and lint checks.
files = [
"./components/shared/net/tests/parsable_mime/text",
"./resources/hsts_preload.fstmap",
"./components/default-resources/resources/hsts_preload.fstmap",
"./tests/wpt/meta/MANIFEST.json",
"./tests/wpt/mozilla/meta/MANIFEST.json",
# Long encoded string