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/core/v8/ExceptionState.h" 24 #include "core/MediaFeatureNames.h" 25 #include "core/css/CSSStyleSheet.h" 26 #include "core/css/MediaQuery.h" 27 #include "core/css/MediaQueryExp.h" 28 #include "core/css/parser/MediaQueryParser.h" 29 #include "core/dom/Document.h" 30 #include "core/frame/LocalDOMWindow.h" 31 #include "core/inspector/ConsoleMessage.h" 32 #include "wtf/text/StringBuilder.h" 33 34 namespace blink { 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 return MediaQueryParser::parseMediaQuerySet(mediaString); 75 } 76 77 PassRefPtrWillBeRawPtr<MediaQuerySet> MediaQuerySet::createOffMainThread(const String& mediaString) 78 { 79 if (mediaString.isEmpty()) 80 return MediaQuerySet::create(); 81 82 return MediaQueryParser::parseMediaQuerySet(mediaString); 83 } 84 85 bool MediaQuerySet::set(const String& mediaString) 86 { 87 RefPtrWillBeRawPtr<MediaQuerySet> result = create(mediaString); 88 m_queries.swap(result->m_queries); 89 return true; 90 } 91 92 bool MediaQuerySet::add(const String& queryString) 93 { 94 // To "parse a media query" for a given string means to follow "the parse 95 // a media query list" steps and return "null" if more than one media query 96 // is returned, or else the returned media query. 97 RefPtrWillBeRawPtr<MediaQuerySet> result = create(queryString); 98 99 // Only continue if exactly one media query is found, as described above. 100 if (result->m_queries.size() != 1) 101 return true; 102 103 OwnPtrWillBeRawPtr<MediaQuery> newQuery = result->m_queries[0].release(); 104 ASSERT(newQuery); 105 106 // If comparing with any of the media queries in the collection of media 107 // queries returns true terminate these steps. 108 for (size_t i = 0; i < m_queries.size(); ++i) { 109 MediaQuery* query = m_queries[i].get(); 110 if (*query == *newQuery) 111 return true; 112 } 113 114 m_queries.append(newQuery.release()); 115 return true; 116 } 117 118 bool MediaQuerySet::remove(const String& queryStringToRemove) 119 { 120 // To "parse a media query" for a given string means to follow "the parse 121 // a media query list" steps and return "null" if more than one media query 122 // is returned, or else the returned media query. 123 RefPtrWillBeRawPtr<MediaQuerySet> result = create(queryStringToRemove); 124 125 // Only continue if exactly one media query is found, as described above. 126 if (result->m_queries.size() != 1) 127 return true; 128 129 OwnPtrWillBeRawPtr<MediaQuery> newQuery = result->m_queries[0].release(); 130 ASSERT(newQuery); 131 132 // Remove any media query from the collection of media queries for which 133 // comparing with the media query returns true. 134 bool found = false; 135 for (size_t i = 0; i < m_queries.size(); ++i) { 136 MediaQuery* query = m_queries[i].get(); 137 if (*query == *newQuery) { 138 m_queries.remove(i); 139 --i; 140 found = true; 141 } 142 } 143 144 return found; 145 } 146 147 void MediaQuerySet::addMediaQuery(PassOwnPtrWillBeRawPtr<MediaQuery> mediaQuery) 148 { 149 m_queries.append(mediaQuery); 150 } 151 152 String MediaQuerySet::mediaText() const 153 { 154 StringBuilder text; 155 156 bool first = true; 157 for (size_t i = 0; i < m_queries.size(); ++i) { 158 if (!first) 159 text.appendLiteral(", "); 160 else 161 first = false; 162 text.append(m_queries[i]->cssText()); 163 } 164 return text.toString(); 165 } 166 167 void MediaQuerySet::trace(Visitor* visitor) 168 { 169 // We don't support tracing of vectors of OwnPtrs (ie. OwnPtr<Vector<OwnPtr<MediaQuery> > >). 170 // Since this is a transitional object we are just ifdef'ing it out when oilpan is not enabled. 171 #if ENABLE(OILPAN) 172 visitor->trace(m_queries); 173 #endif 174 } 175 176 MediaList::MediaList(MediaQuerySet* mediaQueries, CSSStyleSheet* parentSheet) 177 : m_mediaQueries(mediaQueries) 178 , m_parentStyleSheet(parentSheet) 179 , m_parentRule(nullptr) 180 { 181 } 182 183 MediaList::MediaList(MediaQuerySet* mediaQueries, CSSRule* parentRule) 184 : m_mediaQueries(mediaQueries) 185 , m_parentStyleSheet(nullptr) 186 , m_parentRule(parentRule) 187 { 188 } 189 190 MediaList::~MediaList() 191 { 192 } 193 194 void MediaList::setMediaText(const String& value) 195 { 196 CSSStyleSheet::RuleMutationScope mutationScope(m_parentRule); 197 198 m_mediaQueries->set(value); 199 200 if (m_parentStyleSheet) 201 m_parentStyleSheet->didMutate(); 202 } 203 204 String MediaList::item(unsigned index) const 205 { 206 const WillBeHeapVector<OwnPtrWillBeMember<MediaQuery> >& queries = m_mediaQueries->queryVector(); 207 if (index < queries.size()) 208 return queries[index]->cssText(); 209 return String(); 210 } 211 212 void MediaList::deleteMedium(const String& medium, ExceptionState& exceptionState) 213 { 214 CSSStyleSheet::RuleMutationScope mutationScope(m_parentRule); 215 216 bool success = m_mediaQueries->remove(medium); 217 if (!success) { 218 exceptionState.throwDOMException(NotFoundError, "Failed to delete '" + medium + "'."); 219 return; 220 } 221 if (m_parentStyleSheet) 222 m_parentStyleSheet->didMutate(); 223 } 224 225 void MediaList::appendMedium(const String& medium, ExceptionState& exceptionState) 226 { 227 CSSStyleSheet::RuleMutationScope mutationScope(m_parentRule); 228 229 bool success = m_mediaQueries->add(medium); 230 if (!success) { 231 exceptionState.throwDOMException(InvalidCharacterError, "The value provided ('" + medium + "') is not a valid medium."); 232 return; 233 } 234 235 if (m_parentStyleSheet) 236 m_parentStyleSheet->didMutate(); 237 } 238 239 void MediaList::reattach(MediaQuerySet* mediaQueries) 240 { 241 ASSERT(mediaQueries); 242 m_mediaQueries = mediaQueries; 243 } 244 245 void MediaList::trace(Visitor* visitor) 246 { 247 visitor->trace(m_mediaQueries); 248 visitor->trace(m_parentStyleSheet); 249 visitor->trace(m_parentRule); 250 } 251 252 static void addResolutionWarningMessageToConsole(Document* document, const String& serializedExpression, CSSPrimitiveValue::UnitType type) 253 { 254 ASSERT(document); 255 256 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: ")); 257 DEFINE_STATIC_LOCAL(String, mediaValueDPI, ("dpi")); 258 DEFINE_STATIC_LOCAL(String, mediaValueDPCM, ("dpcm")); 259 DEFINE_STATIC_LOCAL(String, lengthUnitInch, ("inch")); 260 DEFINE_STATIC_LOCAL(String, lengthUnitCentimeter, ("centimeter")); 261 262 StringBuilder message; 263 if (CSSPrimitiveValue::isDotsPerInch(type)) 264 message.append(String(mediaQueryMessage).replace("%replacementUnits%", mediaValueDPI).replace("%lengthUnit%", lengthUnitInch)); 265 else if (CSSPrimitiveValue::isDotsPerCentimeter(type)) 266 message.append(String(mediaQueryMessage).replace("%replacementUnits%", mediaValueDPCM).replace("%lengthUnit%", lengthUnitCentimeter)); 267 else 268 ASSERT_NOT_REACHED(); 269 270 message.append(serializedExpression); 271 272 document->addConsoleMessage(ConsoleMessage::create(CSSMessageSource, DebugMessageLevel, message.toString())); 273 } 274 275 static inline bool isResolutionMediaFeature(const String& mediaFeature) 276 { 277 return mediaFeature == MediaFeatureNames::resolutionMediaFeature 278 || mediaFeature == MediaFeatureNames::maxResolutionMediaFeature 279 || mediaFeature == MediaFeatureNames::minResolutionMediaFeature; 280 } 281 282 void reportMediaQueryWarningIfNeeded(Document* document, const MediaQuerySet* mediaQuerySet) 283 { 284 if (!mediaQuerySet || !document) 285 return; 286 287 const WillBeHeapVector<OwnPtrWillBeMember<MediaQuery> >& mediaQueries = mediaQuerySet->queryVector(); 288 const size_t queryCount = mediaQueries.size(); 289 290 if (!queryCount) 291 return; 292 293 CSSPrimitiveValue::UnitType suspiciousType = CSSPrimitiveValue::CSS_UNKNOWN; 294 bool dotsPerPixelUsed = false; 295 for (size_t i = 0; i < queryCount; ++i) { 296 const MediaQuery* query = mediaQueries[i].get(); 297 if (equalIgnoringCase(query->mediaType(), "print")) 298 continue; 299 300 const ExpressionHeapVector& expressions = query->expressions(); 301 for (size_t j = 0; j < expressions.size(); ++j) { 302 const MediaQueryExp* expression = expressions.at(j).get(); 303 if (isResolutionMediaFeature(expression->mediaFeature())) { 304 MediaQueryExpValue expValue = expression->expValue(); 305 if (expValue.isValue) { 306 if (CSSPrimitiveValue::isDotsPerPixel(expValue.unit)) 307 dotsPerPixelUsed = true; 308 else if (CSSPrimitiveValue::isDotsPerInch(expValue.unit) || CSSPrimitiveValue::isDotsPerCentimeter(expValue.unit)) 309 suspiciousType = expValue.unit; 310 } 311 } 312 } 313 } 314 315 if (suspiciousType && !dotsPerPixelUsed) 316 addResolutionWarningMessageToConsole(document, mediaQuerySet->mediaText(), suspiciousType); 317 } 318 319 } 320