| 1 | // Copyright 2014 The Chromium Authors. All rights reserved. |
| 2 | // Copyright (C) 2016 Apple Inc. All rights reserved. |
| 3 | // |
| 4 | // Redistribution and use in source and binary forms, with or without |
| 5 | // modification, are permitted provided that the following conditions are |
| 6 | // met: |
| 7 | // |
| 8 | // * Redistributions of source code must retain the above copyright |
| 9 | // notice, this list of conditions and the following disclaimer. |
| 10 | // * Redistributions in binary form must reproduce the above |
| 11 | // copyright notice, this list of conditions and the following disclaimer |
| 12 | // in the documentation and/or other materials provided with the |
| 13 | // distribution. |
| 14 | // * Neither the name of Google Inc. nor the names of its |
| 15 | // contributors may be used to endorse or promote products derived from |
| 16 | // this software without specific prior written permission. |
| 17 | // |
| 18 | // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS |
| 19 | // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT |
| 20 | // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR |
| 21 | // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT |
| 22 | // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, |
| 23 | // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT |
| 24 | // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, |
| 25 | // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY |
| 26 | // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT |
| 27 | // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE |
| 28 | // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
| 29 | |
| 30 | #include "config.h" |
| 31 | #include "SizesAttributeParser.h" |
| 32 | |
| 33 | #include "CSSPrimitiveValue.h" |
| 34 | #include "CSSToLengthConversionData.h" |
| 35 | #include "CSSTokenizer.h" |
| 36 | #include "FontCascade.h" |
| 37 | #include "MediaList.h" |
| 38 | #include "MediaQueryEvaluator.h" |
| 39 | #include "MediaQueryParser.h" |
| 40 | #include "MediaQueryParserContext.h" |
| 41 | #include "RenderView.h" |
| 42 | #include "SizesCalcParser.h" |
| 43 | #include "StyleScope.h" |
| 44 | |
| 45 | namespace WebCore { |
| 46 | |
| 47 | float SizesAttributeParser::computeLength(double value, CSSPrimitiveValue::UnitType type, const Document& document) |
| 48 | { |
| 49 | auto* renderer = document.renderView(); |
| 50 | if (!renderer) |
| 51 | return 0; |
| 52 | auto& style = renderer->style(); |
| 53 | |
| 54 | CSSToLengthConversionData conversionData(&style, &style, renderer); |
| 55 | |
| 56 | // Because we evaluate "sizes" at parse time (before style has been resolved), the font metrics used for these specific units |
| 57 | // are not available. The font selector's internal consistency isn't guaranteed just yet, so we can just temporarily clear |
| 58 | // the pointer to it for the duration of the unit evaluation. This is acceptible because the style always comes from the |
| 59 | // RenderView, which has its font information hardcoded in resolveForDocument() to be -webkit-standard, whose operations |
| 60 | // don't require a font selector. |
| 61 | if (type == CSSPrimitiveValue::CSS_EXS || type == CSSPrimitiveValue::CSS_CHS) { |
| 62 | RefPtr<FontSelector> fontSelector = style.fontCascade().fontSelector(); |
| 63 | style.fontCascade().update(nullptr); |
| 64 | float result = CSSPrimitiveValue::computeNonCalcLengthDouble(conversionData, type, value); |
| 65 | style.fontCascade().update(fontSelector.get()); |
| 66 | return result; |
| 67 | } |
| 68 | |
| 69 | return clampTo<float>(CSSPrimitiveValue::computeNonCalcLengthDouble(conversionData, type, value)); |
| 70 | } |
| 71 | |
| 72 | SizesAttributeParser::SizesAttributeParser(const String& attribute, const Document& document) |
| 73 | : m_document(document) |
| 74 | , m_length(0) |
| 75 | , m_lengthWasSet(false) |
| 76 | { |
| 77 | m_isValid = parse(CSSTokenizer(attribute).tokenRange()); |
| 78 | } |
| 79 | |
| 80 | float SizesAttributeParser::length() |
| 81 | { |
| 82 | if (m_isValid) |
| 83 | return effectiveSize(); |
| 84 | return effectiveSizeDefaultValue(); |
| 85 | } |
| 86 | |
| 87 | bool SizesAttributeParser::calculateLengthInPixels(CSSParserTokenRange range, float& result) |
| 88 | { |
| 89 | const CSSParserToken& startToken = range.peek(); |
| 90 | CSSParserTokenType type = startToken.type(); |
| 91 | if (type == DimensionToken) { |
| 92 | if (!CSSPrimitiveValue::isLength(startToken.unitType())) |
| 93 | return false; |
| 94 | result = computeLength(startToken.numericValue(), startToken.unitType(), m_document); |
| 95 | if (result >= 0) |
| 96 | return true; |
| 97 | } else if (type == FunctionToken) { |
| 98 | SizesCalcParser calcParser(range, m_document); |
| 99 | if (!calcParser.isValid()) |
| 100 | return false; |
| 101 | result = calcParser.result(); |
| 102 | return true; |
| 103 | } else if (type == NumberToken && !startToken.numericValue()) { |
| 104 | result = 0; |
| 105 | return true; |
| 106 | } |
| 107 | |
| 108 | return false; |
| 109 | } |
| 110 | |
| 111 | bool SizesAttributeParser::mediaConditionMatches(const MediaQuerySet& mediaCondition) |
| 112 | { |
| 113 | // A Media Condition cannot have a media type other than screen. |
| 114 | auto* renderer = m_document.renderView(); |
| 115 | if (!renderer) |
| 116 | return false; |
| 117 | auto& style = renderer->style(); |
| 118 | return MediaQueryEvaluator { "screen" , m_document, &style }.evaluate(mediaCondition, const_cast<Style::Scope&>(m_document.styleScope()).resolverIfExists()); |
| 119 | } |
| 120 | |
| 121 | bool SizesAttributeParser::parse(CSSParserTokenRange range) |
| 122 | { |
| 123 | // Split on a comma token and parse the result tokens as (media-condition, length) pairs |
| 124 | while (!range.atEnd()) { |
| 125 | const CSSParserToken* mediaConditionStart = &range.peek(); |
| 126 | // The length is the last component value before the comma which isn't whitespace or a comment |
| 127 | const CSSParserToken* lengthTokenStart = &range.peek(); |
| 128 | const CSSParserToken* lengthTokenEnd = &range.peek(); |
| 129 | while (!range.atEnd() && range.peek().type() != CommaToken) { |
| 130 | lengthTokenStart = &range.peek(); |
| 131 | range.consumeComponentValue(); |
| 132 | lengthTokenEnd = &range.peek(); |
| 133 | range.consumeWhitespace(); |
| 134 | } |
| 135 | range.consume(); |
| 136 | |
| 137 | float length; |
| 138 | if (!calculateLengthInPixels(range.makeSubRange(lengthTokenStart, lengthTokenEnd), length)) |
| 139 | continue; |
| 140 | RefPtr<MediaQuerySet> mediaCondition = MediaQueryParser::parseMediaCondition(range.makeSubRange(mediaConditionStart, lengthTokenStart), MediaQueryParserContext(m_document)); |
| 141 | if (!mediaCondition || !mediaConditionMatches(*mediaCondition)) |
| 142 | continue; |
| 143 | m_length = length; |
| 144 | m_lengthWasSet = true; |
| 145 | return true; |
| 146 | } |
| 147 | return false; |
| 148 | } |
| 149 | |
| 150 | float SizesAttributeParser::effectiveSize() |
| 151 | { |
| 152 | if (m_lengthWasSet) |
| 153 | return m_length; |
| 154 | return effectiveSizeDefaultValue(); |
| 155 | } |
| 156 | |
| 157 | unsigned SizesAttributeParser::effectiveSizeDefaultValue() |
| 158 | { |
| 159 | auto* renderer = m_document.renderView(); |
| 160 | if (!renderer) |
| 161 | return 0; |
| 162 | auto& style = renderer->style(); |
| 163 | return clampTo<float>(CSSPrimitiveValue::computeNonCalcLengthDouble({ &style, &style, renderer }, CSSPrimitiveValue::CSS_VW, 100.0)); |
| 164 | } |
| 165 | |
| 166 | } // namespace WebCore |
| 167 | |