Auto merge of #24928 - servo:2020-images, r=SimonSapin

Start supporting images in layout 2020
This commit is contained in:
bors-servo 2019-12-01 09:53:45 -05:00 committed by GitHub
commit 40fd7910a5
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
13 changed files with 267 additions and 65 deletions

1
Cargo.lock generated
View file

@ -2723,6 +2723,7 @@ dependencies = [
"ipc-channel", "ipc-channel",
"libc", "libc",
"msg", "msg",
"net_traits",
"range", "range",
"rayon", "rayon",
"rayon_croissant", "rayon_croissant",

View file

@ -22,6 +22,7 @@ gfx_traits = {path = "../gfx_traits"}
ipc-channel = "0.12" ipc-channel = "0.12"
libc = "0.2" libc = "0.2"
msg = {path = "../msg"} msg = {path = "../msg"}
net_traits = {path = "../net_traits"}
range = {path = "../range"} range = {path = "../range"}
rayon = "1" rayon = "1"
rayon_croissant = "0.1.1" rayon_croissant = "0.1.1"

View file

@ -78,6 +78,34 @@ impl Fragment {
.wr .wr
.push_text(&common, rect.into(), &glyphs, t.font_key, rgba(color), None); .push_text(&common, rect.into(), &glyphs, t.font_key, rgba(color), None);
}, },
Fragment::Image(i) => {
use style::computed_values::image_rendering::T as ImageRendering;
is_contentful.0 = true;
let rect = i
.content_rect
.to_physical(i.style.writing_mode(), containing_block)
.translate(&containing_block.top_left);
let common = CommonItemProperties {
clip_rect: rect.clone().into(),
clip_id: wr::ClipId::root(builder.pipeline_id),
spatial_id: wr::SpatialId::root_scroll_node(builder.pipeline_id),
hit_info: None,
// TODO(gw): Make use of the WR backface visibility functionality.
flags: PrimitiveFlags::default(),
};
builder.wr.push_image(
&common,
rect.into(),
match i.style.get_inherited_box().image_rendering {
ImageRendering::Auto => wr::ImageRendering::Auto,
ImageRendering::CrispEdges => wr::ImageRendering::CrispEdges,
ImageRendering::Pixelated => wr::ImageRendering::Pixelated,
},
wr::AlphaType::PremultipliedAlpha,
i.image_key,
wr::ColorF::WHITE,
);
},
} }
} }
} }

View file

@ -3,18 +3,22 @@
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */ * file, You can obtain one at https://mozilla.org/MPL/2.0/. */
use crate::element_data::{LayoutBox, LayoutDataForElement}; use crate::element_data::{LayoutBox, LayoutDataForElement};
use crate::geom::physical::Vec2;
use crate::replaced::ReplacedContent; use crate::replaced::ReplacedContent;
use crate::style_ext::{Display, DisplayGeneratingBox, DisplayInside, DisplayOutside}; use crate::style_ext::{Display, DisplayGeneratingBox, DisplayInside, DisplayOutside};
use crate::wrapper::GetRawData; use crate::wrapper::GetRawData;
use atomic_refcell::{AtomicRefCell, AtomicRefMut}; use atomic_refcell::{AtomicRefCell, AtomicRefMut};
use net_traits::image::base::{Image as NetImage, ImageMetadata};
use script_layout_interface::wrapper_traits::{LayoutNode, ThreadSafeLayoutNode}; use script_layout_interface::wrapper_traits::{LayoutNode, ThreadSafeLayoutNode};
use servo_arc::Arc as ServoArc; use servo_arc::Arc as ServoArc;
use std::convert::TryInto; use std::convert::TryInto;
use std::marker::PhantomData as marker; use std::marker::PhantomData as marker;
use std::sync::Arc;
use style::context::SharedStyleContext; use style::context::SharedStyleContext;
use style::dom::TNode; use style::dom::TNode;
use style::properties::ComputedValues; use style::properties::ComputedValues;
use style::selector_parser::PseudoElement; use style::selector_parser::PseudoElement;
use style::values::computed::Length;
#[derive(Clone, Copy)] #[derive(Clone, Copy)]
pub enum WhichPseudoElement { pub enum WhichPseudoElement {
@ -90,11 +94,12 @@ fn traverse_element<'dom, Node>(
) where ) where
Node: NodeExt<'dom>, Node: NodeExt<'dom>,
{ {
let replaced = ReplacedContent::for_element(element);
let style = element.style(context); let style = element.style(context);
match Display::from(style.get_box().display) { match Display::from(style.get_box().display) {
Display::None => element.unset_boxes_in_subtree(), Display::None => element.unset_boxes_in_subtree(),
Display::Contents => { Display::Contents => {
if ReplacedContent::for_element(element, context).is_some() { if replaced.is_some() {
// `display: content` on a replaced element computes to `display: none` // `display: content` on a replaced element computes to `display: none`
// <https://drafts.csswg.org/css-display-3/#valdef-display-contents> // <https://drafts.csswg.org/css-display-3/#valdef-display-contents>
element.unset_boxes_in_subtree() element.unset_boxes_in_subtree()
@ -107,10 +112,7 @@ fn traverse_element<'dom, Node>(
handler.handle_element( handler.handle_element(
&style, &style,
display, display,
match ReplacedContent::for_element(element, context) { replaced.map_or(Contents::OfElement(element), Contents::Replaced),
Some(replaced) => Contents::Replaced(replaced),
None => Contents::OfElement(element),
},
element.element_box_slot(), element.element_box_slot(),
); );
}, },
@ -287,6 +289,7 @@ impl Drop for BoxSlot<'_> {
pub(crate) trait NodeExt<'dom>: 'dom + Copy + LayoutNode + Send + Sync { pub(crate) trait NodeExt<'dom>: 'dom + Copy + LayoutNode + Send + Sync {
fn is_element(self) -> bool; fn is_element(self) -> bool;
fn as_text(self) -> Option<String>; fn as_text(self) -> Option<String>;
fn as_image(self) -> Option<(Option<Arc<NetImage>>, Vec2<Length>)>;
fn first_child(self) -> Option<Self>; fn first_child(self) -> Option<Self>;
fn next_sibling(self) -> Option<Self>; fn next_sibling(self) -> Option<Self>;
fn parent_node(self) -> Option<Self>; fn parent_node(self) -> Option<Self>;
@ -315,6 +318,26 @@ where
} }
} }
fn as_image(self) -> Option<(Option<Arc<NetImage>>, Vec2<Length>)> {
let node = self.to_threadsafe();
let (resource, metadata) = node.image_data()?;
let (width, height) = resource
.as_ref()
.map(|image| (image.width, image.height))
.or_else(|| metadata.map(|metadata| (metadata.width, metadata.height)))
.unwrap_or((0, 0));
let (mut width, mut height) = (width as f32, height as f32);
if let Some(density) = node.image_density().filter(|density| *density != 1.) {
width = (width as f64 / density) as f32;
height = (height as f64 / density) as f32;
}
let size = Vec2 {
x: Length::new(width),
y: Length::new(height),
};
Some((resource, size))
}
fn first_child(self) -> Option<Self> { fn first_child(self) -> Option<Self> {
TNode::first_child(&self) TNode::first_child(&self)
} }

View file

@ -325,10 +325,14 @@ where
contents: Contents<Node>, contents: Contents<Node>,
) -> Arc<InlineLevelBox> { ) -> Arc<InlineLevelBox> {
let box_ = match contents.try_into() { let box_ = match contents.try_into() {
Err(replaced) => Arc::new(InlineLevelBox::Atomic { Err(replaced) => Arc::new(InlineLevelBox::Atomic(
style: style.clone(), IndependentFormattingContext::construct(
contents: replaced, self.context,
}), style.clone(),
display_inside,
<Contents<Node>>::Replaced(replaced),
),
)),
Ok(non_replaced) => match display_inside { Ok(non_replaced) => match display_inside {
DisplayInside::Flow | DisplayInside::Flow |
// TODO: Properly implement display: inline-block. // TODO: Properly implement display: inline-block.
@ -449,7 +453,7 @@ where
let box_ = Arc::new(InlineLevelBox::OutOfFlowAbsolutelyPositionedBox( let box_ = Arc::new(InlineLevelBox::OutOfFlowAbsolutelyPositionedBox(
AbsolutelyPositionedBox { AbsolutelyPositionedBox {
contents: IndependentFormattingContext::construct( contents: IndependentFormattingContext::construct(
unimplemented!(), self.context,
style, style,
display_inside, display_inside,
contents, contents,

View file

@ -5,12 +5,12 @@
use crate::context::LayoutContext; use crate::context::LayoutContext;
use crate::flow::float::FloatBox; use crate::flow::float::FloatBox;
use crate::flow::FlowLayout; use crate::flow::FlowLayout;
use crate::formatting_contexts::IndependentFormattingContext;
use crate::fragments::{ use crate::fragments::{
AnonymousFragment, BoxFragment, CollapsedBlockMargins, Fragment, TextFragment, AnonymousFragment, BoxFragment, CollapsedBlockMargins, Fragment, TextFragment,
}; };
use crate::geom::flow_relative::{Rect, Sides, Vec2}; use crate::geom::flow_relative::{Rect, Sides, Vec2};
use crate::positioned::{AbsolutelyPositionedBox, AbsolutelyPositionedFragment}; use crate::positioned::{AbsolutelyPositionedBox, AbsolutelyPositionedFragment};
use crate::replaced::ReplacedContent;
use crate::style_ext::{ComputedValuesExt, Display, DisplayGeneratingBox, DisplayOutside}; use crate::style_ext::{ComputedValuesExt, Display, DisplayGeneratingBox, DisplayOutside};
use crate::{relative_adjustement, ContainingBlock}; use crate::{relative_adjustement, ContainingBlock};
use servo_arc::Arc; use servo_arc::Arc;
@ -29,11 +29,7 @@ pub(crate) enum InlineLevelBox {
TextRun(TextRun), TextRun(TextRun),
OutOfFlowAbsolutelyPositionedBox(AbsolutelyPositionedBox), OutOfFlowAbsolutelyPositionedBox(AbsolutelyPositionedBox),
OutOfFlowFloatBox(FloatBox), OutOfFlowFloatBox(FloatBox),
Atomic { Atomic(IndependentFormattingContext),
style: Arc<ComputedValues>,
// FIXME: this should be IndependentFormattingContext:
contents: ReplacedContent,
},
} }
#[derive(Debug)] #[derive(Debug)]
@ -112,9 +108,9 @@ impl InlineFormattingContext {
ifc.partial_inline_boxes_stack.push(partial) ifc.partial_inline_boxes_stack.push(partial)
}, },
InlineLevelBox::TextRun(run) => run.layout(layout_context, &mut ifc), InlineLevelBox::TextRun(run) => run.layout(layout_context, &mut ifc),
InlineLevelBox::Atomic { style: _, contents } => { InlineLevelBox::Atomic(_independent) => {
// FIXME // TODO
match *contents {} continue;
}, },
InlineLevelBox::OutOfFlowAbsolutelyPositionedBox(box_) => { InlineLevelBox::OutOfFlowAbsolutelyPositionedBox(box_) => {
let initial_start_corner = let initial_start_corner =

View file

@ -15,6 +15,7 @@ use crate::geom::flow_relative::{Rect, Sides, Vec2};
use crate::positioned::{ use crate::positioned::{
adjust_static_positions, AbsolutelyPositionedBox, AbsolutelyPositionedFragment, adjust_static_positions, AbsolutelyPositionedBox, AbsolutelyPositionedFragment,
}; };
use crate::replaced::ReplacedContent;
use crate::style_ext::{ComputedValuesExt, Position}; use crate::style_ext::{ComputedValuesExt, Position};
use crate::{relative_adjustement, ContainingBlock}; use crate::{relative_adjustement, ContainingBlock};
use rayon::iter::{IndexedParallelIterator, IntoParallelRefIterator, ParallelIterator}; use rayon::iter::{IndexedParallelIterator, IntoParallelRefIterator, ParallelIterator};
@ -295,10 +296,12 @@ impl BlockLevelBox {
)) ))
}, },
BlockLevelBox::Independent(contents) => match contents.as_replaced() { BlockLevelBox::Independent(contents) => match contents.as_replaced() {
Ok(replaced) => { Ok(replaced) => Fragment::Box(layout_in_flow_replaced_block_level(
// FIXME layout_context,
match *replaced {} containing_block,
}, &contents.style,
replaced,
)),
Err(non_replaced) => Fragment::Box(layout_in_flow_non_replaced_block_level( Err(non_replaced) => Fragment::Box(layout_in_flow_non_replaced_block_level(
layout_context, layout_context,
containing_block, containing_block,
@ -360,27 +363,15 @@ fn layout_in_flow_non_replaced_block_level<'a>(
let box_size = style.box_size(); let box_size = style.box_size();
let inline_size = box_size.inline.percentage_relative_to(cbis); let inline_size = box_size.inline.percentage_relative_to(cbis);
if let LengthOrAuto::LengthPercentage(is) = inline_size { if let LengthOrAuto::LengthPercentage(is) = inline_size {
let inline_margins = cbis - is - pb.inline_sum(); let (margin_inline_start, margin_inline_end) = solve_inline_margins_for_in_flow_block_level(
match ( containing_block,
&mut computed_margin.inline_start, pb.inline_sum(),
&mut computed_margin.inline_end, computed_margin.inline_start,
) { computed_margin.inline_end,
(s @ &mut LengthOrAuto::Auto, e @ &mut LengthOrAuto::Auto) => { is,
*s = LengthOrAuto::LengthPercentage(inline_margins / 2.); );
*e = LengthOrAuto::LengthPercentage(inline_margins / 2.); computed_margin.inline_start = LengthOrAuto::LengthPercentage(margin_inline_start);
}, computed_margin.inline_end = LengthOrAuto::LengthPercentage(margin_inline_end);
(s @ &mut LengthOrAuto::Auto, _) => {
*s = LengthOrAuto::LengthPercentage(inline_margins);
},
(_, e @ &mut LengthOrAuto::Auto) => {
*e = LengthOrAuto::LengthPercentage(inline_margins);
},
(_, e @ _) => {
// Either the inline-end margin is auto,
// or were over-constrained and we do as if it were.
*e = LengthOrAuto::LengthPercentage(inline_margins);
},
}
} }
let margin = computed_margin.auto_is(Length::zero); let margin = computed_margin.auto_is(Length::zero);
let mut block_margins_collapsed_with_children = CollapsedBlockMargins::from_margin(&margin); let mut block_margins_collapsed_with_children = CollapsedBlockMargins::from_margin(&margin);
@ -479,3 +470,104 @@ fn layout_in_flow_non_replaced_block_level<'a>(
block_margins_collapsed_with_children, block_margins_collapsed_with_children,
} }
} }
/// https://drafts.csswg.org/css2/visudet.html#block-replaced-width
/// https://drafts.csswg.org/css2/visudet.html#inline-replaced-width
/// https://drafts.csswg.org/css2/visudet.html#inline-replaced-height
fn layout_in_flow_replaced_block_level<'a>(
layout_context: &LayoutContext,
containing_block: &ContainingBlock,
style: &Arc<ComputedValues>,
replaced: &ReplacedContent,
) -> BoxFragment {
let cbis = containing_block.inline_size;
let padding = style.padding().percentages_relative_to(cbis);
let border = style.border_width();
let computed_margin = style.margin().percentages_relative_to(cbis);
let pb = &padding + &border;
let mode = style.writing_mode();
// FIXME(nox): We shouldn't pretend we always have a fully known intrinsic size.
let intrinsic_size = replaced.intrinsic_size.size_to_flow_relative(mode);
// FIXME(nox): This can divide by zero.
let intrinsic_ratio = intrinsic_size.inline.px() / intrinsic_size.block.px();
let box_size = style.box_size();
let inline_size = box_size.inline.percentage_relative_to(cbis);
let block_size = box_size
.block
.maybe_percentage_relative_to(containing_block.block_size.non_auto());
let (inline_size, block_size) = match (inline_size, block_size) {
(LengthOrAuto::LengthPercentage(inline), LengthOrAuto::LengthPercentage(block)) => {
(inline, block)
},
(LengthOrAuto::LengthPercentage(inline), LengthOrAuto::Auto) => {
(inline, inline / intrinsic_ratio)
},
(LengthOrAuto::Auto, LengthOrAuto::LengthPercentage(block)) => {
(block * intrinsic_ratio, block)
},
(LengthOrAuto::Auto, LengthOrAuto::Auto) => (intrinsic_size.inline, intrinsic_size.block),
};
let (margin_inline_start, margin_inline_end) = solve_inline_margins_for_in_flow_block_level(
containing_block,
pb.inline_sum(),
computed_margin.inline_start,
computed_margin.inline_end,
inline_size,
);
let margin = Sides {
inline_start: margin_inline_start,
inline_end: margin_inline_end,
block_start: computed_margin.block_start.auto_is(Length::zero),
block_end: computed_margin.block_end.auto_is(Length::zero),
};
let containing_block_for_children = ContainingBlock {
inline_size,
block_size: LengthOrAuto::LengthPercentage(block_size),
mode,
};
// https://drafts.csswg.org/css-writing-modes/#orthogonal-flows
assert_eq!(
containing_block.mode, containing_block_for_children.mode,
"Mixed writing modes are not supported yet"
);
let independent_layout = replaced.layout(layout_context, style, &containing_block_for_children);
let relative_adjustement = relative_adjustement(
style,
inline_size,
LengthOrAuto::LengthPercentage(block_size),
);
let content_rect = Rect {
start_corner: Vec2 {
block: pb.block_start + relative_adjustement.block,
inline: pb.inline_start + relative_adjustement.inline + margin.inline_start,
},
size: Vec2 {
block: block_size,
inline: inline_size,
},
};
BoxFragment {
style: style.clone(),
children: independent_layout.fragments,
content_rect,
padding,
border,
block_margins_collapsed_with_children: CollapsedBlockMargins::from_margin(&margin),
margin,
}
}
fn solve_inline_margins_for_in_flow_block_level(
containing_block: &ContainingBlock,
padding_border_inline_sum: Length,
computed_margin_inline_start: LengthOrAuto,
computed_margin_inline_end: LengthOrAuto,
inline_size: Length,
) -> (Length, Length) {
let inline_margins = containing_block.inline_size - padding_border_inline_sum - inline_size;
match (computed_margin_inline_start, computed_margin_inline_end) {
(LengthOrAuto::Auto, LengthOrAuto::Auto) => (inline_margins / 2., inline_margins / 2.),
(LengthOrAuto::Auto, LengthOrAuto::LengthPercentage(end)) => (inline_margins - end, end),
(LengthOrAuto::LengthPercentage(start), _) => (start, inline_margins - start),
}
}

View file

@ -48,7 +48,7 @@ fn construct_for_root_element<'dom>(
root_element: impl NodeExt<'dom>, root_element: impl NodeExt<'dom>,
) -> (ContainsFloats, Vec<Arc<BlockLevelBox>>) { ) -> (ContainsFloats, Vec<Arc<BlockLevelBox>>) {
let style = root_element.style(context); let style = root_element.style(context);
let replaced = ReplacedContent::for_element(root_element, context); let replaced = ReplacedContent::for_element(root_element);
let box_style = style.get_box(); let box_style = style.get_box();
let display_inside = match Display::from(box_style.display) { let display_inside = match Display::from(box_style.display) {
@ -63,21 +63,13 @@ fn construct_for_root_element<'dom>(
Display::GeneratingBox(DisplayGeneratingBox::OutsideInside { inside, .. }) => inside, Display::GeneratingBox(DisplayGeneratingBox::OutsideInside { inside, .. }) => inside,
}; };
if let Some(replaced) = replaced {
let _box = match replaced {};
#[allow(unreachable_code)]
{
return (ContainsFloats::No, vec![Arc::new(_box)]);
}
}
let position = box_style.position; let position = box_style.position;
let float = box_style.float; let float = box_style.float;
let contents = IndependentFormattingContext::construct( let contents = IndependentFormattingContext::construct(
context, context,
style, style,
display_inside, display_inside,
Contents::OfElement(root_element), replaced.map_or(Contents::OfElement(root_element), Contents::Replaced),
); );
if position.is_absolutely_positioned() { if position.is_absolutely_positioned() {
( (

View file

@ -83,7 +83,7 @@ impl IndependentFormattingContext {
absolutely_positioned_fragments: &mut Vec<AbsolutelyPositionedFragment<'a>>, absolutely_positioned_fragments: &mut Vec<AbsolutelyPositionedFragment<'a>>,
) -> IndependentLayout { ) -> IndependentLayout {
match self.as_replaced() { match self.as_replaced() {
Ok(replaced) => match *replaced {}, Ok(replaced) => replaced.layout(layout_context, &self.style, containing_block),
Err(ifc) => ifc.layout( Err(ifc) => ifc.layout(
layout_context, layout_context,
containing_block, containing_block,

View file

@ -10,12 +10,13 @@ use std::sync::Arc;
use style::properties::ComputedValues; use style::properties::ComputedValues;
use style::values::computed::Length; use style::values::computed::Length;
use style::Zero; use style::Zero;
use webrender_api::FontInstanceKey; use webrender_api::{FontInstanceKey, ImageKey};
pub(crate) enum Fragment { pub(crate) enum Fragment {
Box(BoxFragment), Box(BoxFragment),
Anonymous(AnonymousFragment), Anonymous(AnonymousFragment),
Text(TextFragment), Text(TextFragment),
Image(ImageFragment),
} }
pub(crate) struct BoxFragment { pub(crate) struct BoxFragment {
@ -61,6 +62,12 @@ pub(crate) struct TextFragment {
pub glyphs: Vec<Arc<GlyphStore>>, pub glyphs: Vec<Arc<GlyphStore>>,
} }
pub(crate) struct ImageFragment {
pub style: ServoArc<ComputedValues>,
pub content_rect: Rect<Length>,
pub image_key: ImageKey,
}
impl AnonymousFragment { impl AnonymousFragment {
pub fn no_op(mode: (WritingMode, Direction)) -> Self { pub fn no_op(mode: (WritingMode, Direction)) -> Self {
Self { Self {

View file

@ -2,20 +2,77 @@
* License, v. 2.0. If a copy of the MPL was not distributed with this * 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/. */ * file, You can obtain one at https://mozilla.org/MPL/2.0/. */
use crate::context::LayoutContext;
use crate::dom_traversal::NodeExt; use crate::dom_traversal::NodeExt;
use crate::formatting_contexts::IndependentLayout;
use crate::fragments::{Fragment, ImageFragment};
use crate::geom::{flow_relative, physical};
use crate::positioned::AbsolutelyPositionedFragment;
use crate::ContainingBlock;
use net_traits::image::base::Image;
use servo_arc::Arc as ServoArc;
use std::sync::Arc;
use style::context::SharedStyleContext; use style::context::SharedStyleContext;
use style::properties::ComputedValues;
use style::values::computed::Length;
use style::Zero;
#[derive(Debug)] #[derive(Debug)]
pub(super) enum ReplacedContent { pub(crate) struct ReplacedContent {
// Not implemented yet pub kind: ReplacedContentKind,
pub intrinsic_size: physical::Vec2<Length>,
}
#[derive(Debug)]
pub(crate) enum ReplacedContentKind {
Image(Option<Arc<Image>>),
} }
impl ReplacedContent { impl ReplacedContent {
pub fn for_element<'dom, Node>(element: Node, _context: &SharedStyleContext) -> Option<Self> pub fn for_element<'dom>(element: impl NodeExt<'dom>) -> Option<Self> {
where if let Some((image, intrinsic_size)) = element.as_image() {
Node: NodeExt<'dom>, return Some(Self {
{ kind: ReplacedContentKind::Image(image),
// FIXME: implement <img> etc. intrinsic_size,
});
}
None None
} }
pub fn layout<'a>(
&'a self,
layout_context: &LayoutContext,
style: &ServoArc<ComputedValues>,
containing_block: &ContainingBlock,
) -> IndependentLayout {
let (fragments, content_block_size) = match self.kind {
ReplacedContentKind::Image(ref image) => {
// FIXME(nox): We should not assume block size is known.
let block_size = containing_block.block_size.non_auto().unwrap();
let fragments = image
.as_ref()
.and_then(|image| image.id)
.map(|image_key| {
Fragment::Image(ImageFragment {
style: style.clone(),
content_rect: flow_relative::Rect {
start_corner: flow_relative::Vec2::zero(),
size: flow_relative::Vec2 {
inline: containing_block.inline_size,
block: block_size,
},
},
image_key,
})
})
.into_iter()
.collect::<Vec<_>>();
(fragments, block_size)
},
};
IndependentLayout {
fragments,
content_block_size,
}
}
} }

View file

@ -72,9 +72,10 @@ ${helpers.single_keyword(
${helpers.single_keyword( ${helpers.single_keyword(
"image-rendering", "image-rendering",
"auto crisp-edges", "auto crisp-edges",
engines="gecko servo-2013", engines="gecko servo-2013 servo-2020",
extra_gecko_values="optimizespeed optimizequality", extra_gecko_values="optimizespeed optimizequality",
extra_servo_2013_values="pixelated", extra_servo_2013_values="pixelated",
extra_servo_2020_values="pixelated",
gecko_aliases="-moz-crisp-edges=crisp-edges", gecko_aliases="-moz-crisp-edges=crisp-edges",
animation_value_type="discrete", animation_value_type="discrete",
spec="https://drafts.csswg.org/css-images/#propdef-image-rendering", spec="https://drafts.csswg.org/css-images/#propdef-image-rendering",

View file

@ -1,2 +1,2 @@
[containing-block-007.xht] [containing-block-007.xht]
expected: CRASH expected: FAIL