Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 27 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions moli-dom/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ percent-encoding = "2.3"
selectors = "0.40"
serde_json = "1.0.145"
servo_arc = "0.4.3"
smol_str = "0.3.6"
thin-vec = "0.2.14"
url = "2.5.7"

Expand Down
5 changes: 5 additions & 0 deletions moli-dom/src/native/host/mutation/tree.rs
Original file line number Diff line number Diff line change
Expand Up @@ -544,6 +544,11 @@ impl DomHost {
self.dom.text_content(handle)
}

pub fn shared_text_content(&self, handle: DomHandle) -> Option<std::sync::Arc<str>> {
self.node(handle)
.map(|node| node.shared_text_content(&self.dom))
}

pub fn inner_html(&self, handle: DomHandle) -> Option<String> {
self.dom.inner_html(handle)
}
Expand Down
4 changes: 2 additions & 2 deletions moli-dom/src/native/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -402,7 +402,7 @@ impl NativeDom {
data: &str,
) -> NativeNodeId {
self.create_node(
NodeData::Text(Text::new(data.to_owned())),
NodeData::Text(Text::new(data)),
Some(owner_document),
false,
false,
Expand Down Expand Up @@ -908,7 +908,7 @@ mod tests {
"DocumentType grew to {} bytes",
size_of::<DocumentType>()
);
assert_eq!(size_of::<Text>(), 16);
assert_eq!(size_of::<Text>(), 24);
assert_eq!(size_of::<CDataSection>(), 16);
assert_eq!(size_of::<Comment>(), 16);
assert_eq!(size_of::<ProcessingInstruction>(), 32);
Expand Down
37 changes: 37 additions & 0 deletions moli-dom/src/native/node/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ pub use types::{CDataSection, Comment, ProcessingInstruction, Text};

use std::fmt;
use std::num::NonZeroU32;
use std::sync::Arc;

use super::NativeDom;
use super::element::Element;
Expand Down Expand Up @@ -509,6 +510,42 @@ impl Node {
})
}

pub(crate) fn shared_text_content(&self, dom: &NativeDom) -> Arc<str> {
match self.data() {
NodeData::Text(text) => return text.shared_data(),
NodeData::CDataSection(cdata) => return Arc::from(cdata.data()),
NodeData::Comment(comment) => return Arc::from(comment.data()),
NodeData::ProcessingInstruction(processing_instruction) => {
return Arc::from(processing_instruction.data());
}
NodeData::DocumentType(_) => return Arc::from(""),
NodeData::Document(_) | NodeData::Element(_) | NodeData::DocumentFragment(_) => {}
}

let mut only_text = None;
let mut stack = dom.child_ids_reversed(self.id()).collect::<Vec<_>>();
while let Some(node_id) = stack.pop() {
let Some(node) = dom.node(node_id) else {
continue;
};
match node.data() {
NodeData::Text(text) if only_text.is_none() => only_text = Some(text),
NodeData::Text(_) | NodeData::CDataSection(_) => {
return Arc::from(self.text_content(dom));
}
NodeData::Document(_) | NodeData::Element(_) | NodeData::DocumentFragment(_) => {
stack.extend(dom.child_ids_reversed(node_id));
}
NodeData::Comment(_)
| NodeData::ProcessingInstruction(_)
| NodeData::DocumentType(_) => {}
}
}
only_text
.map(Text::shared_data)
.unwrap_or_else(|| Arc::from(""))
}

pub fn metadata(&self) -> LiveDomNodeMetadata {
match self.data() {
NodeData::Document(_) => LiveDomNodeMetadata {
Expand Down
43 changes: 35 additions & 8 deletions moli-dom/src/native/node/types.rs
Original file line number Diff line number Diff line change
@@ -1,21 +1,48 @@
use smol_str::SmolStr;
use std::sync::Arc;

#[derive(Debug, Clone)]
pub struct Text {
data: Box<str>,
data: SmolStr,
}

impl Text {
pub fn new(data: String) -> Self {
Self {
data: data.into_boxed_str(),
}
pub fn new(data: impl Into<SmolStr>) -> Self {
Self { data: data.into() }
}

pub fn data(&self) -> &str {
&self.data
self.data.as_str()
}

pub fn set_data(&mut self, data: impl Into<String>) {
self.data = data.into().into_boxed_str();
pub(crate) fn shared_data(&self) -> Arc<str> {
Arc::from(self.data.clone())
}

pub fn set_data(&mut self, data: impl Into<Arc<str>>) {
self.data = SmolStr::from(data.into());
}
}

#[cfg(test)]
mod tests {
use super::Text;
use std::sync::Arc;

#[test]
fn text_inlines_short_data() {
let text = Text::new("short text");

assert!(!text.data.is_heap_allocated());
}

#[test]
fn text_shares_long_data_when_requested() {
let text = Text::new("x".repeat(64));

let first = text.shared_data();
let second = text.shared_data();
assert!(Arc::ptr_eq(&first, &second));
}
}

Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
use std::collections::HashSet;
use std::{collections::HashSet, sync::Arc};

use super::{
JsContextHost,
Expand Down Expand Up @@ -873,13 +873,13 @@ impl JsContextHost {
}

pub(crate) fn sync_owner_style_sheet_text(&mut self, owner: DomHandle) {
let css_text = self.dom_host().text_content(owner).unwrap_or_default();
let css_text = self
.dom_host()
.shared_text_content(owner)
.unwrap_or_else(|| Arc::from(""));
let dom_host = self.dom_host() as *const _;
self.style_engine.sync_owner_style_sheet_text_with_host(
unsafe { &*dom_host },
owner,
css_text,
);
self.style_engine
.sync_owner_style_sheet_text_backing_with_host(unsafe { &*dom_host }, owner, css_text);
self.install_owner_live_stylesheet(owner);
}

Expand Down
25 changes: 18 additions & 7 deletions moli-renderer-v8/src/style_engine/source/mod.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
use std::collections::HashSet;
use std::{collections::HashSet, sync::Arc};

pub(super) mod adopted;
pub(super) mod imports;
Expand Down Expand Up @@ -160,21 +160,31 @@ impl MoliStyleEngine {
.tracks_document(document)
}

#[cfg(test)]
pub(crate) fn set_owner_style_sheet_text_with_host(
&mut self,
host: &DomHost,
owner: DomHandle,
css_text: String,
) {
self.set_owner_style_sheet_text_backing_with_host(host, owner, css_text.into());
}

fn set_owner_style_sheet_text_backing_with_host(
&mut self,
host: &DomHost,
owner: DomHandle,
css_text: Arc<str>,
) {
let parser_base = stylesheet_source_base_url(host, owner);
self.set_owner_style_sheet_source_with_parser_base(host, owner, css_text, parser_base);
}

pub(crate) fn sync_owner_style_sheet_text_with_host(
pub(crate) fn sync_owner_style_sheet_text_backing_with_host(
&mut self,
host: &DomHost,
owner: DomHandle,
css_text: String,
css_text: Arc<str>,
) {
if self.owner_document_world(host, owner).is_some_and(|world| {
world
Expand All @@ -184,14 +194,14 @@ impl MoliStyleEngine {
}) {
return;
}
self.set_owner_style_sheet_text_with_host(host, owner, css_text);
self.set_owner_style_sheet_text_backing_with_host(host, owner, css_text);
}

fn process_owner_style_sheet_text_with_host(
&mut self,
host: &DomHost,
owner: DomHandle,
css_text: String,
css_text: Arc<str>,
) {
let Some(document) = owner_document_for_source_owner(host, owner) else {
return;
Expand All @@ -214,7 +224,7 @@ impl MoliStyleEngine {
&mut self,
host: &DomHost,
owner: DomHandle,
css_text: String,
css_text: Arc<str>,
parser_base: url::Url,
) {
let Some(document) = owner_document_for_source_owner(host, owner) else {
Expand Down Expand Up @@ -518,7 +528,8 @@ impl MoliStyleEngine {
self.process_owner_style_sheet_text_with_host(
host,
owner,
host.text_content(owner).unwrap_or_default(),
host.shared_text_content(owner)
.unwrap_or_else(|| Arc::from("")),
);
} else if matches!(
change.kind(),
Expand Down
19 changes: 18 additions & 1 deletion moli-renderer-v8/src/style_engine/source/shared_cache.rs
Original file line number Diff line number Diff line change
Expand Up @@ -49,13 +49,19 @@ struct SharedStyleSourceCache {
pub(super) fn shared_style_source_contents(
css_text: String,
base_url: url::Url,
) -> Arc<SharedStyleSourceContents> {
shared_style_source_contents_from_shared(css_text.into(), base_url)
}

pub(super) fn shared_style_source_contents_from_shared(
css_text: Arc<str>,
base_url: url::Url,
) -> Arc<SharedStyleSourceContents> {
let key = SharedStyleSourceCacheKey::new(&css_text, &base_url);
if let Some(cached) = CACHE.lock().lookup(&key, &css_text, &base_url) {
return cached;
}

let css_text = Arc::<str>::from(css_text);
let metadata = style_source_metadata_for_css_text(&css_text, &base_url);
let source = Arc::new(SharedStyleSourceContents {
source_metadata: SharedStyleSourceMetadata::from_metadata(
Expand Down Expand Up @@ -288,6 +294,17 @@ mod tests {
assert_eq!(cache.retained_bytes, 0);
}

#[test]
fn shared_input_becomes_the_retained_css_text_backing() {
let css_text = Arc::<str>::from(".shared-input { color: green; }");
let source = shared_style_source_contents_from_shared(
Arc::clone(&css_text),
url::Url::parse("https://shared-input.test/style.css").expect("valid base URL"),
);

assert!(Arc::ptr_eq(&css_text, &source.css_text_handle()));
}

#[test]
fn weak_cache_evicts_oldest_index_to_byte_budget() {
let mut cache = SharedStyleSourceCache::with_retained_bytes_limit(2 * ENTRY_RETAINED_BYTES);
Expand Down
28 changes: 27 additions & 1 deletion moli-renderer-v8/src/style_engine/source/store.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,10 @@ use super::super::{
source_key::{StyleSourceKey, StyleSourceSetKey},
};
use super::imports::stylesheet_top_level_import_urls;
use super::shared_cache::{SharedStyleSourceContents, shared_style_source_contents};
use super::shared_cache::{
SharedStyleSourceContents, shared_style_source_contents,
shared_style_source_contents_from_shared,
};
use crate::{
document_runtime::DomHandle, protocol_types::EmulatedMediaOverrides,
style_engine::StyleViewport,
Expand Down Expand Up @@ -92,6 +95,15 @@ pub(crate) struct OwnerStyleSheetSource {
impl StyloStylesheetSource {
pub(crate) fn new(css_text: String, base_url: url::Url) -> Self {
let shared = shared_style_source_contents(css_text, base_url);
Self::from_shared_contents(shared)
}

fn from_shared_text(css_text: StdArc<str>, base_url: url::Url) -> Self {
let shared = shared_style_source_contents_from_shared(css_text, base_url);
Self::from_shared_contents(shared)
}

fn from_shared_contents(shared: StdArc<SharedStyleSourceContents>) -> Self {
let cache_key =
StyleSourceKey::from_css_fingerprint(shared.css_fingerprint(), shared.base_url());
let base_url = shared.base_url_handle();
Expand Down Expand Up @@ -542,8 +554,22 @@ impl StylesheetFontFaceDescriptor {
}

impl OwnerStyleSheetSource {
#[cfg(test)]
pub(crate) fn new(owner: DomHandle, css_text: String, parser_base: url::Url) -> Self {
let source = StyloStylesheetSource::new(css_text, parser_base);
Self::from_source(owner, source)
}

pub(crate) fn from_shared_text(
owner: DomHandle,
css_text: StdArc<str>,
parser_base: url::Url,
) -> Self {
let source = StyloStylesheetSource::from_shared_text(css_text, parser_base);
Self::from_source(owner, source)
}

fn from_source(owner: DomHandle, source: StyloStylesheetSource) -> Self {
let processing_contents = source
.processing_contents()
.expect("owner processing source must remain text-backed");
Expand Down
Loading
Loading