1/* 2 * CSS Media Query 3 * 4 * Copyright (C) 2006 Kimmo Kinnunen <kimmo.t.kinnunen@nokia.com>. 5 * Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies). 6 * Copyright (C) 2013 Apple Inc. All rights reserved. 7 * 8 * Redistribution and use in source and binary forms, with or without 9 * modification, are permitted provided that the following conditions 10 * are met: 11 * 1. Redistributions of source code must retain the above copyright 12 * notice, this list of conditions and the following disclaimer. 13 * 2. Redistributions in binary form must reproduce the above copyright 14 * notice, this list of conditions and the following disclaimer in the 15 * documentation and/or other materials provided with the distribution. 16 * 17 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY 18 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 19 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 20 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE COMPUTER, INC. OR 21 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, 22 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, 23 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR 24 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY 25 * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 26 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 27 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 28 */ 29 30#include "config.h" 31#include "core/css/MediaQueryExp.h" 32 33#include "core/css/CSSAspectRatioValue.h" 34#include "core/css/CSSPrimitiveValue.h" 35#include "core/css/parser/CSSParserValues.h" 36#include "core/html/parser/HTMLParserIdioms.h" 37#include "platform/Decimal.h" 38#include "platform/RuntimeEnabledFeatures.h" 39#include "wtf/text/StringBuffer.h" 40#include "wtf/text/StringBuilder.h" 41 42namespace blink { 43 44using namespace MediaFeatureNames; 45 46static inline bool featureWithCSSValueID(const String& mediaFeature, const CSSParserValue* value) 47{ 48 if (!value->id) 49 return false; 50 51 return mediaFeature == orientationMediaFeature 52 || mediaFeature == pointerMediaFeature 53 || mediaFeature == anyPointerMediaFeature 54 || (mediaFeature == hoverMediaFeature && RuntimeEnabledFeatures::hoverMediaQueryKeywordsEnabled()) 55 || mediaFeature == anyHoverMediaFeature 56 || mediaFeature == scanMediaFeature; 57} 58 59static inline bool featureWithValidIdent(const String& mediaFeature, CSSValueID ident) 60{ 61 if (mediaFeature == orientationMediaFeature) 62 return ident == CSSValuePortrait || ident == CSSValueLandscape; 63 64 if (mediaFeature == pointerMediaFeature || mediaFeature == anyPointerMediaFeature) 65 return ident == CSSValueNone || ident == CSSValueCoarse || ident == CSSValueFine; 66 67 if ((mediaFeature == hoverMediaFeature && RuntimeEnabledFeatures::hoverMediaQueryKeywordsEnabled()) 68 || mediaFeature == anyHoverMediaFeature) 69 return ident == CSSValueNone || ident == CSSValueOnDemand || ident == CSSValueHover; 70 71 if (mediaFeature == scanMediaFeature) 72 return ident == CSSValueInterlace || ident == CSSValueProgressive; 73 74 ASSERT_NOT_REACHED(); 75 return false; 76} 77 78static bool positiveLengthUnit(const int unit) 79{ 80 switch (unit) { 81 case CSSPrimitiveValue::CSS_EMS: 82 case CSSPrimitiveValue::CSS_EXS: 83 case CSSPrimitiveValue::CSS_PX: 84 case CSSPrimitiveValue::CSS_CM: 85 case CSSPrimitiveValue::CSS_MM: 86 case CSSPrimitiveValue::CSS_IN: 87 case CSSPrimitiveValue::CSS_PT: 88 case CSSPrimitiveValue::CSS_PC: 89 case CSSPrimitiveValue::CSS_REMS: 90 case CSSPrimitiveValue::CSS_CHS: 91 return true; 92 } 93 return false; 94} 95 96static inline bool featureWithValidPositiveLength(const String& mediaFeature, const CSSParserValue* value) 97{ 98 if (!(positiveLengthUnit(value->unit) || (value->unit == CSSPrimitiveValue::CSS_NUMBER && value->fValue == 0)) || value->fValue < 0) 99 return false; 100 101 102 return mediaFeature == heightMediaFeature 103 || mediaFeature == maxHeightMediaFeature 104 || mediaFeature == minHeightMediaFeature 105 || mediaFeature == widthMediaFeature 106 || mediaFeature == maxWidthMediaFeature 107 || mediaFeature == minWidthMediaFeature 108 || mediaFeature == deviceHeightMediaFeature 109 || mediaFeature == maxDeviceHeightMediaFeature 110 || mediaFeature == minDeviceHeightMediaFeature 111 || mediaFeature == deviceWidthMediaFeature 112 || mediaFeature == minDeviceWidthMediaFeature 113 || mediaFeature == maxDeviceWidthMediaFeature; 114} 115 116static inline bool featureWithValidDensity(const String& mediaFeature, const CSSParserValue* value) 117{ 118 if ((value->unit != CSSPrimitiveValue::CSS_DPPX && value->unit != CSSPrimitiveValue::CSS_DPI && value->unit != CSSPrimitiveValue::CSS_DPCM) || value->fValue <= 0) 119 return false; 120 121 return mediaFeature == resolutionMediaFeature 122 || mediaFeature == minResolutionMediaFeature 123 || mediaFeature == maxResolutionMediaFeature; 124} 125 126static inline bool featureWithPositiveInteger(const String& mediaFeature, const CSSParserValue* value) 127{ 128 if (!value->isInt || value->fValue < 0) 129 return false; 130 131 return mediaFeature == colorMediaFeature 132 || mediaFeature == maxColorMediaFeature 133 || mediaFeature == minColorMediaFeature 134 || mediaFeature == colorIndexMediaFeature 135 || mediaFeature == maxColorIndexMediaFeature 136 || mediaFeature == minColorIndexMediaFeature 137 || mediaFeature == monochromeMediaFeature 138 || mediaFeature == maxMonochromeMediaFeature 139 || mediaFeature == minMonochromeMediaFeature; 140} 141 142static inline bool featureWithPositiveNumber(const String& mediaFeature, const CSSParserValue* value) 143{ 144 if (value->unit != CSSPrimitiveValue::CSS_NUMBER || value->fValue < 0) 145 return false; 146 147 return mediaFeature == transform3dMediaFeature 148 || mediaFeature == devicePixelRatioMediaFeature 149 || mediaFeature == maxDevicePixelRatioMediaFeature 150 || mediaFeature == minDevicePixelRatioMediaFeature; 151} 152 153static inline bool featureWithZeroOrOne(const String& mediaFeature, const CSSParserValue* value) 154{ 155 if (!value->isInt || !(value->fValue == 1 || !value->fValue)) 156 return false; 157 158 return mediaFeature == gridMediaFeature 159 || (mediaFeature == hoverMediaFeature && !RuntimeEnabledFeatures::hoverMediaQueryKeywordsEnabled()); 160} 161 162static inline bool featureWithAspectRatio(const String& mediaFeature) 163{ 164 return mediaFeature == aspectRatioMediaFeature 165 || mediaFeature == deviceAspectRatioMediaFeature 166 || mediaFeature == minAspectRatioMediaFeature 167 || mediaFeature == maxAspectRatioMediaFeature 168 || mediaFeature == minDeviceAspectRatioMediaFeature 169 || mediaFeature == maxDeviceAspectRatioMediaFeature; 170} 171 172static inline bool featureWithoutValue(const String& mediaFeature) 173{ 174 // Media features that are prefixed by min/max cannot be used without a value. 175 return mediaFeature == monochromeMediaFeature 176 || mediaFeature == colorMediaFeature 177 || mediaFeature == colorIndexMediaFeature 178 || mediaFeature == gridMediaFeature 179 || mediaFeature == heightMediaFeature 180 || mediaFeature == widthMediaFeature 181 || mediaFeature == deviceHeightMediaFeature 182 || mediaFeature == deviceWidthMediaFeature 183 || mediaFeature == orientationMediaFeature 184 || mediaFeature == aspectRatioMediaFeature 185 || mediaFeature == deviceAspectRatioMediaFeature 186 || mediaFeature == hoverMediaFeature 187 || mediaFeature == anyHoverMediaFeature 188 || mediaFeature == transform3dMediaFeature 189 || mediaFeature == pointerMediaFeature 190 || mediaFeature == anyPointerMediaFeature 191 || mediaFeature == devicePixelRatioMediaFeature 192 || mediaFeature == resolutionMediaFeature 193 || mediaFeature == scanMediaFeature; 194} 195 196bool MediaQueryExp::isViewportDependent() const 197{ 198 return m_mediaFeature == widthMediaFeature 199 || m_mediaFeature == heightMediaFeature 200 || m_mediaFeature == minWidthMediaFeature 201 || m_mediaFeature == minHeightMediaFeature 202 || m_mediaFeature == maxWidthMediaFeature 203 || m_mediaFeature == maxHeightMediaFeature 204 || m_mediaFeature == orientationMediaFeature 205 || m_mediaFeature == aspectRatioMediaFeature 206 || m_mediaFeature == minAspectRatioMediaFeature 207 || m_mediaFeature == devicePixelRatioMediaFeature 208 || m_mediaFeature == resolutionMediaFeature 209 || m_mediaFeature == maxAspectRatioMediaFeature; 210} 211 212MediaQueryExp::MediaQueryExp(const MediaQueryExp& other) 213 : m_mediaFeature(other.mediaFeature()) 214 , m_expValue(other.expValue()) 215{ 216} 217 218MediaQueryExp::MediaQueryExp(const String& mediaFeature, const MediaQueryExpValue& expValue) 219 : m_mediaFeature(mediaFeature) 220 , m_expValue(expValue) 221{ 222} 223 224PassOwnPtrWillBeRawPtr<MediaQueryExp> MediaQueryExp::createIfValid(const String& mediaFeature, CSSParserValueList* valueList) 225{ 226 ASSERT(!mediaFeature.isNull()); 227 228 MediaQueryExpValue expValue; 229 bool isValid = false; 230 String lowerMediaFeature = attemptStaticStringCreation(mediaFeature.lower()); 231 232 // Create value for media query expression that must have 1 or more values. 233 if (valueList && valueList->size() > 0) { 234 if (valueList->size() == 1) { 235 CSSParserValue* value = valueList->current(); 236 ASSERT(value); 237 238 if (featureWithCSSValueID(lowerMediaFeature, value) && featureWithValidIdent(lowerMediaFeature, value->id)) { 239 // Media features that use CSSValueIDs. 240 expValue.id = value->id; 241 expValue.unit = CSSPrimitiveValue::CSS_VALUE_ID; 242 expValue.isID = true; 243 } else if (featureWithValidDensity(lowerMediaFeature, value) 244 || featureWithValidPositiveLength(lowerMediaFeature, value)) { 245 // Media features that must have non-negative <density>, ie. dppx, dpi or dpcm, 246 // or Media features that must have non-negative <length> or number value. 247 expValue.value = value->fValue; 248 expValue.unit = (CSSPrimitiveValue::UnitType)value->unit; 249 expValue.isValue = true; 250 } else if (featureWithPositiveInteger(lowerMediaFeature, value) 251 || featureWithPositiveNumber(lowerMediaFeature, value) 252 || featureWithZeroOrOne(lowerMediaFeature, value)) { 253 // Media features that must have non-negative integer value, 254 // or media features that must have non-negative number value, 255 // or media features that must have (0|1) value. 256 expValue.value = value->fValue; 257 expValue.unit = CSSPrimitiveValue::CSS_NUMBER; 258 expValue.isValue = true; 259 } 260 261 isValid = (expValue.isID || expValue.isValue); 262 263 } else if (valueList->size() == 3 && featureWithAspectRatio(lowerMediaFeature)) { 264 // Create list of values. 265 // Currently accepts only <integer>/<integer>. 266 // Applicable to device-aspect-ratio and aspec-ratio. 267 isValid = true; 268 float numeratorValue = 0; 269 float denominatorValue = 0; 270 // The aspect-ratio must be <integer> (whitespace)? / (whitespace)? <integer>. 271 for (unsigned i = 0; i < 3; ++i, valueList->next()) { 272 const CSSParserValue* value = valueList->current(); 273 if (i != 1 && value->unit == CSSPrimitiveValue::CSS_NUMBER && value->fValue > 0 && value->isInt) { 274 if (!i) 275 numeratorValue = value->fValue; 276 else 277 denominatorValue = value->fValue; 278 } else if (i == 1 && value->unit == CSSParserValue::Operator && value->iValue == '/') { 279 continue; 280 } else { 281 isValid = false; 282 break; 283 } 284 } 285 286 if (isValid) { 287 expValue.numerator = (unsigned)numeratorValue; 288 expValue.denominator = (unsigned)denominatorValue; 289 expValue.isRatio = true; 290 } 291 } 292 } else if (featureWithoutValue(lowerMediaFeature)) { 293 isValid = true; 294 } 295 296 if (!isValid) 297 return nullptr; 298 299 return adoptPtrWillBeNoop(new MediaQueryExp(lowerMediaFeature, expValue)); 300} 301 302MediaQueryExp::~MediaQueryExp() 303{ 304} 305 306bool MediaQueryExp::operator==(const MediaQueryExp& other) const 307{ 308 return (other.m_mediaFeature == m_mediaFeature) 309 && ((!other.m_expValue.isValid() && !m_expValue.isValid()) 310 || (other.m_expValue.isValid() && m_expValue.isValid() && other.m_expValue.equals(m_expValue))); 311} 312 313String MediaQueryExp::serialize() const 314{ 315 StringBuilder result; 316 result.append('('); 317 result.append(m_mediaFeature.lower()); 318 if (m_expValue.isValid()) { 319 result.appendLiteral(": "); 320 result.append(m_expValue.cssText()); 321 } 322 result.append(')'); 323 324 return result.toString(); 325} 326 327static inline String printNumber(double number) 328{ 329 return Decimal::fromDouble(number).toString(); 330} 331 332String MediaQueryExpValue::cssText() const 333{ 334 StringBuilder output; 335 if (isValue) { 336 output.append(printNumber(value)); 337 output.append(CSSPrimitiveValue::unitTypeToString(unit)); 338 } else if (isRatio) { 339 output.append(printNumber(numerator)); 340 output.append('/'); 341 output.append(printNumber(denominator)); 342 } else if (isID) { 343 output.append(getValueName(id)); 344 } 345 346 return output.toString(); 347} 348 349} // namespace 350