Home | History | Annotate | Download | only in resolver
      1 /*
      2  * Copyright (C) 1999 Lars Knoll (knoll (at) kde.org)
      3  *           (C) 2004-2005 Allan Sandfeld Jensen (kde (at) carewolf.com)
      4  * Copyright (C) 2006, 2007 Nicholas Shanks (webkit (at) nickshanks.com)
      5  * Copyright (C) 2005, 2006, 2007, 2008, 2009, 2010, 2011, 2012, 2013 Apple Inc. All rights reserved.
      6  * Copyright (C) 2007 Alexey Proskuryakov <ap (at) webkit.org>
      7  * Copyright (C) 2007, 2008 Eric Seidel <eric (at) webkit.org>
      8  * Copyright (C) 2008, 2009 Torch Mobile Inc. All rights reserved. (http://www.torchmobile.com/)
      9  * Copyright (c) 2011, Code Aurora Forum. All rights reserved.
     10  * Copyright (C) Research In Motion Limited 2011. All rights reserved.
     11  * Copyright (C) 2012 Google Inc. All rights reserved.
     12  *
     13  * This library is free software; you can redistribute it and/or
     14  * modify it under the terms of the GNU Library General Public
     15  * License as published by the Free Software Foundation; either
     16  * version 2 of the License, or (at your option) any later version.
     17  *
     18  * This library is distributed in the hope that it will be useful,
     19  * but WITHOUT ANY WARRANTY; without even the implied warranty of
     20  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
     21  * Library General Public License for more details.
     22  *
     23  * You should have received a copy of the GNU Library General Public License
     24  * along with this library; see the file COPYING.LIB.  If not, write to
     25  * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
     26  * Boston, MA 02110-1301, USA.
     27  */
     28 
     29 #include "config.h"
     30 #include "core/css/resolver/FilterOperationResolver.h"
     31 
     32 #include "core/css/CSSFilterValue.h"
     33 #include "core/css/CSSMixFunctionValue.h"
     34 #include "core/css/CSSParser.h"
     35 #include "core/css/CSSPrimitiveValueMappings.h"
     36 #include "core/css/CSSShaderValue.h"
     37 #include "core/css/CSSShadowValue.h"
     38 #include "core/css/resolver/TransformBuilder.h"
     39 #include "core/rendering/style/StyleCustomFilterProgram.h"
     40 #include "core/rendering/style/StyleShader.h"
     41 #include "core/rendering/svg/ReferenceFilterBuilder.h"
     42 #include "core/svg/SVGURIReference.h"
     43 #include "platform/graphics/filters/custom/CustomFilterArrayParameter.h"
     44 #include "platform/graphics/filters/custom/CustomFilterConstants.h"
     45 #include "platform/graphics/filters/custom/CustomFilterNumberParameter.h"
     46 #include "platform/graphics/filters/custom/CustomFilterOperation.h"
     47 #include "platform/graphics/filters/custom/CustomFilterParameter.h"
     48 #include "platform/graphics/filters/custom/CustomFilterProgramInfo.h"
     49 #include "platform/graphics/filters/custom/CustomFilterTransformParameter.h"
     50 
     51 namespace WebCore {
     52 
     53 static FilterOperation::OperationType filterOperationForType(CSSFilterValue::FilterOperationType type)
     54 {
     55     switch (type) {
     56     case CSSFilterValue::ReferenceFilterOperation:
     57         return FilterOperation::REFERENCE;
     58     case CSSFilterValue::GrayscaleFilterOperation:
     59         return FilterOperation::GRAYSCALE;
     60     case CSSFilterValue::SepiaFilterOperation:
     61         return FilterOperation::SEPIA;
     62     case CSSFilterValue::SaturateFilterOperation:
     63         return FilterOperation::SATURATE;
     64     case CSSFilterValue::HueRotateFilterOperation:
     65         return FilterOperation::HUE_ROTATE;
     66     case CSSFilterValue::InvertFilterOperation:
     67         return FilterOperation::INVERT;
     68     case CSSFilterValue::OpacityFilterOperation:
     69         return FilterOperation::OPACITY;
     70     case CSSFilterValue::BrightnessFilterOperation:
     71         return FilterOperation::BRIGHTNESS;
     72     case CSSFilterValue::ContrastFilterOperation:
     73         return FilterOperation::CONTRAST;
     74     case CSSFilterValue::BlurFilterOperation:
     75         return FilterOperation::BLUR;
     76     case CSSFilterValue::DropShadowFilterOperation:
     77         return FilterOperation::DROP_SHADOW;
     78     case CSSFilterValue::CustomFilterOperation:
     79         return FilterOperation::CUSTOM;
     80     case CSSFilterValue::UnknownFilterOperation:
     81         return FilterOperation::NONE;
     82     }
     83     return FilterOperation::NONE;
     84 }
     85 
     86 static StyleShader* styleShader(CSSValue* value)
     87 {
     88     if (value->isShaderValue())
     89         return toCSSShaderValue(value)->cachedOrPendingShader();
     90     return 0;
     91 }
     92 
     93 static PassRefPtr<CustomFilterParameter> parseCustomFilterArrayParameter(const String& name, CSSValueList* values)
     94 {
     95     RefPtr<CustomFilterArrayParameter> arrayParameter = CustomFilterArrayParameter::create(name);
     96     for (unsigned i = 0, length = values->length(); i < length; ++i) {
     97         CSSValue* value = values->itemWithoutBoundsCheck(i);
     98         if (!value->isPrimitiveValue())
     99             return 0;
    100         CSSPrimitiveValue* primitiveValue = toCSSPrimitiveValue(value);
    101         if (primitiveValue->primitiveType() != CSSPrimitiveValue::CSS_NUMBER)
    102             return 0;
    103         arrayParameter->addValue(primitiveValue->getDoubleValue());
    104     }
    105     return arrayParameter.release();
    106 }
    107 
    108 static PassRefPtr<CustomFilterParameter> parseCustomFilterNumberParameter(const String& name, CSSValueList* values)
    109 {
    110     RefPtr<CustomFilterNumberParameter> numberParameter = CustomFilterNumberParameter::create(name);
    111     for (unsigned i = 0; i < values->length(); ++i) {
    112         CSSValue* value = values->itemWithoutBoundsCheck(i);
    113         if (!value->isPrimitiveValue())
    114             return 0;
    115         CSSPrimitiveValue* primitiveValue = toCSSPrimitiveValue(value);
    116         if (primitiveValue->primitiveType() != CSSPrimitiveValue::CSS_NUMBER)
    117             return 0;
    118         numberParameter->addValue(primitiveValue->getDoubleValue());
    119     }
    120     return numberParameter.release();
    121 }
    122 
    123 static PassRefPtr<CustomFilterParameter> parseCustomFilterTransformParameter(const String& name, CSSValueList* values, StyleResolverState& state)
    124 {
    125     RefPtr<CustomFilterTransformParameter> transformParameter = CustomFilterTransformParameter::create(name);
    126     TransformOperations operations;
    127     TransformBuilder::createTransformOperations(values, state.cssToLengthConversionData(), operations);
    128     transformParameter->setOperations(operations);
    129     return transformParameter.release();
    130 }
    131 
    132 static PassRefPtr<CustomFilterParameter> parseCustomFilterParameter(const String& name, CSSValue* parameterValue, StyleResolverState& state)
    133 {
    134     // FIXME: Implement other parameters types parsing.
    135     // booleans: https://bugs.webkit.org/show_bug.cgi?id=76438
    136     // textures: https://bugs.webkit.org/show_bug.cgi?id=71442
    137     // mat2, mat3, mat4: https://bugs.webkit.org/show_bug.cgi?id=71444
    138     // Number parameters are wrapped inside a CSSValueList and all
    139     // the other functions values inherit from CSSValueList.
    140     if (!parameterValue->isValueList())
    141         return 0;
    142 
    143     CSSValueList* values = toCSSValueList(parameterValue);
    144     if (!values->length())
    145         return 0;
    146 
    147     if (parameterValue->isArrayFunctionValue())
    148         return parseCustomFilterArrayParameter(name, values);
    149 
    150     // If the first value of the list is a transform function,
    151     // then we could safely assume that all the remaining items
    152     // are transforms. parseCustomFilterTransformParameter will
    153     // return 0 if that assumption is incorrect.
    154     if (values->itemWithoutBoundsCheck(0)->isTransformValue())
    155         return parseCustomFilterTransformParameter(name, values, state);
    156 
    157     // We can have only arrays of booleans or numbers, so use the first value to choose between those two.
    158     // We need up to 4 values (all booleans or all numbers).
    159     if (!values->itemWithoutBoundsCheck(0)->isPrimitiveValue() || values->length() > 4)
    160         return 0;
    161 
    162     CSSPrimitiveValue* firstPrimitiveValue = toCSSPrimitiveValue(values->itemWithoutBoundsCheck(0));
    163     if (firstPrimitiveValue->primitiveType() == CSSPrimitiveValue::CSS_NUMBER)
    164         return parseCustomFilterNumberParameter(name, values);
    165 
    166     // FIXME: Implement the boolean array parameter here.
    167     // https://bugs.webkit.org/show_bug.cgi?id=76438
    168 
    169     return 0;
    170 }
    171 
    172 static bool parseCustomFilterParameterList(CSSValue* parametersValue, CustomFilterParameterList& parameterList, StyleResolverState& state)
    173 {
    174     HashSet<String> knownParameterNames;
    175     CSSValueListIterator parameterIterator(parametersValue);
    176     for (; parameterIterator.hasMore(); parameterIterator.advance()) {
    177         if (!parameterIterator.value()->isValueList())
    178             return false;
    179         CSSValueListIterator iterator(parameterIterator.value());
    180         if (!iterator.isPrimitiveValue())
    181             return false;
    182         CSSPrimitiveValue* primitiveValue = toCSSPrimitiveValue(iterator.value());
    183         if (primitiveValue->primitiveType() != CSSPrimitiveValue::CSS_STRING)
    184             return false;
    185 
    186         String name = primitiveValue->getStringValue();
    187         // Do not allow duplicate parameter names.
    188         if (!knownParameterNames.add(name).isNewEntry)
    189             return false;
    190 
    191         iterator.advance();
    192 
    193         if (!iterator.hasMore())
    194             return false;
    195 
    196         RefPtr<CustomFilterParameter> parameter = parseCustomFilterParameter(name, iterator.value(), state);
    197         if (!parameter)
    198             return false;
    199         parameterList.append(parameter.release());
    200     }
    201 
    202     // Make sure we sort the parameters before passing them down to the CustomFilterOperation.
    203     parameterList.sortParametersByName();
    204 
    205     return true;
    206 }
    207 
    208 static PassRefPtr<CustomFilterOperation> createCustomFilterOperationWithAtRuleReferenceSyntax(CSSFilterValue*)
    209 {
    210     // FIXME: Implement style resolution for the custom filter at-rule reference syntax.
    211     return 0;
    212 }
    213 
    214 static PassRefPtr<CustomFilterProgram> createCustomFilterProgram(CSSShaderValue* vertexShader, CSSShaderValue* fragmentShader,
    215     CustomFilterProgramType programType, const CustomFilterProgramMixSettings& mixSettings, CustomFilterMeshType meshType,
    216     StyleResolverState& state)
    217 {
    218     ResourceFetcher* fetcher = state.document().fetcher();
    219     KURL vertexShaderURL = vertexShader ? vertexShader->completeURL(fetcher) : KURL();
    220     KURL fragmentShaderURL = fragmentShader ? fragmentShader->completeURL(fetcher) : KURL();
    221     // We re-resolve the custom filter style after the shaders are loaded.
    222     // We always create a StyleCustomFilterProgram here, and later replace it with a program from the StyleCustomFilterProgramCache, if available.
    223     StyleShader* styleVertexShader = vertexShader ? styleShader(vertexShader) : 0;
    224     StyleShader* styleFragmentShader = fragmentShader ? styleShader(fragmentShader) : 0;
    225     RefPtr<StyleCustomFilterProgram> program = StyleCustomFilterProgram::create(vertexShaderURL, styleVertexShader,
    226         fragmentShaderURL, styleFragmentShader, programType, mixSettings, meshType);
    227     state.elementStyleResources().setHasNewCustomFilterProgram(true);
    228     return program.release();
    229 }
    230 
    231 static PassRefPtr<CustomFilterOperation> createCustomFilterOperationWithInlineSyntax(CSSFilterValue* filterValue, StyleResolverState& state)
    232 {
    233     CSSValue* shadersValue = filterValue->itemWithoutBoundsCheck(0);
    234     ASSERT_WITH_SECURITY_IMPLICATION(shadersValue->isValueList());
    235     CSSValueList* shadersList = toCSSValueList(shadersValue);
    236 
    237     unsigned shadersListLength = shadersList->length();
    238     ASSERT(shadersListLength);
    239 
    240     CSSShaderValue* vertexShader = 0;
    241     CSSShaderValue* fragmentShader = 0;
    242 
    243     if (shadersList->itemWithoutBoundsCheck(0)->isShaderValue())
    244         vertexShader = toCSSShaderValue(shadersList->itemWithoutBoundsCheck(0));
    245 
    246     CustomFilterProgramType programType = ProgramTypeBlendsElementTexture;
    247     CustomFilterProgramMixSettings mixSettings;
    248 
    249     if (shadersListLength > 1) {
    250         CSSValue* fragmentShaderOrMixFunction = shadersList->itemWithoutBoundsCheck(1);
    251         if (fragmentShaderOrMixFunction->isMixFunctionValue()) {
    252             CSSMixFunctionValue* mixFunction = toCSSMixFunctionValue(fragmentShaderOrMixFunction);
    253             CSSValueListIterator iterator(mixFunction);
    254 
    255             ASSERT(mixFunction->length());
    256             if (iterator.value()->isShaderValue())
    257                 fragmentShader = toCSSShaderValue(iterator.value());
    258 
    259             iterator.advance();
    260 
    261             ASSERT(mixFunction->length() <= 3);
    262             while (iterator.hasMore()) {
    263                 CSSPrimitiveValue* primitiveValue = toCSSPrimitiveValue(iterator.value());
    264                 if (CSSParser::isBlendMode(primitiveValue->getValueID()))
    265                     mixSettings.blendMode = *primitiveValue;
    266                 else if (CSSParser::isCompositeOperator(primitiveValue->getValueID()))
    267                     mixSettings.compositeOperator = *primitiveValue;
    268                 else
    269                     ASSERT_NOT_REACHED();
    270                 iterator.advance();
    271             }
    272         } else {
    273             programType = ProgramTypeNoElementTexture;
    274             if (fragmentShaderOrMixFunction->isShaderValue())
    275                 fragmentShader = toCSSShaderValue(fragmentShaderOrMixFunction);
    276         }
    277     }
    278 
    279     if (!vertexShader && !fragmentShader)
    280         return 0;
    281 
    282     unsigned meshRows = 1;
    283     unsigned meshColumns = 1;
    284     CustomFilterMeshType meshType = MeshTypeAttached;
    285 
    286     CSSValue* parametersValue = 0;
    287 
    288     if (filterValue->length() > 1) {
    289         CSSValueListIterator iterator(filterValue->itemWithoutBoundsCheck(1));
    290 
    291         // The second value might be the mesh box or the list of parameters:
    292         // If it starts with a number or any of the mesh-box identifiers it is
    293         // the mesh-box list, if not it means it is the parameters list.
    294 
    295         if (iterator.hasMore() && iterator.isPrimitiveValue()) {
    296             CSSPrimitiveValue* primitiveValue = toCSSPrimitiveValue(iterator.value());
    297             if (primitiveValue->isNumber()) {
    298                 // If only one integer value is specified, it will set both
    299                 // the rows and the columns.
    300                 meshColumns = meshRows = primitiveValue->getIntValue();
    301                 iterator.advance();
    302 
    303                 // Try to match another number for the rows.
    304                 if (iterator.hasMore() && iterator.isPrimitiveValue()) {
    305                     CSSPrimitiveValue* primitiveValue = toCSSPrimitiveValue(iterator.value());
    306                     if (primitiveValue->isNumber()) {
    307                         meshRows = primitiveValue->getIntValue();
    308                         iterator.advance();
    309                     }
    310                 }
    311             }
    312         }
    313 
    314         if (iterator.hasMore() && iterator.isPrimitiveValue()) {
    315             CSSPrimitiveValue* primitiveValue = toCSSPrimitiveValue(iterator.value());
    316             if (primitiveValue->getValueID() == CSSValueDetached) {
    317                 meshType = MeshTypeDetached;
    318                 iterator.advance();
    319             }
    320         }
    321 
    322         if (!iterator.index()) {
    323             // If no value was consumed from the mesh value, then it is just a parameter list, meaning that we end up
    324             // having just two CSSListValues: list of shaders and list of parameters.
    325             ASSERT(filterValue->length() == 2);
    326             parametersValue = filterValue->itemWithoutBoundsCheck(1);
    327         }
    328     }
    329 
    330     if (filterValue->length() > 2 && !parametersValue)
    331         parametersValue = filterValue->itemWithoutBoundsCheck(2);
    332 
    333     CustomFilterParameterList parameterList;
    334     if (parametersValue && !parseCustomFilterParameterList(parametersValue, parameterList, state))
    335         return 0;
    336 
    337     RefPtr<CustomFilterProgram> program = createCustomFilterProgram(vertexShader, fragmentShader, programType, mixSettings, meshType, state);
    338     return CustomFilterOperation::create(program.release(), parameterList, meshRows, meshColumns, meshType);
    339 }
    340 
    341 static PassRefPtr<CustomFilterOperation> createCustomFilterOperation(CSSFilterValue* filterValue, StyleResolverState& state)
    342 {
    343     ASSERT(filterValue->length());
    344     bool isAtRuleReferenceSyntax = filterValue->itemWithoutBoundsCheck(0)->isPrimitiveValue();
    345     return isAtRuleReferenceSyntax ? createCustomFilterOperationWithAtRuleReferenceSyntax(filterValue) : createCustomFilterOperationWithInlineSyntax(filterValue, state);
    346 }
    347 
    348 
    349 bool FilterOperationResolver::createFilterOperations(CSSValue* inValue, const CSSToLengthConversionData& unadjustedConversionData, FilterOperations& outOperations, StyleResolverState& state)
    350 {
    351     ASSERT(outOperations.isEmpty());
    352 
    353     if (!inValue)
    354         return false;
    355 
    356     if (inValue->isPrimitiveValue()) {
    357         CSSPrimitiveValue* primitiveValue = toCSSPrimitiveValue(inValue);
    358         if (primitiveValue->getValueID() == CSSValueNone)
    359             return true;
    360     }
    361 
    362     if (!inValue->isValueList())
    363         return false;
    364 
    365     float zoomFactor = unadjustedConversionData.zoom() * state.elementStyleResources().deviceScaleFactor();
    366     const CSSToLengthConversionData& conversionData = unadjustedConversionData.copyWithAdjustedZoom(zoomFactor);
    367     FilterOperations operations;
    368     for (CSSValueListIterator i = inValue; i.hasMore(); i.advance()) {
    369         CSSValue* currValue = i.value();
    370         if (!currValue->isFilterValue())
    371             continue;
    372 
    373         CSSFilterValue* filterValue = toCSSFilterValue(i.value());
    374         FilterOperation::OperationType operationType = filterOperationForType(filterValue->operationType());
    375 
    376         if (operationType == FilterOperation::VALIDATED_CUSTOM) {
    377             // ValidatedCustomFilterOperation is not supposed to end up in the RenderStyle.
    378             ASSERT_NOT_REACHED();
    379             continue;
    380         }
    381         if (operationType == FilterOperation::CUSTOM) {
    382             RefPtr<CustomFilterOperation> operation = createCustomFilterOperation(filterValue, state);
    383             if (!operation)
    384                 return false;
    385 
    386             operations.operations().append(operation);
    387             continue;
    388         }
    389         if (operationType == FilterOperation::REFERENCE) {
    390             if (filterValue->length() != 1)
    391                 continue;
    392             CSSValue* argument = filterValue->itemWithoutBoundsCheck(0);
    393 
    394             if (!argument->isSVGDocumentValue())
    395                 continue;
    396 
    397             CSSSVGDocumentValue* svgDocumentValue = toCSSSVGDocumentValue(argument);
    398             KURL url = state.document().completeURL(svgDocumentValue->url());
    399 
    400             RefPtr<ReferenceFilterOperation> operation = ReferenceFilterOperation::create(svgDocumentValue->url(), url.fragmentIdentifier());
    401             if (SVGURIReference::isExternalURIReference(svgDocumentValue->url(), state.document())) {
    402                 if (!svgDocumentValue->loadRequested())
    403                     state.elementStyleResources().addPendingSVGDocument(operation.get(), svgDocumentValue);
    404                 else if (svgDocumentValue->cachedSVGDocument())
    405                     ReferenceFilterBuilder::setDocumentResourceReference(operation.get(), adoptPtr(new DocumentResourceReference(svgDocumentValue->cachedSVGDocument())));
    406             }
    407             operations.operations().append(operation);
    408             continue;
    409         }
    410 
    411         // Check that all parameters are primitive values, with the
    412         // exception of drop shadow which has a CSSShadowValue parameter.
    413         if (operationType != FilterOperation::DROP_SHADOW) {
    414             bool haveNonPrimitiveValue = false;
    415             for (unsigned j = 0; j < filterValue->length(); ++j) {
    416                 if (!filterValue->itemWithoutBoundsCheck(j)->isPrimitiveValue()) {
    417                     haveNonPrimitiveValue = true;
    418                     break;
    419                 }
    420             }
    421             if (haveNonPrimitiveValue)
    422                 continue;
    423         }
    424 
    425         CSSPrimitiveValue* firstValue = filterValue->length() && filterValue->itemWithoutBoundsCheck(0)->isPrimitiveValue() ? toCSSPrimitiveValue(filterValue->itemWithoutBoundsCheck(0)) : 0;
    426         switch (filterValue->operationType()) {
    427         case CSSFilterValue::GrayscaleFilterOperation:
    428         case CSSFilterValue::SepiaFilterOperation:
    429         case CSSFilterValue::SaturateFilterOperation: {
    430             double amount = 1;
    431             if (filterValue->length() == 1) {
    432                 amount = firstValue->getDoubleValue();
    433                 if (firstValue->isPercentage())
    434                     amount /= 100;
    435             }
    436 
    437             operations.operations().append(BasicColorMatrixFilterOperation::create(amount, operationType));
    438             break;
    439         }
    440         case CSSFilterValue::HueRotateFilterOperation: {
    441             double angle = 0;
    442             if (filterValue->length() == 1)
    443                 angle = firstValue->computeDegrees();
    444 
    445             operations.operations().append(BasicColorMatrixFilterOperation::create(angle, operationType));
    446             break;
    447         }
    448         case CSSFilterValue::InvertFilterOperation:
    449         case CSSFilterValue::BrightnessFilterOperation:
    450         case CSSFilterValue::ContrastFilterOperation:
    451         case CSSFilterValue::OpacityFilterOperation: {
    452             double amount = (filterValue->operationType() == CSSFilterValue::BrightnessFilterOperation) ? 0 : 1;
    453             if (filterValue->length() == 1) {
    454                 amount = firstValue->getDoubleValue();
    455                 if (firstValue->isPercentage())
    456                     amount /= 100;
    457             }
    458 
    459             operations.operations().append(BasicComponentTransferFilterOperation::create(amount, operationType));
    460             break;
    461         }
    462         case CSSFilterValue::BlurFilterOperation: {
    463             Length stdDeviation = Length(0, Fixed);
    464             if (filterValue->length() >= 1)
    465                 stdDeviation = firstValue->convertToLength<FixedConversion | PercentConversion>(conversionData);
    466             if (stdDeviation.isUndefined())
    467                 return false;
    468 
    469             operations.operations().append(BlurFilterOperation::create(stdDeviation));
    470             break;
    471         }
    472         case CSSFilterValue::DropShadowFilterOperation: {
    473             if (filterValue->length() != 1)
    474                 return false;
    475 
    476             CSSValue* cssValue = filterValue->itemWithoutBoundsCheck(0);
    477             if (!cssValue->isShadowValue())
    478                 continue;
    479 
    480             CSSShadowValue* item = toCSSShadowValue(cssValue);
    481             IntPoint location(item->x->computeLength<int>(conversionData), item->y->computeLength<int>(conversionData));
    482             int blur = item->blur ? item->blur->computeLength<int>(conversionData) : 0;
    483             Color shadowColor;
    484             if (item->color)
    485                 shadowColor = state.document().textLinkColors().colorFromPrimitiveValue(item->color.get(), state.style()->color());
    486 
    487             operations.operations().append(DropShadowFilterOperation::create(location, blur, shadowColor.isValid() ? shadowColor : Color::transparent));
    488             break;
    489         }
    490         case CSSFilterValue::UnknownFilterOperation:
    491         default:
    492             ASSERT_NOT_REACHED();
    493             break;
    494         }
    495     }
    496 
    497     outOperations = operations;
    498     return true;
    499 }
    500 
    501 } // namespace WebCore
    502