aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--Cargo.lock14
-rw-r--r--components/layout_2020/Cargo.toml4
-rw-r--r--components/layout_2020/flow/float.rs473
-rw-r--r--components/layout_2020/flow/mod.rs4
-rw-r--r--components/layout_2020/geom.rs22
-rw-r--r--components/layout_2020/lib.rs6
-rw-r--r--components/layout_2020/tests/floats.rs823
-rw-r--r--python/servo/testing_commands.py13
-rw-r--r--servo-tidy.toml1
-rw-r--r--tests/unit/style/parsing/mod.rs14
10 files changed, 1354 insertions, 20 deletions
diff --git a/Cargo.lock b/Cargo.lock
index d096614da3c..546e53460b7 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -2912,12 +2912,14 @@ dependencies = [
"gfx_traits",
"html5ever",
"ipc-channel",
+ "lazy_static",
"libc",
"log",
"mitochondria",
"msg",
"net_traits",
"parking_lot 0.10.2",
+ "quickcheck",
"range",
"rayon",
"rayon_croissant",
@@ -4366,6 +4368,18 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "eda5fe9b71976e62bc81b781206aaa076401769b2143379d3eb2118388babac4"
[[package]]
+name = "quickcheck"
+version = "0.9.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a44883e74aa97ad63db83c4bf8ca490f02b2fc02f92575e720c8551e843c945f"
+dependencies = [
+ "env_logger",
+ "log",
+ "rand",
+ "rand_core",
+]
+
+[[package]]
name = "quote"
version = "0.6.13"
source = "registry+https://github.com/rust-lang/crates.io-index"
diff --git a/components/layout_2020/Cargo.toml b/components/layout_2020/Cargo.toml
index 886549971d6..6ca3d709627 100644
--- a/components/layout_2020/Cargo.toml
+++ b/components/layout_2020/Cargo.toml
@@ -44,3 +44,7 @@ style = { path = "../style", features = ["servo", "servo-layout-2020"] }
style_traits = { path = "../style_traits" }
unicode-script = { version = "0.3", features = ["harfbuzz"] }
webrender_api = { git = "https://github.com/servo/webrender" }
+
+[dev-dependencies]
+lazy_static = "1"
+quickcheck = "0.9"
diff --git a/components/layout_2020/flow/float.rs b/components/layout_2020/flow/float.rs
index 22b0b1bb66e..5cb349310e3 100644
--- a/components/layout_2020/flow/float.rs
+++ b/components/layout_2020/flow/float.rs
@@ -2,29 +2,494 @@
* 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/. */
+//! Float layout.
+//!
+//! See CSS 2.1 § 9.5.1: https://www.w3.org/TR/CSS2/visuren.html#float-position
+
use crate::context::LayoutContext;
use crate::dom_traversal::{Contents, NodeAndStyleInfo, NodeExt};
use crate::formatting_contexts::IndependentFormattingContext;
+use crate::geom::flow_relative::{Rect, Vec2};
use crate::style_ext::DisplayInside;
+use euclid::num::Zero;
+use servo_arc::Arc;
+use std::f32;
+use std::ops::Range;
+use style::values::computed::Length;
use style::values::specified::text::TextDecorationLine;
+/// A floating box.
#[derive(Debug, Serialize)]
pub(crate) struct FloatBox {
+ /// The formatting context that makes up the content of this box.
pub contents: IndependentFormattingContext,
}
/// Data kept during layout about the floats in a given block formatting context.
-pub(crate) struct FloatContext {
- // TODO
+///
+/// This is a persistent data structure. Each float has its own private copy of the float context,
+/// although such copies may share portions of the `bands` tree.
+#[derive(Clone, Debug)]
+pub struct FloatContext {
+ /// A persistent AA tree of float bands.
+ ///
+ /// This tree is immutable; modification operations return the new tree, which may share nodes
+ /// with previous versions of the tree.
+ pub bands: FloatBandTree,
+ /// The logical width of this context. No floats may extend outside the width of this context
+ /// unless they are as far (logically) left or right as possible.
+ pub inline_size: Length,
+ /// The current (logically) vertical position. No new floats may be placed (logically) above
+ /// this line.
+ pub ceiling: Length,
}
impl FloatContext {
- pub fn new() -> Self {
- FloatContext {}
+ /// Returns a new float context representing a containing block with the given content
+ /// inline-size.
+ pub fn new(inline_size: Length) -> Self {
+ let mut bands = FloatBandTree::new();
+ bands = bands.insert(FloatBand {
+ top: Length::zero(),
+ left: None,
+ right: None,
+ });
+ bands = bands.insert(FloatBand {
+ top: Length::new(f32::INFINITY),
+ left: None,
+ right: None,
+ });
+ FloatContext {
+ bands,
+ inline_size,
+ ceiling: Length::zero(),
+ }
+ }
+
+ /// Returns the current ceiling value. No new floats may be placed (logically) above this line.
+ pub fn ceiling(&self) -> Length {
+ self.ceiling
+ }
+
+ /// (Logically) lowers the ceiling to at least `new_ceiling` units.
+ ///
+ /// If the ceiling is already logically lower (i.e. larger) than this, does nothing.
+ pub fn lower_ceiling(&mut self, new_ceiling: Length) {
+ self.ceiling = self.ceiling.max(new_ceiling);
+ }
+
+ /// Returns the highest block position that is both logically below the current ceiling and
+ /// clear of floats on the given side or sides.
+ pub fn clearance(&self, side: ClearSide) -> Length {
+ let mut band = self.bands.find(self.ceiling).unwrap();
+ while !band.is_clear(side) {
+ let next_band = self.bands.find_next(band.top).unwrap();
+ if next_band.top.px().is_infinite() {
+ break;
+ }
+ band = next_band;
+ }
+ band.top.max(self.ceiling)
+ }
+
+ /// Places a new float and adds it to the list. Returns the start corner of its margin box.
+ pub fn add_float(&mut self, new_float: FloatInfo) -> Vec2<Length> {
+ // Find the first band this float fits in.
+ let mut first_band = self.bands.find(self.ceiling).unwrap();
+ while !first_band.float_fits(&new_float, self.inline_size) {
+ let next_band = self.bands.find_next(first_band.top).unwrap();
+ if next_band.top.px().is_infinite() {
+ break;
+ }
+ first_band = next_band;
+ }
+
+ // The float fits perfectly here. Place it.
+ let (new_float_origin, new_float_extent);
+ match new_float.side {
+ FloatSide::Left => {
+ new_float_origin = Vec2 {
+ inline: first_band.left.unwrap_or(Length::zero()),
+ block: first_band.top.max(self.ceiling),
+ };
+ new_float_extent = new_float_origin.inline + new_float.size.inline;
+ },
+ FloatSide::Right => {
+ new_float_origin = Vec2 {
+ inline: first_band.right.unwrap_or(self.inline_size) - new_float.size.inline,
+ block: first_band.top.max(self.ceiling),
+ };
+ new_float_extent = new_float_origin.inline;
+ },
+ };
+ let new_float_rect = Rect {
+ start_corner: new_float_origin,
+ size: new_float.size,
+ };
+
+ // Split the first band if necessary.
+ first_band.top = new_float_rect.start_corner.block;
+ self.bands = self.bands.insert(first_band);
+
+ // Split the last band if necessary.
+ let mut last_band = self
+ .bands
+ .find(new_float_rect.max_block_position())
+ .unwrap();
+ last_band.top = new_float_rect.max_block_position();
+ self.bands = self.bands.insert(last_band);
+
+ // Update all bands that contain this float to reflect the new available size.
+ let block_range = new_float_rect.start_corner.block..new_float_rect.max_block_position();
+ self.bands = self
+ .bands
+ .set_range(&block_range, new_float.side, new_float_extent);
+
+ // CSS 2.1 § 9.5.1 rule 6: The outer top of a floating box may not be higher than the outer
+ // top of any block or floated box generated by an element earlier in the source document.
+ self.ceiling = self.ceiling.max(new_float_rect.start_corner.block);
+ new_float_rect.start_corner
+ }
+}
+
+/// Information needed to place a float.
+#[derive(Clone, Debug)]
+pub struct FloatInfo {
+ /// The *margin* box size of the float.
+ pub size: Vec2<Length>,
+ /// Whether the float is left or right.
+ pub side: FloatSide,
+ /// Which side or sides to clear floats on.
+ pub clear: ClearSide,
+}
+
+/// Whether the float is left or right.
+///
+/// See CSS 2.1 § 9.5.1: https://www.w3.org/TR/CSS2/visuren.html#float-position
+#[derive(Clone, Copy, Debug, PartialEq)]
+pub enum FloatSide {
+ Left,
+ Right,
+}
+
+/// Which side or sides to clear floats on.
+///
+/// See CSS 2.1 § 9.5.2: https://www.w3.org/TR/CSS2/visuren.html#flow-control
+#[derive(Clone, Copy, Debug, PartialEq)]
+pub enum ClearSide {
+ None = 0,
+ Left = 1,
+ Right = 2,
+ Both = 3,
+}
+
+/// Internal data structure that describes a nonoverlapping vertical region in which floats may be
+/// placed. Floats must go between "left edge + `left`" and "right edge - `right`".
+#[derive(Clone, Copy, Debug, PartialEq)]
+pub struct FloatBand {
+ /// The logical vertical position of the top of this band.
+ pub top: Length,
+ /// The distance from the left edge to the first legal (logically) horizontal position where
+ /// floats may be placed. If `None`, there are no floats to the left; distinguishing between
+ /// the cases of "a zero-width float is present" and "no floats at all are present" is
+ /// necessary to, for example, clear past zero-width floats.
+ pub left: Option<Length>,
+ /// The distance from the right edge to the first legal (logically) horizontal position where
+ /// floats may be placed. If `None`, there are no floats to the right; distinguishing between
+ /// the cases of "a zero-width float is present" and "no floats at all are present" is
+ /// necessary to, for example, clear past zero-width floats.
+ pub right: Option<Length>,
+}
+
+impl FloatBand {
+ fn is_clear(&self, side: ClearSide) -> bool {
+ match (side, self.left, self.right) {
+ (ClearSide::Left, Some(_), _) |
+ (ClearSide::Right, _, Some(_)) |
+ (ClearSide::Both, Some(_), _) |
+ (ClearSide::Both, _, Some(_)) => false,
+ (ClearSide::None, _, _) |
+ (ClearSide::Left, None, _) |
+ (ClearSide::Right, _, None) |
+ (ClearSide::Both, None, None) => true,
+ }
+ }
+
+ fn float_fits(&self, new_float: &FloatInfo, container_inline_size: Length) -> bool {
+ let available_space =
+ self.right.unwrap_or(container_inline_size) - self.left.unwrap_or(Length::zero());
+ self.is_clear(new_float.clear) &&
+ (new_float.size.inline <= available_space ||
+ (self.left.is_none() && self.right.is_none()))
+ }
+}
+
+// Float band storage
+
+/// A persistent AA tree for float band storage.
+///
+/// Bands here are nonoverlapping, and there is guaranteed to be a band at block-position 0 and
+/// another band at block-position infinity.
+///
+/// AA trees were chosen for simplicity.
+///
+/// See: https://en.wikipedia.org/wiki/AA_tree
+/// https://arxiv.org/pdf/1412.4882.pdf
+#[derive(Clone, Debug)]
+pub struct FloatBandTree {
+ pub root: FloatBandLink,
+}
+
+/// A single edge (or lack thereof) in the float band tree.
+#[derive(Clone, Debug)]
+pub struct FloatBandLink(pub Option<Arc<FloatBandNode>>);
+
+/// A single node in the float band tree.
+#[derive(Clone, Debug)]
+pub struct FloatBandNode {
+ /// The actual band.
+ pub band: FloatBand,
+ /// The left child.
+ pub left: FloatBandLink,
+ /// The right child.
+ pub right: FloatBandLink,
+ /// The level, which increases as you go up the tree.
+ ///
+ /// This value is needed for tree balancing.
+ pub level: i32,
+}
+
+impl FloatBandTree {
+ /// Creates a new float band tree.
+ pub fn new() -> FloatBandTree {
+ FloatBandTree {
+ root: FloatBandLink(None),
+ }
+ }
+
+ /// Returns the first band whose top is less than or equal to the given `block_position`.
+ pub fn find(&self, block_position: Length) -> Option<FloatBand> {
+ self.root.find(block_position)
+ }
+
+ /// Returns the first band whose top is strictly greater than to the given `block_position`.
+ pub fn find_next(&self, block_position: Length) -> Option<FloatBand> {
+ self.root.find_next(block_position)
+ }
+
+ /// Sets the side values of all bands within the given half-open range to be at least
+ /// `new_value`.
+ #[must_use]
+ pub fn set_range(
+ &self,
+ range: &Range<Length>,
+ side: FloatSide,
+ new_value: Length,
+ ) -> FloatBandTree {
+ FloatBandTree {
+ root: FloatBandLink(
+ self.root
+ .0
+ .as_ref()
+ .map(|root| root.set_range(range, side, new_value)),
+ ),
+ }
+ }
+
+ /// Inserts a new band into the tree. If the band has the same level as a pre-existing one,
+ /// replaces the existing band with the new one.
+ #[must_use]
+ pub fn insert(&self, band: FloatBand) -> FloatBandTree {
+ FloatBandTree {
+ root: self.root.insert(band),
+ }
+ }
+}
+
+impl FloatBandNode {
+ fn new(band: FloatBand) -> FloatBandNode {
+ FloatBandNode {
+ band,
+ left: FloatBandLink(None),
+ right: FloatBandLink(None),
+ level: 1,
+ }
+ }
+
+ /// Sets the side values of all bands within the given half-open range to be at least
+ /// `new_value`.
+ fn set_range(
+ &self,
+ range: &Range<Length>,
+ side: FloatSide,
+ new_value: Length,
+ ) -> Arc<FloatBandNode> {
+ let mut new_band = self.band.clone();
+ if self.band.top >= range.start && self.band.top < range.end {
+ match side {
+ FloatSide::Left => match new_band.left {
+ None => new_band.left = Some(new_value),
+ Some(ref mut old_value) => *old_value = old_value.max(new_value),
+ },
+ FloatSide::Right => match new_band.right {
+ None => new_band.right = Some(new_value),
+ Some(ref mut old_value) => *old_value = old_value.min(new_value),
+ },
+ }
+ }
+
+ let new_left = match self.left.0 {
+ None => FloatBandLink(None),
+ Some(ref old_left) if range.start < new_band.top => {
+ FloatBandLink(Some(old_left.set_range(range, side, new_value)))
+ },
+ Some(ref old_left) => FloatBandLink(Some((*old_left).clone())),
+ };
+
+ let new_right = match self.right.0 {
+ None => FloatBandLink(None),
+ Some(ref old_right) if range.end > new_band.top => {
+ FloatBandLink(Some(old_right.set_range(range, side, new_value)))
+ },
+ Some(ref old_right) => FloatBandLink(Some((*old_right).clone())),
+ };
+
+ Arc::new(FloatBandNode {
+ band: new_band,
+ left: new_left,
+ right: new_right,
+ level: self.level,
+ })
+ }
+}
+
+impl FloatBandLink {
+ /// Returns the first band whose top is less than or equal to the given `block_position`.
+ fn find(&self, block_position: Length) -> Option<FloatBand> {
+ let this = match self.0 {
+ None => return None,
+ Some(ref node) => node,
+ };
+
+ if block_position < this.band.top {
+ return this.left.find(block_position);
+ }
+
+ // It's somewhere in this subtree, but we aren't sure whether it's here or in the right
+ // subtree.
+ if let Some(band) = this.right.find(block_position) {
+ return Some(band);
+ }
+
+ Some(this.band.clone())
+ }
+
+ /// Returns the first band whose top is strictly greater than the given `block_position`.
+ fn find_next(&self, block_position: Length) -> Option<FloatBand> {
+ let this = match self.0 {
+ None => return None,
+ Some(ref node) => node,
+ };
+
+ if block_position >= this.band.top {
+ return this.right.find_next(block_position);
+ }
+
+ // It's somewhere in this subtree, but we aren't sure whether it's here or in the left
+ // subtree.
+ if let Some(band) = this.left.find_next(block_position) {
+ return Some(band);
+ }
+
+ Some(this.band.clone())
+ }
+
+ // Inserts a new band into the tree. If the band has the same level as a pre-existing one,
+ // replaces the existing band with the new one.
+ fn insert(&self, band: FloatBand) -> FloatBandLink {
+ let mut this = match self.0 {
+ None => return FloatBandLink(Some(Arc::new(FloatBandNode::new(band)))),
+ Some(ref this) => (**this).clone(),
+ };
+
+ if band.top < this.band.top {
+ this.left = this.left.insert(band);
+ return FloatBandLink(Some(Arc::new(this))).skew().split();
+ }
+ if band.top > this.band.top {
+ this.right = this.right.insert(band);
+ return FloatBandLink(Some(Arc::new(this))).skew().split();
+ }
+
+ this.band = band;
+ FloatBandLink(Some(Arc::new(this)))
+ }
+
+ // Corrects tree balance:
+ //
+ // T L
+ // / \ / \
+ // L R → A T if level(T) = level(L)
+ // / \ / \
+ // A B B R
+ fn skew(&self) -> FloatBandLink {
+ if let Some(ref this) = self.0 {
+ if let Some(ref left) = this.left.0 {
+ if this.level == left.level {
+ return FloatBandLink(Some(Arc::new(FloatBandNode {
+ level: this.level,
+ left: left.left.clone(),
+ band: left.band.clone(),
+ right: FloatBandLink(Some(Arc::new(FloatBandNode {
+ level: this.level,
+ left: left.right.clone(),
+ band: this.band.clone(),
+ right: this.right.clone(),
+ }))),
+ })));
+ }
+ }
+ }
+
+ (*self).clone()
+ }
+
+ // Corrects tree balance:
+ //
+ // T R
+ // / \ / \
+ // A R → T X if level(T) = level(X)
+ // / \ / \
+ // B X A B
+ fn split(&self) -> FloatBandLink {
+ if let Some(ref this) = self.0 {
+ if let Some(ref right) = this.right.0 {
+ if let Some(ref right_right) = right.right.0 {
+ if this.level == right_right.level {
+ return FloatBandLink(Some(Arc::new(FloatBandNode {
+ level: this.level + 1,
+ left: FloatBandLink(Some(Arc::new(FloatBandNode {
+ level: this.level,
+ left: this.left.clone(),
+ band: this.band.clone(),
+ right: right.left.clone(),
+ }))),
+ band: right.band.clone(),
+ right: right.right.clone(),
+ })));
+ }
+ }
+ }
+ }
+
+ (*self).clone()
}
}
+// Float boxes
+
impl FloatBox {
+ /// Creates a new float box.
pub fn construct<'dom>(
context: &LayoutContext,
info: &NodeAndStyleInfo<impl NodeExt<'dom>>,
diff --git a/components/layout_2020/flow/mod.rs b/components/layout_2020/flow/mod.rs
index 9af17e0a4b1..f042f814dd3 100644
--- a/components/layout_2020/flow/mod.rs
+++ b/components/layout_2020/flow/mod.rs
@@ -30,7 +30,7 @@ use style::values::computed::{Length, LengthOrAuto};
use style::Zero;
mod construct;
-mod float;
+pub mod float;
pub mod inline;
mod root;
@@ -80,7 +80,7 @@ impl BlockFormattingContext {
) -> IndependentLayout {
let mut float_context;
let float_context = if self.contains_floats {
- float_context = FloatContext::new();
+ float_context = FloatContext::new(containing_block.inline_size);
Some(&mut float_context)
} else {
None
diff --git a/components/layout_2020/geom.rs b/components/layout_2020/geom.rs
index ad8166296b2..7d687069d49 100644
--- a/components/layout_2020/geom.rs
+++ b/components/layout_2020/geom.rs
@@ -19,21 +19,21 @@ pub type PhysicalSides<U> = euclid::SideOffsets2D<U, CSSPixel>;
pub type LengthOrAuto = AutoOr<Length>;
pub type LengthPercentageOrAuto<'a> = AutoOr<&'a LengthPercentage>;
-pub(crate) mod flow_relative {
+pub mod flow_relative {
#[derive(Clone, Serialize)]
- pub(crate) struct Vec2<T> {
+ pub struct Vec2<T> {
pub inline: T,
pub block: T,
}
#[derive(Clone, Serialize)]
- pub(crate) struct Rect<T> {
+ pub struct Rect<T> {
pub start_corner: Vec2<T>,
pub size: Vec2<T>,
}
#[derive(Clone, Serialize)]
- pub(crate) struct Sides<T> {
+ pub struct Sides<T> {
pub inline_start: T,
pub inline_end: T,
pub block_start: T,
@@ -325,6 +325,20 @@ where
}
impl<T> flow_relative::Rect<T> {
+ pub fn max_inline_position(&self) -> T
+ where
+ T: Add<Output = T> + Copy,
+ {
+ self.start_corner.inline + self.size.inline
+ }
+
+ pub fn max_block_position(&self) -> T
+ where
+ T: Add<Output = T> + Copy,
+ {
+ self.start_corner.block + self.size.block
+ }
+
pub fn inflate(&self, sides: &flow_relative::Sides<T>) -> Self
where
T: Add<Output = T> + Copy,
diff --git a/components/layout_2020/lib.rs b/components/layout_2020/lib.rs
index 24dc902bdd4..96fcbb448fa 100644
--- a/components/layout_2020/lib.rs
+++ b/components/layout_2020/lib.rs
@@ -16,10 +16,10 @@ pub mod display_list;
mod dom_traversal;
pub mod element_data;
mod flexbox;
-mod flow;
+pub mod flow;
mod formatting_contexts;
mod fragments;
-mod geom;
+pub mod geom;
#[macro_use]
pub mod layout_debug;
mod opaque_node;
@@ -37,7 +37,7 @@ use crate::geom::flow_relative::Vec2;
use style::properties::ComputedValues;
use style::values::computed::{Length, LengthOrAuto};
-struct ContainingBlock<'a> {
+pub struct ContainingBlock<'a> {
inline_size: Length,
block_size: LengthOrAuto,
style: &'a ComputedValues,
diff --git a/components/layout_2020/tests/floats.rs b/components/layout_2020/tests/floats.rs
new file mode 100644
index 00000000000..212ed8026bb
--- /dev/null
+++ b/components/layout_2020/tests/floats.rs
@@ -0,0 +1,823 @@
+/* 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/. */
+
+//! Property-based randomized testing for the core float layout algorithm.
+
+#[macro_use]
+extern crate lazy_static;
+
+use euclid::num::Zero;
+use layout::flow::float::{ClearSide, FloatBand, FloatBandNode, FloatBandTree, FloatContext};
+use layout::flow::float::{FloatInfo, FloatSide};
+use layout::geom::flow_relative::{Rect, Vec2};
+use quickcheck::{Arbitrary, Gen};
+use std::f32;
+use std::ops::Range;
+use std::panic::{self, PanicInfo};
+use std::sync::{Mutex, MutexGuard};
+use std::thread;
+use std::u32;
+use style::values::computed::Length;
+
+lazy_static! {
+ static ref PANIC_HOOK_MUTEX: Mutex<()> = Mutex::new(());
+}
+
+// Suppresses panic messages. Some tests need to fail and we don't want them to spam the console.
+// Note that, because the panic hook is process-wide, tests that are expected to fail might
+// suppress panic messages from other failing tests. To work around this, run failing tests one at
+// a time or use only a single test thread.
+struct PanicMsgSuppressor<'a> {
+ #[allow(dead_code)]
+ mutex_guard: MutexGuard<'a, ()>,
+ prev_hook: Option<Box<dyn Fn(&PanicInfo<'_>) + 'static + Sync + Send>>,
+}
+
+impl<'a> PanicMsgSuppressor<'a> {
+ fn new(mutex_guard: MutexGuard<'a, ()>) -> PanicMsgSuppressor<'a> {
+ let prev_hook = panic::take_hook();
+ panic::set_hook(Box::new(|_| ()));
+ PanicMsgSuppressor {
+ mutex_guard,
+ prev_hook: Some(prev_hook),
+ }
+ }
+}
+
+impl<'a> Drop for PanicMsgSuppressor<'a> {
+ fn drop(&mut self) {
+ panic::set_hook(self.prev_hook.take().unwrap())
+ }
+}
+
+// AA tree helpers
+
+#[derive(Clone, Debug)]
+struct FloatBandWrapper(FloatBand);
+
+impl Arbitrary for FloatBandWrapper {
+ fn arbitrary<G>(generator: &mut G) -> FloatBandWrapper
+ where
+ G: Gen,
+ {
+ let top: u32 = Arbitrary::arbitrary(generator);
+ let left: Option<u32> = Arbitrary::arbitrary(generator);
+ let right: Option<u32> = Arbitrary::arbitrary(generator);
+ FloatBandWrapper(FloatBand {
+ top: Length::new(top as f32),
+ left: left.map(|value| Length::new(value as f32)),
+ right: right.map(|value| Length::new(value as f32)),
+ })
+ }
+}
+
+#[derive(Clone, Debug)]
+struct FloatRangeInput {
+ start_index: u32,
+ band_count: u32,
+ side: FloatSide,
+ length: u32,
+}
+
+impl Arbitrary for FloatRangeInput {
+ fn arbitrary<G>(generator: &mut G) -> FloatRangeInput
+ where
+ G: Gen,
+ {
+ let start_index: u32 = Arbitrary::arbitrary(generator);
+ let band_count: u32 = Arbitrary::arbitrary(generator);
+ let is_left: bool = Arbitrary::arbitrary(generator);
+ let length: u32 = Arbitrary::arbitrary(generator);
+ FloatRangeInput {
+ start_index,
+ band_count,
+ side: if is_left {
+ FloatSide::Left
+ } else {
+ FloatSide::Right
+ },
+ length,
+ }
+ }
+}
+
+// AA tree predicates
+
+fn check_node_ordering(node: &FloatBandNode) {
+ let mid = node.band.top;
+ if let Some(ref left) = node.left.0 {
+ assert!(left.band.top < mid);
+ }
+ if let Some(ref right) = node.right.0 {
+ assert!(right.band.top > mid);
+ }
+ if let Some(ref left) = node.left.0 {
+ check_node_ordering(left);
+ }
+ if let Some(ref right) = node.right.0 {
+ check_node_ordering(right);
+ }
+}
+
+// https://en.wikipedia.org/wiki/AA_tree#Balancing_rotations
+fn check_node_balance(node: &FloatBandNode) {
+ // 1. The level of every leaf node is one.
+ if node.left.0.is_none() && node.right.0.is_none() {
+ assert_eq!(node.level, 1);
+ }
+ // 2. The level of every left child is exactly one less than that of its parent.
+ if let Some(ref left) = node.left.0 {
+ assert_eq!(left.level, node.level - 1);
+ }
+ // 3. The level of every right child is equal to or one less than that of its parent.
+ if let Some(ref right) = node.right.0 {
+ assert!(right.level == node.level || right.level == node.level - 1);
+ }
+ // 4. The level of every right grandchild is strictly less than that of its grandparent.
+ if let Some(ref right) = node.right.0 {
+ if let Some(ref right_right) = right.right.0 {
+ assert!(right_right.level < node.level);
+ }
+ }
+ // 5. Every node of level greater than one has two children.
+ if node.level > 1 {
+ assert!(node.left.0.is_some() && node.right.0.is_some());
+ }
+}
+
+fn check_tree_ordering(tree: FloatBandTree) {
+ if let Some(ref root) = tree.root.0 {
+ check_node_ordering(root);
+ }
+}
+
+fn check_tree_balance(tree: FloatBandTree) {
+ if let Some(ref root) = tree.root.0 {
+ check_node_balance(root);
+ }
+}
+
+fn check_tree_find(tree: &FloatBandTree, block_position: Length, sorted_bands: &[FloatBand]) {
+ let found_band = tree
+ .find(block_position)
+ .expect("Couldn't find the band in the tree!");
+ let reference_band_index = sorted_bands
+ .iter()
+ .position(|band| band.top > block_position)
+ .expect("Couldn't find the reference band!") -
+ 1;
+ let reference_band = &sorted_bands[reference_band_index];
+ assert_eq!(found_band.top, reference_band.top);
+ assert_eq!(found_band.left, reference_band.left);
+ assert_eq!(found_band.right, reference_band.right);
+}
+
+fn check_tree_find_next(tree: &FloatBandTree, block_position: Length, sorted_bands: &[FloatBand]) {
+ let found_band = tree
+ .find_next(block_position)
+ .expect("Couldn't find the band in the tree!");
+ let reference_band_index = sorted_bands
+ .iter()
+ .position(|band| band.top > block_position)
+ .expect("Couldn't find the reference band!");
+ let reference_band = &sorted_bands[reference_band_index];
+ assert_eq!(found_band.top, reference_band.top);
+ assert_eq!(found_band.left, reference_band.left);
+ assert_eq!(found_band.right, reference_band.right);
+}
+
+fn check_node_range_setting(
+ node: &FloatBandNode,
+ block_range: &Range<Length>,
+ side: FloatSide,
+ value: Length,
+) {
+ if node.band.top >= block_range.start && node.band.top < block_range.end {
+ match side {
+ FloatSide::Left => assert!(node.band.left.unwrap() >= value),
+ FloatSide::Right => assert!(node.band.right.unwrap() <= value),
+ }
+ }
+
+ if let Some(ref left) = node.left.0 {
+ check_node_range_setting(left, block_range, side, value)
+ }
+ if let Some(ref right) = node.right.0 {
+ check_node_range_setting(right, block_range, side, value)
+ }
+}
+
+fn check_tree_range_setting(
+ tree: &FloatBandTree,
+ block_range: &Range<Length>,
+ side: FloatSide,
+ value: Length,
+) {
+ if let Some(ref root) = tree.root.0 {
+ check_node_range_setting(root, block_range, side, value)
+ }
+}
+
+// AA tree unit tests
+
+// Tests that the tree is a properly-ordered binary tree.
+#[test]
+fn test_tree_ordering() {
+ let f: fn(Vec<FloatBandWrapper>) = check;
+ quickcheck::quickcheck(f);
+ fn check(bands: Vec<FloatBandWrapper>) {
+ let mut tree = FloatBandTree::new();
+ for FloatBandWrapper(band) in bands {
+ tree = tree.insert(band);
+ }
+ check_tree_ordering(tree);
+ }
+}
+
+// Tests that the tree is balanced (i.e. AA tree invariants are maintained).
+#[test]
+fn test_tree_balance() {
+ let f: fn(Vec<FloatBandWrapper>) = check;
+ quickcheck::quickcheck(f);
+ fn check(bands: Vec<FloatBandWrapper>) {
+ let mut tree = FloatBandTree::new();
+ for FloatBandWrapper(band) in bands {
+ tree = tree.insert(band);
+ }
+ check_tree_balance(tree);
+ }
+}
+
+// Tests that the `find()` method works.
+#[test]
+fn test_tree_find() {
+ let f: fn(Vec<FloatBandWrapper>, Vec<u32>) = check;
+ quickcheck::quickcheck(f);
+ fn check(bands: Vec<FloatBandWrapper>, lookups: Vec<u32>) {
+ let mut bands: Vec<FloatBand> = bands.into_iter().map(|band| band.0).collect();
+ bands.push(FloatBand {
+ top: Length::zero(),
+ left: None,
+ right: None,
+ });
+ bands.push(FloatBand {
+ top: Length::new(f32::INFINITY),
+ left: None,
+ right: None,
+ });
+ let mut tree = FloatBandTree::new();
+ for ref band in &bands {
+ tree = tree.insert((*band).clone());
+ }
+ bands.sort_by(|a, b| a.top.partial_cmp(&b.top).unwrap());
+ for lookup in lookups {
+ check_tree_find(&tree, Length::new(lookup as f32), &bands);
+ }
+ }
+}
+
+// Tests that the `find_next()` method works.
+#[test]
+fn test_tree_find_next() {
+ let f: fn(Vec<FloatBandWrapper>, Vec<u32>) = check;
+ quickcheck::quickcheck(f);
+ fn check(bands: Vec<FloatBandWrapper>, lookups: Vec<u32>) {
+ let mut bands: Vec<FloatBand> = bands.into_iter().map(|band| band.0).collect();
+ bands.push(FloatBand {
+ top: Length::zero(),
+ left: None,
+ right: None,
+ });
+ bands.push(FloatBand {
+ top: Length::new(f32::INFINITY),
+ left: None,
+ right: None,
+ });
+ bands.sort_by(|a, b| a.top.partial_cmp(&b.top).unwrap());
+ bands.dedup_by(|a, b| a.top == b.top);
+ let mut tree = FloatBandTree::new();
+ for ref band in &bands {
+ tree = tree.insert((*band).clone());
+ }
+ for lookup in lookups {
+ check_tree_find_next(&tree, Length::new(lookup as f32), &bands);
+ }
+ }
+}
+
+// Tests that `set_range()` works.
+#[test]
+fn test_tree_range_setting() {
+ let f: fn(Vec<FloatBandWrapper>, Vec<FloatRangeInput>) = check;
+ quickcheck::quickcheck(f);
+ fn check(bands: Vec<FloatBandWrapper>, ranges: Vec<FloatRangeInput>) {
+ let mut tree = FloatBandTree::new();
+ for FloatBandWrapper(ref band) in &bands {
+ tree = tree.insert((*band).clone());
+ }
+
+ let mut tops: Vec<Length> = bands.iter().map(|band| band.0.top).collect();
+ tops.push(Length::new(f32::INFINITY));
+ tops.sort_by(|a, b| a.px().partial_cmp(&b.px()).unwrap());
+
+ for range in ranges {
+ let start = range.start_index.min(tops.len() as u32 - 1);
+ let end = (range.start_index + range.length).min(tops.len() as u32 - 1);
+ let block_range = tops[start as usize]..tops[end as usize];
+ let length = Length::new(range.length as f32);
+ let new_tree = tree.set_range(&block_range, range.side, length);
+ check_tree_range_setting(&new_tree, &block_range, range.side, length);
+ }
+ }
+}
+
+// Float predicates
+
+#[derive(Clone, Debug)]
+struct FloatInput {
+ // Information needed to place the float.
+ info: FloatInfo,
+ // The float may be placed no higher than this line. This simulates the effect of line boxes
+ // per CSS 2.1 § 9.5.1 rule 6.
+ ceiling: u32,
+}
+
+impl Arbitrary for FloatInput {
+ fn arbitrary<G>(generator: &mut G) -> FloatInput
+ where
+ G: Gen,
+ {
+ let width: u32 = Arbitrary::arbitrary(generator);
+ let height: u32 = Arbitrary::arbitrary(generator);
+ let is_left: bool = Arbitrary::arbitrary(generator);
+ let ceiling: u32 = Arbitrary::arbitrary(generator);
+ let clear: u8 = Arbitrary::arbitrary(generator);
+ FloatInput {
+ info: FloatInfo {
+ size: Vec2 {
+ inline: Length::new(width as f32),
+ block: Length::new(height as f32),
+ },
+ side: if is_left {
+ FloatSide::Left
+ } else {
+ FloatSide::Right
+ },
+ clear: new_clear_side(clear),
+ },
+ ceiling,
+ }
+ }
+
+ fn shrink(&self) -> Box<dyn Iterator<Item = FloatInput>> {
+ let mut this = (*self).clone();
+ let mut shrunk = false;
+ if let Some(inline_size) = self.info.size.inline.px().shrink().next() {
+ this.info.size.inline = Length::new(inline_size);
+ shrunk = true;
+ }
+ if let Some(block_size) = self.info.size.block.px().shrink().next() {
+ this.info.size.block = Length::new(block_size);
+ shrunk = true;
+ }
+ if let Some(clear_side) = (self.info.clear as u8).shrink().next() {
+ this.info.clear = new_clear_side(clear_side);
+ shrunk = true;
+ }
+ if let Some(ceiling) = self.ceiling.shrink().next() {
+ this.ceiling = ceiling;
+ shrunk = true;
+ }
+ if shrunk {
+ quickcheck::single_shrinker(this)
+ } else {
+ quickcheck::empty_shrinker()
+ }
+ }
+}
+
+fn new_clear_side(value: u8) -> ClearSide {
+ match value & 3 {
+ 0 => ClearSide::None,
+ 1 => ClearSide::Left,
+ 2 => ClearSide::Right,
+ _ => ClearSide::Both,
+ }
+}
+
+#[derive(Clone)]
+struct FloatPlacement {
+ float_context: FloatContext,
+ placed_floats: Vec<PlacedFloat>,
+}
+
+// Information about the placement of a float.
+#[derive(Clone)]
+struct PlacedFloat {
+ origin: Vec2<Length>,
+ info: FloatInfo,
+ ceiling: Length,
+}
+
+impl Drop for FloatPlacement {
+ fn drop(&mut self) {
+ if !thread::panicking() {
+ return;
+ }
+
+ // Dump the float context for debugging.
+ eprintln!(
+ "Failing float placement (inline size: {:?}):",
+ self.float_context.inline_size
+ );
+ for placed_float in &self.placed_floats {
+ eprintln!(" * {:?} @ {:?}", placed_float.info, placed_float.origin);
+ }
+ eprintln!("Bands:\n{:?}\n", self.float_context.bands);
+ }
+}
+
+impl PlacedFloat {
+ fn rect(&self) -> Rect<Length> {
+ Rect {
+ start_corner: self.origin.clone(),
+ size: self.info.size.clone(),
+ }
+ }
+}
+
+impl FloatPlacement {
+ fn place(inline_size: u32, floats: Vec<FloatInput>) -> FloatPlacement {
+ let mut float_context = FloatContext::new(Length::new(inline_size as f32));
+ let mut placed_floats = vec![];
+ for float in floats {
+ let ceiling = Length::new(float.ceiling as f32);
+ float_context.lower_ceiling(ceiling);
+ placed_floats.push(PlacedFloat {
+ origin: float_context.add_float(float.info.clone()),
+ info: float.info,
+ ceiling,
+ })
+ }
+ FloatPlacement {
+ float_context,
+ placed_floats,
+ }
+ }
+}
+
+// From CSS 2.1 § 9.5.1 [1].
+//
+// [1]: https://www.w3.org/TR/CSS2/visuren.html#float-position
+
+// 1. The left outer edge of a left-floating box may not be to the left of the left edge of its
+// containing block. An analogous rule holds for right-floating elements.
+fn check_floats_rule_1(placement: &FloatPlacement) {
+ for placed_float in &placement.placed_floats {
+ match placed_float.info.side {
+ FloatSide::Left => assert!(placed_float.origin.inline >= Length::zero()),
+ FloatSide::Right => assert!(
+ placed_float.rect().max_inline_position() <= placement.float_context.inline_size
+ ),
+ }
+ }
+}
+
+// 2. If the current box is left-floating, and there are any left-floating boxes generated by
+// elements earlier in the source document, then for each such earlier box, either the left
+// outer edge of the current box must be to the right of the right outer edge of the earlier
+// box, or its top must be lower than the bottom of the earlier box. Analogous rules hold for
+// right-floating boxes.
+fn check_floats_rule_2(placement: &FloatPlacement) {
+ for (this_float_index, this_float) in placement.placed_floats.iter().enumerate() {
+ for prev_float in &placement.placed_floats[0..this_float_index] {
+ match (this_float.info.side, prev_float.info.side) {
+ (FloatSide::Left, FloatSide::Left) => {
+ assert!(
+ this_float.origin.inline >= prev_float.rect().max_inline_position() ||
+ this_float.origin.block >= prev_float.rect().max_block_position()
+ );
+ },
+ (FloatSide::Right, FloatSide::Right) => {
+ assert!(
+ this_float.rect().max_inline_position() <= prev_float.origin.inline ||
+ this_float.origin.block >= prev_float.rect().max_block_position()
+ );
+ },
+ (FloatSide::Left, FloatSide::Right) | (FloatSide::Right, FloatSide::Left) => {},
+ }
+ }
+ }
+}
+
+// 3. The right outer edge of a left-floating box may not be to the right of the left outer edge of
+// any right-floating box that is next to it. Analogous rules hold for right-floating elements.
+fn check_floats_rule_3(placement: &FloatPlacement) {
+ for (this_float_index, this_float) in placement.placed_floats.iter().enumerate() {
+ for other_float in &placement.placed_floats[0..this_float_index] {
+ // This logic to check intersection is complicated by the fact that we need to treat
+ // zero-height floats later in the document as "next to" floats earlier in the
+ // document. Otherwise we might end up with a situation like:
+ //
+ // <div id="a" style="float: left; width: 32px; height: 32px"></div>
+ // <div id="b" style="float: right; width: 0px; height: 0px"></div>
+ //
+ // Where the top of `b` should probably be 32px per Rule 3, but unless this distinction
+ // is made the top of `b` could legally be 0px.
+ if this_float.origin.block >= other_float.rect().max_block_position() ||
+ (this_float.info.size.block == Length::zero() &&
+ this_float.rect().max_block_position() < other_float.origin.block) ||
+ (this_float.info.size.block > Length::zero() &&
+ this_float.rect().max_block_position() <= other_float.origin.block)
+ {
+ continue;
+ }
+
+ match (this_float.info.side, other_float.info.side) {
+ (FloatSide::Left, FloatSide::Right) => {
+ assert!(this_float.rect().max_inline_position() <= other_float.origin.inline);
+ },
+ (FloatSide::Right, FloatSide::Left) => {
+ assert!(this_float.origin.inline >= other_float.rect().max_inline_position());
+ },
+ (FloatSide::Left, FloatSide::Left) | (FloatSide::Right, FloatSide::Right) => {},
+ }
+ }
+ }
+}
+
+// 4. A floating box's outer top may not be higher than the top of its containing block. When the
+// float occurs between two collapsing margins, the float is positioned as if it had an
+// otherwise empty anonymous block parent taking part in the flow. The position of such a parent
+// is defined by the rules in the section on margin collapsing.
+fn check_floats_rule_4(placement: &FloatPlacement) {
+ for placed_float in &placement.placed_floats {
+ assert!(placed_float.origin.block >= Length::zero());
+ }
+}
+
+// 5. The outer top of a floating box may not be higher than the outer top of any block or floated
+// box generated by an element earlier in the source document.
+fn check_floats_rule_5(placement: &FloatPlacement) {
+ let mut block_position = Length::zero();
+ for placed_float in &placement.placed_floats {
+ assert!(placed_float.origin.block >= block_position);
+ block_position = placed_float.origin.block;
+ }
+}
+
+// 6. The outer top of an element's floating box may not be higher than the top of any line-box
+// containing a box generated by an element earlier in the source document.
+fn check_floats_rule_6(placement: &FloatPlacement) {
+ for placed_float in &placement.placed_floats {
+ assert!(placed_float.origin.block >= placed_float.ceiling);
+ }
+}
+
+// 7. A left-floating box that has another left-floating box to its left may not have its right
+// outer edge to the right of its containing block's right edge. (Loosely: a left float may not
+// stick out at the right edge, unless it is already as far to the left as possible.) An
+// analogous rule holds for right-floating elements.
+fn check_floats_rule_7(placement: &FloatPlacement) {
+ for (placed_float_index, placed_float) in placement.placed_floats.iter().enumerate() {
+ // Only consider floats that stick out.
+ match placed_float.info.side {
+ FloatSide::Left => {
+ if placed_float.rect().max_inline_position() <= placement.float_context.inline_size
+ {
+ continue;
+ }
+ },
+ FloatSide::Right => {
+ if placed_float.origin.inline >= Length::zero() {
+ continue;
+ }
+ },
+ }
+
+ // Make sure there are no previous floats to the left or right.
+ for prev_float in &placement.placed_floats[0..placed_float_index] {
+ assert!(
+ prev_float.info.side != placed_float.info.side ||
+ prev_float.rect().max_block_position() <= placed_float.origin.block ||
+ prev_float.origin.block >= placed_float.rect().max_block_position()
+ );
+ }
+ }
+}
+
+// 8. A floating box must be placed as high as possible.
+fn check_floats_rule_8(inline_size: u32, floats_and_perturbations: Vec<(FloatInput, u32)>) {
+ let floats = floats_and_perturbations
+ .iter()
+ .map(|&(ref float, _)| (*float).clone())
+ .collect();
+ let placement = FloatPlacement::place(inline_size, floats);
+
+ for (float_index, &(_, perturbation)) in floats_and_perturbations.iter().enumerate() {
+ if perturbation == 0 {
+ continue;
+ }
+
+ let mut placement = placement.clone();
+ placement.placed_floats[float_index].origin.block =
+ placement.placed_floats[float_index].origin.block - Length::new(perturbation as f32);
+
+ let result = {
+ let mutex_guard = PANIC_HOOK_MUTEX.lock().unwrap();
+ let _suppressor = PanicMsgSuppressor::new(mutex_guard);
+ panic::catch_unwind(|| check_basic_float_rules(&placement))
+ };
+ assert!(result.is_err());
+ }
+}
+
+// 9. A left-floating box must be put as far to the left as possible, a right-floating box as far
+// to the right as possible. A higher position is preferred over one that is further to the
+// left/right.
+fn check_floats_rule_9(inline_size: u32, floats_and_perturbations: Vec<(FloatInput, u32)>) {
+ let floats = floats_and_perturbations
+ .iter()
+ .map(|&(ref float, _)| (*float).clone())
+ .collect();
+ let placement = FloatPlacement::place(inline_size, floats);
+
+ for (float_index, &(_, perturbation)) in floats_and_perturbations.iter().enumerate() {
+ if perturbation == 0 {
+ continue;
+ }
+
+ let mut placement = placement.clone();
+ {
+ let mut placed_float = &mut placement.placed_floats[float_index];
+ let perturbation = Length::new(perturbation as f32);
+ match placed_float.info.side {
+ FloatSide::Left => {
+ placed_float.origin.inline = placed_float.origin.inline - perturbation
+ },
+ FloatSide::Right => {
+ placed_float.origin.inline = placed_float.origin.inline + perturbation
+ },
+ }
+ }
+
+ let result = {
+ let mutex_guard = PANIC_HOOK_MUTEX.lock().unwrap();
+ let _suppressor = PanicMsgSuppressor::new(mutex_guard);
+ panic::catch_unwind(|| check_basic_float_rules(&placement))
+ };
+ assert!(result.is_err());
+ }
+}
+
+// From CSS 2.1 § 9.5.2 (https://www.w3.org/TR/CSS2/visuren.html#propdef-clear):
+//
+// 10. The top outer edge of the float must be below the bottom outer edge of all earlier
+// left-floating boxes (in the case of 'clear: left'), or all earlier right-floating boxes (in
+// the case of 'clear: right'), or both ('clear: both').
+fn check_floats_rule_10(placement: &FloatPlacement) {
+ let mut block_position = Length::zero();
+ for placed_float in &placement.placed_floats {
+ assert!(placed_float.origin.block >= block_position);
+ block_position = placed_float.origin.block;
+ }
+
+ for (this_float_index, this_float) in placement.placed_floats.iter().enumerate() {
+ if this_float.info.clear == ClearSide::None {
+ continue;
+ }
+
+ for other_float in &placement.placed_floats[0..this_float_index] {
+ // This logic to check intersection is complicated by the fact that we need to treat
+ // zero-height floats later in the document as "next to" floats earlier in the
+ // document. Otherwise we might end up with a situation like:
+ //
+ // <div id="a" style="float: left; width: 32px; height: 32px"></div>
+ // <div id="b" style="float: right; width: 0px; height: 0px"></div>
+ //
+ // Where the top of `b` should probably be 32px per Rule 3, but unless this distinction
+ // is made the top of `b` could legally be 0px.
+ if this_float.origin.block >= other_float.rect().max_block_position() ||
+ (this_float.info.size.block == Length::zero() &&
+ this_float.rect().max_block_position() < other_float.origin.block) ||
+ (this_float.info.size.block > Length::zero() &&
+ this_float.rect().max_block_position() <= other_float.origin.block)
+ {
+ continue;
+ }
+
+ match this_float.info.clear {
+ ClearSide::Left => assert_ne!(other_float.info.side, FloatSide::Left),
+ ClearSide::Right => assert_ne!(other_float.info.side, FloatSide::Right),
+ ClearSide::Both => assert!(false),
+ ClearSide::None => unreachable!(),
+ }
+ }
+ }
+}
+
+// Checks that rule 1-7 and rule 10 hold (i.e. all rules that don't specify that floats are placed
+// "as far as possible" in some direction).
+fn check_basic_float_rules(placement: &FloatPlacement) {
+ check_floats_rule_1(placement);
+ check_floats_rule_2(placement);
+ check_floats_rule_3(placement);
+ check_floats_rule_4(placement);
+ check_floats_rule_5(placement);
+ check_floats_rule_6(placement);
+ check_floats_rule_7(placement);
+ check_floats_rule_10(placement);
+}
+
+// Float unit tests
+
+#[test]
+fn test_floats_rule_1() {
+ let f: fn(u32, Vec<FloatInput>) = check;
+ quickcheck::quickcheck(f);
+ fn check(inline_size: u32, floats: Vec<FloatInput>) {
+ check_floats_rule_1(&FloatPlacement::place(inline_size, floats));
+ }
+}
+
+#[test]
+fn test_floats_rule_2() {
+ let f: fn(u32, Vec<FloatInput>) = check;
+ quickcheck::quickcheck(f);
+ fn check(inline_size: u32, floats: Vec<FloatInput>) {
+ check_floats_rule_2(&FloatPlacement::place(inline_size, floats));
+ }
+}
+
+#[test]
+fn test_floats_rule_3() {
+ let f: fn(u32, Vec<FloatInput>) = check;
+ quickcheck::quickcheck(f);
+ fn check(inline_size: u32, floats: Vec<FloatInput>) {
+ check_floats_rule_3(&FloatPlacement::place(inline_size, floats));
+ }
+}
+
+#[test]
+fn test_floats_rule_4() {
+ let f: fn(u32, Vec<FloatInput>) = check;
+ quickcheck::quickcheck(f);
+ fn check(inline_size: u32, floats: Vec<FloatInput>) {
+ check_floats_rule_4(&FloatPlacement::place(inline_size, floats));
+ }
+}
+
+#[test]
+fn test_floats_rule_5() {
+ let f: fn(u32, Vec<FloatInput>) = check;
+ quickcheck::quickcheck(f);
+ fn check(inline_size: u32, floats: Vec<FloatInput>) {
+ check_floats_rule_5(&FloatPlacement::place(inline_size, floats));
+ }
+}
+
+#[test]
+fn test_floats_rule_6() {
+ let f: fn(u32, Vec<FloatInput>) = check;
+ quickcheck::quickcheck(f);
+ fn check(inline_size: u32, floats: Vec<FloatInput>) {
+ check_floats_rule_6(&FloatPlacement::place(inline_size, floats));
+ }
+}
+
+#[test]
+fn test_floats_rule_7() {
+ let f: fn(u32, Vec<FloatInput>) = check;
+ quickcheck::quickcheck(f);
+ fn check(inline_size: u32, floats: Vec<FloatInput>) {
+ check_floats_rule_7(&FloatPlacement::place(inline_size, floats));
+ }
+}
+
+#[test]
+fn test_floats_rule_8() {
+ let f: fn(u32, Vec<(FloatInput, u32)>) = check;
+ quickcheck::quickcheck(f);
+ fn check(inline_size: u32, floats: Vec<(FloatInput, u32)>) {
+ check_floats_rule_8(inline_size, floats);
+ }
+}
+
+#[test]
+fn test_floats_rule_9() {
+ let f: fn(u32, Vec<(FloatInput, u32)>) = check;
+ quickcheck::quickcheck(f);
+ fn check(inline_size: u32, floats: Vec<(FloatInput, u32)>) {
+ check_floats_rule_9(inline_size, floats);
+ }
+}
+
+#[test]
+fn test_floats_rule_10() {
+ let f: fn(u32, Vec<FloatInput>) = check;
+ quickcheck::quickcheck(f);
+ fn check(inline_size: u32, floats: Vec<FloatInput>) {
+ check_floats_rule_10(&FloatPlacement::place(inline_size, floats));
+ }
+}
diff --git a/python/servo/testing_commands.py b/python/servo/testing_commands.py
index ec3a74ffdfa..f5784ce4f4d 100644
--- a/python/servo/testing_commands.py
+++ b/python/servo/testing_commands.py
@@ -222,7 +222,7 @@ class MachCommands(CommandBase):
@CommandArgument('--nocapture', default=False, action="store_true",
help="Run tests with nocapture ( show test stdout )")
@CommandBase.build_like_command_arguments
- def test_unit(self, test_name=None, package=None, bench=False, nocapture=False, **kwargs):
+ def test_unit(self, test_name=None, package=None, bench=False, nocapture=False, with_layout_2020=False, **kwargs):
if test_name is None:
test_name = []
@@ -255,7 +255,6 @@ class MachCommands(CommandBase):
self_contained_tests = [
"background_hang_monitor",
"gfx",
- "layout_2013",
"msg",
"net",
"net_traits",
@@ -263,6 +262,10 @@ class MachCommands(CommandBase):
"servo_config",
"servo_remutex",
]
+ if with_layout_2020:
+ self_contained_tests.append("layout_2020")
+ else:
+ self_contained_tests.append("layout_2013")
if not packages:
packages = set(os.listdir(path.join(self.context.topdir, "tests", "unit"))) - set(['.DS_Store'])
packages |= set(self_contained_tests)
@@ -298,7 +301,11 @@ class MachCommands(CommandBase):
if nocapture:
args += ["--", "--nocapture"]
- err = self.run_cargo_build_like_command("bench" if bench else "test", args, env=env, **kwargs)
+ err = self.run_cargo_build_like_command("bench" if bench else "test",
+ args,
+ env=env,
+ with_layout_2020=with_layout_2020,
+ **kwargs)
if err:
return err
diff --git a/servo-tidy.toml b/servo-tidy.toml
index 6893dac4ba8..935ba0a205f 100644
--- a/servo-tidy.toml
+++ b/servo-tidy.toml
@@ -18,6 +18,7 @@ rand = [
"hashglobe", # Only used in tests
"ipc-channel",
"phf_generator",
+ "quickcheck", # Only used in tests
"servo_rand",
"tempfile",
"uuid",
diff --git a/tests/unit/style/parsing/mod.rs b/tests/unit/style/parsing/mod.rs
index ce4f7a0f600..80a2595caeb 100644
--- a/tests/unit/style/parsing/mod.rs
+++ b/tests/unit/style/parsing/mod.rs
@@ -112,16 +112,22 @@ macro_rules! parse_longhand {
};
}
-mod background;
-mod border;
mod box_;
-mod column;
mod effects;
mod image;
mod inherited_text;
mod outline;
mod selectors;
mod supports;
-mod text_overflow;
mod transition_duration;
mod transition_timing_function;
+
+// These tests test features that are only available in 2013 layout.
+#[cfg(feature = "layout_2013")]
+mod background;
+#[cfg(feature = "layout_2013")]
+mod border;
+#[cfg(feature = "layout_2013")]
+mod column;
+#[cfg(feature = "layout_2013")]
+mod text_overflow;