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