mirror of
https://github.com/servo/servo
synced 2026-04-25 17:15:48 +02:00
script: Rewrite the layout DOM wrappers (#44114)
This change reworks the layout DOM wrappers so that they are simpler and easier to reason about. The main changes here: **Combine layout wrappers into one interface:** - `LayoutNode`/`ThreadSafeLayoutNode` is combined into `LayoutNode`: The idea here is that `LayoutNode` is always thread-safe when used in layout as long as no `unsafe` calls are used. These interfaces only expose what is necessary for layout. - `LayoutElement`/`ThreadSafeLayoutElement` is combined into `LayoutElement`: See above. **Expose two new interfaces to be used *only* with `stylo` and `selectors`:** `DangerousStyleNode` and `DangerousStyleElement`. `stylo` and `selectors` have a different way of ensuring safety that is incompatible with Servo's layout (access all of the DOM tree anywhere, but ensure that writing only happens from a single-thread). These types only implement things like `TElement`, `TNode` and are not intended to be used by layout at all. All traits and implementations are moved to files that are named after the struct or trait inside them, in order to better understand what one is looking at. The main goals here are: - Make it easier to reason about the safe use of the DOM APIs. - Remove the interdependencies between the `stylo` and `selectors` interface implementations and the layout interface. This helps with the first point as well and makes it simpler to know where a method is implemented. - Reduce the amount of code. - Make it possible to eliminate `TrustedNodeAddress` in the future. - Document and bring the method naming up to modern Rust conventions. This is a lot of code changes, but is very well tested by the WPT tests. Unfortunately, it is difficult to make a change like this iteratively. In addition, this new design comes with new documentation at servo/book#225. Testing: This should not change behavior so should be covered by existing WPT tests. Signed-off-by: Martin Robinson <mrobinson@fastmail.fm>
This commit is contained in:
@@ -8,17 +8,16 @@ use std::sync::Arc;
|
||||
use embedder_traits::UntrustedNodeAddress;
|
||||
use euclid::Size2D;
|
||||
use fonts::FontContext;
|
||||
use layout_api::wrapper_traits::ThreadSafeLayoutNode;
|
||||
use layout_api::{
|
||||
AnimatingImages, IFrameSizes, LayoutImageDestination, PendingImage, PendingImageState,
|
||||
PendingRasterizationImage,
|
||||
AnimatingImages, IFrameSizes, LayoutImageDestination, LayoutNode, PendingImage,
|
||||
PendingImageState, PendingRasterizationImage,
|
||||
};
|
||||
use net_traits::image_cache::{
|
||||
Image as CachedImage, ImageCache, ImageCacheResult, ImageOrMetadataAvailable, PendingImageId,
|
||||
};
|
||||
use parking_lot::{Mutex, RwLock};
|
||||
use pixels::RasterImage;
|
||||
use script::layout_dom::ServoThreadSafeLayoutNode;
|
||||
use script::layout_dom::ServoLayoutNode;
|
||||
use servo_base::id::PainterId;
|
||||
use servo_url::{ImmutableOrigin, ServoUrl};
|
||||
use style::context::SharedStyleContext;
|
||||
@@ -238,10 +237,7 @@ impl ImageResolver {
|
||||
result
|
||||
}
|
||||
|
||||
pub(crate) fn queue_svg_element_for_serialization(
|
||||
&self,
|
||||
element: ServoThreadSafeLayoutNode<'_>,
|
||||
) {
|
||||
pub(crate) fn queue_svg_element_for_serialization(&self, element: ServoLayoutNode<'_>) {
|
||||
self.pending_svg_elements_for_serialization
|
||||
.lock()
|
||||
.push(element.opaque().into())
|
||||
|
||||
@@ -6,13 +6,12 @@ use std::marker::PhantomData;
|
||||
|
||||
use atomic_refcell::{AtomicRef, AtomicRefCell, AtomicRefMut};
|
||||
use html5ever::{local_name, ns};
|
||||
use layout_api::wrapper_traits::{LayoutDataTrait, ThreadSafeLayoutElement, ThreadSafeLayoutNode};
|
||||
use layout_api::{
|
||||
GenericLayoutDataTrait, LayoutElementType, LayoutNodeType as ScriptLayoutNodeType,
|
||||
SVGElementData,
|
||||
GenericLayoutDataTrait, LayoutDataTrait, LayoutElement, LayoutElementType, LayoutNode,
|
||||
LayoutNodeType as ScriptLayoutNodeType, SVGElementData,
|
||||
};
|
||||
use malloc_size_of_derive::MallocSizeOf;
|
||||
use script::layout_dom::ServoThreadSafeLayoutNode;
|
||||
use script::layout_dom::ServoLayoutNode;
|
||||
use servo_arc::Arc as ServoArc;
|
||||
use smallvec::SmallVec;
|
||||
use style::context::SharedStyleContext;
|
||||
@@ -82,7 +81,7 @@ impl InnerDOMLayoutData {
|
||||
.unwrap_or_default()
|
||||
}
|
||||
|
||||
fn repair_style(&self, node: &ServoThreadSafeLayoutNode, context: &SharedStyleContext) {
|
||||
fn repair_style(&self, node: &ServoLayoutNode, context: &SharedStyleContext) {
|
||||
if let Some(layout_object) = &*self.self_box.borrow() {
|
||||
layout_object.repair_style(context, node, &node.style(context));
|
||||
}
|
||||
@@ -161,7 +160,7 @@ impl LayoutBox {
|
||||
fn repair_style(
|
||||
&self,
|
||||
context: &SharedStyleContext,
|
||||
node: &ServoThreadSafeLayoutNode,
|
||||
node: &ServoLayoutNode,
|
||||
new_style: &ServoArc<ComputedValues>,
|
||||
) {
|
||||
match self {
|
||||
@@ -350,7 +349,7 @@ pub(crate) trait NodeExt<'dom> {
|
||||
) -> bool;
|
||||
}
|
||||
|
||||
impl<'dom> NodeExt<'dom> for ServoThreadSafeLayoutNode<'dom> {
|
||||
impl<'dom> NodeExt<'dom> for ServoLayoutNode<'dom> {
|
||||
fn as_image(&self) -> Option<(ImageInfo, PhysicalSize<f64>)> {
|
||||
let (resource, metadata) = self.image_data()?;
|
||||
let width = metadata.map(|metadata| metadata.width).unwrap_or_default();
|
||||
@@ -423,11 +422,11 @@ impl<'dom> NodeExt<'dom> for ServoThreadSafeLayoutNode<'dom> {
|
||||
// supports any `<object>` that's an image, it should support those with URLs
|
||||
// and `type` attributes with image mime types.
|
||||
let element = self.as_element()?;
|
||||
if element.get_attr(&ns!(), &local_name!("type")).is_some() {
|
||||
if element.attribute(&ns!(), &local_name!("type")).is_some() {
|
||||
return None;
|
||||
}
|
||||
element
|
||||
.get_attr(&ns!(), &local_name!("data"))
|
||||
.attribute_as_str(&ns!(), &local_name!("data"))
|
||||
.map(|string| string.to_owned())
|
||||
}
|
||||
|
||||
|
||||
@@ -5,12 +5,10 @@
|
||||
use std::borrow::Cow;
|
||||
|
||||
use html5ever::LocalName;
|
||||
use layout_api::wrapper_traits::{
|
||||
PseudoElementChain, ThreadSafeLayoutElement, ThreadSafeLayoutNode,
|
||||
use layout_api::{
|
||||
LayoutElement, LayoutElementType, LayoutNode, LayoutNodeType, PseudoElementChain,
|
||||
};
|
||||
use layout_api::{LayoutElementType, LayoutNodeType};
|
||||
use script::layout_dom::ServoThreadSafeLayoutNode;
|
||||
use selectors::Element as SelectorsElement;
|
||||
use script::layout_dom::ServoLayoutNode;
|
||||
use servo_arc::Arc as ServoArc;
|
||||
use style::dom::NodeInfo;
|
||||
use style::properties::ComputedValues;
|
||||
@@ -30,15 +28,12 @@ use crate::style_ext::{Display, DisplayGeneratingBox, DisplayInside, DisplayOuts
|
||||
/// avoid having to repeat the same arguments in argument lists.
|
||||
#[derive(Clone)]
|
||||
pub(crate) struct NodeAndStyleInfo<'dom> {
|
||||
pub node: ServoThreadSafeLayoutNode<'dom>,
|
||||
pub node: ServoLayoutNode<'dom>,
|
||||
pub style: ServoArc<ComputedValues>,
|
||||
}
|
||||
|
||||
impl<'dom> NodeAndStyleInfo<'dom> {
|
||||
pub(crate) fn new(
|
||||
node: ServoThreadSafeLayoutNode<'dom>,
|
||||
style: ServoArc<ComputedValues>,
|
||||
) -> Self {
|
||||
pub(crate) fn new(node: ServoLayoutNode<'dom>, style: ServoArc<ComputedValues>) -> Self {
|
||||
Self { node, style }
|
||||
}
|
||||
|
||||
@@ -122,7 +117,7 @@ fn traverse_children_of<'dom>(
|
||||
traverse_eager_pseudo_element(PseudoElement::Before, parent_element_info, context, handler);
|
||||
}
|
||||
|
||||
for child in parent_element_info.node.children() {
|
||||
for child in parent_element_info.node.flat_tree_children() {
|
||||
if child.is_text_node() {
|
||||
let info = NodeAndStyleInfo::new(child, child.style(&context.style_context));
|
||||
handler.handle_text(&info, child.text_content());
|
||||
@@ -137,7 +132,7 @@ fn traverse_children_of<'dom>(
|
||||
}
|
||||
|
||||
fn traverse_element<'dom>(
|
||||
element: ServoThreadSafeLayoutNode<'dom>,
|
||||
element: ServoLayoutNode<'dom>,
|
||||
context: &LayoutContext,
|
||||
handler: &mut impl TraversalHandler<'dom>,
|
||||
) {
|
||||
@@ -255,10 +250,7 @@ impl Contents {
|
||||
matches!(self, Contents::Replaced(_))
|
||||
}
|
||||
|
||||
pub(crate) fn for_element(
|
||||
node: ServoThreadSafeLayoutNode<'_>,
|
||||
context: &LayoutContext,
|
||||
) -> Self {
|
||||
pub(crate) fn for_element(node: ServoLayoutNode<'_>, context: &LayoutContext) -> Self {
|
||||
if let Some(replaced) = ReplacedContents::for_element(node, context) {
|
||||
return Self::Replaced(replaced);
|
||||
}
|
||||
@@ -358,7 +350,7 @@ pub(crate) fn generate_pseudo_element_content(
|
||||
.node
|
||||
.set_uses_content_attribute_with_attr(true);
|
||||
let attr_val =
|
||||
element.get_attr(&attr.namespace_url, &LocalName::from(attr_name));
|
||||
element.attribute(&attr.namespace_url, &LocalName::from(attr_name));
|
||||
vec.push(PseudoElementContentItem::Text(
|
||||
attr_val.map_or("".to_string(), |s| s.to_string()),
|
||||
));
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
|
||||
use geom::{FlexAxis, MainStartCrossStart};
|
||||
use malloc_size_of_derive::MallocSizeOf;
|
||||
use script::layout_dom::ServoThreadSafeLayoutNode;
|
||||
use script::layout_dom::ServoLayoutNode;
|
||||
use servo_arc::Arc as ServoArc;
|
||||
use style::context::SharedStyleContext;
|
||||
use style::logical_geometry::WritingMode;
|
||||
@@ -161,7 +161,7 @@ impl FlexLevelBox {
|
||||
pub(crate) fn repair_style(
|
||||
&mut self,
|
||||
context: &SharedStyleContext,
|
||||
node: &ServoThreadSafeLayoutNode,
|
||||
node: &ServoLayoutNode,
|
||||
new_style: &ServoArc<ComputedValues>,
|
||||
) {
|
||||
match self {
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
|
||||
use std::borrow::Cow;
|
||||
|
||||
use layout_api::LayoutNode;
|
||||
use rayon::iter::{IntoParallelIterator, ParallelIterator};
|
||||
use servo_arc::Arc;
|
||||
use style::properties::ComputedValues;
|
||||
|
||||
@@ -7,7 +7,7 @@ use std::char::{ToLowercase, ToUppercase};
|
||||
use std::ops::Range;
|
||||
|
||||
use icu_segmenter::WordSegmenter;
|
||||
use layout_api::wrapper_traits::{SharedSelection, ThreadSafeLayoutNode};
|
||||
use layout_api::{LayoutNode, SharedSelection};
|
||||
use style::computed_values::_webkit_text_security::T as WebKitTextSecurity;
|
||||
use style::computed_values::white_space_collapse::T as WhiteSpaceCollapse;
|
||||
use style::selector_parser::PseudoElement;
|
||||
|
||||
@@ -7,8 +7,9 @@ use std::vec::IntoIter;
|
||||
|
||||
use app_units::Au;
|
||||
use fonts::{FontMetrics, FontRef};
|
||||
use layout_api::LayoutNode;
|
||||
use malloc_size_of_derive::MallocSizeOf;
|
||||
use script::layout_dom::ServoThreadSafeLayoutNode;
|
||||
use script::layout_dom::ServoLayoutNode;
|
||||
use servo_arc::Arc as ServoArc;
|
||||
use style::context::SharedStyleContext;
|
||||
use style::properties::ComputedValues;
|
||||
@@ -57,7 +58,7 @@ impl InlineBox {
|
||||
pub(crate) fn repair_style(
|
||||
&mut self,
|
||||
context: &SharedStyleContext,
|
||||
node: &ServoThreadSafeLayoutNode,
|
||||
node: &ServoLayoutNode,
|
||||
new_style: &ServoArc<ComputedValues>,
|
||||
) {
|
||||
self.base.repair_style(new_style);
|
||||
|
||||
@@ -9,7 +9,7 @@ use app_units::Au;
|
||||
use bitflags::bitflags;
|
||||
use fonts::GlyphStore;
|
||||
use itertools::Either;
|
||||
use layout_api::wrapper_traits::SharedSelection;
|
||||
use layout_api::SharedSelection;
|
||||
use malloc_size_of_derive::MallocSizeOf;
|
||||
use style::Zero;
|
||||
use style::computed_values::position::T as Position;
|
||||
|
||||
@@ -87,14 +87,14 @@ use icu_locid::LanguageIdentifier;
|
||||
use icu_locid::subtags::{Language, language};
|
||||
use icu_segmenter::{LineBreakOptions, LineBreakStrictness, LineBreakWordOption};
|
||||
use inline_box::{InlineBox, InlineBoxContainerState, InlineBoxIdentifier, InlineBoxes};
|
||||
use layout_api::wrapper_traits::SharedSelection;
|
||||
use layout_api::{LayoutNode, SharedSelection};
|
||||
use line::{
|
||||
AbsolutelyPositionedLineItem, AtomicLineItem, FloatLineItem, LineItem, LineItemLayout,
|
||||
TextRunLineItem,
|
||||
};
|
||||
use line_breaker::LineBreaker;
|
||||
use malloc_size_of_derive::MallocSizeOf;
|
||||
use script::layout_dom::ServoThreadSafeLayoutNode;
|
||||
use script::layout_dom::ServoLayoutNode;
|
||||
use servo_arc::Arc as ServoArc;
|
||||
use style::Zero;
|
||||
use style::computed_values::line_break::T as LineBreak;
|
||||
@@ -269,7 +269,7 @@ impl InlineItem {
|
||||
pub(crate) fn repair_style(
|
||||
&self,
|
||||
context: &SharedStyleContext,
|
||||
node: &ServoThreadSafeLayoutNode,
|
||||
node: &ServoLayoutNode,
|
||||
new_style: &ServoArc<ComputedValues>,
|
||||
) {
|
||||
match self {
|
||||
@@ -1906,7 +1906,7 @@ impl InlineFormattingContext {
|
||||
pub(crate) fn repair_style(
|
||||
&self,
|
||||
context: &SharedStyleContext,
|
||||
node: &ServoThreadSafeLayoutNode,
|
||||
node: &ServoLayoutNode,
|
||||
new_style: &ServoArc<ComputedValues>,
|
||||
) {
|
||||
*self.shared_inline_styles.style.borrow_mut() = new_style.clone();
|
||||
|
||||
@@ -7,10 +7,10 @@
|
||||
|
||||
use app_units::{Au, MAX_AU};
|
||||
use inline::InlineFormattingContext;
|
||||
use layout_api::wrapper_traits::ThreadSafeLayoutNode;
|
||||
use layout_api::LayoutNode;
|
||||
use malloc_size_of_derive::MallocSizeOf;
|
||||
use rayon::iter::{IndexedParallelIterator, IntoParallelRefIterator, ParallelIterator};
|
||||
use script::layout_dom::ServoThreadSafeLayoutNode;
|
||||
use script::layout_dom::ServoLayoutNode;
|
||||
use servo_arc::Arc;
|
||||
use style::Zero;
|
||||
use style::computed_values::clear::T as StyleClear;
|
||||
@@ -79,7 +79,7 @@ impl BlockContainer {
|
||||
pub(crate) fn repair_style(
|
||||
&mut self,
|
||||
context: &SharedStyleContext,
|
||||
node: &ServoThreadSafeLayoutNode,
|
||||
node: &ServoLayoutNode,
|
||||
new_style: &Arc<ComputedValues>,
|
||||
) {
|
||||
match self {
|
||||
@@ -108,7 +108,7 @@ impl BlockLevelBox {
|
||||
pub(crate) fn repair_style(
|
||||
&mut self,
|
||||
context: &SharedStyleContext,
|
||||
node: &ServoThreadSafeLayoutNode,
|
||||
node: &ServoLayoutNode,
|
||||
new_style: &Arc<ComputedValues>,
|
||||
) {
|
||||
match self {
|
||||
@@ -388,7 +388,7 @@ impl OutsideMarker {
|
||||
fn repair_style(
|
||||
&mut self,
|
||||
context: &SharedStyleContext,
|
||||
node: &ServoThreadSafeLayoutNode,
|
||||
node: &ServoLayoutNode,
|
||||
new_style: &Arc<ComputedValues>,
|
||||
) {
|
||||
self.list_item_style = node.parent_style(context);
|
||||
@@ -456,7 +456,7 @@ impl BlockFormattingContext {
|
||||
pub(crate) fn repair_style(
|
||||
&mut self,
|
||||
context: &SharedStyleContext,
|
||||
node: &ServoThreadSafeLayoutNode,
|
||||
node: &ServoLayoutNode,
|
||||
new_style: &Arc<ComputedValues>,
|
||||
) {
|
||||
self.contents.repair_style(context, node, new_style);
|
||||
|
||||
@@ -5,11 +5,10 @@
|
||||
use app_units::Au;
|
||||
use euclid::Rect;
|
||||
use euclid::default::Size2D as UntypedSize2D;
|
||||
use layout_api::AxesOverflow;
|
||||
use layout_api::wrapper_traits::{LayoutNode, ThreadSafeLayoutElement, ThreadSafeLayoutNode};
|
||||
use layout_api::{AxesOverflow, LayoutElement, LayoutNode};
|
||||
use malloc_size_of_derive::MallocSizeOf;
|
||||
use paint_api::display_list::AxesScrollSensitivity;
|
||||
use script::layout_dom::{ServoLayoutNode, ServoThreadSafeLayoutNode};
|
||||
use script::layout_dom::ServoLayoutNode;
|
||||
use style::values::computed::Overflow;
|
||||
use style_traits::CSSPixel;
|
||||
|
||||
@@ -39,7 +38,6 @@ pub struct BoxTree {
|
||||
impl BoxTree {
|
||||
#[servo_tracing::instrument(name = "Box Tree Construction", skip_all)]
|
||||
pub(crate) fn construct(context: &LayoutContext, root_element: ServoLayoutNode<'_>) -> Self {
|
||||
let root_element = root_element.to_threadsafe();
|
||||
let boxes = construct_for_root_element(context, root_element);
|
||||
|
||||
// Zero box for `:root { display: none }`, one for the root element otherwise.
|
||||
@@ -61,7 +59,7 @@ impl BoxTree {
|
||||
}
|
||||
|
||||
fn viewport_overflow(
|
||||
root_element: ServoThreadSafeLayoutNode<'_>,
|
||||
root_element: ServoLayoutNode<'_>,
|
||||
root_box: Option<&ArcRefCell<BlockLevelBox>>,
|
||||
) -> AxesOverflow {
|
||||
// From https://www.w3.org/TR/css-overflow-3/#overflow-propagation:
|
||||
@@ -84,7 +82,7 @@ impl BoxTree {
|
||||
// Unlike what the spec implies, we stop iterating when we find the first <body>,
|
||||
// even if it's not suitable because it lacks a box. This matches other browsers.
|
||||
// See https://github.com/w3c/csswg-drafts/issues/12644
|
||||
let body = root_element.children().find(|child| {
|
||||
let body = root_element.flat_tree_children().find(|child| {
|
||||
child
|
||||
.as_element()
|
||||
.is_some_and(|element| element.is_body_element_of_html_element_root())
|
||||
@@ -120,7 +118,7 @@ impl BoxTree {
|
||||
|
||||
fn construct_for_root_element(
|
||||
context: &LayoutContext,
|
||||
root_element: ServoThreadSafeLayoutNode<'_>,
|
||||
root_element: ServoLayoutNode<'_>,
|
||||
) -> Vec<ArcRefCell<BlockLevelBox>> {
|
||||
let info = NodeAndStyleInfo::new(root_element, root_element.style(&context.style_context));
|
||||
let box_style = info.style.get_box();
|
||||
|
||||
@@ -3,9 +3,9 @@
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
|
||||
|
||||
use app_units::Au;
|
||||
use layout_api::wrapper_traits::ThreadSafeLayoutNode;
|
||||
use layout_api::LayoutNode;
|
||||
use malloc_size_of_derive::MallocSizeOf;
|
||||
use script::layout_dom::{ServoLayoutElement, ServoThreadSafeLayoutNode};
|
||||
use script::layout_dom::{ServoDangerousStyleElement, ServoLayoutNode};
|
||||
use servo_arc::Arc;
|
||||
use style::context::SharedStyleContext;
|
||||
use style::logical_geometry::Direction;
|
||||
@@ -217,7 +217,7 @@ impl IndependentFormattingContext {
|
||||
let table_grid_style = context
|
||||
.style_context
|
||||
.stylist
|
||||
.style_for_anonymous::<ServoLayoutElement>(
|
||||
.style_for_anonymous::<ServoDangerousStyleElement>(
|
||||
&context.style_context.guards,
|
||||
&PseudoElement::ServoTableGrid,
|
||||
&node_and_style_info.style,
|
||||
@@ -335,7 +335,7 @@ impl IndependentFormattingContext {
|
||||
pub(crate) fn repair_style(
|
||||
&mut self,
|
||||
context: &SharedStyleContext,
|
||||
node: &ServoThreadSafeLayoutNode,
|
||||
node: &ServoLayoutNode,
|
||||
new_style: &Arc<ComputedValues>,
|
||||
) {
|
||||
self.base.repair_style(new_style);
|
||||
|
||||
@@ -8,13 +8,10 @@ use app_units::Au;
|
||||
use atomic_refcell::AtomicRef;
|
||||
use bitflags::bitflags;
|
||||
use html5ever::local_name;
|
||||
use layout_api::combine_id_with_fragment_type;
|
||||
use layout_api::wrapper_traits::{
|
||||
PseudoElementChain, ThreadSafeLayoutElement, ThreadSafeLayoutNode,
|
||||
};
|
||||
use layout_api::{LayoutElement, LayoutNode, PseudoElementChain, combine_id_with_fragment_type};
|
||||
use malloc_size_of::malloc_size_of_is_0;
|
||||
use malloc_size_of_derive::MallocSizeOf;
|
||||
use script::layout_dom::ServoThreadSafeLayoutNode;
|
||||
use script::layout_dom::ServoLayoutNode;
|
||||
use servo_arc::Arc as ServoArc;
|
||||
use style::dom::OpaqueNode;
|
||||
use style::properties::ComputedValues;
|
||||
@@ -180,8 +177,8 @@ impl From<&NodeAndStyleInfo<'_>> for BaseFragmentInfo {
|
||||
}
|
||||
}
|
||||
|
||||
impl From<ServoThreadSafeLayoutNode<'_>> for BaseFragmentInfo {
|
||||
fn from(node: ServoThreadSafeLayoutNode) -> Self {
|
||||
impl From<ServoLayoutNode<'_>> for BaseFragmentInfo {
|
||||
fn from(node: ServoLayoutNode) -> Self {
|
||||
let pseudo_element_chain = node.pseudo_element_chain();
|
||||
let mut flags = FragmentFlags::empty();
|
||||
|
||||
@@ -205,7 +202,7 @@ impl From<ServoThreadSafeLayoutNode<'_>> for BaseFragmentInfo {
|
||||
flags.insert(FragmentFlags::IS_BODY_ELEMENT_OF_HTML_ELEMENT_ROOT);
|
||||
}
|
||||
|
||||
match element.get_local_name() {
|
||||
match element.local_name() {
|
||||
&local_name!("br") => {
|
||||
flags.insert(FragmentFlags::IS_BR_ELEMENT);
|
||||
},
|
||||
@@ -215,7 +212,7 @@ impl From<ServoThreadSafeLayoutNode<'_>> for BaseFragmentInfo {
|
||||
_ => {},
|
||||
}
|
||||
|
||||
if ThreadSafeLayoutElement::is_root(&element) {
|
||||
if element.is_root() {
|
||||
flags.insert(FragmentFlags::IS_ROOT_ELEMENT);
|
||||
}
|
||||
};
|
||||
@@ -287,8 +284,8 @@ impl Tag {
|
||||
}
|
||||
}
|
||||
|
||||
impl From<ServoThreadSafeLayoutNode<'_>> for Tag {
|
||||
fn from(node: ServoThreadSafeLayoutNode<'_>) -> Self {
|
||||
impl From<ServoLayoutNode<'_>> for Tag {
|
||||
fn from(node: ServoLayoutNode<'_>) -> Self {
|
||||
Self {
|
||||
node: node.opaque(),
|
||||
pseudo_element_chain: node.pseudo_element_chain(),
|
||||
|
||||
@@ -18,12 +18,12 @@ use euclid::{Point2D, Rect, Scale, Size2D};
|
||||
use fonts::{FontContext, FontContextWebFontMethods, WebFontDocumentContext};
|
||||
use fonts_traits::StylesheetWebFontLoadFinishedCallback;
|
||||
use icu_locid::subtags::Language;
|
||||
use layout_api::wrapper_traits::LayoutNode;
|
||||
use layout_api::{
|
||||
AxesOverflow, BoxAreaType, CSSPixelRectIterator, IFrameSizes, Layout, LayoutConfig,
|
||||
LayoutFactory, OffsetParentResponse, PhysicalSides, QueryMsg, ReflowGoal, ReflowPhasesRun,
|
||||
ReflowRequest, ReflowRequestRestyle, ReflowResult, ReflowStatistics, ScrollContainerQueryFlags,
|
||||
ScrollContainerResponse, TrustedNodeAddress, with_layout_state,
|
||||
AxesOverflow, BoxAreaType, CSSPixelRectIterator, DangerousStyleNode, IFrameSizes, Layout,
|
||||
LayoutConfig, LayoutElement, LayoutFactory, LayoutNode, OffsetParentResponse, PhysicalSides,
|
||||
QueryMsg, ReflowGoal, ReflowPhasesRun, ReflowRequest, ReflowRequestRestyle, ReflowResult,
|
||||
ReflowStatistics, ScrollContainerQueryFlags, ScrollContainerResponse, TrustedNodeAddress,
|
||||
with_layout_state,
|
||||
};
|
||||
use log::{debug, error, warn};
|
||||
use malloc_size_of::{MallocConditionalSizeOf, MallocSizeOf, MallocSizeOfOps};
|
||||
@@ -37,7 +37,9 @@ use profile_traits::time::{
|
||||
};
|
||||
use profile_traits::{path, time_profile};
|
||||
use rustc_hash::FxHashMap;
|
||||
use script::layout_dom::{ServoLayoutDocument, ServoLayoutElement, ServoLayoutNode};
|
||||
use script::layout_dom::{
|
||||
ServoDangerousStyleDocument, ServoDangerousStyleElement, ServoLayoutElement, ServoLayoutNode,
|
||||
};
|
||||
use script_traits::{DrawAPaintImageResult, PaintWorkletError, Painter, ScriptThreadMessage};
|
||||
use servo_arc::Arc as ServoArc;
|
||||
use servo_base::generic_channel::GenericSender;
|
||||
@@ -318,7 +320,7 @@ impl Layout for LayoutThread {
|
||||
}
|
||||
|
||||
let node = unsafe { ServoLayoutNode::new(&node) };
|
||||
process_padding_request(node.to_threadsafe())
|
||||
process_padding_request(node)
|
||||
})
|
||||
}
|
||||
|
||||
@@ -348,7 +350,7 @@ impl Layout for LayoutThread {
|
||||
.expect("Should always have a StackingContextTree for box area queries");
|
||||
process_box_area_request(
|
||||
stacking_context_tree,
|
||||
node.to_threadsafe(),
|
||||
node,
|
||||
area,
|
||||
exclude_transform_and_inline,
|
||||
)
|
||||
@@ -373,7 +375,7 @@ impl Layout for LayoutThread {
|
||||
let stacking_context_tree = stacking_context_tree
|
||||
.as_ref()
|
||||
.expect("Should always have a StackingContextTree for box area queries");
|
||||
process_box_areas_request(stacking_context_tree, node.to_threadsafe(), area)
|
||||
process_box_areas_request(stacking_context_tree, node, area)
|
||||
})
|
||||
}
|
||||
|
||||
@@ -381,7 +383,7 @@ impl Layout for LayoutThread {
|
||||
fn query_client_rect(&self, node: TrustedNodeAddress) -> Rect<i32, CSSPixel> {
|
||||
with_layout_state(|| {
|
||||
let node = unsafe { ServoLayoutNode::new(&node) };
|
||||
process_client_rect_request(node.to_threadsafe())
|
||||
process_client_rect_request(node)
|
||||
})
|
||||
}
|
||||
|
||||
@@ -442,7 +444,7 @@ impl Layout for LayoutThread {
|
||||
) -> String {
|
||||
with_layout_state(|| {
|
||||
let node = unsafe { ServoLayoutNode::new(&node) };
|
||||
let document = node.owner_doc();
|
||||
let document = unsafe { node.dangerous_style_node() }.owner_doc();
|
||||
let document_shared_lock = document.style_shared_lock();
|
||||
let guards = StylesheetGuards {
|
||||
author: &document_shared_lock.read(),
|
||||
@@ -472,7 +474,7 @@ impl Layout for LayoutThread {
|
||||
) -> Option<ServoArc<Font>> {
|
||||
with_layout_state(|| {
|
||||
let node = unsafe { ServoLayoutNode::new(&node) };
|
||||
let document = node.owner_doc();
|
||||
let document = unsafe { node.dangerous_style_node() }.owner_doc();
|
||||
let document_shared_lock = document.style_shared_lock();
|
||||
let guards = StylesheetGuards {
|
||||
author: &document_shared_lock.read(),
|
||||
@@ -500,7 +502,7 @@ impl Layout for LayoutThread {
|
||||
#[servo_tracing::instrument(skip_all)]
|
||||
fn query_scrolling_area(&self, node: Option<TrustedNodeAddress>) -> Rect<i32, CSSPixel> {
|
||||
with_layout_state(|| {
|
||||
let node = node.map(|node| unsafe { ServoLayoutNode::new(&node).to_threadsafe() });
|
||||
let node = node.map(|node| unsafe { ServoLayoutNode::new(&node) });
|
||||
process_node_scroll_area_request(node, self.fragment_tree.borrow().clone())
|
||||
})
|
||||
}
|
||||
@@ -512,7 +514,7 @@ impl Layout for LayoutThread {
|
||||
point_in_node: Point2D<Au, CSSPixel>,
|
||||
) -> Option<usize> {
|
||||
with_layout_state(|| {
|
||||
let node = unsafe { ServoLayoutNode::new(&node).to_threadsafe() };
|
||||
let node = unsafe { ServoLayoutNode::new(&node) };
|
||||
let stacking_context_tree = self.stacking_context_tree.borrow_mut();
|
||||
let stacking_context_tree = stacking_context_tree.as_ref()?;
|
||||
find_character_offset_in_fragment_descendants(
|
||||
@@ -541,7 +543,7 @@ impl Layout for LayoutThread {
|
||||
#[servo_tracing::instrument(skip_all)]
|
||||
fn query_effective_overflow(&self, node: TrustedNodeAddress) -> Option<AxesOverflow> {
|
||||
with_layout_state(|| {
|
||||
let node = unsafe { ServoLayoutNode::new(&node).to_threadsafe() };
|
||||
let node = unsafe { ServoLayoutNode::new(&node) };
|
||||
process_effective_overflow_query(node)
|
||||
})
|
||||
}
|
||||
@@ -865,7 +867,9 @@ impl LayoutThread {
|
||||
}
|
||||
|
||||
let document = unsafe { ServoLayoutNode::new(&reflow_request.document) };
|
||||
let document = document.as_document().unwrap();
|
||||
let document = unsafe { document.dangerous_style_node() }
|
||||
.as_document()
|
||||
.unwrap();
|
||||
let Some(root_element) = document.root_element() else {
|
||||
debug!("layout: No root node: bailing");
|
||||
return None;
|
||||
@@ -922,7 +926,7 @@ impl LayoutThread {
|
||||
fn prepare_stylist_for_reflow<'dom>(
|
||||
&mut self,
|
||||
reflow_request: &ReflowRequest,
|
||||
document: ServoLayoutDocument<'dom>,
|
||||
document: ServoDangerousStyleDocument<'dom>,
|
||||
guards: &StylesheetGuards,
|
||||
ua_stylesheets: &UserAgentStylesheets,
|
||||
) -> StylesheetInvalidationSet {
|
||||
@@ -976,7 +980,7 @@ impl LayoutThread {
|
||||
fn restyle_and_build_trees(
|
||||
&mut self,
|
||||
reflow_request: &mut ReflowRequest,
|
||||
document: ServoLayoutDocument<'_>,
|
||||
document: ServoDangerousStyleDocument<'_>,
|
||||
root_element: ServoLayoutElement<'_>,
|
||||
image_resolver: &Arc<ImageResolver>,
|
||||
) -> (ReflowPhasesRun, IFrameSizes) {
|
||||
@@ -999,6 +1003,7 @@ impl LayoutThread {
|
||||
let rayon_pool = rayon_pool.as_ref();
|
||||
|
||||
let device_has_changed = std::mem::replace(&mut self.device_has_changed, false);
|
||||
let dangerous_root_element = unsafe { root_element.dangerous_style_element() };
|
||||
if device_has_changed {
|
||||
let sheet_origins_affected_by_device_change = self
|
||||
.stylist
|
||||
@@ -1006,13 +1011,13 @@ impl LayoutThread {
|
||||
self.stylist
|
||||
.force_stylesheet_origins_dirty(sheet_origins_affected_by_device_change);
|
||||
|
||||
if let Some(mut data) = root_element.mutate_data() {
|
||||
if let Some(mut data) = dangerous_root_element.mutate_data() {
|
||||
data.hint.insert(RestyleHint::recascade_subtree());
|
||||
}
|
||||
}
|
||||
|
||||
self.prepare_stylist_for_reflow(reflow_request, document, &guards, ua_stylesheets)
|
||||
.process_style(root_element, Some(&snapshot_map));
|
||||
.process_style(dangerous_root_element, Some(&snapshot_map));
|
||||
|
||||
if self.previously_highlighted_dom_node.get() != reflow_request.highlighted_dom_node {
|
||||
// Need to manually force layout to build a new display list regardless of whether the box tree
|
||||
@@ -1052,12 +1057,14 @@ impl LayoutThread {
|
||||
ServoLayoutNode::new(&restyle.dirty_root.unwrap())
|
||||
.as_element()
|
||||
.unwrap()
|
||||
.dangerous_style_element()
|
||||
};
|
||||
|
||||
recalc_style_traversal = RecalcStyle::new(&layout_context);
|
||||
let token = {
|
||||
let shared =
|
||||
DomTraversal::<ServoLayoutElement>::shared_context(&recalc_style_traversal);
|
||||
let shared = DomTraversal::<ServoDangerousStyleElement>::shared_context(
|
||||
&recalc_style_traversal,
|
||||
);
|
||||
RecalcStyle::pre_traverse(original_dirty_root, shared)
|
||||
};
|
||||
|
||||
@@ -1083,7 +1090,7 @@ impl LayoutThread {
|
||||
compute_damage_and_rebuild_box_tree(
|
||||
box_tree,
|
||||
&layout_context,
|
||||
dirty_root,
|
||||
dirty_root.layout_node(),
|
||||
root_node,
|
||||
damage_from_environment,
|
||||
)
|
||||
@@ -1129,7 +1136,7 @@ impl LayoutThread {
|
||||
if self.debug.style_tree {
|
||||
println!(
|
||||
"{:?}",
|
||||
ShowSubtreeDataAndPrimaryValues(root_element.as_node())
|
||||
ShowSubtreeDataAndPrimaryValues(dangerous_root_element.as_node())
|
||||
);
|
||||
}
|
||||
if self.debug.rule_tree {
|
||||
@@ -1577,14 +1584,17 @@ impl SnapshotSetter<'_> {
|
||||
|
||||
// If we haven't styled this node yet, we don't need to track a
|
||||
// restyle.
|
||||
let Some(mut style_data) = element.mutate_data() else {
|
||||
unsafe { element.unset_snapshot_flags() };
|
||||
let Some(mut style_data) = element
|
||||
.style_data()
|
||||
.map(|data| data.element_data.borrow_mut())
|
||||
else {
|
||||
element.unset_snapshot_flags();
|
||||
continue;
|
||||
};
|
||||
|
||||
debug!("Noting restyle for {:?}: {:?}", element, style_data);
|
||||
if let Some(s) = restyle.snapshot {
|
||||
unsafe { element.set_has_snapshot() };
|
||||
element.set_has_snapshot();
|
||||
snapshot_map.insert(element.as_node().opaque(), s);
|
||||
}
|
||||
|
||||
@@ -1601,7 +1611,7 @@ impl SnapshotSetter<'_> {
|
||||
impl Drop for SnapshotSetter<'_> {
|
||||
fn drop(&mut self) {
|
||||
for element in &self.elements_with_snapshot {
|
||||
unsafe { element.unset_snapshot_flags() }
|
||||
element.unset_snapshot_flags();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,13 +8,13 @@ use std::rc::Rc;
|
||||
use app_units::Au;
|
||||
use euclid::{Point2D, Rect, SideOffsets2D, Size2D};
|
||||
use itertools::Itertools;
|
||||
use layout_api::wrapper_traits::{LayoutNode, ThreadSafeLayoutElement, ThreadSafeLayoutNode};
|
||||
use layout_api::{
|
||||
AxesOverflow, BoxAreaType, CSSPixelRectIterator, LayoutElementType, LayoutNodeType,
|
||||
OffsetParentResponse, PhysicalSides, ScrollContainerQueryFlags, ScrollContainerResponse,
|
||||
AxesOverflow, BoxAreaType, CSSPixelRectIterator, LayoutElement, LayoutElementType, LayoutNode,
|
||||
LayoutNodeType, OffsetParentResponse, PhysicalSides, ScrollContainerQueryFlags,
|
||||
ScrollContainerResponse,
|
||||
};
|
||||
use paint_api::display_list::ScrollTree;
|
||||
use script::layout_dom::{ServoLayoutNode, ServoThreadSafeLayoutNode};
|
||||
use script::layout_dom::ServoLayoutNode;
|
||||
use servo_arc::Arc as ServoArc;
|
||||
use servo_geometry::{FastLayoutTransform, au_rect_to_f32_rect, f32_rect_to_au_rect};
|
||||
use servo_url::ServoUrl;
|
||||
@@ -23,7 +23,7 @@ use style::computed_values::position::T as Position;
|
||||
use style::computed_values::visibility::T as Visibility;
|
||||
use style::computed_values::white_space_collapse::T as WhiteSpaceCollapseValue;
|
||||
use style::context::{QuirksMode, SharedStyleContext, StyleContext, ThreadLocalStyleContext};
|
||||
use style::dom::{NodeInfo, TElement, TNode};
|
||||
use style::dom::NodeInfo;
|
||||
use style::properties::style_structs::Font;
|
||||
use style::properties::{
|
||||
ComputedValues, Importance, LonghandId, PropertyDeclarationBlock, PropertyDeclarationId,
|
||||
@@ -57,7 +57,7 @@ use crate::taffy::SpecificTaffyGridInfo;
|
||||
/// calculate its cumulative transform from its root scroll node to the scroll node.
|
||||
fn root_transform_for_layout_node(
|
||||
scroll_tree: &ScrollTree,
|
||||
node: ServoThreadSafeLayoutNode<'_>,
|
||||
node: ServoLayoutNode<'_>,
|
||||
) -> Option<FastLayoutTransform> {
|
||||
let fragments = node.fragments_for_pseudo(None);
|
||||
let box_fragment = fragments
|
||||
@@ -68,9 +68,7 @@ fn root_transform_for_layout_node(
|
||||
Some(scroll_tree.cumulative_node_to_root_transform(scroll_tree_node_id))
|
||||
}
|
||||
|
||||
pub(crate) fn process_padding_request(
|
||||
node: ServoThreadSafeLayoutNode<'_>,
|
||||
) -> Option<PhysicalSides> {
|
||||
pub(crate) fn process_padding_request(node: ServoLayoutNode<'_>) -> Option<PhysicalSides> {
|
||||
let fragments = node.fragments_for_pseudo(None);
|
||||
let fragment = fragments.first()?;
|
||||
Some(match fragment {
|
||||
@@ -89,7 +87,7 @@ pub(crate) fn process_padding_request(
|
||||
|
||||
pub(crate) fn process_box_area_request(
|
||||
stacking_context_tree: &StackingContextTree,
|
||||
node: ServoThreadSafeLayoutNode<'_>,
|
||||
node: ServoLayoutNode<'_>,
|
||||
area: BoxAreaType,
|
||||
exclude_transform_and_inline: bool,
|
||||
) -> Option<Rect<Au, CSSPixel>> {
|
||||
@@ -123,7 +121,7 @@ pub(crate) fn process_box_area_request(
|
||||
|
||||
pub(crate) fn process_box_areas_request(
|
||||
stacking_context_tree: &StackingContextTree,
|
||||
node: ServoThreadSafeLayoutNode<'_>,
|
||||
node: ServoLayoutNode<'_>,
|
||||
area: BoxAreaType,
|
||||
) -> CSSPixelRectIterator {
|
||||
let fragments = node
|
||||
@@ -140,7 +138,7 @@ pub(crate) fn process_box_areas_request(
|
||||
Box::new(fragments.filter_map(move |rect| transform_au_rectangle(rect, transform)))
|
||||
}
|
||||
|
||||
pub fn process_client_rect_request(node: ServoThreadSafeLayoutNode<'_>) -> Rect<i32, CSSPixel> {
|
||||
pub fn process_client_rect_request(node: ServoLayoutNode<'_>) -> Rect<i32, CSSPixel> {
|
||||
node.fragments_for_pseudo(None)
|
||||
.first()
|
||||
.map(Fragment::client_rect)
|
||||
@@ -154,7 +152,7 @@ pub fn process_client_rect_request(node: ServoThreadSafeLayoutNode<'_>) -> Rect<
|
||||
/// values from the element up to the root. Returns 1.0 if the element is not
|
||||
/// being rendered (has no associated box).
|
||||
pub fn process_current_css_zoom_query(node: ServoLayoutNode<'_>) -> f32 {
|
||||
let Some(layout_data) = node.to_threadsafe().inner_layout_data() else {
|
||||
let Some(layout_data) = node.inner_layout_data() else {
|
||||
return 1.0;
|
||||
};
|
||||
let layout_box = layout_data.self_box.borrow();
|
||||
@@ -168,7 +166,7 @@ pub fn process_current_css_zoom_query(node: ServoLayoutNode<'_>) -> f32 {
|
||||
|
||||
/// <https://drafts.csswg.org/cssom-view/#scrolling-area>
|
||||
pub fn process_node_scroll_area_request(
|
||||
requested_node: Option<ServoThreadSafeLayoutNode<'_>>,
|
||||
requested_node: Option<ServoLayoutNode<'_>>,
|
||||
fragment_tree: Option<Rc<FragmentTree>>,
|
||||
) -> Rect<i32, CSSPixel> {
|
||||
let Some(tree) = fragment_tree else {
|
||||
@@ -200,13 +198,16 @@ pub fn process_resolved_style_request(
|
||||
pseudo: &Option<PseudoElement>,
|
||||
property: &PropertyId,
|
||||
) -> String {
|
||||
if !node.as_element().unwrap().has_data() {
|
||||
if node
|
||||
.as_element()
|
||||
.is_none_or(|element| element.style_data().is_none())
|
||||
{
|
||||
return process_resolved_style_request_for_unstyled_node(context, node, pseudo, property);
|
||||
}
|
||||
|
||||
// We call process_resolved_style_request after performing a whole-document
|
||||
// traversal, so in the common case, the element is styled.
|
||||
let layout_element = node.to_threadsafe().as_element().unwrap();
|
||||
let layout_element = node.as_element().unwrap();
|
||||
let layout_element = match pseudo {
|
||||
Some(pseudo_element_type) => {
|
||||
match layout_element.with_pseudo(*pseudo_element_type) {
|
||||
@@ -395,8 +396,7 @@ pub fn process_resolved_style_request(
|
||||
.to_css_string()
|
||||
};
|
||||
|
||||
node.to_threadsafe()
|
||||
.fragments_for_pseudo(*pseudo)
|
||||
node.fragments_for_pseudo(*pseudo)
|
||||
.first()
|
||||
.map(resolve_for_fragment)
|
||||
.unwrap_or_else(|| computed_style(None))
|
||||
@@ -490,6 +490,7 @@ fn resolve_grid_template(
|
||||
}
|
||||
}
|
||||
|
||||
#[expect(unsafe_code)]
|
||||
pub fn process_resolved_style_request_for_unstyled_node(
|
||||
context: &SharedStyleContext,
|
||||
node: ServoLayoutNode<'_>,
|
||||
@@ -510,7 +511,7 @@ pub fn process_resolved_style_request_for_unstyled_node(
|
||||
let element = node.as_element().unwrap();
|
||||
let styles = resolve_style(
|
||||
&mut context,
|
||||
element,
|
||||
unsafe { element.dangerous_style_element() },
|
||||
RuleInclusion::All,
|
||||
pseudo.as_ref(),
|
||||
None,
|
||||
@@ -570,17 +571,14 @@ struct OffsetParentFragments {
|
||||
}
|
||||
|
||||
/// <https://www.w3.org/TR/2016/WD-cssom-view-1-20160317/#dom-htmlelement-offsetparent>
|
||||
#[expect(unsafe_code)]
|
||||
fn offset_parent_fragments(node: ServoLayoutNode<'_>) -> Option<OffsetParentFragments> {
|
||||
// 1. If any of the following holds true return null and terminate this algorithm:
|
||||
// * The element does not have an associated CSS layout box.
|
||||
// * The element is the root element.
|
||||
// * The element is the HTML body element.
|
||||
// * The element’s computed value of the position property is fixed.
|
||||
let fragment = node
|
||||
.to_threadsafe()
|
||||
.fragments_for_pseudo(None)
|
||||
.first()
|
||||
.cloned()?;
|
||||
let fragment = node.fragments_for_pseudo(None).first().cloned()?;
|
||||
let flags = fragment.base()?.flags;
|
||||
if flags.intersects(
|
||||
FragmentFlags::IS_ROOT_ELEMENT | FragmentFlags::IS_BODY_ELEMENT_OF_HTML_ELEMENT_ROOT,
|
||||
@@ -599,26 +597,18 @@ fn offset_parent_fragments(node: ServoLayoutNode<'_>) -> Option<OffsetParentFrag
|
||||
// * It is the HTML body element.
|
||||
// * The computed value of the position property of the element is static and the
|
||||
// ancestor is one of the following HTML elements: td, th, or table.
|
||||
let mut maybe_parent_node = node.parent_node();
|
||||
let mut maybe_parent_node = unsafe { node.dangerous_dom_parent() };
|
||||
while let Some(parent_node) = maybe_parent_node {
|
||||
maybe_parent_node = parent_node.parent_node();
|
||||
maybe_parent_node = unsafe { parent_node.dangerous_dom_parent() };
|
||||
|
||||
if let Some(parent_fragment) = parent_node
|
||||
.to_threadsafe()
|
||||
.fragments_for_pseudo(None)
|
||||
.first()
|
||||
{
|
||||
if let Some(parent_fragment) = parent_node.fragments_for_pseudo(None).first() {
|
||||
let parent_fragment = match parent_fragment {
|
||||
Fragment::Box(box_fragment) | Fragment::Float(box_fragment) => box_fragment,
|
||||
_ => continue,
|
||||
};
|
||||
|
||||
let grandparent_fragment = maybe_parent_node.and_then(|node| {
|
||||
node.to_threadsafe()
|
||||
.fragments_for_pseudo(None)
|
||||
.first()
|
||||
.cloned()
|
||||
});
|
||||
let grandparent_fragment =
|
||||
maybe_parent_node.and_then(|node| node.fragments_for_pseudo(None).first().cloned());
|
||||
|
||||
if parent_fragment.borrow().style().get_box().position != Position::Static {
|
||||
return Some(OffsetParentFragments {
|
||||
@@ -664,11 +654,7 @@ pub fn process_offset_parent_query(
|
||||
// [1]: https://github.com/w3c/csswg-drafts/issues/4541
|
||||
// > 1. If the element is the HTML body element or does not have any associated CSS
|
||||
// layout box return zero and terminate this algorithm.
|
||||
let fragment = node
|
||||
.to_threadsafe()
|
||||
.fragments_for_pseudo(None)
|
||||
.first()
|
||||
.cloned()?;
|
||||
let fragment = node.fragments_for_pseudo(None).first().cloned()?;
|
||||
let mut border_box = fragment.cumulative_box_area_rect(BoxAreaType::Border)?;
|
||||
let cumulative_sticky_offsets = fragment
|
||||
.retrieve_box_fragment()
|
||||
@@ -764,7 +750,7 @@ pub(crate) fn process_scroll_container_query(
|
||||
return Some(ScrollContainerResponse::Viewport(viewport_overflow));
|
||||
};
|
||||
|
||||
let layout_data = node.to_threadsafe().inner_layout_data()?;
|
||||
let layout_data = node.inner_layout_data()?;
|
||||
|
||||
// 1. If any of the following holds true, return null and terminate this algorithm:
|
||||
// - The element does not have an associated box.
|
||||
@@ -816,11 +802,13 @@ pub(crate) fn process_scroll_container_query(
|
||||
//
|
||||
// TODO: Handle the situation where the ancestor is "closed-shadow-hidden" from the element.
|
||||
let mut current_position_value = style.clone_position();
|
||||
let mut current_ancestor = node.as_element()?;
|
||||
while let Some(ancestor) = current_ancestor.traversal_parent() {
|
||||
let mut current_ancestor = node;
|
||||
|
||||
#[expect(unsafe_code)]
|
||||
while let Some(ancestor) = unsafe { current_ancestor.dangerous_flat_tree_parent() } {
|
||||
current_ancestor = ancestor;
|
||||
|
||||
let Some(layout_data) = ancestor.as_node().to_threadsafe().inner_layout_data() else {
|
||||
let Some(layout_data) = ancestor.inner_layout_data() else {
|
||||
continue;
|
||||
};
|
||||
let ancestor_layout_box = layout_data.self_box.borrow();
|
||||
@@ -851,7 +839,7 @@ pub(crate) fn process_scroll_container_query(
|
||||
|
||||
if ancestor_style.establishes_scroll_container(ancestor_flags) {
|
||||
return Some(ScrollContainerResponse::Element(
|
||||
ancestor.as_node().opaque().into(),
|
||||
ancestor.opaque().into(),
|
||||
ancestor_style.effective_overflow(ancestor_flags),
|
||||
));
|
||||
}
|
||||
@@ -954,6 +942,7 @@ impl Default for RenderedTextCollectionState {
|
||||
}
|
||||
|
||||
/// <https://html.spec.whatwg.org/multipage/#rendered-text-collection-steps>
|
||||
#[expect(unsafe_code)]
|
||||
fn rendered_text_collection_steps(
|
||||
node: ServoLayoutNode<'_>,
|
||||
state: &mut RenderedTextCollectionState,
|
||||
@@ -967,27 +956,33 @@ fn rendered_text_collection_steps(
|
||||
}
|
||||
|
||||
match node.type_id() {
|
||||
LayoutNodeType::Text => {
|
||||
if let Some(parent_node) = node.parent_node() {
|
||||
Some(LayoutNodeType::Text) => {
|
||||
if let Some(parent_node) = unsafe { node.dangerous_dom_parent() } {
|
||||
match parent_node.type_id() {
|
||||
// Any text contained in these elements must be ignored.
|
||||
LayoutNodeType::Element(LayoutElementType::HTMLCanvasElement) |
|
||||
LayoutNodeType::Element(LayoutElementType::HTMLImageElement) |
|
||||
LayoutNodeType::Element(LayoutElementType::HTMLIFrameElement) |
|
||||
LayoutNodeType::Element(LayoutElementType::HTMLObjectElement) |
|
||||
LayoutNodeType::Element(LayoutElementType::HTMLInputElement) |
|
||||
LayoutNodeType::Element(LayoutElementType::HTMLTextAreaElement) |
|
||||
LayoutNodeType::Element(LayoutElementType::HTMLMediaElement) => {
|
||||
Some(
|
||||
LayoutNodeType::Element(LayoutElementType::HTMLCanvasElement) |
|
||||
LayoutNodeType::Element(LayoutElementType::HTMLImageElement) |
|
||||
LayoutNodeType::Element(LayoutElementType::HTMLIFrameElement) |
|
||||
LayoutNodeType::Element(LayoutElementType::HTMLObjectElement) |
|
||||
LayoutNodeType::Element(LayoutElementType::HTMLInputElement) |
|
||||
LayoutNodeType::Element(LayoutElementType::HTMLTextAreaElement) |
|
||||
LayoutNodeType::Element(LayoutElementType::HTMLMediaElement),
|
||||
) => {
|
||||
return items;
|
||||
},
|
||||
// Select/Option/OptGroup elements are handled a bit differently.
|
||||
// Basically: a Select can only contain Options or OptGroups, while
|
||||
// OptGroups may also contain Options. Everything else gets ignored.
|
||||
LayoutNodeType::Element(LayoutElementType::HTMLOptGroupElement) => {
|
||||
if let Some(element) = parent_node.parent_node() {
|
||||
Some(LayoutNodeType::Element(LayoutElementType::HTMLOptGroupElement)) => {
|
||||
if let Some(grandparent_node) =
|
||||
unsafe { parent_node.dangerous_dom_parent() }
|
||||
{
|
||||
if !matches!(
|
||||
element.type_id(),
|
||||
LayoutNodeType::Element(LayoutElementType::HTMLSelectElement)
|
||||
grandparent_node.type_id(),
|
||||
Some(LayoutNodeType::Element(
|
||||
LayoutElementType::HTMLSelectElement
|
||||
))
|
||||
) {
|
||||
return items;
|
||||
}
|
||||
@@ -995,7 +990,9 @@ fn rendered_text_collection_steps(
|
||||
return items;
|
||||
}
|
||||
},
|
||||
LayoutNodeType::Element(LayoutElementType::HTMLSelectElement) => return items,
|
||||
Some(LayoutNodeType::Element(LayoutElementType::HTMLSelectElement)) => {
|
||||
return items;
|
||||
},
|
||||
_ => {},
|
||||
}
|
||||
|
||||
@@ -1006,7 +1003,7 @@ fn rendered_text_collection_steps(
|
||||
return items;
|
||||
}
|
||||
|
||||
let Some(parent_element) = parent_node.to_threadsafe().as_element() else {
|
||||
let Some(parent_element) = parent_node.as_element() else {
|
||||
return items;
|
||||
};
|
||||
let Some(style_data) = parent_element.style_data() else {
|
||||
@@ -1045,7 +1042,7 @@ fn rendered_text_collection_steps(
|
||||
}
|
||||
}
|
||||
|
||||
let text_content = node.to_threadsafe().text_content();
|
||||
let text_content = node.text_content();
|
||||
|
||||
let white_space_collapse = style.clone_white_space_collapse();
|
||||
let preserve_whitespace = white_space_collapse == WhiteSpaceCollapseValue::Preserve;
|
||||
@@ -1121,12 +1118,10 @@ fn rendered_text_collection_steps(
|
||||
} else {
|
||||
// If we don't have a parent element then there's no style data available,
|
||||
// in this (pretty unlikely) case we just return the Text fragment as is.
|
||||
items.push(InnerOrOuterTextItem::Text(
|
||||
node.to_threadsafe().text_content().into(),
|
||||
));
|
||||
items.push(InnerOrOuterTextItem::Text(node.text_content().into()));
|
||||
}
|
||||
},
|
||||
LayoutNodeType::Element(LayoutElementType::HTMLBRElement) => {
|
||||
Some(LayoutNodeType::Element(LayoutElementType::HTMLBRElement)) => {
|
||||
// Step 5: If node is a br element, then append a string containing a single U+000A
|
||||
// LF code point to items.
|
||||
state.did_truncate_trailing_white_space = false;
|
||||
@@ -1136,7 +1131,7 @@ fn rendered_text_collection_steps(
|
||||
_ => {
|
||||
// First we need to gather some infos to setup the various flags
|
||||
// before rendering the child nodes
|
||||
let Some(element) = node.to_threadsafe().as_element() else {
|
||||
let Some(element) = node.as_element() else {
|
||||
return items;
|
||||
};
|
||||
let Some(style_data) = element.style_data() else {
|
||||
@@ -1230,13 +1225,15 @@ fn rendered_text_collection_steps(
|
||||
match node.type_id() {
|
||||
// Step 8: If node is a p element, then append 2 (a required line break count) at
|
||||
// the beginning and end of items.
|
||||
LayoutNodeType::Element(LayoutElementType::HTMLParagraphElement) => {
|
||||
Some(LayoutNodeType::Element(LayoutElementType::HTMLParagraphElement)) => {
|
||||
surrounding_line_breaks = 2;
|
||||
},
|
||||
// Option/OptGroup elements should go on separate lines, by treating them like
|
||||
// Block elements we can achieve that.
|
||||
LayoutNodeType::Element(LayoutElementType::HTMLOptionElement) |
|
||||
LayoutNodeType::Element(LayoutElementType::HTMLOptGroupElement) => {
|
||||
Some(
|
||||
LayoutNodeType::Element(LayoutElementType::HTMLOptionElement) |
|
||||
LayoutNodeType::Element(LayoutElementType::HTMLOptGroupElement),
|
||||
) => {
|
||||
surrounding_line_breaks = 1;
|
||||
},
|
||||
_ => {},
|
||||
@@ -1255,13 +1252,15 @@ fn rendered_text_collection_steps(
|
||||
// However we still need to check whether we have to prepend a
|
||||
// space, since for example <span>asd <input> qwe</span> must
|
||||
// product "asd qwe" (note the 2 spaces)
|
||||
LayoutNodeType::Element(LayoutElementType::HTMLCanvasElement) |
|
||||
LayoutNodeType::Element(LayoutElementType::HTMLImageElement) |
|
||||
LayoutNodeType::Element(LayoutElementType::HTMLIFrameElement) |
|
||||
LayoutNodeType::Element(LayoutElementType::HTMLObjectElement) |
|
||||
LayoutNodeType::Element(LayoutElementType::HTMLInputElement) |
|
||||
LayoutNodeType::Element(LayoutElementType::HTMLTextAreaElement) |
|
||||
LayoutNodeType::Element(LayoutElementType::HTMLMediaElement) => {
|
||||
Some(
|
||||
LayoutNodeType::Element(LayoutElementType::HTMLCanvasElement) |
|
||||
LayoutNodeType::Element(LayoutElementType::HTMLImageElement) |
|
||||
LayoutNodeType::Element(LayoutElementType::HTMLIFrameElement) |
|
||||
LayoutNodeType::Element(LayoutElementType::HTMLObjectElement) |
|
||||
LayoutNodeType::Element(LayoutElementType::HTMLInputElement) |
|
||||
LayoutNodeType::Element(LayoutElementType::HTMLTextAreaElement) |
|
||||
LayoutNodeType::Element(LayoutElementType::HTMLMediaElement),
|
||||
) => {
|
||||
if display != Display::Block && state.did_truncate_trailing_white_space {
|
||||
items.push(InnerOrOuterTextItem::Text(String::from(" ")));
|
||||
state.did_truncate_trailing_white_space = false;
|
||||
@@ -1325,7 +1324,7 @@ impl ClosestFragment {
|
||||
}
|
||||
|
||||
pub fn find_character_offset_in_fragment_descendants(
|
||||
node: &ServoThreadSafeLayoutNode,
|
||||
node: &ServoLayoutNode,
|
||||
stacking_context_tree: &StackingContextTree,
|
||||
point_in_viewport: Point2D<Au, CSSPixel>,
|
||||
) -> Option<usize> {
|
||||
@@ -1450,7 +1449,7 @@ where
|
||||
};
|
||||
context
|
||||
.stylist
|
||||
.compute_for_declarations::<E::ConcreteElement>(
|
||||
.compute_for_declarations::<E::ConcreteDangerousStyleElement>(
|
||||
&context.guards,
|
||||
parent_style,
|
||||
ServoArc::new(shared_lock.wrap(declarations)),
|
||||
@@ -1467,15 +1466,22 @@ where
|
||||
// 2. Get resolved styles for the parent element
|
||||
let element = node.as_element().unwrap();
|
||||
let parent_style = if node.is_connected() {
|
||||
if element.has_data() {
|
||||
node.to_threadsafe().as_element().unwrap().style(context)
|
||||
if element.style_data().is_some() {
|
||||
element.style(context)
|
||||
} else {
|
||||
let mut tlc = ThreadLocalStyleContext::new();
|
||||
let mut context = StyleContext {
|
||||
shared: context,
|
||||
thread_local: &mut tlc,
|
||||
};
|
||||
let styles = resolve_style(&mut context, element, RuleInclusion::All, None, None);
|
||||
#[expect(unsafe_code)]
|
||||
let styles = resolve_style(
|
||||
&mut context,
|
||||
unsafe { element.dangerous_style_element() },
|
||||
RuleInclusion::All,
|
||||
None,
|
||||
None,
|
||||
);
|
||||
styles.primary().clone()
|
||||
}
|
||||
} else {
|
||||
@@ -1505,9 +1511,7 @@ pub(crate) fn transform_au_rectangle(
|
||||
outer_transformed_rect.map(|transformed_rect| f32_rect_to_au_rect(transformed_rect).cast_unit())
|
||||
}
|
||||
|
||||
pub(crate) fn process_effective_overflow_query(
|
||||
node: ServoThreadSafeLayoutNode<'_>,
|
||||
) -> Option<AxesOverflow> {
|
||||
pub(crate) fn process_effective_overflow_query(node: ServoLayoutNode<'_>) -> Option<AxesOverflow> {
|
||||
let fragments = node.fragments_for_pseudo(None);
|
||||
let box_fragment = fragments.first()?.retrieve_box_fragment()?;
|
||||
let box_fragment = box_fragment.borrow();
|
||||
|
||||
@@ -7,12 +7,10 @@ use data_url::DataUrl;
|
||||
use embedder_traits::ViewportDetails;
|
||||
use euclid::{Scale, Size2D};
|
||||
use html5ever::local_name;
|
||||
use layout_api::wrapper_traits::ThreadSafeLayoutNode;
|
||||
use layout_api::{IFrameSize, LayoutImageDestination, SVGElementData};
|
||||
use layout_api::{IFrameSize, LayoutElement, LayoutImageDestination, LayoutNode, SVGElementData};
|
||||
use malloc_size_of_derive::MallocSizeOf;
|
||||
use net_traits::image_cache::{Image, ImageOrMetadataAvailable, VectorImage};
|
||||
use script::layout_dom::ServoThreadSafeLayoutNode;
|
||||
use selectors::Element;
|
||||
use script::layout_dom::ServoLayoutNode;
|
||||
use servo_arc::Arc as ServoArc;
|
||||
use servo_base::id::{BrowsingContextId, PipelineId};
|
||||
use servo_url::ServoUrl;
|
||||
@@ -148,10 +146,7 @@ pub(crate) enum ReplacedContentKind {
|
||||
}
|
||||
|
||||
impl ReplacedContents {
|
||||
pub fn for_element(
|
||||
node: ServoThreadSafeLayoutNode<'_>,
|
||||
context: &LayoutContext,
|
||||
) -> Option<Self> {
|
||||
pub fn for_element(node: ServoLayoutNode<'_>, context: &LayoutContext) -> Option<Self> {
|
||||
if let Some(ref data_attribute_string) = node.as_typeless_object_with_data_attribute() {
|
||||
if let Some(url) = try_to_parse_image_data_url(data_attribute_string) {
|
||||
return Self::from_image_url(
|
||||
@@ -191,7 +186,7 @@ impl ReplacedContents {
|
||||
Self::svg_kind_size(svg_data, context, node)
|
||||
} else if node
|
||||
.as_html_element()
|
||||
.is_some_and(|element| element.has_local_name(&local_name!("audio")))
|
||||
.is_some_and(|element| element.local_name() == &local_name!("audio"))
|
||||
{
|
||||
let natural_size = NaturalSizes {
|
||||
width: None,
|
||||
@@ -226,7 +221,7 @@ impl ReplacedContents {
|
||||
fn svg_kind_size(
|
||||
svg_data: SVGElementData,
|
||||
context: &LayoutContext,
|
||||
node: ServoThreadSafeLayoutNode<'_>,
|
||||
node: ServoLayoutNode<'_>,
|
||||
) -> (ReplacedContentKind, NaturalSizes) {
|
||||
let rule_cache_conditions = &mut RuleCacheConditions::default();
|
||||
|
||||
@@ -309,10 +304,7 @@ impl ReplacedContents {
|
||||
(ReplacedContentKind::SVGElement(vector_image), natural_size)
|
||||
}
|
||||
|
||||
fn from_content_property(
|
||||
node: ServoThreadSafeLayoutNode<'_>,
|
||||
context: &LayoutContext,
|
||||
) -> Option<Self> {
|
||||
fn from_content_property(node: ServoLayoutNode<'_>, context: &LayoutContext) -> Option<Self> {
|
||||
// If the `content` property is a single image URL, non-replaced boxes
|
||||
// and images get replaced with the given image.
|
||||
if let Content::Items(GenericContentItems { items, .. }) =
|
||||
@@ -330,7 +322,7 @@ impl ReplacedContents {
|
||||
}
|
||||
|
||||
pub fn from_image_url(
|
||||
node: ServoThreadSafeLayoutNode<'_>,
|
||||
node: ServoLayoutNode<'_>,
|
||||
context: &LayoutContext,
|
||||
image_url: &ComputedUrl,
|
||||
) -> Option<Self> {
|
||||
@@ -370,7 +362,7 @@ impl ReplacedContents {
|
||||
}
|
||||
|
||||
pub fn from_image(
|
||||
element: ServoThreadSafeLayoutNode<'_>,
|
||||
element: ServoLayoutNode<'_>,
|
||||
context: &LayoutContext,
|
||||
image: &ComputedImage,
|
||||
) -> Option<Self> {
|
||||
@@ -380,7 +372,7 @@ impl ReplacedContents {
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn zero_sized_invalid_image(node: ServoThreadSafeLayoutNode<'_>) -> Self {
|
||||
pub(crate) fn zero_sized_invalid_image(node: ServoLayoutNode<'_>) -> Self {
|
||||
Self {
|
||||
kind: ReplacedContentKind::Image(ImageInfo {
|
||||
image: None,
|
||||
|
||||
@@ -6,7 +6,7 @@ use std::borrow::Cow;
|
||||
use std::iter::repeat_n;
|
||||
|
||||
use atomic_refcell::AtomicRef;
|
||||
use layout_api::wrapper_traits::ThreadSafeLayoutNode;
|
||||
use layout_api::LayoutNode;
|
||||
use log::warn;
|
||||
use servo_arc::Arc;
|
||||
use style::properties::ComputedValues;
|
||||
@@ -1037,8 +1037,8 @@ impl<'dom> TraversalHandler<'dom> for TableRowBuilder<'_, '_, 'dom, '_> {
|
||||
// This value will already have filtered out rowspan=0
|
||||
// in quirks mode, so we don't have to worry about that.
|
||||
let (rowspan, colspan) = if info.pseudo_element_chain().is_empty() {
|
||||
let rowspan = info.node.get_rowspan().unwrap_or(1) as usize;
|
||||
let colspan = info.node.get_colspan().unwrap_or(1) as usize;
|
||||
let rowspan = info.node.table_rowspan().unwrap_or(1) as usize;
|
||||
let colspan = info.node.table_colspan().unwrap_or(1) as usize;
|
||||
|
||||
// The HTML specification clamps value of `rowspan` to [0, 65534] and
|
||||
// `colspan` to [1, 1000].
|
||||
@@ -1161,7 +1161,7 @@ fn add_column(
|
||||
old_column: Option<ArcRefCell<TableTrack>>,
|
||||
) -> ArcRefCell<TableTrack> {
|
||||
let span = if column_info.pseudo_element_chain().is_empty() {
|
||||
column_info.node.get_span().unwrap_or(1)
|
||||
column_info.node.table_span().unwrap_or(1)
|
||||
} else {
|
||||
1
|
||||
};
|
||||
|
||||
@@ -76,7 +76,7 @@ pub(crate) use construct::AnonymousTableContent;
|
||||
pub use construct::TableBuilder;
|
||||
use euclid::{Point2D, Size2D, UnknownUnit, Vector2D};
|
||||
use malloc_size_of_derive::MallocSizeOf;
|
||||
use script::layout_dom::{ServoLayoutElement, ServoThreadSafeLayoutNode};
|
||||
use script::layout_dom::{ServoDangerousStyleElement, ServoLayoutNode};
|
||||
use servo_arc::Arc;
|
||||
use style::context::SharedStyleContext;
|
||||
use style::properties::ComputedValues;
|
||||
@@ -203,11 +203,13 @@ impl Table {
|
||||
new_style: &Arc<ComputedValues>,
|
||||
) {
|
||||
self.style = new_style.clone();
|
||||
self.grid_style = context.stylist.style_for_anonymous::<ServoLayoutElement>(
|
||||
&context.guards,
|
||||
&PseudoElement::ServoTableGrid,
|
||||
new_style,
|
||||
);
|
||||
self.grid_style = context
|
||||
.stylist
|
||||
.style_for_anonymous::<ServoDangerousStyleElement>(
|
||||
&context.guards,
|
||||
&PseudoElement::ServoTableGrid,
|
||||
new_style,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -420,7 +422,7 @@ impl TableLevelBox {
|
||||
pub(crate) fn repair_style(
|
||||
&self,
|
||||
context: &SharedStyleContext<'_>,
|
||||
node: &ServoThreadSafeLayoutNode,
|
||||
node: &ServoLayoutNode,
|
||||
new_style: &Arc<ComputedValues>,
|
||||
) {
|
||||
match self {
|
||||
|
||||
@@ -7,7 +7,7 @@ use std::fmt;
|
||||
|
||||
use app_units::Au;
|
||||
use malloc_size_of_derive::MallocSizeOf;
|
||||
use script::layout_dom::ServoThreadSafeLayoutNode;
|
||||
use script::layout_dom::ServoLayoutNode;
|
||||
use servo_arc::Arc;
|
||||
use style::context::SharedStyleContext;
|
||||
use style::properties::ComputedValues;
|
||||
@@ -150,7 +150,7 @@ impl TaffyItemBox {
|
||||
pub(crate) fn repair_style(
|
||||
&mut self,
|
||||
context: &SharedStyleContext,
|
||||
node: &ServoThreadSafeLayoutNode,
|
||||
node: &ServoLayoutNode,
|
||||
new_style: &Arc<ComputedValues>,
|
||||
) {
|
||||
self.style = new_style.clone();
|
||||
|
||||
@@ -6,11 +6,10 @@ use std::cell::Cell;
|
||||
use std::sync::Arc;
|
||||
|
||||
use bitflags::Flags;
|
||||
use layout_api::LayoutDamage;
|
||||
use layout_api::wrapper_traits::{
|
||||
LayoutElement, LayoutNode, ThreadSafeLayoutElement, ThreadSafeLayoutNode,
|
||||
use layout_api::{
|
||||
DangerousStyleElement, DangerousStyleNode, LayoutDamage, LayoutElement, LayoutNode,
|
||||
};
|
||||
use script::layout_dom::{ServoLayoutNode, ServoThreadSafeLayoutNode};
|
||||
use script::layout_dom::ServoLayoutNode;
|
||||
use style::context::{SharedStyleContext, StyleContext};
|
||||
use style::data::ElementData;
|
||||
use style::dom::{NodeInfo, TElement, TNode};
|
||||
@@ -35,11 +34,10 @@ impl<'a> RecalcStyle<'a> {
|
||||
}
|
||||
}
|
||||
|
||||
#[expect(unsafe_code)]
|
||||
impl<'dom, E> DomTraversal<E> for RecalcStyle<'_>
|
||||
where
|
||||
E: LayoutElement<'dom> + TElement,
|
||||
E::ConcreteNode: 'dom + LayoutNode<'dom>,
|
||||
E: DangerousStyleElement<'dom> + TElement,
|
||||
E::ConcreteNode: 'dom + DangerousStyleNode<'dom>,
|
||||
{
|
||||
fn process_preorder<F>(
|
||||
&self,
|
||||
@@ -50,18 +48,15 @@ where
|
||||
) where
|
||||
F: FnMut(E::ConcreteNode),
|
||||
{
|
||||
if node.is_text_node() {
|
||||
let Some(dangerous_style_element) = node.as_element() else {
|
||||
return;
|
||||
}
|
||||
};
|
||||
|
||||
let element = node.as_element().unwrap();
|
||||
let had_style_data = element.style_data().is_some();
|
||||
let layout_element = dangerous_style_element.layout_element();
|
||||
let had_style_data = layout_element.style_data().is_some();
|
||||
layout_element.initialize_style_and_layout_data::<DOMLayoutData>();
|
||||
|
||||
unsafe {
|
||||
element.initialize_style_and_layout_data::<DOMLayoutData>();
|
||||
}
|
||||
|
||||
let mut element_data = element.mutate_data().unwrap();
|
||||
let mut element_data = dangerous_style_element.mutate_data().unwrap();
|
||||
if !had_style_data {
|
||||
element_data.damage = RestyleDamage::reconstruct();
|
||||
}
|
||||
@@ -70,13 +65,14 @@ where
|
||||
self,
|
||||
traversal_data,
|
||||
context,
|
||||
element,
|
||||
dangerous_style_element,
|
||||
&mut element_data,
|
||||
note_child,
|
||||
);
|
||||
|
||||
#[expect(unsafe_code)]
|
||||
unsafe {
|
||||
element.unset_dirty_descendants();
|
||||
dangerous_style_element.unset_dirty_descendants();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -90,7 +86,7 @@ where
|
||||
}
|
||||
|
||||
fn text_node_needs_traversal(node: E::ConcreteNode, parent_data: &ElementData) -> bool {
|
||||
node.layout_data().is_none() || !parent_data.damage.is_empty()
|
||||
node.layout_node().layout_data().is_none() || !parent_data.damage.is_empty()
|
||||
}
|
||||
|
||||
fn shared_context(&self) -> &SharedStyleContext<'_> {
|
||||
@@ -98,6 +94,7 @@ where
|
||||
}
|
||||
}
|
||||
|
||||
#[expect(unsafe_code)]
|
||||
#[servo_tracing::instrument(skip_all)]
|
||||
pub(crate) fn compute_damage_and_rebuild_box_tree(
|
||||
box_tree: &mut Option<Arc<BoxTree>>,
|
||||
@@ -108,7 +105,7 @@ pub(crate) fn compute_damage_and_rebuild_box_tree(
|
||||
) -> RestyleDamage {
|
||||
let restyle_damage = compute_damage_and_rebuild_box_tree_inner(
|
||||
layout_context,
|
||||
dirty_root.to_threadsafe(),
|
||||
dirty_root,
|
||||
damage_from_environment,
|
||||
);
|
||||
|
||||
@@ -134,14 +131,12 @@ pub(crate) fn compute_damage_and_rebuild_box_tree(
|
||||
let mut needs_box_tree_rebuild = layout_damage.needs_new_box();
|
||||
|
||||
let mut damage_for_ancestors = LayoutDamage::RECOMPUTE_INLINE_CONTENT_SIZES;
|
||||
let mut maybe_parent_node = dirty_root.traversal_parent();
|
||||
while let Some(parent_node) = maybe_parent_node {
|
||||
let threadsafe_parent_node = parent_node.as_node().to_threadsafe();
|
||||
|
||||
let mut maybe_parent_node = unsafe { dirty_root.dangerous_flat_tree_parent() };
|
||||
while let Some(parent_node) = maybe_parent_node {
|
||||
// If we need box tree reconstruction, try it here.
|
||||
if needs_box_tree_rebuild &&
|
||||
threadsafe_parent_node
|
||||
.rebuild_box_tree_from_independent_formatting_context(layout_context)
|
||||
parent_node.rebuild_box_tree_from_independent_formatting_context(layout_context)
|
||||
{
|
||||
needs_box_tree_rebuild = false;
|
||||
}
|
||||
@@ -150,12 +145,12 @@ pub(crate) fn compute_damage_and_rebuild_box_tree(
|
||||
// We have not yet found a place to run box tree reconstruction, so clear this
|
||||
// node's boxes to ensure that they are invalidated for the reconstruction we
|
||||
// will run later.
|
||||
threadsafe_parent_node.unset_all_boxes();
|
||||
parent_node.unset_all_boxes();
|
||||
} else {
|
||||
// Reconstruction has already run or was not necessary, so we just need to
|
||||
// ensure that fragment tree layout does not reuse any cached fragments.
|
||||
let new_damage_for_ancestors = Cell::new(LayoutDamage::empty());
|
||||
threadsafe_parent_node.with_layout_box_base_including_pseudos(|base| {
|
||||
parent_node.with_layout_box_base_including_pseudos(|base| {
|
||||
new_damage_for_ancestors.set(
|
||||
new_damage_for_ancestors.get() |
|
||||
base.add_damage(Default::default(), damage_for_ancestors),
|
||||
@@ -164,7 +159,7 @@ pub(crate) fn compute_damage_and_rebuild_box_tree(
|
||||
damage_for_ancestors = new_damage_for_ancestors.get();
|
||||
}
|
||||
|
||||
maybe_parent_node = parent_node.traversal_parent();
|
||||
maybe_parent_node = unsafe { parent_node.dangerous_flat_tree_parent() };
|
||||
}
|
||||
|
||||
// We could not find a place in the middle of the tree to run box tree reconstruction,
|
||||
@@ -178,7 +173,7 @@ pub(crate) fn compute_damage_and_rebuild_box_tree(
|
||||
|
||||
pub(crate) fn compute_damage_and_rebuild_box_tree_inner(
|
||||
layout_context: &LayoutContext,
|
||||
node: ServoThreadSafeLayoutNode<'_>,
|
||||
node: ServoLayoutNode<'_>,
|
||||
damage_from_parent: RestyleDamage,
|
||||
) -> RestyleDamage {
|
||||
// Don't do any kind of damage propagation or box tree constuction for non-Element nodes,
|
||||
@@ -224,7 +219,7 @@ pub(crate) fn compute_damage_and_rebuild_box_tree_inner(
|
||||
}
|
||||
|
||||
let mut damage_from_children = RestyleDamage::empty();
|
||||
for child in node.children() {
|
||||
for child in node.flat_tree_children() {
|
||||
if child.is_element() {
|
||||
damage_from_children |= compute_damage_and_rebuild_box_tree_inner(
|
||||
layout_context,
|
||||
|
||||
@@ -1054,6 +1054,27 @@ pub(crate) fn get_attr_for_layout<'dom>(
|
||||
}
|
||||
|
||||
impl<'dom> LayoutDom<'dom, Element> {
|
||||
#[inline]
|
||||
pub(crate) fn is_root(&self) -> bool {
|
||||
self.upcast::<Node>()
|
||||
.parent_node_ref()
|
||||
.is_some_and(|parent| matches!(parent.type_id_for_layout(), NodeTypeId::Document(_)))
|
||||
}
|
||||
|
||||
/// Returns true if this element is the body child of an html element root element.
|
||||
pub(crate) fn is_body_element_of_html_element_root(&self) -> bool {
|
||||
if self.local_name() != &local_name!("body") {
|
||||
return false;
|
||||
}
|
||||
let Some(parent_node) = self.upcast::<Node>().parent_node_ref() else {
|
||||
return false;
|
||||
};
|
||||
let Some(parent_element) = parent_node.downcast::<Element>() else {
|
||||
return false;
|
||||
};
|
||||
parent_element.local_name() == &local_name!("html")
|
||||
}
|
||||
|
||||
#[expect(unsafe_code)]
|
||||
#[inline]
|
||||
pub(crate) fn attrs(self) -> &'dom [LayoutDom<'dom, Attr>] {
|
||||
|
||||
@@ -11,7 +11,7 @@ use fonts::{ByteIndex, TextByteRange};
|
||||
use html5ever::{LocalName, Prefix, local_name, ns};
|
||||
use js::context::JSContext;
|
||||
use js::rust::HandleObject;
|
||||
use layout_api::wrapper_traits::{ScriptSelection, SharedSelection};
|
||||
use layout_api::{ScriptSelection, SharedSelection};
|
||||
use servo_base::text::Utf16CodeUnitLength;
|
||||
use style::attr::AttrValue;
|
||||
use stylo_dom::ElementState;
|
||||
|
||||
@@ -19,7 +19,7 @@ use js::jsapi::{
|
||||
use js::jsval::UndefinedValue;
|
||||
use js::rust::wrappers::{CheckRegExpSyntax, ExecuteRegExpNoStatics, ObjectIsRegExp};
|
||||
use js::rust::{HandleObject, MutableHandleObject};
|
||||
use layout_api::wrapper_traits::{ScriptSelection, SharedSelection};
|
||||
use layout_api::{ScriptSelection, SharedSelection};
|
||||
use script_bindings::codegen::GenericBindings::AttrBinding::AttrMethods;
|
||||
use script_bindings::domstring::parse_floating_point_number;
|
||||
use servo_base::generic_channel::GenericSender;
|
||||
|
||||
@@ -26,11 +26,10 @@ use js::context::{JSContext, NoGC};
|
||||
use js::jsapi::JSObject;
|
||||
use js::rust::HandleObject;
|
||||
use keyboard_types::Modifiers;
|
||||
use layout_api::wrapper_traits::SharedSelection;
|
||||
use layout_api::{
|
||||
AxesOverflow, BoxAreaType, CSSPixelRectIterator, GenericLayoutData, HTMLCanvasData,
|
||||
HTMLMediaData, LayoutElementType, LayoutNodeType, PhysicalSides, SVGElementData,
|
||||
TrustedNodeAddress, with_layout_state,
|
||||
SharedSelection, TrustedNodeAddress, with_layout_state,
|
||||
};
|
||||
use libc::{self, c_void, uintptr_t};
|
||||
use malloc_size_of::{MallocSizeOf, MallocSizeOfOps};
|
||||
@@ -131,7 +130,7 @@ use crate::dom::text::Text;
|
||||
use crate::dom::types::{CDATASection, KeyboardEvent};
|
||||
use crate::dom::virtualmethods::{VirtualMethods, vtable_for};
|
||||
use crate::dom::window::Window;
|
||||
use crate::layout_dom::ServoLayoutNode;
|
||||
use crate::layout_dom::ServoDangerousStyleNode;
|
||||
use crate::script_runtime::CanGc;
|
||||
use crate::script_thread::ScriptThread;
|
||||
|
||||
@@ -1542,12 +1541,12 @@ impl Node {
|
||||
|
||||
let first_matching_element = with_layout_state(|| {
|
||||
let layout_node = unsafe { traced_node.to_layout() };
|
||||
ServoLayoutNode::from_layout_dom(layout_node)
|
||||
ServoDangerousStyleNode::from(layout_node)
|
||||
.scope_match_a_selectors_string::<QueryFirst>(document_url, &selectors.str())
|
||||
})?;
|
||||
|
||||
Ok(first_matching_element
|
||||
.map(|element| DomRoot::from_ref(unsafe { element.to_layout_dom().as_ref() })))
|
||||
.map(|element| DomRoot::from_ref(unsafe { element.layout_dom().as_ref() })))
|
||||
}
|
||||
|
||||
/// <https://dom.spec.whatwg.org/#dom-parentnode-queryselectorall>
|
||||
@@ -1562,12 +1561,12 @@ impl Node {
|
||||
let traced_node = Dom::from_ref(self);
|
||||
let matching_elements = with_layout_state(|| {
|
||||
let layout_node = unsafe { traced_node.to_layout() };
|
||||
ServoLayoutNode::from_layout_dom(layout_node)
|
||||
ServoDangerousStyleNode::from(layout_node)
|
||||
.scope_match_a_selectors_string::<QueryAll>(document_url, &selectors.str())
|
||||
})?;
|
||||
let iter = matching_elements
|
||||
.into_iter()
|
||||
.map(|element| DomRoot::from_ref(unsafe { element.to_layout_dom().as_ref() }))
|
||||
.map(|element| DomRoot::from_ref(unsafe { element.layout_dom().as_ref() }))
|
||||
.map(DomRoot::upcast::<Node>);
|
||||
|
||||
// NodeList::new_simple_list immediately collects the iterator, so we're not leaking LayoutDom
|
||||
@@ -2112,6 +2111,18 @@ impl<'dom> LayoutDom<'dom, Node> {
|
||||
parent
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub(crate) fn traversal_parent(self) -> Option<LayoutDom<'dom, Element>> {
|
||||
if let Some(assigned_slot) = self.assigned_slot_for_layout() {
|
||||
return Some(assigned_slot.upcast());
|
||||
}
|
||||
let parent = self.parent_node_ref()?;
|
||||
if let Some(shadow) = parent.downcast::<ShadowRoot>() {
|
||||
return Some(shadow.get_host_for_layout());
|
||||
};
|
||||
parent.downcast()
|
||||
}
|
||||
|
||||
#[inline]
|
||||
#[expect(unsafe_code)]
|
||||
pub(crate) fn first_child_ref(self) -> Option<LayoutDom<'dom, Node>> {
|
||||
|
||||
101
components/script/layout_dom/iterators.rs
Normal file
101
components/script/layout_dom/iterators.rs
Normal file
@@ -0,0 +1,101 @@
|
||||
/* 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::iter::FusedIterator;
|
||||
|
||||
use layout_api::{DangerousStyleNode, LayoutElement, LayoutNode};
|
||||
use style::dom::{DomChildren, TElement, TShadowRoot};
|
||||
|
||||
use crate::layout_dom::{ServoDangerousStyleElement, ServoDangerousStyleNode, ServoLayoutNode};
|
||||
|
||||
pub struct ReverseChildrenIterator<'dom> {
|
||||
current: Option<ServoLayoutNode<'dom>>,
|
||||
}
|
||||
|
||||
impl<'dom> Iterator for ReverseChildrenIterator<'dom> {
|
||||
type Item = ServoLayoutNode<'dom>;
|
||||
|
||||
#[expect(unsafe_code)]
|
||||
fn next(&mut self) -> Option<Self::Item> {
|
||||
let node = self.current;
|
||||
self.current = node.and_then(|node| unsafe { node.dangerous_previous_sibling() });
|
||||
node
|
||||
}
|
||||
}
|
||||
|
||||
pub enum ServoLayoutNodeChildrenIterator<'dom> {
|
||||
/// Iterating over the children of a node
|
||||
Node(Option<ServoLayoutNode<'dom>>),
|
||||
/// Iterating over the assigned nodes of a `HTMLSlotElement`
|
||||
Slottables(<Vec<ServoDangerousStyleNode<'dom>> as IntoIterator>::IntoIter),
|
||||
}
|
||||
|
||||
impl<'dom> ServoLayoutNodeChildrenIterator<'dom> {
|
||||
#[expect(unsafe_code)]
|
||||
pub(super) fn new_for_flat_tree(parent: ServoLayoutNode<'dom>) -> Self {
|
||||
if let Some(element) = parent.as_element() {
|
||||
if let Some(shadow) = element.shadow_root() {
|
||||
return Self::new_for_flat_tree(shadow.as_node().layout_node());
|
||||
};
|
||||
|
||||
let element = unsafe { element.dangerous_style_element() };
|
||||
let slotted_nodes = element.slotted_nodes();
|
||||
if !slotted_nodes.is_empty() {
|
||||
#[expect(clippy::unnecessary_to_owned)] // Clippy is wrong.
|
||||
return Self::Slottables(slotted_nodes.to_owned().into_iter());
|
||||
}
|
||||
}
|
||||
|
||||
Self::Node(unsafe { parent.dangerous_first_child() })
|
||||
}
|
||||
|
||||
#[expect(unsafe_code)]
|
||||
pub(super) fn new_for_dom_tree(parent: ServoLayoutNode<'dom>) -> Self {
|
||||
Self::Node(unsafe { parent.dangerous_first_child() })
|
||||
}
|
||||
}
|
||||
|
||||
impl<'dom> Iterator for ServoLayoutNodeChildrenIterator<'dom> {
|
||||
type Item = ServoLayoutNode<'dom>;
|
||||
|
||||
fn next(&mut self) -> Option<Self::Item> {
|
||||
match self {
|
||||
Self::Node(node) => {
|
||||
#[expect(unsafe_code)]
|
||||
let next_sibling = unsafe { (*node)?.dangerous_next_sibling() };
|
||||
std::mem::replace(node, next_sibling)
|
||||
},
|
||||
Self::Slottables(slots) => slots.next().map(|node| node.layout_node()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl FusedIterator for ServoLayoutNodeChildrenIterator<'_> {}
|
||||
|
||||
pub enum DOMDescendantIterator<'dom> {
|
||||
/// Iterating over the children of a node, including children of a potential
|
||||
/// [ShadowRoot](crate::dom::shadow_root::ShadowRoot)
|
||||
Children(DomChildren<ServoDangerousStyleNode<'dom>>),
|
||||
/// Iterating over the content's of a [`<slot>`](HTMLSlotElement) element.
|
||||
Slottables {
|
||||
slot: ServoDangerousStyleElement<'dom>,
|
||||
index: usize,
|
||||
},
|
||||
}
|
||||
|
||||
impl<'dom> Iterator for DOMDescendantIterator<'dom> {
|
||||
type Item = ServoDangerousStyleNode<'dom>;
|
||||
|
||||
fn next(&mut self) -> Option<Self::Item> {
|
||||
match self {
|
||||
Self::Children(children) => children.next(),
|
||||
Self::Slottables { slot, index } => {
|
||||
let slottables = slot.slotted_nodes();
|
||||
let slot = slottables.get(*index)?;
|
||||
*index += 1;
|
||||
Some(*slot)
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -19,14 +19,18 @@
|
||||
//! will race and cause spurious thread failure. (Note that I do not believe these races are
|
||||
//! exploitable, but they'll result in brokenness nonetheless.)
|
||||
|
||||
#![expect(unsafe_code)]
|
||||
mod iterators;
|
||||
mod servo_dangerous_style_document;
|
||||
mod servo_dangerous_style_element;
|
||||
mod servo_dangerous_style_node;
|
||||
mod servo_dangerous_style_shadow_root;
|
||||
mod servo_layout_element;
|
||||
mod servo_layout_node;
|
||||
|
||||
mod document;
|
||||
mod element;
|
||||
mod node;
|
||||
mod shadow_root;
|
||||
|
||||
pub use document::*;
|
||||
pub use element::*;
|
||||
pub use node::*;
|
||||
pub use shadow_root::*;
|
||||
pub use iterators::*;
|
||||
pub use servo_dangerous_style_document::*;
|
||||
pub use servo_dangerous_style_element::*;
|
||||
pub use servo_dangerous_style_node::*;
|
||||
pub use servo_dangerous_style_shadow_root::*;
|
||||
pub use servo_layout_element::*;
|
||||
pub use servo_layout_node::*;
|
||||
|
||||
@@ -1,562 +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/. */
|
||||
|
||||
#![expect(unsafe_code)]
|
||||
|
||||
use std::borrow::Cow;
|
||||
use std::fmt;
|
||||
use std::iter::FusedIterator;
|
||||
|
||||
use layout_api::wrapper_traits::{
|
||||
LayoutDataTrait, LayoutNode, PseudoElementChain, SharedSelection, ThreadSafeLayoutElement,
|
||||
ThreadSafeLayoutNode,
|
||||
};
|
||||
use layout_api::{
|
||||
GenericLayoutData, HTMLCanvasData, HTMLMediaData, LayoutElementType, LayoutNodeType,
|
||||
SVGElementData, TrustedNodeAddress,
|
||||
};
|
||||
use net_traits::image_cache::Image;
|
||||
use pixels::ImageMetadata;
|
||||
use script_bindings::error::Fallible;
|
||||
use selectors::Element as _;
|
||||
use servo_arc::Arc;
|
||||
use servo_base::id::{BrowsingContextId, PipelineId};
|
||||
use servo_url::ServoUrl;
|
||||
use style;
|
||||
use style::context::SharedStyleContext;
|
||||
use style::dom::{NodeInfo, TElement, TNode, TShadowRoot};
|
||||
use style::dom_apis::{MayUseInvalidation, SelectorQuery, query_selector};
|
||||
use style::properties::ComputedValues;
|
||||
use style::selector_parser::{PseudoElement, SelectorParser};
|
||||
use style::stylesheets::UrlExtraData;
|
||||
use url::Url;
|
||||
|
||||
use super::{
|
||||
ServoLayoutDocument, ServoLayoutElement, ServoShadowRoot, ServoThreadSafeLayoutElement,
|
||||
};
|
||||
use crate::dom::bindings::error::Error;
|
||||
use crate::dom::bindings::inheritance::NodeTypeId;
|
||||
use crate::dom::bindings::root::LayoutDom;
|
||||
use crate::dom::element::Element;
|
||||
use crate::dom::node::{Node, NodeFlags, NodeTypeIdWrapper};
|
||||
|
||||
/// A wrapper around a `LayoutDom<Node>` which provides a safe interface that
|
||||
/// can be used during layout. This implements the `LayoutNode` trait as well as
|
||||
/// several style and selectors traits for use during layout. This version
|
||||
/// should only be used on a single thread. If you need to use nodes across
|
||||
/// threads use ServoThreadSafeLayoutNode.
|
||||
#[derive(Clone, Copy, PartialEq)]
|
||||
#[repr(transparent)]
|
||||
pub struct ServoLayoutNode<'dom> {
|
||||
/// The wrapped private DOM node.
|
||||
pub(super) node: LayoutDom<'dom, Node>,
|
||||
}
|
||||
|
||||
/// Those are supposed to be sound, but they aren't because the entire system
|
||||
/// between script and layout so far has been designed to work around their
|
||||
/// absence. Switching the entire thing to the inert crate infra will help.
|
||||
///
|
||||
/// FIXME(mrobinson): These are required because Layout 2020 sends non-threadsafe
|
||||
/// nodes to different threads. This should be adressed in a comprehensive way.
|
||||
unsafe impl Send for ServoLayoutNode<'_> {}
|
||||
unsafe impl Sync for ServoLayoutNode<'_> {}
|
||||
|
||||
impl fmt::Debug for ServoLayoutNode<'_> {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
if let Some(element) = self.as_element() {
|
||||
element.fmt(f)
|
||||
} else if self.is_text_node() {
|
||||
write!(f, "<text node> ({:#x})", self.opaque().0)
|
||||
} else {
|
||||
write!(f, "<non-text node> ({:#x})", self.opaque().0)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'dom> ServoLayoutNode<'dom> {
|
||||
pub(crate) fn from_layout_dom(node: LayoutDom<'dom, Node>) -> Self {
|
||||
ServoLayoutNode { node }
|
||||
}
|
||||
|
||||
/// Create a new [`ServoLayoutNode`] for this given [`TrustedNodeAddress`].
|
||||
///
|
||||
/// # Safety
|
||||
///
|
||||
/// The address pointed to by `address` should point to a valid node in memory.
|
||||
pub unsafe fn new(address: &TrustedNodeAddress) -> Self {
|
||||
let node = unsafe { LayoutDom::from_trusted_node_address(*address) };
|
||||
ServoLayoutNode::from_layout_dom(node)
|
||||
}
|
||||
|
||||
pub(super) fn script_type_id(&self) -> NodeTypeId {
|
||||
self.node.type_id_for_layout()
|
||||
}
|
||||
|
||||
/// Returns the interior of this node as a `LayoutDom`.
|
||||
///
|
||||
/// This method must never be exposed to layout as it returns
|
||||
/// a `LayoutDom`.
|
||||
pub(crate) fn to_layout_dom(self) -> LayoutDom<'dom, Node> {
|
||||
self.node
|
||||
}
|
||||
|
||||
pub(crate) fn assigned_slot(self) -> Option<ServoLayoutElement<'dom>> {
|
||||
self.node
|
||||
.assigned_slot_for_layout()
|
||||
.as_ref()
|
||||
.map(LayoutDom::upcast)
|
||||
.map(ServoLayoutElement::from_layout_dom)
|
||||
}
|
||||
|
||||
/// <https://dom.spec.whatwg.org/#scope-match-a-selectors-string>
|
||||
pub(crate) fn scope_match_a_selectors_string<Query>(
|
||||
self,
|
||||
document_url: Arc<Url>,
|
||||
selector: &str,
|
||||
) -> Fallible<Query::Output>
|
||||
where
|
||||
Query: SelectorQuery<ServoLayoutElement<'dom>>,
|
||||
Query::Output: Default,
|
||||
{
|
||||
let mut result = Query::Output::default();
|
||||
|
||||
// Step 1. Let selector be the result of parse a selector selectors.
|
||||
let selector_or_error =
|
||||
SelectorParser::parse_author_origin_no_namespace(selector, &UrlExtraData(document_url));
|
||||
|
||||
// Step 2. If selector is failure, then throw a "SyntaxError" DOMException.
|
||||
let Ok(selector_list) = selector_or_error else {
|
||||
return Err(Error::Syntax(None));
|
||||
};
|
||||
|
||||
// Step 3. Return the result of match a selector against a tree with selector
|
||||
// and node’s root using scoping root node.
|
||||
query_selector::<ServoLayoutElement<'dom>, Query>(
|
||||
self,
|
||||
&selector_list,
|
||||
&mut result,
|
||||
MayUseInvalidation::No,
|
||||
);
|
||||
|
||||
Ok(result)
|
||||
}
|
||||
}
|
||||
|
||||
impl style::dom::NodeInfo for ServoLayoutNode<'_> {
|
||||
fn is_element(&self) -> bool {
|
||||
self.node.is_element_for_layout()
|
||||
}
|
||||
|
||||
fn is_text_node(&self) -> bool {
|
||||
self.node.is_text_node_for_layout()
|
||||
}
|
||||
}
|
||||
|
||||
impl<'dom> style::dom::TNode for ServoLayoutNode<'dom> {
|
||||
type ConcreteDocument = ServoLayoutDocument<'dom>;
|
||||
type ConcreteElement = ServoLayoutElement<'dom>;
|
||||
type ConcreteShadowRoot = ServoShadowRoot<'dom>;
|
||||
|
||||
fn parent_node(&self) -> Option<Self> {
|
||||
self.node.parent_node_ref().map(Self::from_layout_dom)
|
||||
}
|
||||
|
||||
fn first_child(&self) -> Option<Self> {
|
||||
self.node.first_child_ref().map(Self::from_layout_dom)
|
||||
}
|
||||
|
||||
fn last_child(&self) -> Option<Self> {
|
||||
self.node.last_child_ref().map(Self::from_layout_dom)
|
||||
}
|
||||
|
||||
fn prev_sibling(&self) -> Option<Self> {
|
||||
self.node.prev_sibling_ref().map(Self::from_layout_dom)
|
||||
}
|
||||
|
||||
fn next_sibling(&self) -> Option<Self> {
|
||||
self.node.next_sibling_ref().map(Self::from_layout_dom)
|
||||
}
|
||||
|
||||
fn owner_doc(&self) -> Self::ConcreteDocument {
|
||||
ServoLayoutDocument::from_layout_dom(self.node.owner_doc_for_layout())
|
||||
}
|
||||
|
||||
fn traversal_parent(&self) -> Option<ServoLayoutElement<'dom>> {
|
||||
if let Some(assigned_slot) = self.assigned_slot() {
|
||||
return Some(assigned_slot);
|
||||
}
|
||||
let parent = self.parent_node()?;
|
||||
if let Some(shadow) = parent.as_shadow_root() {
|
||||
return Some(shadow.host());
|
||||
};
|
||||
parent.as_element()
|
||||
}
|
||||
|
||||
fn opaque(&self) -> style::dom::OpaqueNode {
|
||||
self.to_layout_dom().opaque()
|
||||
}
|
||||
|
||||
fn debug_id(self) -> usize {
|
||||
self.opaque().0
|
||||
}
|
||||
|
||||
fn as_element(&self) -> Option<ServoLayoutElement<'dom>> {
|
||||
self.node
|
||||
.downcast()
|
||||
.map(ServoLayoutElement::from_layout_dom)
|
||||
}
|
||||
|
||||
fn as_document(&self) -> Option<ServoLayoutDocument<'dom>> {
|
||||
self.node
|
||||
.downcast()
|
||||
.map(ServoLayoutDocument::from_layout_dom)
|
||||
}
|
||||
|
||||
fn as_shadow_root(&self) -> Option<ServoShadowRoot<'dom>> {
|
||||
self.node.downcast().map(ServoShadowRoot::from_layout_dom)
|
||||
}
|
||||
|
||||
fn is_in_document(&self) -> bool {
|
||||
unsafe { self.node.get_flag(NodeFlags::IS_IN_A_DOCUMENT_TREE) }
|
||||
}
|
||||
}
|
||||
|
||||
impl<'dom> LayoutNode<'dom> for ServoLayoutNode<'dom> {
|
||||
type ConcreteThreadSafeLayoutNode = ServoThreadSafeLayoutNode<'dom>;
|
||||
type ConcreteLayoutElement = ServoLayoutElement<'dom>;
|
||||
|
||||
fn to_threadsafe(&self) -> Self::ConcreteThreadSafeLayoutNode {
|
||||
ServoThreadSafeLayoutNode::new(*self)
|
||||
}
|
||||
|
||||
fn type_id(&self) -> LayoutNodeType {
|
||||
NodeTypeIdWrapper(self.script_type_id()).into()
|
||||
}
|
||||
|
||||
fn is_connected(&self) -> bool {
|
||||
unsafe { self.node.get_flag(NodeFlags::IS_CONNECTED) }
|
||||
}
|
||||
|
||||
fn layout_data(&self) -> Option<&'dom GenericLayoutData> {
|
||||
self.to_layout_dom().layout_data()
|
||||
}
|
||||
}
|
||||
|
||||
/// A wrapper around a `ServoLayoutNode` that can be used safely on different threads.
|
||||
/// It's very important that this never mutate anything except this wrapped node and
|
||||
/// never access any other node apart from its parent.
|
||||
#[derive(Clone, Copy, Debug, PartialEq)]
|
||||
pub struct ServoThreadSafeLayoutNode<'dom> {
|
||||
/// The wrapped [`ServoLayoutNode`].
|
||||
pub(super) node: ServoLayoutNode<'dom>,
|
||||
|
||||
/// The possibly nested [`PseudoElementChain`] for this node.
|
||||
pub(super) pseudo_element_chain: PseudoElementChain,
|
||||
}
|
||||
|
||||
impl<'dom> ServoThreadSafeLayoutNode<'dom> {
|
||||
/// Creates a new `ServoThreadSafeLayoutNode` from the given `ServoLayoutNode`.
|
||||
pub fn new(node: ServoLayoutNode<'dom>) -> Self {
|
||||
ServoThreadSafeLayoutNode {
|
||||
node,
|
||||
pseudo_element_chain: Default::default(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns the interior of this node as a `LayoutDom`. This is highly unsafe for layout to
|
||||
/// call and as such is marked `unsafe`.
|
||||
unsafe fn get_jsmanaged(&self) -> LayoutDom<'dom, Node> {
|
||||
self.node.to_layout_dom()
|
||||
}
|
||||
|
||||
/// Get the first child of this node. Important: this is not safe for
|
||||
/// layout to call, so it should *never* be made public.
|
||||
unsafe fn dangerous_first_child(&self) -> Option<Self> {
|
||||
let js_managed = unsafe { self.get_jsmanaged() };
|
||||
js_managed
|
||||
.first_child_ref()
|
||||
.map(ServoLayoutNode::from_layout_dom)
|
||||
.map(Self::new)
|
||||
}
|
||||
|
||||
/// Get the next sibling of this node. Important: this is not safe for
|
||||
/// layout to call, so it should *never* be made public.
|
||||
unsafe fn dangerous_next_sibling(&self) -> Option<Self> {
|
||||
let js_managed = unsafe { self.get_jsmanaged() };
|
||||
js_managed
|
||||
.next_sibling_ref()
|
||||
.map(ServoLayoutNode::from_layout_dom)
|
||||
.map(Self::new)
|
||||
}
|
||||
|
||||
/// Whether this is a container for the text within a single-line text input. This
|
||||
/// is used to solve the special case of line height for a text entry widget.
|
||||
/// <https://html.spec.whatwg.org/multipage/#the-input-element-as-a-text-entry-widget>
|
||||
// TODO(stevennovaryo): Remove the addition of HTMLInputElement here once all of the
|
||||
// input element is implemented with UA shadow DOM. This is temporary
|
||||
// workaround for past version of input element where we are
|
||||
// rendering it as a bare html element.
|
||||
pub fn is_single_line_text_input(&self) -> bool {
|
||||
self.type_id() == Some(LayoutNodeType::Element(LayoutElementType::HTMLInputElement)) ||
|
||||
(self.pseudo_element_chain.is_empty() &&
|
||||
self.node.node.is_text_container_of_single_line_input())
|
||||
}
|
||||
|
||||
pub fn selected_style(&self, context: &SharedStyleContext) -> Arc<ComputedValues> {
|
||||
let Some(element) = self.as_element() else {
|
||||
// TODO(stshine): What should the selected style be for text?
|
||||
debug_assert!(self.is_text_node());
|
||||
return self.parent_style(context);
|
||||
};
|
||||
|
||||
let style_data = &element.element_data().styles;
|
||||
let get_selected_style = || {
|
||||
// This is a workaround for handling the `::selection` pseudos where it would not
|
||||
// propagate to the children and Shadow DOM elements. For this case, UA widget
|
||||
// inner elements should follow the originating element in terms of selection.
|
||||
if self.node.node.is_in_ua_widget() {
|
||||
return Some(
|
||||
element
|
||||
.containing_shadow_host()?
|
||||
.as_node()
|
||||
.selected_style(context),
|
||||
);
|
||||
}
|
||||
style_data.pseudos.get(&PseudoElement::Selection).cloned()
|
||||
};
|
||||
|
||||
get_selected_style().unwrap_or_else(|| style_data.primary().clone())
|
||||
}
|
||||
}
|
||||
|
||||
impl style::dom::NodeInfo for ServoThreadSafeLayoutNode<'_> {
|
||||
fn is_element(&self) -> bool {
|
||||
self.node.is_element()
|
||||
}
|
||||
|
||||
fn is_text_node(&self) -> bool {
|
||||
self.node.is_text_node()
|
||||
}
|
||||
}
|
||||
|
||||
impl<'dom> ThreadSafeLayoutNode<'dom> for ServoThreadSafeLayoutNode<'dom> {
|
||||
type ConcreteNode = ServoLayoutNode<'dom>;
|
||||
type ConcreteThreadSafeLayoutElement = ServoThreadSafeLayoutElement<'dom>;
|
||||
type ConcreteElement = ServoLayoutElement<'dom>;
|
||||
type ChildrenIterator = ServoThreadSafeLayoutNodeChildrenIterator<'dom>;
|
||||
|
||||
fn opaque(&self) -> style::dom::OpaqueNode {
|
||||
unsafe { self.get_jsmanaged().opaque() }
|
||||
}
|
||||
|
||||
fn pseudo_element_chain(&self) -> PseudoElementChain {
|
||||
self.pseudo_element_chain
|
||||
}
|
||||
|
||||
fn type_id(&self) -> Option<LayoutNodeType> {
|
||||
if self.pseudo_element_chain.is_empty() {
|
||||
Some(self.node.type_id())
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
fn parent_style(&self, context: &SharedStyleContext) -> Arc<ComputedValues> {
|
||||
if let Some(chain) = self.pseudo_element_chain.without_innermost() {
|
||||
let mut parent = *self;
|
||||
parent.pseudo_element_chain = chain;
|
||||
return parent.style(context);
|
||||
}
|
||||
let parent_element = self.node.traversal_parent().unwrap();
|
||||
let parent_data = parent_element.borrow_data().unwrap();
|
||||
parent_data.styles.primary().clone()
|
||||
}
|
||||
|
||||
fn initialize_layout_data<RequestedLayoutDataType: LayoutDataTrait>(&self) {
|
||||
let inner = self.node.to_layout_dom();
|
||||
if inner.layout_data().is_none() {
|
||||
unsafe {
|
||||
inner.initialize_layout_data(Box::<RequestedLayoutDataType>::default());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn debug_id(self) -> usize {
|
||||
self.node.debug_id()
|
||||
}
|
||||
|
||||
fn children(&self) -> style::dom::LayoutIterator<Self::ChildrenIterator> {
|
||||
style::dom::LayoutIterator(ServoThreadSafeLayoutNodeChildrenIterator::new(*self))
|
||||
}
|
||||
|
||||
fn as_element(&self) -> Option<ServoThreadSafeLayoutElement<'dom>> {
|
||||
self.node
|
||||
.node
|
||||
.downcast()
|
||||
.map(|element| ServoThreadSafeLayoutElement {
|
||||
element: ServoLayoutElement::from_layout_dom(element),
|
||||
pseudo_element_chain: self.pseudo_element_chain,
|
||||
})
|
||||
}
|
||||
|
||||
fn as_html_element(&self) -> Option<ServoThreadSafeLayoutElement<'dom>> {
|
||||
self.as_element()
|
||||
.filter(|element| element.element.is_html_element())
|
||||
}
|
||||
|
||||
fn layout_data(&self) -> Option<&'dom GenericLayoutData> {
|
||||
self.node.layout_data()
|
||||
}
|
||||
|
||||
fn unsafe_get(self) -> Self::ConcreteNode {
|
||||
self.node
|
||||
}
|
||||
|
||||
fn text_content(self) -> Cow<'dom, str> {
|
||||
unsafe { self.get_jsmanaged().text_content() }
|
||||
}
|
||||
|
||||
fn selection(&self) -> Option<SharedSelection> {
|
||||
let this = unsafe { self.get_jsmanaged() };
|
||||
this.selection()
|
||||
}
|
||||
|
||||
fn image_url(&self) -> Option<ServoUrl> {
|
||||
let this = unsafe { self.get_jsmanaged() };
|
||||
this.image_url()
|
||||
}
|
||||
|
||||
fn image_density(&self) -> Option<f64> {
|
||||
let this = unsafe { self.get_jsmanaged() };
|
||||
this.image_density()
|
||||
}
|
||||
|
||||
fn showing_broken_image_icon(&self) -> bool {
|
||||
let this = unsafe { self.get_jsmanaged() };
|
||||
this.showing_broken_image_icon()
|
||||
}
|
||||
|
||||
fn image_data(&self) -> Option<(Option<Image>, Option<ImageMetadata>)> {
|
||||
let this = unsafe { self.get_jsmanaged() };
|
||||
this.image_data()
|
||||
}
|
||||
|
||||
fn canvas_data(&self) -> Option<HTMLCanvasData> {
|
||||
let this = unsafe { self.get_jsmanaged() };
|
||||
this.canvas_data()
|
||||
}
|
||||
|
||||
fn media_data(&self) -> Option<HTMLMediaData> {
|
||||
let this = unsafe { self.get_jsmanaged() };
|
||||
this.media_data()
|
||||
}
|
||||
|
||||
fn svg_data(&self) -> Option<SVGElementData<'dom>> {
|
||||
let this = unsafe { self.get_jsmanaged() };
|
||||
this.svg_data()
|
||||
}
|
||||
|
||||
// Can return None if the iframe has no nested browsing context
|
||||
fn iframe_browsing_context_id(&self) -> Option<BrowsingContextId> {
|
||||
let this = unsafe { self.get_jsmanaged() };
|
||||
this.iframe_browsing_context_id()
|
||||
}
|
||||
|
||||
// Can return None if the iframe has no nested browsing context
|
||||
fn iframe_pipeline_id(&self) -> Option<PipelineId> {
|
||||
let this = unsafe { self.get_jsmanaged() };
|
||||
this.iframe_pipeline_id()
|
||||
}
|
||||
|
||||
fn get_span(&self) -> Option<u32> {
|
||||
unsafe {
|
||||
self.get_jsmanaged()
|
||||
.downcast::<Element>()
|
||||
.unwrap()
|
||||
.get_span()
|
||||
}
|
||||
}
|
||||
|
||||
fn get_colspan(&self) -> Option<u32> {
|
||||
unsafe {
|
||||
self.get_jsmanaged()
|
||||
.downcast::<Element>()
|
||||
.unwrap()
|
||||
.get_colspan()
|
||||
}
|
||||
}
|
||||
|
||||
fn get_rowspan(&self) -> Option<u32> {
|
||||
unsafe {
|
||||
self.get_jsmanaged()
|
||||
.downcast::<Element>()
|
||||
.unwrap()
|
||||
.get_rowspan()
|
||||
}
|
||||
}
|
||||
|
||||
fn with_pseudo_element_chain(&self, pseudo_element_chain: PseudoElementChain) -> Self {
|
||||
Self {
|
||||
node: self.node,
|
||||
pseudo_element_chain,
|
||||
}
|
||||
}
|
||||
|
||||
/// # Safety
|
||||
///
|
||||
/// This function accesses and modifies the underlying DOM object and should
|
||||
/// not be used by more than a single thread at once.
|
||||
fn set_uses_content_attribute_with_attr(&self, uses_content_attribute_with_attr: bool) {
|
||||
unsafe {
|
||||
self.node.node.set_flag(
|
||||
NodeFlags::USES_ATTR_IN_CONTENT_ATTRIBUTE,
|
||||
uses_content_attribute_with_attr,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub enum ServoThreadSafeLayoutNodeChildrenIterator<'dom> {
|
||||
/// Iterating over the children of a node
|
||||
Node(Option<ServoThreadSafeLayoutNode<'dom>>),
|
||||
/// Iterating over the assigned nodes of a `HTMLSlotElement`
|
||||
Slottables(<Vec<ServoLayoutNode<'dom>> as IntoIterator>::IntoIter),
|
||||
}
|
||||
|
||||
impl<'dom> ServoThreadSafeLayoutNodeChildrenIterator<'dom> {
|
||||
#[expect(unsafe_code)]
|
||||
fn new(
|
||||
parent: ServoThreadSafeLayoutNode<'dom>,
|
||||
) -> ServoThreadSafeLayoutNodeChildrenIterator<'dom> {
|
||||
if let Some(element) = parent.as_element() {
|
||||
if let Some(shadow) = element.shadow_root() {
|
||||
return Self::new(shadow.as_node().to_threadsafe());
|
||||
};
|
||||
|
||||
let slotted_nodes = element.slotted_nodes();
|
||||
if !slotted_nodes.is_empty() {
|
||||
#[expect(clippy::unnecessary_to_owned)] // Clippy is wrong.
|
||||
return Self::Slottables(slotted_nodes.to_owned().into_iter());
|
||||
}
|
||||
}
|
||||
|
||||
Self::Node(unsafe { parent.dangerous_first_child() })
|
||||
}
|
||||
}
|
||||
|
||||
impl<'dom> Iterator for ServoThreadSafeLayoutNodeChildrenIterator<'dom> {
|
||||
type Item = ServoThreadSafeLayoutNode<'dom>;
|
||||
|
||||
fn next(&mut self) -> Option<Self::Item> {
|
||||
match self {
|
||||
Self::Node(node) => {
|
||||
let next_sibling = unsafe { (*node)?.dangerous_next_sibling() };
|
||||
std::mem::replace(node, next_sibling)
|
||||
},
|
||||
Self::Slottables(slots) => slots.next().map(|node| node.to_threadsafe()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl FusedIterator for ServoThreadSafeLayoutNodeChildrenIterator<'_> {}
|
||||
@@ -2,31 +2,45 @@
|
||||
* 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/. */
|
||||
|
||||
#![deny(missing_docs)]
|
||||
|
||||
use layout_api::DangerousStyleElement;
|
||||
use selectors::matching::QuirksMode;
|
||||
use style::dom::{TDocument, TNode};
|
||||
use style::shared_lock::{
|
||||
SharedRwLock as StyleSharedRwLock, SharedRwLockReadGuard as StyleSharedRwLockReadGuard,
|
||||
};
|
||||
use style::stylist::Stylist;
|
||||
use style::values::AtomIdent;
|
||||
|
||||
use crate::dom::bindings::root::LayoutDom;
|
||||
use crate::dom::document::Document;
|
||||
use crate::dom::node::{Node, NodeFlags};
|
||||
use crate::layout_dom::{ServoLayoutElement, ServoLayoutNode, ServoShadowRoot};
|
||||
use crate::layout_dom::{
|
||||
ServoDangerousStyleElement, ServoDangerousStyleNode, ServoDangerousStyleShadowRoot,
|
||||
ServoLayoutElement,
|
||||
};
|
||||
|
||||
// A wrapper around documents that ensures ayout can only ever access safe properties.
|
||||
/// A wrapper around documents that ensures layout can only ever access safe properties.
|
||||
///
|
||||
/// TODO: This should become a trait like `LayoutNode`.
|
||||
#[derive(Clone, Copy)]
|
||||
pub struct ServoLayoutDocument<'dom> {
|
||||
pub struct ServoDangerousStyleDocument<'dom> {
|
||||
/// The wrapped private DOM Document
|
||||
document: LayoutDom<'dom, Document>,
|
||||
}
|
||||
|
||||
use style::values::AtomIdent;
|
||||
impl<'ld> ::style::dom::TDocument for ServoLayoutDocument<'ld> {
|
||||
type ConcreteNode = ServoLayoutNode<'ld>;
|
||||
impl<'dom> From<LayoutDom<'dom, Document>> for ServoDangerousStyleDocument<'dom> {
|
||||
fn from(document: LayoutDom<'dom, Document>) -> Self {
|
||||
Self { document }
|
||||
}
|
||||
}
|
||||
|
||||
fn as_node(&self) -> Self::ConcreteNode {
|
||||
ServoLayoutNode::from_layout_dom(self.document.upcast())
|
||||
impl<'dom> ::style::dom::TDocument for ServoDangerousStyleDocument<'dom> {
|
||||
type ConcreteNode = ServoDangerousStyleNode<'dom>;
|
||||
|
||||
fn as_node(&self) -> ServoDangerousStyleNode<'dom> {
|
||||
self.document.upcast().into()
|
||||
}
|
||||
|
||||
fn quirks_mode(&self) -> QuirksMode {
|
||||
@@ -41,16 +55,21 @@ impl<'ld> ::style::dom::TDocument for ServoLayoutDocument<'ld> {
|
||||
self.document.style_shared_lock()
|
||||
}
|
||||
|
||||
fn elements_with_id<'a>(&self, id: &AtomIdent) -> Result<&'a [ServoLayoutElement<'ld>], ()>
|
||||
fn elements_with_id<'a>(
|
||||
&self,
|
||||
id: &AtomIdent,
|
||||
) -> Result<&'a [ServoDangerousStyleElement<'dom>], ()>
|
||||
where
|
||||
Self: 'a,
|
||||
{
|
||||
let elements_with_id = self.document.elements_with_id(&id.0);
|
||||
|
||||
// SAFETY: ServoLayoutElement is known to have the same layout and alignment as LayoutDom<Element>
|
||||
// SAFETY: ServoDangerousStyleElement is known to have the same layout and alignment as
|
||||
// LayoutDom<Element>.
|
||||
#[expect(unsafe_code)]
|
||||
let result = unsafe {
|
||||
std::slice::from_raw_parts(
|
||||
elements_with_id.as_ptr() as *const ServoLayoutElement<'ld>,
|
||||
elements_with_id.as_ptr() as *const ServoDangerousStyleElement<'dom>,
|
||||
elements_with_id.len(),
|
||||
)
|
||||
};
|
||||
@@ -58,36 +77,46 @@ impl<'ld> ::style::dom::TDocument for ServoLayoutDocument<'ld> {
|
||||
}
|
||||
}
|
||||
|
||||
impl<'ld> ServoLayoutDocument<'ld> {
|
||||
pub fn root_element(&self) -> Option<ServoLayoutElement<'ld>> {
|
||||
impl<'dom> ServoDangerousStyleDocument<'dom> {
|
||||
/// Get the root node for this [`ServoDangerousStyleDocument`].
|
||||
pub fn root_element(&self) -> Option<ServoLayoutElement<'dom>> {
|
||||
self.as_node()
|
||||
.dom_children()
|
||||
.flat_map(|n| n.as_element())
|
||||
.next()
|
||||
.map(|element| element.layout_element())
|
||||
}
|
||||
|
||||
/// Get the shared style lock for styling with Stylo for this [`ServoDangerousStyleDocument`].
|
||||
pub fn style_shared_lock(&self) -> &StyleSharedRwLock {
|
||||
self.document.style_shared_lock()
|
||||
}
|
||||
|
||||
pub fn shadow_roots(&self) -> Vec<ServoShadowRoot<'_>> {
|
||||
fn shadow_roots(&self) -> Vec<ServoDangerousStyleShadowRoot<'_>> {
|
||||
#[expect(unsafe_code)]
|
||||
unsafe {
|
||||
self.document
|
||||
.shadow_roots()
|
||||
.iter()
|
||||
.map(|sr| {
|
||||
debug_assert!(sr.upcast::<Node>().get_flag(NodeFlags::IS_CONNECTED));
|
||||
ServoShadowRoot::from_layout_dom(*sr)
|
||||
.map(|shadow_root| {
|
||||
debug_assert!(
|
||||
shadow_root
|
||||
.upcast::<Node>()
|
||||
.get_flag(NodeFlags::IS_CONNECTED)
|
||||
);
|
||||
(*shadow_root).into()
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
}
|
||||
|
||||
/// Flush the the stylesheets of all descendant shadow roots.
|
||||
pub fn flush_shadow_roots_stylesheets(
|
||||
&self,
|
||||
stylist: &mut Stylist,
|
||||
guard: &StyleSharedRwLockReadGuard,
|
||||
) {
|
||||
#[expect(unsafe_code)]
|
||||
unsafe {
|
||||
if !self.document.shadow_roots_styles_changed() {
|
||||
return;
|
||||
@@ -98,8 +127,4 @@ impl<'ld> ServoLayoutDocument<'ld> {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn from_layout_dom(document: LayoutDom<'ld, Document>) -> Self {
|
||||
ServoLayoutDocument { document }
|
||||
}
|
||||
}
|
||||
@@ -2,18 +2,18 @@
|
||||
* 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/. */
|
||||
|
||||
#![expect(unsafe_code)]
|
||||
#![deny(missing_docs)]
|
||||
|
||||
use std::hash::Hash;
|
||||
use std::slice;
|
||||
use std::sync::atomic::Ordering;
|
||||
use std::{fmt, slice};
|
||||
|
||||
use embedder_traits::UntrustedNodeAddress;
|
||||
use euclid::default::Size2D;
|
||||
use html5ever::{LocalName, Namespace, local_name, ns};
|
||||
use js::jsapi::JSObject;
|
||||
use layout_api::wrapper_traits::{
|
||||
LayoutDataTrait, LayoutElement, LayoutNode, PseudoElementChain, ThreadSafeLayoutElement,
|
||||
ThreadSafeLayoutNode,
|
||||
};
|
||||
use layout_api::{LayoutDamage, LayoutNodeType, StyleData};
|
||||
use layout_api::{DangerousStyleElement, LayoutDamage, LayoutNode};
|
||||
use selectors::Element as _;
|
||||
use selectors::attr::{AttrSelectorOperation, CaseSensitivity, NamespaceConstraint};
|
||||
use selectors::bloom::{BLOOM_HASH_MASK, BloomFilter};
|
||||
@@ -27,7 +27,7 @@ use style::attr::AttrValue;
|
||||
use style::bloom::each_relevant_element_hash;
|
||||
use style::context::SharedStyleContext;
|
||||
use style::data::{ElementDataMut, ElementDataRef};
|
||||
use style::dom::{DomChildren, LayoutIterator, TDocument, TElement, TNode, TShadowRoot};
|
||||
use style::dom::{LayoutIterator, TDocument, TElement, TNode, TShadowRoot};
|
||||
use style::properties::{ComputedValues, PropertyDeclarationBlock};
|
||||
use style::selector_parser::{
|
||||
AttrValue as SelectorAttrValue, Lang, NonTSPseudoClass, PseudoElement, RestyleDamage,
|
||||
@@ -44,181 +44,63 @@ use stylo_atoms::Atom;
|
||||
use stylo_dom::ElementState;
|
||||
|
||||
use crate::dom::bindings::inheritance::{
|
||||
Castable, CharacterDataTypeId, DocumentFragmentTypeId, ElementTypeId, HTMLElementTypeId,
|
||||
NodeTypeId, TextTypeId,
|
||||
CharacterDataTypeId, DocumentFragmentTypeId, ElementTypeId, HTMLElementTypeId, NodeTypeId,
|
||||
TextTypeId,
|
||||
};
|
||||
use crate::dom::bindings::root::LayoutDom;
|
||||
use crate::dom::element::Element;
|
||||
use crate::dom::html::htmlslotelement::HTMLSlotElement;
|
||||
use crate::dom::node::{Node, NodeFlags};
|
||||
use crate::layout_dom::{ServoLayoutNode, ServoShadowRoot, ServoThreadSafeLayoutNode};
|
||||
use crate::layout_dom::{
|
||||
DOMDescendantIterator, ServoDangerousStyleNode, ServoDangerousStyleShadowRoot,
|
||||
ServoLayoutElement, ServoLayoutNode,
|
||||
};
|
||||
|
||||
/// A wrapper around elements that ensures layout can only ever access safe properties.
|
||||
#[derive(Clone, Copy, Eq, Hash, PartialEq)]
|
||||
#[repr(transparent)]
|
||||
pub struct ServoLayoutElement<'dom> {
|
||||
/// The wrapped private DOM Element.
|
||||
element: LayoutDom<'dom, Element>,
|
||||
}
|
||||
|
||||
/// Those are supposed to be sound, but they aren't because the entire system
|
||||
/// between script and layout so far has been designed to work around their
|
||||
/// absence. Switching the entire thing to the inert crate infra will help.
|
||||
/// A wrapper around [`LayoutDom<_, Element>`] to be used with `stylo` and `selectors`.
|
||||
///
|
||||
/// FIXME(mrobinson): These are required because Layout 2020 sends non-threadsafe
|
||||
/// nodes to different threads. This should be adressed in a comprehensive way.
|
||||
unsafe impl Send for ServoLayoutElement<'_> {}
|
||||
unsafe impl Sync for ServoLayoutElement<'_> {}
|
||||
|
||||
impl fmt::Debug for ServoLayoutElement<'_> {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
write!(f, "<{}", self.element.local_name())?;
|
||||
if let Some(id) = self.id() {
|
||||
write!(f, " id={}", id)?;
|
||||
}
|
||||
write!(f, "> ({:#x})", self.as_node().opaque().0)
|
||||
}
|
||||
/// Note: This should only be used for `stylo` or `selectors interaction.
|
||||
#[derive(Copy, Clone, Debug, Eq, Hash, PartialEq)]
|
||||
pub struct ServoDangerousStyleElement<'dom> {
|
||||
pub(crate) element: LayoutDom<'dom, Element>,
|
||||
}
|
||||
|
||||
impl<'dom> LayoutElement<'dom> for ServoLayoutElement<'dom> {
|
||||
unsafe fn initialize_style_and_layout_data<RequestedLayoutDataType: LayoutDataTrait>(&self) {
|
||||
let inner = self.to_layout_dom();
|
||||
if inner.style_data().is_none() {
|
||||
unsafe { inner.initialize_style_data() };
|
||||
}
|
||||
unsafe impl Send for ServoDangerousStyleElement<'_> {}
|
||||
unsafe impl Sync for ServoDangerousStyleElement<'_> {}
|
||||
|
||||
let node = self.element.upcast::<Node>();
|
||||
if node.layout_data().is_none() {
|
||||
unsafe { node.initialize_layout_data(Box::<RequestedLayoutDataType>::default()) };
|
||||
}
|
||||
}
|
||||
|
||||
fn style_data(self) -> Option<&'dom StyleData> {
|
||||
self.to_layout_dom().style_data()
|
||||
}
|
||||
}
|
||||
|
||||
impl<'dom> ServoLayoutElement<'dom> {
|
||||
pub(super) fn from_layout_dom(element: LayoutDom<'dom, Element>) -> Self {
|
||||
ServoLayoutElement { element }
|
||||
}
|
||||
|
||||
/// Returns the interior of this element as a `LayoutDom`.
|
||||
///
|
||||
/// This method must never be exposed to layout as it returns
|
||||
/// a `LayoutDom`.
|
||||
pub(crate) fn to_layout_dom(self) -> LayoutDom<'dom, Element> {
|
||||
impl<'dom> ServoDangerousStyleElement<'dom> {
|
||||
pub(crate) fn layout_dom(&self) -> LayoutDom<'dom, Element> {
|
||||
self.element
|
||||
}
|
||||
}
|
||||
|
||||
pub(super) fn is_html_element(&self) -> bool {
|
||||
self.element.is_html_element()
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn get_attr_enum(&self, namespace: &Namespace, name: &LocalName) -> Option<&AttrValue> {
|
||||
self.element.get_attr_for_layout(namespace, name)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn get_attr(&self, namespace: &Namespace, name: &LocalName) -> Option<&str> {
|
||||
self.element.get_attr_val_for_layout(namespace, name)
|
||||
}
|
||||
|
||||
/// Unset the snapshot flags on the underlying DOM object for this element.
|
||||
///
|
||||
/// # Safety
|
||||
///
|
||||
/// This function accesses and modifies the underlying DOM object and should
|
||||
/// not be used by more than a single thread at once.
|
||||
pub unsafe fn unset_snapshot_flags(&self) {
|
||||
unsafe {
|
||||
self.as_node()
|
||||
.node
|
||||
.set_flag(NodeFlags::HAS_SNAPSHOT | NodeFlags::HANDLED_SNAPSHOT, false);
|
||||
}
|
||||
}
|
||||
|
||||
/// Unset the snapshot flags on the underlying DOM object for this element.
|
||||
///
|
||||
/// # Safety
|
||||
///
|
||||
/// This function accesses and modifies the underlying DOM object and should
|
||||
/// not be used by more than a single thread at once.
|
||||
pub unsafe fn set_has_snapshot(&self) {
|
||||
unsafe {
|
||||
self.as_node().node.set_flag(NodeFlags::HAS_SNAPSHOT, true);
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns true if this element is the body child of an html element root element.
|
||||
fn is_body_element_of_html_element_root(&self) -> bool {
|
||||
if self.element.local_name() != &local_name!("body") {
|
||||
return false;
|
||||
}
|
||||
|
||||
self.parent_element().is_some_and(|element| {
|
||||
element.is_root() && element.element.local_name() == &local_name!("html")
|
||||
})
|
||||
}
|
||||
|
||||
/// Returns the parent element of this element, if it has one.
|
||||
fn parent_element(&self) -> Option<Self> {
|
||||
self.as_node().parent_element()
|
||||
}
|
||||
|
||||
fn is_root(&self) -> bool {
|
||||
match self.as_node().parent_node() {
|
||||
None => false,
|
||||
Some(node) => matches!(node.script_type_id(), NodeTypeId::Document(_)),
|
||||
}
|
||||
impl<'dom> From<LayoutDom<'dom, Element>> for ServoDangerousStyleElement<'dom> {
|
||||
fn from(element: LayoutDom<'dom, Element>) -> Self {
|
||||
Self { element }
|
||||
}
|
||||
}
|
||||
|
||||
pub enum DOMDescendantIterator<E>
|
||||
where
|
||||
E: TElement,
|
||||
{
|
||||
/// Iterating over the children of a node, including children of a potential
|
||||
/// [ShadowRoot](crate::dom::shadow_root::ShadowRoot)
|
||||
Children(DomChildren<E::ConcreteNode>),
|
||||
/// Iterating over the content's of a [`<slot>`](HTMLSlotElement) element.
|
||||
Slottables { slot: E, index: usize },
|
||||
}
|
||||
impl<'dom> DangerousStyleElement<'dom> for ServoDangerousStyleElement<'dom> {
|
||||
type ConcreteLayoutElement = ServoLayoutElement<'dom>;
|
||||
|
||||
impl<E> Iterator for DOMDescendantIterator<E>
|
||||
where
|
||||
E: TElement,
|
||||
{
|
||||
type Item = E::ConcreteNode;
|
||||
|
||||
fn next(&mut self) -> Option<Self::Item> {
|
||||
match self {
|
||||
Self::Children(children) => children.next(),
|
||||
Self::Slottables { slot, index } => {
|
||||
let slottables = slot.slotted_nodes();
|
||||
let slot = slottables.get(*index)?;
|
||||
*index += 1;
|
||||
Some(*slot)
|
||||
},
|
||||
}
|
||||
fn layout_element(&self) -> Self::ConcreteLayoutElement {
|
||||
self.element.into()
|
||||
}
|
||||
}
|
||||
|
||||
impl<'dom> style::dom::AttributeProvider for ServoLayoutElement<'dom> {
|
||||
impl<'dom> style::dom::AttributeProvider for ServoDangerousStyleElement<'dom> {
|
||||
fn get_attr(&self, attr: &style::LocalName, namespace: &style::Namespace) -> Option<String> {
|
||||
self.element
|
||||
.get_attr_val_for_layout(namespace, attr)
|
||||
.map(String::from)
|
||||
.map(Into::into)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'dom> style::dom::TElement for ServoLayoutElement<'dom> {
|
||||
type ConcreteNode = ServoLayoutNode<'dom>;
|
||||
type TraversalChildrenIterator = DOMDescendantIterator<Self>;
|
||||
impl<'dom> style::dom::TElement for ServoDangerousStyleElement<'dom> {
|
||||
type ConcreteNode = ServoDangerousStyleNode<'dom>;
|
||||
type TraversalChildrenIterator = DOMDescendantIterator<'dom>;
|
||||
|
||||
fn as_node(&self) -> ServoLayoutNode<'dom> {
|
||||
ServoLayoutNode::from_layout_dom(self.element.upcast())
|
||||
fn as_node(&self) -> ServoDangerousStyleNode<'dom> {
|
||||
self.element.upcast().into()
|
||||
}
|
||||
|
||||
fn traversal_children(&self) -> LayoutIterator<Self::TraversalChildrenIterator> {
|
||||
@@ -258,7 +140,7 @@ impl<'dom> style::dom::TElement for ServoLayoutElement<'dom> {
|
||||
}
|
||||
|
||||
fn is_html_element(&self) -> bool {
|
||||
ServoLayoutElement::is_html_element(self)
|
||||
self.element.is_html_element()
|
||||
}
|
||||
|
||||
fn is_mathml_element(&self) -> bool {
|
||||
@@ -401,11 +283,10 @@ impl<'dom> style::dom::TElement for ServoLayoutElement<'dom> {
|
||||
}
|
||||
|
||||
unsafe fn set_dirty_descendants(&self) {
|
||||
debug_assert!(self.as_node().is_connected());
|
||||
let node = self.as_node();
|
||||
unsafe {
|
||||
self.as_node()
|
||||
.node
|
||||
.set_flag(NodeFlags::HAS_DIRTY_DESCENDANTS, true)
|
||||
debug_assert!(node.node.get_flag(NodeFlags::IS_CONNECTED));
|
||||
node.node.set_flag(NodeFlags::HAS_DIRTY_DESCENDANTS, true)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -433,14 +314,14 @@ impl<'dom> style::dom::TElement for ServoLayoutElement<'dom> {
|
||||
}
|
||||
|
||||
fn store_children_to_process(&self, n: isize) {
|
||||
let data = self.style_data().unwrap();
|
||||
let data = self.element.style_data().unwrap();
|
||||
data.parallel
|
||||
.children_to_process
|
||||
.store(n, Ordering::Relaxed);
|
||||
}
|
||||
|
||||
fn did_process_child(&self) -> isize {
|
||||
let data = self.style_data().unwrap();
|
||||
let data = self.element.style_data().unwrap();
|
||||
let old_value = data
|
||||
.parallel
|
||||
.children_to_process
|
||||
@@ -451,7 +332,7 @@ impl<'dom> style::dom::TElement for ServoLayoutElement<'dom> {
|
||||
|
||||
unsafe fn clear_data(&self) {
|
||||
unsafe { self.element.clear_style_data() };
|
||||
unsafe { self.as_node().to_layout_dom().clear_layout_data() }
|
||||
unsafe { self.as_node().node.clear_layout_data() }
|
||||
}
|
||||
|
||||
unsafe fn ensure_data(&self) -> ElementDataMut<'_> {
|
||||
@@ -461,17 +342,21 @@ impl<'dom> style::dom::TElement for ServoLayoutElement<'dom> {
|
||||
|
||||
/// Whether there is an ElementData container.
|
||||
fn has_data(&self) -> bool {
|
||||
self.style_data().is_some()
|
||||
self.element.style_data().is_some()
|
||||
}
|
||||
|
||||
/// Immutably borrows the ElementData.
|
||||
fn borrow_data(&self) -> Option<ElementDataRef<'_>> {
|
||||
self.style_data().map(|data| data.element_data.borrow())
|
||||
self.element
|
||||
.style_data()
|
||||
.map(|data| data.element_data.borrow())
|
||||
}
|
||||
|
||||
/// Mutably borrows the ElementData.
|
||||
fn mutate_data(&self) -> Option<ElementDataMut<'_>> {
|
||||
self.style_data().map(|data| data.element_data.borrow_mut())
|
||||
self.element
|
||||
.style_data()
|
||||
.map(|data| data.element_data.borrow_mut())
|
||||
}
|
||||
|
||||
fn skip_item_display_fixup(&self) -> bool {
|
||||
@@ -504,8 +389,12 @@ impl<'dom> style::dom::TElement for ServoLayoutElement<'dom> {
|
||||
|
||||
#[inline]
|
||||
fn lang_attr(&self) -> Option<SelectorAttrValue> {
|
||||
self.get_attr(&ns!(xml), &local_name!("lang"))
|
||||
.or_else(|| self.get_attr(&ns!(), &local_name!("lang")))
|
||||
self.element
|
||||
.get_attr_for_layout(&ns!(xml), &local_name!("lang"))
|
||||
.or_else(|| {
|
||||
self.element
|
||||
.get_attr_for_layout(&ns!(), &local_name!("lang"))
|
||||
})
|
||||
.map(|v| SelectorAttrValue::from(v as &str))
|
||||
}
|
||||
|
||||
@@ -536,7 +425,7 @@ impl<'dom> style::dom::TElement for ServoLayoutElement<'dom> {
|
||||
}
|
||||
|
||||
fn is_html_document_body_element(&self) -> bool {
|
||||
self.is_body_element_of_html_element_root()
|
||||
self.element.is_body_element_of_html_element_root()
|
||||
}
|
||||
|
||||
fn synthesize_presentational_hints_for_legacy_attributes<V>(
|
||||
@@ -551,18 +440,16 @@ impl<'dom> style::dom::TElement for ServoLayoutElement<'dom> {
|
||||
}
|
||||
|
||||
/// The shadow root this element is a host of.
|
||||
fn shadow_root(&self) -> Option<ServoShadowRoot<'dom>> {
|
||||
self.element
|
||||
.get_shadow_root_for_layout()
|
||||
.map(ServoShadowRoot::from_layout_dom)
|
||||
fn shadow_root(&self) -> Option<ServoDangerousStyleShadowRoot<'dom>> {
|
||||
self.element.get_shadow_root_for_layout().map(Into::into)
|
||||
}
|
||||
|
||||
/// The shadow root which roots the subtree this element is contained in.
|
||||
fn containing_shadow(&self) -> Option<ServoShadowRoot<'dom>> {
|
||||
fn containing_shadow(&self) -> Option<ServoDangerousStyleShadowRoot<'dom>> {
|
||||
self.element
|
||||
.upcast()
|
||||
.containing_shadow_root_for_layout()
|
||||
.map(ServoShadowRoot::from_layout_dom)
|
||||
.map(Into::into)
|
||||
}
|
||||
|
||||
fn local_name(&self) -> &LocalName {
|
||||
@@ -573,10 +460,7 @@ impl<'dom> style::dom::TElement for ServoLayoutElement<'dom> {
|
||||
self.element.namespace()
|
||||
}
|
||||
|
||||
fn query_container_size(
|
||||
&self,
|
||||
_display: &Display,
|
||||
) -> euclid::default::Size2D<Option<app_units::Au>> {
|
||||
fn query_container_size(&self, _display: &Display) -> Size2D<Option<app_units::Au>> {
|
||||
todo!();
|
||||
}
|
||||
|
||||
@@ -616,18 +500,18 @@ impl<'dom> style::dom::TElement for ServoLayoutElement<'dom> {
|
||||
}
|
||||
|
||||
fn slotted_nodes(&self) -> &[Self::ConcreteNode] {
|
||||
let Some(slot_element) = self.element.unsafe_get().downcast::<HTMLSlotElement>() else {
|
||||
let Some(slot_element) = self.element.downcast::<HTMLSlotElement>() else {
|
||||
return &[];
|
||||
};
|
||||
let assigned_nodes = slot_element.assigned_nodes();
|
||||
let assigned_nodes = slot_element.unsafe_get().assigned_nodes();
|
||||
|
||||
// SAFETY:
|
||||
// Self::ConcreteNode (aka ServoLayoutNode) and Slottable are guaranteed to have the same
|
||||
// layout and alignment as ptr::NonNull<T>. Lifetimes are not an issue because the
|
||||
// slottables are being kept alive by the slot element.
|
||||
// Self::ConcreteNode (aka ServoDangerousStyleNode) and Slottable are guaranteed to
|
||||
// have the same layout and alignment as ptr::NonNull<T>. Lifetimes are not an issue
|
||||
// because the slottables are being kept alive by the slot element.
|
||||
unsafe {
|
||||
slice::from_raw_parts(
|
||||
assigned_nodes.as_ptr() as *const Self::ConcreteNode,
|
||||
assigned_nodes.as_ptr() as *const ServoDangerousStyleNode,
|
||||
assigned_nodes.len(),
|
||||
)
|
||||
}
|
||||
@@ -771,7 +655,7 @@ impl<'dom> style::dom::TElement for ServoLayoutElement<'dom> {
|
||||
}
|
||||
}
|
||||
|
||||
impl<'dom> ::selectors::Element for ServoLayoutElement<'dom> {
|
||||
impl<'dom> ::selectors::Element for ServoDangerousStyleElement<'dom> {
|
||||
type Impl = SelectorImpl;
|
||||
|
||||
fn opaque(&self) -> ::selectors::OpaqueElement {
|
||||
@@ -779,21 +663,20 @@ impl<'dom> ::selectors::Element for ServoLayoutElement<'dom> {
|
||||
}
|
||||
|
||||
fn parent_element(&self) -> Option<Self> {
|
||||
ServoLayoutElement::parent_element(self)
|
||||
self.as_node().parent_node()?.as_element()
|
||||
}
|
||||
|
||||
fn parent_node_is_shadow_root(&self) -> bool {
|
||||
match self.as_node().parent_node() {
|
||||
None => false,
|
||||
Some(node) => {
|
||||
node.script_type_id() ==
|
||||
NodeTypeId::DocumentFragment(DocumentFragmentTypeId::ShadowRoot)
|
||||
},
|
||||
}
|
||||
self.as_node().parent_node().is_some_and(|parent_node| {
|
||||
parent_node.node.type_id_for_layout() ==
|
||||
NodeTypeId::DocumentFragment(DocumentFragmentTypeId::ShadowRoot)
|
||||
})
|
||||
}
|
||||
|
||||
fn containing_shadow_host(&self) -> Option<Self> {
|
||||
self.containing_shadow().map(|s| s.host())
|
||||
self.containing_shadow()
|
||||
.as_ref()
|
||||
.map(ServoDangerousStyleShadowRoot::host)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
@@ -819,7 +702,7 @@ impl<'dom> ::selectors::Element for ServoLayoutElement<'dom> {
|
||||
None
|
||||
}
|
||||
|
||||
fn next_sibling_element(&self) -> Option<ServoLayoutElement<'dom>> {
|
||||
fn next_sibling_element(&self) -> Option<ServoDangerousStyleElement<'dom>> {
|
||||
let mut node = self.as_node();
|
||||
while let Some(sibling) = node.next_sibling() {
|
||||
if let Some(element) = sibling.as_element() {
|
||||
@@ -844,7 +727,8 @@ impl<'dom> ::selectors::Element for ServoLayoutElement<'dom> {
|
||||
) -> bool {
|
||||
match *ns {
|
||||
NamespaceConstraint::Specific(ns) => self
|
||||
.get_attr_enum(ns, local_name)
|
||||
.element
|
||||
.get_attr_for_layout(ns, local_name)
|
||||
.is_some_and(|value| value.eval_selector(operation)),
|
||||
NamespaceConstraint::Any => self
|
||||
.element
|
||||
@@ -854,13 +738,13 @@ impl<'dom> ::selectors::Element for ServoLayoutElement<'dom> {
|
||||
}
|
||||
|
||||
fn is_root(&self) -> bool {
|
||||
ServoLayoutElement::is_root(self)
|
||||
self.element.is_root()
|
||||
}
|
||||
|
||||
fn is_empty(&self) -> bool {
|
||||
self.as_node()
|
||||
.dom_children()
|
||||
.all(|node| match node.script_type_id() {
|
||||
.all(|node| match node.node.type_id_for_layout() {
|
||||
NodeTypeId::Element(..) => false,
|
||||
NodeTypeId::CharacterData(CharacterDataTypeId::Text(TextTypeId::Text)) => {
|
||||
node.node.downcast().unwrap().data_for_layout().is_empty()
|
||||
@@ -954,7 +838,7 @@ impl<'dom> ::selectors::Element for ServoLayoutElement<'dom> {
|
||||
|
||||
#[inline]
|
||||
fn is_link(&self) -> bool {
|
||||
match self.as_node().script_type_id() {
|
||||
match self.as_node().node.type_id_for_layout() {
|
||||
// https://html.spec.whatwg.org/multipage/#selector-link
|
||||
NodeTypeId::Element(ElementTypeId::HTMLElement(
|
||||
HTMLElementTypeId::HTMLAnchorElement,
|
||||
@@ -1006,7 +890,13 @@ impl<'dom> ::selectors::Element for ServoLayoutElement<'dom> {
|
||||
}
|
||||
|
||||
fn assigned_slot(&self) -> Option<Self> {
|
||||
self.as_node().assigned_slot()
|
||||
Some(
|
||||
self.element
|
||||
.upcast::<Node>()
|
||||
.assigned_slot_for_layout()?
|
||||
.upcast()
|
||||
.into(),
|
||||
)
|
||||
}
|
||||
|
||||
fn is_html_element_in_html_document(&self) -> bool {
|
||||
@@ -1042,301 +932,3 @@ impl<'dom> ::selectors::Element for ServoLayoutElement<'dom> {
|
||||
has_state
|
||||
}
|
||||
}
|
||||
|
||||
/// A wrapper around elements that ensures layout can only
|
||||
/// ever access safe properties and cannot race on elements.
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
pub struct ServoThreadSafeLayoutElement<'dom> {
|
||||
/// The wrapped [`ServoLayoutElement`].
|
||||
pub(super) element: ServoLayoutElement<'dom>,
|
||||
|
||||
/// The possibly nested [`PseudoElementChain`] for this element.
|
||||
pub(super) pseudo_element_chain: PseudoElementChain,
|
||||
}
|
||||
|
||||
impl<'dom> ServoThreadSafeLayoutElement<'dom> {
|
||||
/// The shadow root this element is a host of.
|
||||
pub fn shadow_root(&self) -> Option<ServoShadowRoot<'dom>> {
|
||||
self.element
|
||||
.element
|
||||
.get_shadow_root_for_layout()
|
||||
.map(ServoShadowRoot::from_layout_dom)
|
||||
}
|
||||
|
||||
pub fn slotted_nodes(&self) -> &[ServoLayoutNode<'dom>] {
|
||||
self.element.slotted_nodes()
|
||||
}
|
||||
}
|
||||
|
||||
impl<'dom> ThreadSafeLayoutElement<'dom> for ServoThreadSafeLayoutElement<'dom> {
|
||||
type ConcreteThreadSafeLayoutNode = ServoThreadSafeLayoutNode<'dom>;
|
||||
type ConcreteElement = ServoLayoutElement<'dom>;
|
||||
|
||||
fn as_node(&self) -> ServoThreadSafeLayoutNode<'dom> {
|
||||
ServoThreadSafeLayoutNode {
|
||||
node: self.element.as_node(),
|
||||
pseudo_element_chain: self.pseudo_element_chain,
|
||||
}
|
||||
}
|
||||
|
||||
fn pseudo_element_chain(&self) -> PseudoElementChain {
|
||||
self.pseudo_element_chain
|
||||
}
|
||||
|
||||
fn with_pseudo(&self, pseudo_element: PseudoElement) -> Option<Self> {
|
||||
if pseudo_element.is_eager() &&
|
||||
self.element_data()
|
||||
.styles
|
||||
.pseudos
|
||||
.get(&pseudo_element)
|
||||
.is_none()
|
||||
{
|
||||
return None;
|
||||
}
|
||||
|
||||
if pseudo_element == PseudoElement::DetailsContent &&
|
||||
(!self.has_local_name(&local_name!("details")) ||
|
||||
!self.has_namespace(&ns!(html)) ||
|
||||
self.get_attr(&ns!(), &local_name!("open")).is_none())
|
||||
{
|
||||
return None;
|
||||
}
|
||||
|
||||
// These pseudo-element type cannot be nested.
|
||||
if !self.pseudo_element_chain.is_empty() {
|
||||
assert!(!pseudo_element.is_eager());
|
||||
assert!(pseudo_element != PseudoElement::DetailsContent);
|
||||
}
|
||||
|
||||
Some(ServoThreadSafeLayoutElement {
|
||||
element: self.element,
|
||||
pseudo_element_chain: self.pseudo_element_chain.with_pseudo(pseudo_element),
|
||||
})
|
||||
}
|
||||
|
||||
fn type_id(&self) -> Option<LayoutNodeType> {
|
||||
self.as_node().type_id()
|
||||
}
|
||||
|
||||
fn unsafe_get(self) -> ServoLayoutElement<'dom> {
|
||||
self.element
|
||||
}
|
||||
|
||||
fn get_local_name(&self) -> &LocalName {
|
||||
self.element.local_name()
|
||||
}
|
||||
|
||||
fn get_attr_enum(&self, namespace: &Namespace, name: &LocalName) -> Option<&AttrValue> {
|
||||
self.element.get_attr_enum(namespace, name)
|
||||
}
|
||||
|
||||
fn get_attr<'a>(&'a self, namespace: &Namespace, name: &LocalName) -> Option<&'a str> {
|
||||
self.element.get_attr(namespace, name)
|
||||
}
|
||||
|
||||
fn style_data(&self) -> Option<&'dom StyleData> {
|
||||
self.element.style_data()
|
||||
}
|
||||
|
||||
fn is_shadow_host(&self) -> bool {
|
||||
self.element.shadow_root().is_some()
|
||||
}
|
||||
|
||||
fn is_body_element_of_html_element_root(&self) -> bool {
|
||||
self.element.is_html_document_body_element()
|
||||
}
|
||||
|
||||
fn is_root(&self) -> bool {
|
||||
self.element.is_root()
|
||||
}
|
||||
}
|
||||
|
||||
/// This implementation of `::selectors::Element` is used for implementing lazy
|
||||
/// pseudo-elements.
|
||||
///
|
||||
/// Lazy pseudo-elements in Servo only allows selectors using safe properties,
|
||||
/// i.e., local_name, attributes, so they can only be used for **private**
|
||||
/// pseudo-elements (like `::details-content`).
|
||||
///
|
||||
/// Probably a few more of this functions can be implemented (like `has_class`, etc.),
|
||||
/// but they have no use right now.
|
||||
///
|
||||
/// Note that the element implementation is needed only for selector matching,
|
||||
/// not for inheritance (styles are inherited appropriately).
|
||||
impl ::selectors::Element for ServoThreadSafeLayoutElement<'_> {
|
||||
type Impl = SelectorImpl;
|
||||
|
||||
fn opaque(&self) -> ::selectors::OpaqueElement {
|
||||
::selectors::OpaqueElement::new(unsafe { &*(self.as_node().opaque().0 as *const ()) })
|
||||
}
|
||||
|
||||
fn parent_element(&self) -> Option<Self> {
|
||||
warn!("ServoThreadSafeLayoutElement::parent_element called");
|
||||
None
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn parent_node_is_shadow_root(&self) -> bool {
|
||||
self.element.parent_node_is_shadow_root()
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn containing_shadow_host(&self) -> Option<Self> {
|
||||
self.element
|
||||
.containing_shadow_host()
|
||||
.and_then(|element| element.as_node().to_threadsafe().as_element())
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn is_pseudo_element(&self) -> bool {
|
||||
self.element.is_pseudo_element()
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn pseudo_element_originating_element(&self) -> Option<Self> {
|
||||
self.element
|
||||
.pseudo_element_originating_element()
|
||||
.and_then(|element| element.as_node().to_threadsafe().as_element())
|
||||
}
|
||||
|
||||
// Skips non-element nodes
|
||||
fn prev_sibling_element(&self) -> Option<Self> {
|
||||
warn!("ServoThreadSafeLayoutElement::prev_sibling_element called");
|
||||
None
|
||||
}
|
||||
|
||||
// Skips non-element nodes
|
||||
fn next_sibling_element(&self) -> Option<Self> {
|
||||
warn!("ServoThreadSafeLayoutElement::next_sibling_element called");
|
||||
None
|
||||
}
|
||||
|
||||
// Skips non-element nodes
|
||||
fn first_element_child(&self) -> Option<Self> {
|
||||
warn!("ServoThreadSafeLayoutElement::first_element_child called");
|
||||
None
|
||||
}
|
||||
|
||||
fn is_html_slot_element(&self) -> bool {
|
||||
self.element.is_html_slot_element()
|
||||
}
|
||||
|
||||
fn is_html_element_in_html_document(&self) -> bool {
|
||||
self.element.is_html_element_in_html_document()
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn has_local_name(&self, name: &LocalName) -> bool {
|
||||
self.element.local_name() == name
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn has_namespace(&self, ns: &Namespace) -> bool {
|
||||
self.element.namespace() == ns
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn is_same_type(&self, other: &Self) -> bool {
|
||||
self.element.local_name() == other.element.local_name() &&
|
||||
self.element.namespace() == other.element.namespace()
|
||||
}
|
||||
|
||||
fn attr_matches(
|
||||
&self,
|
||||
ns: &NamespaceConstraint<&style::Namespace>,
|
||||
local_name: &style::LocalName,
|
||||
operation: &AttrSelectorOperation<&AtomString>,
|
||||
) -> bool {
|
||||
match *ns {
|
||||
NamespaceConstraint::Specific(ns) => self
|
||||
.get_attr_enum(ns, local_name)
|
||||
.is_some_and(|value| value.eval_selector(operation)),
|
||||
NamespaceConstraint::Any => self
|
||||
.element
|
||||
.element
|
||||
.get_attr_vals_for_layout(local_name)
|
||||
.any(|v| v.eval_selector(operation)),
|
||||
}
|
||||
}
|
||||
|
||||
fn match_non_ts_pseudo_class(
|
||||
&self,
|
||||
_: &NonTSPseudoClass,
|
||||
_: &mut MatchingContext<Self::Impl>,
|
||||
) -> bool {
|
||||
// NB: This could maybe be implemented
|
||||
warn!("ServoThreadSafeLayoutElement::match_non_ts_pseudo_class called");
|
||||
false
|
||||
}
|
||||
|
||||
fn match_pseudo_element(
|
||||
&self,
|
||||
pseudo: &PseudoElement,
|
||||
context: &mut MatchingContext<Self::Impl>,
|
||||
) -> bool {
|
||||
self.element.match_pseudo_element(pseudo, context)
|
||||
}
|
||||
|
||||
fn is_link(&self) -> bool {
|
||||
warn!("ServoThreadSafeLayoutElement::is_link called");
|
||||
false
|
||||
}
|
||||
|
||||
fn has_id(&self, _id: &AtomIdent, _case_sensitivity: CaseSensitivity) -> bool {
|
||||
debug!("ServoThreadSafeLayoutElement::has_id called");
|
||||
false
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn is_part(&self, _name: &AtomIdent) -> bool {
|
||||
debug!("ServoThreadSafeLayoutElement::is_part called");
|
||||
false
|
||||
}
|
||||
|
||||
fn imported_part(&self, _: &AtomIdent) -> Option<AtomIdent> {
|
||||
debug!("ServoThreadSafeLayoutElement::imported_part called");
|
||||
None
|
||||
}
|
||||
|
||||
fn has_class(&self, _name: &AtomIdent, _case_sensitivity: CaseSensitivity) -> bool {
|
||||
debug!("ServoThreadSafeLayoutElement::has_class called");
|
||||
false
|
||||
}
|
||||
|
||||
fn is_empty(&self) -> bool {
|
||||
warn!("ServoThreadSafeLayoutElement::is_empty called");
|
||||
false
|
||||
}
|
||||
|
||||
fn is_root(&self) -> bool {
|
||||
warn!("ServoThreadSafeLayoutElement::is_root called");
|
||||
false
|
||||
}
|
||||
|
||||
fn apply_selector_flags(&self, flags: ElementSelectorFlags) {
|
||||
// Handle flags that apply to the element.
|
||||
let self_flags = flags.for_self();
|
||||
if !self_flags.is_empty() {
|
||||
self.element.element.insert_selector_flags(flags);
|
||||
}
|
||||
|
||||
// Handle flags that apply to the parent.
|
||||
let parent_flags = flags.for_parent();
|
||||
if !parent_flags.is_empty() {
|
||||
if let Some(p) = self.element.parent_element() {
|
||||
p.element.insert_selector_flags(flags);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn add_element_unique_hashes(&self, filter: &mut BloomFilter) -> bool {
|
||||
each_relevant_element_hash(self.element, |hash| {
|
||||
filter.insert_hash(hash & BLOOM_HASH_MASK)
|
||||
});
|
||||
true
|
||||
}
|
||||
|
||||
fn has_custom_state(&self, _name: &AtomIdent) -> bool {
|
||||
false
|
||||
}
|
||||
}
|
||||
151
components/script/layout_dom/servo_dangerous_style_node.rs
Normal file
151
components/script/layout_dom/servo_dangerous_style_node.rs
Normal file
@@ -0,0 +1,151 @@
|
||||
/* 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/. */
|
||||
|
||||
#![expect(unsafe_code)]
|
||||
#![deny(missing_docs)]
|
||||
|
||||
use layout_api::DangerousStyleNode;
|
||||
use script_bindings::error::Fallible;
|
||||
use servo_arc::Arc;
|
||||
use style;
|
||||
use style::dom::{NodeInfo, TNode};
|
||||
use style::dom_apis::{MayUseInvalidation, SelectorQuery, query_selector};
|
||||
use style::selector_parser::SelectorParser;
|
||||
use style::stylesheets::UrlExtraData;
|
||||
use url::Url;
|
||||
|
||||
use super::{ServoDangerousStyleDocument, ServoDangerousStyleShadowRoot};
|
||||
use crate::dom::bindings::error::Error;
|
||||
use crate::dom::bindings::root::LayoutDom;
|
||||
use crate::dom::node::{Node, NodeFlags};
|
||||
use crate::layout_dom::{ServoDangerousStyleElement, ServoLayoutNode};
|
||||
|
||||
/// A wrapper around [`LayoutDom<_, Node>`] to be used with `stylo` and `selectors`.
|
||||
///
|
||||
/// Note: This should only be used for `stylo` or `selectors interaction.
|
||||
#[derive(Copy, Clone, Debug, Eq, PartialEq)]
|
||||
#[repr(transparent)]
|
||||
pub struct ServoDangerousStyleNode<'dom> {
|
||||
pub(crate) node: LayoutDom<'dom, Node>,
|
||||
}
|
||||
|
||||
unsafe impl Send for ServoDangerousStyleNode<'_> {}
|
||||
unsafe impl Sync for ServoDangerousStyleNode<'_> {}
|
||||
|
||||
impl<'dom> ServoDangerousStyleNode<'dom> {
|
||||
/// <https://dom.spec.whatwg.org/#scope-match-a-selectors-string>
|
||||
pub(crate) fn scope_match_a_selectors_string<Query>(
|
||||
self,
|
||||
document_url: Arc<Url>,
|
||||
selector: &str,
|
||||
) -> Fallible<Query::Output>
|
||||
where
|
||||
Query: SelectorQuery<ServoDangerousStyleElement<'dom>>,
|
||||
Query::Output: Default,
|
||||
{
|
||||
let mut result = Query::Output::default();
|
||||
|
||||
// Step 1. Let selector be the result of parse a selector selectors.
|
||||
let selector_or_error =
|
||||
SelectorParser::parse_author_origin_no_namespace(selector, &UrlExtraData(document_url));
|
||||
|
||||
// Step 2. If selector is failure, then throw a "SyntaxError" DOMException.
|
||||
let Ok(selector_list) = selector_or_error else {
|
||||
return Err(Error::Syntax(None));
|
||||
};
|
||||
|
||||
// Step 3. Return the result of match a selector against a tree with selector
|
||||
// and node’s root using scoping root node.
|
||||
query_selector::<ServoDangerousStyleElement<'dom>, Query>(
|
||||
self,
|
||||
&selector_list,
|
||||
&mut result,
|
||||
MayUseInvalidation::No,
|
||||
);
|
||||
|
||||
Ok(result)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'dom> From<LayoutDom<'dom, Node>> for ServoDangerousStyleNode<'dom> {
|
||||
fn from(node: LayoutDom<'dom, Node>) -> Self {
|
||||
Self { node }
|
||||
}
|
||||
}
|
||||
|
||||
impl<'dom> DangerousStyleNode<'dom> for ServoDangerousStyleNode<'dom> {
|
||||
type ConcreteLayoutNode = ServoLayoutNode<'dom>;
|
||||
|
||||
fn layout_node(&self) -> Self::ConcreteLayoutNode {
|
||||
self.node.into()
|
||||
}
|
||||
}
|
||||
|
||||
impl NodeInfo for ServoDangerousStyleNode<'_> {
|
||||
fn is_element(&self) -> bool {
|
||||
self.node.is_element_for_layout()
|
||||
}
|
||||
|
||||
fn is_text_node(&self) -> bool {
|
||||
self.node.is_text_node_for_layout()
|
||||
}
|
||||
}
|
||||
|
||||
impl<'dom> TNode for ServoDangerousStyleNode<'dom> {
|
||||
type ConcreteDocument = ServoDangerousStyleDocument<'dom>;
|
||||
type ConcreteElement = ServoDangerousStyleElement<'dom>;
|
||||
type ConcreteShadowRoot = ServoDangerousStyleShadowRoot<'dom>;
|
||||
|
||||
fn parent_node(&self) -> Option<Self> {
|
||||
self.node.parent_node_ref().map(Into::into)
|
||||
}
|
||||
|
||||
fn first_child(&self) -> Option<Self> {
|
||||
self.node.first_child_ref().map(Into::into)
|
||||
}
|
||||
|
||||
fn last_child(&self) -> Option<Self> {
|
||||
self.node.last_child_ref().map(Into::into)
|
||||
}
|
||||
|
||||
fn prev_sibling(&self) -> Option<Self> {
|
||||
self.node.prev_sibling_ref().map(Into::into)
|
||||
}
|
||||
|
||||
fn next_sibling(&self) -> Option<Self> {
|
||||
self.node.next_sibling_ref().map(Into::into)
|
||||
}
|
||||
|
||||
fn owner_doc(&self) -> Self::ConcreteDocument {
|
||||
self.node.owner_doc_for_layout().into()
|
||||
}
|
||||
|
||||
fn traversal_parent(&self) -> Option<ServoDangerousStyleElement<'dom>> {
|
||||
Some(self.node.traversal_parent()?.into())
|
||||
}
|
||||
|
||||
fn opaque(&self) -> style::dom::OpaqueNode {
|
||||
self.node.opaque()
|
||||
}
|
||||
|
||||
fn debug_id(self) -> usize {
|
||||
self.opaque().0
|
||||
}
|
||||
|
||||
fn as_element(&self) -> Option<ServoDangerousStyleElement<'dom>> {
|
||||
Some(self.node.downcast()?.into())
|
||||
}
|
||||
|
||||
fn as_document(&self) -> Option<ServoDangerousStyleDocument<'dom>> {
|
||||
self.node.downcast().map(Into::into)
|
||||
}
|
||||
|
||||
fn as_shadow_root(&self) -> Option<ServoDangerousStyleShadowRoot<'dom>> {
|
||||
self.node.downcast().map(Into::into)
|
||||
}
|
||||
|
||||
fn is_in_document(&self) -> bool {
|
||||
unsafe { self.node.get_flag(NodeFlags::IS_IN_A_DOCUMENT_TREE) }
|
||||
}
|
||||
}
|
||||
@@ -2,6 +2,8 @@
|
||||
* 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/. */
|
||||
|
||||
#![deny(missing_docs)]
|
||||
|
||||
use std::fmt;
|
||||
|
||||
use style::dom::TShadowRoot;
|
||||
@@ -10,29 +12,40 @@ use style::stylist::{CascadeData, Stylist};
|
||||
|
||||
use crate::dom::bindings::root::LayoutDom;
|
||||
use crate::dom::shadowroot::ShadowRoot;
|
||||
use crate::layout_dom::{ServoLayoutElement, ServoLayoutNode};
|
||||
use crate::layout_dom::{ServoDangerousStyleElement, ServoDangerousStyleNode};
|
||||
|
||||
/// A wrapper around [`LayoutDom<_, ShadowRoot>`] to be used with `stylo` and `selectors`.
|
||||
///
|
||||
/// Note: This should only be used for `stylo` or `selectors interaction.
|
||||
#[derive(Clone, Copy, PartialEq)]
|
||||
pub struct ServoShadowRoot<'dom> {
|
||||
pub struct ServoDangerousStyleShadowRoot<'dom> {
|
||||
/// The wrapped private DOM ShadowRoot.
|
||||
shadow_root: LayoutDom<'dom, ShadowRoot>,
|
||||
}
|
||||
|
||||
impl fmt::Debug for ServoShadowRoot<'_> {
|
||||
impl fmt::Debug for ServoDangerousStyleShadowRoot<'_> {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
self.as_node().fmt(f)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'dom> TShadowRoot for ServoShadowRoot<'dom> {
|
||||
type ConcreteNode = ServoLayoutNode<'dom>;
|
||||
impl<'dom> From<LayoutDom<'dom, ShadowRoot>> for ServoDangerousStyleShadowRoot<'dom> {
|
||||
fn from(shadow_root: LayoutDom<'dom, ShadowRoot>) -> Self {
|
||||
Self { shadow_root }
|
||||
}
|
||||
}
|
||||
|
||||
fn as_node(&self) -> Self::ConcreteNode {
|
||||
ServoLayoutNode::from_layout_dom(self.shadow_root.upcast())
|
||||
impl<'dom> TShadowRoot for ServoDangerousStyleShadowRoot<'dom> {
|
||||
type ConcreteNode = ServoDangerousStyleNode<'dom>;
|
||||
|
||||
fn as_node(&self) -> ServoDangerousStyleNode<'dom> {
|
||||
self.shadow_root.upcast().into()
|
||||
}
|
||||
|
||||
fn host(&self) -> ServoLayoutElement<'dom> {
|
||||
ServoLayoutElement::from_layout_dom(self.shadow_root.get_host_for_layout())
|
||||
fn host(&self) -> ServoDangerousStyleElement<'dom> {
|
||||
ServoDangerousStyleElement {
|
||||
element: self.shadow_root.get_host_for_layout(),
|
||||
}
|
||||
}
|
||||
|
||||
fn style_data<'a>(&self) -> Option<&'a CascadeData>
|
||||
@@ -43,18 +56,15 @@ impl<'dom> TShadowRoot for ServoShadowRoot<'dom> {
|
||||
}
|
||||
}
|
||||
|
||||
impl<'dom> ServoShadowRoot<'dom> {
|
||||
pub(super) fn from_layout_dom(shadow_root: LayoutDom<'dom, ShadowRoot>) -> Self {
|
||||
ServoShadowRoot { shadow_root }
|
||||
}
|
||||
|
||||
impl<'dom> ServoDangerousStyleShadowRoot<'dom> {
|
||||
/// Flush the stylesheets for the underlying shadow root.
|
||||
///
|
||||
/// # Safety
|
||||
///
|
||||
/// This modifies a DOM object, so should care should be taken that only one
|
||||
/// thread has a reference to this object.
|
||||
pub unsafe fn flush_stylesheets(
|
||||
#[expect(unsafe_code)]
|
||||
pub(crate) unsafe fn flush_stylesheets(
|
||||
&self,
|
||||
stylist: &mut Stylist,
|
||||
guard: &StyleSharedRwLockReadGuard,
|
||||
@@ -62,6 +72,8 @@ impl<'dom> ServoShadowRoot<'dom> {
|
||||
unsafe { self.shadow_root.flush_stylesheets(stylist, guard) }
|
||||
}
|
||||
|
||||
/// Whether or not this [`ServoDangerousStyleShadowRoot`] is the root
|
||||
/// of a user agent widget.
|
||||
pub fn is_ua_widget(&self) -> bool {
|
||||
self.shadow_root.is_ua_widget()
|
||||
}
|
||||
254
components/script/layout_dom/servo_layout_element.rs
Normal file
254
components/script/layout_dom/servo_layout_element.rs
Normal file
@@ -0,0 +1,254 @@
|
||||
/* 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/. */
|
||||
|
||||
#![expect(unsafe_code)]
|
||||
#![deny(missing_docs)]
|
||||
|
||||
use std::fmt;
|
||||
use std::hash::Hash;
|
||||
|
||||
use html5ever::{LocalName, Namespace, local_name, ns};
|
||||
use layout_api::{
|
||||
LayoutDataTrait, LayoutElement, LayoutNode, LayoutNodeType, PseudoElementChain, StyleData,
|
||||
};
|
||||
use servo_arc::Arc;
|
||||
use style::attr::AttrValue;
|
||||
use style::context::SharedStyleContext;
|
||||
use style::data::{ElementDataMut, ElementDataRef};
|
||||
use style::properties::ComputedValues;
|
||||
use style::selector_parser::{PseudoElement, PseudoElementCascadeType};
|
||||
use style::stylist::RuleInclusion;
|
||||
|
||||
use crate::dom::bindings::root::LayoutDom;
|
||||
use crate::dom::element::Element;
|
||||
use crate::dom::node::{Node, NodeFlags};
|
||||
use crate::layout_dom::{
|
||||
ServoDangerousStyleElement, ServoDangerousStyleShadowRoot, ServoLayoutNode,
|
||||
};
|
||||
|
||||
impl fmt::Debug for LayoutDom<'_, Element> {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
write!(f, "<{}", self.local_name())?;
|
||||
if let Some(id) = unsafe { (*self.id_attribute()).as_ref() } {
|
||||
write!(f, " id={id}")?;
|
||||
}
|
||||
write!(f, "> ({:#x})", self.upcast::<Node>().opaque().0)
|
||||
}
|
||||
}
|
||||
|
||||
/// An implementation of [`LayoutElement`] for Servo's `script` crate.
|
||||
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
|
||||
pub struct ServoLayoutElement<'dom> {
|
||||
/// The wrapped private DOM Element.
|
||||
pub(super) element: LayoutDom<'dom, Element>,
|
||||
/// The possibly nested [`PseudoElementChain`] for this element.
|
||||
pub(super) pseudo_element_chain: PseudoElementChain,
|
||||
}
|
||||
|
||||
unsafe impl Send for ServoLayoutElement<'_> {}
|
||||
unsafe impl Sync for ServoLayoutElement<'_> {}
|
||||
|
||||
impl<'dom> ServoLayoutElement<'dom> {
|
||||
pub(super) fn is_html_element(&self) -> bool {
|
||||
self.element.is_html_element()
|
||||
}
|
||||
|
||||
/// The shadow root this element is a host of.
|
||||
pub fn shadow_root(&self) -> Option<ServoDangerousStyleShadowRoot<'dom>> {
|
||||
self.element.get_shadow_root_for_layout().map(Into::into)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'dom> From<LayoutDom<'dom, Element>> for ServoLayoutElement<'dom> {
|
||||
fn from(element: LayoutDom<'dom, Element>) -> Self {
|
||||
Self {
|
||||
element,
|
||||
pseudo_element_chain: Default::default(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'dom> LayoutElement<'dom> for ServoLayoutElement<'dom> {
|
||||
type ConcreteLayoutNode = ServoLayoutNode<'dom>;
|
||||
type ConcreteStyleElement = ServoDangerousStyleElement<'dom>;
|
||||
|
||||
fn with_pseudo(&self, pseudo_element: PseudoElement) -> Option<Self> {
|
||||
if pseudo_element.is_eager() &&
|
||||
self.element_data()
|
||||
.styles
|
||||
.pseudos
|
||||
.get(&pseudo_element)
|
||||
.is_none()
|
||||
{
|
||||
return None;
|
||||
}
|
||||
|
||||
if pseudo_element == PseudoElement::DetailsContent &&
|
||||
(self.element.local_name() != &local_name!("details") ||
|
||||
self.element.namespace() != &ns!(html) ||
|
||||
self.attribute(&ns!(), &local_name!("open")).is_none())
|
||||
{
|
||||
return None;
|
||||
}
|
||||
|
||||
// These pseudo-element type cannot be nested.
|
||||
if !self.pseudo_element_chain.is_empty() {
|
||||
assert!(!pseudo_element.is_eager());
|
||||
assert!(pseudo_element != PseudoElement::DetailsContent);
|
||||
}
|
||||
|
||||
Some(Self {
|
||||
element: self.element,
|
||||
pseudo_element_chain: self.pseudo_element_chain.with_pseudo(pseudo_element),
|
||||
})
|
||||
}
|
||||
|
||||
fn pseudo_element_chain(&self) -> PseudoElementChain {
|
||||
self.pseudo_element_chain
|
||||
}
|
||||
|
||||
fn as_node(&self) -> ServoLayoutNode<'dom> {
|
||||
ServoLayoutNode {
|
||||
node: self.element.upcast(),
|
||||
pseudo_element_chain: self.pseudo_element_chain,
|
||||
}
|
||||
}
|
||||
|
||||
fn initialize_style_and_layout_data<RequestedLayoutDataType: LayoutDataTrait>(&self) {
|
||||
if self.element.style_data().is_none() {
|
||||
unsafe { self.element.initialize_style_data() };
|
||||
}
|
||||
|
||||
let node = self.element.upcast::<Node>();
|
||||
if node.layout_data().is_none() {
|
||||
unsafe { node.initialize_layout_data(Box::<RequestedLayoutDataType>::default()) };
|
||||
}
|
||||
}
|
||||
|
||||
fn unset_snapshot_flags(&self) {
|
||||
unsafe {
|
||||
self.as_node()
|
||||
.node
|
||||
.set_flag(NodeFlags::HAS_SNAPSHOT | NodeFlags::HANDLED_SNAPSHOT, false);
|
||||
}
|
||||
}
|
||||
|
||||
fn set_has_snapshot(&self) {
|
||||
unsafe {
|
||||
self.as_node().node.set_flag(NodeFlags::HAS_SNAPSHOT, true);
|
||||
}
|
||||
}
|
||||
|
||||
fn style_data(self) -> Option<&'dom StyleData> {
|
||||
self.element.style_data()
|
||||
}
|
||||
|
||||
fn element_data(&self) -> ElementDataRef<'dom> {
|
||||
self.style_data()
|
||||
.expect("Unstyled layout node?")
|
||||
.element_data
|
||||
.borrow()
|
||||
}
|
||||
|
||||
fn element_data_mut(&self) -> ElementDataMut<'dom> {
|
||||
self.style_data()
|
||||
.expect("Unstyled layout node?")
|
||||
.element_data
|
||||
.borrow_mut()
|
||||
}
|
||||
|
||||
fn style(&self, context: &SharedStyleContext) -> Arc<ComputedValues> {
|
||||
let get_style_for_pseudo_element =
|
||||
|data: &ElementDataRef<'_>,
|
||||
base_style: &Arc<ComputedValues>,
|
||||
pseudo_element: PseudoElement| {
|
||||
// Precompute non-eagerly-cascaded pseudo-element styles if not
|
||||
// cached before.
|
||||
match pseudo_element.cascade_type() {
|
||||
// Already computed during the cascade.
|
||||
PseudoElementCascadeType::Eager => {
|
||||
data.styles.pseudos.get(&pseudo_element).unwrap().clone()
|
||||
},
|
||||
PseudoElementCascadeType::Precomputed => context
|
||||
.stylist
|
||||
.precomputed_values_for_pseudo::<Self::ConcreteStyleElement>(
|
||||
&context.guards,
|
||||
&pseudo_element,
|
||||
Some(base_style),
|
||||
),
|
||||
PseudoElementCascadeType::Lazy => {
|
||||
context
|
||||
.stylist
|
||||
.lazily_compute_pseudo_element_style(
|
||||
&context.guards,
|
||||
unsafe { self.dangerous_style_element() },
|
||||
&pseudo_element,
|
||||
RuleInclusion::All,
|
||||
base_style,
|
||||
/* is_probe = */ false,
|
||||
/* matching_func = */ None,
|
||||
)
|
||||
.unwrap()
|
||||
},
|
||||
}
|
||||
};
|
||||
|
||||
let data = self.element_data();
|
||||
let element_style = data.styles.primary();
|
||||
let pseudo_element_chain = self.pseudo_element_chain();
|
||||
|
||||
let primary_pseudo_style = match pseudo_element_chain.primary {
|
||||
Some(pseudo_element) => {
|
||||
get_style_for_pseudo_element(&data, element_style, pseudo_element)
|
||||
},
|
||||
None => return element_style.clone(),
|
||||
};
|
||||
match pseudo_element_chain.secondary {
|
||||
Some(pseudo_element) => {
|
||||
get_style_for_pseudo_element(&data, &primary_pseudo_style, pseudo_element)
|
||||
},
|
||||
None => primary_pseudo_style,
|
||||
}
|
||||
}
|
||||
|
||||
fn type_id(&self) -> Option<LayoutNodeType> {
|
||||
self.as_node().type_id()
|
||||
}
|
||||
|
||||
unsafe fn dangerous_style_element(self) -> ServoDangerousStyleElement<'dom> {
|
||||
self.element.into()
|
||||
}
|
||||
|
||||
fn local_name(&self) -> &LocalName {
|
||||
self.element.local_name()
|
||||
}
|
||||
|
||||
fn attribute(&self, namespace: &Namespace, name: &LocalName) -> Option<&AttrValue> {
|
||||
self.element.get_attr_for_layout(namespace, name)
|
||||
}
|
||||
|
||||
fn attribute_as_str<'a>(&'a self, namespace: &Namespace, name: &LocalName) -> Option<&'a str> {
|
||||
self.element.get_attr_val_for_layout(namespace, name)
|
||||
}
|
||||
|
||||
fn is_shadow_host(&self) -> bool {
|
||||
self.element.get_shadow_root_for_layout().is_some()
|
||||
}
|
||||
|
||||
fn is_body_element_of_html_element_root(&self) -> bool {
|
||||
self.element.is_body_element_of_html_element_root()
|
||||
}
|
||||
|
||||
fn is_html_element_in_html_document(&self) -> bool {
|
||||
self.element.is_html_element() &&
|
||||
self.element
|
||||
.upcast::<Node>()
|
||||
.owner_doc_for_layout()
|
||||
.is_html_document_for_layout()
|
||||
}
|
||||
|
||||
fn is_root(&self) -> bool {
|
||||
self.element.is_root()
|
||||
}
|
||||
}
|
||||
331
components/script/layout_dom/servo_layout_node.rs
Normal file
331
components/script/layout_dom/servo_layout_node.rs
Normal file
@@ -0,0 +1,331 @@
|
||||
/* 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/. */
|
||||
|
||||
#![expect(unsafe_code)]
|
||||
#![deny(missing_docs)]
|
||||
|
||||
use std::borrow::Cow;
|
||||
use std::fmt;
|
||||
|
||||
use layout_api::{
|
||||
GenericLayoutData, HTMLCanvasData, HTMLMediaData, LayoutDataTrait, LayoutElement, LayoutNode,
|
||||
LayoutNodeType, PseudoElementChain, SVGElementData, SharedSelection, TrustedNodeAddress,
|
||||
};
|
||||
use net_traits::image_cache::Image;
|
||||
use pixels::ImageMetadata;
|
||||
use servo_arc::Arc;
|
||||
use servo_base::id::{BrowsingContextId, PipelineId};
|
||||
use servo_url::ServoUrl;
|
||||
use style;
|
||||
use style::context::SharedStyleContext;
|
||||
use style::dom::{LayoutIterator, NodeInfo};
|
||||
use style::properties::ComputedValues;
|
||||
use style::selector_parser::PseudoElement;
|
||||
|
||||
use super::ServoLayoutElement;
|
||||
use crate::dom::bindings::root::LayoutDom;
|
||||
use crate::dom::element::Element;
|
||||
use crate::dom::node::{Node, NodeFlags, NodeTypeIdWrapper};
|
||||
use crate::layout_dom::{
|
||||
ServoDangerousStyleElement, ServoDangerousStyleNode, ServoLayoutNodeChildrenIterator,
|
||||
};
|
||||
|
||||
impl fmt::Debug for LayoutDom<'_, Node> {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
if let Some(element) = self.downcast::<Element>() {
|
||||
element.fmt(f)
|
||||
} else if self.is_text_node_for_layout() {
|
||||
write!(f, "<text node> ({:#x})", self.opaque().0)
|
||||
} else {
|
||||
write!(f, "<non-text node> ({:#x})", self.opaque().0)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// A wrapper around a `LayoutDom<Node>` which provides a safe interface that
|
||||
/// can be used during layout. This implements the `LayoutNode` trait.
|
||||
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
|
||||
pub struct ServoLayoutNode<'dom> {
|
||||
/// The wrapped private DOM node.
|
||||
pub(super) node: LayoutDom<'dom, Node>,
|
||||
/// The possibly nested [`PseudoElementChain`] for this node.
|
||||
pub(super) pseudo_element_chain: PseudoElementChain,
|
||||
}
|
||||
|
||||
/// Those are supposed to be sound, but they aren't because the entire system
|
||||
/// between script and layout so far has been designed to work around their
|
||||
/// absence. Switching the entire thing to the inert crate infra will help.
|
||||
unsafe impl Send for ServoLayoutNode<'_> {}
|
||||
unsafe impl Sync for ServoLayoutNode<'_> {}
|
||||
|
||||
impl<'dom> ServoLayoutNode<'dom> {
|
||||
/// Create a new [`ServoLayoutNode`] for this given [`TrustedNodeAddress`].
|
||||
///
|
||||
/// # Safety
|
||||
///
|
||||
/// The address pointed to by `address` should point to a valid node in memory.
|
||||
pub unsafe fn new(address: &TrustedNodeAddress) -> Self {
|
||||
unsafe { LayoutDom::from_trusted_node_address(*address) }.into()
|
||||
}
|
||||
|
||||
/// Get the first child of this node.
|
||||
///
|
||||
/// # Safety
|
||||
///
|
||||
/// This node should never be exposed directly to the layout interface, as that may allow
|
||||
/// mutating a node that is being laid out in another thread. Thus, this should *never* be
|
||||
/// made public or exposed in the `LayoutNode` trait.
|
||||
pub(super) unsafe fn dangerous_first_child(&self) -> Option<Self> {
|
||||
self.node.first_child_ref().map(Into::into)
|
||||
}
|
||||
|
||||
/// Get the next sibling of this node.
|
||||
///
|
||||
/// # Safety
|
||||
///
|
||||
/// This node should never be exposed directly to the layout interface, as that may allow
|
||||
/// mutating a node that is being laid out in another thread. Thus, this should *never* be
|
||||
/// made public or exposed in the `LayoutNode` trait.
|
||||
pub(super) unsafe fn dangerous_next_sibling(&self) -> Option<Self> {
|
||||
self.node.next_sibling_ref().map(Into::into)
|
||||
}
|
||||
|
||||
/// Get the previous sibling of this node.
|
||||
///
|
||||
/// # Safety
|
||||
///
|
||||
/// This node should never be exposed directly to the layout interface, as that may allow
|
||||
/// mutating a node that is being laid out in another thread. Thus, this should *never* be
|
||||
/// made public or exposed in the `LayoutNode` trait.
|
||||
pub(super) unsafe fn dangerous_previous_sibling(&self) -> Option<Self> {
|
||||
self.node.prev_sibling_ref().map(Into::into)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'dom> From<LayoutDom<'dom, Node>> for ServoLayoutNode<'dom> {
|
||||
fn from(node: LayoutDom<'dom, Node>) -> Self {
|
||||
Self {
|
||||
node,
|
||||
pseudo_element_chain: Default::default(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'dom> LayoutNode<'dom> for ServoLayoutNode<'dom> {
|
||||
type ConcreteDangerousStyleNode = ServoDangerousStyleNode<'dom>;
|
||||
type ConcreteDangerousStyleElement = ServoDangerousStyleElement<'dom>;
|
||||
type ConcreteLayoutElement = ServoLayoutElement<'dom>;
|
||||
type ChildIterator = ServoLayoutNodeChildrenIterator<'dom>;
|
||||
|
||||
fn with_pseudo(&self, pseudo_element_type: PseudoElement) -> Option<Self> {
|
||||
Some(
|
||||
self.as_element()?
|
||||
.with_pseudo(pseudo_element_type)?
|
||||
.as_node(),
|
||||
)
|
||||
}
|
||||
|
||||
unsafe fn dangerous_style_node(self) -> Self::ConcreteDangerousStyleNode {
|
||||
self.node.into()
|
||||
}
|
||||
|
||||
unsafe fn dangerous_dom_parent(self) -> Option<Self> {
|
||||
self.node.parent_node_ref().map(Into::into)
|
||||
}
|
||||
|
||||
unsafe fn dangerous_flat_tree_parent(self) -> Option<Self> {
|
||||
self.node
|
||||
.traversal_parent()
|
||||
.map(|parent_element| parent_element.upcast().into())
|
||||
}
|
||||
|
||||
fn is_connected(&self) -> bool {
|
||||
unsafe { self.node.get_flag(NodeFlags::IS_CONNECTED) }
|
||||
}
|
||||
|
||||
fn layout_data(&self) -> Option<&'dom GenericLayoutData> {
|
||||
self.node.layout_data()
|
||||
}
|
||||
|
||||
fn opaque(&self) -> style::dom::OpaqueNode {
|
||||
self.node.opaque()
|
||||
}
|
||||
|
||||
fn pseudo_element_chain(&self) -> PseudoElementChain {
|
||||
self.pseudo_element_chain
|
||||
}
|
||||
|
||||
fn type_id(&self) -> Option<LayoutNodeType> {
|
||||
if self.pseudo_element_chain.is_empty() {
|
||||
Some(NodeTypeIdWrapper(self.node.type_id_for_layout()).into())
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
fn style(&self, context: &SharedStyleContext) -> Arc<ComputedValues> {
|
||||
if let Some(element) = self.as_element() {
|
||||
element.style(context)
|
||||
} else {
|
||||
// Text nodes are not styled during traversal,instead we simply
|
||||
// return parent style here and do cascading during layout.
|
||||
debug_assert!(self.is_text_node());
|
||||
self.parent_style(context)
|
||||
}
|
||||
}
|
||||
|
||||
fn parent_style(&self, context: &SharedStyleContext) -> Arc<ComputedValues> {
|
||||
if let Some(chain) = self.pseudo_element_chain.without_innermost() {
|
||||
let mut parent = *self;
|
||||
parent.pseudo_element_chain = chain;
|
||||
return parent.style(context);
|
||||
}
|
||||
unsafe { self.dangerous_flat_tree_parent() }
|
||||
.unwrap()
|
||||
.style(context)
|
||||
}
|
||||
|
||||
fn selected_style(&self, context: &SharedStyleContext) -> Arc<ComputedValues> {
|
||||
let Some(element) = self.as_element() else {
|
||||
// TODO(stshine): What should the selected style be for text?
|
||||
debug_assert!(self.is_text_node());
|
||||
return self.parent_style(context);
|
||||
};
|
||||
|
||||
let style_data = &element.element_data().styles;
|
||||
let get_selected_style = || {
|
||||
// This is a workaround for handling the `::selection` pseudos where it would not
|
||||
// propagate to the children and Shadow DOM elements. For this case, UA widget
|
||||
// inner elements should follow the originating element in terms of selection.
|
||||
if self.node.is_in_ua_widget() {
|
||||
return Some(
|
||||
Self::from(
|
||||
self.node
|
||||
.containing_shadow_root_for_layout()?
|
||||
.get_host_for_layout()
|
||||
.upcast(),
|
||||
)
|
||||
.selected_style(context),
|
||||
);
|
||||
}
|
||||
style_data.pseudos.get(&PseudoElement::Selection).cloned()
|
||||
};
|
||||
|
||||
get_selected_style().unwrap_or_else(|| style_data.primary().clone())
|
||||
}
|
||||
|
||||
fn initialize_layout_data<RequestedLayoutDataType: LayoutDataTrait>(&self) {
|
||||
if self.node.layout_data().is_none() {
|
||||
unsafe {
|
||||
self.node
|
||||
.initialize_layout_data(Box::<RequestedLayoutDataType>::default());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn flat_tree_children(&self) -> LayoutIterator<ServoLayoutNodeChildrenIterator<'dom>> {
|
||||
LayoutIterator(ServoLayoutNodeChildrenIterator::new_for_flat_tree(*self))
|
||||
}
|
||||
|
||||
fn dom_children(&self) -> LayoutIterator<ServoLayoutNodeChildrenIterator<'dom>> {
|
||||
LayoutIterator(ServoLayoutNodeChildrenIterator::new_for_dom_tree(*self))
|
||||
}
|
||||
|
||||
fn as_element(&self) -> Option<ServoLayoutElement<'dom>> {
|
||||
self.node.downcast().map(|element| ServoLayoutElement {
|
||||
element,
|
||||
pseudo_element_chain: self.pseudo_element_chain,
|
||||
})
|
||||
}
|
||||
|
||||
fn as_html_element(&self) -> Option<ServoLayoutElement<'dom>> {
|
||||
self.as_element()
|
||||
.filter(|element| element.is_html_element())
|
||||
}
|
||||
|
||||
fn text_content(self) -> Cow<'dom, str> {
|
||||
self.node.text_content()
|
||||
}
|
||||
|
||||
fn selection(&self) -> Option<SharedSelection> {
|
||||
self.node.selection()
|
||||
}
|
||||
|
||||
fn image_url(&self) -> Option<ServoUrl> {
|
||||
self.node.image_url()
|
||||
}
|
||||
|
||||
fn image_density(&self) -> Option<f64> {
|
||||
self.node.image_density()
|
||||
}
|
||||
|
||||
fn showing_broken_image_icon(&self) -> bool {
|
||||
self.node.showing_broken_image_icon()
|
||||
}
|
||||
|
||||
fn image_data(&self) -> Option<(Option<Image>, Option<ImageMetadata>)> {
|
||||
self.node.image_data()
|
||||
}
|
||||
|
||||
fn canvas_data(&self) -> Option<HTMLCanvasData> {
|
||||
self.node.canvas_data()
|
||||
}
|
||||
|
||||
fn media_data(&self) -> Option<HTMLMediaData> {
|
||||
self.node.media_data()
|
||||
}
|
||||
|
||||
fn svg_data(&self) -> Option<SVGElementData<'dom>> {
|
||||
self.node.svg_data()
|
||||
}
|
||||
|
||||
fn iframe_browsing_context_id(&self) -> Option<BrowsingContextId> {
|
||||
self.node.iframe_browsing_context_id()
|
||||
}
|
||||
|
||||
fn iframe_pipeline_id(&self) -> Option<PipelineId> {
|
||||
self.node.iframe_pipeline_id()
|
||||
}
|
||||
|
||||
fn table_span(&self) -> Option<u32> {
|
||||
self.node
|
||||
.downcast::<Element>()
|
||||
.and_then(|element| element.get_span())
|
||||
}
|
||||
|
||||
fn table_colspan(&self) -> Option<u32> {
|
||||
self.node
|
||||
.downcast::<Element>()
|
||||
.and_then(|element| element.get_colspan())
|
||||
}
|
||||
|
||||
fn table_rowspan(&self) -> Option<u32> {
|
||||
self.node
|
||||
.downcast::<Element>()
|
||||
.and_then(|element| element.get_rowspan())
|
||||
}
|
||||
|
||||
fn set_uses_content_attribute_with_attr(&self, uses_content_attribute_with_attr: bool) {
|
||||
unsafe {
|
||||
self.node.set_flag(
|
||||
NodeFlags::USES_ATTR_IN_CONTENT_ATTRIBUTE,
|
||||
uses_content_attribute_with_attr,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
fn is_single_line_text_input(&self) -> bool {
|
||||
self.pseudo_element_chain.is_empty() && self.node.is_text_container_of_single_line_input()
|
||||
}
|
||||
}
|
||||
|
||||
impl NodeInfo for ServoLayoutNode<'_> {
|
||||
fn is_element(&self) -> bool {
|
||||
self.node.is_element_for_layout()
|
||||
}
|
||||
|
||||
fn is_text_node(&self) -> bool {
|
||||
self.node.is_text_node_for_layout()
|
||||
}
|
||||
}
|
||||
152
components/shared/layout/layout_element.rs
Normal file
152
components/shared/layout/layout_element.rs
Normal file
@@ -0,0 +1,152 @@
|
||||
/* 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/. */
|
||||
|
||||
#![expect(unsafe_code)]
|
||||
#![deny(missing_docs)]
|
||||
|
||||
use std::fmt::Debug;
|
||||
|
||||
use html5ever::{LocalName, Namespace};
|
||||
use servo_arc::Arc;
|
||||
use style::attr::AttrValue;
|
||||
use style::context::SharedStyleContext;
|
||||
use style::data::{ElementDataMut, ElementDataRef};
|
||||
use style::dom::TElement;
|
||||
use style::properties::ComputedValues;
|
||||
use style::selector_parser::{PseudoElement, SelectorImpl};
|
||||
|
||||
use crate::{LayoutDataTrait, LayoutNode, LayoutNodeType, PseudoElementChain, StyleData};
|
||||
|
||||
/// A trait that exposes a DOM element to layout. Implementors of this trait must abide by certain
|
||||
/// safety requirements. Layout will only ever access and mutate each element from a single thread
|
||||
/// at a time, though children may be used in parallel from other threads. That is why this trait
|
||||
/// does not allow access to parent nodes, as it would make it easy to cause race conditions and
|
||||
/// memory errors.
|
||||
///
|
||||
/// Note that the related [`DangerousStyleElement`] trait *may* access parent nodes, which is why
|
||||
/// that API is marked as `unsafe` here. In general [`DangerousStyleElement`] should only be used
|
||||
/// when interfacing with the `stylo` and `selectors`.
|
||||
pub trait LayoutElement<'dom>: Copy + Debug + Send + Sync {
|
||||
/// An associated type that refers to the concrete implementation of [`DangerousStyleElement`]
|
||||
/// implemented in `script`.
|
||||
type ConcreteStyleElement: DangerousStyleElement<'dom>;
|
||||
/// An associated type that refers to the concrete implementation of [`LayoutNode`]
|
||||
/// implemented in `script`.
|
||||
type ConcreteLayoutNode: LayoutNode<'dom>;
|
||||
|
||||
/// Creates a new `LayoutElement` for the same `LayoutElement`
|
||||
/// with a different pseudo-element type.
|
||||
///
|
||||
/// Returns `None` if this pseudo doesn't apply to the given element for one of
|
||||
/// the following reasons:
|
||||
///
|
||||
/// 1. `pseudo` is eager and is not defined in the stylesheet. In this case, there
|
||||
/// is not reason to process the pseudo element at all.
|
||||
/// 2. `pseudo` is for `::servo-details-content` and
|
||||
/// it doesn't apply to this element, either because it isn't a details or is
|
||||
/// in the wrong state.
|
||||
fn with_pseudo(&self, pseudo: PseudoElement) -> Option<Self>;
|
||||
|
||||
/// Returns the [`PseudoElementChain`] for this [`LayoutElement`].
|
||||
fn pseudo_element_chain(&self) -> PseudoElementChain;
|
||||
|
||||
/// Return this [`LayoutElement`] as a [`LayoutNode`], preserving the internal
|
||||
/// pseudo-element chain.
|
||||
fn as_node(&self) -> Self::ConcreteLayoutNode;
|
||||
|
||||
/// Returns access to a version of this LayoutElement that can be used by stylo
|
||||
/// and selectors. This is dangerous as it allows more access to ancestor nodes
|
||||
/// that might be in the process of being read or written to in other threads.
|
||||
/// This should *only* be used when handing a node to stylo or selectors.
|
||||
///
|
||||
/// # Safety
|
||||
///
|
||||
/// This should only ever be called from the main script thread. It is never
|
||||
/// okay to explicitly create a node for style while any layout worker threads
|
||||
/// are running.
|
||||
unsafe fn dangerous_style_element(self) -> Self::ConcreteStyleElement;
|
||||
|
||||
/// Initialize this node with empty style and opaque layout data.
|
||||
fn initialize_style_and_layout_data<RequestedLayoutDataType: LayoutDataTrait>(&self);
|
||||
|
||||
/// Unset the snapshot flags on the underlying DOM object for this element.
|
||||
fn unset_snapshot_flags(&self);
|
||||
|
||||
/// Set the snapshot flags on the underlying DOM object for this element.
|
||||
fn set_has_snapshot(&self);
|
||||
|
||||
/// Get the [`StyleData`] for this [`LayoutElement`].
|
||||
fn style_data(self) -> Option<&'dom StyleData>;
|
||||
|
||||
/// Returns the type ID of this node.
|
||||
/// Returns `None` if this is a pseudo-element; otherwise, returns `Some`.
|
||||
fn type_id(&self) -> Option<LayoutNodeType>;
|
||||
|
||||
/// Get the local name of this element. See
|
||||
/// <https://dom.spec.whatwg.org/#concept-element-local-name>.
|
||||
fn local_name(&self) -> &LocalName;
|
||||
|
||||
/// Get the attribute with the given `namespace` and `name` as an [`AttrValue`] if it
|
||||
/// exists, otherwise return `None`.
|
||||
fn attribute(&self, namespace: &Namespace, name: &LocalName) -> Option<&AttrValue>;
|
||||
|
||||
/// Get the attribute with the given `namespace` and `name` as an [`AttrValue`] if it
|
||||
/// exists and converting the result to a `&str`, otherwise return `None`.
|
||||
fn attribute_as_str<'a>(&'a self, namespace: &Namespace, name: &LocalName) -> Option<&'a str>;
|
||||
|
||||
/// Get a reference to the inner [`ElementDataRef`] for this element's [`StyleData`]. This will
|
||||
/// panic if the element is unstyled.
|
||||
fn element_data(&self) -> ElementDataRef<'dom>;
|
||||
|
||||
/// Get a mutable reference to the inner [`ElementDataRef`] for this element's [`StyleData`].
|
||||
/// This will panic if the element is unstyled.
|
||||
fn element_data_mut(&self) -> ElementDataMut<'dom>;
|
||||
|
||||
/// Returns the computed style for the given element, properly handling pseudo-elements.
|
||||
///
|
||||
/// # Panics
|
||||
///
|
||||
/// Calling this method will panic if the element has no style data, whether because styling has
|
||||
/// not run yet or was not run for this element.
|
||||
fn style(&self, context: &SharedStyleContext) -> Arc<ComputedValues>;
|
||||
|
||||
/// Returns `true` if this [`LayoutElement`] is a shadow DOM host and `false` otherwise.
|
||||
fn is_shadow_host(&self) -> bool;
|
||||
|
||||
/// Returns whether this node is a body element of an HTML element root
|
||||
/// in an HTML document.
|
||||
///
|
||||
/// Note that this does require accessing the parent, which this interface
|
||||
/// technically forbids. But accessing the parent is only unsafe insofar as
|
||||
/// it can be used to reach siblings and cousins. A simple immutable borrow
|
||||
/// of the parent data is fine, since the bottom-up traversal will not process
|
||||
/// the parent until all the children have been processed.
|
||||
fn is_body_element_of_html_element_root(&self) -> bool;
|
||||
|
||||
/// Returns `true` if this [`LayoutNode`] is any kind of HTML element inside an HTML document
|
||||
/// and `false` otherwise.
|
||||
fn is_html_element_in_html_document(&self) -> bool;
|
||||
|
||||
/// Returns whether this node is the root element in an HTML document element.
|
||||
///
|
||||
/// Note that, like `Self::is_body_element_of_html_element_root`, this accesses the parent.
|
||||
/// As in that case, since this is an immutable borrow, we do not violate thread safety.
|
||||
fn is_root(&self) -> bool;
|
||||
}
|
||||
|
||||
/// An element that can be passed to `stylo` and `selectors` that allows accessing the
|
||||
/// parent node. We consider this to be too dangerous for normal layout, so it is
|
||||
/// reserved only for using `stylo` and `selectors`.
|
||||
///
|
||||
/// If you are not interfacing with `stylo` and `selectors` you *should not* use this
|
||||
/// type, unless you know what you are doing.
|
||||
pub trait DangerousStyleElement<'dom>:
|
||||
TElement + ::selectors::Element<Impl = SelectorImpl> + Send + Sync
|
||||
{
|
||||
/// The concrete implementation of [`LayoutElement`] implemented in `script`.
|
||||
type ConcreteLayoutElement: LayoutElement<'dom>;
|
||||
/// The concrete implementation of [`LayoutNode`] implemented in `script`.
|
||||
/// Get a handle to the original "safe" version of this element, a [`LayoutElement`].
|
||||
fn layout_element(&self) -> Self::ConcreteLayoutElement;
|
||||
}
|
||||
240
components/shared/layout/layout_node.rs
Normal file
240
components/shared/layout/layout_node.rs
Normal file
@@ -0,0 +1,240 @@
|
||||
/* 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/. */
|
||||
|
||||
#![expect(unsafe_code)]
|
||||
#![deny(missing_docs)]
|
||||
|
||||
use std::borrow::Cow;
|
||||
use std::fmt::Debug;
|
||||
|
||||
use net_traits::image_cache::Image;
|
||||
use pixels::ImageMetadata;
|
||||
use servo_arc::Arc;
|
||||
use servo_base::id::{BrowsingContextId, PipelineId};
|
||||
use servo_url::ServoUrl;
|
||||
use style::context::SharedStyleContext;
|
||||
use style::dom::{LayoutIterator, NodeInfo, OpaqueNode, TNode};
|
||||
use style::properties::ComputedValues;
|
||||
use style::selector_parser::PseudoElement;
|
||||
|
||||
use crate::layout_element::{DangerousStyleElement, LayoutElement};
|
||||
use crate::pseudo_element_chain::PseudoElementChain;
|
||||
use crate::{
|
||||
GenericLayoutData, HTMLCanvasData, HTMLMediaData, LayoutDataTrait, LayoutNodeType,
|
||||
SVGElementData, SharedSelection,
|
||||
};
|
||||
|
||||
/// A trait that exposes a DOM nodes to layout. Implementors of this trait must abide by certain
|
||||
/// safety requirements. Layout will only ever access and mutate each node from a single thread
|
||||
/// at a time, though children may be used in parallel from other threads. That is why this trait
|
||||
/// does not allow access to parent nodes, as it would make it easy to cause race conditions and
|
||||
/// memory errors.
|
||||
///
|
||||
/// Note that the related [`DangerousStyleNode`] trait *may* access parent nodes, which is why
|
||||
/// that API is marked as `unsafe` here. In general [`DangerousStyleNode`] should only be used
|
||||
/// when interfacing with the `stylo` and `selectors`.
|
||||
pub trait LayoutNode<'dom>: Copy + Debug + NodeInfo + Send + Sync {
|
||||
/// The concrete implementation of [`DangerousStyleNode`] implemented in `script`.
|
||||
type ConcreteDangerousStyleNode: DangerousStyleNode<'dom>;
|
||||
/// The concrete implementation of [`DangerousStyleElement`] implemented in `script`.
|
||||
type ConcreteDangerousStyleElement: DangerousStyleElement<'dom>;
|
||||
/// The concrete implementation of [`ConcreteLayoutElement`] implemented in `script`.
|
||||
type ConcreteLayoutElement: LayoutElement<'dom>;
|
||||
/// The concrete implementation of [`ChildIterator`] implemented in `script`.
|
||||
type ChildIterator: Iterator<Item = Self> + Sized;
|
||||
|
||||
/// Creates a new `LayoutNode` for the same `LayoutNode` with a different pseudo-element type.
|
||||
///
|
||||
/// Returns `None` if this pseudo doesn't apply to the given element for one of
|
||||
/// the following reasons:
|
||||
///
|
||||
/// 1. This node is not an element.
|
||||
/// 2. `pseudo` is eager and is not defined in the stylesheet. In this case, there
|
||||
/// is not reason to process the pseudo element at all.
|
||||
/// 3. `pseudo` is for `::servo-details-content` and
|
||||
/// it doesn't apply to this element, either because it isn't a details or is
|
||||
/// in the wrong state.
|
||||
fn with_pseudo(&self, pseudo_element_type: PseudoElement) -> Option<Self>;
|
||||
|
||||
/// Returns the [`PseudoElementChain`] for this [`LayoutElement`].
|
||||
fn pseudo_element_chain(&self) -> PseudoElementChain;
|
||||
|
||||
/// Returns access to a version of this LayoutNode that can be used by stylo
|
||||
/// and selectors. This is dangerous as it allows more access to ancestors nodes
|
||||
/// than LayoutNode. This should *only* be used when handing a node to stylo
|
||||
/// or selectors.
|
||||
///
|
||||
/// # Safety
|
||||
///
|
||||
/// This should only ever be called from the main script thread. It is never
|
||||
/// okay to explicitly create a node for style while any layout worker threads
|
||||
/// are running.
|
||||
unsafe fn dangerous_style_node(self) -> Self::ConcreteDangerousStyleNode;
|
||||
|
||||
/// Returns access to the DOM parent node of this node. This *does not* take
|
||||
/// into account shadow tree children and slottables. For that use
|
||||
/// [`Self::dangerous_flat_tree_parent`].
|
||||
///
|
||||
/// # Safety
|
||||
///
|
||||
/// This should only ever be called from the main script thread. It is never
|
||||
/// okay to explicitly access the parent node while any layout worker threads
|
||||
/// are running.
|
||||
unsafe fn dangerous_dom_parent(self) -> Option<Self>;
|
||||
|
||||
/// Returns access to the flat tree parent node of this node. This takes
|
||||
/// into account shadow tree children and slottables. For that use
|
||||
/// [`Self::dangerous_flat_tree_parent`].
|
||||
///
|
||||
/// # Safety
|
||||
///
|
||||
/// This should only ever be called from the main script thread. It is never
|
||||
/// okay to explicitly access the parent node while any layout worker threads
|
||||
/// are running.
|
||||
unsafe fn dangerous_flat_tree_parent(self) -> Option<Self>;
|
||||
|
||||
/// Get the layout data of this node, attempting to downcast it to the desired type.
|
||||
/// Returns None if there is no layout data or it isn't of the desired type.
|
||||
fn layout_data(&self) -> Option<&'dom GenericLayoutData>;
|
||||
|
||||
/// Returns whether the node is connected.
|
||||
fn is_connected(&self) -> bool;
|
||||
|
||||
/// Converts self into an `OpaqueNode`.
|
||||
fn opaque(&self) -> OpaqueNode;
|
||||
|
||||
/// Returns the type ID of this node. Returns `None` if this is a pseudo-element; otherwise,
|
||||
/// returns `Some`.
|
||||
fn type_id(&self) -> Option<LayoutNodeType>;
|
||||
|
||||
/// Initialize this node with empty opaque layout data.
|
||||
fn initialize_layout_data<RequestedLayoutDataType: LayoutDataTrait>(&self);
|
||||
|
||||
/// Returns an iterator over this node's children in the [flat tree]. This
|
||||
/// takes into account shadow tree children and slottables.
|
||||
///
|
||||
/// [flat tree]: https://drafts.csswg.org/css-shadow-1/#flat-tree
|
||||
fn flat_tree_children(&self) -> LayoutIterator<Self::ChildIterator>;
|
||||
|
||||
/// Returns an iterator over this node's children in the DOM. This
|
||||
/// *does not* take shadow roots and assigned slottables into account.
|
||||
/// For that use [`Self::flat_tree_children`].
|
||||
fn dom_children(&self) -> LayoutIterator<Self::ChildIterator>;
|
||||
|
||||
/// Returns a [`LayoutElement`] if this is an element in the HTML namespace, None otherwise.
|
||||
fn as_html_element(&self) -> Option<Self::ConcreteLayoutElement>;
|
||||
|
||||
/// Returns a [`LayoutElement`] if this is an element.
|
||||
fn as_element(&self) -> Option<Self::ConcreteLayoutElement>;
|
||||
|
||||
/// Returns the computed style for the given node, properly handling pseudo-elements. For
|
||||
/// elements this returns their style and for other nodes, this returns the style of the parent
|
||||
/// element, if one exists.
|
||||
///
|
||||
/// # Panics
|
||||
///
|
||||
/// - Calling this method will panic it is an element has no style data, whether because
|
||||
/// styling has not run yet or was not run for this element.
|
||||
/// - Calling this method will panic if it is a non-element node without a parent element.
|
||||
fn style(&self, context: &SharedStyleContext) -> Arc<ComputedValues>;
|
||||
|
||||
/// Returns the style for a text node. This is computed on the fly from the
|
||||
/// parent style to avoid traversing text nodes in the style system.
|
||||
///
|
||||
/// # Safety
|
||||
///
|
||||
/// Note that this does require accessing the parent, which this interface
|
||||
/// technically forbids. But accessing the parent is only unsafe insofar as
|
||||
/// it can be used to reach siblings and cousins. A simple immutable borrow
|
||||
/// of the parent data is fine, since the bottom-up traversal will not process
|
||||
/// the parent until all the children have been processed.
|
||||
///
|
||||
/// # Panics
|
||||
///
|
||||
/// - Calling this method will panic if the parent element has no style data, whether
|
||||
/// because styling has not run yet or was not run for this element.
|
||||
/// - Calling this method will panic if it is a non-element node without a parent element.
|
||||
fn parent_style(&self, context: &SharedStyleContext) -> Arc<ComputedValues>;
|
||||
|
||||
/// Returns the computed `:selected` style for the given node, properly handling
|
||||
/// pseudo-elements. For elements this returns their style and for other nodes, this
|
||||
/// returns the style of the parent element, if one exists.
|
||||
///
|
||||
/// # Panics
|
||||
///
|
||||
/// - Calling this method will panic it is an element has no style data, whether because
|
||||
/// styling has not run yet or was not run for this element.
|
||||
/// - Calling this method will panic if it is a non-element node without a parent element.
|
||||
fn selected_style(&self, context: &SharedStyleContext) -> Arc<ComputedValues>;
|
||||
|
||||
/// Get the text content of this node, if it is a text node.
|
||||
///
|
||||
/// # Panics
|
||||
///
|
||||
/// This method will panic if called on a node that is not a DOM text node.
|
||||
fn text_content(self) -> Cow<'dom, str>;
|
||||
|
||||
/// If this node manages a selection, this returns the shared selection for the node.
|
||||
fn selection(&self) -> Option<SharedSelection>;
|
||||
|
||||
/// If this is an image element, returns its URL. If this is not an image element, fails.
|
||||
fn image_url(&self) -> Option<ServoUrl>;
|
||||
|
||||
/// If this is an image element, returns its current-pixel-density. If this is not an image element, fails.
|
||||
fn image_density(&self) -> Option<f64>;
|
||||
|
||||
/// If this is an image element, returns its image data. Otherwise, returns `None`.
|
||||
fn image_data(&self) -> Option<(Option<Image>, Option<ImageMetadata>)>;
|
||||
|
||||
/// Whether or not this is an image element that is showing a broken image icon.
|
||||
fn showing_broken_image_icon(&self) -> bool;
|
||||
|
||||
/// Return the [`HTMLCanvas`] data for this node, if it is a canvas.
|
||||
fn canvas_data(&self) -> Option<HTMLCanvasData>;
|
||||
|
||||
/// Return the [`SVGElementData`] for this node, if it is an SVG subtree.
|
||||
fn svg_data(&self) -> Option<SVGElementData<'dom>>;
|
||||
|
||||
/// Return the [`HTMLMediaData`] for this node, if it is a media element.
|
||||
fn media_data(&self) -> Option<HTMLMediaData>;
|
||||
|
||||
/// If this node is an iframe element, returns its browsing context ID. If this node is
|
||||
/// not an iframe element, fails. Returns None if there is no nested browsing context.
|
||||
fn iframe_browsing_context_id(&self) -> Option<BrowsingContextId>;
|
||||
|
||||
/// If this node is an iframe element, returns its pipeline ID. If this node is
|
||||
/// not an iframe element, fails. Returns None if there is no nested browsing context.
|
||||
fn iframe_pipeline_id(&self) -> Option<PipelineId>;
|
||||
|
||||
/// Return the table span property if it is an element that supports it.
|
||||
fn table_span(&self) -> Option<u32>;
|
||||
|
||||
/// Return the table colspan property if it is an element that supports it.
|
||||
fn table_colspan(&self) -> Option<u32>;
|
||||
|
||||
/// Return the table rowspan property if it is an element that supports it.
|
||||
fn table_rowspan(&self) -> Option<u32>;
|
||||
|
||||
/// Whether this is a container for the text within a single-line text input. This
|
||||
/// is used to solve the special case of line height for a text entry widget.
|
||||
/// <https://html.spec.whatwg.org/multipage/#the-input-element-as-a-text-entry-widget>
|
||||
fn is_single_line_text_input(&self) -> bool;
|
||||
|
||||
/// Set whether or not this node has an active pseudo-element style with a `content`
|
||||
/// attribute that uses `attr`.
|
||||
fn set_uses_content_attribute_with_attr(&self, _uses_content_attribute_with_attr: bool);
|
||||
}
|
||||
|
||||
/// A node that can be passed to `stylo` and `selectors` that allows accessing the
|
||||
/// parent node. We consider this to be too dangerous for normal layout, so it is
|
||||
/// reserved only for using `stylo` and `selectors`.
|
||||
///
|
||||
/// If you are not interfacing with `stylo` and `selectors` you *should not* use this
|
||||
/// type, unless you know what you are doing.
|
||||
pub trait DangerousStyleNode<'dom>: TNode + Sized + NodeInfo + Send + Sync {
|
||||
/// The concrete implementation of [`LayoutNode`] implemented in `script`.
|
||||
type ConcreteLayoutNode: LayoutNode<'dom>;
|
||||
/// Get a handle to the original "safe" version of this node, a [`LayoutNode`] implementation.
|
||||
fn layout_node(&self) -> Self::ConcreteLayoutNode;
|
||||
}
|
||||
@@ -9,9 +9,12 @@
|
||||
#![deny(unsafe_code)]
|
||||
|
||||
mod layout_damage;
|
||||
pub mod wrapper_traits;
|
||||
mod layout_element;
|
||||
mod layout_node;
|
||||
mod pseudo_element_chain;
|
||||
|
||||
use std::any::Any;
|
||||
use std::ops::Range;
|
||||
use std::rc::Rc;
|
||||
use std::sync::Arc;
|
||||
use std::sync::atomic::AtomicIsize;
|
||||
@@ -19,12 +22,15 @@ use std::thread::JoinHandle;
|
||||
use std::time::Duration;
|
||||
|
||||
use app_units::Au;
|
||||
use atomic_refcell::AtomicRefCell;
|
||||
use background_hang_monitor_api::BackgroundHangMonitorRegister;
|
||||
use bitflags::bitflags;
|
||||
use embedder_traits::{Cursor, Theme, UntrustedNodeAddress, ViewportDetails};
|
||||
use euclid::{Point2D, Rect};
|
||||
use fonts::{FontContext, WebFontDocumentContext};
|
||||
use fonts::{FontContext, TextByteRange, WebFontDocumentContext};
|
||||
pub use layout_damage::LayoutDamage;
|
||||
pub use layout_element::{DangerousStyleElement, LayoutElement};
|
||||
pub use layout_node::{DangerousStyleNode, LayoutNode};
|
||||
use libc::c_void;
|
||||
use malloc_size_of::{MallocSizeOf as MallocSizeOfTrait, MallocSizeOfOps, malloc_size_of_is_0};
|
||||
use malloc_size_of_derive::MallocSizeOf;
|
||||
@@ -34,6 +40,7 @@ use parking_lot::RwLock;
|
||||
use pixels::RasterImage;
|
||||
use profile_traits::mem::Report;
|
||||
use profile_traits::time;
|
||||
pub use pseudo_element_chain::PseudoElementChain;
|
||||
use rustc_hash::FxHashMap;
|
||||
use script_traits::{InitialScriptState, Painter, ScriptThreadMessage};
|
||||
use serde::{Deserialize, Serialize};
|
||||
@@ -62,11 +69,12 @@ use style_traits::CSSPixel;
|
||||
use webrender_api::units::{DeviceIntSize, LayoutPoint, LayoutVector2D};
|
||||
use webrender_api::{ExternalScrollId, ImageKey};
|
||||
|
||||
pub trait GenericLayoutDataTrait: Any + MallocSizeOfTrait {
|
||||
pub trait GenericLayoutDataTrait: Any + MallocSizeOfTrait + Send + Sync + 'static {
|
||||
fn as_any(&self) -> &dyn Any;
|
||||
}
|
||||
|
||||
pub type GenericLayoutData = dyn GenericLayoutDataTrait + Send + Sync;
|
||||
pub trait LayoutDataTrait: GenericLayoutDataTrait + Default {}
|
||||
pub type GenericLayoutData = dyn GenericLayoutDataTrait;
|
||||
|
||||
#[derive(Default, MallocSizeOf)]
|
||||
pub struct StyleData {
|
||||
@@ -120,6 +128,21 @@ pub enum LayoutElementType {
|
||||
SVGSVGElement,
|
||||
}
|
||||
|
||||
/// A selection shared between script and layout. This selection is managed by the DOM
|
||||
/// node that maintains it, and can be modified from script. Once modified, layout is
|
||||
/// expected to reflect the new selection visual on the next display list update.
|
||||
#[derive(Clone, Debug, Default, MallocSizeOf, PartialEq)]
|
||||
pub struct ScriptSelection {
|
||||
/// The range of this selection in the DOM node that manages it.
|
||||
pub range: TextByteRange,
|
||||
/// The character range of this selection in the DOM node that manages it.
|
||||
pub character_range: Range<usize>,
|
||||
/// Whether or not this selection is enabled. Selections may be disabled
|
||||
/// when their node loses focus.
|
||||
pub enabled: bool,
|
||||
}
|
||||
|
||||
pub type SharedSelection = Arc<AtomicRefCell<ScriptSelection>>;
|
||||
pub struct HTMLCanvasData {
|
||||
pub image_key: Option<ImageKey>,
|
||||
pub width: u32,
|
||||
|
||||
62
components/shared/layout/pseudo_element_chain.rs
Normal file
62
components/shared/layout/pseudo_element_chain.rs
Normal file
@@ -0,0 +1,62 @@
|
||||
/* 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::fmt::Debug;
|
||||
|
||||
use malloc_size_of_derive::MallocSizeOf;
|
||||
use style::selector_parser::PseudoElement;
|
||||
|
||||
/// A chain of pseudo-elements up to two levels deep. This is used to represent cases
|
||||
/// where a pseudo-element has its own child pseudo element (for instance
|
||||
/// `.div::after::marker`). If both [`Self::primary`] and [`Self::secondary`] are `None`,
|
||||
/// then this chain represents the element itself. Not all combinations of pseudo-elements
|
||||
/// are possible and we may not be able to calculate a style for all
|
||||
/// [`PseudoElementChain`]s.
|
||||
#[derive(Clone, Copy, Debug, Default, Eq, Hash, MallocSizeOf, PartialEq)]
|
||||
pub struct PseudoElementChain {
|
||||
pub primary: Option<PseudoElement>,
|
||||
pub secondary: Option<PseudoElement>,
|
||||
}
|
||||
|
||||
impl PseudoElementChain {
|
||||
pub fn unnested(pseudo_element: PseudoElement) -> Self {
|
||||
Self {
|
||||
primary: Some(pseudo_element),
|
||||
secondary: None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn innermost(&self) -> Option<PseudoElement> {
|
||||
self.secondary.or(self.primary)
|
||||
}
|
||||
|
||||
/// Return a possibly nested [`PseudoElementChain`]. Currently only `::before` and
|
||||
/// `::after` only support nesting. If the primary [`PseudoElement`] on the chain is
|
||||
/// not `::before` or `::after` a single element chain is returned for the given
|
||||
/// [`PseudoElement`].
|
||||
pub fn with_pseudo(&self, pseudo_element: PseudoElement) -> Self {
|
||||
match self.primary {
|
||||
Some(primary) if primary.is_before_or_after() => Self {
|
||||
primary: self.primary,
|
||||
secondary: Some(pseudo_element),
|
||||
},
|
||||
_ => {
|
||||
assert!(self.secondary.is_none());
|
||||
Self::unnested(pseudo_element)
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
pub fn without_innermost(&self) -> Option<Self> {
|
||||
let primary = self.primary?;
|
||||
Some(
|
||||
self.secondary
|
||||
.map_or_else(Self::default, |_| Self::unnested(primary)),
|
||||
)
|
||||
}
|
||||
|
||||
pub fn is_empty(&self) -> bool {
|
||||
self.primary.is_none()
|
||||
}
|
||||
}
|
||||
@@ -1,479 +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/. */
|
||||
|
||||
#![expect(unsafe_code)]
|
||||
|
||||
use std::borrow::Cow;
|
||||
use std::fmt::Debug;
|
||||
use std::ops::Range;
|
||||
|
||||
use atomic_refcell::AtomicRefCell;
|
||||
use fonts::TextByteRange;
|
||||
use html5ever::{LocalName, Namespace};
|
||||
use malloc_size_of_derive::MallocSizeOf;
|
||||
use net_traits::image_cache::Image;
|
||||
use pixels::ImageMetadata;
|
||||
use servo_arc::Arc;
|
||||
use servo_base::id::{BrowsingContextId, PipelineId};
|
||||
use servo_url::ServoUrl;
|
||||
use style::attr::AttrValue;
|
||||
use style::context::SharedStyleContext;
|
||||
use style::data::{ElementDataMut, ElementDataRef};
|
||||
use style::dom::{LayoutIterator, NodeInfo, OpaqueNode, TElement, TNode};
|
||||
use style::properties::ComputedValues;
|
||||
use style::selector_parser::{PseudoElement, PseudoElementCascadeType, SelectorImpl};
|
||||
use style::stylist::RuleInclusion;
|
||||
|
||||
use crate::{
|
||||
GenericLayoutData, GenericLayoutDataTrait, HTMLCanvasData, HTMLMediaData, LayoutNodeType,
|
||||
SVGElementData, StyleData,
|
||||
};
|
||||
|
||||
pub trait LayoutDataTrait: GenericLayoutDataTrait + Default + Send + Sync + 'static {}
|
||||
|
||||
/// A wrapper so that layout can access only the methods that it should have access to. Layout must
|
||||
/// only ever see these and must never see instances of `LayoutDom`.
|
||||
/// FIXME(mrobinson): `Send + Sync` is required here for Layout 2020, but eventually it
|
||||
/// should stop sending LayoutNodes to other threads and rely on ThreadSafeLayoutNode
|
||||
/// or some other mechanism to ensure thread safety.
|
||||
pub trait LayoutNode<'dom>: Copy + Debug + TNode + Send + Sync {
|
||||
type ConcreteThreadSafeLayoutNode: ThreadSafeLayoutNode<'dom>;
|
||||
type ConcreteLayoutElement: LayoutElement<'dom>;
|
||||
|
||||
fn to_threadsafe(&self) -> Self::ConcreteThreadSafeLayoutNode;
|
||||
|
||||
/// Returns the type ID of this node.
|
||||
fn type_id(&self) -> LayoutNodeType;
|
||||
|
||||
/// Get the layout data of this node, attempting to downcast it to the desired type.
|
||||
/// Returns None if there is no layout data or it isn't of the desired type.
|
||||
fn layout_data(&self) -> Option<&'dom GenericLayoutData>;
|
||||
|
||||
fn rev_children(self) -> LayoutIterator<ReverseChildrenIterator<Self>> {
|
||||
LayoutIterator(ReverseChildrenIterator {
|
||||
current: self.last_child(),
|
||||
})
|
||||
}
|
||||
|
||||
fn traverse_preorder(self) -> TreeIterator<Self> {
|
||||
TreeIterator::new(self)
|
||||
}
|
||||
|
||||
/// Returns whether the node is connected.
|
||||
fn is_connected(&self) -> bool;
|
||||
}
|
||||
|
||||
pub struct ReverseChildrenIterator<ConcreteNode> {
|
||||
current: Option<ConcreteNode>,
|
||||
}
|
||||
|
||||
impl<'dom, ConcreteNode> Iterator for ReverseChildrenIterator<ConcreteNode>
|
||||
where
|
||||
ConcreteNode: LayoutNode<'dom>,
|
||||
{
|
||||
type Item = ConcreteNode;
|
||||
fn next(&mut self) -> Option<ConcreteNode> {
|
||||
let node = self.current;
|
||||
self.current = node.and_then(|node| node.prev_sibling());
|
||||
node
|
||||
}
|
||||
}
|
||||
|
||||
pub struct TreeIterator<ConcreteNode> {
|
||||
stack: Vec<ConcreteNode>,
|
||||
}
|
||||
|
||||
impl<'dom, ConcreteNode> TreeIterator<ConcreteNode>
|
||||
where
|
||||
ConcreteNode: LayoutNode<'dom>,
|
||||
{
|
||||
fn new(root: ConcreteNode) -> TreeIterator<ConcreteNode> {
|
||||
let stack = vec![root];
|
||||
TreeIterator { stack }
|
||||
}
|
||||
|
||||
pub fn next_skipping_children(&mut self) -> Option<ConcreteNode> {
|
||||
self.stack.pop()
|
||||
}
|
||||
}
|
||||
|
||||
impl<'dom, ConcreteNode> Iterator for TreeIterator<ConcreteNode>
|
||||
where
|
||||
ConcreteNode: LayoutNode<'dom>,
|
||||
{
|
||||
type Item = ConcreteNode;
|
||||
fn next(&mut self) -> Option<ConcreteNode> {
|
||||
let ret = self.stack.pop();
|
||||
if let Some(node) = ret {
|
||||
self.stack.extend(node.rev_children())
|
||||
}
|
||||
ret
|
||||
}
|
||||
}
|
||||
|
||||
pub trait LayoutElement<'dom>: Copy + Debug + Send + Sync {
|
||||
/// Initialize this node with empty style and opaque layout data.
|
||||
///
|
||||
/// # Safety
|
||||
///
|
||||
/// This method is unsafe because it modifies the given node during
|
||||
/// layout. Callers should ensure that no other layout thread is
|
||||
/// attempting to read or modify the opaque layout data of this node.
|
||||
unsafe fn initialize_style_and_layout_data<RequestedLayoutDataType: LayoutDataTrait>(&self);
|
||||
|
||||
/// Get the [`StyleData`] for this [`LayoutElement`].
|
||||
fn style_data(self) -> Option<&'dom StyleData>;
|
||||
}
|
||||
|
||||
/// A thread-safe version of `LayoutNode`, used during flow construction. This type of layout
|
||||
/// node does not allow any parents or siblings of nodes to be accessed, to avoid races.
|
||||
pub trait ThreadSafeLayoutNode<'dom>: Clone + Copy + Debug + NodeInfo + PartialEq + Sized {
|
||||
type ConcreteNode: LayoutNode<'dom, ConcreteThreadSafeLayoutNode = Self>;
|
||||
type ConcreteElement: TElement;
|
||||
|
||||
type ConcreteThreadSafeLayoutElement: ThreadSafeLayoutElement<'dom, ConcreteThreadSafeLayoutNode = Self>
|
||||
+ ::selectors::Element<Impl = SelectorImpl>;
|
||||
type ChildrenIterator: Iterator<Item = Self> + Sized;
|
||||
|
||||
/// Converts self into an `OpaqueNode`.
|
||||
fn opaque(&self) -> OpaqueNode;
|
||||
|
||||
/// Returns the type ID of this node.
|
||||
/// Returns `None` if this is a pseudo-element; otherwise, returns `Some`.
|
||||
fn type_id(&self) -> Option<LayoutNodeType>;
|
||||
|
||||
/// Returns the style for a text node. This is computed on the fly from the
|
||||
/// parent style to avoid traversing text nodes in the style system.
|
||||
///
|
||||
/// Note that this does require accessing the parent, which this interface
|
||||
/// technically forbids. But accessing the parent is only unsafe insofar as
|
||||
/// it can be used to reach siblings and cousins. A simple immutable borrow
|
||||
/// of the parent data is fine, since the bottom-up traversal will not process
|
||||
/// the parent until all the children have been processed.
|
||||
fn parent_style(&self, context: &SharedStyleContext) -> Arc<ComputedValues>;
|
||||
|
||||
/// Initialize this node with empty opaque layout data.
|
||||
///
|
||||
/// # Safety
|
||||
///
|
||||
/// This method is unsafe because it modifies the given node during
|
||||
/// layout. Callers should ensure that no other layout thread is
|
||||
/// attempting to read or modify the opaque layout data of this node.
|
||||
fn initialize_layout_data<RequestedLayoutDataType: LayoutDataTrait>(&self);
|
||||
|
||||
fn debug_id(self) -> usize;
|
||||
|
||||
/// Returns an iterator over this node's children.
|
||||
fn children(&self) -> LayoutIterator<Self::ChildrenIterator>;
|
||||
|
||||
/// Returns a ThreadSafeLayoutElement if this is an element, None otherwise.
|
||||
fn as_element(&self) -> Option<Self::ConcreteThreadSafeLayoutElement>;
|
||||
|
||||
/// Returns a ThreadSafeLayoutElement if this is an element in an HTML namespace, None otherwise.
|
||||
fn as_html_element(&self) -> Option<Self::ConcreteThreadSafeLayoutElement>;
|
||||
|
||||
/// Get the layout data of this node, attempting to downcast it to the desired type.
|
||||
/// Returns None if there is no layout data or it isn't of the desired type.
|
||||
fn layout_data(&self) -> Option<&'dom GenericLayoutData>;
|
||||
|
||||
fn style(&self, context: &SharedStyleContext) -> Arc<ComputedValues> {
|
||||
if let Some(el) = self.as_element() {
|
||||
el.style(context)
|
||||
} else {
|
||||
// Text nodes are not styled during traversal,instead we simply
|
||||
// return parent style here and do cascading during layout.
|
||||
debug_assert!(self.is_text_node());
|
||||
self.parent_style(context)
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns true if this node contributes content. This is used in the implementation of
|
||||
/// `empty_cells` per CSS 2.1 § 17.6.1.1.
|
||||
fn is_content(&self) -> bool {
|
||||
self.type_id().is_some()
|
||||
}
|
||||
|
||||
/// Returns access to the underlying LayoutNode. This is breaks the abstraction
|
||||
/// barrier of ThreadSafeLayout wrapper layer, and can lead to races if not used
|
||||
/// carefully.
|
||||
///
|
||||
/// We need this because the implementation of some methods need to access the layout
|
||||
/// data flags, and we have this annoying trait separation between script and layout :-(
|
||||
fn unsafe_get(self) -> Self::ConcreteNode;
|
||||
|
||||
/// Get the text content of this node, if it is a text node.
|
||||
///
|
||||
/// Note: This should only be called on text nodes.
|
||||
fn text_content(self) -> Cow<'dom, str>;
|
||||
|
||||
/// If this node manages a selection, this returns the shared selection for the node.
|
||||
fn selection(&self) -> Option<SharedSelection>;
|
||||
|
||||
/// If this is an image element, returns its URL. If this is not an image element, fails.
|
||||
fn image_url(&self) -> Option<ServoUrl>;
|
||||
|
||||
/// If this is an image element, returns its current-pixel-density. If this is not an image element, fails.
|
||||
fn image_density(&self) -> Option<f64>;
|
||||
|
||||
/// If this is an image element, returns its image data. Otherwise, returns `None`.
|
||||
fn image_data(&self) -> Option<(Option<Image>, Option<ImageMetadata>)>;
|
||||
|
||||
/// Whether or not this is an image element that is showing a broken image icon.
|
||||
fn showing_broken_image_icon(&self) -> bool;
|
||||
|
||||
fn canvas_data(&self) -> Option<HTMLCanvasData>;
|
||||
|
||||
fn svg_data(&self) -> Option<SVGElementData<'dom>>;
|
||||
|
||||
fn media_data(&self) -> Option<HTMLMediaData>;
|
||||
|
||||
/// If this node is an iframe element, returns its browsing context ID. If this node is
|
||||
/// not an iframe element, fails. Returns None if there is no nested browsing context.
|
||||
fn iframe_browsing_context_id(&self) -> Option<BrowsingContextId>;
|
||||
|
||||
/// If this node is an iframe element, returns its pipeline ID. If this node is
|
||||
/// not an iframe element, fails. Returns None if there is no nested browsing context.
|
||||
fn iframe_pipeline_id(&self) -> Option<PipelineId>;
|
||||
|
||||
fn get_span(&self) -> Option<u32>;
|
||||
fn get_colspan(&self) -> Option<u32>;
|
||||
fn get_rowspan(&self) -> Option<u32>;
|
||||
|
||||
fn pseudo_element_chain(&self) -> PseudoElementChain;
|
||||
|
||||
fn with_pseudo(&self, pseudo_element_type: PseudoElement) -> Option<Self> {
|
||||
self.as_element()
|
||||
.and_then(|element| element.with_pseudo(pseudo_element_type))
|
||||
.as_ref()
|
||||
.map(ThreadSafeLayoutElement::as_node)
|
||||
}
|
||||
|
||||
fn with_pseudo_element_chain(&self, pseudo_element_chain: PseudoElementChain) -> Self;
|
||||
|
||||
/// Set whether or not this node has an active pseudo-element style with a `content`
|
||||
/// attribute that uses `attr`.
|
||||
///
|
||||
/// # Safety
|
||||
///
|
||||
/// This function accesses and modifies the underlying DOM object and should
|
||||
/// not be used by more than a single thread at once.
|
||||
fn set_uses_content_attribute_with_attr(&self, _uses_content_attribute_with_attr: bool);
|
||||
}
|
||||
|
||||
pub trait ThreadSafeLayoutElement<'dom>:
|
||||
Clone + Copy + Sized + Debug + ::selectors::Element<Impl = SelectorImpl>
|
||||
{
|
||||
type ConcreteThreadSafeLayoutNode: ThreadSafeLayoutNode<'dom, ConcreteThreadSafeLayoutElement = Self>;
|
||||
|
||||
/// This type alias is just a work-around to avoid writing
|
||||
///
|
||||
/// <Self::ConcreteThreadSafeLayoutNode as ThreadSafeLayoutNode>::ConcreteElement
|
||||
///
|
||||
type ConcreteElement: TElement;
|
||||
|
||||
fn as_node(&self) -> Self::ConcreteThreadSafeLayoutNode;
|
||||
|
||||
/// Creates a new `ThreadSafeLayoutElement` for the same `LayoutElement`
|
||||
/// with a different pseudo-element type.
|
||||
///
|
||||
/// Returns `None` if this pseudo doesn't apply to the given element for one of
|
||||
/// the following reasons:
|
||||
///
|
||||
/// 1. `pseudo` is eager and is not defined in the stylesheet. In this case, there
|
||||
/// is not reason to process the pseudo element at all.
|
||||
/// 2. `pseudo` is for `::servo-details-content` and
|
||||
/// it doesn't apply to this element, either because it isn't a details or is
|
||||
/// in the wrong state.
|
||||
fn with_pseudo(&self, pseudo: PseudoElement) -> Option<Self>;
|
||||
|
||||
/// Returns the type ID of this node.
|
||||
/// Returns `None` if this is a pseudo-element; otherwise, returns `Some`.
|
||||
fn type_id(&self) -> Option<LayoutNodeType>;
|
||||
|
||||
/// Returns access to the underlying TElement. This is breaks the abstraction
|
||||
/// barrier of ThreadSafeLayout wrapper layer, and can lead to races if not used
|
||||
/// carefully.
|
||||
///
|
||||
/// We need this so that the functions defined on this trait can call
|
||||
/// lazily_compute_pseudo_element_style, which operates on TElement.
|
||||
fn unsafe_get(self) -> Self::ConcreteElement;
|
||||
|
||||
/// Get the local name of this element. See
|
||||
/// <https://dom.spec.whatwg.org/#concept-element-local-name>.
|
||||
fn get_local_name(&self) -> &LocalName;
|
||||
|
||||
fn get_attr(&self, namespace: &Namespace, name: &LocalName) -> Option<&str>;
|
||||
|
||||
fn get_attr_enum(&self, namespace: &Namespace, name: &LocalName) -> Option<&AttrValue>;
|
||||
|
||||
/// Get the [`StyleData`] for this node. Returns None if the node is unstyled.
|
||||
fn style_data(&self) -> Option<&'dom StyleData>;
|
||||
|
||||
/// Get a reference to the inner [`ElementDataRef`] for this element's [`StyleData`]. This will
|
||||
/// panic if the element is unstyled.
|
||||
fn element_data(&self) -> ElementDataRef<'dom> {
|
||||
self.style_data()
|
||||
.expect("Unstyled layout node?")
|
||||
.element_data
|
||||
.borrow()
|
||||
}
|
||||
|
||||
/// Get a mutable reference to the inner [`ElementDataRef`] for this element's [`StyleData`].
|
||||
/// This will panic if the element is unstyled.
|
||||
fn element_data_mut(&self) -> ElementDataMut<'dom> {
|
||||
self.style_data()
|
||||
.expect("Unstyled layout node?")
|
||||
.element_data
|
||||
.borrow_mut()
|
||||
}
|
||||
|
||||
fn pseudo_element_chain(&self) -> PseudoElementChain;
|
||||
|
||||
/// Returns the style results for the given node. If CSS selector matching
|
||||
/// has not yet been performed, fails.
|
||||
///
|
||||
/// Unlike the version on TNode, this handles pseudo-elements.
|
||||
#[inline]
|
||||
fn style(&self, context: &SharedStyleContext) -> Arc<ComputedValues> {
|
||||
let get_style_for_pseudo_element =
|
||||
|data: &ElementDataRef<'_>,
|
||||
base_style: &Arc<ComputedValues>,
|
||||
pseudo_element: PseudoElement| {
|
||||
// Precompute non-eagerly-cascaded pseudo-element styles if not
|
||||
// cached before.
|
||||
match pseudo_element.cascade_type() {
|
||||
// Already computed during the cascade.
|
||||
PseudoElementCascadeType::Eager => {
|
||||
data.styles.pseudos.get(&pseudo_element).unwrap().clone()
|
||||
},
|
||||
PseudoElementCascadeType::Precomputed => context
|
||||
.stylist
|
||||
.precomputed_values_for_pseudo::<Self::ConcreteElement>(
|
||||
&context.guards,
|
||||
&pseudo_element,
|
||||
Some(base_style),
|
||||
),
|
||||
PseudoElementCascadeType::Lazy => {
|
||||
context
|
||||
.stylist
|
||||
.lazily_compute_pseudo_element_style(
|
||||
&context.guards,
|
||||
self.unsafe_get(),
|
||||
&pseudo_element,
|
||||
RuleInclusion::All,
|
||||
base_style,
|
||||
/* is_probe = */ false,
|
||||
/* matching_func = */ None,
|
||||
)
|
||||
.unwrap()
|
||||
},
|
||||
}
|
||||
};
|
||||
|
||||
let data = self.element_data();
|
||||
let element_style = data.styles.primary();
|
||||
let pseudo_element_chain = self.pseudo_element_chain();
|
||||
|
||||
let primary_pseudo_style = match pseudo_element_chain.primary {
|
||||
Some(pseudo_element) => {
|
||||
get_style_for_pseudo_element(&data, element_style, pseudo_element)
|
||||
},
|
||||
None => return element_style.clone(),
|
||||
};
|
||||
match pseudo_element_chain.secondary {
|
||||
Some(pseudo_element) => {
|
||||
get_style_for_pseudo_element(&data, &primary_pseudo_style, pseudo_element)
|
||||
},
|
||||
None => primary_pseudo_style,
|
||||
}
|
||||
}
|
||||
|
||||
fn is_shadow_host(&self) -> bool;
|
||||
|
||||
/// Returns whether this node is a body element of an html element root
|
||||
/// in an HTML element document.
|
||||
///
|
||||
/// Note that this does require accessing the parent, which this interface
|
||||
/// technically forbids. But accessing the parent is only unsafe insofar as
|
||||
/// it can be used to reach siblings and cousins. A simple immutable borrow
|
||||
/// of the parent data is fine, since the bottom-up traversal will not process
|
||||
/// the parent until all the children have been processed.
|
||||
fn is_body_element_of_html_element_root(&self) -> bool;
|
||||
|
||||
/// Returns whether this node is the root element in an HTML document element.
|
||||
///
|
||||
/// Note that, like `Self::is_body_element_of_html_element_root`, this accesses the parent.
|
||||
/// As in that case, since this is an immutable borrow, we do not violate thread safety.
|
||||
fn is_root(&self) -> bool;
|
||||
}
|
||||
|
||||
/// A chain of pseudo-elements up to two levels deep. This is used to represent cases
|
||||
/// where a pseudo-element has its own child pseudo element (for instance
|
||||
/// `.div::after::marker`). If both [`Self::primary`] and [`Self::secondary`] are `None`,
|
||||
/// then this chain represents the element itself. Not all combinations of pseudo-elements
|
||||
/// are possible and we may not be able to calculate a style for all
|
||||
/// [`PseudoElementChain`]s.
|
||||
#[derive(Clone, Copy, Debug, Default, Eq, Hash, MallocSizeOf, PartialEq)]
|
||||
pub struct PseudoElementChain {
|
||||
pub primary: Option<PseudoElement>,
|
||||
pub secondary: Option<PseudoElement>,
|
||||
}
|
||||
|
||||
impl PseudoElementChain {
|
||||
pub fn unnested(pseudo_element: PseudoElement) -> Self {
|
||||
Self {
|
||||
primary: Some(pseudo_element),
|
||||
secondary: None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn innermost(&self) -> Option<PseudoElement> {
|
||||
self.secondary.or(self.primary)
|
||||
}
|
||||
|
||||
/// Return a possibly nested [`PseudoElementChain`]. Currently only `::before` and
|
||||
/// `::after` only support nesting. If the primary [`PseudoElement`] on the chain is
|
||||
/// not `::before` or `::after` a single element chain is returned for the given
|
||||
/// [`PseudoElement`].
|
||||
pub fn with_pseudo(&self, pseudo_element: PseudoElement) -> Self {
|
||||
match self.primary {
|
||||
Some(primary) if primary.is_before_or_after() => Self {
|
||||
primary: self.primary,
|
||||
secondary: Some(pseudo_element),
|
||||
},
|
||||
_ => {
|
||||
assert!(self.secondary.is_none());
|
||||
Self::unnested(pseudo_element)
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
pub fn without_innermost(&self) -> Option<Self> {
|
||||
let primary = self.primary?;
|
||||
Some(
|
||||
self.secondary
|
||||
.map_or_else(Self::default, |_| Self::unnested(primary)),
|
||||
)
|
||||
}
|
||||
|
||||
pub fn is_empty(&self) -> bool {
|
||||
self.primary.is_none()
|
||||
}
|
||||
}
|
||||
|
||||
/// A selection shared between script and layout. This selection is managed by the DOM
|
||||
/// node that maintains it, and can be modified from script. Once modified, layout is
|
||||
/// expected to reflect the new selection visual on the next display list update.
|
||||
#[derive(Clone, Debug, Default, MallocSizeOf, PartialEq)]
|
||||
pub struct ScriptSelection {
|
||||
/// The range of this selection in the DOM node that manages it.
|
||||
pub range: TextByteRange,
|
||||
/// The character range of this selection in the DOM node that manages it.
|
||||
pub character_range: Range<usize>,
|
||||
/// Whether or not this selection is enabled. Selections may be disabled
|
||||
/// when their node loses focus.
|
||||
pub enabled: bool,
|
||||
}
|
||||
|
||||
pub type SharedSelection = std::sync::Arc<AtomicRefCell<ScriptSelection>>;
|
||||
Reference in New Issue
Block a user