1/* 2 * (C) 1999-2003 Lars Knoll (knoll@kde.org) 3 * Copyright (C) 2004, 2006, 2010, 2012 Apple Inc. All rights reserved. 4 * 5 * This library is free software; you can redistribute it and/or 6 * modify it under the terms of the GNU Library General Public 7 * License as published by the Free Software Foundation; either 8 * version 2 of the License, or (at your option) any later version. 9 * 10 * This library is distributed in the hope that it will be useful, 11 * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 13 * Library General Public License for more details. 14 * 15 * You should have received a copy of the GNU Library General Public License 16 * along with this library; see the file COPYING.LIB. If not, write to 17 * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, 18 * Boston, MA 02110-1301, USA. 19 */ 20#include "config.h" 21#include "MediaList.h" 22 23#include "CSSImportRule.h" 24#include "CSSParser.h" 25#include "CSSStyleSheet.h" 26#include "DOMWindow.h" 27#include "Document.h" 28#include "ExceptionCode.h" 29#include "MediaFeatureNames.h" 30#include "MediaQuery.h" 31#include "MediaQueryExp.h" 32#include "ScriptableDocumentParser.h" 33#include <wtf/text/StringBuilder.h> 34 35namespace WebCore { 36 37/* MediaList is used to store 3 types of media related entities which mean the same: 38 * Media Queries, Media Types and Media Descriptors. 39 * Currently MediaList always tries to parse media queries and if parsing fails, 40 * tries to fallback to Media Descriptors if m_fallbackToDescriptor flag is set. 41 * Slight problem with syntax error handling: 42 * CSS 2.1 Spec (http://www.w3.org/TR/CSS21/media.html) 43 * specifies that failing media type parsing is a syntax error 44 * CSS 3 Media Queries Spec (http://www.w3.org/TR/css3-mediaqueries/) 45 * specifies that failing media query is a syntax error 46 * HTML 4.01 spec (http://www.w3.org/TR/REC-html40/present/styles.html#adef-media) 47 * specifies that Media Descriptors should be parsed with forward-compatible syntax 48 * DOM Level 2 Style Sheet spec (http://www.w3.org/TR/DOM-Level-2-Style/) 49 * talks about MediaList.mediaText and refers 50 * - to Media Descriptors of HTML 4.0 in context of StyleSheet 51 * - to Media Types of CSS 2.0 in context of CSSMediaRule and CSSImportRule 52 * 53 * These facts create situation where same (illegal) media specification may result in 54 * different parses depending on whether it is media attr of style element or part of 55 * css @media rule. 56 * <style media="screen and resolution > 40dpi"> ..</style> will be enabled on screen devices where as 57 * @media screen and resolution > 40dpi {..} will not. 58 * This gets more counter-intuitive in JavaScript: 59 * document.styleSheets[0].media.mediaText = "screen and resolution > 40dpi" will be ok and 60 * enabled, while 61 * document.styleSheets[0].cssRules[0].media.mediaText = "screen and resolution > 40dpi" will 62 * throw SYNTAX_ERR exception. 63 */ 64 65MediaQuerySet::MediaQuerySet() 66 : m_fallbackToDescriptor(false) 67 , m_lastLine(0) 68{ 69} 70 71MediaQuerySet::MediaQuerySet(const String& mediaString, bool fallbackToDescriptor) 72 : m_fallbackToDescriptor(fallbackToDescriptor) 73 , m_lastLine(0) 74{ 75 bool success = parse(mediaString); 76 // FIXME: parsing can fail. The problem with failing constructor is that 77 // we would need additional flag saying MediaList is not valid 78 // Parse can fail only when fallbackToDescriptor == false, i.e when HTML4 media descriptor 79 // forward-compatible syntax is not in use. 80 // DOMImplementationCSS seems to mandate that media descriptors are used 81 // for both html and svg, even though svg:style doesn't use media descriptors 82 // Currently the only places where parsing can fail are 83 // creating <svg:style>, creating css media / import rules from js 84 85 // FIXME: This doesn't make much sense. 86 if (!success) 87 parse("invalid"); 88} 89 90MediaQuerySet::MediaQuerySet(const MediaQuerySet& o) 91 : RefCounted<MediaQuerySet>() 92 , m_fallbackToDescriptor(o.m_fallbackToDescriptor) 93 , m_lastLine(o.m_lastLine) 94 , m_queries(o.m_queries.size()) 95{ 96 for (unsigned i = 0; i < m_queries.size(); ++i) 97 m_queries[i] = o.m_queries[i]->copy(); 98} 99 100MediaQuerySet::~MediaQuerySet() 101{ 102} 103 104static String parseMediaDescriptor(const String& string) 105{ 106 // http://www.w3.org/TR/REC-html40/types.html#type-media-descriptors 107 // "Each entry is truncated just before the first character that isn't a 108 // US ASCII letter [a-zA-Z] (ISO 10646 hex 41-5a, 61-7a), digit [0-9] (hex 30-39), 109 // or hyphen (hex 2d)." 110 unsigned length = string.length(); 111 unsigned i = 0; 112 for (; i < length; ++i) { 113 unsigned short c = string[i]; 114 if (! ((c >= 'a' && c <= 'z') 115 || (c >= 'A' && c <= 'Z') 116 || (c >= '1' && c <= '9') 117 || (c == '-'))) 118 break; 119 } 120 return string.left(i); 121} 122 123bool MediaQuerySet::parse(const String& mediaString) 124{ 125 CSSParser parser(CSSStrictMode); 126 127 Vector<std::unique_ptr<MediaQuery>> result; 128 Vector<String> list; 129 mediaString.split(',', list); 130 for (unsigned i = 0; i < list.size(); ++i) { 131 String medium = list[i].stripWhiteSpace(); 132 if (medium.isEmpty()) { 133 if (!m_fallbackToDescriptor) 134 return false; 135 continue; 136 } 137 std::unique_ptr<MediaQuery> mediaQuery = parser.parseMediaQuery(medium); 138 if (!mediaQuery) { 139 if (!m_fallbackToDescriptor) 140 return false; 141 String mediaDescriptor = parseMediaDescriptor(medium); 142 if (mediaDescriptor.isNull()) 143 continue; 144 mediaQuery = std::make_unique<MediaQuery>(MediaQuery::None, mediaDescriptor, nullptr); 145 } 146 result.append(WTF::move(mediaQuery)); 147 } 148 // ",,,," falls straight through, but is not valid unless fallback 149 if (!m_fallbackToDescriptor && list.isEmpty()) { 150 String strippedMediaString = mediaString.stripWhiteSpace(); 151 if (!strippedMediaString.isEmpty()) 152 return false; 153 } 154 m_queries = WTF::move(result); 155 return true; 156} 157 158bool MediaQuerySet::add(const String& queryString) 159{ 160 CSSParser parser(CSSStrictMode); 161 162 std::unique_ptr<MediaQuery> parsedQuery = parser.parseMediaQuery(queryString); 163 if (!parsedQuery && m_fallbackToDescriptor) { 164 String medium = parseMediaDescriptor(queryString); 165 if (!medium.isNull()) 166 parsedQuery = std::make_unique<MediaQuery>(MediaQuery::None, medium, nullptr); 167 } 168 if (!parsedQuery) 169 return false; 170 171 m_queries.append(WTF::move(parsedQuery)); 172 return true; 173} 174 175bool MediaQuerySet::remove(const String& queryStringToRemove) 176{ 177 CSSParser parser(CSSStrictMode); 178 179 std::unique_ptr<MediaQuery> parsedQuery = parser.parseMediaQuery(queryStringToRemove); 180 if (!parsedQuery && m_fallbackToDescriptor) { 181 String medium = parseMediaDescriptor(queryStringToRemove); 182 if (!medium.isNull()) 183 parsedQuery = std::make_unique<MediaQuery>(MediaQuery::None, medium, nullptr); 184 } 185 if (!parsedQuery) 186 return false; 187 188 for (size_t i = 0; i < m_queries.size(); ++i) { 189 MediaQuery* query = m_queries[i].get(); 190 if (*query == *parsedQuery) { 191 m_queries.remove(i); 192 return true; 193 } 194 } 195 return false; 196} 197 198void MediaQuerySet::addMediaQuery(std::unique_ptr<MediaQuery> mediaQuery) 199{ 200 m_queries.append(WTF::move(mediaQuery)); 201} 202 203String MediaQuerySet::mediaText() const 204{ 205 StringBuilder text; 206 207 bool first = true; 208 for (size_t i = 0; i < m_queries.size(); ++i) { 209 if (!first) 210 text.appendLiteral(", "); 211 else 212 first = false; 213 text.append(m_queries[i]->cssText()); 214 } 215 return text.toString(); 216} 217 218MediaList::MediaList(MediaQuerySet* mediaQueries, CSSStyleSheet* parentSheet) 219 : m_mediaQueries(mediaQueries) 220 , m_parentStyleSheet(parentSheet) 221 , m_parentRule(0) 222{ 223} 224 225MediaList::MediaList(MediaQuerySet* mediaQueries, CSSRule* parentRule) 226 : m_mediaQueries(mediaQueries) 227 , m_parentStyleSheet(0) 228 , m_parentRule(parentRule) 229{ 230} 231 232MediaList::~MediaList() 233{ 234} 235 236void MediaList::setMediaText(const String& value, ExceptionCode& ec) 237{ 238 CSSStyleSheet::RuleMutationScope mutationScope(m_parentRule); 239 240 bool success = m_mediaQueries->parse(value); 241 if (!success) { 242 ec = SYNTAX_ERR; 243 return; 244 } 245 if (m_parentStyleSheet) 246 m_parentStyleSheet->didMutate(); 247} 248 249String MediaList::item(unsigned index) const 250{ 251 auto& queries = m_mediaQueries->queryVector(); 252 if (index < queries.size()) 253 return queries[index]->cssText(); 254 return String(); 255} 256 257void MediaList::deleteMedium(const String& medium, ExceptionCode& ec) 258{ 259 CSSStyleSheet::RuleMutationScope mutationScope(m_parentRule); 260 261 bool success = m_mediaQueries->remove(medium); 262 if (!success) { 263 ec = NOT_FOUND_ERR; 264 return; 265 } 266 if (m_parentStyleSheet) 267 m_parentStyleSheet->didMutate(); 268} 269 270void MediaList::appendMedium(const String& medium, ExceptionCode& ec) 271{ 272 CSSStyleSheet::RuleMutationScope mutationScope(m_parentRule); 273 274 bool success = m_mediaQueries->add(medium); 275 if (!success) { 276 // FIXME: Should this really be INVALID_CHARACTER_ERR? 277 ec = INVALID_CHARACTER_ERR; 278 return; 279 } 280 if (m_parentStyleSheet) 281 m_parentStyleSheet->didMutate(); 282} 283 284void MediaList::reattach(MediaQuerySet* mediaQueries) 285{ 286 ASSERT(mediaQueries); 287 m_mediaQueries = mediaQueries; 288} 289 290#if ENABLE(RESOLUTION_MEDIA_QUERY) 291static void addResolutionWarningMessageToConsole(Document* document, const String& serializedExpression, const CSSPrimitiveValue* value) 292{ 293 ASSERT(document); 294 ASSERT(value); 295 296 DEPRECATED_DEFINE_STATIC_LOCAL(String, mediaQueryMessage, (ASCIILiteral("Consider using 'dppx' units instead of '%replacementUnits%', as in CSS '%replacementUnits%' means dots-per-CSS-%lengthUnit%, not dots-per-physical-%lengthUnit%, so does not correspond to the actual '%replacementUnits%' of a screen. In media query expression: "))); 297 DEPRECATED_DEFINE_STATIC_LOCAL(String, mediaValueDPI, (ASCIILiteral("dpi"))); 298 DEPRECATED_DEFINE_STATIC_LOCAL(String, mediaValueDPCM, (ASCIILiteral("dpcm"))); 299 DEPRECATED_DEFINE_STATIC_LOCAL(String, lengthUnitInch, (ASCIILiteral("inch"))); 300 DEPRECATED_DEFINE_STATIC_LOCAL(String, lengthUnitCentimeter, (ASCIILiteral("centimeter"))); 301 302 String message; 303 if (value->isDotsPerInch()) 304 message = String(mediaQueryMessage).replace("%replacementUnits%", mediaValueDPI).replace("%lengthUnit%", lengthUnitInch); 305 else if (value->isDotsPerCentimeter()) 306 message = String(mediaQueryMessage).replace("%replacementUnits%", mediaValueDPCM).replace("%lengthUnit%", lengthUnitCentimeter); 307 else 308 ASSERT_NOT_REACHED(); 309 310 message.append(serializedExpression); 311 312 document->addConsoleMessage(MessageSource::CSS, MessageLevel::Debug, message); 313} 314 315void reportMediaQueryWarningIfNeeded(Document* document, const MediaQuerySet* mediaQuerySet) 316{ 317 if (!mediaQuerySet || !document) 318 return; 319 320 auto& mediaQueries = mediaQuerySet->queryVector(); 321 const size_t queryCount = mediaQueries.size(); 322 323 if (!queryCount) 324 return; 325 326 for (size_t i = 0; i < queryCount; ++i) { 327 const MediaQuery* query = mediaQueries[i].get(); 328 String mediaType = query->mediaType(); 329 if (!query->ignored() && !equalIgnoringCase(mediaType, "print")) { 330 auto& expressions = query->expressions(); 331 for (size_t j = 0; j < expressions.size(); ++j) { 332 const MediaQueryExp* exp = expressions.at(j).get(); 333 if (exp->mediaFeature() == MediaFeatureNames::resolutionMediaFeature || exp->mediaFeature() == MediaFeatureNames::max_resolutionMediaFeature || exp->mediaFeature() == MediaFeatureNames::min_resolutionMediaFeature) { 334 CSSValue* cssValue = exp->value(); 335 if (cssValue && cssValue->isPrimitiveValue()) { 336 CSSPrimitiveValue* primitiveValue = toCSSPrimitiveValue(cssValue); 337 if (primitiveValue->isDotsPerInch() || primitiveValue->isDotsPerCentimeter()) 338 addResolutionWarningMessageToConsole(document, mediaQuerySet->mediaText(), primitiveValue); 339 } 340 } 341 } 342 } 343 } 344} 345#endif 346 347} 348