LibWeb: Implement CSS perspective-origin

This commit is contained in:
Psychpsyo 2025-11-12 21:47:17 +01:00 committed by Sam Atkins
parent 27e4793c5d
commit 2db3796fd3
Notes: github-actions[bot] 2025-11-21 11:15:32 +00:00
31 changed files with 547 additions and 42 deletions

View file

@ -751,6 +751,11 @@ Optional<CSSPixels> ComputedProperties::perspective() const
VERIFY_NOT_REACHED(); VERIFY_NOT_REACHED();
} }
Position ComputedProperties::perspective_origin() const
{
return position_value(PropertyID::PerspectiveOrigin);
}
TransformOrigin ComputedProperties::transform_origin() const TransformOrigin ComputedProperties::transform_origin() const
{ {
auto length_percentage_with_keywords_resolved = [](StyleValue const& value) -> LengthPercentage { auto length_percentage_with_keywords_resolved = [](StyleValue const& value) -> LengthPercentage {

View file

@ -213,6 +213,7 @@ public:
Optional<Transformation> translate() const; Optional<Transformation> translate() const;
Optional<Transformation> scale() const; Optional<Transformation> scale() const;
Optional<CSSPixels> perspective() const; Optional<CSSPixels> perspective() const;
Position perspective_origin() const;
MaskType mask_type() const; MaskType mask_type() const;
float stop_opacity() const; float stop_opacity() const;

View file

@ -68,6 +68,17 @@ struct Position {
LengthPercentage offset_x { Percentage(50) }; LengthPercentage offset_x { Percentage(50) };
PositionEdge edge_y { PositionEdge::Top }; PositionEdge edge_y { PositionEdge::Top };
LengthPercentage offset_y { Percentage(50) }; LengthPercentage offset_y { Percentage(50) };
CSSPixelPoint resolved(Layout::Node const& node, CSSPixelRect const& rect) const
{
CSSPixels x = offset_x.to_px(node, rect.width());
CSSPixels y = offset_y.to_px(node, rect.height());
if (edge_x == PositionEdge::Right)
x = rect.width() - x;
if (edge_y == PositionEdge::Bottom)
y = rect.height() - y;
return CSSPixelPoint { rect.x() + x, rect.y() + y };
}
}; };
// https://drafts.csswg.org/css-contain-2/#containment-types // https://drafts.csswg.org/css-contain-2/#containment-types
@ -645,6 +656,7 @@ public:
Optional<Transformation> const& translate() const { return m_noninherited.translate; } Optional<Transformation> const& translate() const { return m_noninherited.translate; }
Optional<Transformation> const& scale() const { return m_noninherited.scale; } Optional<Transformation> const& scale() const { return m_noninherited.scale; }
Optional<CSSPixels> const& perspective() const { return m_noninherited.perspective; } Optional<CSSPixels> const& perspective() const { return m_noninherited.perspective; }
Position const& perspective_origin() const { return m_noninherited.perspective_origin; }
Gfx::FontCascadeList const& font_list() const { return *m_inherited.font_list; } Gfx::FontCascadeList const& font_list() const { return *m_inherited.font_list; }
CSSPixels font_size() const { return m_inherited.font_size; } CSSPixels font_size() const { return m_inherited.font_size; }
@ -848,6 +860,7 @@ protected:
Optional<Transformation> translate; Optional<Transformation> translate;
Optional<Transformation> scale; Optional<Transformation> scale;
Optional<CSSPixels> perspective; Optional<CSSPixels> perspective;
Position perspective_origin;
Optional<MaskReference> mask; Optional<MaskReference> mask;
MaskType mask_type { InitialValues::mask_type() }; MaskType mask_type { InitialValues::mask_type() };
@ -1001,6 +1014,7 @@ public:
void set_rotate(Transformation value) { m_noninherited.rotate = move(value); } void set_rotate(Transformation value) { m_noninherited.rotate = move(value); }
void set_scale(Transformation value) { m_noninherited.scale = move(value); } void set_scale(Transformation value) { m_noninherited.scale = move(value); }
void set_perspective(Optional<CSSPixels> value) { m_noninherited.perspective = move(value); } void set_perspective(Optional<CSSPixels> value) { m_noninherited.perspective = move(value); }
void set_perspective_origin(Position value) { m_noninherited.perspective_origin = move(value); }
void set_transformations(Vector<Transformation> value) { m_noninherited.transformations = move(value); } void set_transformations(Vector<Transformation> value) { m_noninherited.transformations = move(value); }
void set_transform_box(TransformBox value) { m_noninherited.transform_box = value; } void set_transform_box(TransformBox value) { m_noninherited.transform_box = value; }
void set_transform_origin(TransformOrigin value) { m_noninherited.transform_origin = move(value); } void set_transform_origin(TransformOrigin value) { m_noninherited.transform_origin = move(value); }

View file

@ -3226,6 +3226,16 @@
"length [0,∞]" "length [0,∞]"
] ]
}, },
"perspective-origin": {
"animation-type": "by-computed-value",
"affects-layout": false,
"inherited": false,
"initial": "50% 50%",
"valid-types": [
"position"
],
"percentages-resolve-to": "length"
},
"place-content": { "place-content": {
"inherited": false, "inherited": false,
"initial": "normal", "initial": "normal",

View file

@ -698,8 +698,9 @@ void NodeWithStyle::apply_style(CSS::ComputedProperties const& computed_style)
computed_values.set_transformations(computed_style.transformations()); computed_values.set_transformations(computed_style.transformations());
computed_values.set_transform_box(computed_style.transform_box()); computed_values.set_transform_box(computed_style.transform_box());
computed_values.set_transform_origin(computed_style.transform_origin()); computed_values.set_transform_origin(computed_style.transform_origin());
computed_values.set_perspective(computed_style.perspective());
computed_values.set_transform_style(computed_style.transform_style()); computed_values.set_transform_style(computed_style.transform_style());
computed_values.set_perspective(computed_style.perspective());
computed_values.set_perspective_origin(computed_style.perspective_origin());
auto const& transition_delay_property = computed_style.property(CSS::PropertyID::TransitionDelay); auto const& transition_delay_property = computed_style.property(CSS::PropertyID::TransitionDelay);
if (transition_delay_property.is_time()) { if (transition_delay_property.is_time()) {

View file

@ -230,6 +230,8 @@ void DisplayListPlayerSkia::push_stacking_context(PushStackingContext const& com
auto new_transform = Gfx::translation_matrix(Vector3<float>(command.transform.origin.x(), command.transform.origin.y(), 0)); auto new_transform = Gfx::translation_matrix(Vector3<float>(command.transform.origin.x(), command.transform.origin.y(), 0));
new_transform = new_transform * command.transform.matrix; new_transform = new_transform * command.transform.matrix;
new_transform = new_transform * Gfx::translation_matrix(Vector3<float>(-command.transform.origin.x(), -command.transform.origin.y(), 0)); new_transform = new_transform * Gfx::translation_matrix(Vector3<float>(-command.transform.origin.x(), -command.transform.origin.y(), 0));
if (command.transform.parent_perspective_matrix.has_value())
new_transform = command.transform.parent_perspective_matrix.value() * new_transform;
auto matrix = to_skia_matrix4x4(new_transform); auto matrix = to_skia_matrix4x4(new_transform);
surface().canvas().save(); surface().canvas().save();

View file

@ -12,13 +12,14 @@
namespace Web::Painting { namespace Web::Painting {
StackingContextTransform::StackingContextTransform(Gfx::FloatPoint origin, Gfx::FloatMatrix4x4 matrix, float scale) StackingContextTransform::StackingContextTransform(Gfx::FloatPoint origin, Gfx::FloatMatrix4x4 matrix, Optional<Gfx::FloatMatrix4x4> parent_perspective_matrix, float scale)
{ {
this->origin = origin.scaled(scale); this->origin = origin.scaled(scale);
matrix[0, 3] *= scale; matrix[0, 3] *= scale;
matrix[1, 3] *= scale; matrix[1, 3] *= scale;
matrix[2, 3] *= scale; matrix[2, 3] *= scale;
this->matrix = matrix; this->matrix = matrix;
this->parent_perspective_matrix = parent_perspective_matrix;
} }
DisplayListRecorder::DisplayListRecorder(DisplayList& command_list) DisplayListRecorder::DisplayListRecorder(DisplayList& command_list)

View file

@ -33,8 +33,9 @@ namespace Web::Painting {
struct StackingContextTransform { struct StackingContextTransform {
Gfx::FloatPoint origin; Gfx::FloatPoint origin;
Gfx::FloatMatrix4x4 matrix; Gfx::FloatMatrix4x4 matrix;
Optional<Gfx::FloatMatrix4x4> parent_perspective_matrix;
StackingContextTransform(Gfx::FloatPoint origin, Gfx::FloatMatrix4x4 matrix, float scale); StackingContextTransform(Gfx::FloatPoint origin, Gfx::FloatMatrix4x4 matrix, Optional<Gfx::FloatMatrix4x4> parent_perspective_matrix, float scale);
[[nodiscard]] bool is_identity() const { return matrix.is_identity(); } [[nodiscard]] bool is_identity() const { return matrix.is_identity(); }
}; };

View file

@ -182,12 +182,15 @@ void Paintable::paint_inspector_overlay(DisplayListRecordingContext& context) co
auto to_device_pixels_scale = float(context.device_pixels_per_css_pixel()); auto to_device_pixels_scale = float(context.device_pixels_per_css_pixel());
auto transform_matrix = box->transform(); auto transform_matrix = box->transform();
auto transform_origin = box->transform_origin().to_type<float>(); auto transform_origin = box->transform_origin().to_type<float>();
Optional<Gfx::FloatMatrix4x4> parent_perspective_matrix;
if (auto const* parent = as_if<PaintableBox>(box->parent()))
parent_perspective_matrix = parent->perspective_matrix();
// We only want the transform here, everything else undesirable for the inspector overlay // We only want the transform here, everything else undesirable for the inspector overlay
DisplayListRecorder::PushStackingContextParams push_stacking_context_params { DisplayListRecorder::PushStackingContextParams push_stacking_context_params {
.opacity = 1.0, .opacity = 1.0,
.compositing_and_blending_operator = Gfx::CompositingAndBlendingOperator::Normal, .compositing_and_blending_operator = Gfx::CompositingAndBlendingOperator::Normal,
.isolate = false, .isolate = false,
.transform = StackingContextTransform(transform_origin, transform_matrix, to_device_pixels_scale), .transform = StackingContextTransform(transform_origin, transform_matrix, parent_perspective_matrix, to_device_pixels_scale),
}; };
context.display_list_recorder().push_stacking_context(push_stacking_context_params); context.display_list_recorder().push_stacking_context(push_stacking_context_params);
} }

View file

@ -1553,41 +1553,33 @@ void PaintableBox::resolve_paint_properties()
matrix = matrix * transform.to_matrix(*this).release_value(); matrix = matrix * transform.to_matrix(*this).release_value();
set_transform(matrix); set_transform(matrix);
// https://drafts.csswg.org/css-transforms-2/#perspective
auto const& perspective = computed_values.perspective();
if (perspective.has_value()) {
// The perspective matrix is computed as follows:
// 1. Start with the identity matrix.
m_perspective_matrix = Gfx::FloatMatrix4x4::identity();
// 2. Translate by the computed X and Y values of 'perspective-origin'
// FIXME: Implement this.
// 3. Multiply by the matrix that would be obtained from the 'perspective()' transform function, where the
// length is provided by the value of the perspective property
auto perspective_value = perspective.value();
// https://drafts.csswg.org/css-transforms-2/#perspective-property
// As very small <length> values can produce bizarre rendering results and stress the numerical accuracy
// of transform calculations, values less than '1px' must be treated as '1px' for rendering purposes. (This
// clamping does not affect the underlying value, so 'perspective: 0;' in a stylesheet will still serialize back
// as '0'.)
if (perspective_value < 1)
perspective_value = 1;
m_perspective_matrix = m_perspective_matrix * CSS::Transformation(CSS::TransformFunction::Perspective, Vector<CSS::TransformValue>({ CSS::TransformValue(CSS::Length::make_px(perspective_value)) })).to_matrix(*this).release_value();
// 4. Translate by the negated computed X and Y values of 'perspective-origin'
// FIXME: Implement this.
}
auto const& transform_origin = computed_values.transform_origin(); auto const& transform_origin = computed_values.transform_origin();
auto reference_box = transform_reference_box(); auto reference_box = transform_reference_box();
auto x = reference_box.left() + transform_origin.x.to_px(layout_node, reference_box.width()); auto x = reference_box.left() + transform_origin.x.to_px(layout_node, reference_box.width());
auto y = reference_box.top() + transform_origin.y.to_px(layout_node, reference_box.height()); auto y = reference_box.top() + transform_origin.y.to_px(layout_node, reference_box.height());
set_transform_origin({ x, y }); set_transform_origin({ x, y });
set_transform_origin({ x, y });
// https://drafts.csswg.org/css-transforms-2/#perspective-matrix
if (auto perspective = computed_values.perspective(); perspective.has_value()) {
// The perspective matrix is computed as follows:
// 1. Start with the identity matrix.
// 2. Translate by the computed X and Y values of 'perspective-origin'
// https://drafts.csswg.org/css-transforms-2/#perspective-origin-property
// Percentages: refer to the size of the reference box
auto perspective_origin = computed_values.perspective_origin().resolved(layout_node, reference_box).to_type<float>();
auto computed_x = perspective_origin.x();
auto computed_y = perspective_origin.y();
m_perspective_matrix = Gfx::translation_matrix(Vector3<float>(computed_x, computed_y, 0));
// 3. Multiply by the matrix that would be obtained from the 'perspective()' transform function, where the
// length is provided by the value of the perspective property
// NB: Length values less than 1px being clamped to 1px is handled by the perspective() function already.
m_perspective_matrix = m_perspective_matrix.value() * CSS::Transformation(CSS::TransformFunction::Perspective, Vector<CSS::TransformValue>({ CSS::TransformValue(CSS::Length::make_px(perspective.value())) })).to_matrix({}).release_value();
// 4. Translate by the negated computed X and Y values of 'perspective-origin'
m_perspective_matrix = m_perspective_matrix.value() * Gfx::translation_matrix(Vector3<float>(-computed_x, -computed_y, 0));
}
// Outlines // Outlines
auto outline_data = borders_data_for_outline(layout_node, computed_values.outline_color(), computed_values.outline_style(), computed_values.outline_width()); auto outline_data = borders_data_for_outline(layout_node, computed_values.outline_color(), computed_values.outline_style(), computed_values.outline_width());

View file

@ -201,8 +201,8 @@ public:
void set_transform(Gfx::FloatMatrix4x4 transform) { m_transform = transform; } void set_transform(Gfx::FloatMatrix4x4 transform) { m_transform = transform; }
Gfx::FloatMatrix4x4 const& transform() const { return m_transform; } Gfx::FloatMatrix4x4 const& transform() const { return m_transform; }
void set_perspective_matrix(Gfx::FloatMatrix4x4 perspective_matrix) { m_perspective_matrix = perspective_matrix; } void set_perspective_matrix(Optional<Gfx::FloatMatrix4x4> perspective_matrix) { m_perspective_matrix = perspective_matrix; }
Gfx::FloatMatrix4x4 const& perspective_matrix() const { return m_perspective_matrix; } Optional<Gfx::FloatMatrix4x4> const& perspective_matrix() const { return m_perspective_matrix; }
void set_transform_origin(CSSPixelPoint transform_origin) { m_transform_origin = transform_origin; } void set_transform_origin(CSSPixelPoint transform_origin) { m_transform_origin = transform_origin; }
CSSPixelPoint const& transform_origin() const { return m_transform_origin; } CSSPixelPoint const& transform_origin() const { return m_transform_origin; }
@ -331,8 +331,8 @@ private:
BorderRadiiData m_border_radii_data; BorderRadiiData m_border_radii_data;
Vector<ShadowData> m_box_shadow_data; Vector<ShadowData> m_box_shadow_data;
Gfx::FloatMatrix4x4 m_transform { Gfx::FloatMatrix4x4::identity() }; Gfx::FloatMatrix4x4 m_transform { Gfx::FloatMatrix4x4::identity() };
Gfx::FloatMatrix4x4 m_perspective_matrix { Gfx::FloatMatrix4x4::identity() };
CSSPixelPoint m_transform_origin; CSSPixelPoint m_transform_origin;
Optional<Gfx::FloatMatrix4x4> m_perspective_matrix {};
Optional<BordersData> m_outline_data; Optional<BordersData> m_outline_data;
CSSPixels m_outline_offset { 0 }; CSSPixels m_outline_offset { 0 };

View file

@ -310,8 +310,9 @@ void StackingContext::paint(DisplayListRecordingContext& context) const
// Second, the 'perspective' and 'perspective-origin' properties can be applied to an element to influence the // Second, the 'perspective' and 'perspective-origin' properties can be applied to an element to influence the
// rendering of its 3d-transformed children, giving them a shared perspective that provides the impression of // rendering of its 3d-transformed children, giving them a shared perspective that provides the impression of
// them living in the same three-dimensional scene. // them living in the same three-dimensional scene.
Optional<Gfx::FloatMatrix4x4> parent_perspective_matrix;
if (auto const* parent = as_if<PaintableBox>(paintable_box().parent())) if (auto const* parent = as_if<PaintableBox>(paintable_box().parent()))
transform_matrix = parent->perspective_matrix() * transform_matrix; parent_perspective_matrix = parent->perspective_matrix();
auto transform_origin = paintable_box().transform_origin().to_type<float>(); auto transform_origin = paintable_box().transform_origin().to_type<float>();
@ -321,7 +322,7 @@ void StackingContext::paint(DisplayListRecordingContext& context) const
.opacity = opacity, .opacity = opacity,
.compositing_and_blending_operator = compositing_and_blending_operator, .compositing_and_blending_operator = compositing_and_blending_operator,
.isolate = paintable_box().computed_values().isolation() == CSS::Isolation::Isolate, .isolate = paintable_box().computed_values().isolation() == CSS::Isolation::Isolate,
.transform = StackingContextTransform(transform_origin, transform_matrix, to_device_pixels_scale), .transform = StackingContextTransform(transform_origin, transform_matrix, parent_perspective_matrix, to_device_pixels_scale),
}; };
auto const& computed_values = paintable_box().computed_values(); auto const& computed_values = paintable_box().computed_values();

View file

@ -0,0 +1,30 @@
<!DOCTYPE html>
<html>
<head>
<title>Reference File</title>
<link rel="author" title="Andres Ugarte" href="mailto:anduga@gmail.com">
<style type="text/css">
.container {
position: absolute;
width: 150px;
height: 150px;
top: 100px;
left: 100px;
}
.greenSquare {
position: absolute;
top: 0px;
left: 0px;
width: 150px;
height: 150px;
background: green;
}
</style>
</head>
<body>
<p>The test passes if there is a green square and no red.</p>
<div class="container">
<div class="greenSquare"></div>
</div>
</body>
</html>

View file

@ -0,0 +1,30 @@
<!DOCTYPE html>
<html>
<head>
<title>Reference File</title>
<link rel="author" title="Andres Ugarte" href="mailto:anduga@gmail.com">
<style type="text/css">
.container {
position: absolute;
width: 150px;
height: 150px;
top: 100px;
left: 100px;
}
.greenSquare {
position: absolute;
top: 0px;
left: 0px;
width: 150px;
height: 150px;
background: green;
}
</style>
</head>
<body>
<p>The test passes if there is a green square and no red.</p>
<div class="container">
<div class="greenSquare"></div>
</div>
</body>
</html>

View file

@ -0,0 +1,16 @@
<!DOCTYPE html>
<html>
<head>
<title>CSS Reftest Reference</title>
<link rel="author" title="Matt Woodrow" href="mailto:mwoodrow@mozilla.com">
<link rel="author" title="Aryeh Gregor" href="mailto:ayg@aryeh.name">
<link rel="mismatch" href="transform3d-rotatex-ref.html">
</head>
<body>
<div style="perspective: 1000px; perspective-origin: top left;">
<div style="transform: rotatex(45deg); width: 100px; height: 100px;
background: lime">
</div>
</div>
</body>
</html>

View file

@ -0,0 +1,39 @@
<!DOCTYPE html>
<meta charset="utf-8">
<title>CSS Transforms Test: perspective-origin - 0px center('center' computes to '50%' in vertical position)</title>
<link rel="author" title="Intel" href="http://www.intel.com">
<link rel="author" title="Jieqiong Cui" href="mailto:jieqiongx.cui@intel.com">
<link rel="help" title="11. The 'perspective-origin' Property" href="http://www.w3.org/TR/css-transforms-2/#propdef-perspective-origin">
<link rel="match" href="../../../../expected/wpt-import/css/css-transforms/../reference/ref-filled-green-100px-square.xht">
<meta name="assert" content="The 'perspective-origin' property set 'center' computes to 50% for the vertical position.">
<style>
div {
height: 100px;
position: absolute;
width: 100px;
}
#test {
perspective: 2px;
perspective-origin: 0px center;
}
#redSquare {
background-color: red;
transform: translateZ(0px);
}
#ref {
perspective: 2px;
perspective-origin: 0px 50%;
}
#greenSquare {
background-color: green;
height: 50px;
top: 25px;
transform: translateZ(1px);
width: 50px;
}
</style>
<body>
<p>Test passes if there is a filled green square and <strong>no red</strong>.</p>
<div id="test"><div id="redSquare"></div></div>
<div id="ref"><div id="greenSquare"></div></div>
</body>

View file

@ -0,0 +1,39 @@
<!DOCTYPE html>
<meta charset="utf-8">
<title>CSS Transforms Test: perspective-origin - center 0px('center' computes to '50%' in horizontal position)</title>
<link rel="author" title="Intel" href="http://www.intel.com">
<link rel="author" title="Jieqiong Cui" href="mailto:jieqiongx.cui@intel.com">
<link rel="help" title="11. The 'perspective-origin' Property" href="http://www.w3.org/TR/css-transforms-2/#propdef-perspective-origin">
<link rel="match" href="../../../../expected/wpt-import/css/css-transforms/../reference/ref-filled-green-100px-square.xht">
<meta name="assert" content="The 'perspective-origin' property set 'center' computes to 50% for the horizontal position.">
<style>
div {
height: 100px;
position: absolute;
width: 100px;
}
#test {
perspective: 2px;
perspective-origin: center 0px;
}
#redSquare {
background-color: red;
transform: translateZ(0px);
}
#ref {
perspective: 2px;
perspective-origin: 50% 0px;
}
#greenSquare {
background-color: green;
height: 50px;
left: 25px;
transform: translateZ(1px);
width: 50px;
}
</style>
<body>
<p>Test passes if there is a filled green square and <strong>no red</strong>.</p>
<div id="test"><div id="redSquare"></div></div>
<div id="ref"><div id="greenSquare"></div></div>
</body>

View file

@ -0,0 +1,40 @@
<!DOCTYPE html>
<meta charset="utf-8">
<title>CSS Transforms Test: perspective-origin - 50% bottom('bottom' computes to '100%' in vertical position)</title>
<link rel="author" title="Intel" href="http://www.intel.com">
<link rel="author" title="Jieqiong Cui" href="mailto:jieqiongx.cui@intel.com">
<link rel="help" title="11. The 'perspective-origin' Property" href="http://www.w3.org/TR/css-transforms-2/#propdef-perspective-origin">
<link rel="match" href="../../../../expected/wpt-import/css/css-transforms/../reference/ref-filled-green-100px-square.xht">
<meta name="assert" content="The 'perspective-origin' property set 'bottom' computes to 100% for the vertical position.">
<style>
div {
height: 100px;
position: absolute;
width: 100px;
}
#test {
perspective: 2px;
perspective-origin: 50% bottom;
}
#redSquare {
background-color: red;
transform: translateZ(0px);
}
#ref {
perspective: 2px;
perspective-origin: 50% 100%;
}
#greenSquare {
background-color: green;
height: 50px;
left: 25px;
top: 50px;
transform: translateZ(1px);
width: 50px;
}
</style>
<body>
<p>Test passes if there is a filled green square and <strong>no red</strong>.</p>
<div id="test"><div id="redSquare"></div></div>
<div id="ref"><div id="greenSquare"></div></div>
</body>

View file

@ -0,0 +1,39 @@
<!DOCTYPE html>
<meta charset="utf-8">
<title>CSS Transforms Test: perspective-origin - 50% top('top' computes to '0%' in vertical position)</title>
<link rel="author" title="Intel" href="http://www.intel.com">
<link rel="author" title="Jieqiong Cui" href="mailto:jieqiongx.cui@intel.com">
<link rel="help" title="11. The 'perspective-origin' Property" href="http://www.w3.org/TR/css-transforms-2/#propdef-perspective-origin">
<link rel="match" href="../../../../expected/wpt-import/css/css-transforms/../reference/ref-filled-green-100px-square.xht">
<meta name="assert" content="The 'perspective-origin' property set 'top' computes to 0% for the vertical position.">
<style>
div {
height: 100px;
position: absolute;
width: 100px;
}
#test {
perspective: 2px;
perspective-origin: 50% top;
}
#redSquare {
background-color: red;
transform: translateZ(0px);
}
#ref {
perspective: 2px;
perspective-origin: 50% 0%;
}
#greenSquare {
background-color: green;
height: 50px;
left: 25px;
transform: translateZ(1px);
width: 50px;
}
</style>
<body>
<p>Test passes if there is a filled green square and <strong>no red</strong>.</p>
<div id="test"><div id="redSquare"></div></div>
<div id="ref"><div id="greenSquare"></div></div>
</body>

View file

@ -0,0 +1,39 @@
<!DOCTYPE html>
<meta charset="utf-8">
<title>CSS Transforms Test: perspective-origin - left 50%('left' computes to '0%' in horizontal position)</title>
<link rel="author" title="Intel" href="http://www.intel.com">
<link rel="author" title="Jieqiong Cui" href="mailto:jieqiongx.cui@intel.com">
<link rel="help" title="11. The 'perspective-origin' Property" href="http://www.w3.org/TR/css-transforms-2/#propdef-perspective-origin">
<link rel="match" href="../../../../expected/wpt-import/css/css-transforms/../reference/ref-filled-green-100px-square.xht">
<meta name="assert" content="The 'perspective-origin' property set 'left' computes to 0% for the horizontal position.">
<style>
div {
height: 100px;
position: absolute;
width: 100px;
}
#test {
perspective: 2px;
perspective-origin: left 50%;
}
#redSquare {
background-color: red;
transform: translateZ(0px);
}
#ref {
perspective: 2px;
perspective-origin: 0% 50%;
}
#greenSquare {
background-color: green;
height: 50px;
top: 25px;
transform: translateZ(1px);
width: 50px;
}
</style>
<body>
<p>Test passes if there is a filled green square and <strong>no red</strong>.</p>
<div id="test"><div id="redSquare"></div></div>
<div id="ref"><div id="greenSquare"></div></div>
</body>

View file

@ -0,0 +1,40 @@
<!DOCTYPE html>
<meta charset="utf-8">
<title>CSS Transforms Test: perspective-origin - right 50%('right' computes to '100%' in horizontal position)</title>
<link rel="author" title="Intel" href="http://www.intel.com">
<link rel="author" title="Jieqiong Cui" href="mailto:jieqiongx.cui@intel.com">
<link rel="help" title="11. The 'perspective-origin' Property" href="http://www.w3.org/TR/css-transforms-2/#propdef-perspective-origin">
<link rel="match" href="../../../../expected/wpt-import/css/css-transforms/../reference/ref-filled-green-100px-square.xht">
<meta name="assert" content="The 'perspective-origin' property set 'right' computes to 100% for the horizontal position.">
<style>
div {
height: 100px;
position: absolute;
width: 100px;
}
#test {
perspective: 2px;
perspective-origin: right 50%;
}
#redSquare {
background-color: red;
transform: translateZ(0px);
}
#ref {
perspective: 2px;
perspective-origin: 100% 50%;
}
#greenSquare {
background-color: green;
height: 50px;
left: 50px;
top: 25px;
transform: translateZ(1px);
width: 50px;
}
</style>
<body>
<p>Test passes if there is a filled green square and <strong>no red</strong>.</p>
<div id="test"><div id="redSquare"></div></div>
<div id="ref"><div id="greenSquare"></div></div>
</body>

View file

@ -0,0 +1,40 @@
<!DOCTYPE html>
<meta charset="utf-8">
<title>The 'perspective-origin's position must not move with the children of a scroll container if it is on one.</title>
<link rel="author" title="Psychpsyo" href="mailto:psychpsyo@gmail.com">
<link rel="help" href="https://www.w3.org/TR/css-transforms-2/#perspective-origin-property">
<link rel="help" href="https://www.w3.org/TR/css-transforms-2/#perspective-matrix-computation">
<link rel="match" href="../../../../expected/wpt-import/css/css-transforms/../reference/ref-filled-green-100px-square.xht">
<meta name="assert" content="The 'perspective-origin's position must not move with the children of a scroll container if it is on one.">
<style>
div {
position: absolute;
}
#holder {
width: 100px;
height: 100px;
overflow: hidden;
perspective: 2px;
perspective-origin: 0px 0px;
}
#red {
background-color: red;
height: 200px;
width: 100px;
}
#greenSquare {
transform: translateZ(1px);
background-color: green;
width: 50px;
height: 50px;
top: 100px;
}
</style>
<p>Test passes if there is a filled green square and <strong>no red</strong>.</p>
<div id="holder">
<div id="red"></div>
<div id="greenSquare"></div>
</div>
<script>
holder.scroll({top: 100, behavior: "instant"});
</script>

View file

@ -0,0 +1,49 @@
<!DOCTYPE html>
<html>
<head>
<title>CSS Transforms Test: perspective property</title>
<link rel="author" title="Francisca Gil" href="mailto:pancha0.0@gmail.com">
<link rel="reviewer" title="Apple Inc." href="http://www.apple.com">
<link rel="help" href="http://www.w3.org/TR/css-transforms-2/#propdef-perspective-origin">
<link rel="help" href="http://www.w3.org/TR/css-transforms-2/#perspective-property">
<link rel="help" href="http://www.w3.org/TR/css-transforms-1/#transform-property">
<link rel="match" href="../../../../expected/wpt-import/css/css-transforms/reference/perspective-origin-reftest.html">
<meta name="assert" content="Asserts that origin 'x1' visually moves the objects '-x1*d/(d-1)' ">
<style type="text/css">
.container {
position: absolute;
width: 150px;
height: 150px;
top: 100px;
left: 100px;
perspective: 3px;
perspective-origin: 0px 50%;
}
.redSquare {
position: absolute;
top: 0px;
left: 0px;
width: 150px;
height: 150px;
background: red;
transform: translateZ(0px);
}
.greenSquare {
position: absolute;
top: 25px;
left: 0px;
width: 100px;
height: 100px;
background: green;
transform: translateZ(1px);
}
</style>
</head>
<body>
<p>The test passes if there is a green square and no red.</p>
<div class="container">
<div class="redSquare"></div>
<div class="greenSquare"></div>
</div>
</body>
</html>

View file

@ -0,0 +1,48 @@
<!DOCTYPE html>
<html>
<head>
<title>CSS Transforms Test: perspective property</title>
<link rel="author" title="Francisca Gil" href="mailto:pancha0.0@gmail.com">
<link rel="reviewer" title="Apple Inc." href="http://www.apple.com">
<link rel="help" href="http://www.w3.org/TR/css-transforms-2/#propdef-perspective-origin">
<link rel="help" href="http://www.w3.org/TR/css-transforms-1/#transform-property">
<link rel="match" href="../../../../expected/wpt-import/css/css-transforms/reference/perspective-reftest.html">
<meta name="assert" content="Asserts that origin '<x,y>' visually moves the objects '<-x,-y>*d/(d-1)' ">
<style type="text/css">
.container {
position: absolute;
width: 150px;
height: 150px;
top: 100px;
left: 100px;
perspective: 3px;
perspective-origin: 0px 0px;
}
.redSquare {
position: absolute;
top: 0px;
left: 0px;
width: 150px;
height: 150px;
background: red;
transform: translateZ(0px);
}
.greenSquare {
position: absolute;
top: 0px;
left: 0px;
width: 100px;
height: 100px;
background: green;
transform: translateZ(1px);
}
</style>
</head>
<body>
<p>The test passes if there is a green square and no red.</p>
<div class="container">
<div class="redSquare"></div>
<div class="greenSquare"></div>
</div>
</body>
</html>

View file

@ -0,0 +1,20 @@
<!DOCTYPE html>
<html>
<head>
<title>CSS Test (Transforms): perspective-origin</title>
<link rel="author" title="Matt Woodrow" href="mailto:mwoodrow@mozilla.com">
<link rel="author" title="Aryeh Gregor" href="mailto:ayg@aryeh.name">
<link rel="help" href="http://www.w3.org/TR/css-transforms-2/#propdef-perspective-origin">
<meta name="assert" content="This tests that 'perspective-origin: 0 0' is
the same as 'perspective-origin: top left', different from no
'perspective-origin', and different from no perspective or no transform.">
<link rel="match" href="../../../../expected/wpt-import/css/css-transforms/transform3d-perspective-origin-ref.html">
</head>
<body>
<div style="perspective: 1000px; perspective-origin: 0 0;">
<div style="transform: rotatex(45deg); width: 100px; height: 100px;
background: lime">
</div>
</div>
</body>
</html>

Binary file not shown.

Before

Width:  |  Height:  |  Size: 43 KiB

After

Width:  |  Height:  |  Size: 64 KiB

Before After
Before After

View file

@ -1,6 +1,6 @@
<!DOCTYPE html> <!DOCTYPE html>
<link rel="match" href="../expected/image-transformation-anti-aliasing-ref.html" /> <link rel="match" href="../expected/image-transformation-anti-aliasing-ref.html" />
<meta name="fuzzy" content="maxDifference=0-1;totalPixels=0-477"> <meta name="fuzzy" content="maxDifference=0-1;totalPixels=0-552">
<style> <style>
div { div {
perspective: 900px; perspective: 900px;

View file

@ -245,6 +245,7 @@ All properties associated with getComputedStyle(document.body):
"padding-right", "padding-right",
"padding-top", "padding-top",
"perspective", "perspective",
"perspective-origin",
"position", "position",
"position-anchor", "position-anchor",
"position-area", "position-area",

View file

@ -664,6 +664,8 @@ All supported properties and their default values exposed from CSSStylePropertie
'paintOrder': 'normal' 'paintOrder': 'normal'
'paint-order': 'normal' 'paint-order': 'normal'
'perspective': 'none' 'perspective': 'none'
'perspectiveOrigin': '50% 50%'
'perspective-origin': '50% 50%'
'placeContent': 'normal' 'placeContent': 'normal'
'place-content': 'normal' 'place-content': 'normal'
'placeItems': 'normal legacy' 'placeItems': 'normal legacy'

View file

@ -243,6 +243,7 @@ padding-left: 0px
padding-right: 0px padding-right: 0px
padding-top: 0px padding-top: 0px
perspective: none perspective: none
perspective-origin: 50% 50%
position: static position: static
position-anchor: auto position-anchor: auto
position-area: none position-area: none

View file

@ -1,8 +1,8 @@
Harness status: OK Harness status: OK
Found 271 tests Found 272 tests
265 Pass 266 Pass
6 Fail 6 Fail
Pass accent-color Pass accent-color
Pass border-collapse Pass border-collapse
@ -232,6 +232,7 @@ Pass padding-left
Pass padding-right Pass padding-right
Pass padding-top Pass padding-top
Pass perspective Pass perspective
Pass perspective-origin
Pass position Pass position
Pass r Pass r
Pass right Pass right