LibWeb: Store outline-width in computed form in ComputedProperties

We now also store `outline-width` in ComputedValues as a `CSSPixels`
since we know it's an absolute length at `apply_style` time - this saves
us some work in converting to CSSPixels during layout.

Gains us 46 new passes since we now interpolate keywords (thick, thin,
etc) correctly.

Also loses us 4 WPT tests as we longer clamp negative values produced by
interpolation from the point of view of getComputedStyle (although the
'used' value is still clamped).
This commit is contained in:
Callum Law 2025-08-28 14:08:47 +12:00 committed by Sam Atkins
parent 6eae92511f
commit 0ce6cc38b7
7 changed files with 283 additions and 42 deletions

View File

@ -821,10 +821,6 @@ RefPtr<StyleValue const> CSSStyleProperties::style_value_for_computed_property(L
auto opacity = layout_node.computed_values().flood_opacity();
return NumberStyleValue::create(opacity);
}
case PropertyID::OutlineWidth: {
auto outline_width = layout_node.computed_values().outline_width();
return LengthStyleValue::create(outline_width);
}
case PropertyID::WebkitTextFillColor:
return resolve_color_style_value(get_computed_value(property_id), layout_node.computed_values().webkit_text_fill_color());
case PropertyID::Invalid:

View File

@ -223,7 +223,7 @@ public:
static Color outline_color() { return Color::Black; }
static CSS::Length outline_offset() { return CSS::Length::make_px(0); }
static CSS::OutlineStyle outline_style() { return CSS::OutlineStyle::None; }
static CSS::Length outline_width() { return CSS::Length::make_px(3); }
static CSSPixels outline_width() { return 3; }
static CSS::TableLayout table_layout() { return CSS::TableLayout::Auto; }
static QuotesData quotes() { return QuotesData { .type = QuotesData::Type::Auto }; }
static CSS::TransformBox transform_box() { return CSS::TransformBox::ViewBox; }
@ -626,7 +626,7 @@ public:
Color outline_color() const { return m_noninherited.outline_color; }
CSS::Length outline_offset() const { return m_noninherited.outline_offset; }
CSS::OutlineStyle outline_style() const { return m_noninherited.outline_style; }
CSS::Length outline_width() const { return m_noninherited.outline_width; }
CSSPixels outline_width() const { return m_noninherited.outline_width; }
CSS::TableLayout table_layout() const { return m_noninherited.table_layout; }
@ -801,7 +801,7 @@ protected:
Color outline_color { InitialValues::outline_color() };
CSS::Length outline_offset { InitialValues::outline_offset() };
CSS::OutlineStyle outline_style { InitialValues::outline_style() };
CSS::Length outline_width { InitialValues::outline_width() };
CSSPixels outline_width { InitialValues::outline_width() };
CSS::TableLayout table_layout { InitialValues::table_layout() };
CSS::ObjectFit object_fit { InitialValues::object_fit() };
CSS::ObjectPosition object_position { InitialValues::object_position() };
@ -1023,7 +1023,7 @@ public:
void set_outline_color(Color value) { m_noninherited.outline_color = value; }
void set_outline_offset(CSS::Length value) { m_noninherited.outline_offset = value; }
void set_outline_style(CSS::OutlineStyle value) { m_noninherited.outline_style = value; }
void set_outline_width(CSS::Length value) { m_noninherited.outline_width = value; }
void set_outline_width(CSSPixels value) { m_noninherited.outline_width = value; }
void set_mask(MaskReference value) { m_noninherited.mask = value; }
void set_mask_type(CSS::MaskType value) { m_noninherited.mask_type = value; }
void set_mask_image(CSS::AbstractImageStyleValue const& value) { m_noninherited.mask_image = value; }

View File

@ -3231,6 +3231,8 @@ NonnullRefPtr<StyleValue const> StyleComputer::compute_value_of_property(Propert
return compute_border_or_outline_width(specified_value, get_property_specified_value(PropertyID::BorderRightStyle), computation_context);
case PropertyID::BorderTopWidth:
return compute_border_or_outline_width(specified_value, get_property_specified_value(PropertyID::BorderTopStyle), computation_context);
case PropertyID::OutlineWidth:
return compute_border_or_outline_width(specified_value, get_property_specified_value(PropertyID::OutlineStyle), computation_context);
default:
// FIXME: We should replace this with a VERIFY_NOT_REACHED() once all properties have their own handling.
return specified_value;

View File

@ -802,32 +802,6 @@ void NodeWithStyle::apply_style(CSS::ComputedProperties const& computed_style)
computed_values.set_transition_delay(transition_delay.resolve_time_deprecated({ .length_resolution_context = CSS::Length::ResolutionContext::for_layout_node(*this) }).value());
}
auto resolve_border_width = [&](CSS::PropertyID width_property) -> CSSPixels {
auto const& value = computed_style.property(width_property);
if (value.is_calculated())
return max(CSSPixels { 0 },
value.as_calculated().resolve_length_deprecated({ .length_resolution_context = CSS::Length::ResolutionContext::for_layout_node(*this) })->to_px(*this));
if (value.is_length()) {
// FIXME: Currently, interpolation can set property values outside of their valid range.
// We should instead clamp property values to the valid range when interpolating.
return max(CSSPixels { 0 }, value.as_length().length().to_px(*this));
}
if (value.is_keyword()) {
// https://www.w3.org/TR/css-backgrounds-3/#valdef-line-width-thin
switch (value.to_keyword()) {
case CSS::Keyword::Thin:
return 1;
case CSS::Keyword::Medium:
return 3;
case CSS::Keyword::Thick:
return 5;
default:
VERIFY_NOT_REACHED();
}
}
VERIFY_NOT_REACHED();
};
auto do_border_style = [&](CSS::BorderData& border, CSS::PropertyID width_property, CSS::PropertyID color_property, CSS::PropertyID style_property) {
// FIXME: The default border color value is `currentcolor`, but since we can't resolve that easily,
// we just manually grab the value from `color`. This makes it dependent on `color` being
@ -850,12 +824,8 @@ void NodeWithStyle::apply_style(CSS::ComputedProperties const& computed_style)
computed_values.set_outline_offset(outline_offset.as_length().length());
computed_values.set_outline_style(computed_style.outline_style());
CSSPixels resolved_outline_width = 0;
if (computed_values.outline_style() != CSS::OutlineStyle::None)
resolved_outline_width = max(CSSPixels { 0 }, resolve_border_width(CSS::PropertyID::OutlineWidth));
auto snapped_outline_width = snap_a_length_as_a_border_width(document().page().client().device_pixels_per_css_pixel(), resolved_outline_width);
computed_values.set_outline_width(CSS::Length::make_px(snapped_outline_width));
// FIXME: Interpolation can cause negative values - we clamp here but should instead clamp as part of interpolation.
computed_values.set_outline_width(max(CSSPixels { 0 }, computed_style.length(CSS::PropertyID::OutlineWidth).absolute_length_to_px()));
computed_values.set_grid_auto_columns(computed_style.grid_auto_columns());
computed_values.set_grid_auto_rows(computed_style.grid_auto_rows());

View File

@ -1516,8 +1516,7 @@ void PaintableBox::resolve_paint_properties()
set_transform_origin({ x, y });
// Outlines
auto outline_width = computed_values.outline_width().to_px(layout_node);
auto outline_data = borders_data_for_outline(layout_node, computed_values.outline_color(), computed_values.outline_style(), outline_width);
auto outline_data = borders_data_for_outline(layout_node, computed_values.outline_color(), computed_values.outline_style(), computed_values.outline_width());
auto outline_offset = computed_values.outline_offset().to_px(layout_node);
set_outline_data(outline_data);
set_outline_offset(outline_offset);

View File

@ -0,0 +1,154 @@
Harness status: OK
Found 148 tests
132 Pass
16 Fail
Pass CSS Transitions: property <outline-width> from neutral to [20px] at (-0.3) should be [7px]
Pass CSS Transitions: property <outline-width> from neutral to [20px] at (0) should be [10px]
Pass CSS Transitions: property <outline-width> from neutral to [20px] at (0.3) should be [13px]
Pass CSS Transitions: property <outline-width> from neutral to [20px] at (0.6) should be [16px]
Pass CSS Transitions: property <outline-width> from neutral to [20px] at (1) should be [20px]
Pass CSS Transitions: property <outline-width> from neutral to [20px] at (1.5) should be [25px]
Pass CSS Transitions with transition: all: property <outline-width> from neutral to [20px] at (-0.3) should be [7px]
Pass CSS Transitions with transition: all: property <outline-width> from neutral to [20px] at (0) should be [10px]
Pass CSS Transitions with transition: all: property <outline-width> from neutral to [20px] at (0.3) should be [13px]
Pass CSS Transitions with transition: all: property <outline-width> from neutral to [20px] at (0.6) should be [16px]
Pass CSS Transitions with transition: all: property <outline-width> from neutral to [20px] at (1) should be [20px]
Pass CSS Transitions with transition: all: property <outline-width> from neutral to [20px] at (1.5) should be [25px]
Pass CSS Animations: property <outline-width> from neutral to [20px] at (-0.3) should be [7px]
Pass CSS Animations: property <outline-width> from neutral to [20px] at (0) should be [10px]
Pass CSS Animations: property <outline-width> from neutral to [20px] at (0.3) should be [13px]
Pass CSS Animations: property <outline-width> from neutral to [20px] at (0.6) should be [16px]
Pass CSS Animations: property <outline-width> from neutral to [20px] at (1) should be [20px]
Pass CSS Animations: property <outline-width> from neutral to [20px] at (1.5) should be [25px]
Pass Web Animations: property <outline-width> from neutral to [20px] at (-0.3) should be [7px]
Pass Web Animations: property <outline-width> from neutral to [20px] at (0) should be [10px]
Pass Web Animations: property <outline-width> from neutral to [20px] at (0.3) should be [13px]
Pass Web Animations: property <outline-width> from neutral to [20px] at (0.6) should be [16px]
Pass Web Animations: property <outline-width> from neutral to [20px] at (1) should be [20px]
Pass Web Animations: property <outline-width> from neutral to [20px] at (1.5) should be [25px]
Fail CSS Transitions: property <outline-width> from [initial] to [23px] at (-0.3) should be [0px]
Pass CSS Transitions: property <outline-width> from [initial] to [23px] at (0) should be [3px]
Pass CSS Transitions: property <outline-width> from [initial] to [23px] at (0.3) should be [9px]
Pass CSS Transitions: property <outline-width> from [initial] to [23px] at (0.6) should be [15px]
Pass CSS Transitions: property <outline-width> from [initial] to [23px] at (1) should be [23px]
Pass CSS Transitions: property <outline-width> from [initial] to [23px] at (1.5) should be [33px]
Fail CSS Transitions with transition: all: property <outline-width> from [initial] to [23px] at (-0.3) should be [0px]
Pass CSS Transitions with transition: all: property <outline-width> from [initial] to [23px] at (0) should be [3px]
Pass CSS Transitions with transition: all: property <outline-width> from [initial] to [23px] at (0.3) should be [9px]
Pass CSS Transitions with transition: all: property <outline-width> from [initial] to [23px] at (0.6) should be [15px]
Pass CSS Transitions with transition: all: property <outline-width> from [initial] to [23px] at (1) should be [23px]
Pass CSS Transitions with transition: all: property <outline-width> from [initial] to [23px] at (1.5) should be [33px]
Fail CSS Animations: property <outline-width> from [initial] to [23px] at (-0.3) should be [0px]
Pass CSS Animations: property <outline-width> from [initial] to [23px] at (0) should be [3px]
Pass CSS Animations: property <outline-width> from [initial] to [23px] at (0.3) should be [9px]
Pass CSS Animations: property <outline-width> from [initial] to [23px] at (0.6) should be [15px]
Pass CSS Animations: property <outline-width> from [initial] to [23px] at (1) should be [23px]
Pass CSS Animations: property <outline-width> from [initial] to [23px] at (1.5) should be [33px]
Fail Web Animations: property <outline-width> from [initial] to [23px] at (-0.3) should be [0px]
Pass Web Animations: property <outline-width> from [initial] to [23px] at (0) should be [3px]
Pass Web Animations: property <outline-width> from [initial] to [23px] at (0.3) should be [9px]
Pass Web Animations: property <outline-width> from [initial] to [23px] at (0.6) should be [15px]
Pass Web Animations: property <outline-width> from [initial] to [23px] at (1) should be [23px]
Pass Web Animations: property <outline-width> from [initial] to [23px] at (1.5) should be [33px]
Pass CSS Transitions: property <outline-width> from [inherit] to [20px] at (-0.3) should be [33px]
Pass CSS Transitions: property <outline-width> from [inherit] to [20px] at (0) should be [30px]
Pass CSS Transitions: property <outline-width> from [inherit] to [20px] at (0.3) should be [27px]
Pass CSS Transitions: property <outline-width> from [inherit] to [20px] at (0.6) should be [24px]
Pass CSS Transitions: property <outline-width> from [inherit] to [20px] at (1) should be [20px]
Pass CSS Transitions: property <outline-width> from [inherit] to [20px] at (1.5) should be [15px]
Pass CSS Transitions with transition: all: property <outline-width> from [inherit] to [20px] at (-0.3) should be [33px]
Pass CSS Transitions with transition: all: property <outline-width> from [inherit] to [20px] at (0) should be [30px]
Pass CSS Transitions with transition: all: property <outline-width> from [inherit] to [20px] at (0.3) should be [27px]
Pass CSS Transitions with transition: all: property <outline-width> from [inherit] to [20px] at (0.6) should be [24px]
Pass CSS Transitions with transition: all: property <outline-width> from [inherit] to [20px] at (1) should be [20px]
Pass CSS Transitions with transition: all: property <outline-width> from [inherit] to [20px] at (1.5) should be [15px]
Pass CSS Animations: property <outline-width> from [inherit] to [20px] at (-0.3) should be [33px]
Pass CSS Animations: property <outline-width> from [inherit] to [20px] at (0) should be [30px]
Pass CSS Animations: property <outline-width> from [inherit] to [20px] at (0.3) should be [27px]
Pass CSS Animations: property <outline-width> from [inherit] to [20px] at (0.6) should be [24px]
Pass CSS Animations: property <outline-width> from [inherit] to [20px] at (1) should be [20px]
Pass CSS Animations: property <outline-width> from [inherit] to [20px] at (1.5) should be [15px]
Pass Web Animations: property <outline-width> from [inherit] to [20px] at (-0.3) should be [33px]
Pass Web Animations: property <outline-width> from [inherit] to [20px] at (0) should be [30px]
Pass Web Animations: property <outline-width> from [inherit] to [20px] at (0.3) should be [27px]
Pass Web Animations: property <outline-width> from [inherit] to [20px] at (0.6) should be [24px]
Pass Web Animations: property <outline-width> from [inherit] to [20px] at (1) should be [20px]
Pass Web Animations: property <outline-width> from [inherit] to [20px] at (1.5) should be [15px]
Fail CSS Transitions: property <outline-width> from [unset] to [23px] at (-0.3) should be [0px]
Pass CSS Transitions: property <outline-width> from [unset] to [23px] at (0) should be [3px]
Pass CSS Transitions: property <outline-width> from [unset] to [23px] at (0.3) should be [9px]
Pass CSS Transitions: property <outline-width> from [unset] to [23px] at (0.6) should be [15px]
Pass CSS Transitions: property <outline-width> from [unset] to [23px] at (1) should be [23px]
Pass CSS Transitions: property <outline-width> from [unset] to [23px] at (1.5) should be [33px]
Fail CSS Transitions with transition: all: property <outline-width> from [unset] to [23px] at (-0.3) should be [0px]
Pass CSS Transitions with transition: all: property <outline-width> from [unset] to [23px] at (0) should be [3px]
Pass CSS Transitions with transition: all: property <outline-width> from [unset] to [23px] at (0.3) should be [9px]
Pass CSS Transitions with transition: all: property <outline-width> from [unset] to [23px] at (0.6) should be [15px]
Pass CSS Transitions with transition: all: property <outline-width> from [unset] to [23px] at (1) should be [23px]
Pass CSS Transitions with transition: all: property <outline-width> from [unset] to [23px] at (1.5) should be [33px]
Fail CSS Animations: property <outline-width> from [unset] to [23px] at (-0.3) should be [0px]
Pass CSS Animations: property <outline-width> from [unset] to [23px] at (0) should be [3px]
Pass CSS Animations: property <outline-width> from [unset] to [23px] at (0.3) should be [9px]
Pass CSS Animations: property <outline-width> from [unset] to [23px] at (0.6) should be [15px]
Pass CSS Animations: property <outline-width> from [unset] to [23px] at (1) should be [23px]
Pass CSS Animations: property <outline-width> from [unset] to [23px] at (1.5) should be [33px]
Fail Web Animations: property <outline-width> from [unset] to [23px] at (-0.3) should be [0px]
Pass Web Animations: property <outline-width> from [unset] to [23px] at (0) should be [3px]
Pass Web Animations: property <outline-width> from [unset] to [23px] at (0.3) should be [9px]
Pass Web Animations: property <outline-width> from [unset] to [23px] at (0.6) should be [15px]
Pass Web Animations: property <outline-width> from [unset] to [23px] at (1) should be [23px]
Pass Web Animations: property <outline-width> from [unset] to [23px] at (1.5) should be [33px]
Fail CSS Transitions: property <outline-width> from [0px] to [10px] at (-0.3) should be [0px]
Pass CSS Transitions: property <outline-width> from [0px] to [10px] at (0) should be [0px]
Pass CSS Transitions: property <outline-width> from [0px] to [10px] at (0.3) should be [3px]
Pass CSS Transitions: property <outline-width> from [0px] to [10px] at (0.6) should be [6px]
Pass CSS Transitions: property <outline-width> from [0px] to [10px] at (1) should be [10px]
Pass CSS Transitions: property <outline-width> from [0px] to [10px] at (1.5) should be [15px]
Fail CSS Transitions with transition: all: property <outline-width> from [0px] to [10px] at (-0.3) should be [0px]
Pass CSS Transitions with transition: all: property <outline-width> from [0px] to [10px] at (0) should be [0px]
Pass CSS Transitions with transition: all: property <outline-width> from [0px] to [10px] at (0.3) should be [3px]
Pass CSS Transitions with transition: all: property <outline-width> from [0px] to [10px] at (0.6) should be [6px]
Pass CSS Transitions with transition: all: property <outline-width> from [0px] to [10px] at (1) should be [10px]
Pass CSS Transitions with transition: all: property <outline-width> from [0px] to [10px] at (1.5) should be [15px]
Fail CSS Animations: property <outline-width> from [0px] to [10px] at (-0.3) should be [0px]
Pass CSS Animations: property <outline-width> from [0px] to [10px] at (0) should be [0px]
Pass CSS Animations: property <outline-width> from [0px] to [10px] at (0.3) should be [3px]
Pass CSS Animations: property <outline-width> from [0px] to [10px] at (0.6) should be [6px]
Pass CSS Animations: property <outline-width> from [0px] to [10px] at (1) should be [10px]
Pass CSS Animations: property <outline-width> from [0px] to [10px] at (1.5) should be [15px]
Fail Web Animations: property <outline-width> from [0px] to [10px] at (-0.3) should be [0px]
Pass Web Animations: property <outline-width> from [0px] to [10px] at (0) should be [0px]
Pass Web Animations: property <outline-width> from [0px] to [10px] at (0.3) should be [3px]
Pass Web Animations: property <outline-width> from [0px] to [10px] at (0.6) should be [6px]
Pass Web Animations: property <outline-width> from [0px] to [10px] at (1) should be [10px]
Pass Web Animations: property <outline-width> from [0px] to [10px] at (1.5) should be [15px]
Fail CSS Transitions: property <outline-width> from [thick] to [15px] at (-2) should be [0px]
Pass CSS Transitions: property <outline-width> from [thick] to [15px] at (-0.3) should be [2px]
Pass CSS Transitions: property <outline-width> from [thick] to [15px] at (0) should be [5px]
Pass CSS Transitions: property <outline-width> from [thick] to [15px] at (0.3) should be [8px]
Pass CSS Transitions: property <outline-width> from [thick] to [15px] at (0.6) should be [11px]
Pass CSS Transitions: property <outline-width> from [thick] to [15px] at (1) should be [15px]
Pass CSS Transitions: property <outline-width> from [thick] to [15px] at (1.5) should be [20px]
Fail CSS Transitions with transition: all: property <outline-width> from [thick] to [15px] at (-2) should be [0px]
Pass CSS Transitions with transition: all: property <outline-width> from [thick] to [15px] at (-0.3) should be [2px]
Pass CSS Transitions with transition: all: property <outline-width> from [thick] to [15px] at (0) should be [5px]
Pass CSS Transitions with transition: all: property <outline-width> from [thick] to [15px] at (0.3) should be [8px]
Pass CSS Transitions with transition: all: property <outline-width> from [thick] to [15px] at (0.6) should be [11px]
Pass CSS Transitions with transition: all: property <outline-width> from [thick] to [15px] at (1) should be [15px]
Pass CSS Transitions with transition: all: property <outline-width> from [thick] to [15px] at (1.5) should be [20px]
Fail CSS Animations: property <outline-width> from [thick] to [15px] at (-2) should be [0px]
Pass CSS Animations: property <outline-width> from [thick] to [15px] at (-0.3) should be [2px]
Pass CSS Animations: property <outline-width> from [thick] to [15px] at (0) should be [5px]
Pass CSS Animations: property <outline-width> from [thick] to [15px] at (0.3) should be [8px]
Pass CSS Animations: property <outline-width> from [thick] to [15px] at (0.6) should be [11px]
Pass CSS Animations: property <outline-width> from [thick] to [15px] at (1) should be [15px]
Pass CSS Animations: property <outline-width> from [thick] to [15px] at (1.5) should be [20px]
Fail Web Animations: property <outline-width> from [thick] to [15px] at (-2) should be [0px]
Pass Web Animations: property <outline-width> from [thick] to [15px] at (-0.3) should be [2px]
Pass Web Animations: property <outline-width> from [thick] to [15px] at (0) should be [5px]
Pass Web Animations: property <outline-width> from [thick] to [15px] at (0.3) should be [8px]
Pass Web Animations: property <outline-width> from [thick] to [15px] at (0.6) should be [11px]
Pass Web Animations: property <outline-width> from [thick] to [15px] at (1) should be [15px]
Pass Web Animations: property <outline-width> from [thick] to [15px] at (1.5) should be [20px]

View File

@ -0,0 +1,120 @@
<!DOCTYPE html>
<meta charset="UTF-8">
<title>outline-width interpolation</title>
<link rel="help" href="https://drafts.csswg.org/css-ui-3/#outline-width">
<meta name="assert" content="outline-width supports animation by computed value">
<script src="../../../resources/testharness.js"></script>
<script src="../../../resources/testharnessreport.js"></script>
<script src="../../../css/support/interpolation-testcommon.js"></script>
<style>
.parent {
outline: solid transparent;
outline-width: 30px;
}
.target {
width: 50px;
height: 50px;
background-color: black;
display: inline-block;
margin: 18px;
outline: solid orange;
outline-width: 10px;
opacity: 0.5;
}
.expected {
background-color: green;
}
</style>
<body></body>
<script>
// NOTE: The below tests make assumptions about the values of medium (for unset
// and initial) and thick, namely that:
// * medium=3px
// * thick=3px
//
// A better version of these tests would dynamically generate the expected values
// by querying the computed style from the UA.
test_interpolation({
property: 'outline-width',
from: neutralKeyframe,
to: '20px',
}, [
{at: -0.3, expect: '7px'},
{at: 0, expect: '10px'},
{at: 0.3, expect: '13px'},
{at: 0.6, expect: '16px'},
{at: 1, expect: '20px'},
{at: 1.5, expect: '25px'},
]);
test_interpolation({
property: 'outline-width',
from: 'initial',
to: '23px',
}, [
{at: -0.3, expect: '0px'},
{at: 0, expect: '3px'},
{at: 0.3, expect: '9px'},
{at: 0.6, expect: '15px'},
{at: 1, expect: '23px'},
{at: 1.5, expect: '33px'},
]);
test_interpolation({
property: 'outline-width',
from: 'inherit',
to: '20px',
}, [
{at: -0.3, expect: '33px'},
{at: 0, expect: '30px'},
{at: 0.3, expect: '27px'},
{at: 0.6, expect: '24px'},
{at: 1, expect: '20px'},
{at: 1.5, expect: '15px'},
]);
test_interpolation({
property: 'outline-width',
from: 'unset',
to: '23px',
}, [
{at: -0.3, expect: '0px'},
{at: 0, expect: '3px'},
{at: 0.3, expect: '9px'},
{at: 0.6, expect: '15px'},
{at: 1, expect: '23px'},
{at: 1.5, expect: '33px'},
]);
test_interpolation({
property: 'outline-width',
from: '0px',
to: '10px',
}, [
{at: -0.3, expect: '0px'}, // CSS outline-width can't be negative.
{at: 0, expect: '0px'},
{at: 0.3, expect: '3px'},
{at: 0.6, expect: '6px'},
{at: 1, expect: '10px'},
{at: 1.5, expect: '15px'}
]);
test_interpolation({
property: 'outline-width',
from: 'thick',
to: '15px',
}, [
{at: -2, expect: '0px'}, // CSS outline-width can't be negative.
{at: -0.3, expect: '2px'},
{at: 0, expect: '5px'},
{at: 0.3, expect: '8px'},
{at: 0.6, expect: '11px'},
{at: 1, expect: '15px'},
{at: 1.5, expect: '20px'}
]);
</script>