aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorbors-servo <lbergstrom+bors@mozilla.com>2017-01-12 17:31:19 -0800
committerGitHub <noreply@github.com>2017-01-12 17:31:19 -0800
commit6a04aea4a5a0da583e8cc7fc0f76c9bfea857538 (patch)
tree2e9dffe4db05f9fbf212ee249af24291524d9174
parent38fccce3ba512bbf7100574e225d73f538fad5f0 (diff)
parentd4ea380440153ebc06b79646262801ea553e2b83 (diff)
downloadservo-6a04aea4a5a0da583e8cc7fc0f76c9bfea857538.tar.gz
servo-6a04aea4a5a0da583e8cc7fc0f76c9bfea857538.zip
Auto merge of #14989 - notriddle:ellipsis_reflow, r=emilio
Fix the incrmental reflow behavior of text-overflow This patch allows Servo to incrementally reflow truncated fragments correctly. * The untruncated version of a fragment is preserved, and when incrementally reflowing, the untruncated version is what gets reflowed. If it needs truncated, it will get truncated again. * The ellipsis fragments are skipped when incrementally reflowing a line. If it is still needed, it will be recreated. --- - [X] `./mach build -d` does not report any errors - [X] `./mach test-tidy` does not report any errors - [X] These changes fix #14952 - [X] There are tests for these changes OR <!-- Reviewable:start --> --- This change is [<img src="https://reviewable.io/review_button.svg" height="34" align="absmiddle" alt="Reviewable"/>](https://reviewable.io/reviews/servo/servo/14989) <!-- Reviewable:end -->
-rw-r--r--components/layout/display_list_builder.rs15
-rw-r--r--components/layout/fragment.rs161
-rw-r--r--components/layout/inline.rs20
-rw-r--r--tests/wpt/mozilla/meta/MANIFEST.json24
-rw-r--r--tests/wpt/mozilla/tests/css/text_overflow_reflow.html15
-rw-r--r--tests/wpt/mozilla/tests/css/text_overflow_reflow_ref.html5
6 files changed, 211 insertions, 29 deletions
diff --git a/components/layout/display_list_builder.rs b/components/layout/display_list_builder.rs
index 4c7e29866c7..8d9e6547c4d 100644
--- a/components/layout/display_list_builder.rs
+++ b/components/layout/display_list_builder.rs
@@ -19,7 +19,7 @@ use flex::FlexFlow;
use flow::{BaseFlow, Flow, IS_ABSOLUTELY_POSITIONED};
use flow_ref::FlowRef;
use fragment::{CoordinateSystem, Fragment, ImageFragmentInfo, ScannedTextFragmentInfo};
-use fragment::SpecificFragmentInfo;
+use fragment::{SpecificFragmentInfo, TruncatedFragmentInfo};
use gfx::display_list::{BLUR_INFLATION_FACTOR, BaseDisplayItem, BorderDisplayItem};
use gfx::display_list::{BorderRadii, BoxShadowClipMode, BoxShadowDisplayItem, ClippingRegion};
use gfx::display_list::{DisplayItem, DisplayItemMetadata, DisplayList, DisplayListSection};
@@ -1402,7 +1402,11 @@ impl FragmentDisplayListBuilding for Fragment {
self.stacking_relative_content_box(stacking_relative_border_box);
match self.specific {
- SpecificFragmentInfo::ScannedText(ref text_fragment) => {
+ SpecificFragmentInfo::TruncatedFragment(box TruncatedFragmentInfo {
+ text_info: Some(ref text_fragment),
+ ..
+ }) |
+ SpecificFragmentInfo::ScannedText(box ref text_fragment) => {
// Create items for shadows.
//
// NB: According to CSS-BACKGROUNDS, text shadows render in *reverse* order (front
@@ -1410,7 +1414,7 @@ impl FragmentDisplayListBuilding for Fragment {
for text_shadow in self.style.get_inheritedtext().text_shadow.0.iter().rev() {
self.build_display_list_for_text_fragment(state,
- &**text_fragment,
+ &*text_fragment,
&stacking_relative_content_box,
Some(text_shadow),
clip);
@@ -1418,7 +1422,7 @@ impl FragmentDisplayListBuilding for Fragment {
// Create the main text display item.
self.build_display_list_for_text_fragment(state,
- &**text_fragment,
+ &*text_fragment,
&stacking_relative_content_box,
None,
clip);
@@ -1428,7 +1432,7 @@ impl FragmentDisplayListBuilding for Fragment {
self.style(),
stacking_relative_border_box,
&stacking_relative_content_box,
- &**text_fragment,
+ &*text_fragment,
clip);
}
}
@@ -1443,6 +1447,7 @@ impl FragmentDisplayListBuilding for Fragment {
SpecificFragmentInfo::InlineBlock(_) |
SpecificFragmentInfo::InlineAbsoluteHypothetical(_) |
SpecificFragmentInfo::InlineAbsolute(_) |
+ SpecificFragmentInfo::TruncatedFragment(_) |
SpecificFragmentInfo::Svg(_) => {
if opts::get().show_debug_fragment_borders {
self.build_debug_borders_around_fragment(state,
diff --git a/components/layout/fragment.rs b/components/layout/fragment.rs
index 9ac181c432a..558abe47b37 100644
--- a/components/layout/fragment.rs
+++ b/components/layout/fragment.rs
@@ -186,6 +186,12 @@ pub enum SpecificFragmentInfo {
Multicol,
MulticolColumn,
UnscannedText(Box<UnscannedTextFragmentInfo>),
+
+ /// A container for a fragment that got truncated by text-overflow.
+ /// "Totally truncated fragments" are not rendered at all.
+ /// Text fragments may be partially truncated (in which case this renders like a text fragment).
+ /// Other fragments can only be totally truncated or not truncated at all.
+ TruncatedFragment(Box<TruncatedFragmentInfo>),
}
impl SpecificFragmentInfo {
@@ -206,6 +212,7 @@ impl SpecificFragmentInfo {
SpecificFragmentInfo::Multicol |
SpecificFragmentInfo::MulticolColumn |
SpecificFragmentInfo::UnscannedText(_) |
+ SpecificFragmentInfo::TruncatedFragment(_) |
SpecificFragmentInfo::Generic => return RestyleDamage::empty(),
SpecificFragmentInfo::InlineAbsoluteHypothetical(ref info) => &info.flow_ref,
SpecificFragmentInfo::InlineAbsolute(ref info) => &info.flow_ref,
@@ -237,6 +244,7 @@ impl SpecificFragmentInfo {
SpecificFragmentInfo::Multicol => "SpecificFragmentInfo::Multicol",
SpecificFragmentInfo::MulticolColumn => "SpecificFragmentInfo::MulticolColumn",
SpecificFragmentInfo::UnscannedText(_) => "SpecificFragmentInfo::UnscannedText",
+ SpecificFragmentInfo::TruncatedFragment(_) => "SpecificFragmentInfo::TruncatedFragment"
}
}
}
@@ -573,11 +581,11 @@ pub struct SplitResult {
}
/// Describes how a fragment should be truncated.
-pub struct TruncationResult {
+struct TruncationResult {
/// The part of the fragment remaining after truncation.
- pub split: SplitInfo,
+ split: SplitInfo,
/// The text run which is being truncated.
- pub text_run: Arc<TextRun>,
+ text_run: Arc<TextRun>,
}
/// Data for an unscanned text fragment. Unscanned text fragments are the results of flow
@@ -622,6 +630,15 @@ impl TableColumnFragmentInfo {
}
}
+/// A wrapper for fragments that have been truncated by the `text-overflow` property.
+/// This may have an associated text node, or, if the fragment was completely truncated,
+/// it may act as an invisible marker for incremental reflow.
+#[derive(Clone)]
+pub struct TruncatedFragmentInfo {
+ pub text_info: Option<ScannedTextFragmentInfo>,
+ pub full: Fragment,
+}
+
impl Fragment {
/// Constructs a new `Fragment` instance.
pub fn new<N: ThreadSafeLayoutNode>(node: &N, specific: SpecificFragmentInfo, ctx: &LayoutContext) -> Fragment {
@@ -764,14 +781,17 @@ impl Fragment {
text_overflow_string: String)
-> Fragment {
let mut unscanned_ellipsis_fragments = LinkedList::new();
- unscanned_ellipsis_fragments.push_back(self.transform(
- self.border_box.size,
- SpecificFragmentInfo::UnscannedText(
- box UnscannedTextFragmentInfo::new(text_overflow_string, None))));
+ let mut ellipsis_fragment = self.transform(
+ self.border_box.size,
+ SpecificFragmentInfo::UnscannedText(
+ box UnscannedTextFragmentInfo::new(text_overflow_string, None)));
+ unscanned_ellipsis_fragments.push_back(ellipsis_fragment);
let ellipsis_fragments = TextRunScanner::new().scan_for_runs(&mut layout_context.font_context(),
unscanned_ellipsis_fragments);
debug_assert!(ellipsis_fragments.len() == 1);
- ellipsis_fragments.fragments.into_iter().next().unwrap()
+ ellipsis_fragment = ellipsis_fragments.fragments.into_iter().next().unwrap();
+ ellipsis_fragment.flags |= IS_ELLIPSIS;
+ ellipsis_fragment
}
pub fn restyle_damage(&self) -> RestyleDamage {
@@ -843,6 +863,7 @@ impl Fragment {
base_quantities
}
}
+ SpecificFragmentInfo::TruncatedFragment(_) |
SpecificFragmentInfo::ScannedText(_) |
SpecificFragmentInfo::TableColumn(_) |
SpecificFragmentInfo::UnscannedText(_) |
@@ -1498,7 +1519,11 @@ impl Fragment {
});
}
- SpecificFragmentInfo::ScannedText(ref text_fragment_info) => {
+ SpecificFragmentInfo::TruncatedFragment(box TruncatedFragmentInfo {
+ text_info: Some(ref text_fragment_info),
+ ..
+ }) |
+ SpecificFragmentInfo::ScannedText(box ref text_fragment_info) => {
let range = &text_fragment_info.range;
// See http://dev.w3.org/csswg/css-sizing/#max-content-inline-size.
@@ -1518,6 +1543,12 @@ impl Fragment {
preferred_inline_size: max_line_inline_size,
})
}
+
+ SpecificFragmentInfo::TruncatedFragment(box TruncatedFragmentInfo {
+ text_info: None,
+ ..
+ }) => return IntrinsicISizesContribution::new(),
+
SpecificFragmentInfo::UnscannedText(..) => {
panic!("Unscanned text fragments should have been scanned by now!")
}
@@ -1559,7 +1590,11 @@ impl Fragment {
/// this fragment.)
pub fn minimum_splittable_inline_size(&self) -> Au {
match self.specific {
- SpecificFragmentInfo::ScannedText(ref text) => {
+ SpecificFragmentInfo::TruncatedFragment(box TruncatedFragmentInfo {
+ text_info: Some(ref text),
+ ..
+ }) |
+ SpecificFragmentInfo::ScannedText(box ref text) => {
text.run.minimum_splittable_inline_size(&text.range)
}
_ => Au(0),
@@ -1621,8 +1656,54 @@ impl Fragment {
}
/// Truncates this fragment to the given `max_inline_size`, using a character-based breaking
+ /// strategy. The resulting fragment will have `SpecificFragmentInfo::TruncatedFragment`,
+ /// preserving the original fragment for use in incremental reflow.
+ ///
+ /// This function will panic if self is already truncated.
+ pub fn truncate_to_inline_size(self, max_inline_size: Au) -> Fragment {
+ if let SpecificFragmentInfo::TruncatedFragment(_) = self.specific {
+ panic!("Cannot truncate an already truncated fragment");
+ }
+ let info = self.calculate_truncate_to_inline_size(max_inline_size);
+ let (size, text_info) = match info {
+ Some(TruncationResult { split: SplitInfo { inline_size, range }, text_run } ) => {
+ let size = LogicalSize::new(self.style.writing_mode,
+ inline_size,
+ self.border_box.size.block);
+ // Preserve the insertion point if it is in this fragment's range or it is at line end.
+ let (flags, insertion_point) = match self.specific {
+ SpecificFragmentInfo::ScannedText(ref info) => {
+ match info.insertion_point {
+ Some(index) if range.contains(index) => (info.flags, info.insertion_point),
+ Some(index) if index == ByteIndex(text_run.text.chars().count() as isize - 1) &&
+ index == range.end() => (info.flags, info.insertion_point),
+ _ => (info.flags, None)
+ }
+ },
+ _ => (ScannedTextFlags::empty(), None)
+ };
+ let text_info = ScannedTextFragmentInfo::new(
+ text_run,
+ range,
+ size,
+ insertion_point,
+ flags);
+ (size, Some(text_info))
+ }
+ None =>
+ (LogicalSize::zero(self.style.writing_mode), None)
+ };
+ let mut result = self.transform(size, SpecificFragmentInfo::Generic);
+ result.specific = SpecificFragmentInfo::TruncatedFragment(box TruncatedFragmentInfo {
+ text_info: text_info,
+ full: self,
+ });
+ result
+ }
+
+ /// Truncates this fragment to the given `max_inline_size`, using a character-based breaking
/// strategy. If no characters could fit, returns `None`.
- pub fn truncate_to_inline_size(&self, max_inline_size: Au) -> Option<TruncationResult> {
+ fn calculate_truncate_to_inline_size(&self, max_inline_size: Au) -> Option<TruncationResult> {
let text_fragment_info =
if let SpecificFragmentInfo::ScannedText(ref text_fragment_info) = self.specific {
text_fragment_info
@@ -1818,6 +1899,10 @@ impl Fragment {
container_inline_size: Au,
container_block_size: Option<Au>) {
match self.specific {
+ SpecificFragmentInfo::TruncatedFragment(box TruncatedFragmentInfo {
+ text_info: None,
+ ..
+ }) |
SpecificFragmentInfo::Generic |
SpecificFragmentInfo::GeneratedContent(_) |
SpecificFragmentInfo::Table |
@@ -1839,6 +1924,7 @@ impl Fragment {
SpecificFragmentInfo::InlineAbsoluteHypothetical(_) |
SpecificFragmentInfo::InlineAbsolute(_) |
SpecificFragmentInfo::ScannedText(_) |
+ SpecificFragmentInfo::TruncatedFragment(_) |
SpecificFragmentInfo::Svg(_) => {}
};
@@ -1870,7 +1956,11 @@ impl Fragment {
}
// Text
- SpecificFragmentInfo::ScannedText(ref info) => {
+ SpecificFragmentInfo::TruncatedFragment(box TruncatedFragmentInfo {
+ text_info: Some(ref info),
+ ..
+ }) |
+ SpecificFragmentInfo::ScannedText(box ref info) => {
// Scanned text fragments will have already had their content inline-sizes assigned
// by this point.
self.border_box.size.inline = info.content_size.inline +
@@ -1895,6 +1985,10 @@ impl Fragment {
/// Ideally, this should follow CSS 2.1 § 10.6.2.
pub fn assign_replaced_block_size_if_necessary(&mut self) {
match self.specific {
+ SpecificFragmentInfo::TruncatedFragment(box TruncatedFragmentInfo {
+ text_info: None,
+ ..
+ }) |
SpecificFragmentInfo::Generic |
SpecificFragmentInfo::GeneratedContent(_) |
SpecificFragmentInfo::Table |
@@ -1916,12 +2010,20 @@ impl Fragment {
SpecificFragmentInfo::InlineAbsoluteHypothetical(_) |
SpecificFragmentInfo::InlineAbsolute(_) |
SpecificFragmentInfo::ScannedText(_) |
+ SpecificFragmentInfo::TruncatedFragment(box TruncatedFragmentInfo {
+ text_info: Some(_),
+ ..
+ }) |
SpecificFragmentInfo::Svg(_) => {}
}
match self.specific {
// Text
- SpecificFragmentInfo::ScannedText(ref info) => {
+ SpecificFragmentInfo::TruncatedFragment(box TruncatedFragmentInfo {
+ text_info: Some(ref info),
+ ..
+ }) |
+ SpecificFragmentInfo::ScannedText(box ref info) => {
// Scanned text fragments' content block-sizes are calculated by the text run
// scanner during flow construction.
self.border_box.size.block = info.content_size.block +
@@ -1998,7 +2100,11 @@ impl Fragment {
ascent: ascent,
}
}
- SpecificFragmentInfo::ScannedText(ref info) => {
+ SpecificFragmentInfo::TruncatedFragment(box TruncatedFragmentInfo {
+ text_info: Some(ref info),
+ ..
+ }) |
+ SpecificFragmentInfo::ScannedText(box ref info) => {
// Fragments with no glyphs don't contribute any inline metrics.
// TODO: Filter out these fragments during flow construction?
if info.insertion_point.is_none() && info.content_size.inline == Au(0) {
@@ -2016,6 +2122,10 @@ impl Fragment {
SpecificFragmentInfo::InlineAbsoluteHypothetical(ref info) => {
inline_metrics_of_block(&info.flow_ref, &*self.style)
}
+ SpecificFragmentInfo::TruncatedFragment(box TruncatedFragmentInfo {
+ text_info: None,
+ ..
+ }) |
SpecificFragmentInfo::InlineAbsolute(_) => {
InlineMetrics::new(Au(0), Au(0), Au(0))
}
@@ -2264,6 +2374,7 @@ impl Fragment {
SpecificFragmentInfo::TableCell |
SpecificFragmentInfo::TableColumn(_) |
SpecificFragmentInfo::TableRow |
+ SpecificFragmentInfo::TruncatedFragment(_) |
SpecificFragmentInfo::Multicol |
SpecificFragmentInfo::UnscannedText(_) => true,
}
@@ -2351,6 +2462,7 @@ impl Fragment {
pub fn establishes_stacking_context(&self) -> bool {
// Text fragments shouldn't create stacking contexts.
match self.specific {
+ SpecificFragmentInfo::TruncatedFragment(_) |
SpecificFragmentInfo::ScannedText(_) |
SpecificFragmentInfo::UnscannedText(_) => return false,
_ => {}
@@ -2481,7 +2593,11 @@ impl Fragment {
pub fn requires_line_break_afterward_if_wrapping_on_newlines(&self) -> bool {
match self.specific {
- SpecificFragmentInfo::ScannedText(ref scanned_text) => {
+ SpecificFragmentInfo::TruncatedFragment(box TruncatedFragmentInfo {
+ text_info: Some(ref scanned_text),
+ ..
+ }) |
+ SpecificFragmentInfo::ScannedText(box ref scanned_text) => {
scanned_text.requires_line_break_afterward_if_wrapping_on_newlines()
}
_ => false,
@@ -2494,7 +2610,11 @@ impl Fragment {
}
match self.specific {
- SpecificFragmentInfo::ScannedText(ref mut scanned_text_fragment_info) => {
+ SpecificFragmentInfo::TruncatedFragment(box TruncatedFragmentInfo {
+ text_info: Some(ref mut scanned_text_fragment_info),
+ ..
+ }) |
+ SpecificFragmentInfo::ScannedText(box ref mut scanned_text_fragment_info) => {
let leading_whitespace_byte_count = scanned_text_fragment_info.text()
.find(|c| !char_is_whitespace(c))
.unwrap_or(scanned_text_fragment_info.text().len());
@@ -2548,7 +2668,11 @@ impl Fragment {
}
match self.specific {
- SpecificFragmentInfo::ScannedText(ref mut scanned_text_fragment_info) => {
+ SpecificFragmentInfo::TruncatedFragment(box TruncatedFragmentInfo {
+ text_info: Some(ref mut scanned_text_fragment_info),
+ ..
+ }) |
+ SpecificFragmentInfo::ScannedText(box ref mut scanned_text_fragment_info) => {
let mut trailing_whitespace_start_byte = 0;
for (i, c) in scanned_text_fragment_info.text().char_indices().rev() {
if !char_is_whitespace(c) {
@@ -2734,6 +2858,7 @@ impl Fragment {
SpecificFragmentInfo::Iframe(_) |
SpecificFragmentInfo::Image(_) |
SpecificFragmentInfo::ScannedText(_) |
+ SpecificFragmentInfo::TruncatedFragment(_) |
SpecificFragmentInfo::Svg(_) |
SpecificFragmentInfo::UnscannedText(_) => true
}
@@ -2997,6 +3122,8 @@ bitflags! {
const IS_INLINE_FLEX_ITEM = 0b0000_0001,
/// Whether this fragment represents a child in a column flex container.
const IS_BLOCK_FLEX_ITEM = 0b0000_0010,
+ /// Whether this fragment represents the generated text from a text-overflow clip.
+ const IS_ELLIPSIS = 0b0000_0100,
}
}
diff --git a/components/layout/inline.rs b/components/layout/inline.rs
index 2d591b1ee02..8a086d34320 100644
--- a/components/layout/inline.rs
+++ b/components/layout/inline.rs
@@ -15,6 +15,7 @@ use flow::{CONTAINS_TEXT_OR_REPLACED_FRAGMENTS, EarlyAbsolutePositionInfo, Mutab
use flow::OpaqueFlow;
use flow_ref::FlowRef;
use fragment::{CoordinateSystem, Fragment, FragmentBorderBoxIterator, Overflow};
+use fragment::IS_ELLIPSIS;
use fragment::SpecificFragmentInfo;
use gfx::display_list::OpaqueNode;
use gfx::font::FontMetrics;
@@ -332,6 +333,15 @@ impl LineBreaker {
Some(fragment) => fragment,
};
+ // Do not reflow truncated fragments. Reflow the original fragment only.
+ let fragment = if fragment.flags.contains(IS_ELLIPSIS) {
+ continue
+ } else if let SpecificFragmentInfo::TruncatedFragment(info) = fragment.specific {
+ info.full
+ } else {
+ fragment
+ };
+
// Try to append the fragment.
self.reflow_fragment(fragment, flow, layout_context);
}
@@ -707,13 +717,9 @@ impl LineBreaker {
if let Some(string) = ellipsis {
let ellipsis = fragment.transform_into_ellipsis(layout_context, string);
- if let Some(truncation_info) =
- fragment.truncate_to_inline_size(available_inline_size -
- ellipsis.margin_box_inline_size()) {
- let fragment = fragment.transform_with_split_info(&truncation_info.split,
- truncation_info.text_run);
- self.push_fragment_to_line_ignoring_text_overflow(fragment, layout_context);
- }
+ let truncated = fragment.truncate_to_inline_size(available_inline_size -
+ ellipsis.margin_box_inline_size());
+ self.push_fragment_to_line_ignoring_text_overflow(truncated, layout_context);
self.push_fragment_to_line_ignoring_text_overflow(ellipsis, layout_context);
} else {
self.push_fragment_to_line_ignoring_text_overflow(fragment, layout_context);
diff --git a/tests/wpt/mozilla/meta/MANIFEST.json b/tests/wpt/mozilla/meta/MANIFEST.json
index d49d2d362d1..794a743fe62 100644
--- a/tests/wpt/mozilla/meta/MANIFEST.json
+++ b/tests/wpt/mozilla/meta/MANIFEST.json
@@ -5544,6 +5544,18 @@
"url": "/_mozilla/css/text_overflow_ellipsis.html"
}
],
+ "css/text_overflow_reflow.html": [
+ {
+ "path": "css/text_overflow_reflow.html",
+ "references": [
+ [
+ "/_mozilla/css/text_overflow_reflow_ref.html",
+ "=="
+ ]
+ ],
+ "url": "/_mozilla/css/text_overflow_reflow.html"
+ }
+ ],
"css/text_overflow_string.html": [
{
"path": "css/text_overflow_string.html",
@@ -20880,6 +20892,18 @@
"url": "/_mozilla/css/text_overflow_ellipsis.html"
}
],
+ "css/text_overflow_reflow.html": [
+ {
+ "path": "css/text_overflow_reflow.html",
+ "references": [
+ [
+ "/_mozilla/css/text_overflow_reflow_ref.html",
+ "=="
+ ]
+ ],
+ "url": "/_mozilla/css/text_overflow_reflow.html"
+ }
+ ],
"css/text_overflow_string.html": [
{
"path": "css/text_overflow_string.html",
diff --git a/tests/wpt/mozilla/tests/css/text_overflow_reflow.html b/tests/wpt/mozilla/tests/css/text_overflow_reflow.html
new file mode 100644
index 00000000000..958e2826fa4
--- /dev/null
+++ b/tests/wpt/mozilla/tests/css/text_overflow_reflow.html
@@ -0,0 +1,15 @@
+<!doctype html>
+<html class="reftest-wait">
+<meta charset="utf-8">
+<title>Text overflow should disappear when the container becomes large enough</title>
+<meta name="description" content="This test is targetted at bug #14952 in Servo's incremental reflow engine.">
+<link rel="match" href="text_overflow_reflow_ref.html">
+<style>html{font-family:Ahem}</style>
+<div id=goat style="width:5em"><p style="text-overflow:ellipsis;overflow:hidden">XXXXXXXXXX</p></div>
+<script>
+var goat = document.getElementById("goat");
+requestAnimationFrame(function() {
+ goat.style.width = "20em";
+ document.documentElement.className = "";
+});
+</script>
diff --git a/tests/wpt/mozilla/tests/css/text_overflow_reflow_ref.html b/tests/wpt/mozilla/tests/css/text_overflow_reflow_ref.html
new file mode 100644
index 00000000000..3997908406b
--- /dev/null
+++ b/tests/wpt/mozilla/tests/css/text_overflow_reflow_ref.html
@@ -0,0 +1,5 @@
+<!doctype html>
+<meta charset="utf-8">
+<title>Text overflow should disappear when the container becomes large enough</title>
+<style>html{font-family:Ahem}</style>
+<div style="width:20em"><p style="text-overflow:ellipsis;overflow:hidden">XXXXXXXXXX</p></div>