Home | History | Annotate | Download | only in css
      1 /*
      2  * (C) 1999-2003 Lars Knoll (knoll (at) kde.org)
      3  * Copyright (C) 2004, 2006, 2010 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 "ExceptionCode.h"
     27 #include "MediaQuery.h"
     28 #include "MediaQueryExp.h"
     29 
     30 namespace WebCore {
     31 
     32 /* MediaList is used to store 3 types of media related entities which mean the same:
     33  * Media Queries, Media Types and Media Descriptors.
     34  * Currently MediaList always tries to parse media queries and if parsing fails,
     35  * tries to fallback to Media Descriptors if m_fallback flag is set.
     36  * Slight problem with syntax error handling:
     37  * CSS 2.1 Spec (http://www.w3.org/TR/CSS21/media.html)
     38  * specifies that failing media type parsing is a syntax error
     39  * CSS 3 Media Queries Spec (http://www.w3.org/TR/css3-mediaqueries/)
     40  * specifies that failing media query is a syntax error
     41  * HTML 4.01 spec (http://www.w3.org/TR/REC-html40/present/styles.html#adef-media)
     42  * specifies that Media Descriptors should be parsed with forward-compatible syntax
     43  * DOM Level 2 Style Sheet spec (http://www.w3.org/TR/DOM-Level-2-Style/)
     44  * talks about MediaList.mediaText and refers
     45  *   -  to Media Descriptors of HTML 4.0 in context of StyleSheet
     46  *   -  to Media Types of CSS 2.0 in context of CSSMediaRule and CSSImportRule
     47  *
     48  * These facts create situation where same (illegal) media specification may result in
     49  * different parses depending on whether it is media attr of style element or part of
     50  * css @media rule.
     51  * <style media="screen and resolution > 40dpi"> ..</style> will be enabled on screen devices where as
     52  * @media screen and resolution > 40dpi {..} will not.
     53  * This gets more counter-intuitive in JavaScript:
     54  * document.styleSheets[0].media.mediaText = "screen and resolution > 40dpi" will be ok and
     55  * enabled, while
     56  * document.styleSheets[0].cssRules[0].media.mediaText = "screen and resolution > 40dpi" will
     57  * throw SYNTAX_ERR exception.
     58  */
     59 
     60 MediaList::MediaList(CSSStyleSheet* parentSheet, bool fallbackToDescriptor)
     61     : StyleBase(parentSheet)
     62     , m_fallback(fallbackToDescriptor)
     63 {
     64 }
     65 
     66 MediaList::MediaList(CSSStyleSheet* parentSheet, const String& media, bool fallbackToDescriptor)
     67     : StyleBase(parentSheet)
     68     , m_fallback(fallbackToDescriptor)
     69 {
     70     ExceptionCode ec = 0;
     71     setMediaText(media, ec);
     72     // FIXME: parsing can fail. The problem with failing constructor is that
     73     // we would need additional flag saying MediaList is not valid
     74     // Parse can fail only when fallbackToDescriptor == false, i.e when HTML4 media descriptor
     75     // forward-compatible syntax is not in use.
     76     // DOMImplementationCSS seems to mandate that media descriptors are used
     77     // for both html and svg, even though svg:style doesn't use media descriptors
     78     // Currently the only places where parsing can fail are
     79     // creating <svg:style>, creating css media / import rules from js
     80     if (ec)
     81         setMediaText("invalid", ec);
     82 }
     83 
     84 MediaList::MediaList(CSSImportRule* parentRule, const String& media)
     85     : StyleBase(parentRule)
     86     , m_fallback(false)
     87 {
     88     ExceptionCode ec = 0;
     89     setMediaText(media, ec);
     90     if (ec)
     91         setMediaText("invalid", ec);
     92 }
     93 
     94 MediaList::~MediaList()
     95 {
     96     deleteAllValues(m_queries);
     97 }
     98 
     99 static String parseMediaDescriptor(const String& s)
    100 {
    101     int len = s.length();
    102 
    103     // http://www.w3.org/TR/REC-html40/types.html#type-media-descriptors
    104     // "Each entry is truncated just before the first character that isn't a
    105     // US ASCII letter [a-zA-Z] (ISO 10646 hex 41-5a, 61-7a), digit [0-9] (hex 30-39),
    106     // or hyphen (hex 2d)."
    107     int i;
    108     unsigned short c;
    109     for (i = 0; i < len; ++i) {
    110         c = s[i];
    111         if (! ((c >= 'a' && c <= 'z')
    112             || (c >= 'A' && c <= 'Z')
    113             || (c >= '1' && c <= '9')
    114             || (c == '-')))
    115             break;
    116     }
    117     return s.left(i);
    118 }
    119 
    120 void MediaList::deleteMedium(const String& oldMedium, ExceptionCode& ec)
    121 {
    122     RefPtr<MediaList> tempMediaList = MediaList::create();
    123     CSSParser p(true);
    124 
    125     MediaQuery* oldQuery = 0;
    126     OwnPtr<MediaQuery> createdQuery;
    127 
    128     if (p.parseMediaQuery(tempMediaList.get(), oldMedium)) {
    129         if (tempMediaList->m_queries.size() > 0)
    130             oldQuery = tempMediaList->m_queries[0];
    131     } else if (m_fallback) {
    132         String medium = parseMediaDescriptor(oldMedium);
    133         if (!medium.isNull()) {
    134             createdQuery = adoptPtr(new MediaQuery(MediaQuery::None, medium, 0));
    135             oldQuery = createdQuery.get();
    136         }
    137     }
    138 
    139     // DOM Style Sheets spec doesn't allow SYNTAX_ERR to be thrown in deleteMedium
    140     ec = NOT_FOUND_ERR;
    141 
    142     if (oldQuery) {
    143         for (size_t i = 0; i < m_queries.size(); ++i) {
    144             MediaQuery* a = m_queries[i];
    145             if (*a == *oldQuery) {
    146                 m_queries.remove(i);
    147                 delete a;
    148                 ec = 0;
    149                 break;
    150             }
    151         }
    152     }
    153 
    154     if (!ec)
    155         notifyChanged();
    156 }
    157 
    158 String MediaList::mediaText() const
    159 {
    160     String text("");
    161 
    162     bool first = true;
    163     for (size_t i = 0; i < m_queries.size(); ++i) {
    164         if (!first)
    165             text += ", ";
    166         else
    167             first = false;
    168         text += m_queries[i]->cssText();
    169     }
    170 
    171     return text;
    172 }
    173 
    174 void MediaList::setMediaText(const String& value, ExceptionCode& ec)
    175 {
    176     RefPtr<MediaList> tempMediaList = MediaList::create();
    177     CSSParser p(true);
    178 
    179     Vector<String> list;
    180     value.split(',', list);
    181     Vector<String>::const_iterator end = list.end();
    182     for (Vector<String>::const_iterator it = list.begin(); it != end; ++it) {
    183         String medium = (*it).stripWhiteSpace();
    184         if (!medium.isEmpty()) {
    185             if (!p.parseMediaQuery(tempMediaList.get(), medium)) {
    186                 if (m_fallback) {
    187                     String mediaDescriptor = parseMediaDescriptor(medium);
    188                     if (!mediaDescriptor.isNull())
    189                         tempMediaList->m_queries.append(new MediaQuery(MediaQuery::None, mediaDescriptor, 0));
    190                 } else {
    191                     ec = SYNTAX_ERR;
    192                     return;
    193                 }
    194             }
    195         } else if (!m_fallback) {
    196             ec = SYNTAX_ERR;
    197             return;
    198         }
    199     }
    200     // ",,,," falls straight through, but is not valid unless fallback
    201     if (!m_fallback && list.begin() == list.end()) {
    202         String s = value.stripWhiteSpace();
    203         if (!s.isEmpty()) {
    204             ec = SYNTAX_ERR;
    205             return;
    206         }
    207     }
    208 
    209     ec = 0;
    210     deleteAllValues(m_queries);
    211     m_queries = tempMediaList->m_queries;
    212     tempMediaList->m_queries.clear();
    213     notifyChanged();
    214 }
    215 
    216 String MediaList::item(unsigned index) const
    217 {
    218     if (index < m_queries.size()) {
    219         MediaQuery* query = m_queries[index];
    220         return query->cssText();
    221     }
    222 
    223     return String();
    224 }
    225 
    226 void MediaList::appendMedium(const String& newMedium, ExceptionCode& ec)
    227 {
    228     ec = INVALID_CHARACTER_ERR;
    229     CSSParser p(true);
    230     if (p.parseMediaQuery(this, newMedium)) {
    231         ec = 0;
    232     } else if (m_fallback) {
    233         String medium = parseMediaDescriptor(newMedium);
    234         if (!medium.isNull()) {
    235             m_queries.append(new MediaQuery(MediaQuery::None, medium, 0));
    236             ec = 0;
    237         }
    238     }
    239 
    240     if (!ec)
    241         notifyChanged();
    242 }
    243 
    244 void MediaList::appendMediaQuery(PassOwnPtr<MediaQuery> mediaQuery)
    245 {
    246     m_queries.append(mediaQuery.leakPtr());
    247 }
    248 
    249 void MediaList::notifyChanged()
    250 {
    251     for (StyleBase* p = parent(); p; p = p->parent()) {
    252         if (p->isCSSStyleSheet())
    253             return static_cast<CSSStyleSheet*>(p)->styleSheetChanged();
    254     }
    255 }
    256 
    257 }
    258