aboutsummaryrefslogtreecommitdiffstats
path: root/components/layout_2020/fragment_tree/box_fragment.rs
diff options
context:
space:
mode:
Diffstat (limited to 'components/layout_2020/fragment_tree/box_fragment.rs')
-rw-r--r--components/layout_2020/fragment_tree/box_fragment.rs280
1 files changed, 280 insertions, 0 deletions
diff --git a/components/layout_2020/fragment_tree/box_fragment.rs b/components/layout_2020/fragment_tree/box_fragment.rs
new file mode 100644
index 00000000000..6f697ef23fd
--- /dev/null
+++ b/components/layout_2020/fragment_tree/box_fragment.rs
@@ -0,0 +1,280 @@
+/* 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 super::{BaseFragment, BaseFragmentInfo, CollapsedBlockMargins, Fragment};
+use crate::cell::ArcRefCell;
+use crate::geom::flow_relative::{Rect, Sides};
+use crate::geom::{PhysicalPoint, PhysicalRect, PhysicalSides, PhysicalSize};
+use gfx_traits::print_tree::PrintTree;
+use servo_arc::Arc as ServoArc;
+use style::computed_values::overflow_x::T as ComputedOverflow;
+use style::computed_values::position::T as ComputedPosition;
+use style::properties::ComputedValues;
+use style::values::computed::{CSSPixelLength, Length, LengthPercentage, LengthPercentageOrAuto};
+use style::Zero;
+
+#[derive(Serialize)]
+pub(crate) struct BoxFragment {
+ pub base: BaseFragment,
+
+ #[serde(skip_serializing)]
+ pub style: ServoArc<ComputedValues>,
+ pub children: Vec<ArcRefCell<Fragment>>,
+
+ /// From the containing block’s start corner…?
+ /// This might be broken when the containing block is in a different writing mode:
+ /// https://drafts.csswg.org/css-writing-modes/#orthogonal-flows
+ pub content_rect: Rect<Length>,
+
+ pub padding: Sides<Length>,
+ pub border: Sides<Length>,
+ pub margin: Sides<Length>,
+
+ pub clearance: Length,
+
+ pub block_margins_collapsed_with_children: CollapsedBlockMargins,
+
+ /// The scrollable overflow of this box fragment.
+ pub scrollable_overflow_from_children: PhysicalRect<Length>,
+
+ /// Whether or not this box was overconstrained in the given dimension.
+ overconstrained: PhysicalSize<bool>,
+}
+
+impl BoxFragment {
+ pub fn new(
+ base_fragment_info: BaseFragmentInfo,
+ style: ServoArc<ComputedValues>,
+ children: Vec<Fragment>,
+ content_rect: Rect<Length>,
+ padding: Sides<Length>,
+ border: Sides<Length>,
+ margin: Sides<Length>,
+ clearance: Length,
+ block_margins_collapsed_with_children: CollapsedBlockMargins,
+ ) -> BoxFragment {
+ let position = style.get_box().position;
+ let insets = style.get_position();
+ let width_overconstrained = position == ComputedPosition::Relative &&
+ !insets.left.is_auto() &&
+ !insets.right.is_auto();
+ let height_overconstrained = position == ComputedPosition::Relative &&
+ !insets.left.is_auto() &&
+ !insets.bottom.is_auto();
+
+ Self::new_with_overconstrained(
+ base_fragment_info,
+ style,
+ children,
+ content_rect,
+ padding,
+ border,
+ margin,
+ clearance,
+ block_margins_collapsed_with_children,
+ PhysicalSize::new(width_overconstrained, height_overconstrained),
+ )
+ }
+
+ pub fn new_with_overconstrained(
+ base_fragment_info: BaseFragmentInfo,
+ style: ServoArc<ComputedValues>,
+ children: Vec<Fragment>,
+ content_rect: Rect<Length>,
+ padding: Sides<Length>,
+ border: Sides<Length>,
+ margin: Sides<Length>,
+ clearance: Length,
+ block_margins_collapsed_with_children: CollapsedBlockMargins,
+ overconstrained: PhysicalSize<bool>,
+ ) -> BoxFragment {
+ // FIXME(mrobinson, bug 25564): We should be using the containing block
+ // here to properly convert scrollable overflow to physical geometry.
+ let containing_block = PhysicalRect::zero();
+ let scrollable_overflow_from_children =
+ children.iter().fold(PhysicalRect::zero(), |acc, child| {
+ acc.union(&child.scrollable_overflow(&containing_block))
+ });
+
+ BoxFragment {
+ base: base_fragment_info.into(),
+ style,
+ children: children
+ .into_iter()
+ .map(|fragment| ArcRefCell::new(fragment))
+ .collect(),
+ content_rect,
+ padding,
+ border,
+ margin,
+ clearance,
+ block_margins_collapsed_with_children,
+ scrollable_overflow_from_children,
+ overconstrained,
+ }
+ }
+
+ pub fn scrollable_overflow(
+ &self,
+ containing_block: &PhysicalRect<Length>,
+ ) -> PhysicalRect<Length> {
+ let physical_padding_rect = self
+ .padding_rect()
+ .to_physical(self.style.writing_mode, containing_block);
+
+ let content_origin = self
+ .content_rect
+ .start_corner
+ .to_physical(self.style.writing_mode);
+ physical_padding_rect.union(
+ &self
+ .scrollable_overflow_from_children
+ .translate(content_origin.to_vector()),
+ )
+ }
+
+ pub fn padding_rect(&self) -> Rect<Length> {
+ self.content_rect.inflate(&self.padding)
+ }
+
+ pub fn border_rect(&self) -> Rect<Length> {
+ self.padding_rect().inflate(&self.border)
+ }
+
+ pub fn print(&self, tree: &mut PrintTree) {
+ tree.new_level(format!(
+ "Box\
+ \nbase={:?}\
+ \ncontent={:?}\
+ \npadding rect={:?}\
+ \nborder rect={:?}\
+ \nclearance={:?}\
+ \nscrollable_overflow={:?}\
+ \noverflow={:?} / {:?}",
+ self.base,
+ self.content_rect,
+ self.padding_rect(),
+ self.border_rect(),
+ self.clearance,
+ self.scrollable_overflow(&PhysicalRect::zero()),
+ self.style.get_box().overflow_x,
+ self.style.get_box().overflow_y,
+ ));
+
+ for child in &self.children {
+ child.borrow().print(tree);
+ }
+ tree.end_level();
+ }
+
+ pub fn scrollable_overflow_for_parent(
+ &self,
+ containing_block: &PhysicalRect<Length>,
+ ) -> PhysicalRect<Length> {
+ let mut overflow = self
+ .border_rect()
+ .to_physical(self.style.writing_mode, containing_block);
+
+ if self.style.get_box().overflow_y != ComputedOverflow::Visible &&
+ self.style.get_box().overflow_x != ComputedOverflow::Visible
+ {
+ return overflow;
+ }
+
+ // https://www.w3.org/TR/css-overflow-3/#scrollable
+ // Only include the scrollable overflow of a child box if it has overflow: visible.
+ let scrollable_overflow = self.scrollable_overflow(&containing_block);
+ let bottom_right = PhysicalPoint::new(
+ overflow.max_x().max(scrollable_overflow.max_x()),
+ overflow.max_y().max(scrollable_overflow.max_y()),
+ );
+
+ if self.style.get_box().overflow_y == ComputedOverflow::Visible {
+ overflow.origin.y = overflow.origin.y.min(scrollable_overflow.origin.y);
+ overflow.size.height = bottom_right.y - overflow.origin.y;
+ }
+
+ if self.style.get_box().overflow_x == ComputedOverflow::Visible {
+ overflow.origin.x = overflow.origin.x.min(scrollable_overflow.origin.x);
+ overflow.size.width = bottom_right.x - overflow.origin.x;
+ }
+
+ overflow
+ }
+
+ pub(crate) fn calculate_resolved_insets_if_positioned(
+ &self,
+ containing_block: &PhysicalRect<CSSPixelLength>,
+ ) -> PhysicalSides<CSSPixelLength> {
+ let position = self.style.get_box().position;
+ debug_assert_ne!(
+ position,
+ ComputedPosition::Static,
+ "Should not call this method on statically positioned box."
+ );
+
+ let (cb_width, cb_height) = (containing_block.width(), containing_block.height());
+ let content_rect = self
+ .content_rect
+ .to_physical(self.style.writing_mode, &containing_block);
+
+ // "A resolved value special case property like top defined in another
+ // specification If the property applies to a positioned element and the
+ // resolved value of the display property is not none or contents, and
+ // the property is not over-constrained, then the resolved value is the
+ // used value. Otherwise the resolved value is the computed value."
+ // https://drafts.csswg.org/cssom/#resolved-values
+ let insets = self.style.get_position();
+ if position == ComputedPosition::Relative {
+ let get_resolved_axis =
+ |start: &LengthPercentageOrAuto,
+ end: &LengthPercentageOrAuto,
+ container_length: CSSPixelLength| {
+ let start = start.map(|v| v.percentage_relative_to(container_length));
+ let end = end.map(|v| v.percentage_relative_to(container_length));
+ match (start.non_auto(), end.non_auto()) {
+ (None, None) => (Length::zero(), Length::zero()),
+ (None, Some(end)) => (-end, end),
+ (Some(start), None) => (start, -start),
+ // This is the overconstrained case, for which the resolved insets will
+ // simply be the computed insets.
+ (Some(start), Some(end)) => (start, end),
+ }
+ };
+ let (left, right) = get_resolved_axis(&insets.left, &insets.right, cb_width);
+ let (top, bottom) = get_resolved_axis(&insets.top, &insets.bottom, cb_height);
+ return PhysicalSides::new(top, right, bottom, left);
+ }
+
+ debug_assert!(
+ position == ComputedPosition::Fixed || position == ComputedPosition::Absolute,
+ "Got unknown position."
+ );
+
+ let resolve = |value: &LengthPercentageOrAuto, container_length| {
+ value
+ .auto_is(LengthPercentage::zero)
+ .percentage_relative_to(container_length)
+ };
+
+ let (top, bottom) = if self.overconstrained.height {
+ (
+ resolve(&insets.top, cb_height),
+ resolve(&insets.bottom, cb_height),
+ )
+ } else {
+ (content_rect.origin.y, cb_height - content_rect.max_y())
+ };
+ let (left, right) = if self.overconstrained.width {
+ (
+ resolve(&insets.left, cb_width),
+ resolve(&insets.right, cb_width),
+ )
+ } else {
+ (content_rect.origin.x, cb_width - content_rect.max_x())
+ };
+
+ PhysicalSides::new(top, right, bottom, left)
+ }
+}