Changeset 222190 in webkit


Ignore:
Timestamp:
Sep 18, 2017 4:36:20 PM (7 years ago)
Author:
timothy_horton@apple.com
Message:

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.

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.

Location:
trunk
Files:
14 edited

Legend:

Unmodified
Added
Removed
  • trunk/LayoutTests/ChangeLog

    r222189 r222190  
     12017-09-18  Tim Horton  <timothy_horton@apple.com>
     2
     3        Support min() and max() in calc()
     4        https://bugs.webkit.org/show_bug.cgi?id=167000
     5        <rdar://problem/30153481>
     6
     7        Reviewed by David Hyatt.
     8        Patch originally by Myles Maxfield.
     9
     10        * css3/calc/minmax-errors-expected.txt:
     11        * css3/calc/minmax-errors.html:
     12        * css3/calc/simple-minmax-expected.txt:
     13        * css3/calc/simple-minmax.html:
     14        Revive previously-unused tests for an earlier never-implemented version
     15        of this feature, and add a bunch more interesting test cases from reading the spec.
     16
    1172017-09-18  Per Arne Vollan  <pvollan@apple.com>
    218
  • trunk/LayoutTests/css3/calc/minmax-errors-expected.txt

    r83415 r222190  
    11All boxes below should be 100px * 100px and green.
    22
    3 MIN
     3Bare min()
    44unclosed min => PASS
    55unclosed min with garbage => PASS
     
    1313mix percent and number => PASS
    1414mix number and percent => PASS
    15 MAX
     15
     16
     17min() inside calc()
     18unclosed min => PASS
     19unclosed min with garbage => PASS
     20garbage => PASS
     21extra trailing comma => PASS
     22leading comma => PASS
     23trailing garbage => PASS
     24bad expression => PASS
     25mix length and number => PASS
     26mix number and length => PASS
     27mix percent and number => PASS
     28mix number and percent => PASS
     29
     30
     31Bare max()
    1632unclosed max => PASS
    1733unclosed max with garbage => PASS
     
    2036mix percent and number => PASS
    2137mix number and percent => PASS
     38
     39
     40max() inside calc
     41unclosed max => PASS
     42unclosed max with garbage => PASS
     43mix length and number => PASS
     44mix number and length => PASS
     45mix percent and number => PASS
     46mix number and percent => PASS
  • trunk/LayoutTests/css3/calc/minmax-errors.html

    r119990 r222190  
    1313<div id="test">
    1414
    15 MIN
    16 <div style="width: 100px; width: -webkit-min(">unclosed min</div>
    17 <div style="width: 100px; width: -webkit-min( bob">unclosed min with garbage</div>
    18 <div style="width: 100px; width: -webkit-min( bob );">garbage</div>
    19 <div style="width: 100px; width: -webkit-min(20px,);">extra trailing comma</div>
    20 <div style="width: 100px; width: -webkit-min(,20px);">leading comma</div>
    21 <div style="width: 100px; width: -webkit-min(20px, bob);">trailing garbage</div>
    22 <div style="width: 100px; width: -webmit-min(20px, 10px + flim);">bad expression</div>
    23 <div style="width: 100px; width: -webkit-min(256px, 120);">mix length and number</div>
    24 <div style="width: 100px; width: -webkit-min(256, 120px);">mix number and length</div>
    25 <div style="width: 100px; width: -webkit-min(50%, 150);">mix percent and number</div>
    26 <div style="width: 100px; width: -webkit-min(150, 50%);">mix number and percent</div>
     15Bare min()
     16<div style="width: 100px; width: min(">unclosed min</div>
     17<div style="width: 100px; width: min( bob">unclosed min with garbage</div>
     18<div style="width: 100px; width: min( bob );">garbage</div>
     19<div style="width: 100px; width: min(20px,);">extra trailing comma</div>
     20<div style="width: 100px; width: min(,20px);">leading comma</div>
     21<div style="width: 100px; width: min(20px, bob);">trailing garbage</div>
     22<div style="width: 100px; width: min(20px, 10px + flim);">bad expression</div>
     23<div style="width: 100px; width: min(256px, 120);">mix length and number</div>
     24<div style="width: 100px; width: min(256, 120px);">mix number and length</div>
     25<div style="width: 100px; width: min(50%, 150);">mix percent and number</div>
     26<div style="width: 100px; width: min(150, 50%);">mix number and percent</div>
    2727
    28 MAX
    29 <div style="width: 100px; width: -webkit-max(">unclosed max</div>
    30 <div style="width: 100px; width: -webkit-max( bob">unclosed max with garbage</div>
    31 <div style="width: 100px; width: -webkit-max(256px, 120);">mix length and number</div>
    32 <div style="width: 100px; width: -webkit-max(256, 120px);">mix number and length</div>
    33 <div style="width: 100px; width: -webkit-max(50%, 150);">mix percent and number</div>
    34 <div style="width: 100px; width: -webkit-max(150, 50%);">mix number and percent</div>
     28<br/><br/>min() inside calc()
     29<div style="width: 100px; width: calc(min(">unclosed min</div>
     30<div style="width: 100px; width: calc(min( bob">unclosed min with garbage</div>
     31<div style="width: 100px; width: calc(min( bob ));">garbage</div>
     32<div style="width: 100px; width: calc(min(20px,));">extra trailing comma</div>
     33<div style="width: 100px; width: calc(min(,20px));">leading comma</div>
     34<div style="width: 100px; width: calc(min(20px, bob));">trailing garbage</div>
     35<div style="width: 100px; width: calc(min(20px, 10px + flim));">bad expression</div>
     36<div style="width: 100px; width: calc(min(256px, 120));">mix length and number</div>
     37<div style="width: 100px; width: calc(min(256, 120px));">mix number and length</div>
     38<div style="width: 100px; width: calc(min(50%, 150));">mix percent and number</div>
     39<div style="width: 100px; width: calc(min(150, 50%));">mix number and percent</div>
     40
     41<br/><br/>Bare max()
     42<div style="width: 100px; width: max(">unclosed max</div>
     43<div style="width: 100px; width: max( bob">unclosed max with garbage</div>
     44<div style="width: 100px; width: max(256px, 120);">mix length and number</div>
     45<div style="width: 100px; width: max(256, 120px);">mix number and length</div>
     46<div style="width: 100px; width: max(50%, 150);">mix percent and number</div>
     47<div style="width: 100px; width: max(150, 50%);">mix number and percent</div>
     48
     49<br/><br/>max() inside calc
     50<div style="width: 100px; width: calc(max(">unclosed max</div>
     51<div style="width: 100px; width: calc(max( bob">unclosed max with garbage</div>
     52<div style="width: 100px; width: calc(max(256px, 120));">mix length and number</div>
     53<div style="width: 100px; width: calc(max(256, 120px));">mix number and length</div>
     54<div style="width: 100px; width: calc(max(50%, 150));">mix percent and number</div>
     55<div style="width: 100px; width: calc(max(150, 50%));">mix number and percent</div>
    3556
    3657</div>
  • trunk/LayoutTests/css3/calc/simple-minmax-expected.txt

    r83415 r222190  
    11All boxes below should be 100px * 100px and green.
    22
    3 min(100px) => FAIL: expected width of 100, but was 256
    4 min( 100px ) => FAIL: expected width of 100, but was 256
    5 min((((100px)))) => FAIL: expected width of 100, but was 256
    6 min(150px,100px) => FAIL: expected width of 100, but was 256
    7 min(150px,100px,200px) => FAIL: expected width of 100, but was 256
    8 min( 150px , 100px ,200px) => FAIL: expected width of 100, but was 256
    9 min(90px + 50px ,100px) => FAIL: expected width of 100, but was 256
    10 min(100%,100px) - where 100% is 200px => FAIL: expected width of 100, but was 256
    11 min(100px,100%) - where 100% is 200px => FAIL: expected width of 100, but was 256
    12 max(100px) => FAIL: expected width of 100, but was 256
    13 max(50px,100px) => FAIL: expected width of 100, but was 256
    14 max(50px,100px,20px) => FAIL: expected width of 100, but was 256
    15 max(120px - 50px,100px) => FAIL: expected width of 100, but was 256
    16 max(100%,100px) - where 100% is 50px => FAIL: expected width of 100, but was 256
    17 max(100px,100%) - where 100% is 50px => FAIL: expected width of 100, but was 256
    18 min(200px,100px) => FAIL: expected height of 100, but was 50
     3Bare min()
     4min(100px) => PASS
     5min( 100px ) => PASS
     6min((((100px)))) => PASS
     7min(150px,100px) => PASS
     8min(150px,100px,200px) => PASS
     9min( 150px , 100px ,200px) => PASS
     10min(90px + 50px ,100px) => PASS
     11min(100%,100px) - where 100% is 200px => PASS
     12min(50%,150px) - where 50% is 100px => PASS
     13min(100% - 50px,100px) - where 100% is 200px => PASS
     14min(25% + 50px,150px) - where 25% is 50px => PASS
     15min(100px,100%) - where 100% is 200px => PASS
     16min(200px,100px) => PASS
     17
     18
     19min() inside calc()
     20calc(min(100px)) => PASS
     21calc(min(150px, 200px) - 50px) => PASS
     22calc(50 + min(150px, 50px)) => PASS
     23calc(min(250px, 200px) / 2) => PASS
     24calc(min( 100px )) => PASS
     25calc(min((((100px))))) => PASS
     26calc(min(150px,100px)) => PASS
     27calc(min(150px,100px,200px)) => PASS
     28calc(min( 150px , 100px ,200px)) => PASS
     29calc(min(90px + 50px ,100px)) => PASS
     30calc(min(100%,100px)) - where 100% is 200px => PASS
     31calc(min(50%,150px)) - where 50% is 100px => PASS
     32calc(min(100% - 50px,100px)) - where 100% is 200px => PASS
     33calc(min(25% + 50px,150px)) - where 25% is 50px => PASS
     34calc(min(100px,100%)) - where 100% is 200px => PASS
     35calc(min(200px,100px)) => PASS
     36
     37
     38Bare max()
     39max(100px) => PASS
     40calc(max(150px, 100px) - 50px) => PASS
     41max(50px,100px) => PASS
     42max(50px,100px,20px) => PASS
     43max(120px - 50px,100px) => PASS
     44max(100%,100px) - where 100% is 50px => PASS
     45max(100px,100%) - where 100% is 50px => PASS
     46
     47
     48max() inside calc()
     49calc(max(100px)) => PASS
     50calc(max(50px,100px)) => PASS
     51calc(max(50px,100px,20px)) => PASS
     52calc(max(120px - 50px,100px)) => PASS
     53calc(max(100%,100px)) - where 100% is 50px => PASS
     54calc(max(100px,100%)) - where 100% is 50px => PASS
  • trunk/LayoutTests/css3/calc/simple-minmax.html

    r168840 r222190  
    2020<div id="test">
    2121
    22 <div class="width-test" style="width: -webkit-min(100px);">min(100px)</div>
    23 <div class="width-test" style="width: -webkit-min( 100px );">min( 100px )</div>
    24 <div class="width-test" style="width: -webkit-min((((100px))));">min((((100px))))</div>
    25 <div class="width-test" style="width: -webkit-min(150px,100px);">min(150px,100px)</div>
    26 <div class="width-test" style="width: -webkit-min(150px,100px,200px);">min(150px,100px,200px)</div>
    27 <div class="width-test" style="width: -webkit-min(  150px ,  100px  ,200px);">min(  150px  ,  100px  ,200px)</div>
    28 <div class="width-test" style="width: -webkit-min(90px + 50px ,100px);">min(90px + 50px ,100px)</div>
     22Bare min()
     23<div class="width-test" style="width: min(100px);">min(100px)</div>
     24<div class="width-test" style="width: min( 100px );">min( 100px )</div>
     25<div class="width-test" style="width: min((((100px))));">min((((100px))))</div>
     26<div class="width-test" style="width: min(150px,100px);">min(150px,100px)</div>
     27<div class="width-test" style="width: min(150px,100px,200px);">min(150px,100px,200px)</div>
     28<div class="width-test" style="width: min(  150px ,  100px  ,200px);">min(  150px  ,  100px  ,200px)</div>
     29<div class="width-test" style="width: min(90px + 50px ,100px);">min(90px + 50px ,100px)</div>
    2930<div style="width: 200px; background-color: white;" class="wrapper">
    30   <div class="width-test" style="width: -webkit-min(100%,100px);">min(100%,100px) - where 100% is 200px</div>
     31  <div class="width-test" style="width: min(100%,100px);">min(100%,100px) - where 100% is 200px</div>
    3132</div>
    3233<div style="width: 200px; background-color: white;" class="wrapper">
    33   <div class="width-test" style="width: -webkit-min(100px,100%);">min(100px,100%) - where 100% is 200px</div>
     34  <div class="width-test" style="width: min(50%,150px);">min(50%,150px) - where 50% is 100px</div>
     35</div>
     36<div style="width: 200px; background-color: white;" class="wrapper">
     37  <div class="width-test" style="width: min(100% - 50px,100px);">min(100% - 50px,100px) - where 100% is 200px</div>
     38</div>
     39<div style="width: 200px; background-color: white;" class="wrapper">
     40  <div class="width-test" style="width: min(25% + 50px,150px);">min(25% + 50px,150px) - where 25% is 50px</div>
     41</div>
     42<div style="width: 200px; background-color: white;" class="wrapper">
     43  <div class="width-test" style="width: min(100px,100%);">min(100px,100%) - where 100% is 200px</div>
     44</div>
     45<div class="height-test" style="height: min(200px, 100px);">min(200px,100px)</div>
     46
     47<br/><br/>min() inside calc()
     48<div class="width-test" style="width: calc(min(100px));">calc(min(100px))</div>
     49<div class="width-test" style="width: calc(min(150px, 200px) - 50px);">calc(min(150px, 200px) - 50px)</div>
     50<div class="width-test" style="width: calc(50px + min(150px, 50px));">calc(50 + min(150px, 50px))</div>
     51<div class="width-test" style="width: calc(min(250px, 200px) / 2);">calc(min(250px, 200px) / 2)</div>
     52<div class="width-test" style="width: calc(min( 100px ));">calc(min( 100px ))</div>
     53<div class="width-test" style="width: calc(min((((100px)))));">calc(min((((100px)))))</div>
     54<div class="width-test" style="width: calc(min(150px,100px));">calc(min(150px,100px))</div>
     55<div class="width-test" style="width: calc(min(150px,100px,200px));">calc(min(150px,100px,200px))</div>
     56<div class="width-test" style="width: calc(min(  150px ,  100px  ,200px));">calc(min(  150px  ,  100px  ,200px))</div>
     57<div class="width-test" style="width: calc(min(90px + 50px ,100px));">calc(min(90px + 50px ,100px))</div>
     58<div style="width: 200px; background-color: white;" class="wrapper">
     59  <div class="width-test" style="width: calc(min(100%,100px));">calc(min(100%,100px)) - where 100% is 200px</div>
     60</div>
     61<div style="width: 200px; background-color: white;" class="wrapper">
     62  <div class="width-test" style="width: calc(min(50%,150px));">calc(min(50%,150px)) - where 50% is 100px</div>
     63</div>
     64<div style="width: 200px; background-color: white;" class="wrapper">
     65  <div class="width-test" style="width: calc(min(100% - 50px,100px));">calc(min(100% - 50px,100px)) - where 100% is 200px</div>
     66</div>
     67<div style="width: 200px; background-color: white;" class="wrapper">
     68  <div class="width-test" style="width: calc(min(25% + 50px,150px));">calc(min(25% + 50px,150px)) - where 25% is 50px</div>
     69</div>
     70<div style="width: 200px; background-color: white;" class="wrapper">
     71  <div class="width-test" style="width: calc(min(100px,100%));">calc(min(100px,100%)) - where 100% is 200px</div>
     72</div>
     73<div class="height-test" style="height: calc(min(200px, 100px));">calc(min(200px,100px))</div>
     74
     75<br/><br/>Bare max()
     76<div class="width-test" style="width: max(100px);">max(100px)</div>
     77<div class="width-test" style="width: calc(max(150px, 100px) - 50px);">calc(max(150px, 100px) - 50px)</div>
     78<div class="width-test" style="width: max(50px,100px);">max(50px,100px)</div>
     79<div class="width-test" style="width: max(50px,100px,20px);">max(50px,100px,20px)</div>
     80<div class="width-test" style="width: max(120px - 50px,100px);">max(120px - 50px,100px)</div>
     81<div style="width: 50px; background-color: white;" class="wrapper">
     82  <div class="width-test" style="width: max(100%,100px);">max(100%,100px) - where 100% is 50px</div>
     83</div>
     84<div style="width: 50px; background-color: white;" class="wrapper">
     85  <div class="width-test" style="width: max(100px,100%);">max(100px,100%) - where 100% is 50px</div>
    3486</div>
    3587
    36 <div class="width-test" style="width: -webkit-max(100px);">max(100px)</div>
    37 <div class="width-test" style="width: -webkit-max(50px,100px);">max(50px,100px)</div>
    38 <div class="width-test" style="width: -webkit-max(50px,100px,20px);">max(50px,100px,20px)</div>
    39 <div class="width-test" style="width: -webkit-max(120px - 50px,100px);">max(120px - 50px,100px)</div>
     88<br/><br/>max() inside calc()
     89<div class="width-test" style="width: calc(max(100px));">calc(max(100px))</div>
     90<div class="width-test" style="width: calc(max(50px,100px));">calc(max(50px,100px))</div>
     91<div class="width-test" style="width: calc(max(50px,100px,20px));">calc(max(50px,100px,20px))</div>
     92<div class="width-test" style="width: calc(max(120px - 50px,100px));">calc(max(120px - 50px,100px))</div>
    4093<div style="width: 50px; background-color: white;" class="wrapper">
    41   <div class="width-test" style="width: -webkit-max(100%,100px);">max(100%,100px) - where 100% is 50px</div>
     94  <div class="width-test" style="width: calc(max(100%,100px));">calc(max(100%,100px)) - where 100% is 50px</div>
    4295</div>
    4396<div style="width: 50px; background-color: white;" class="wrapper">
    44   <div class="width-test" style="width: -webkit-max(100px,100%);">max(100px,100%) - where 100% is 50px</div>
     97  <div class="width-test" style="width: calc(max(100px,100%));">calc(max(100px,100%)) - where 100% is 50px</div>
    4598</div>
    46 
    47 <div class="height-test" style="height: -webkit-min(200px, 100px);">min(200px,100px)</div>
    4899
    49100</div>
  • trunk/Source/WebCore/ChangeLog

    r222187 r222190  
     12017-09-18  Tim Horton  <timothy_horton@apple.com>
     2
     3        Support min() and max() in calc()
     4        https://bugs.webkit.org/show_bug.cgi?id=167000
     5        <rdar://problem/30153481>
     6
     7        Reviewed by David Hyatt.
     8        Patch originally by Myles Maxfield.
     9
     10        Add two new toplevel functions to CSS, min() and max(), which take an
     11        arbirary number of arguments and resolve to the minimum and maximum of
     12        the resolved value of the arguments, respectively. It is also possible
     13        to use min() and max() inside calc(), and to use calc()-like math
     14        inside min() and max().
     15
     16        * css/CSSCalculationValue.cpp:
     17        (WebCore::determineCategory):
     18        min and max operators don't use determineCategory; we have a specific
     19        implementation for them in createMinOrMax.
     20
     21        (WebCore::resolvedTypeForMinOrMax):
     22        The spec says that min() and max() should be marked as invalid if they
     23        have values of more than one type, but that percentages should resolve
     24        against the destination type before making this determination. So,
     25        if the destination type is length, percent turns into percent-length,
     26        and similarly for number.
     27
     28        (WebCore::isIntegerResult):
     29        Add an n-way implementation of isIntegerResult.
     30
     31        (WebCore::isSamePair):
     32        (WebCore::CSSCalcOperation::createMinOrMax): Create a min() or max()
     33        operation, as long as the types of arguments are all the same. Allow
     34        lengths to upgrade the whole operation to percent-length, and numbers
     35        to percent-number, which will cause us to use CalculationValue and friends
     36        in order to do proper resolution of all of the parameters instead of
     37        just comparing their numeric values.
     38
     39        (WebCore::CSSCalcOperation::createCalcExpression):
     40        (WebCore::CSSCalcOperation::doubleValue):
     41        (WebCore::CSSCalcOperation::computeLengthPx):
     42        (WebCore::CSSCalcOperation::customCSSText):
     43        (WebCore::CSSCalcOperation::primitiveType):
     44        (WebCore::CSSCalcOperation::CSSCalcOperation):
     45        (WebCore::CSSCalcOperation::evaluate):
     46        (WebCore::CSSCalcOperation::evaluateOperator):
     47        Adapt to child counts greater than two.
     48
     49        (WebCore::CSSCalcOperation::buildCssText):
     50        Add support for min() and max().
     51
     52        (WebCore::CSSCalcExpressionNodeParser::parseCalc):
     53        parseCalc now accepts a CSSValueID parameter indicating which calc function
     54        it should parse (calc, webkit-calc, min, or max), and delegates to either
     55        parseValueExpression or parseMinMaxExpression.
     56
     57        (WebCore::CSSCalcExpressionNodeParser::operatorValue):
     58        (WebCore::CSSCalcExpressionNodeParser::parseValue):
     59        If min() or max() are found while parsing a value (i.e. nested inside
     60        either calc or themselves), use parseMinMaxExpression on that subtree.
     61
     62        (WebCore::CSSCalcExpressionNodeParser::parseValueTerm):
     63        (WebCore::CSSCalcExpressionNodeParser::parseValueMultiplicativeExpression):
     64        (WebCore::CSSCalcExpressionNodeParser::parseAdditiveValueExpression):
     65        Adjust to the CSSCalcBinaryOperation->CSSCalcOperation rename.
     66
     67        (WebCore::CSSCalcExpressionNodeParser::parseMinMaxExpression):
     68        Added. Parse an arbitrary number of comma-and-whitespace-separated children.
     69
     70        (WebCore::createBlendHalf):
     71        Adjust to the CSSCalcBinaryOperation->CSSCalcOperation rename.
     72
     73        (WebCore::createCSS):
     74        Build the CSSCalcOperation for the platform-independent min and max operations.
     75
     76        (WebCore::CSSCalcValue::create):
     77        Pass the function being parsed and the destination calc category for the
     78        property being parsed for into create, and then into the parser so that
     79        it can know which function it is parsing for, and what kind of result it
     80        needs (as previously mentioned above in resolvedTypeForMinOrMax).
     81
     82        * css/CSSCalculationValue.h:
     83        * css/CSSValueKeywords.in:
     84        Add min and max functions as CSS keywords.
     85
     86        * css/StyleBuilderConverter.h:
     87        (WebCore::StyleBuilderConverter::convertLength):
     88        (WebCore::StyleBuilderConverter::convertTo100PercentMinusLength):
     89        * platform/Length.cpp:
     90        (WebCore::convertTo100PercentMinusLength):
     91        Adapt to the CalcExpressionOperation constructor taking a vector of
     92        arguments instead of two.
     93
     94        * css/parser/CSSPropertyParserHelpers.cpp:
     95        (WebCore::CSSPropertyParserHelpers::CalcParser::CalcParser):
     96        Store and pass the specific function being parsed down into CSSCalcValue.
     97
     98        (WebCore::CSSPropertyParserHelpers::consumeInteger):
     99        (WebCore::CSSPropertyParserHelpers::consumePositiveIntegerRaw):
     100        (WebCore::CSSPropertyParserHelpers::consumeNumberRaw):
     101        (WebCore::CSSPropertyParserHelpers::consumeNumber):
     102        (WebCore::CSSPropertyParserHelpers::consumeFontWeightNumber):
     103        (WebCore::CSSPropertyParserHelpers::consumeLength):
     104        (WebCore::CSSPropertyParserHelpers::consumePercent):
     105        (WebCore::CSSPropertyParserHelpers::consumeLengthOrPercent):
     106        (WebCore::CSSPropertyParserHelpers::consumeAngle):
     107        (WebCore::CSSPropertyParserHelpers::consumeTime):
     108        Pass the destination type into each calc parser.
     109
     110        * platform/CalculationValue.cpp:
     111        (WebCore::CalcExpressionOperation::evaluate const):
     112        (WebCore::CalcExpressionOperation::operator== const):
     113        (WebCore::CalcExpressionOperation::dump const):
     114        (WebCore::operator<<):
     115        (WebCore::CalcExpressionBinaryOperation::evaluate const): Deleted.
     116        (WebCore::CalcExpressionBinaryOperation::operator== const): Deleted.
     117        (WebCore::CalcExpressionBinaryOperation::dump const): Deleted.
     118        * platform/CalculationValue.h:
     119        (WebCore::CalcExpressionOperation::CalcExpressionOperation):
     120        (WebCore::operator==):
     121        (WebCore::toCalcExpressionOperation):
     122        (WebCore::CalcExpressionBinaryOperation::CalcExpressionBinaryOperation): Deleted.
     123        (WebCore::toCalcExpressionBinaryOperation): Deleted.
     124        Adjust to the CSSCalcBinaryOperation->CSSCalcOperation rename.
     125        Adjust to having n>2 children.
     126        Support min() and max() operators in various places.
     127       
     128
    11292017-09-18  Basuke Suzuki  <Basuke.Suzuki@sony.com>
    2130
  • trunk/Source/WebCore/css/CSSCalculationValue.cpp

    r216188 r222190  
    326326            return CalcOther;
    327327        return leftCategory;
     328    case CalcMin:
     329    case CalcMax:
     330        ASSERT_NOT_REACHED();
     331        return CalcOther;
    328332    }
    329333
    330334    ASSERT_NOT_REACHED();
     335    return CalcOther;
     336}
     337
     338static CalculationCategory resolvedTypeForMinOrMax(CalculationCategory category, CalculationCategory destinationCategory)
     339{
     340    switch (category) {
     341    case CalcNumber:
     342    case CalcLength:
     343    case CalcPercentNumber:
     344    case CalcPercentLength:
     345    case CalcAngle:
     346    case CalcTime:
     347    case CalcFrequency:
     348    case CalcOther:
     349        return category;
     350
     351    case CalcPercent:
     352        if (destinationCategory == CalcLength)
     353            return CalcPercentLength;
     354        if (destinationCategory == CalcNumber)
     355            return CalcPercentNumber;
     356        return category;
     357    }
     358
    331359    return CalcOther;
    332360}
     
    339367}
    340368
    341 class CSSCalcBinaryOperation final : public CSSCalcExpressionNode {
     369static inline bool isIntegerResult(CalcOperator op, const Vector<Ref<CSSCalcExpressionNode>>& nodes)
     370{
     371    // Performs W3C spec's type checking for calc integers.
     372    // http://www.w3.org/TR/css3-values/#calc-type-checking
     373    if (op == CalcDivide)
     374        return false;
     375
     376    for (auto& node : nodes) {
     377        if (!node->isInteger())
     378            return false;
     379    }
     380
     381    return true;
     382}
     383
     384static bool isSamePair(CalculationCategory a, CalculationCategory b, CalculationCategory x, CalculationCategory y)
     385{
     386    return (a == x && b == y) || (a == y && b == x);
     387}
     388
     389class CSSCalcOperation final : public CSSCalcExpressionNode {
    342390    WTF_MAKE_FAST_ALLOCATED;
    343391public:
    344     static RefPtr<CSSCalcBinaryOperation> create(CalcOperator op, RefPtr<CSSCalcExpressionNode>&& leftSide, RefPtr<CSSCalcExpressionNode>&& rightSide)
     392    static RefPtr<CSSCalcOperation> create(CalcOperator op, RefPtr<CSSCalcExpressionNode>&& leftSide, RefPtr<CSSCalcExpressionNode>&& rightSide)
    345393    {
    346394        if (!leftSide || !rightSide)
     
    353401        if (newCategory == CalcOther)
    354402            return nullptr;
    355         return adoptRef(new CSSCalcBinaryOperation(newCategory, op, leftSide.releaseNonNull(), rightSide.releaseNonNull()));
     403
     404        return adoptRef(new CSSCalcOperation(newCategory, op, leftSide.releaseNonNull(), rightSide.releaseNonNull()));
     405    }
     406
     407    static RefPtr<CSSCalcOperation> createMinOrMax(CalcOperator op, Vector<Ref<CSSCalcExpressionNode>>&& values, CalculationCategory destinationCategory)
     408    {
     409        ASSERT(op == CalcMin || op == CalcMax);
     410
     411        std::optional<CalculationCategory> category = std::nullopt;
     412        for (auto& value : values) {
     413            auto valueCategory = resolvedTypeForMinOrMax(value->category(), destinationCategory);
     414
     415            ASSERT(valueCategory < CalcOther);
     416            if (!category) {
     417                if (valueCategory == CalcOther)
     418                    return nullptr;
     419                category = valueCategory;
     420            }
     421
     422            if (category != valueCategory) {
     423                if (isSamePair(category.value(), valueCategory, CalcLength, CalcPercentLength)) {
     424                    category = CalcPercentLength;
     425                    continue;
     426                }
     427                if (isSamePair(category.value(), valueCategory, CalcNumber, CalcPercentNumber)) {
     428                    category = CalcPercentNumber;
     429                    continue;
     430                }
     431                return nullptr;
     432            }
     433        }
     434
     435        return adoptRef(new CSSCalcOperation(category.value(), op, WTFMove(values)));
    356436    }
    357437
     
    371451        if (leftCategory == CalcNumber && rightCategory == CalcNumber) {
    372452            CSSPrimitiveValue::UnitType evaluationType = CSSPrimitiveValue::CSS_NUMBER;
    373             return CSSCalcPrimitiveValue::create(evaluateOperator(op, leftSide->doubleValue(), rightSide->doubleValue()), evaluationType, isInteger);
     453            return CSSCalcPrimitiveValue::create(evaluateOperator(op, { leftSide->doubleValue(), rightSide->doubleValue() }), evaluationType, isInteger);
    374454        }
    375455
     
    381461                    CSSPrimitiveValue::UnitType rightType = rightSide->primitiveType();
    382462                    if (leftType == rightType)
    383                         return CSSCalcPrimitiveValue::create(evaluateOperator(op, leftSide->doubleValue(), rightSide->doubleValue()), leftType, isInteger);
     463                        return CSSCalcPrimitiveValue::create(evaluateOperator(op, { leftSide->doubleValue(), rightSide->doubleValue() }), leftType, isInteger);
    384464                    CSSPrimitiveValue::UnitCategory leftUnitCategory = CSSPrimitiveValue::unitCategory(leftType);
    385465                    if (leftUnitCategory != CSSPrimitiveValue::UOther && leftUnitCategory == CSSPrimitiveValue::unitCategory(rightType)) {
     
    388468                            double leftValue = leftSide->doubleValue() * CSSPrimitiveValue::conversionToCanonicalUnitsScaleFactor(leftType);
    389469                            double rightValue = rightSide->doubleValue() * CSSPrimitiveValue::conversionToCanonicalUnitsScaleFactor(rightType);
    390                             return CSSCalcPrimitiveValue::create(evaluateOperator(op, leftValue, rightValue), canonicalType, isInteger);
     470                            return CSSCalcPrimitiveValue::create(evaluateOperator(op, { leftValue, rightValue }), canonicalType, isInteger);
    391471                        }
    392472                    }
     
    411491            auto otherType = otherSide.primitiveType();
    412492            if (hasDoubleValue(otherType))
    413                 return CSSCalcPrimitiveValue::create(evaluateOperator(op, otherSide.doubleValue(), number), otherType, isInteger);
     493                return CSSCalcPrimitiveValue::create(evaluateOperator(op, { otherSide.doubleValue(), number }), otherType, isInteger);
    414494        }
    415495
     
    425505    std::unique_ptr<CalcExpressionNode> createCalcExpression(const CSSToLengthConversionData& conversionData) const final
    426506    {
    427         auto left = m_leftSide->createCalcExpression(conversionData);
    428         if (!left)
    429             return nullptr;
    430         auto right = m_rightSide->createCalcExpression(conversionData);
    431         if (!right)
    432             return nullptr;
    433         return std::make_unique<CalcExpressionBinaryOperation>(WTFMove(left), WTFMove(right), m_operator);
     507        Vector<std::unique_ptr<CalcExpressionNode>> nodes;
     508        nodes.reserveInitialCapacity(m_children.size());
     509
     510        for (auto& child : m_children) {
     511            auto node = child->createCalcExpression(conversionData);
     512            if (!node)
     513                return nullptr;
     514            nodes.uncheckedAppend(WTFMove(node));
     515        }
     516        return std::make_unique<CalcExpressionOperation>(WTFMove(nodes), m_operator);
    434517    }
    435518
    436519    double doubleValue() const final
    437520    {
    438         return evaluate(m_leftSide->doubleValue(), m_rightSide->doubleValue());
     521        Vector<double> doubleValues;
     522        for (auto& child : m_children)
     523            doubleValues.append(child->doubleValue());
     524        return evaluate(doubleValues);
    439525    }
    440526
    441527    double computeLengthPx(const CSSToLengthConversionData& conversionData) const final
    442528    {
    443         const double leftValue = m_leftSide->computeLengthPx(conversionData);
    444         const double rightValue = m_rightSide->computeLengthPx(conversionData);
    445         return evaluate(leftValue, rightValue);
    446     }
    447 
    448     static String buildCssText(const String& leftExpression, const String& rightExpression, CalcOperator op)
     529        Vector<double> doubleValues;
     530        for (auto& child : m_children)
     531            doubleValues.append(child->computeLengthPx(conversionData));
     532        return evaluate(doubleValues);
     533    }
     534
     535    static String buildCssText(Vector<String> childExpressions, CalcOperator op)
    449536    {
    450537        StringBuilder result;
    451538        result.append('(');
    452         result.append(leftExpression);
    453         result.append(' ');
    454         result.append(static_cast<char>(op));
    455         result.append(' ');
    456         result.append(rightExpression);
     539        switch (op) {
     540        case CalcAdd:
     541        case CalcSubtract:
     542        case CalcMultiply:
     543        case CalcDivide:
     544            ASSERT(childExpressions.size() == 2);
     545            result.append(childExpressions[0]);
     546            result.append(' ');
     547            result.append(static_cast<char>(op));
     548            result.append(' ');
     549            result.append(childExpressions[1]);
     550            break;
     551        case CalcMin:
     552        case CalcMax:
     553            ASSERT(!childExpressions.isEmpty());
     554            const char* functionName = op == CalcMin ? "min(" : "max(";
     555            result.append(functionName);
     556            result.append(childExpressions[0]);
     557            for (size_t i = 1; i < childExpressions.size(); ++i) {
     558                result.append(',');
     559                result.append(' ');
     560                result.append(childExpressions[i]);
     561            }
     562            result.append(')');
     563        }
    457564        result.append(')');
    458565
     
    462569    String customCSSText() const final
    463570    {
    464         return buildCssText(m_leftSide->customCSSText(), m_rightSide->customCSSText(), m_operator);
     571        Vector<String> cssTexts;
     572        for (auto& child : m_children)
     573            cssTexts.append(child->customCSSText());
     574        return buildCssText(cssTexts, m_operator);
    465575    }
    466576
     
    470580            return false;
    471581
    472         const CSSCalcBinaryOperation& other = static_cast<const CSSCalcBinaryOperation&>(exp);
    473         return compareCSSValuePtr(m_leftSide, other.m_leftSide)
    474             && compareCSSValuePtr(m_rightSide, other.m_rightSide)
    475             && m_operator == other.m_operator;
    476     }
    477 
    478     Type type() const final { return CssCalcBinaryOperation; }
     582        const CSSCalcOperation& other = static_cast<const CSSCalcOperation&>(exp);
     583
     584        if (m_children.size() != other.m_children.size() || m_operator != other.m_operator)
     585            return false;
     586
     587        for (size_t i = 0; i < m_children.size(); ++i) {
     588            if (!compareCSSValue(m_children[i], other.m_children[i]))
     589                return false;
     590        }
     591        return true;
     592    }
     593
     594    Type type() const final { return CssCalcOperation; }
    479595
    480596    CSSPrimitiveValue::UnitType primitiveType() const final
     
    482598        switch (category()) {
    483599        case CalcNumber:
    484             ASSERT(m_leftSide->category() == CalcNumber && m_rightSide->category() == CalcNumber);
     600#if !ASSERT_DISABLED
     601            for (auto& child : m_children)
     602                ASSERT(child->category() == CalcNumber);
     603#endif
    485604            return CSSPrimitiveValue::CSS_NUMBER;
    486605        case CalcLength:
    487606        case CalcPercent: {
    488             if (m_leftSide->category() == CalcNumber)
    489                 return m_rightSide->primitiveType();
    490             if (m_rightSide->category() == CalcNumber)
    491                 return m_leftSide->primitiveType();
    492             CSSPrimitiveValue::UnitType leftType = m_leftSide->primitiveType();
    493             if (leftType == m_rightSide->primitiveType())
    494                 return leftType;
    495             return CSSPrimitiveValue::CSS_UNKNOWN;
     607            if (m_children.isEmpty())
     608                return CSSPrimitiveValue::CSS_UNKNOWN;
     609            if (m_children.size() == 2) {
     610                if (m_children[0]->category() == CalcNumber)
     611                    return m_children[1]->primitiveType();
     612                if (m_children[1]->category() == CalcNumber)
     613                    return m_children[0]->primitiveType();
     614            }
     615            CSSPrimitiveValue::UnitType firstType = m_children[0]->primitiveType();
     616            for (auto& child : m_children) {
     617                if (firstType != child->primitiveType())
     618                    return CSSPrimitiveValue::CSS_UNKNOWN;
     619            }
     620            return firstType;
    496621        }
    497622        case CalcAngle:
     
    510635    }
    511636
    512     CSSCalcBinaryOperation(CalculationCategory category, CalcOperator op, Ref<CSSCalcExpressionNode>&& leftSide, Ref<CSSCalcExpressionNode>&& rightSide)
     637    CSSCalcOperation(CalculationCategory category, CalcOperator op, Ref<CSSCalcExpressionNode>&& leftSide, Ref<CSSCalcExpressionNode>&& rightSide)
    513638        : CSSCalcExpressionNode(category, isIntegerResult(op, leftSide.get(), rightSide.get()))
    514         , m_leftSide(WTFMove(leftSide))
    515         , m_rightSide(WTFMove(rightSide))
     639        , m_operator(op)
     640    {
     641        m_children.append(WTFMove(leftSide));
     642        m_children.append(WTFMove(rightSide));
     643    }
     644
     645    CSSCalcOperation(CalculationCategory category, CalcOperator op, Vector<Ref<CSSCalcExpressionNode>>&& children)
     646        : CSSCalcExpressionNode(category, isIntegerResult(op, children))
     647        , m_children(WTFMove(children))
    516648        , m_operator(op)
    517649    {
     
    527659    }
    528660
    529     double evaluate(double leftSide, double rightSide) const
    530     {
    531         return evaluateOperator(m_operator, leftSide, rightSide);
    532     }
    533 
    534     static double evaluateOperator(CalcOperator op, double leftValue, double rightValue)
     661    double evaluate(const Vector<double>& children) const
     662    {
     663        return evaluateOperator(m_operator, children);
     664    }
     665
     666    static double evaluateOperator(CalcOperator op, const Vector<double>& children)
    535667    {
    536668        switch (op) {
    537669        case CalcAdd:
    538             return leftValue + rightValue;
     670            ASSERT(children.size() == 2);
     671            return children[0] + children[1];
    539672        case CalcSubtract:
    540             return leftValue - rightValue;
     673            ASSERT(children.size() == 2);
     674            return children[0] - children[1];
    541675        case CalcMultiply:
    542             return leftValue * rightValue;
     676            ASSERT(children.size() == 2);
     677            return children[0] * children[1];
    543678        case CalcDivide:
    544             if (rightValue)
    545                 return leftValue / rightValue;
    546             return std::numeric_limits<double>::quiet_NaN();
     679            ASSERT(children.size() == 1 || children.size() == 2);
     680            if (children.size() == 1)
     681                return std::numeric_limits<double>::quiet_NaN();
     682            return children[0] / children[1];
     683        case CalcMin: {
     684            if (children.isEmpty())
     685                return std::numeric_limits<double>::quiet_NaN();
     686            double minimum = children[0];
     687            for (auto child : children)
     688                minimum = std::min(minimum, child);
     689            return minimum;
     690        }
     691        case CalcMax: {
     692            if (children.isEmpty())
     693                return std::numeric_limits<double>::quiet_NaN();
     694            double maximum = children[0];
     695            for (auto child : children)
     696                maximum = std::max(maximum, child);
     697            return maximum;
     698        }
    547699        }
    548700        ASSERT_NOT_REACHED();
     
    550702    }
    551703
    552     const RefPtr<CSSCalcExpressionNode> m_leftSide;
    553     const RefPtr<CSSCalcExpressionNode> m_rightSide;
     704    Vector<Ref<CSSCalcExpressionNode>> m_children;
    554705    const CalcOperator m_operator;
    555706};
     
    567718class CSSCalcExpressionNodeParser {
    568719public:
    569     RefPtr<CSSCalcExpressionNode> parseCalc(CSSParserTokenRange tokens)
     720    explicit CSSCalcExpressionNodeParser(CalculationCategory destinationCategory)
     721        : m_destinationCategory(destinationCategory)
     722    { }
     723
     724    RefPtr<CSSCalcExpressionNode> parseCalc(CSSParserTokenRange tokens, CSSValueID function)
    570725    {
    571726        Value result;
    572727        tokens.consumeWhitespace();
    573         bool ok = parseValueExpression(tokens, 0, &result);
     728        bool ok = false;
     729        if (function == CSSValueCalc || function == CSSValueWebkitCalc)
     730            ok = parseValueExpression(tokens, 0, &result);
     731        else if (function == CSSValueMin || function == CSSValueMax)
     732            ok = parseMinMaxExpression(tokens, function, 0, &result);
    574733        if (!ok || !tokens.atEnd())
    575734            return nullptr;
     
    609768        if (checkDepthAndIndex(&depth, tokens) != OK)
    610769            return false;
    611        
    612         if (tokens.peek().type() == LeftParenthesisToken || tokens.peek().functionId() == CSSValueCalc) {
     770
     771        auto functionId = tokens.peek().functionId();
     772       
     773        if (tokens.peek().type() == LeftParenthesisToken || functionId == CSSValueCalc) {
    613774            CSSParserTokenRange innerRange = tokens.consumeBlock();
    614775            tokens.consumeWhitespace();
    615776            innerRange.consumeWhitespace();
    616777            return parseValueExpression(innerRange, depth, result);
     778        }
     779
     780        if (functionId == CSSValueMax || functionId == CSSValueMin) {
     781            CSSParserTokenRange innerRange = tokens.consumeBlock();
     782            tokens.consumeWhitespace();
     783            innerRange.consumeWhitespace();
     784            return parseMinMaxExpression(innerRange, functionId, depth, result);
    617785        }
    618786       
     
    638806                return false;
    639807           
    640             result->value = CSSCalcBinaryOperation::createSimplified(static_cast<CalcOperator>(operatorCharacter), WTFMove(result->value), WTFMove(rhs.value));
     808            result->value = CSSCalcOperation::createSimplified(static_cast<CalcOperator>(operatorCharacter), WTFMove(result->value), WTFMove(rhs.value));
    641809
    642810            if (!result->value)
     
    670838                return false;
    671839           
    672             result->value = CSSCalcBinaryOperation::createSimplified(static_cast<CalcOperator>(operatorCharacter), WTFMove(result->value), WTFMove(rhs.value));
     840            result->value = CSSCalcOperation::createSimplified(static_cast<CalcOperator>(operatorCharacter), WTFMove(result->value), WTFMove(rhs.value));
    673841            if (!result->value)
    674842                return false;
     
    677845        return true;
    678846    }
    679    
     847
     848    bool parseMinMaxExpression(CSSParserTokenRange& tokens, CSSValueID minMaxFunction, int depth, Value* result)
     849    {
     850        if (checkDepthAndIndex(&depth, tokens) != OK)
     851            return false;
     852
     853        CalcOperator op = (minMaxFunction == CSSValueMin) ? CalcMin : CalcMax;
     854
     855        Value value;
     856        if (!parseValueExpression(tokens, depth, &value))
     857            return false;
     858
     859        Vector<Ref<CSSCalcExpressionNode>> nodes;
     860        nodes.append(value.value.releaseNonNull());
     861
     862        while (!tokens.atEnd()) {
     863            tokens.consumeWhitespace();
     864            if (tokens.consume().type() != CommaToken)
     865                return false;
     866            tokens.consumeWhitespace();
     867
     868            if (!parseValueExpression(tokens, depth, &value))
     869                return false;
     870
     871            nodes.append(value.value.releaseNonNull());
     872        }
     873
     874        result->value = CSSCalcOperation::createMinOrMax(op, WTFMove(nodes), m_destinationCategory);
     875        return result->value;
     876    }
     877
    680878    bool parseValueExpression(CSSParserTokenRange& tokens, int depth, Value* result)
    681879    {
    682880        return parseAdditiveValueExpression(tokens, depth, result);
    683881    }
     882
     883    CalculationCategory m_destinationCategory;
    684884};
    685885
    686 static inline RefPtr<CSSCalcBinaryOperation> createBlendHalf(const Length& length, const RenderStyle& style, float progress)
    687 {
    688     return CSSCalcBinaryOperation::create(CalcMultiply, createCSS(length, style),
     886static inline RefPtr<CSSCalcOperation> createBlendHalf(const Length& length, const RenderStyle& style, float progress)
     887{
     888    return CSSCalcOperation::create(CalcMultiply, createCSS(length, style),
    689889        CSSCalcPrimitiveValue::create(CSSPrimitiveValue::create(progress, CSSPrimitiveValue::CSS_NUMBER), !progress || progress == 1));
    690890}
     
    699899    case CalcExpressionNodeLength:
    700900        return createCSS(toCalcExpressionLength(node).length(), style);
    701     case CalcExpressionNodeBinaryOperation: {
    702         auto& binaryNode = toCalcExpressionBinaryOperation(node);
    703         return CSSCalcBinaryOperation::create(binaryNode.getOperator(), createCSS(binaryNode.leftSide(), style), createCSS(binaryNode.rightSide(), style));
     901    case CalcExpressionNodeOperation: {
     902        auto& operationNode = toCalcExpressionOperation(node);
     903        auto& operationChildren = operationNode.children();
     904        CalcOperator op = operationNode.getOperator();
     905        if (op == CalcMin || op == CalcMax) {
     906            Vector<Ref<CSSCalcExpressionNode>> values;
     907            values.reserveInitialCapacity(operationChildren.size());
     908            for (auto& child : operationChildren) {
     909                auto cssNode = createCSS(*child, style);
     910                if (!cssNode)
     911                    return nullptr;
     912                values.uncheckedAppend(*cssNode);
     913            }
     914            return CSSCalcOperation::createMinOrMax(operationNode.getOperator(), WTFMove(values), CalcOther);
     915        }
     916
     917        if (operationChildren.size() == 2)
     918            return CSSCalcOperation::create(operationNode.getOperator(), createCSS(*operationChildren[0], style), createCSS(*operationChildren[1], style));
     919
     920        return nullptr;
    704921    }
    705922    case CalcExpressionNodeBlendLength: {
     
    707924        auto& blend = toCalcExpressionBlendLength(node);
    708925        float progress = blend.progress();
    709         return CSSCalcBinaryOperation::create(CalcAdd, createBlendHalf(blend.from(), style, 1 - progress), createBlendHalf(blend.to(), style, progress));
     926        return CSSCalcOperation::create(CalcAdd, createBlendHalf(blend.from(), style, 1 - progress), createBlendHalf(blend.to(), style, progress));
    710927    }
    711928    case CalcExpressionNodeUndefined:
     
    737954}
    738955
    739 RefPtr<CSSCalcValue> CSSCalcValue::create(const CSSParserTokenRange& tokens, ValueRange range)
    740 {
    741     CSSCalcExpressionNodeParser parser;
    742     auto expression = parser.parseCalc(tokens);
     956RefPtr<CSSCalcValue> CSSCalcValue::create(CSSValueID function, const CSSParserTokenRange& tokens, CalculationCategory destinationCategory, ValueRange range)
     957{
     958    CSSCalcExpressionNodeParser parser(destinationCategory);
     959    auto expression = parser.parseCalc(tokens, function);
    743960    if (!expression)
    744961        return nullptr;
  • trunk/Source/WebCore/css/CSSCalculationValue.h

    r209758 r222190  
    5656    enum Type {
    5757        CssCalcPrimitiveValue = 1,
    58         CssCalcBinaryOperation
     58        CssCalcOperation
    5959    };
    6060
     
    8686class CSSCalcValue final : public CSSValue {
    8787public:
    88     static RefPtr<CSSCalcValue> create(const CSSParserTokenRange&, ValueRange);
    89    
     88    static RefPtr<CSSCalcValue> create(CSSValueID function, const CSSParserTokenRange&, CalculationCategory destinationCategory, ValueRange);
     89
    9090    static RefPtr<CSSCalcValue> create(const CalculationValue&, const RenderStyle&);
    9191
  • trunk/Source/WebCore/css/CSSValueKeywords.in

    r220730 r222190  
    12501250calc
    12511251-webkit-calc
     1252min
     1253max
    12521254
    12531255#if defined(ENABLE_CSS_IMAGE_RESOLUTION) && ENABLE_CSS_IMAGE_RESOLUTION
  • trunk/Source/WebCore/css/StyleBuilderConverter.h

    r219543 r222190  
    323323   
    324324    // Turn this into a calc expression: calc(100% - length)
    325     auto lhs = std::make_unique<CalcExpressionLength>(Length(100, Percent));
    326     auto rhs = std::make_unique<CalcExpressionLength>(length);
    327     auto op = std::make_unique<CalcExpressionBinaryOperation>(WTFMove(lhs), WTFMove(rhs), CalcSubtract);
     325    Vector<std::unique_ptr<CalcExpressionNode>> lengths;
     326    lengths.reserveInitialCapacity(2);
     327    lengths.uncheckedAppend(std::make_unique<CalcExpressionLength>(Length(100, Percent)));
     328    lengths.uncheckedAppend(std::make_unique<CalcExpressionLength>(length));
     329    auto op = std::make_unique<CalcExpressionOperation>(WTFMove(lengths), CalcSubtract);
    328330    return Length(CalculationValue::create(WTFMove(op), ValueRangeAll));
    329331}
  • trunk/Source/WebCore/css/parser/CSSPropertyParserHelpers.cpp

    r216833 r222190  
    7979
    8080public:
    81     explicit CalcParser(CSSParserTokenRange& range, ValueRange valueRange = ValueRangeAll)
     81    explicit CalcParser(CSSParserTokenRange& range, CalculationCategory destinationCategory, ValueRange valueRange = ValueRangeAll)
    8282        : m_sourceRange(range)
    8383        , m_range(range)
    8484    {
    8585        const CSSParserToken& token = range.peek();
    86         if (token.functionId() == CSSValueCalc || token.functionId() == CSSValueWebkitCalc)
    87             m_calcValue = CSSCalcValue::create(consumeFunction(m_range), valueRange);
     86        auto functionId = token.functionId();
     87        if (functionId == CSSValueCalc || functionId == CSSValueWebkitCalc || functionId == CSSValueMin || functionId == CSSValueMax)
     88            m_calcValue = CSSCalcValue::create(functionId, consumeFunction(m_range), destinationCategory, valueRange);
    8889    }
    8990
     
    138139        return CSSValuePool::singleton().createValue(range.consumeIncludingWhitespace().numericValue(), CSSPrimitiveValue::UnitType::CSS_NUMBER);
    139140    }
    140     CalcParser calcParser(range);
     141    CalcParser calcParser(range, CalcNumber);
    141142    if (const CSSCalcValue* calculation = calcParser.value()) {
    142143        if (calculation->category() != CalcNumber || !calculation->isInt())
     
    164165        return true;
    165166    }
    166     CalcParser calcParser(range);
     167    CalcParser calcParser(range, CalcNumber);
    167168    return calcParser.consumePositiveIntegerRaw(result);
    168169}
     
    174175        return true;
    175176    }
    176     CalcParser calcParser(range, ValueRangeAll);
     177    CalcParser calcParser(range, CalcNumber, ValueRangeAll);
    177178    return calcParser.consumeNumberRaw(result);
    178179}
     
    187188        return CSSValuePool::singleton().createValue(range.consumeIncludingWhitespace().numericValue(), token.unitType());
    188189    }
    189     CalcParser calcParser(range, ValueRangeAll);
     190    CalcParser calcParser(range, CalcNumber, ValueRangeAll);
    190191    if (const CSSCalcValue* calculation = calcParser.value()) {
    191192        // FIXME: Calcs should not be subject to parse time range checks.
     
    217218
    218219    // "[For calc()], the used value resulting from an expression must be clamped to the range allowed in the target context."
    219     CalcParser calcParser(range, ValueRangeAll);
     220    CalcParser calcParser(range, CalcNumber, ValueRangeAll);
    220221    double result;
    221222    if (calcParser.consumeNumberRaw(result)
     
    279280        return CSSValuePool::singleton().createValue(range.consumeIncludingWhitespace().numericValue(), unitType);
    280281    }
    281     CalcParser calcParser(range, valueRange);
     282    CalcParser calcParser(range, CalcLength, valueRange);
    282283    if (calcParser.value() && calcParser.value()->category() == CalcLength)
    283284        return calcParser.consumeValue();
     
    293294        return CSSValuePool::singleton().createValue(range.consumeIncludingWhitespace().numericValue(), CSSPrimitiveValue::UnitType::CSS_PERCENTAGE);
    294295    }
    295     CalcParser calcParser(range, valueRange);
     296    CalcParser calcParser(range, CalcPercent, valueRange);
    296297    if (const CSSCalcValue* calculation = calcParser.value()) {
    297298        if (calculation->category() == CalcPercent)
     
    322323    if (token.type() == PercentageToken)
    323324        return consumePercent(range, valueRange);
    324     CalcParser calcParser(range, valueRange);
     325    CalcParser calcParser(range, CalcLength, valueRange);
    325326    if (const CSSCalcValue* calculation = calcParser.value()) {
    326327        if (canConsumeCalcValue(calculation->category(), cssParserMode))
     
    348349    }
    349350
    350     CalcParser calcParser(range, ValueRangeAll);
     351    CalcParser calcParser(range, CalcAngle, ValueRangeAll);
    351352    if (const CSSCalcValue* calculation = calcParser.value()) {
    352353        if (calculation->category() == CalcAngle)
     
    370371        return nullptr;
    371372    }
    372     CalcParser calcParser(range, valueRange);
     373    CalcParser calcParser(range, CalcTime, valueRange);
    373374    if (const CSSCalcValue* calculation = calcParser.value()) {
    374375        if (calculation->category() == CalcTime)
  • trunk/Source/WebCore/platform/CalculationValue.cpp

    r220503 r222190  
    6969}
    7070
    71 float CalcExpressionBinaryOperation::evaluate(float maxValue) const
     71float CalcExpressionOperation::evaluate(float maxValue) const
    7272{
    73     float left = m_leftSide->evaluate(maxValue);
    74     float right = m_rightSide->evaluate(maxValue);
    7573    switch (m_operator) {
    76     case CalcAdd:
     74    case CalcAdd: {
     75        ASSERT(m_children.size() == 2);
     76        float left = m_children[0]->evaluate(maxValue);
     77        float right = m_children[1]->evaluate(maxValue);
    7778        return left + right;
    78     case CalcSubtract:
     79    }
     80    case CalcSubtract: {
     81        ASSERT(m_children.size() == 2);
     82        float left = m_children[0]->evaluate(maxValue);
     83        float right = m_children[1]->evaluate(maxValue);
    7984        return left - right;
    80     case CalcMultiply:
     85    }
     86    case CalcMultiply: {
     87        ASSERT(m_children.size() == 2);
     88        float left = m_children[0]->evaluate(maxValue);
     89        float right = m_children[1]->evaluate(maxValue);
    8190        return left * right;
    82     case CalcDivide:
    83         if (!right)
     91    }
     92    case CalcDivide: {
     93        ASSERT(m_children.size() == 1 || m_children.size() == 2);
     94        if (m_children.size() == 1)
    8495            return std::numeric_limits<float>::quiet_NaN();
     96        float left = m_children[0]->evaluate(maxValue);
     97        float right = m_children[1]->evaluate(maxValue);
    8598        return left / right;
     99    }
     100    case CalcMin: {
     101        if (m_children.isEmpty())
     102            return std::numeric_limits<float>::quiet_NaN();
     103        float minimum = m_children[0]->evaluate(maxValue);
     104        for (auto& child : m_children)
     105            minimum = std::min(minimum, child->evaluate(maxValue));
     106        return minimum;
     107    }
     108    case CalcMax: {
     109        if (m_children.isEmpty())
     110            return std::numeric_limits<float>::quiet_NaN();
     111        float maximum = m_children[0]->evaluate(maxValue);
     112        for (auto& child : m_children)
     113            maximum = std::max(maximum, child->evaluate(maxValue));
     114        return maximum;
     115    }
    86116    }
    87117    ASSERT_NOT_REACHED();
     
    89119}
    90120
    91 bool CalcExpressionBinaryOperation::operator==(const CalcExpressionNode& other) const
     121bool CalcExpressionOperation::operator==(const CalcExpressionNode& other) const
    92122{
    93     return other.type() == CalcExpressionNodeBinaryOperation && *this == toCalcExpressionBinaryOperation(other);
     123    return other.type() == CalcExpressionNodeOperation && *this == toCalcExpressionOperation(other);
    94124}
    95125
    96 void CalcExpressionBinaryOperation::dump(TextStream& ts) const
     126void CalcExpressionOperation::dump(TextStream& ts) const
    97127{
    98     ts << *m_leftSide << " " << m_operator << " " << *m_rightSide;
     128    if (m_operator == CalcMin || m_operator == CalcMax) {
     129        ts << m_operator << "(";
     130        size_t childrenCount = m_children.size();
     131        for (size_t i = 0; i < childrenCount; i++) {
     132            ts << m_children[i].get();
     133            if (i < childrenCount - 1)
     134                ts << ", ";
     135        }
     136        ts << ")";
     137    } else
     138        ts << m_children[0].get() << " " << m_operator << " " << m_children[1].get();
    99139}
    100140
     
    136176    case CalcMultiply: ts << "*"; break;
    137177    case CalcDivide: ts << "/"; break;
     178    case CalcMin: ts << "max"; break;
     179    case CalcMax: ts << "min"; break;
    138180    }
    139181    return ts;
  • trunk/Source/WebCore/platform/CalculationValue.h

    r220503 r222190  
    3636#include <wtf/Ref.h>
    3737#include <wtf/RefCounted.h>
     38#include <wtf/Vector.h>
    3839
    3940namespace WTF {
     
    4748    CalcSubtract = '-',
    4849    CalcMultiply = '*',
    49     CalcDivide = '/'
     50    CalcDivide = '/',
     51    CalcMin = 0,
     52    CalcMax = 1,
    5053};
    5154
     
    5457    CalcExpressionNodeNumber,
    5558    CalcExpressionNodeLength,
    56     CalcExpressionNodeBinaryOperation,
     59    CalcExpressionNodeOperation,
    5760    CalcExpressionNodeBlendLength,
    5861};
     
    102105};
    103106
    104 class CalcExpressionBinaryOperation final : public CalcExpressionNode {
    105 public:
    106     CalcExpressionBinaryOperation(std::unique_ptr<CalcExpressionNode> leftSide, std::unique_ptr<CalcExpressionNode> rightSide, CalcOperator);
    107 
    108     const CalcExpressionNode& leftSide() const { return *m_leftSide; }
    109     const CalcExpressionNode& rightSide() const { return *m_rightSide; }
     107class CalcExpressionOperation final : public CalcExpressionNode {
     108public:
     109    CalcExpressionOperation(Vector<std::unique_ptr<CalcExpressionNode>>&& children, CalcOperator);
     110
    110111    CalcOperator getOperator() const { return m_operator; }
    111112
     113    const Vector<std::unique_ptr<CalcExpressionNode>>& children() const { return m_children; }
     114
    112115private:
    113116    float evaluate(float maxValue) const override;
     
    115118    void dump(WTF::TextStream&) const override;
    116119
    117     std::unique_ptr<CalcExpressionNode> m_leftSide;
    118     std::unique_ptr<CalcExpressionNode> m_rightSide;
     120    Vector<std::unique_ptr<CalcExpressionNode>> m_children;
    119121    CalcOperator m_operator;
    120122};
     
    203205}
    204206
    205 inline CalcExpressionBinaryOperation::CalcExpressionBinaryOperation(std::unique_ptr<CalcExpressionNode> leftSide, std::unique_ptr<CalcExpressionNode> rightSide, CalcOperator op)
    206     : CalcExpressionNode(CalcExpressionNodeBinaryOperation)
    207     , m_leftSide(WTFMove(leftSide))
    208     , m_rightSide(WTFMove(rightSide))
     207inline CalcExpressionOperation::CalcExpressionOperation(Vector<std::unique_ptr<CalcExpressionNode>>&& children, CalcOperator op)
     208    : CalcExpressionNode(CalcExpressionNodeOperation)
     209    , m_children(WTFMove(children))
    209210    , m_operator(op)
    210211{
    211212}
    212213
    213 inline bool operator==(const CalcExpressionBinaryOperation& a, const CalcExpressionBinaryOperation& b)
    214 {
    215     return a.getOperator() == b.getOperator() && a.leftSide() == b.leftSide() && a.rightSide() == b.rightSide();
    216 }
    217 
    218 inline const CalcExpressionBinaryOperation& toCalcExpressionBinaryOperation(const CalcExpressionNode& value)
    219 {
    220     ASSERT_WITH_SECURITY_IMPLICATION(value.type() == CalcExpressionNodeBinaryOperation);
    221     return static_cast<const CalcExpressionBinaryOperation&>(value);
     214inline bool operator==(const CalcExpressionOperation& a, const CalcExpressionOperation& b)
     215{
     216    return a.getOperator() == b.getOperator() && a.children() == b.children();
     217}
     218
     219inline const CalcExpressionOperation& toCalcExpressionOperation(const CalcExpressionNode& value)
     220{
     221    ASSERT_WITH_SECURITY_IMPLICATION(value.type() == CalcExpressionNodeOperation);
     222    return static_cast<const CalcExpressionOperation&>(value);
    222223}
    223224
  • trunk/Source/WebCore/platform/Length.cpp

    r220503 r222190  
    292292   
    293293    // Turn this into a calc expression: calc(100% - length)
    294     auto lhs = std::make_unique<CalcExpressionLength>(Length(100, Percent));
    295     auto rhs = std::make_unique<CalcExpressionLength>(length);
    296     auto op = std::make_unique<CalcExpressionBinaryOperation>(WTFMove(lhs), WTFMove(rhs), CalcSubtract);
     294    Vector<std::unique_ptr<CalcExpressionNode>> lengths;
     295    lengths.reserveInitialCapacity(2);
     296    lengths.uncheckedAppend(std::make_unique<CalcExpressionLength>(Length(100, Percent)));
     297    lengths.uncheckedAppend(std::make_unique<CalcExpressionLength>(length));
     298    auto op = std::make_unique<CalcExpressionOperation>(WTFMove(lengths), CalcSubtract);
    297299    return Length(CalculationValue::create(WTFMove(op), ValueRangeAll));
    298300}
Note: See TracChangeset for help on using the changeset viewer.