Skip to content
Permalink
Browse files
Implement unit checks of CSSNumericValue.{min,max}
https://bugs.webkit.org/show_bug.cgi?id=240559

Reviewed by Chris Dumez.

This is part of an off-by-default experimental feature.
This is in the spec but not implemented in Chrome yet.
Covered by an addition to WPT tests at web-platform-tests/wpt#34103

* Source/WebCore/Sources.txt:
* Source/WebCore/WebCore.xcodeproj/project.pbxproj:
* Source/WebCore/css/typedom/CSSNumericValue.cpp:
* Source/WebCore/css/typedom/CSSNumericValue.h:
* Source/WebCore/css/typedom/numeric/CSSMathProduct.cpp:
* Source/WebCore/css/typedom/numeric/CSSMathSum.cpp:
* Source/WebCore/css/typedom/numeric/CSSNumericType.cpp:
* Source/WebCore/css/typedom/numeric/CSSNumericType.h:

Canonical link: https://commits.webkit.org/250765@main
git-svn-id: https://svn.webkit.org/repository/webkit/trunk@294508 268f45cc-cd09-0410-ab3c-d52691b4dbfc
  • Loading branch information
achristensen07 committed May 19, 2022
1 parent 111a759 commit a3889af2eb3762dc79dd1c36520d48b9dfccd13f
Showing 10 changed files with 230 additions and 162 deletions.
@@ -62,4 +62,6 @@ PASS Calling CSSNumericValue.div inverts all argument values
PASS Can not divide with CSSUnitValue which has zero value and number type
PASS CSSNumericValue.add should throw TypeError when the types are different.
PASS CSSNumericValue.sub should throw TypeError when the types are different.
PASS CSSNumericValue.max should throw TypeError when the types are different.
PASS CSSNumericValue.min should throw TypeError when the types are different.

@@ -150,15 +150,11 @@
assert_throws_js(RangeError, () => CSS.number(2).div(CSS.number(0), CSS.number(0)));
}, 'Can not divide with CSSUnitValue which has zero value and number type');

test(() => {
assert_throws_js(TypeError, () => CSS.number(3).add(CSS.px(10) ,CSS.number(0)));
assert_throws_js(TypeError, () => CSS.px(2).add(CSS.deg(10)));
}, 'CSSNumericValue.add should throw TypeError when the types are different.');

test(() => {
assert_throws_js(TypeError, () => CSS.number(3).sub(CSS.px(10) ,CSS.number(0)));
assert_throws_js(TypeError, () => CSS.px(2).sub(CSS.deg(10)));
}, 'CSSNumericValue.sub should throw TypeError when the types are different.');

for (const methodName of ["add", "sub", "max", "min"]) {
test(() => {
assert_throws_js(TypeError, () => CSS.number(3)[methodName](CSS.px(10) ,CSS.number(0)));
assert_throws_js(TypeError, () => CSS.px(2)[methodName](CSS.deg(10)));
}, 'CSSNumericValue.' + methodName + ' should throw TypeError when the types are different.');
}

</script>
@@ -879,6 +879,7 @@ css/typedom/numeric/CSSMathNegate.cpp
css/typedom/numeric/CSSMathProduct.cpp
css/typedom/numeric/CSSMathSum.cpp
css/typedom/numeric/CSSNumericArray.cpp
css/typedom/numeric/CSSNumericType.cpp
css/typedom/transform/CSSMatrixComponent.cpp
css/typedom/transform/CSSPerspective.cpp
css/typedom/transform/CSSRotate.cpp
@@ -10741,6 +10741,7 @@
5CDD833F1E43253D00621E92 /* LibWebRTCDataChannelHandler.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = LibWebRTCDataChannelHandler.cpp; path = libwebrtc/LibWebRTCDataChannelHandler.cpp; sourceTree = "<group>"; };
5CDD83401E43253D00621E92 /* LibWebRTCDataChannelHandler.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = LibWebRTCDataChannelHandler.h; path = libwebrtc/LibWebRTCDataChannelHandler.h; sourceTree = "<group>"; };
5CDFA6C71AA4F2DA00EA8746 /* ContentExtensionActions.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ContentExtensionActions.h; sourceTree = "<group>"; };
5CE111EA2834702300150C47 /* CSSNumericType.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = CSSNumericType.cpp; sourceTree = "<group>"; };
5CE5D9A01DB897C300CE7A8D /* TextDecoder.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = TextDecoder.cpp; sourceTree = "<group>"; };
5CE5D9A11DB897C300CE7A8D /* TextDecoder.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = TextDecoder.h; sourceTree = "<group>"; };
5CE5D9A21DB897C300CE7A8D /* TextDecoder.idl */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = TextDecoder.idl; sourceTree = "<group>"; };
@@ -20866,6 +20867,7 @@
2AEF6FC426E716EE00326D02 /* CSSNumericArray.idl */,
2AEF6FCF26E71F2E00326D02 /* CSSNumericBaseType.h */,
2AEF6FCD26E71F2D00326D02 /* CSSNumericBaseType.idl */,
5CE111EA2834702300150C47 /* CSSNumericType.cpp */,
2AEF6FCE26E71F2D00326D02 /* CSSNumericType.h */,
2AEF6FD026E71F2E00326D02 /* CSSNumericType.idl */,
);
@@ -186,30 +186,32 @@ ExceptionOr<Ref<CSSNumericValue>> CSSNumericValue::div(FixedVector<CSSNumberish>
return multiplyInternal(WTFMove(invertedValues));
}

Ref<CSSNumericValue> CSSNumericValue::min(FixedVector<CSSNumberish>&& numberishes)
ExceptionOr<Ref<CSSNumericValue>> CSSNumericValue::min(FixedVector<CSSNumberish>&& numberishes)
{
// https://drafts.css-houdini.org/css-typed-om/#dom-cssnumericvalue-min
auto values = prependItemsOfTypeOrThis<CSSMathMin>(WTF::map(WTFMove(numberishes), rectifyNumberish));

if (auto result = operationOnValuesOfSameUnit<const double&(*)(const double&, const double&)>(std::min<double>, values))
return *result;
return { *result };

// FIXME: Implement step 4 to check that the types can be added.
if (!CSSNumericType::addTypes(values))
return Exception { TypeError };

return CSSMathMin::create(WTFMove(values));
return { CSSMathMin::create(WTFMove(values)) };
}

Ref<CSSNumericValue> CSSNumericValue::max(FixedVector<CSSNumberish>&& numberishes)
ExceptionOr<Ref<CSSNumericValue>> CSSNumericValue::max(FixedVector<CSSNumberish>&& numberishes)
{
// https://drafts.css-houdini.org/css-typed-om/#dom-cssnumericvalue-max
auto values = prependItemsOfTypeOrThis<CSSMathMax>(WTF::map(WTFMove(numberishes), rectifyNumberish));

if (auto result = operationOnValuesOfSameUnit<const double&(*)(const double&, const double&)>(std::max<double>, values))
return *result;
return { *result };

// FIXME: Implement step 4 to check that the types can be added.
if (!CSSNumericType::addTypes(values))
return Exception { TypeError };

return CSSMathMax::create(WTFMove(values));
return { CSSMathMax::create(WTFMove(values)) };
}

Ref<CSSNumericValue> CSSNumericValue::rectifyNumberish(CSSNumberish&& numberish)
@@ -50,8 +50,8 @@ class CSSNumericValue : public CSSStyleValue {
ExceptionOr<Ref<CSSNumericValue>> sub(FixedVector<CSSNumberish>&&);
ExceptionOr<Ref<CSSNumericValue>> mul(FixedVector<CSSNumberish>&&);
ExceptionOr<Ref<CSSNumericValue>> div(FixedVector<CSSNumberish>&&);
Ref<CSSNumericValue> min(FixedVector<CSSNumberish>&&);
Ref<CSSNumericValue> max(FixedVector<CSSNumberish>&&);
ExceptionOr<Ref<CSSNumericValue>> min(FixedVector<CSSNumberish>&&);
ExceptionOr<Ref<CSSNumericValue>> max(FixedVector<CSSNumberish>&&);

bool equals(FixedVector<CSSNumberish>&&);

@@ -37,32 +37,6 @@ namespace WebCore {

WTF_MAKE_ISO_ALLOCATED_IMPL(CSSMathProduct);

static std::optional<CSSNumericType> multiplyTypes(const CSSNumericType& a, const CSSNumericType& b)
{
// https://drafts.css-houdini.org/css-typed-om/#cssnumericvalue-multiply-two-types
if (a.percentHint && b.percentHint && *a.percentHint != *b.percentHint)
return std::nullopt;

auto add = [] (auto left, auto right) -> CSSNumericType::BaseTypeStorage {
if (!left)
return right;
if (!right)
return left;
return *left + *right;
};

return { {
add(a.length, b.length),
add(a.angle, b.angle),
add(a.time, b.time),
add(a.frequency, b.frequency),
add(a.resolution, b.resolution),
add(a.flex, b.flex),
add(a.percent, b.percent),
a.percentHint ? a.percentHint : b.percentHint
} };
}

ExceptionOr<Ref<CSSMathProduct>> CSSMathProduct::create(FixedVector<CSSNumberish> numberishes)
{
return create(WTF::map(WTFMove(numberishes), rectifyNumberish));
@@ -72,13 +46,10 @@ ExceptionOr<Ref<CSSMathProduct>> CSSMathProduct::create(Vector<Ref<CSSNumericVal
{
if (values.isEmpty())
return Exception { SyntaxError };

std::optional<CSSNumericType> type = values[0]->type();
for (size_t i = 1; i < values.size(); i++) {
type = multiplyTypes(*type, values[i]->type());
if (!type)
return Exception { TypeError };
}

auto type = CSSNumericType::multiplyTypes(values);
if (!type)
return Exception { TypeError };

return adoptRef(*new CSSMathProduct(WTFMove(values), WTFMove(*type)));
}
@@ -38,34 +38,6 @@ namespace WebCore {

WTF_MAKE_ISO_ALLOCATED_IMPL(CSSMathSum);

static std::optional<CSSNumericType> addTypes(CSSNumericType a, CSSNumericType b)
{
// https://drafts.css-houdini.org/css-typed-om/#cssnumericvalue-add-two-types
if (a.percentHint && b.percentHint && *a.percentHint != *b.percentHint)
return std::nullopt;

if (a.percentHint)
b.applyPercentHint(*a.percentHint);
if (b.percentHint)
a.applyPercentHint(*b.percentHint);

if (a == b)
return { WTFMove(a) };

for (auto type : eachBaseType()) {
if (type == CSSNumericBaseType::Percent)
continue;
if (!a.valueForType(type) && !b.valueForType(type))
continue;
a.applyPercentHint(type);
b.applyPercentHint(type);
if (a.valueForType(type) != b.valueForType(type))
return std::nullopt;
}

return { WTFMove(a) };
}

ExceptionOr<Ref<CSSMathSum>> CSSMathSum::create(FixedVector<CSSNumberish> numberishes)
{
return create(WTF::map(WTFMove(numberishes), rectifyNumberish));
@@ -76,12 +48,9 @@ ExceptionOr<Ref<CSSMathSum>> CSSMathSum::create(Vector<Ref<CSSNumericValue>> val
if (values.isEmpty())
return Exception { SyntaxError };

std::optional<CSSNumericType> type = values[0]->type();
for (size_t i = 1; i < values.size(); i++) {
type = addTypes(*type, values[i]->type());
if (!type)
return Exception { TypeError };
}
auto type = CSSNumericType::addTypes(values);
if (!type)
return Exception { TypeError };

return adoptRef(*new CSSMathSum(WTFMove(values), WTFMove(*type)));
}
@@ -0,0 +1,185 @@
/*
* Copyright (C) 2022 Apple Inc. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY
* EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
* PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
* OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/

#pragma once

#include "config.h"
#include "CSSNumericType.h"

#if ENABLE(CSS_TYPED_OM)

namespace WebCore {

std::optional<CSSNumericType> CSSNumericType::addTypes(CSSNumericType a, CSSNumericType b)
{
// https://drafts.css-houdini.org/css-typed-om/#cssnumericvalue-add-two-types
if (a.percentHint && b.percentHint && *a.percentHint != *b.percentHint)
return std::nullopt;

if (a.percentHint)
b.applyPercentHint(*a.percentHint);
if (b.percentHint)
a.applyPercentHint(*b.percentHint);

if (a == b)
return { WTFMove(a) };

for (auto type : eachBaseType()) {
if (type == CSSNumericBaseType::Percent)
continue;
if (!a.valueForType(type) && !b.valueForType(type))
continue;
a.applyPercentHint(type);
b.applyPercentHint(type);
if (a.valueForType(type) != b.valueForType(type))
return std::nullopt;
}

return { WTFMove(a) };
}

template<typename Argument> std::optional<CSSNumericType> typeFromVector(const Vector<Ref<CSSNumericValue>>& values, std::optional<CSSNumericType>(*function)(Argument, Argument))
{
if (values.isEmpty())
return std::nullopt;
std::optional<CSSNumericType> type = values[0]->type();
for (size_t i = 1; i < values.size(); i++) {
type = function(*type, values[i]->type());
if (!type)
return std::nullopt;
}
return type;
}

std::optional<CSSNumericType> CSSNumericType::addTypes(const Vector<Ref<CSSNumericValue>>& values)
{
return typeFromVector(values, addTypes);
}

std::optional<CSSNumericType> CSSNumericType::multiplyTypes(const CSSNumericType& a, const CSSNumericType& b)
{
// https://drafts.css-houdini.org/css-typed-om/#cssnumericvalue-multiply-two-types
if (a.percentHint && b.percentHint && *a.percentHint != *b.percentHint)
return std::nullopt;

auto add = [] (auto left, auto right) -> CSSNumericType::BaseTypeStorage {
if (!left)
return right;
if (!right)
return left;
return *left + *right;
};

return { {
add(a.length, b.length),
add(a.angle, b.angle),
add(a.time, b.time),
add(a.frequency, b.frequency),
add(a.resolution, b.resolution),
add(a.flex, b.flex),
add(a.percent, b.percent),
a.percentHint ? a.percentHint : b.percentHint
} };
}

std::optional<CSSNumericType> CSSNumericType::multiplyTypes(const Vector<Ref<CSSNumericValue>>& values)
{
return typeFromVector(values, multiplyTypes);
}

String CSSNumericType::debugString() const
{
return makeString("{",
length ? makeString(" length:", *length) : String(),
angle ? makeString(" angle:", *angle) : String(),
time ? makeString(" time:", *time) : String(),
frequency ? makeString(" frequency:", *frequency) : String(),
resolution ? makeString(" resolution:", *resolution) : String(),
flex ? makeString(" flex:", *flex) : String(),
percent ? makeString(" percent:", *percent) : String(),
percentHint ? makeString(" percentHint:", WebCore::debugString(*percentHint)) : String(),
" }");
}

auto CSSNumericType::valueForType(CSSNumericBaseType type) -> BaseTypeStorage&
{
switch (type) {
case CSSNumericBaseType::Length:
return length;
case CSSNumericBaseType::Angle:
return angle;
case CSSNumericBaseType::Time:
return time;
case CSSNumericBaseType::Frequency:
return frequency;
case CSSNumericBaseType::Resolution:
return resolution;
case CSSNumericBaseType::Flex:
return flex;
case CSSNumericBaseType::Percent:
return percent;
}
RELEASE_ASSERT_NOT_REACHED();
}

bool CSSNumericType::operator==(const CSSNumericType& other) const
{
return length == other.length
&& angle == other.angle
&& time == other.time
&& frequency == other.frequency
&& resolution == other.resolution
&& flex == other.flex
&& percent == other.percent
&& percentHint == other.percentHint;
}

void CSSNumericType::applyPercentHint(CSSNumericBaseType hint)
{
// https://drafts.css-houdini.org/css-typed-om/#apply-the-percent-hint
auto& optional = valueForType(hint);
if (!optional)
optional = 0;
if (percent)
*optional += *std::exchange(percent, 0);
percentHint = hint;
}

size_t CSSNumericType::nonZeroEntryCount() const
{
size_t count { 0 };
count += length && *length;
count += angle && *angle;
count += time && *time;
count += frequency && *frequency;
count += resolution && *resolution;
count += flex && *flex;
count += percent && *percent;
return count;
}

} // namespace WebCore

#endif

0 comments on commit a3889af

Please sign in to comment.