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