Support min() and max() in calc()
authortimothy_horton@apple.com <timothy_horton@apple.com@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Mon, 18 Sep 2017 23:36:20 +0000 (23:36 +0000)
committertimothy_horton@apple.com <timothy_horton@apple.com@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Mon, 18 Sep 2017 23:36:20 +0000 (23:36 +0000)
https://bugs.webkit.org/show_bug.cgi?id=167000
<rdar://problem/30153481>

Reviewed by David Hyatt.
Patch originally by Myles Maxfield.

Source/WebCore:

Add two new toplevel functions to CSS, min() and max(), which take an
arbirary number of arguments and resolve to the minimum and maximum of
the resolved value of the arguments, respectively. It is also possible
to use min() and max() inside calc(), and to use calc()-like math
inside min() and max().

* css/CSSCalculationValue.cpp:
(WebCore::determineCategory):
min and max operators don't use determineCategory; we have a specific
implementation for them in createMinOrMax.

(WebCore::resolvedTypeForMinOrMax):
The spec says that min() and max() should be marked as invalid if they
have values of more than one type, but that percentages should resolve
against the destination type before making this determination. So,
if the destination type is length, percent turns into percent-length,
and similarly for number.

(WebCore::isIntegerResult):
Add an n-way implementation of isIntegerResult.

(WebCore::isSamePair):
(WebCore::CSSCalcOperation::createMinOrMax): Create a min() or max()
operation, as long as the types of arguments are all the same. Allow
lengths to upgrade the whole operation to percent-length, and numbers
to percent-number, which will cause us to use CalculationValue and friends
in order to do proper resolution of all of the parameters instead of
just comparing their numeric values.

(WebCore::CSSCalcOperation::createCalcExpression):
(WebCore::CSSCalcOperation::doubleValue):
(WebCore::CSSCalcOperation::computeLengthPx):
(WebCore::CSSCalcOperation::customCSSText):
(WebCore::CSSCalcOperation::primitiveType):
(WebCore::CSSCalcOperation::CSSCalcOperation):
(WebCore::CSSCalcOperation::evaluate):
(WebCore::CSSCalcOperation::evaluateOperator):
Adapt to child counts greater than two.

(WebCore::CSSCalcOperation::buildCssText):
Add support for min() and max().

(WebCore::CSSCalcExpressionNodeParser::parseCalc):
parseCalc now accepts a CSSValueID parameter indicating which calc function
it should parse (calc, webkit-calc, min, or max), and delegates to either
parseValueExpression or parseMinMaxExpression.

(WebCore::CSSCalcExpressionNodeParser::operatorValue):
(WebCore::CSSCalcExpressionNodeParser::parseValue):
If min() or max() are found while parsing a value (i.e. nested inside
either calc or themselves), use parseMinMaxExpression on that subtree.

(WebCore::CSSCalcExpressionNodeParser::parseValueTerm):
(WebCore::CSSCalcExpressionNodeParser::parseValueMultiplicativeExpression):
(WebCore::CSSCalcExpressionNodeParser::parseAdditiveValueExpression):
Adjust to the CSSCalcBinaryOperation->CSSCalcOperation rename.

(WebCore::CSSCalcExpressionNodeParser::parseMinMaxExpression):
Added. Parse an arbitrary number of comma-and-whitespace-separated children.

(WebCore::createBlendHalf):
Adjust to the CSSCalcBinaryOperation->CSSCalcOperation rename.

(WebCore::createCSS):
Build the CSSCalcOperation for the platform-independent min and max operations.

(WebCore::CSSCalcValue::create):
Pass the function being parsed and the destination calc category for the
property being parsed for into create, and then into the parser so that
it can know which function it is parsing for, and what kind of result it
needs (as previously mentioned above in resolvedTypeForMinOrMax).

* css/CSSCalculationValue.h:
* css/CSSValueKeywords.in:
Add min and max functions as CSS keywords.

* css/StyleBuilderConverter.h:
(WebCore::StyleBuilderConverter::convertLength):
(WebCore::StyleBuilderConverter::convertTo100PercentMinusLength):
* platform/Length.cpp:
(WebCore::convertTo100PercentMinusLength):
Adapt to the CalcExpressionOperation constructor taking a vector of
arguments instead of two.

* css/parser/CSSPropertyParserHelpers.cpp:
(WebCore::CSSPropertyParserHelpers::CalcParser::CalcParser):
Store and pass the specific function being parsed down into CSSCalcValue.

(WebCore::CSSPropertyParserHelpers::consumeInteger):
(WebCore::CSSPropertyParserHelpers::consumePositiveIntegerRaw):
(WebCore::CSSPropertyParserHelpers::consumeNumberRaw):
(WebCore::CSSPropertyParserHelpers::consumeNumber):
(WebCore::CSSPropertyParserHelpers::consumeFontWeightNumber):
(WebCore::CSSPropertyParserHelpers::consumeLength):
(WebCore::CSSPropertyParserHelpers::consumePercent):
(WebCore::CSSPropertyParserHelpers::consumeLengthOrPercent):
(WebCore::CSSPropertyParserHelpers::consumeAngle):
(WebCore::CSSPropertyParserHelpers::consumeTime):
Pass the destination type into each calc parser.

* platform/CalculationValue.cpp:
(WebCore::CalcExpressionOperation::evaluate const):
(WebCore::CalcExpressionOperation::operator== const):
(WebCore::CalcExpressionOperation::dump const):
(WebCore::operator<<):
(WebCore::CalcExpressionBinaryOperation::evaluate const): Deleted.
(WebCore::CalcExpressionBinaryOperation::operator== const): Deleted.
(WebCore::CalcExpressionBinaryOperation::dump const): Deleted.
* platform/CalculationValue.h:
(WebCore::CalcExpressionOperation::CalcExpressionOperation):
(WebCore::operator==):
(WebCore::toCalcExpressionOperation):
(WebCore::CalcExpressionBinaryOperation::CalcExpressionBinaryOperation): Deleted.
(WebCore::toCalcExpressionBinaryOperation): Deleted.
Adjust to the CSSCalcBinaryOperation->CSSCalcOperation rename.
Adjust to having n>2 children.
Support min() and max() operators in various places.

LayoutTests:

* css3/calc/minmax-errors-expected.txt:
* css3/calc/minmax-errors.html:
* css3/calc/simple-minmax-expected.txt:
* css3/calc/simple-minmax.html:
Revive previously-unused tests for an earlier never-implemented version
of this feature, and add a bunch more interesting test cases from reading the spec.

git-svn-id: https://svn.webkit.org/repository/webkit/trunk@222190 268f45cc-cd09-0410-ab3c-d52691b4dbfc

14 files changed:
LayoutTests/ChangeLog
LayoutTests/css3/calc/minmax-errors-expected.txt
LayoutTests/css3/calc/minmax-errors.html
LayoutTests/css3/calc/simple-minmax-expected.txt
LayoutTests/css3/calc/simple-minmax.html
Source/WebCore/ChangeLog
Source/WebCore/css/CSSCalculationValue.cpp
Source/WebCore/css/CSSCalculationValue.h
Source/WebCore/css/CSSValueKeywords.in
Source/WebCore/css/StyleBuilderConverter.h
Source/WebCore/css/parser/CSSPropertyParserHelpers.cpp
Source/WebCore/platform/CalculationValue.cpp
Source/WebCore/platform/CalculationValue.h
Source/WebCore/platform/Length.cpp

index 476a486..a49cd33 100644 (file)
@@ -1,3 +1,19 @@
+2017-09-18  Tim Horton  <timothy_horton@apple.com>
+
+        Support min() and max() in calc()
+        https://bugs.webkit.org/show_bug.cgi?id=167000
+        <rdar://problem/30153481>
+
+        Reviewed by David Hyatt.
+        Patch originally by Myles Maxfield.
+
+        * css3/calc/minmax-errors-expected.txt:
+        * css3/calc/minmax-errors.html:
+        * css3/calc/simple-minmax-expected.txt:
+        * css3/calc/simple-minmax.html:
+        Revive previously-unused tests for an earlier never-implemented version
+        of this feature, and add a bunch more interesting test cases from reading the spec.
+
 2017-09-18  Per Arne Vollan  <pvollan@apple.com>
 
         Mark imported/blink/fast/events/panScroll-panIcon.html as flaky on Windows.
index 5d5707f..6a531df 100644 (file)
@@ -1,6 +1,6 @@
 All boxes below should be 100px * 100px and green.
 
-MIN
+Bare min()
 unclosed min => PASS
 unclosed min with garbage => PASS
 garbage => PASS
@@ -12,7 +12,32 @@ mix length and number => PASS
 mix number and length => PASS
 mix percent and number => PASS
 mix number and percent => PASS
-MAX
+
+
+min() inside calc()
+unclosed min => PASS
+unclosed min with garbage => PASS
+garbage => PASS
+extra trailing comma => PASS
+leading comma => PASS
+trailing garbage => PASS
+bad expression => PASS
+mix length and number => PASS
+mix number and length => PASS
+mix percent and number => PASS
+mix number and percent => PASS
+
+
+Bare max()
+unclosed max => PASS
+unclosed max with garbage => PASS
+mix length and number => PASS
+mix number and length => PASS
+mix percent and number => PASS
+mix number and percent => PASS
+
+
+max() inside calc
 unclosed max => PASS
 unclosed max with garbage => PASS
 mix length and number => PASS
index 2e2f541..c817a70 100644 (file)
 
 <div id="test">
 
-MIN
-<div style="width: 100px; width: -webkit-min(">unclosed min</div>
-<div style="width: 100px; width: -webkit-min( bob">unclosed min with garbage</div>
-<div style="width: 100px; width: -webkit-min( bob );">garbage</div>
-<div style="width: 100px; width: -webkit-min(20px,);">extra trailing comma</div>
-<div style="width: 100px; width: -webkit-min(,20px);">leading comma</div>
-<div style="width: 100px; width: -webkit-min(20px, bob);">trailing garbage</div>
-<div style="width: 100px; width: -webmit-min(20px, 10px + flim);">bad expression</div>
-<div style="width: 100px; width: -webkit-min(256px, 120);">mix length and number</div>
-<div style="width: 100px; width: -webkit-min(256, 120px);">mix number and length</div>
-<div style="width: 100px; width: -webkit-min(50%, 150);">mix percent and number</div>
-<div style="width: 100px; width: -webkit-min(150, 50%);">mix number and percent</div>
-
-MAX
-<div style="width: 100px; width: -webkit-max(">unclosed max</div>
-<div style="width: 100px; width: -webkit-max( bob">unclosed max with garbage</div>
-<div style="width: 100px; width: -webkit-max(256px, 120);">mix length and number</div>
-<div style="width: 100px; width: -webkit-max(256, 120px);">mix number and length</div>
-<div style="width: 100px; width: -webkit-max(50%, 150);">mix percent and number</div>
-<div style="width: 100px; width: -webkit-max(150, 50%);">mix number and percent</div>
+Bare min()
+<div style="width: 100px; width: min(">unclosed min</div>
+<div style="width: 100px; width: min( bob">unclosed min with garbage</div>
+<div style="width: 100px; width: min( bob );">garbage</div>
+<div style="width: 100px; width: min(20px,);">extra trailing comma</div>
+<div style="width: 100px; width: min(,20px);">leading comma</div>
+<div style="width: 100px; width: min(20px, bob);">trailing garbage</div>
+<div style="width: 100px; width: min(20px, 10px + flim);">bad expression</div>
+<div style="width: 100px; width: min(256px, 120);">mix length and number</div>
+<div style="width: 100px; width: min(256, 120px);">mix number and length</div>
+<div style="width: 100px; width: min(50%, 150);">mix percent and number</div>
+<div style="width: 100px; width: min(150, 50%);">mix number and percent</div>
+
+<br/><br/>min() inside calc()
+<div style="width: 100px; width: calc(min(">unclosed min</div>
+<div style="width: 100px; width: calc(min( bob">unclosed min with garbage</div>
+<div style="width: 100px; width: calc(min( bob ));">garbage</div>
+<div style="width: 100px; width: calc(min(20px,));">extra trailing comma</div>
+<div style="width: 100px; width: calc(min(,20px));">leading comma</div>
+<div style="width: 100px; width: calc(min(20px, bob));">trailing garbage</div>
+<div style="width: 100px; width: calc(min(20px, 10px + flim));">bad expression</div>
+<div style="width: 100px; width: calc(min(256px, 120));">mix length and number</div>
+<div style="width: 100px; width: calc(min(256, 120px));">mix number and length</div>
+<div style="width: 100px; width: calc(min(50%, 150));">mix percent and number</div>
+<div style="width: 100px; width: calc(min(150, 50%));">mix number and percent</div>
+
+<br/><br/>Bare max()
+<div style="width: 100px; width: max(">unclosed max</div>
+<div style="width: 100px; width: max( bob">unclosed max with garbage</div>
+<div style="width: 100px; width: max(256px, 120);">mix length and number</div>
+<div style="width: 100px; width: max(256, 120px);">mix number and length</div>
+<div style="width: 100px; width: max(50%, 150);">mix percent and number</div>
+<div style="width: 100px; width: max(150, 50%);">mix number and percent</div>
+
+<br/><br/>max() inside calc
+<div style="width: 100px; width: calc(max(">unclosed max</div>
+<div style="width: 100px; width: calc(max( bob">unclosed max with garbage</div>
+<div style="width: 100px; width: calc(max(256px, 120));">mix length and number</div>
+<div style="width: 100px; width: calc(max(256, 120px));">mix number and length</div>
+<div style="width: 100px; width: calc(max(50%, 150));">mix percent and number</div>
+<div style="width: 100px; width: calc(max(150, 50%));">mix number and percent</div>
 
 </div>
 
index 7596690..2d96f98 100644 (file)
@@ -1,18 +1,54 @@
 All boxes below should be 100px * 100px and green.
 
-min(100px) => FAIL: expected width of 100, but was 256
-min( 100px ) => FAIL: expected width of 100, but was 256
-min((((100px)))) => FAIL: expected width of 100, but was 256
-min(150px,100px) => FAIL: expected width of 100, but was 256
-min(150px,100px,200px) => FAIL: expected width of 100, but was 256
-min( 150px , 100px ,200px) => FAIL: expected width of 100, but was 256
-min(90px + 50px ,100px) => FAIL: expected width of 100, but was 256
-min(100%,100px) - where 100% is 200px => FAIL: expected width of 100, but was 256
-min(100px,100%) - where 100% is 200px => FAIL: expected width of 100, but was 256
-max(100px) => FAIL: expected width of 100, but was 256
-max(50px,100px) => FAIL: expected width of 100, but was 256
-max(50px,100px,20px) => FAIL: expected width of 100, but was 256
-max(120px - 50px,100px) => FAIL: expected width of 100, but was 256
-max(100%,100px) - where 100% is 50px => FAIL: expected width of 100, but was 256
-max(100px,100%) - where 100% is 50px => FAIL: expected width of 100, but was 256
-min(200px,100px) => FAIL: expected height of 100, but was 50
+Bare min()
+min(100px) => PASS
+min( 100px ) => PASS
+min((((100px)))) => PASS
+min(150px,100px) => PASS
+min(150px,100px,200px) => PASS
+min( 150px , 100px ,200px) => PASS
+min(90px + 50px ,100px) => PASS
+min(100%,100px) - where 100% is 200px => PASS
+min(50%,150px) - where 50% is 100px => PASS
+min(100% - 50px,100px) - where 100% is 200px => PASS
+min(25% + 50px,150px) - where 25% is 50px => PASS
+min(100px,100%) - where 100% is 200px => PASS
+min(200px,100px) => PASS
+
+
+min() inside calc()
+calc(min(100px)) => PASS
+calc(min(150px, 200px) - 50px) => PASS
+calc(50 + min(150px, 50px)) => PASS
+calc(min(250px, 200px) / 2) => PASS
+calc(min( 100px )) => PASS
+calc(min((((100px))))) => PASS
+calc(min(150px,100px)) => PASS
+calc(min(150px,100px,200px)) => PASS
+calc(min( 150px , 100px ,200px)) => PASS
+calc(min(90px + 50px ,100px)) => PASS
+calc(min(100%,100px)) - where 100% is 200px => PASS
+calc(min(50%,150px)) - where 50% is 100px => PASS
+calc(min(100% - 50px,100px)) - where 100% is 200px => PASS
+calc(min(25% + 50px,150px)) - where 25% is 50px => PASS
+calc(min(100px,100%)) - where 100% is 200px => PASS
+calc(min(200px,100px)) => PASS
+
+
+Bare max()
+max(100px) => PASS
+calc(max(150px, 100px) - 50px) => PASS
+max(50px,100px) => PASS
+max(50px,100px,20px) => PASS
+max(120px - 50px,100px) => PASS
+max(100%,100px) - where 100% is 50px => PASS
+max(100px,100%) - where 100% is 50px => PASS
+
+
+max() inside calc()
+calc(max(100px)) => PASS
+calc(max(50px,100px)) => PASS
+calc(max(50px,100px,20px)) => PASS
+calc(max(120px - 50px,100px)) => PASS
+calc(max(100%,100px)) - where 100% is 50px => PASS
+calc(max(100px,100%)) - where 100% is 50px => PASS
index bb41dae..eeb0902 100644 (file)
 
 <div id="test">
 
-<div class="width-test" style="width: -webkit-min(100px);">min(100px)</div>
-<div class="width-test" style="width: -webkit-min( 100px );">min( 100px )</div>
-<div class="width-test" style="width: -webkit-min((((100px))));">min((((100px))))</div>
-<div class="width-test" style="width: -webkit-min(150px,100px);">min(150px,100px)</div>
-<div class="width-test" style="width: -webkit-min(150px,100px,200px);">min(150px,100px,200px)</div>
-<div class="width-test" style="width: -webkit-min(  150px ,  100px  ,200px);">min(  150px  ,  100px  ,200px)</div>
-<div class="width-test" style="width: -webkit-min(90px + 50px ,100px);">min(90px + 50px ,100px)</div>
+Bare min()
+<div class="width-test" style="width: min(100px);">min(100px)</div>
+<div class="width-test" style="width: min( 100px );">min( 100px )</div>
+<div class="width-test" style="width: min((((100px))));">min((((100px))))</div>
+<div class="width-test" style="width: min(150px,100px);">min(150px,100px)</div>
+<div class="width-test" style="width: min(150px,100px,200px);">min(150px,100px,200px)</div>
+<div class="width-test" style="width: min(  150px ,  100px  ,200px);">min(  150px  ,  100px  ,200px)</div>
+<div class="width-test" style="width: min(90px + 50px ,100px);">min(90px + 50px ,100px)</div>
 <div style="width: 200px; background-color: white;" class="wrapper">
-  <div class="width-test" style="width: -webkit-min(100%,100px);">min(100%,100px) - where 100% is 200px</div>
+  <div class="width-test" style="width: min(100%,100px);">min(100%,100px) - where 100% is 200px</div>
 </div>
 <div style="width: 200px; background-color: white;" class="wrapper">
-  <div class="width-test" style="width: -webkit-min(100px,100%);">min(100px,100%) - where 100% is 200px</div>
+  <div class="width-test" style="width: min(50%,150px);">min(50%,150px) - where 50% is 100px</div>
 </div>
+<div style="width: 200px; background-color: white;" class="wrapper">
+  <div class="width-test" style="width: min(100% - 50px,100px);">min(100% - 50px,100px) - where 100% is 200px</div>
+</div>
+<div style="width: 200px; background-color: white;" class="wrapper">
+  <div class="width-test" style="width: min(25% + 50px,150px);">min(25% + 50px,150px) - where 25% is 50px</div>
+</div>
+<div style="width: 200px; background-color: white;" class="wrapper">
+  <div class="width-test" style="width: min(100px,100%);">min(100px,100%) - where 100% is 200px</div>
+</div>
+<div class="height-test" style="height: min(200px, 100px);">min(200px,100px)</div>
 
-<div class="width-test" style="width: -webkit-max(100px);">max(100px)</div>
-<div class="width-test" style="width: -webkit-max(50px,100px);">max(50px,100px)</div>
-<div class="width-test" style="width: -webkit-max(50px,100px,20px);">max(50px,100px,20px)</div>
-<div class="width-test" style="width: -webkit-max(120px - 50px,100px);">max(120px - 50px,100px)</div>
+<br/><br/>min() inside calc()
+<div class="width-test" style="width: calc(min(100px));">calc(min(100px))</div>
+<div class="width-test" style="width: calc(min(150px, 200px) - 50px);">calc(min(150px, 200px) - 50px)</div>
+<div class="width-test" style="width: calc(50px + min(150px, 50px));">calc(50 + min(150px, 50px))</div>
+<div class="width-test" style="width: calc(min(250px, 200px) / 2);">calc(min(250px, 200px) / 2)</div>
+<div class="width-test" style="width: calc(min( 100px ));">calc(min( 100px ))</div>
+<div class="width-test" style="width: calc(min((((100px)))));">calc(min((((100px)))))</div>
+<div class="width-test" style="width: calc(min(150px,100px));">calc(min(150px,100px))</div>
+<div class="width-test" style="width: calc(min(150px,100px,200px));">calc(min(150px,100px,200px))</div>
+<div class="width-test" style="width: calc(min(  150px ,  100px  ,200px));">calc(min(  150px  ,  100px  ,200px))</div>
+<div class="width-test" style="width: calc(min(90px + 50px ,100px));">calc(min(90px + 50px ,100px))</div>
+<div style="width: 200px; background-color: white;" class="wrapper">
+  <div class="width-test" style="width: calc(min(100%,100px));">calc(min(100%,100px)) - where 100% is 200px</div>
+</div>
+<div style="width: 200px; background-color: white;" class="wrapper">
+  <div class="width-test" style="width: calc(min(50%,150px));">calc(min(50%,150px)) - where 50% is 100px</div>
+</div>
+<div style="width: 200px; background-color: white;" class="wrapper">
+  <div class="width-test" style="width: calc(min(100% - 50px,100px));">calc(min(100% - 50px,100px)) - where 100% is 200px</div>
+</div>
+<div style="width: 200px; background-color: white;" class="wrapper">
+  <div class="width-test" style="width: calc(min(25% + 50px,150px));">calc(min(25% + 50px,150px)) - where 25% is 50px</div>
+</div>
+<div style="width: 200px; background-color: white;" class="wrapper">
+  <div class="width-test" style="width: calc(min(100px,100%));">calc(min(100px,100%)) - where 100% is 200px</div>
+</div>
+<div class="height-test" style="height: calc(min(200px, 100px));">calc(min(200px,100px))</div>
+
+<br/><br/>Bare max()
+<div class="width-test" style="width: max(100px);">max(100px)</div>
+<div class="width-test" style="width: calc(max(150px, 100px) - 50px);">calc(max(150px, 100px) - 50px)</div>
+<div class="width-test" style="width: max(50px,100px);">max(50px,100px)</div>
+<div class="width-test" style="width: max(50px,100px,20px);">max(50px,100px,20px)</div>
+<div class="width-test" style="width: max(120px - 50px,100px);">max(120px - 50px,100px)</div>
 <div style="width: 50px; background-color: white;" class="wrapper">
-  <div class="width-test" style="width: -webkit-max(100%,100px);">max(100%,100px) - where 100% is 50px</div>
+  <div class="width-test" style="width: max(100%,100px);">max(100%,100px) - where 100% is 50px</div>
 </div>
 <div style="width: 50px; background-color: white;" class="wrapper">
-  <div class="width-test" style="width: -webkit-max(100px,100%);">max(100px,100%) - where 100% is 50px</div>
+  <div class="width-test" style="width: max(100px,100%);">max(100px,100%) - where 100% is 50px</div>
 </div>
 
-<div class="height-test" style="height: -webkit-min(200px, 100px);">min(200px,100px)</div>
+<br/><br/>max() inside calc()
+<div class="width-test" style="width: calc(max(100px));">calc(max(100px))</div>
+<div class="width-test" style="width: calc(max(50px,100px));">calc(max(50px,100px))</div>
+<div class="width-test" style="width: calc(max(50px,100px,20px));">calc(max(50px,100px,20px))</div>
+<div class="width-test" style="width: calc(max(120px - 50px,100px));">calc(max(120px - 50px,100px))</div>
+<div style="width: 50px; background-color: white;" class="wrapper">
+  <div class="width-test" style="width: calc(max(100%,100px));">calc(max(100%,100px)) - where 100% is 50px</div>
+</div>
+<div style="width: 50px; background-color: white;" class="wrapper">
+  <div class="width-test" style="width: calc(max(100px,100%));">calc(max(100px,100%)) - where 100% is 50px</div>
+</div>
 
 </div>
 
index 26f9691..34a263f 100644 (file)
@@ -1,3 +1,131 @@
+2017-09-18  Tim Horton  <timothy_horton@apple.com>
+
+        Support min() and max() in calc()
+        https://bugs.webkit.org/show_bug.cgi?id=167000
+        <rdar://problem/30153481>
+
+        Reviewed by David Hyatt.
+        Patch originally by Myles Maxfield.
+
+        Add two new toplevel functions to CSS, min() and max(), which take an
+        arbirary number of arguments and resolve to the minimum and maximum of
+        the resolved value of the arguments, respectively. It is also possible
+        to use min() and max() inside calc(), and to use calc()-like math
+        inside min() and max().
+
+        * css/CSSCalculationValue.cpp:
+        (WebCore::determineCategory):
+        min and max operators don't use determineCategory; we have a specific
+        implementation for them in createMinOrMax.
+
+        (WebCore::resolvedTypeForMinOrMax):
+        The spec says that min() and max() should be marked as invalid if they
+        have values of more than one type, but that percentages should resolve
+        against the destination type before making this determination. So,
+        if the destination type is length, percent turns into percent-length,
+        and similarly for number.
+
+        (WebCore::isIntegerResult):
+        Add an n-way implementation of isIntegerResult.
+
+        (WebCore::isSamePair):
+        (WebCore::CSSCalcOperation::createMinOrMax): Create a min() or max()
+        operation, as long as the types of arguments are all the same. Allow
+        lengths to upgrade the whole operation to percent-length, and numbers
+        to percent-number, which will cause us to use CalculationValue and friends
+        in order to do proper resolution of all of the parameters instead of
+        just comparing their numeric values.
+
+        (WebCore::CSSCalcOperation::createCalcExpression):
+        (WebCore::CSSCalcOperation::doubleValue):
+        (WebCore::CSSCalcOperation::computeLengthPx):
+        (WebCore::CSSCalcOperation::customCSSText):
+        (WebCore::CSSCalcOperation::primitiveType):
+        (WebCore::CSSCalcOperation::CSSCalcOperation):
+        (WebCore::CSSCalcOperation::evaluate):
+        (WebCore::CSSCalcOperation::evaluateOperator):
+        Adapt to child counts greater than two.
+
+        (WebCore::CSSCalcOperation::buildCssText):
+        Add support for min() and max().
+
+        (WebCore::CSSCalcExpressionNodeParser::parseCalc):
+        parseCalc now accepts a CSSValueID parameter indicating which calc function
+        it should parse (calc, webkit-calc, min, or max), and delegates to either
+        parseValueExpression or parseMinMaxExpression.
+
+        (WebCore::CSSCalcExpressionNodeParser::operatorValue):
+        (WebCore::CSSCalcExpressionNodeParser::parseValue):
+        If min() or max() are found while parsing a value (i.e. nested inside
+        either calc or themselves), use parseMinMaxExpression on that subtree.
+
+        (WebCore::CSSCalcExpressionNodeParser::parseValueTerm):
+        (WebCore::CSSCalcExpressionNodeParser::parseValueMultiplicativeExpression):
+        (WebCore::CSSCalcExpressionNodeParser::parseAdditiveValueExpression):
+        Adjust to the CSSCalcBinaryOperation->CSSCalcOperation rename.
+
+        (WebCore::CSSCalcExpressionNodeParser::parseMinMaxExpression):
+        Added. Parse an arbitrary number of comma-and-whitespace-separated children.
+
+        (WebCore::createBlendHalf):
+        Adjust to the CSSCalcBinaryOperation->CSSCalcOperation rename.
+
+        (WebCore::createCSS):
+        Build the CSSCalcOperation for the platform-independent min and max operations.
+
+        (WebCore::CSSCalcValue::create):
+        Pass the function being parsed and the destination calc category for the
+        property being parsed for into create, and then into the parser so that
+        it can know which function it is parsing for, and what kind of result it
+        needs (as previously mentioned above in resolvedTypeForMinOrMax).
+
+        * css/CSSCalculationValue.h:
+        * css/CSSValueKeywords.in:
+        Add min and max functions as CSS keywords.
+
+        * css/StyleBuilderConverter.h:
+        (WebCore::StyleBuilderConverter::convertLength):
+        (WebCore::StyleBuilderConverter::convertTo100PercentMinusLength):
+        * platform/Length.cpp:
+        (WebCore::convertTo100PercentMinusLength):
+        Adapt to the CalcExpressionOperation constructor taking a vector of
+        arguments instead of two.
+
+        * css/parser/CSSPropertyParserHelpers.cpp:
+        (WebCore::CSSPropertyParserHelpers::CalcParser::CalcParser):
+        Store and pass the specific function being parsed down into CSSCalcValue.
+
+        (WebCore::CSSPropertyParserHelpers::consumeInteger):
+        (WebCore::CSSPropertyParserHelpers::consumePositiveIntegerRaw):
+        (WebCore::CSSPropertyParserHelpers::consumeNumberRaw):
+        (WebCore::CSSPropertyParserHelpers::consumeNumber):
+        (WebCore::CSSPropertyParserHelpers::consumeFontWeightNumber):
+        (WebCore::CSSPropertyParserHelpers::consumeLength):
+        (WebCore::CSSPropertyParserHelpers::consumePercent):
+        (WebCore::CSSPropertyParserHelpers::consumeLengthOrPercent):
+        (WebCore::CSSPropertyParserHelpers::consumeAngle):
+        (WebCore::CSSPropertyParserHelpers::consumeTime):
+        Pass the destination type into each calc parser.
+
+        * platform/CalculationValue.cpp:
+        (WebCore::CalcExpressionOperation::evaluate const):
+        (WebCore::CalcExpressionOperation::operator== const):
+        (WebCore::CalcExpressionOperation::dump const):
+        (WebCore::operator<<):
+        (WebCore::CalcExpressionBinaryOperation::evaluate const): Deleted.
+        (WebCore::CalcExpressionBinaryOperation::operator== const): Deleted.
+        (WebCore::CalcExpressionBinaryOperation::dump const): Deleted.
+        * platform/CalculationValue.h:
+        (WebCore::CalcExpressionOperation::CalcExpressionOperation):
+        (WebCore::operator==):
+        (WebCore::toCalcExpressionOperation):
+        (WebCore::CalcExpressionBinaryOperation::CalcExpressionBinaryOperation): Deleted.
+        (WebCore::toCalcExpressionBinaryOperation): Deleted.
+        Adjust to the CSSCalcBinaryOperation->CSSCalcOperation rename.
+        Adjust to having n>2 children.
+        Support min() and max() operators in various places.
+        
+
 2017-09-18  Basuke Suzuki  <Basuke.Suzuki@sony.com>
 
         [Curl] Move error generation task into ResourceError
index 5976d7f..b758301 100644 (file)
@@ -325,12 +325,40 @@ static CalculationCategory determineCategory(const CSSCalcExpressionNode& leftSi
         if (rightCategory != CalcNumber || rightSide.isZero())
             return CalcOther;
         return leftCategory;
+    case CalcMin:
+    case CalcMax:
+        ASSERT_NOT_REACHED();
+        return CalcOther;
     }
 
     ASSERT_NOT_REACHED();
     return CalcOther;
 }
 
+static CalculationCategory resolvedTypeForMinOrMax(CalculationCategory category, CalculationCategory destinationCategory)
+{
+    switch (category) {
+    case CalcNumber:
+    case CalcLength:
+    case CalcPercentNumber:
+    case CalcPercentLength:
+    case CalcAngle:
+    case CalcTime:
+    case CalcFrequency:
+    case CalcOther:
+        return category;
+
+    case CalcPercent:
+        if (destinationCategory == CalcLength)
+            return CalcPercentLength;
+        if (destinationCategory == CalcNumber)
+            return CalcPercentNumber;
+        return category;
+    }
+
+    return CalcOther;
+}
+
 static inline bool isIntegerResult(CalcOperator op, const CSSCalcExpressionNode& leftSide, const CSSCalcExpressionNode& rightSide)
 {
     // Performs W3C spec's type checking for calc integers.
@@ -338,10 +366,30 @@ static inline bool isIntegerResult(CalcOperator op, const CSSCalcExpressionNode&
     return op != CalcDivide && leftSide.isInteger() && rightSide.isInteger();
 }
 
-class CSSCalcBinaryOperation final : public CSSCalcExpressionNode {
+static inline bool isIntegerResult(CalcOperator op, const Vector<Ref<CSSCalcExpressionNode>>& nodes)
+{
+    // Performs W3C spec's type checking for calc integers.
+    // http://www.w3.org/TR/css3-values/#calc-type-checking
+    if (op == CalcDivide)
+        return false;
+
+    for (auto& node : nodes) {
+        if (!node->isInteger())
+            return false;
+    }
+
+    return true;
+}
+
+static bool isSamePair(CalculationCategory a, CalculationCategory b, CalculationCategory x, CalculationCategory y)
+{
+    return (a == x && b == y) || (a == y && b == x);
+}
+
+class CSSCalcOperation final : public CSSCalcExpressionNode {
     WTF_MAKE_FAST_ALLOCATED;
 public:
-    static RefPtr<CSSCalcBinaryOperation> create(CalcOperator op, RefPtr<CSSCalcExpressionNode>&& leftSide, RefPtr<CSSCalcExpressionNode>&& rightSide)
+    static RefPtr<CSSCalcOperation> create(CalcOperator op, RefPtr<CSSCalcExpressionNode>&& leftSide, RefPtr<CSSCalcExpressionNode>&& rightSide)
     {
         if (!leftSide || !rightSide)
             return nullptr;
@@ -352,7 +400,39 @@ public:
         auto newCategory = determineCategory(*leftSide, *rightSide, op);
         if (newCategory == CalcOther)
             return nullptr;
-        return adoptRef(new CSSCalcBinaryOperation(newCategory, op, leftSide.releaseNonNull(), rightSide.releaseNonNull()));
+
+        return adoptRef(new CSSCalcOperation(newCategory, op, leftSide.releaseNonNull(), rightSide.releaseNonNull()));
+    }
+
+    static RefPtr<CSSCalcOperation> createMinOrMax(CalcOperator op, Vector<Ref<CSSCalcExpressionNode>>&& values, CalculationCategory destinationCategory)
+    {
+        ASSERT(op == CalcMin || op == CalcMax);
+
+        std::optional<CalculationCategory> category = std::nullopt;
+        for (auto& value : values) {
+            auto valueCategory = resolvedTypeForMinOrMax(value->category(), destinationCategory);
+
+            ASSERT(valueCategory < CalcOther);
+            if (!category) {
+                if (valueCategory == CalcOther)
+                    return nullptr;
+                category = valueCategory;
+            }
+
+            if (category != valueCategory) {
+                if (isSamePair(category.value(), valueCategory, CalcLength, CalcPercentLength)) {
+                    category = CalcPercentLength;
+                    continue;
+                }
+                if (isSamePair(category.value(), valueCategory, CalcNumber, CalcPercentNumber)) {
+                    category = CalcPercentNumber;
+                    continue;
+                }
+                return nullptr;
+            }
+        }
+
+        return adoptRef(new CSSCalcOperation(category.value(), op, WTFMove(values)));
     }
 
     static RefPtr<CSSCalcExpressionNode> createSimplified(CalcOperator op, RefPtr<CSSCalcExpressionNode>&& leftSide, RefPtr<CSSCalcExpressionNode>&& rightSide)
@@ -370,7 +450,7 @@ public:
         // Simplify numbers.
         if (leftCategory == CalcNumber && rightCategory == CalcNumber) {
             CSSPrimitiveValue::UnitType evaluationType = CSSPrimitiveValue::CSS_NUMBER;
-            return CSSCalcPrimitiveValue::create(evaluateOperator(op, leftSide->doubleValue(), rightSide->doubleValue()), evaluationType, isInteger);
+            return CSSCalcPrimitiveValue::create(evaluateOperator(op, { leftSide->doubleValue(), rightSide->doubleValue() }), evaluationType, isInteger);
         }
 
         // Simplify addition and subtraction between same types.
@@ -380,14 +460,14 @@ public:
                 if (hasDoubleValue(leftType)) {
                     CSSPrimitiveValue::UnitType rightType = rightSide->primitiveType();
                     if (leftType == rightType)
-                        return CSSCalcPrimitiveValue::create(evaluateOperator(op, leftSide->doubleValue(), rightSide->doubleValue()), leftType, isInteger);
+                        return CSSCalcPrimitiveValue::create(evaluateOperator(op, { leftSide->doubleValue(), rightSide->doubleValue() }), leftType, isInteger);
                     CSSPrimitiveValue::UnitCategory leftUnitCategory = CSSPrimitiveValue::unitCategory(leftType);
                     if (leftUnitCategory != CSSPrimitiveValue::UOther && leftUnitCategory == CSSPrimitiveValue::unitCategory(rightType)) {
                         CSSPrimitiveValue::UnitType canonicalType = CSSPrimitiveValue::canonicalUnitTypeForCategory(leftUnitCategory);
                         if (canonicalType != CSSPrimitiveValue::CSS_UNKNOWN) {
                             double leftValue = leftSide->doubleValue() * CSSPrimitiveValue::conversionToCanonicalUnitsScaleFactor(leftType);
                             double rightValue = rightSide->doubleValue() * CSSPrimitiveValue::conversionToCanonicalUnitsScaleFactor(rightType);
-                            return CSSCalcPrimitiveValue::create(evaluateOperator(op, leftValue, rightValue), canonicalType, isInteger);
+                            return CSSCalcPrimitiveValue::create(evaluateOperator(op, { leftValue, rightValue }), canonicalType, isInteger);
                         }
                     }
                 }
@@ -410,7 +490,7 @@ public:
 
             auto otherType = otherSide.primitiveType();
             if (hasDoubleValue(otherType))
-                return CSSCalcPrimitiveValue::create(evaluateOperator(op, otherSide.doubleValue(), number), otherType, isInteger);
+                return CSSCalcPrimitiveValue::create(evaluateOperator(op, { otherSide.doubleValue(), number }), otherType, isInteger);
         }
 
         return create(op, leftSide.releaseNonNull(), rightSide.releaseNonNull());
@@ -424,36 +504,63 @@ private:
 
     std::unique_ptr<CalcExpressionNode> createCalcExpression(const CSSToLengthConversionData& conversionData) const final
     {
-        auto left = m_leftSide->createCalcExpression(conversionData);
-        if (!left)
-            return nullptr;
-        auto right = m_rightSide->createCalcExpression(conversionData);
-        if (!right)
-            return nullptr;
-        return std::make_unique<CalcExpressionBinaryOperation>(WTFMove(left), WTFMove(right), m_operator);
+        Vector<std::unique_ptr<CalcExpressionNode>> nodes;
+        nodes.reserveInitialCapacity(m_children.size());
+
+        for (auto& child : m_children) {
+            auto node = child->createCalcExpression(conversionData);
+            if (!node)
+                return nullptr;
+            nodes.uncheckedAppend(WTFMove(node));
+        }
+        return std::make_unique<CalcExpressionOperation>(WTFMove(nodes), m_operator);
     }
 
     double doubleValue() const final
     {
-        return evaluate(m_leftSide->doubleValue(), m_rightSide->doubleValue());
+        Vector<double> doubleValues;
+        for (auto& child : m_children)
+            doubleValues.append(child->doubleValue());
+        return evaluate(doubleValues);
     }
 
     double computeLengthPx(const CSSToLengthConversionData& conversionData) const final
     {
-        const double leftValue = m_leftSide->computeLengthPx(conversionData);
-        const double rightValue = m_rightSide->computeLengthPx(conversionData);
-        return evaluate(leftValue, rightValue);
+        Vector<double> doubleValues;
+        for (auto& child : m_children)
+            doubleValues.append(child->computeLengthPx(conversionData));
+        return evaluate(doubleValues);
     }
 
-    static String buildCssText(const String& leftExpression, const String& rightExpression, CalcOperator op)
+    static String buildCssText(Vector<String> childExpressions, CalcOperator op)
     {
         StringBuilder result;
         result.append('(');
-        result.append(leftExpression);
-        result.append(' ');
-        result.append(static_cast<char>(op));
-        result.append(' ');
-        result.append(rightExpression);
+        switch (op) {
+        case CalcAdd:
+        case CalcSubtract:
+        case CalcMultiply:
+        case CalcDivide:
+            ASSERT(childExpressions.size() == 2);
+            result.append(childExpressions[0]);
+            result.append(' ');
+            result.append(static_cast<char>(op));
+            result.append(' ');
+            result.append(childExpressions[1]);
+            break;
+        case CalcMin:
+        case CalcMax:
+            ASSERT(!childExpressions.isEmpty());
+            const char* functionName = op == CalcMin ? "min(" : "max(";
+            result.append(functionName);
+            result.append(childExpressions[0]);
+            for (size_t i = 1; i < childExpressions.size(); ++i) {
+                result.append(',');
+                result.append(' ');
+                result.append(childExpressions[i]);
+            }
+            result.append(')');
+        }
         result.append(')');
 
         return result.toString();
@@ -461,7 +568,10 @@ private:
 
     String customCSSText() const final
     {
-        return buildCssText(m_leftSide->customCSSText(), m_rightSide->customCSSText(), m_operator);
+        Vector<String> cssTexts;
+        for (auto& child : m_children)
+            cssTexts.append(child->customCSSText());
+        return buildCssText(cssTexts, m_operator);
     }
 
     bool equals(const CSSCalcExpressionNode& exp) const final
@@ -469,30 +579,45 @@ private:
         if (type() != exp.type())
             return false;
 
-        const CSSCalcBinaryOperation& other = static_cast<const CSSCalcBinaryOperation&>(exp);
-        return compareCSSValuePtr(m_leftSide, other.m_leftSide)
-            && compareCSSValuePtr(m_rightSide, other.m_rightSide)
-            && m_operator == other.m_operator;
+        const CSSCalcOperation& other = static_cast<const CSSCalcOperation&>(exp);
+
+        if (m_children.size() != other.m_children.size() || m_operator != other.m_operator)
+            return false;
+
+        for (size_t i = 0; i < m_children.size(); ++i) {
+            if (!compareCSSValue(m_children[i], other.m_children[i]))
+                return false;
+        }
+        return true;
     }
 
-    Type type() const final { return CssCalcBinaryOperation; }
+    Type type() const final { return CssCalcOperation; }
 
     CSSPrimitiveValue::UnitType primitiveType() const final
     {
         switch (category()) {
         case CalcNumber:
-            ASSERT(m_leftSide->category() == CalcNumber && m_rightSide->category() == CalcNumber);
+#if !ASSERT_DISABLED
+            for (auto& child : m_children)
+                ASSERT(child->category() == CalcNumber);
+#endif
             return CSSPrimitiveValue::CSS_NUMBER;
         case CalcLength:
         case CalcPercent: {
-            if (m_leftSide->category() == CalcNumber)
-                return m_rightSide->primitiveType();
-            if (m_rightSide->category() == CalcNumber)
-                return m_leftSide->primitiveType();
-            CSSPrimitiveValue::UnitType leftType = m_leftSide->primitiveType();
-            if (leftType == m_rightSide->primitiveType())
-                return leftType;
-            return CSSPrimitiveValue::CSS_UNKNOWN;
+            if (m_children.isEmpty())
+                return CSSPrimitiveValue::CSS_UNKNOWN;
+            if (m_children.size() == 2) {
+                if (m_children[0]->category() == CalcNumber)
+                    return m_children[1]->primitiveType();
+                if (m_children[1]->category() == CalcNumber)
+                    return m_children[0]->primitiveType();
+            }
+            CSSPrimitiveValue::UnitType firstType = m_children[0]->primitiveType();
+            for (auto& child : m_children) {
+                if (firstType != child->primitiveType())
+                    return CSSPrimitiveValue::CSS_UNKNOWN;
+            }
+            return firstType;
         }
         case CalcAngle:
             return CSSPrimitiveValue::CSS_DEG;
@@ -509,10 +634,17 @@ private:
         return CSSPrimitiveValue::CSS_UNKNOWN;
     }
 
-    CSSCalcBinaryOperation(CalculationCategory category, CalcOperator op, Ref<CSSCalcExpressionNode>&& leftSide, Ref<CSSCalcExpressionNode>&& rightSide)
+    CSSCalcOperation(CalculationCategory category, CalcOperator op, Ref<CSSCalcExpressionNode>&& leftSide, Ref<CSSCalcExpressionNode>&& rightSide)
         : CSSCalcExpressionNode(category, isIntegerResult(op, leftSide.get(), rightSide.get()))
-        , m_leftSide(WTFMove(leftSide))
-        , m_rightSide(WTFMove(rightSide))
+        , m_operator(op)
+    {
+        m_children.append(WTFMove(leftSide));
+        m_children.append(WTFMove(rightSide));
+    }
+
+    CSSCalcOperation(CalculationCategory category, CalcOperator op, Vector<Ref<CSSCalcExpressionNode>>&& children)
+        : CSSCalcExpressionNode(category, isIntegerResult(op, children))
+        , m_children(WTFMove(children))
         , m_operator(op)
     {
     }
@@ -526,31 +658,50 @@ private:
         return nullptr;
     }
 
-    double evaluate(double leftSide, double rightSide) const
+    double evaluate(const Vector<double>& children) const
     {
-        return evaluateOperator(m_operator, leftSide, rightSide);
+        return evaluateOperator(m_operator, children);
     }
 
-    static double evaluateOperator(CalcOperator op, double leftValue, double rightValue)
+    static double evaluateOperator(CalcOperator op, const Vector<double>& children)
     {
         switch (op) {
         case CalcAdd:
-            return leftValue + rightValue;
+            ASSERT(children.size() == 2);
+            return children[0] + children[1];
         case CalcSubtract:
-            return leftValue - rightValue;
+            ASSERT(children.size() == 2);
+            return children[0] - children[1];
         case CalcMultiply:
-            return leftValue * rightValue;
+            ASSERT(children.size() == 2);
+            return children[0] * children[1];
         case CalcDivide:
-            if (rightValue)
-                return leftValue / rightValue;
-            return std::numeric_limits<double>::quiet_NaN();
+            ASSERT(children.size() == 1 || children.size() == 2);
+            if (children.size() == 1)
+                return std::numeric_limits<double>::quiet_NaN();
+            return children[0] / children[1];
+        case CalcMin: {
+            if (children.isEmpty())
+                return std::numeric_limits<double>::quiet_NaN();
+            double minimum = children[0];
+            for (auto child : children)
+                minimum = std::min(minimum, child);
+            return minimum;
+        }
+        case CalcMax: {
+            if (children.isEmpty())
+                return std::numeric_limits<double>::quiet_NaN();
+            double maximum = children[0];
+            for (auto child : children)
+                maximum = std::max(maximum, child);
+            return maximum;
+        }
         }
         ASSERT_NOT_REACHED();
         return 0;
     }
 
-    const RefPtr<CSSCalcExpressionNode> m_leftSide;
-    const RefPtr<CSSCalcExpressionNode> m_rightSide;
+    Vector<Ref<CSSCalcExpressionNode>> m_children;
     const CalcOperator m_operator;
 };
 
@@ -566,11 +717,19 @@ static ParseState checkDepthAndIndex(int* depth, CSSParserTokenRange tokens)
 
 class CSSCalcExpressionNodeParser {
 public:
-    RefPtr<CSSCalcExpressionNode> parseCalc(CSSParserTokenRange tokens)
+    explicit CSSCalcExpressionNodeParser(CalculationCategory destinationCategory)
+        : m_destinationCategory(destinationCategory)
+    { }
+
+    RefPtr<CSSCalcExpressionNode> parseCalc(CSSParserTokenRange tokens, CSSValueID function)
     {
         Value result;
         tokens.consumeWhitespace();
-        bool ok = parseValueExpression(tokens, 0, &result);
+        bool ok = false;
+        if (function == CSSValueCalc || function == CSSValueWebkitCalc)
+            ok = parseValueExpression(tokens, 0, &result);
+        else if (function == CSSValueMin || function == CSSValueMax)
+            ok = parseMinMaxExpression(tokens, function, 0, &result);
         if (!ok || !tokens.atEnd())
             return nullptr;
         return result.value;
@@ -608,13 +767,22 @@ private:
     {
         if (checkDepthAndIndex(&depth, tokens) != OK)
             return false;
+
+        auto functionId = tokens.peek().functionId();
         
-        if (tokens.peek().type() == LeftParenthesisToken || tokens.peek().functionId() == CSSValueCalc) {
+        if (tokens.peek().type() == LeftParenthesisToken || functionId == CSSValueCalc) {
             CSSParserTokenRange innerRange = tokens.consumeBlock();
             tokens.consumeWhitespace();
             innerRange.consumeWhitespace();
             return parseValueExpression(innerRange, depth, result);
         }
+
+        if (functionId == CSSValueMax || functionId == CSSValueMin) {
+            CSSParserTokenRange innerRange = tokens.consumeBlock();
+            tokens.consumeWhitespace();
+            innerRange.consumeWhitespace();
+            return parseMinMaxExpression(innerRange, functionId, depth, result);
+        }
         
         return parseValue(tokens, result);
     }
@@ -637,7 +805,7 @@ private:
             if (!parseValueTerm(tokens, depth, &rhs))
                 return false;
             
-            result->value = CSSCalcBinaryOperation::createSimplified(static_cast<CalcOperator>(operatorCharacter), WTFMove(result->value), WTFMove(rhs.value));
+            result->value = CSSCalcOperation::createSimplified(static_cast<CalcOperator>(operatorCharacter), WTFMove(result->value), WTFMove(rhs.value));
 
             if (!result->value)
                 return false;
@@ -669,23 +837,55 @@ private:
             if (!parseValueMultiplicativeExpression(tokens, depth, &rhs))
                 return false;
             
-            result->value = CSSCalcBinaryOperation::createSimplified(static_cast<CalcOperator>(operatorCharacter), WTFMove(result->value), WTFMove(rhs.value));
+            result->value = CSSCalcOperation::createSimplified(static_cast<CalcOperator>(operatorCharacter), WTFMove(result->value), WTFMove(rhs.value));
             if (!result->value)
                 return false;
         }
         
         return true;
     }
-    
+
+    bool parseMinMaxExpression(CSSParserTokenRange& tokens, CSSValueID minMaxFunction, int depth, Value* result)
+    {
+        if (checkDepthAndIndex(&depth, tokens) != OK)
+            return false;
+
+        CalcOperator op = (minMaxFunction == CSSValueMin) ? CalcMin : CalcMax;
+
+        Value value;
+        if (!parseValueExpression(tokens, depth, &value))
+            return false;
+
+        Vector<Ref<CSSCalcExpressionNode>> nodes;
+        nodes.append(value.value.releaseNonNull());
+
+        while (!tokens.atEnd()) {
+            tokens.consumeWhitespace();
+            if (tokens.consume().type() != CommaToken)
+                return false;
+            tokens.consumeWhitespace();
+
+            if (!parseValueExpression(tokens, depth, &value))
+                return false;
+
+            nodes.append(value.value.releaseNonNull());
+        }
+
+        result->value = CSSCalcOperation::createMinOrMax(op, WTFMove(nodes), m_destinationCategory);
+        return result->value;
+    }
+
     bool parseValueExpression(CSSParserTokenRange& tokens, int depth, Value* result)
     {
         return parseAdditiveValueExpression(tokens, depth, result);
     }
+
+    CalculationCategory m_destinationCategory;
 };
 
-static inline RefPtr<CSSCalcBinaryOperation> createBlendHalf(const Length& length, const RenderStyle& style, float progress)
+static inline RefPtr<CSSCalcOperation> createBlendHalf(const Length& length, const RenderStyle& style, float progress)
 {
-    return CSSCalcBinaryOperation::create(CalcMultiply, createCSS(length, style),
+    return CSSCalcOperation::create(CalcMultiply, createCSS(length, style),
         CSSCalcPrimitiveValue::create(CSSPrimitiveValue::create(progress, CSSPrimitiveValue::CSS_NUMBER), !progress || progress == 1));
 }
 
@@ -698,15 +898,32 @@ static RefPtr<CSSCalcExpressionNode> createCSS(const CalcExpressionNode& node, c
     }
     case CalcExpressionNodeLength:
         return createCSS(toCalcExpressionLength(node).length(), style);
-    case CalcExpressionNodeBinaryOperation: {
-        auto& binaryNode = toCalcExpressionBinaryOperation(node);
-        return CSSCalcBinaryOperation::create(binaryNode.getOperator(), createCSS(binaryNode.leftSide(), style), createCSS(binaryNode.rightSide(), style));
+    case CalcExpressionNodeOperation: {
+        auto& operationNode = toCalcExpressionOperation(node);
+        auto& operationChildren = operationNode.children();
+        CalcOperator op = operationNode.getOperator();
+        if (op == CalcMin || op == CalcMax) {
+            Vector<Ref<CSSCalcExpressionNode>> values;
+            values.reserveInitialCapacity(operationChildren.size());
+            for (auto& child : operationChildren) {
+                auto cssNode = createCSS(*child, style);
+                if (!cssNode)
+                    return nullptr;
+                values.uncheckedAppend(*cssNode);
+            }
+            return CSSCalcOperation::createMinOrMax(operationNode.getOperator(), WTFMove(values), CalcOther);
+        }
+
+        if (operationChildren.size() == 2)
+            return CSSCalcOperation::create(operationNode.getOperator(), createCSS(*operationChildren[0], style), createCSS(*operationChildren[1], style));
+
+        return nullptr;
     }
     case CalcExpressionNodeBlendLength: {
         // FIXME: (http://webkit.org/b/122036) Create a CSSCalcExpressionNode equivalent of CalcExpressionBlendLength.
         auto& blend = toCalcExpressionBlendLength(node);
         float progress = blend.progress();
-        return CSSCalcBinaryOperation::create(CalcAdd, createBlendHalf(blend.from(), style, 1 - progress), createBlendHalf(blend.to(), style, progress));
+        return CSSCalcOperation::create(CalcAdd, createBlendHalf(blend.from(), style, 1 - progress), createBlendHalf(blend.to(), style, progress));
     }
     case CalcExpressionNodeUndefined:
         ASSERT_NOT_REACHED();
@@ -736,10 +953,10 @@ static RefPtr<CSSCalcExpressionNode> createCSS(const Length& length, const Rende
     return nullptr;
 }
 
-RefPtr<CSSCalcValue> CSSCalcValue::create(const CSSParserTokenRange& tokens, ValueRange range)
+RefPtr<CSSCalcValue> CSSCalcValue::create(CSSValueID function, const CSSParserTokenRange& tokens, CalculationCategory destinationCategory, ValueRange range)
 {
-    CSSCalcExpressionNodeParser parser;
-    auto expression = parser.parseCalc(tokens);
+    CSSCalcExpressionNodeParser parser(destinationCategory);
+    auto expression = parser.parseCalc(tokens, function);
     if (!expression)
         return nullptr;
     return adoptRef(new CSSCalcValue(expression.releaseNonNull(), range != ValueRangeAll));
index 23f9fdd..2c77c64 100644 (file)
@@ -55,7 +55,7 @@ class CSSCalcExpressionNode : public RefCounted<CSSCalcExpressionNode> {
 public:
     enum Type {
         CssCalcPrimitiveValue = 1,
-        CssCalcBinaryOperation
+        CssCalcOperation
     };
 
     virtual ~CSSCalcExpressionNode() { }
@@ -85,8 +85,8 @@ private:
 
 class CSSCalcValue final : public CSSValue {
 public:
-    static RefPtr<CSSCalcValue> create(const CSSParserTokenRange&, ValueRange);
-    
+    static RefPtr<CSSCalcValue> create(CSSValueID function, const CSSParserTokenRange&, CalculationCategory destinationCategory, ValueRange);
+
     static RefPtr<CSSCalcValue> create(const CalculationValue&, const RenderStyle&);
 
     CalculationCategory category() const { return m_expression->category(); }
index 84ea803..4c94e3e 100644 (file)
@@ -1249,6 +1249,8 @@ path
 
 calc
 -webkit-calc
+min
+max
 
 #if defined(ENABLE_CSS_IMAGE_RESOLUTION) && ENABLE_CSS_IMAGE_RESOLUTION
 from-image
index be05fe1..dd40158 100644 (file)
@@ -322,9 +322,11 @@ inline Length StyleBuilderConverter::convertTo100PercentMinusLength(const Length
         return Length(100 - length.value(), Percent);
     
     // Turn this into a calc expression: calc(100% - length)
-    auto lhs = std::make_unique<CalcExpressionLength>(Length(100, Percent));
-    auto rhs = std::make_unique<CalcExpressionLength>(length);
-    auto op = std::make_unique<CalcExpressionBinaryOperation>(WTFMove(lhs), WTFMove(rhs), CalcSubtract);
+    Vector<std::unique_ptr<CalcExpressionNode>> lengths;
+    lengths.reserveInitialCapacity(2);
+    lengths.uncheckedAppend(std::make_unique<CalcExpressionLength>(Length(100, Percent)));
+    lengths.uncheckedAppend(std::make_unique<CalcExpressionLength>(length));
+    auto op = std::make_unique<CalcExpressionOperation>(WTFMove(lengths), CalcSubtract);
     return Length(CalculationValue::create(WTFMove(op), ValueRangeAll));
 }
 
index 349999b..bbe20b8 100644 (file)
@@ -78,13 +78,14 @@ CSSParserTokenRange consumeFunction(CSSParserTokenRange& range)
 class CalcParser {
 
 public:
-    explicit CalcParser(CSSParserTokenRange& range, ValueRange valueRange = ValueRangeAll)
+    explicit CalcParser(CSSParserTokenRange& range, CalculationCategory destinationCategory, ValueRange valueRange = ValueRangeAll)
         : m_sourceRange(range)
         , m_range(range)
     {
         const CSSParserToken& token = range.peek();
-        if (token.functionId() == CSSValueCalc || token.functionId() == CSSValueWebkitCalc)
-            m_calcValue = CSSCalcValue::create(consumeFunction(m_range), valueRange);
+        auto functionId = token.functionId();
+        if (functionId == CSSValueCalc || functionId == CSSValueWebkitCalc || functionId == CSSValueMin || functionId == CSSValueMax)
+            m_calcValue = CSSCalcValue::create(functionId, consumeFunction(m_range), destinationCategory, valueRange);
     }
 
     const CSSCalcValue* value() const { return m_calcValue.get(); }
@@ -137,7 +138,7 @@ RefPtr<CSSPrimitiveValue> consumeInteger(CSSParserTokenRange& range, double mini
             return nullptr;
         return CSSValuePool::singleton().createValue(range.consumeIncludingWhitespace().numericValue(), CSSPrimitiveValue::UnitType::CSS_NUMBER);
     }
-    CalcParser calcParser(range);
+    CalcParser calcParser(range, CalcNumber);
     if (const CSSCalcValue* calculation = calcParser.value()) {
         if (calculation->category() != CalcNumber || !calculation->isInt())
             return nullptr;
@@ -163,7 +164,7 @@ bool consumePositiveIntegerRaw(CSSParserTokenRange& range, int& result)
         result = range.consumeIncludingWhitespace().numericValue();
         return true;
     }
-    CalcParser calcParser(range);
+    CalcParser calcParser(range, CalcNumber);
     return calcParser.consumePositiveIntegerRaw(result);
 }
     
@@ -173,7 +174,7 @@ bool consumeNumberRaw(CSSParserTokenRange& range, double& result)
         result = range.consumeIncludingWhitespace().numericValue();
         return true;
     }
-    CalcParser calcParser(range, ValueRangeAll);
+    CalcParser calcParser(range, CalcNumber, ValueRangeAll);
     return calcParser.consumeNumberRaw(result);
 }
 
@@ -186,7 +187,7 @@ RefPtr<CSSPrimitiveValue> consumeNumber(CSSParserTokenRange& range, ValueRange v
             return nullptr;
         return CSSValuePool::singleton().createValue(range.consumeIncludingWhitespace().numericValue(), token.unitType());
     }
-    CalcParser calcParser(range, ValueRangeAll);
+    CalcParser calcParser(range, CalcNumber, ValueRangeAll);
     if (const CSSCalcValue* calculation = calcParser.value()) {
         // FIXME: Calcs should not be subject to parse time range checks.
         // spec: https://drafts.csswg.org/css-values-3/#calc-range
@@ -216,7 +217,7 @@ RefPtr<CSSPrimitiveValue> consumeFontWeightNumber(CSSParserTokenRange& range)
         return consumeNumber(range, ValueRangeAll);
 
     // "[For calc()], the used value resulting from an expression must be clamped to the range allowed in the target context."
-    CalcParser calcParser(range, ValueRangeAll);
+    CalcParser calcParser(range, CalcNumber, ValueRangeAll);
     double result;
     if (calcParser.consumeNumberRaw(result)
 #if !ENABLE(VARIATION_FONTS)
@@ -278,7 +279,7 @@ RefPtr<CSSPrimitiveValue> consumeLength(CSSParserTokenRange& range, CSSParserMod
         CSSPrimitiveValue::UnitType unitType = CSSPrimitiveValue::UnitType::CSS_PX;
         return CSSValuePool::singleton().createValue(range.consumeIncludingWhitespace().numericValue(), unitType);
     }
-    CalcParser calcParser(range, valueRange);
+    CalcParser calcParser(range, CalcLength, valueRange);
     if (calcParser.value() && calcParser.value()->category() == CalcLength)
         return calcParser.consumeValue();
     return nullptr;
@@ -292,7 +293,7 @@ RefPtr<CSSPrimitiveValue> consumePercent(CSSParserTokenRange& range, ValueRange
             return nullptr;
         return CSSValuePool::singleton().createValue(range.consumeIncludingWhitespace().numericValue(), CSSPrimitiveValue::UnitType::CSS_PERCENTAGE);
     }
-    CalcParser calcParser(range, valueRange);
+    CalcParser calcParser(range, CalcPercent, valueRange);
     if (const CSSCalcValue* calculation = calcParser.value()) {
         if (calculation->category() == CalcPercent)
             return calcParser.consumeValue();
@@ -321,7 +322,7 @@ RefPtr<CSSPrimitiveValue> consumeLengthOrPercent(CSSParserTokenRange& range, CSS
         return consumeLength(range, cssParserMode, valueRange, unitless);
     if (token.type() == PercentageToken)
         return consumePercent(range, valueRange);
-    CalcParser calcParser(range, valueRange);
+    CalcParser calcParser(range, CalcLength, valueRange);
     if (const CSSCalcValue* calculation = calcParser.value()) {
         if (canConsumeCalcValue(calculation->category(), cssParserMode))
             return calcParser.consumeValue();
@@ -347,7 +348,7 @@ RefPtr<CSSPrimitiveValue> consumeAngle(CSSParserTokenRange& range, CSSParserMode
         return CSSValuePool::singleton().createValue(range.consumeIncludingWhitespace().numericValue(), CSSPrimitiveValue::UnitType::CSS_DEG);
     }
 
-    CalcParser calcParser(range, ValueRangeAll);
+    CalcParser calcParser(range, CalcAngle, ValueRangeAll);
     if (const CSSCalcValue* calculation = calcParser.value()) {
         if (calculation->category() == CalcAngle)
             return calcParser.consumeValue();
@@ -369,7 +370,7 @@ RefPtr<CSSPrimitiveValue> consumeTime(CSSParserTokenRange& range, CSSParserMode
             return CSSValuePool::singleton().createValue(range.consumeIncludingWhitespace().numericValue(), unit);
         return nullptr;
     }
-    CalcParser calcParser(range, valueRange);
+    CalcParser calcParser(range, CalcTime, valueRange);
     if (const CSSCalcValue* calculation = calcParser.value()) {
         if (calculation->category() == CalcTime)
             return calcParser.consumeValue();
index 01dab64..f30216d 100644 (file)
@@ -68,34 +68,74 @@ float CalculationValue::evaluate(float maxValue) const
     return m_shouldClampToNonNegative && result < 0 ? 0 : result;
 }
 
-float CalcExpressionBinaryOperation::evaluate(float maxValue) const
+float CalcExpressionOperation::evaluate(float maxValue) const
 {
-    float left = m_leftSide->evaluate(maxValue);
-    float right = m_rightSide->evaluate(maxValue);
     switch (m_operator) {
-    case CalcAdd:
+    case CalcAdd: {
+        ASSERT(m_children.size() == 2);
+        float left = m_children[0]->evaluate(maxValue);
+        float right = m_children[1]->evaluate(maxValue);
         return left + right;
-    case CalcSubtract:
+    }
+    case CalcSubtract: {
+        ASSERT(m_children.size() == 2);
+        float left = m_children[0]->evaluate(maxValue);
+        float right = m_children[1]->evaluate(maxValue);
         return left - right;
-    case CalcMultiply:
+    }
+    case CalcMultiply: {
+        ASSERT(m_children.size() == 2);
+        float left = m_children[0]->evaluate(maxValue);
+        float right = m_children[1]->evaluate(maxValue);
         return left * right;
-    case CalcDivide:
-        if (!right)
+    }
+    case CalcDivide: {
+        ASSERT(m_children.size() == 1 || m_children.size() == 2);
+        if (m_children.size() == 1)
             return std::numeric_limits<float>::quiet_NaN();
+        float left = m_children[0]->evaluate(maxValue);
+        float right = m_children[1]->evaluate(maxValue);
         return left / right;
     }
+    case CalcMin: {
+        if (m_children.isEmpty())
+            return std::numeric_limits<float>::quiet_NaN();
+        float minimum = m_children[0]->evaluate(maxValue);
+        for (auto& child : m_children)
+            minimum = std::min(minimum, child->evaluate(maxValue));
+        return minimum;
+    }
+    case CalcMax: {
+        if (m_children.isEmpty())
+            return std::numeric_limits<float>::quiet_NaN();
+        float maximum = m_children[0]->evaluate(maxValue);
+        for (auto& child : m_children)
+            maximum = std::max(maximum, child->evaluate(maxValue));
+        return maximum;
+    }
+    }
     ASSERT_NOT_REACHED();
     return std::numeric_limits<float>::quiet_NaN();
 }
 
-bool CalcExpressionBinaryOperation::operator==(const CalcExpressionNode& other) const
+bool CalcExpressionOperation::operator==(const CalcExpressionNode& other) const
 {
-    return other.type() == CalcExpressionNodeBinaryOperation && *this == toCalcExpressionBinaryOperation(other);
+    return other.type() == CalcExpressionNodeOperation && *this == toCalcExpressionOperation(other);
 }
 
-void CalcExpressionBinaryOperation::dump(TextStream& ts) const
+void CalcExpressionOperation::dump(TextStream& ts) const
 {
-    ts << *m_leftSide << " " << m_operator << " " << *m_rightSide;
+    if (m_operator == CalcMin || m_operator == CalcMax) {
+        ts << m_operator << "(";
+        size_t childrenCount = m_children.size();
+        for (size_t i = 0; i < childrenCount; i++) {
+            ts << m_children[i].get();
+            if (i < childrenCount - 1)
+                ts << ", ";
+        }
+        ts << ")";
+    } else
+        ts << m_children[0].get() << " " << m_operator << " " << m_children[1].get();
 }
 
 float CalcExpressionLength::evaluate(float maxValue) const
@@ -135,6 +175,8 @@ TextStream& operator<<(TextStream& ts, CalcOperator op)
     case CalcSubtract: ts << "-"; break;
     case CalcMultiply: ts << "*"; break;
     case CalcDivide: ts << "/"; break;
+    case CalcMin: ts << "max"; break;
+    case CalcMax: ts << "min"; break;
     }
     return ts;
 }
index 37a70ea..cb883d7 100644 (file)
@@ -35,6 +35,7 @@
 #include <memory>
 #include <wtf/Ref.h>
 #include <wtf/RefCounted.h>
+#include <wtf/Vector.h>
 
 namespace WTF {
 class TextStream;
@@ -46,14 +47,16 @@ enum CalcOperator {
     CalcAdd = '+',
     CalcSubtract = '-',
     CalcMultiply = '*',
-    CalcDivide = '/'
+    CalcDivide = '/',
+    CalcMin = 0,
+    CalcMax = 1,
 };
 
 enum CalcExpressionNodeType {
     CalcExpressionNodeUndefined,
     CalcExpressionNodeNumber,
     CalcExpressionNodeLength,
-    CalcExpressionNodeBinaryOperation,
+    CalcExpressionNodeOperation,
     CalcExpressionNodeBlendLength,
 };
 
@@ -101,21 +104,20 @@ private:
     Length m_length;
 };
 
-class CalcExpressionBinaryOperation final : public CalcExpressionNode {
+class CalcExpressionOperation final : public CalcExpressionNode {
 public:
-    CalcExpressionBinaryOperation(std::unique_ptr<CalcExpressionNode> leftSide, std::unique_ptr<CalcExpressionNode> rightSide, CalcOperator);
+    CalcExpressionOperation(Vector<std::unique_ptr<CalcExpressionNode>>&& children, CalcOperator);
 
-    const CalcExpressionNode& leftSide() const { return *m_leftSide; }
-    const CalcExpressionNode& rightSide() const { return *m_rightSide; }
     CalcOperator getOperator() const { return m_operator; }
 
+    const Vector<std::unique_ptr<CalcExpressionNode>>& children() const { return m_children; }
+
 private:
     float evaluate(float maxValue) const override;
     bool operator==(const CalcExpressionNode&) const override;
     void dump(WTF::TextStream&) const override;
 
-    std::unique_ptr<CalcExpressionNode> m_leftSide;
-    std::unique_ptr<CalcExpressionNode> m_rightSide;
+    Vector<std::unique_ptr<CalcExpressionNode>> m_children;
     CalcOperator m_operator;
 };
 
@@ -202,23 +204,22 @@ inline const CalcExpressionLength& toCalcExpressionLength(const CalcExpressionNo
     return static_cast<const CalcExpressionLength&>(value);
 }
 
-inline CalcExpressionBinaryOperation::CalcExpressionBinaryOperation(std::unique_ptr<CalcExpressionNode> leftSide, std::unique_ptr<CalcExpressionNode> rightSide, CalcOperator op)
-    : CalcExpressionNode(CalcExpressionNodeBinaryOperation)
-    , m_leftSide(WTFMove(leftSide))
-    , m_rightSide(WTFMove(rightSide))
+inline CalcExpressionOperation::CalcExpressionOperation(Vector<std::unique_ptr<CalcExpressionNode>>&& children, CalcOperator op)
+    : CalcExpressionNode(CalcExpressionNodeOperation)
+    , m_children(WTFMove(children))
     , m_operator(op)
 {
 }
 
-inline bool operator==(const CalcExpressionBinaryOperation& a, const CalcExpressionBinaryOperation& b)
+inline bool operator==(const CalcExpressionOperation& a, const CalcExpressionOperation& b)
 {
-    return a.getOperator() == b.getOperator() && a.leftSide() == b.leftSide() && a.rightSide() == b.rightSide();
+    return a.getOperator() == b.getOperator() && a.children() == b.children();
 }
 
-inline const CalcExpressionBinaryOperation& toCalcExpressionBinaryOperation(const CalcExpressionNode& value)
+inline const CalcExpressionOperation& toCalcExpressionOperation(const CalcExpressionNode& value)
 {
-    ASSERT_WITH_SECURITY_IMPLICATION(value.type() == CalcExpressionNodeBinaryOperation);
-    return static_cast<const CalcExpressionBinaryOperation&>(value);
+    ASSERT_WITH_SECURITY_IMPLICATION(value.type() == CalcExpressionNodeOperation);
+    return static_cast<const CalcExpressionOperation&>(value);
 }
 
 inline CalcExpressionBlendLength::CalcExpressionBlendLength(Length from, Length to, float progress)
index 7576bd0..0e96072 100644 (file)
@@ -291,9 +291,11 @@ Length convertTo100PercentMinusLength(const Length& length)
         return Length(100 - length.value(), Percent);
     
     // Turn this into a calc expression: calc(100% - length)
-    auto lhs = std::make_unique<CalcExpressionLength>(Length(100, Percent));
-    auto rhs = std::make_unique<CalcExpressionLength>(length);
-    auto op = std::make_unique<CalcExpressionBinaryOperation>(WTFMove(lhs), WTFMove(rhs), CalcSubtract);
+    Vector<std::unique_ptr<CalcExpressionNode>> lengths;
+    lengths.reserveInitialCapacity(2);
+    lengths.uncheckedAppend(std::make_unique<CalcExpressionLength>(Length(100, Percent)));
+    lengths.uncheckedAppend(std::make_unique<CalcExpressionLength>(length));
+    auto op = std::make_unique<CalcExpressionOperation>(WTFMove(lengths), CalcSubtract);
     return Length(CalculationValue::create(WTFMove(op), ValueRangeAll));
 }