1 /* 2 * (C) 1999-2003 Lars Knoll (knoll (at) 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 "core/css/MediaList.h" 22 23 #include "bindings/v8/ExceptionState.h" 24 #include "core/MediaFeatureNames.h" 25 #include "core/css/parser/BisonCSSParser.h" 26 #include "core/css/CSSStyleSheet.h" 27 #include "core/css/MediaQuery.h" 28 #include "core/css/MediaQueryExp.h" 29 #include "core/css/parser/MediaQueryParser.h" 30 #include "core/dom/Document.h" 31 #include "core/frame/LocalDOMWindow.h" 32 #include "wtf/text/StringBuilder.h" 33 34 namespace WebCore { 35 36 /* MediaList is used to store 3 types of media related entities which mean the same: 37 * 38 * Media Queries, Media Types and Media Descriptors. 39 * 40 * Media queries, as described in the Media Queries Level 3 specification, build on 41 * the mechanism outlined in HTML4. The syntax of media queries fit into the media 42 * type syntax reserved in HTML4. The media attribute of HTML4 also exists in XHTML 43 * and generic XML. The same syntax can also be used inside the @media and @import 44 * rules of CSS. 45 * 46 * However, the parsing rules for media queries are incompatible with those of HTML4 47 * and are consistent with those of media queries used in CSS. 48 * 49 * HTML5 (at the moment of writing still work in progress) references the Media Queries 50 * specification directly and thus updates the rules for HTML. 51 * 52 * CSS 2.1 Spec (http://www.w3.org/TR/CSS21/media.html) 53 * CSS 3 Media Queries Spec (http://www.w3.org/TR/css3-mediaqueries/) 54 */ 55 56 MediaQuerySet::MediaQuerySet() 57 { 58 } 59 60 MediaQuerySet::MediaQuerySet(const MediaQuerySet& o) 61 : m_queries(o.m_queries.size()) 62 { 63 for (unsigned i = 0; i < m_queries.size(); ++i) 64 m_queries[i] = o.m_queries[i]->copy(); 65 } 66 67 DEFINE_EMPTY_DESTRUCTOR_WILL_BE_REMOVED(MediaQuerySet) 68 69 PassRefPtrWillBeRawPtr<MediaQuerySet> MediaQuerySet::create(const String& mediaString) 70 { 71 if (mediaString.isEmpty()) 72 return MediaQuerySet::create(); 73 74 if (RuntimeEnabledFeatures::mediaQueryParserEnabled()) 75 return MediaQueryParser::parseMediaQuerySet(mediaString); 76 77 BisonCSSParser parser(strictCSSParserContext()); 78 return parser.parseMediaQueryList(mediaString); 79 } 80 81 PassRefPtrWillBeRawPtr<MediaQuerySet> MediaQuerySet::createOffMainThread(const String& mediaString) 82 { 83 if (mediaString.isEmpty()) 84 return MediaQuerySet::create(); 85 86 return MediaQueryParser::parseMediaQuerySet(mediaString); 87 } 88 89 bool MediaQuerySet::set(const String& mediaString) 90 { 91 RefPtrWillBeRawPtr<MediaQuerySet> result = create(mediaString); 92 m_queries.swap(result->m_queries); 93 return true; 94 } 95 96 bool MediaQuerySet::add(const String& queryString) 97 { 98 // To "parse a media query" for a given string means to follow "the parse 99 // a media query list" steps and return "null" if more than one media query 100 // is returned, or else the returned media query. 101 RefPtrWillBeRawPtr<MediaQuerySet> result = create(queryString); 102 103 // Only continue if exactly one media query is found, as described above. 104 if (result->m_queries.size() != 1) 105 return true; 106 107 OwnPtrWillBeRawPtr<MediaQuery> newQuery = result->m_queries[0].release(); 108 ASSERT(newQuery); 109 110 // If comparing with any of the media queries in the collection of media 111 // queries returns true terminate these steps. 112 for (size_t i = 0; i < m_queries.size(); ++i) { 113 MediaQuery* query = m_queries[i].get(); 114 if (*query == *newQuery) 115 return true; 116 } 117 118 m_queries.append(newQuery.release()); 119 return true; 120 } 121 122 bool MediaQuerySet::remove(const String& queryStringToRemove) 123 { 124 // To "parse a media query" for a given string means to follow "the parse 125 // a media query list" steps and return "null" if more than one media query 126 // is returned, or else the returned media query. 127 RefPtrWillBeRawPtr<MediaQuerySet> result = create(queryStringToRemove); 128 129 // Only continue if exactly one media query is found, as described above. 130 if (result->m_queries.size() != 1) 131 return true; 132 133 OwnPtrWillBeRawPtr<MediaQuery> newQuery = result->m_queries[0].release(); 134 ASSERT(newQuery); 135 136 // Remove any media query from the collection of media queries for which 137 // comparing with the media query returns true. 138 bool found = false; 139 for (size_t i = 0; i < m_queries.size(); ++i) { 140 MediaQuery* query = m_queries[i].get(); 141 if (*query == *newQuery) { 142 m_queries.remove(i); 143 --i; 144 found = true; 145 } 146 } 147 148 return found; 149 } 150 151 void MediaQuerySet::addMediaQuery(PassOwnPtrWillBeRawPtr<MediaQuery> mediaQuery) 152 { 153 m_queries.append(mediaQuery); 154 } 155 156 String MediaQuerySet::mediaText() const 157 { 158 StringBuilder text; 159 160 bool first = true; 161 for (size_t i = 0; i < m_queries.size(); ++i) { 162 if (!first) 163 text.appendLiteral(", "); 164 else 165 first = false; 166 text.append(m_queries[i]->cssText()); 167 } 168 return text.toString(); 169 } 170 171 void MediaQuerySet::trace(Visitor* visitor) 172 { 173 // We don't support tracing of vectors of OwnPtrs (ie. OwnPtr<Vector<OwnPtr<MediaQuery> > >). 174 // Since this is a transitional object we are just ifdef'ing it out when oilpan is not enabled. 175 #if ENABLE(OILPAN) 176 visitor->trace(m_queries); 177 #endif 178 } 179 180 MediaList::MediaList(MediaQuerySet* mediaQueries, CSSStyleSheet* parentSheet) 181 : m_mediaQueries(mediaQueries) 182 , m_parentStyleSheet(parentSheet) 183 , m_parentRule(nullptr) 184 { 185 } 186 187 MediaList::MediaList(MediaQuerySet* mediaQueries, CSSRule* parentRule) 188 : m_mediaQueries(mediaQueries) 189 , m_parentStyleSheet(nullptr) 190 , m_parentRule(parentRule) 191 { 192 } 193 194 MediaList::~MediaList() 195 { 196 } 197 198 void MediaList::setMediaText(const String& value) 199 { 200 CSSStyleSheet::RuleMutationScope mutationScope(m_parentRule); 201 202 m_mediaQueries->set(value); 203 204 if (m_parentStyleSheet) 205 m_parentStyleSheet->didMutate(); 206 } 207 208 String MediaList::item(unsigned index) const 209 { 210 const WillBeHeapVector<OwnPtrWillBeMember<MediaQuery> >& queries = m_mediaQueries->queryVector(); 211 if (index < queries.size()) 212 return queries[index]->cssText(); 213 return String(); 214 } 215 216 void MediaList::deleteMedium(const String& medium, ExceptionState& exceptionState) 217 { 218 CSSStyleSheet::RuleMutationScope mutationScope(m_parentRule); 219 220 bool success = m_mediaQueries->remove(medium); 221 if (!success) { 222 exceptionState.throwDOMException(NotFoundError, "Failed to delete '" + medium + "'."); 223 return; 224 } 225 if (m_parentStyleSheet) 226 m_parentStyleSheet->didMutate(); 227 } 228 229 void MediaList::appendMedium(const String& medium, ExceptionState& exceptionState) 230 { 231 CSSStyleSheet::RuleMutationScope mutationScope(m_parentRule); 232 233 bool success = m_mediaQueries->add(medium); 234 if (!success) { 235 exceptionState.throwDOMException(InvalidCharacterError, "The value provided ('" + medium + "') is not a valid medium."); 236 return; 237 } 238 239 if (m_parentStyleSheet) 240 m_parentStyleSheet->didMutate(); 241 } 242 243 void MediaList::reattach(MediaQuerySet* mediaQueries) 244 { 245 ASSERT(mediaQueries); 246 m_mediaQueries = mediaQueries; 247 } 248 249 void MediaList::trace(Visitor* visitor) 250 { 251 visitor->trace(m_mediaQueries); 252 visitor->trace(m_parentStyleSheet); 253 visitor->trace(m_parentRule); 254 } 255 256 static void addResolutionWarningMessageToConsole(Document* document, const String& serializedExpression, CSSPrimitiveValue::UnitType type) 257 { 258 ASSERT(document); 259 260 DEFINE_STATIC_LOCAL(String, mediaQueryMessage, ("Consider using 'dppx' units, 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: ")); 261 DEFINE_STATIC_LOCAL(String, mediaValueDPI, ("dpi")); 262 DEFINE_STATIC_LOCAL(String, mediaValueDPCM, ("dpcm")); 263 DEFINE_STATIC_LOCAL(String, lengthUnitInch, ("inch")); 264 DEFINE_STATIC_LOCAL(String, lengthUnitCentimeter, ("centimeter")); 265 266 StringBuilder message; 267 if (CSSPrimitiveValue::isDotsPerInch(type)) 268 message.append(String(mediaQueryMessage).replace("%replacementUnits%", mediaValueDPI).replace("%lengthUnit%", lengthUnitInch)); 269 else if (CSSPrimitiveValue::isDotsPerCentimeter(type)) 270 message.append(String(mediaQueryMessage).replace("%replacementUnits%", mediaValueDPCM).replace("%lengthUnit%", lengthUnitCentimeter)); 271 else 272 ASSERT_NOT_REACHED(); 273 274 message.append(serializedExpression); 275 276 document->addConsoleMessage(CSSMessageSource, DebugMessageLevel, message.toString()); 277 } 278 279 static inline bool isResolutionMediaFeature(const String& mediaFeature) 280 { 281 return mediaFeature == MediaFeatureNames::resolutionMediaFeature 282 || mediaFeature == MediaFeatureNames::maxResolutionMediaFeature 283 || mediaFeature == MediaFeatureNames::minResolutionMediaFeature; 284 } 285 286 void reportMediaQueryWarningIfNeeded(Document* document, const MediaQuerySet* mediaQuerySet) 287 { 288 if (!mediaQuerySet || !document) 289 return; 290 291 const WillBeHeapVector<OwnPtrWillBeMember<MediaQuery> >& mediaQueries = mediaQuerySet->queryVector(); 292 const size_t queryCount = mediaQueries.size(); 293 294 if (!queryCount) 295 return; 296 297 CSSPrimitiveValue::UnitType suspiciousType = CSSPrimitiveValue::CSS_UNKNOWN; 298 bool dotsPerPixelUsed = false; 299 for (size_t i = 0; i < queryCount; ++i) { 300 const MediaQuery* query = mediaQueries[i].get(); 301 if (equalIgnoringCase(query->mediaType(), "print")) 302 continue; 303 304 const ExpressionHeapVector& expressions = query->expressions(); 305 for (size_t j = 0; j < expressions.size(); ++j) { 306 const MediaQueryExp* expression = expressions.at(j).get(); 307 if (isResolutionMediaFeature(expression->mediaFeature())) { 308 MediaQueryExpValue expValue = expression->expValue(); 309 if (expValue.isValue) { 310 if (CSSPrimitiveValue::isDotsPerPixel(expValue.unit)) 311 dotsPerPixelUsed = true; 312 else if (CSSPrimitiveValue::isDotsPerInch(expValue.unit) || CSSPrimitiveValue::isDotsPerCentimeter(expValue.unit)) 313 suspiciousType = expValue.unit; 314 } 315 } 316 } 317 } 318 319 if (suspiciousType && !dotsPerPixelUsed) 320 addResolutionWarningMessageToConsole(document, mediaQuerySet->mediaText(), suspiciousType); 321 } 322 323 } 324