servo/components/layout/construct_modern.rs
Narfinger e64f021550
Allow WebViews and fonts to have a RenderingGroupId. (#39140)
Motivation: The font cache currently has to store a cache of Keys which
need to be given by the webrender instance.
Having a cache for every WebViewId in the future when we have every
webview have the different webrender::DocumentId might be too wasteful
to store this key cache per DocumentId. This proposes to include in the
WebViewId another id, the RenderingGroupId. This id can be easily
changed
to be equivalent to the DocumentId when we support multiple DocumentIds
for a unique Webrender instance.
Additionally this will keep it easier to integrate the currently out of
tree patches for multiple rendering contexts with different webrenders.


Change:
We introduce the RenderingGroupId in the WebViewId and allow a method to
extract it. The font key cache uses this cache
and forwards it to the Compositor when requesting new changes. The
compositor currently ignores this id.
Additionally, the WebView can return the RenderingGroupId. The WebViewId
also has an appropiate constructor for specifying a RenderingGroupId.
Because there currently will be only one RenderingGroupId the
performance will be minimal.


Signed-off-by: Narfinger <Narfinger@users.noreply.github.com>

Testing: This should be covered by WPT tests and normal browsing
behavior works fine.

---------

Signed-off-by: Narfinger <Narfinger@users.noreply.github.com>
2025-09-29 10:01:56 +00:00

252 lines
8.8 KiB
Rust

/* 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/. */
//! Layout construction code that is shared between modern layout modes (Flexbox and CSS Grid)
use std::borrow::Cow;
use std::sync::LazyLock;
use rayon::iter::{IntoParallelIterator, ParallelIterator};
use style::selector_parser::PseudoElement;
use crate::PropagatedBoxTreeData;
use crate::context::LayoutContext;
use crate::dom::{BoxSlot, LayoutBox};
use crate::dom_traversal::{Contents, NodeAndStyleInfo, TraversalHandler};
use crate::flow::inline::construct::InlineFormattingContextBuilder;
use crate::flow::{BlockContainer, BlockFormattingContext};
use crate::formatting_contexts::{
IndependentFormattingContext, IndependentFormattingContextContents,
};
use crate::layout_box_base::LayoutBoxBase;
use crate::style_ext::{ComputedValuesExt, DisplayGeneratingBox};
/// A builder used for both flex and grid containers.
pub(crate) struct ModernContainerBuilder<'a, 'dom> {
context: &'a LayoutContext<'a>,
info: &'a NodeAndStyleInfo<'dom>,
propagated_data: PropagatedBoxTreeData,
contiguous_text_runs: Vec<ModernContainerTextRun<'dom>>,
/// To be run in parallel with rayon in `finish`
jobs: Vec<ModernContainerJob<'dom>>,
has_text_runs: bool,
}
enum ModernContainerJob<'dom> {
ElementOrPseudoElement {
info: NodeAndStyleInfo<'dom>,
display: DisplayGeneratingBox,
contents: Contents,
box_slot: BoxSlot<'dom>,
},
TextRuns(Vec<ModernContainerTextRun<'dom>>),
}
impl<'dom> ModernContainerJob<'dom> {
fn finish(
self,
builder: &ModernContainerBuilder,
anonymous_info: &LazyLock<NodeAndStyleInfo<'dom>, impl FnOnce() -> NodeAndStyleInfo<'dom>>,
) -> Option<ModernItem<'dom>> {
match self {
ModernContainerJob::TextRuns(runs) => {
let mut inline_formatting_context_builder =
InlineFormattingContextBuilder::new(builder.info);
for flex_text_run in runs.into_iter() {
inline_formatting_context_builder
.push_text(flex_text_run.text, &flex_text_run.info);
}
let inline_formatting_context = inline_formatting_context_builder.finish(
builder.context,
true, /* has_first_formatted_line */
false, /* is_single_line_text_box */
builder.info.style.to_bidi_level(),
builder.context.rendering_group_id,
)?;
let block_formatting_context = BlockFormattingContext::from_block_container(
BlockContainer::InlineFormattingContext(inline_formatting_context),
);
let info: &NodeAndStyleInfo = anonymous_info;
let formatting_context = IndependentFormattingContext::new(
LayoutBoxBase::new(info.into(), info.style.clone()),
IndependentFormattingContextContents::Flow(block_formatting_context),
);
Some(ModernItem {
kind: ModernItemKind::InFlow(formatting_context),
order: 0,
box_slot: None,
})
},
ModernContainerJob::ElementOrPseudoElement {
info,
display,
contents,
box_slot,
} => {
let is_abspos = info.style.get_box().position.is_absolutely_positioned();
let order = if is_abspos {
0
} else {
info.style.clone_order()
};
if let Some(layout_box) = box_slot
.take_layout_box_if_undamaged(info.damage)
.and_then(|layout_box| match &layout_box {
LayoutBox::FlexLevel(_) | LayoutBox::TaffyItemBox(_) => Some(layout_box),
_ => None,
})
{
return Some(ModernItem {
kind: ModernItemKind::ReusedBox(layout_box),
order,
box_slot: Some(box_slot),
});
}
// Text decorations are not propagated to any out-of-flow descendants. In addition,
// absolutes don't affect the size of ancestors so it is fine to allow descendent
// tables to resolve percentage columns.
let propagated_data = match is_abspos {
false => builder.propagated_data,
true => PropagatedBoxTreeData::default(),
};
let formatting_context = IndependentFormattingContext::construct(
builder.context,
&info,
display.display_inside(),
contents,
propagated_data,
);
let kind = if is_abspos {
ModernItemKind::OutOfFlow(formatting_context)
} else {
ModernItemKind::InFlow(formatting_context)
};
Some(ModernItem {
kind,
order,
box_slot: Some(box_slot),
})
},
}
}
}
struct ModernContainerTextRun<'dom> {
info: NodeAndStyleInfo<'dom>,
text: Cow<'dom, str>,
}
impl ModernContainerTextRun<'_> {
/// <https://drafts.csswg.org/css-text/#white-space>
fn is_only_document_white_space(&self) -> bool {
// FIXME: is this the right definition? See
// https://github.com/w3c/csswg-drafts/issues/5146
// https://github.com/w3c/csswg-drafts/issues/5147
self.text
.bytes()
.all(|byte| matches!(byte, b' ' | b'\n' | b'\t'))
}
}
pub(crate) enum ModernItemKind {
InFlow(IndependentFormattingContext),
OutOfFlow(IndependentFormattingContext),
ReusedBox(LayoutBox),
}
pub(crate) struct ModernItem<'dom> {
pub kind: ModernItemKind,
pub order: i32,
pub box_slot: Option<BoxSlot<'dom>>,
}
impl<'dom> TraversalHandler<'dom> for ModernContainerBuilder<'_, 'dom> {
fn handle_text(&mut self, info: &NodeAndStyleInfo<'dom>, text: Cow<'dom, str>) {
self.contiguous_text_runs.push(ModernContainerTextRun {
info: info.clone(),
text,
})
}
/// Or pseudo-element
fn handle_element(
&mut self,
info: &NodeAndStyleInfo<'dom>,
display: DisplayGeneratingBox,
contents: Contents,
box_slot: BoxSlot<'dom>,
) {
self.wrap_any_text_in_anonymous_block_container();
self.jobs.push(ModernContainerJob::ElementOrPseudoElement {
info: info.clone(),
display,
contents,
box_slot,
})
}
}
impl<'a, 'dom> ModernContainerBuilder<'a, 'dom> {
pub fn new(
context: &'a LayoutContext<'a>,
info: &'a NodeAndStyleInfo<'dom>,
propagated_data: PropagatedBoxTreeData,
) -> Self {
ModernContainerBuilder {
context,
info,
propagated_data: propagated_data.disallowing_percentage_table_columns(),
contiguous_text_runs: Vec::new(),
jobs: Vec::new(),
has_text_runs: false,
}
}
fn wrap_any_text_in_anonymous_block_container(&mut self) {
let runs = std::mem::take(&mut self.contiguous_text_runs);
if runs
.iter()
.all(ModernContainerTextRun::is_only_document_white_space)
{
// There is no text run, or they all only contain document white space characters
} else {
self.jobs.push(ModernContainerJob::TextRuns(runs));
self.has_text_runs = true;
}
}
pub(crate) fn finish(mut self) -> Vec<ModernItem<'dom>> {
self.wrap_any_text_in_anonymous_block_container();
let anonymous_info = LazyLock::new(|| {
self.info
.with_pseudo_element(self.context, PseudoElement::ServoAnonymousBox)
.expect("Should always be able to construct info for anonymous boxes.")
});
let jobs = std::mem::take(&mut self.jobs);
let mut children: Vec<_> = if self.context.use_rayon {
jobs.into_par_iter()
.filter_map(|job| job.finish(&self, &anonymous_info))
.collect()
} else {
jobs.into_iter()
.filter_map(|job| job.finish(&self, &anonymous_info))
.collect()
};
// https://drafts.csswg.org/css-flexbox/#order-modified-document-order
children.sort_by_key(|child| child.order);
children
}
}