aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--src/components/gfx/display_list/mod.rs25
-rw-r--r--src/components/layout/block.rs39
-rw-r--r--src/components/layout/flow.rs12
-rw-r--r--src/components/layout/fragment.rs76
-rw-r--r--src/components/layout/inline.rs13
-rw-r--r--src/components/layout/layout_task.rs8
-rw-r--r--src/components/util/geometry.rs3
m---------src/support/geom/rust-geom0
-rw-r--r--src/test/harness/reftest/reftest.rs10
-rw-r--r--src/test/harness/reftest/reftest.rs.orig415
-rw-r--r--src/test/ref/basic.list1
-rw-r--r--src/test/ref/vertical-lr-blocks.html18
-rw-r--r--src/test/ref/vertical-lr-blocks_ref.html18
13 files changed, 553 insertions, 85 deletions
diff --git a/src/components/gfx/display_list/mod.rs b/src/components/gfx/display_list/mod.rs
index 21c8c8a25b5..0ad7d945ae1 100644
--- a/src/components/gfx/display_list/mod.rs
+++ b/src/components/gfx/display_list/mod.rs
@@ -316,7 +316,6 @@ impl DisplayList {
}
}
-
/// Appends the given item to the display list.
pub fn push(&mut self, item: DisplayItem) {
self.list.push(item)
@@ -328,6 +327,14 @@ impl DisplayList {
self.list.append(other.list)
}
+ pub fn debug(&self) {
+ if log_enabled!(::log::DEBUG) {
+ for item in self.list.iter() {
+ item.debug_with_level(0);
+ }
+ }
+ }
+
/// Draws the display list into the given render context. The display list must be flattened
/// first for correct painting.
pub fn draw_into_context(&self, render_context: &mut RenderContext) {
@@ -724,14 +731,14 @@ impl DisplayItem {
}
pub fn debug_with_level(&self, level: uint) {
- let mut indent = String::new();
- for _ in range(0, level) {
- indent.push_str("| ")
- }
- debug!("{}+ {}", indent, self);
- for child in self.children() {
- child.debug_with_level(level + 1);
- }
+ let mut indent = String::new();
+ for _ in range(0, level) {
+ indent.push_str("| ")
+ }
+ debug!("{}+ {}", indent, self);
+ for child in self.children() {
+ child.debug_with_level(level + 1);
+ }
}
}
diff --git a/src/components/layout/block.rs b/src/components/layout/block.rs
index ea65756c7de..4c17cebc97f 100644
--- a/src/components/layout/block.rs
+++ b/src/components/layout/block.rs
@@ -1110,16 +1110,18 @@ impl BlockFlow {
.relative_containing_block_size,
None);
+ // FIXME(#2795): Get the real container size
+ let container_size = Size2D::zero();
+
// Add the box that starts the block context.
let mut display_list = DisplayList::new();
- let mut accumulator =
- self.fragment.build_display_list(&mut display_list,
- layout_context,
- self.base.abs_position
- .add_point(&offset)
- + rel_offset,
- background_border_level,
- None);
+ let mut accumulator = self.fragment.build_display_list(
+ &mut display_list,
+ layout_context,
+ self.base.abs_position + (offset + rel_offset).to_physical(
+ self.base.writing_mode, container_size),
+ background_border_level,
+ None);
let mut child_layers = DList::new();
for kid in self.base.child_iter() {
@@ -1592,17 +1594,22 @@ impl Flow for BlockFlow {
}
fn compute_absolute_position(&mut self) {
+ // FIXME(#2795): Get the real container size
+ let container_size = Size2D::zero();
+
if self.is_absolutely_positioned() {
+ let position_start = self.base.position.start.to_physical(
+ self.base.writing_mode, container_size);
self.base
.absolute_position_info
.absolute_containing_block_position = if self.is_fixed() {
// The viewport is initially at (0, 0).
- self.base.position.start
+ position_start
} else {
// Absolute position of the containing block + position of absolute flow w/r/t the
// containing block.
self.base.absolute_position_info.absolute_containing_block_position
- .add_point(&self.base.position.start)
+ + position_start
};
// Set the absolute position, which will be passed down later as part
@@ -1622,8 +1629,8 @@ impl Flow for BlockFlow {
if self.is_positioned() {
self.base.absolute_position_info.absolute_containing_block_position =
self.base.abs_position
- .add_point(&self.generated_containing_block_rect().start)
- + relative_offset
+ + (self.generated_containing_block_rect().start
+ + relative_offset).to_physical(self.base.writing_mode, container_size)
}
let float_offset = if self.is_float() {
@@ -1640,14 +1647,14 @@ impl Flow for BlockFlow {
// Process children.
let this_position = self.base.abs_position;
+ let writing_mode = self.base.writing_mode;
for kid in self.base.child_iter() {
if !kid.is_absolutely_positioned() {
let kid_base = flow::mut_base(kid);
- kid_base.abs_position =
- this_position
- .add_point(&kid_base.position.start)
+ kid_base.abs_position = this_position + (
+ kid_base.position.start
.add_point(&float_offset)
- + relative_offset;
+ + relative_offset).to_physical(writing_mode, container_size);
kid_base.absolute_position_info = absolute_position_info
}
}
diff --git a/src/components/layout/flow.rs b/src/components/layout/flow.rs
index e401a1f75c6..27091eb7665 100644
--- a/src/components/layout/flow.rs
+++ b/src/components/layout/flow.rs
@@ -46,13 +46,15 @@ use table_cell::TableCellFlow;
use wrapper::ThreadSafeLayoutNode;
use collections::dlist::DList;
+use geom::Point2D;
use gfx::display_list::DisplayList;
use gfx::render_task::RenderLayer;
use servo_msg::compositor_msg::LayerId;
use servo_util::geometry::Au;
use servo_util::logical_geometry::WritingMode;
-use servo_util::logical_geometry::{LogicalPoint, LogicalRect, LogicalSize};
+use servo_util::logical_geometry::{LogicalRect, LogicalSize};
use std::mem;
+use std::num::Zero;
use std::fmt;
use std::iter::Zip;
use std::sync::atomics::{AtomicUint, Relaxed, SeqCst};
@@ -596,7 +598,7 @@ pub struct AbsolutePositionInfo {
/// The size of the containing block for relatively-positioned descendants.
pub relative_containing_block_size: LogicalSize<Au>,
/// The position of the absolute containing block.
- pub absolute_containing_block_position: LogicalPoint<Au>,
+ pub absolute_containing_block_position: Point2D<Au>,
/// Whether the absolute containing block forces positioned descendants to be layerized.
///
/// FIXME(pcwalton): Move into `FlowFlags`.
@@ -609,7 +611,7 @@ impl AbsolutePositionInfo {
// of the root layer.
AbsolutePositionInfo {
relative_containing_block_size: LogicalSize::zero(writing_mode),
- absolute_containing_block_position: LogicalPoint::zero(writing_mode),
+ absolute_containing_block_position: Zero::zero(),
layers_needed_for_positioned_flows: false,
}
}
@@ -660,7 +662,7 @@ pub struct BaseFlow {
pub collapsible_margins: CollapsibleMargins,
/// The position of this flow in page coordinates, computed during display list construction.
- pub abs_position: LogicalPoint<Au>,
+ pub abs_position: Point2D<Au>,
/// Details about descendants with position 'absolute' or 'fixed' for which we are the
/// containing block. This is in tree order. This includes any direct children.
@@ -724,7 +726,7 @@ impl BaseFlow {
floats: Floats::new(writing_mode),
collapsible_margins: CollapsibleMargins::new(),
- abs_position: LogicalPoint::zero(writing_mode),
+ abs_position: Zero::zero(),
abs_descendants: Descendants::new(),
absolute_static_i_offset: Au::new(0),
fixed_static_i_offset: Au::new(0),
diff --git a/src/components/layout/fragment.rs b/src/components/layout/fragment.rs
index 7f61517a328..316f29c0d23 100644
--- a/src/components/layout/fragment.rs
+++ b/src/components/layout/fragment.rs
@@ -37,7 +37,7 @@ use servo_net::image::holder::ImageHolder;
use servo_net::local_image_cache::LocalImageCache;
use servo_util::geometry::Au;
use servo_util::geometry;
-use servo_util::logical_geometry::{LogicalPoint, LogicalRect, LogicalSize, LogicalMargin};
+use servo_util::logical_geometry::{LogicalRect, LogicalSize, LogicalMargin};
use servo_util::range::*;
use servo_util::namespace;
use servo_util::smallvec::SmallVec;
@@ -776,15 +776,15 @@ impl Fragment {
fn build_debug_borders_around_text_fragments(&self,
display_list: &mut DisplayList,
- flow_origin: LogicalPoint<Au>,
+ flow_origin: Point2D<Au>,
text_fragment: &ScannedTextFragmentInfo) {
- let mut fragment_bounds = self.border_box.clone();
- fragment_bounds.start.i = fragment_bounds.start.i + flow_origin.i;
- fragment_bounds.start.b = fragment_bounds.start.b + flow_origin.b;
// FIXME(#2795): Get the real container size
let container_size = Size2D::zero();
- let absolute_fragment_bounds = fragment_bounds.to_physical(
- self.style.writing_mode, container_size);
+ // Fragment position wrt to the owning flow.
+ let fragment_bounds = self.border_box.to_physical(self.style.writing_mode, container_size);
+ let absolute_fragment_bounds = Rect(
+ fragment_bounds.origin + flow_origin,
+ fragment_bounds.size);
// Compute the text fragment bounds and draw a border surrounding them.
let border_display_item = box BorderDisplayItem {
@@ -797,13 +797,11 @@ impl Fragment {
// Draw a rectangle representing the baselines.
let ascent = text_fragment.run.ascent();
- let baseline = LogicalRect::new(
- self.style.writing_mode,
- fragment_bounds.start.i,
- fragment_bounds.start.b + ascent,
- fragment_bounds.size.inline,
- Au(0)
- ).to_physical(self.style.writing_mode, container_size);
+ let mut baseline = self.border_box.clone();
+ baseline.start.b = baseline.start.b + ascent;
+ baseline.size.block = Au(0);
+ let mut baseline = baseline.to_physical(self.style.writing_mode, container_size);
+ baseline.origin = baseline.origin + flow_origin;
let line_display_item = box LineDisplayItem {
base: BaseDisplayItem::new(baseline, self.node, ContentStackingLevel),
@@ -815,14 +813,14 @@ impl Fragment {
fn build_debug_borders_around_fragment(&self,
display_list: &mut DisplayList,
- flow_origin: LogicalPoint<Au>) {
- let mut fragment_bounds = self.border_box.clone();
- fragment_bounds.start.i = fragment_bounds.start.i + flow_origin.i;
- fragment_bounds.start.b = fragment_bounds.start.b + flow_origin.b;
+ flow_origin: Point2D<Au>) {
// FIXME(#2795): Get the real container size
let container_size = Size2D::zero();
- let absolute_fragment_bounds = fragment_bounds.to_physical(
- self.style.writing_mode, container_size);
+ // Fragment position wrt to the owning flow.
+ let fragment_bounds = self.border_box.to_physical(self.style.writing_mode, container_size);
+ let absolute_fragment_bounds = Rect(
+ fragment_bounds.origin + flow_origin,
+ fragment_bounds.size);
// This prints a debug border around the border of this fragment.
let border_display_item = box BorderDisplayItem {
@@ -845,18 +843,17 @@ impl Fragment {
pub fn build_display_list(&self,
display_list: &mut DisplayList,
layout_context: &LayoutContext,
- flow_origin: LogicalPoint<Au>,
+ flow_origin: Point2D<Au>,
background_and_border_level: BackgroundAndBorderLevel,
inline_fragment_context: Option<InlineFragmentContext>)
-> ChildDisplayListAccumulator {
- // Fragment position wrt to the owning flow.
- let mut fragment_bounds = self.border_box.clone();
- fragment_bounds.start.i = fragment_bounds.start.i + flow_origin.i;
- fragment_bounds.start.b = fragment_bounds.start.b + flow_origin.b;
// FIXME(#2795): Get the real container size
let container_size = Size2D::zero();
- let absolute_fragment_bounds = fragment_bounds.to_physical(
- self.style.writing_mode, container_size);
+ // Fragment position wrt to the owning flow.
+ let fragment_bounds = self.border_box.to_physical(self.style.writing_mode, container_size);
+ let absolute_fragment_bounds = Rect(
+ fragment_bounds.origin + flow_origin,
+ fragment_bounds.size);
debug!("Fragment::build_display_list at rel={}, abs={}: {}",
self.border_box,
absolute_fragment_bounds,
@@ -1413,21 +1410,18 @@ impl Fragment {
#[inline(never)]
fn finalize_position_and_size_of_iframe(&self,
iframe_fragment: &IframeFragmentInfo,
- offset: LogicalPoint<Au>,
+ offset: Point2D<Au>,
layout_context: &LayoutContext) {
- let inline_start = offset.i + self.margin.inline_start + self.border_padding.inline_start;
- let block_start = offset.b + self.margin.block_start + self.border_padding.block_start;
- let inline_size = self.content_box().size.inline;
- let block_size = self.content_box().size.block;
- // FIXME(#2795): Get the real container size
- let container_size = Size2D::zero();
- let rect = LogicalRect::new(
- self.style.writing_mode,
- geometry::to_frac_px(inline_start) as f32,
- geometry::to_frac_px(block_start) as f32,
- geometry::to_frac_px(inline_size) as f32,
- geometry::to_frac_px(block_size) as f32
- ).to_physical(self.style.writing_mode, container_size);
+ let mbp = (self.margin + self.border_padding).to_physical(self.style.writing_mode);
+ let content_size = self.content_box().size.to_physical(self.style.writing_mode);
+
+ let left = offset.x + mbp.left;
+ let top = offset.y + mbp.top;
+ let width = content_size.width;
+ let height = content_size.height;
+ let origin = Point2D(geometry::to_frac_px(left) as f32, geometry::to_frac_px(top) as f32);
+ let size = Size2D(geometry::to_frac_px(width) as f32, geometry::to_frac_px(height) as f32);
+ let rect = Rect(origin, size);
debug!("finalizing position and size of iframe for {:?},{:?}",
iframe_fragment.pipeline_id,
diff --git a/src/components/layout/inline.rs b/src/components/layout/inline.rs
index 1a427f79c0b..9b6b0af28d7 100644
--- a/src/components/layout/inline.rs
+++ b/src/components/layout/inline.rs
@@ -16,7 +16,7 @@ use text;
use wrapper::ThreadSafeLayoutNode;
use collections::{Deque, RingBuf};
-use geom::Size2D;
+use geom::Rect;
use gfx::display_list::ContentLevel;
use gfx::font::FontMetrics;
use gfx::font_context::FontContext;
@@ -927,12 +927,8 @@ impl InlineFlow {
}
pub fn build_display_list_inline(&mut self, layout_context: &LayoutContext) {
- let abs_rect = LogicalRect::from_point_size(
- self.base.writing_mode, self.base.abs_position, self.base.position.size);
- // FIXME(#2795): Get the real container size
- let container_size = Size2D::zero();
- if !abs_rect.to_physical(self.base.writing_mode, container_size)
- .intersects(&layout_context.shared.dirty) {
+ let size = self.base.position.size.to_physical(self.base.writing_mode);
+ if !Rect(self.base.abs_position, size).intersects(&layout_context.shared.dirty) {
return
}
@@ -947,7 +943,8 @@ impl InlineFlow {
Some(context));
drop(fragment.build_display_list(&mut self.base.display_list,
layout_context,
- self.base.abs_position + rel_offset,
+ self.base.abs_position.add_size(
+ &rel_offset.to_physical(self.base.writing_mode)),
ContentLevel,
Some(context)));
}
diff --git a/src/components/layout/layout_task.rs b/src/components/layout/layout_task.rs
index e4cba397379..e104ea443fe 100644
--- a/src/components/layout/layout_task.rs
+++ b/src/components/layout/layout_task.rs
@@ -46,6 +46,7 @@ use gfx::font_cache_task::{FontCacheTask};
use servo_net::local_image_cache::{ImageResponder, LocalImageCache};
use servo_util::geometry::Au;
use servo_util::geometry;
+use servo_util::logical_geometry::LogicalPoint;
use servo_util::opts::Opts;
use servo_util::smallvec::{SmallVec, SmallVec1};
use servo_util::time::{TimeProfilerChan, profile};
@@ -694,10 +695,10 @@ impl LayoutTask {
if data.goal == ReflowForDisplay {
let writing_mode = flow::base(layout_root.get()).writing_mode;
profile(time::LayoutDispListBuildCategory, self.time_profiler_chan.clone(), || {
- // FIXME(#2795): Get the real container size
- let container_size = Size2D::zero();
shared_layout_ctx.dirty = flow::base(layout_root.get()).position.to_physical(
- writing_mode, container_size);
+ writing_mode, self.screen_size);
+ flow::mut_base(layout_root.get_mut()).abs_position =
+ LogicalPoint::zero(writing_mode).to_physical(writing_mode, self.screen_size);
match self.parallel_traversal {
None => {
@@ -718,6 +719,7 @@ impl LayoutTask {
let root_display_list =
mem::replace(&mut flow::mut_base(layout_root.get_mut()).display_list,
DisplayList::new());
+ root_display_list.debug();
let display_list = Arc::new(root_display_list.flatten(ContentStackingLevel));
// FIXME(pcwalton): This is really ugly and can't handle overflow: scroll. Refactor
diff --git a/src/components/util/geometry.rs b/src/components/util/geometry.rs
index 70882f17178..2b1082fa3d2 100644
--- a/src/components/util/geometry.rs
+++ b/src/components/util/geometry.rs
@@ -79,8 +79,7 @@ impl Default for Au {
impl fmt::Show for Au {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
- let Au(n) = *self;
- write!(f, "Au(au={} px={})", n, to_frac_px(*self))
+ write!(f, "{}px", to_frac_px(*self))
}}
impl Add<Au,Au> for Au {
diff --git a/src/support/geom/rust-geom b/src/support/geom/rust-geom
-Subproject b41f144a3a8b6388d0956f341bcffa5bbaecc89
+Subproject c733f78e06bd02f7498e93b391e0f6094d91786
diff --git a/src/test/harness/reftest/reftest.rs b/src/test/harness/reftest/reftest.rs
index f0ec11ddc98..57738bbf517 100644
--- a/src/test/harness/reftest/reftest.rs
+++ b/src/test/harness/reftest/reftest.rs
@@ -114,6 +114,7 @@ struct Reftest {
servo_args: Vec<String>,
render_mode: RenderMode,
is_flaky: bool,
+ experimental: bool,
}
struct TestLine<'a> {
@@ -166,12 +167,14 @@ fn parse_lists(file: &str, servo_args: &[String], render_mode: RenderMode, id_of
let mut conditions_list = test_line.conditions.split(',');
let mut flakiness = RenderMode::empty();
+ let mut experimental = false;
for condition in conditions_list {
match condition {
"flaky_cpu" => flakiness.insert(CpuRendering),
"flaky_gpu" => flakiness.insert(GpuRendering),
"flaky_linux" => flakiness.insert(LinuxTarget),
"flaky_macos" => flakiness.insert(MacOsTarget),
+ "experimental" => experimental = true,
_ => (),
}
}
@@ -184,6 +187,7 @@ fn parse_lists(file: &str, servo_args: &[String], render_mode: RenderMode, id_of
render_mode: render_mode,
servo_args: servo_args.iter().map(|x| x.clone()).collect(),
is_flaky: render_mode.intersects(flakiness),
+ experimental: experimental,
};
tests.push(make_test(reftest));
@@ -212,7 +216,11 @@ fn capture(reftest: &Reftest, side: uint) -> png::Image {
if reftest.render_mode.contains(CpuRendering) {
args.push("-c".to_string());
}
- args.push_all_move(vec!("-f".to_string(), "-o".to_string(), filename.clone(), reftest.files[side].clone()));
+ if reftest.experimental {
+ args.push("--experimental".to_string());
+ }
+ args.push_all(["-f".to_string(), "-o".to_string(), filename.clone(),
+ reftest.files[side].clone()]);
let retval = match Command::new("./servo").args(args.as_slice()).status() {
Ok(status) => status,
diff --git a/src/test/harness/reftest/reftest.rs.orig b/src/test/harness/reftest/reftest.rs.orig
new file mode 100644
index 00000000000..645a8d55307
--- /dev/null
+++ b/src/test/harness/reftest/reftest.rs.orig
@@ -0,0 +1,415 @@
+// Copyright 2013 The Servo Project Developers. See the COPYRIGHT
+// file at the top-level directory of this distribution.
+//
+// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
+// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
+// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
+// option. This file may not be copied, modified, or distributed
+// except according to those terms.
+
+extern crate png;
+extern crate std;
+extern crate test;
+extern crate regex;
+
+use std::ascii::StrAsciiExt;
+use std::io;
+use std::io::{File, Reader, Command};
+use std::io::process::ExitStatus;
+use std::os;
+use test::{AutoColor, DynTestName, DynTestFn, TestDesc, TestOpts, TestDescAndFn};
+use test::run_tests_console;
+use regex::Regex;
+
+
+bitflags!(
+ flags RenderMode: u32 {
+ static CpuRendering = 0x00000001,
+ static GpuRendering = 0x00000010,
+ static LinuxTarget = 0x00000100,
+ static MacOsTarget = 0x00001000,
+ static AndroidTarget = 0x00010000
+ }
+)
+
+
+fn main() {
+ let args = os::args();
+ let mut parts = args.tail().split(|e| "--" == e.as_slice());
+
+ let harness_args = parts.next().unwrap(); // .split() is never empty
+ let servo_args = parts.next().unwrap_or(&[]);
+
+ let (render_mode_string, base_path, testname) = match harness_args {
+ [] | [_] => fail!("USAGE: cpu|gpu base_path [testname regex]"),
+ [ref render_mode_string, ref base_path] => (render_mode_string, base_path, None),
+ [ref render_mode_string, ref base_path, ref testname, ..] => (render_mode_string, base_path, Some(Regex::new(testname.as_slice()).unwrap())),
+ };
+
+ let mut render_mode = match render_mode_string.as_slice() {
+ "cpu" => CpuRendering,
+ "gpu" => GpuRendering,
+ _ => fail!("First argument must specify cpu or gpu as rendering mode")
+ };
+ if cfg!(target_os = "linux") {
+ render_mode.insert(LinuxTarget);
+ }
+ if cfg!(target_os = "macos") {
+ render_mode.insert(MacOsTarget);
+ }
+ if cfg!(target_os = "android") {
+ render_mode.insert(AndroidTarget);
+ }
+
+ let mut all_tests = vec!();
+ println!("Scanning {} for manifests\n", base_path);
+
+ for file in io::fs::walk_dir(&Path::new(base_path.as_slice())).unwrap() {
+ let maybe_extension = file.extension_str();
+ match maybe_extension {
+ Some(extension) => {
+ if extension.to_ascii_lower().as_slice() == "list" && file.is_file() {
+ let manifest = file.as_str().unwrap();
+ let tests = parse_lists(manifest, servo_args, render_mode);
+ println!("\t{} [{} tests]", manifest, tests.len());
+ all_tests.push_all_move(tests);
+ }
+ }
+ _ => {}
+ }
+ }
+
+ let test_opts = TestOpts {
+ filter: testname,
+ run_ignored: false,
+ logfile: None,
+ run_tests: true,
+ run_benchmarks: false,
+ ratchet_noise_percent: None,
+ ratchet_metrics: None,
+ save_metrics: None,
+ test_shard: None,
+ nocapture: false,
+ color: AutoColor
+ };
+
+ match run_tests_console(&test_opts, all_tests) {
+ Ok(false) => os::set_exit_status(1), // tests failed
+ Err(_) => os::set_exit_status(2), // I/O-related failure
+ _ => (),
+ }
+}
+
+#[deriving(PartialEq)]
+enum ReftestKind {
+ Same,
+ Different,
+}
+
+struct Reftest {
+ name: String,
+ kind: ReftestKind,
+ files: [String, ..2],
+ id: uint,
+ servo_args: Vec<String>,
+ render_mode: RenderMode,
+<<<<<<< HEAD
+ is_flaky: bool,
+||||||| merged common ancestors
+ flakiness: uint,
+=======
+ flakiness: uint,
+ experimental: bool,
+>>>>>>> Reftests can opt into --experimental
+}
+
+struct TestLine<'a> {
+ conditions: &'a str,
+ kind: &'a str,
+ file_left: &'a str,
+ file_right: &'a str,
+}
+
+fn parse_lists(file: &str, servo_args: &[String], render_mode: RenderMode) -> Vec<TestDescAndFn> {
+ let mut tests = Vec::new();
+ let mut next_id = 0;
+ let file_path = Path::new(file);
+ let contents = File::open_mode(&file_path, io::Open, io::Read)
+ .and_then(|mut f| f.read_to_string())
+ .ok().expect("Could not read file");
+
+ for line in contents.as_slice().lines() {
+<<<<<<< HEAD
+ // ignore comments or empty lines
+ if line.starts_with("#") || line.is_empty() {
+ continue;
+ }
+
+ let parts: Vec<&str> = line.split(' ').filter(|p| !p.is_empty()).collect();
+
+ let test_line = match parts.len() {
+ 3 => TestLine {
+ conditions: "",
+ kind: parts[0],
+ file_left: parts[1],
+ file_right: parts[2],
+||||||| merged common ancestors
+ // ignore comments or empty lines
+ if line.starts_with("#") || line.is_empty() {
+ continue;
+ }
+
+ let parts: Vec<&str> = line.split(' ').filter(|p| !p.is_empty()).collect();
+
+ let test_line = match parts.len() {
+ 3 => {
+ TestLine {
+ conditions: "",
+ kind: parts[0],
+ file_left: parts[1],
+ file_right: parts[2],
+ }
+ },
+ 4 => {
+ TestLine {
+ conditions: parts[0],
+ kind: parts[1],
+ file_left: parts[2],
+ file_right: parts[3],
+ }
+ },
+ _ => {
+ fail!("reftest line: '{:s}' doesn't match '[CONDITIONS] KIND LEFT RIGHT'", line);
+ }
+ };
+
+ let kind = match test_line.kind {
+ "==" => Same,
+ "!=" => Different,
+ part => fail!("reftest line: '{:s}' has invalid kind '{:s}'", line, part)
+ };
+ let src_path = file_path.dir_path();
+ let src_dir = src_path.display().to_string();
+ let file_left = src_dir.clone().append("/").append(test_line.file_left);
+ let file_right = src_dir.append("/").append(test_line.file_right);
+
+ let mut conditions_list = test_line.conditions.split(',');
+ let mut flakiness = 0;
+ for condition in conditions_list {
+ match condition {
+ "flaky_cpu" => {
+ flakiness |= CpuRendering as uint;
+=======
+ // ignore comments or empty lines
+ if line.starts_with("#") || line.is_empty() {
+ continue;
+ }
+
+ let parts: Vec<&str> = line.split(' ').filter(|p| !p.is_empty()).collect();
+
+ let test_line = match parts.len() {
+ 3 => {
+ TestLine {
+ conditions: "",
+ kind: parts[0],
+ file_left: parts[1],
+ file_right: parts[2],
+ }
+ },
+ 4 => {
+ TestLine {
+ conditions: parts[0],
+ kind: parts[1],
+ file_left: parts[2],
+ file_right: parts[3],
+ }
+ },
+ _ => {
+ fail!("reftest line: '{:s}' doesn't match '[CONDITIONS] KIND LEFT RIGHT'", line);
+ }
+ };
+
+ let kind = match test_line.kind {
+ "==" => Same,
+ "!=" => Different,
+ part => fail!("reftest line: '{:s}' has invalid kind '{:s}'", line, part)
+ };
+ let src_path = file_path.dir_path();
+ let src_dir = src_path.display().to_string();
+ let file_left = src_dir.clone().append("/").append(test_line.file_left);
+ let file_right = src_dir.append("/").append(test_line.file_right);
+
+ let mut conditions_list = test_line.conditions.split(',');
+ let mut flakiness = 0;
+ let mut experimental = false;
+ for condition in conditions_list {
+ match condition {
+ "flaky_cpu" => {
+ flakiness |= CpuRendering as uint;
+>>>>>>> Reftests can opt into --experimental
+ },
+ 4 => TestLine {
+ conditions: parts[0],
+ kind: parts[1],
+ file_left: parts[2],
+ file_right: parts[3],
+ },
+<<<<<<< HEAD
+ _ => fail!("reftest line: '{:s}' doesn't match '[CONDITIONS] KIND LEFT RIGHT'", line),
+ };
+
+ let kind = match test_line.kind {
+ "==" => Same,
+ "!=" => Different,
+ part => fail!("reftest line: '{:s}' has invalid kind '{:s}'", line, part)
+ };
+ let src_path = file_path.dir_path();
+ let src_dir = src_path.display().to_string();
+ let file_left = src_dir.clone().append("/").append(test_line.file_left);
+ let file_right = src_dir.append("/").append(test_line.file_right);
+
+ let mut conditions_list = test_line.conditions.split(',');
+ let mut flakiness = RenderMode::empty();
+ for condition in conditions_list {
+ match condition {
+ "flaky_cpu" => flakiness.insert(CpuRendering),
+ "flaky_gpu" => flakiness.insert(GpuRendering),
+ "flaky_linux" => flakiness.insert(LinuxTarget),
+ "flaky_macos" => flakiness.insert(MacOsTarget),
+ _ => (),
+ }
+ }
+
+ let reftest = Reftest {
+ name: format!("{} {} {}", test_line.file_left, test_line.kind, test_line.file_right),
+ kind: kind,
+ files: [file_left, file_right],
+ id: next_id,
+ render_mode: render_mode,
+ servo_args: servo_args.iter().map(|x| x.clone()).collect(),
+ is_flaky: render_mode.intersects(flakiness),
+ };
+
+ next_id += 1;
+
+ tests.push(make_test(reftest));
+||||||| merged common ancestors
+ _ => {}
+ }
+ }
+
+ let reftest = Reftest {
+ name: test_line.file_left.to_string().append(" / ").append(test_line.file_right),
+ kind: kind,
+ files: [file_left, file_right],
+ id: next_id,
+ render_mode: render_mode,
+ servo_args: servo_args.iter().map(|x| x.clone()).collect(),
+ flakiness: flakiness,
+ };
+
+ next_id += 1;
+
+ tests.push(make_test(reftest));
+=======
+ "experimental" => {
+ experimental = true;
+ },
+ _ => {}
+ }
+ }
+
+ let reftest = Reftest {
+ name: test_line.file_left.to_string().append(" / ").append(test_line.file_right),
+ kind: kind,
+ files: [file_left, file_right],
+ id: next_id,
+ render_mode: render_mode,
+ servo_args: servo_args.iter().map(|x| x.clone()).collect(),
+ flakiness: flakiness,
+ experimental: experimental,
+ };
+
+ next_id += 1;
+
+ tests.push(make_test(reftest));
+>>>>>>> Reftests can opt into --experimental
+ }
+ tests
+}
+
+fn make_test(reftest: Reftest) -> TestDescAndFn {
+ let name = reftest.name.clone();
+ TestDescAndFn {
+ desc: TestDesc {
+ name: DynTestName(name),
+ ignore: false,
+ should_fail: false,
+ },
+ testfn: DynTestFn(proc() {
+ check_reftest(reftest);
+ }),
+ }
+}
+
+fn capture(reftest: &Reftest, side: uint) -> png::Image {
+ let filename = format!("/tmp/servo-reftest-{:06u}-{:u}.png", reftest.id, side);
+ let mut args = reftest.servo_args.clone();
+ // GPU rendering is the default
+ if reftest.render_mode.contains(CpuRendering) {
+ args.push("-c".to_string());
+ }
+ if reftest.experimental {
+ args.push("--experimental".to_string());
+ }
+ args.push_all(["-f".to_string(), "-o".to_string(), filename.clone(),
+ reftest.files[side].clone()]);
+
+ let retval = match Command::new("./servo").args(args.as_slice()).status() {
+ Ok(status) => status,
+ Err(e) => fail!("failed to execute process: {}", e),
+ };
+ assert!(retval == ExitStatus(0));
+
+ png::load_png(&from_str::<Path>(filename.as_slice()).unwrap()).unwrap()
+}
+
+fn check_reftest(reftest: Reftest) {
+ let left = capture(&reftest, 0);
+ let right = capture(&reftest, 1);
+
+ let pixels = left.pixels.iter().zip(right.pixels.iter()).map(|(&a, &b)| {
+ if a as i8 - b as i8 == 0 {
+ // White for correct
+ 0xFF
+ } else {
+ // "1100" in the RGBA channel with an error for an incorrect value
+ // This results in some number of C0 and FFs, which is much more
+ // readable (and distinguishable) than the previous difference-wise
+ // scaling but does not require reconstructing the actual RGBA pixel.
+ 0xC0
+ }
+ }).collect::<Vec<u8>>();
+
+ if pixels.iter().any(|&a| a < 255) {
+ let output_str = format!("/tmp/servo-reftest-{:06u}-diff.png", reftest.id);
+ let output = from_str::<Path>(output_str.as_slice()).unwrap();
+
+ let mut img = png::Image {
+ width: left.width,
+ height: left.height,
+ color_type: png::RGBA8,
+ pixels: pixels,
+ };
+ let res = png::store_png(&mut img, &output);
+ assert!(res.is_ok());
+
+ match (reftest.kind, reftest.is_flaky) {
+ (Same, true) => println!("flaky test - rendering difference: {}", output_str),
+ (Same, false) => fail!("rendering difference: {}", output_str),
+ (Different, _) => {} // Result was different and that's what was expected
+ }
+ } else {
+ assert!(reftest.is_flaky || reftest.kind == Same);
+ }
+}
diff --git a/src/test/ref/basic.list b/src/test/ref/basic.list
index 13835bf64ee..9884fe140b4 100644
--- a/src/test/ref/basic.list
+++ b/src/test/ref/basic.list
@@ -86,6 +86,7 @@ flaky_cpu == linebreak_simple_a.html linebreak_simple_b.html
== position_fixed_overflow_a.html position_fixed_overflow_b.html
== noscript.html noscript_ref.html
== pseudo_inherit.html pseudo_inherit_ref.html
+experimental == vertical-lr-blocks.html vertical-lr-blocks_ref.html
== float_intrinsic_height.html float_intrinsic_height_ref.html
== table_auto_width.html table_auto_width_ref.html
== inline_whitespace_b.html inline_whitespace_ref.html
diff --git a/src/test/ref/vertical-lr-blocks.html b/src/test/ref/vertical-lr-blocks.html
new file mode 100644
index 00000000000..c68fee90788
--- /dev/null
+++ b/src/test/ref/vertical-lr-blocks.html
@@ -0,0 +1,18 @@
+<!doctype html>
+<html>
+<head>
+<style>
+html { writing-mode: vertical-rl }
+body { margin: 10px }
+div { border: blue solid 5px; line-height: 30px; height: 500px }
+p { background: green; margin: 40px 20px }
+p + p { margin-top: 60px }
+</style>
+</head>
+<body>
+<div>
+<p>&nbsp;</p>
+<p>&nbsp;</p>
+</div>
+</body>
+</html>
diff --git a/src/test/ref/vertical-lr-blocks_ref.html b/src/test/ref/vertical-lr-blocks_ref.html
new file mode 100644
index 00000000000..8581695c49f
--- /dev/null
+++ b/src/test/ref/vertical-lr-blocks_ref.html
@@ -0,0 +1,18 @@
+<!doctype html>
+<html>
+<head>
+<style>
+div { border: blue solid 5px; position: absolute;
+ top: 10px; right: 10px; bottom: 10px; width: 120px; height: 500px }
+p { background: green; margin: 0; position: absolute;
+ top: 40px; right: 20px; bottom: 40px; width: 30px }
+p + p { right: 70px; top: 60px }
+</style>
+</head>
+<body>
+<div>
+<p>&nbsp;</p>
+<p>&nbsp;</p>
+</div>
+</body>
+</html>