Home | History | Annotate | Download | only in css
      1 /*
      2  * (C) 1999-2003 Lars Knoll (knoll (at) kde.org)
      3  * Copyright (C) 2004, 2006, 2007, 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 
     21 #include "config.h"
     22 #include "core/css/CSSStyleSheet.h"
     23 
     24 #include "HTMLNames.h"
     25 #include "SVGNames.h"
     26 #include "bindings/v8/ExceptionState.h"
     27 #include "core/css/CSSCharsetRule.h"
     28 #include "core/css/CSSImportRule.h"
     29 #include "core/css/CSSParser.h"
     30 #include "core/css/CSSRuleList.h"
     31 #include "core/css/MediaList.h"
     32 #include "core/css/StyleRule.h"
     33 #include "core/css/StyleSheetContents.h"
     34 #include "core/dom/Document.h"
     35 #include "core/dom/ExceptionCode.h"
     36 #include "core/dom/Node.h"
     37 #include "weborigin/SecurityOrigin.h"
     38 #include "wtf/text/StringBuilder.h"
     39 
     40 namespace WebCore {
     41 
     42 class StyleSheetCSSRuleList : public CSSRuleList {
     43 public:
     44     StyleSheetCSSRuleList(CSSStyleSheet* sheet) : m_styleSheet(sheet) { }
     45 
     46 private:
     47     virtual void ref() { m_styleSheet->ref(); }
     48     virtual void deref() { m_styleSheet->deref(); }
     49 
     50     virtual unsigned length() const { return m_styleSheet->length(); }
     51     virtual CSSRule* item(unsigned index) const { return m_styleSheet->item(index); }
     52 
     53     virtual CSSStyleSheet* styleSheet() const { return m_styleSheet; }
     54 
     55     CSSStyleSheet* m_styleSheet;
     56 };
     57 
     58 #if !ASSERT_DISABLED
     59 static bool isAcceptableCSSStyleSheetParent(Node* parentNode)
     60 {
     61     // Only these nodes can be parents of StyleSheets, and they need to call clearOwnerNode() when moved out of document.
     62     return !parentNode
     63         || parentNode->isDocumentNode()
     64         || parentNode->hasTagName(HTMLNames::linkTag)
     65         || parentNode->hasTagName(HTMLNames::styleTag)
     66         || parentNode->hasTagName(SVGNames::styleTag)
     67         || parentNode->nodeType() == Node::PROCESSING_INSTRUCTION_NODE;
     68 }
     69 #endif
     70 
     71 PassRefPtr<CSSStyleSheet> CSSStyleSheet::create(PassRefPtr<StyleSheetContents> sheet, CSSImportRule* ownerRule)
     72 {
     73     return adoptRef(new CSSStyleSheet(sheet, ownerRule));
     74 }
     75 
     76 PassRefPtr<CSSStyleSheet> CSSStyleSheet::create(PassRefPtr<StyleSheetContents> sheet, Node* ownerNode)
     77 {
     78     return adoptRef(new CSSStyleSheet(sheet, ownerNode, false, TextPosition::minimumPosition()));
     79 }
     80 
     81 PassRefPtr<CSSStyleSheet> CSSStyleSheet::createInline(Node* ownerNode, const KURL& baseURL, const TextPosition& startPosition, const String& encoding)
     82 {
     83     CSSParserContext parserContext(ownerNode->document(), baseURL, encoding);
     84     RefPtr<StyleSheetContents> sheet = StyleSheetContents::create(baseURL.string(), parserContext);
     85     return adoptRef(new CSSStyleSheet(sheet.release(), ownerNode, true, startPosition));
     86 }
     87 
     88 CSSStyleSheet::CSSStyleSheet(PassRefPtr<StyleSheetContents> contents, CSSImportRule* ownerRule)
     89     : m_contents(contents)
     90     , m_isInlineStylesheet(false)
     91     , m_isDisabled(false)
     92     , m_ownerNode(0)
     93     , m_ownerRule(ownerRule)
     94     , m_startPosition(TextPosition::minimumPosition())
     95 {
     96     m_contents->registerClient(this);
     97 }
     98 
     99 CSSStyleSheet::CSSStyleSheet(PassRefPtr<StyleSheetContents> contents, Node* ownerNode, bool isInlineStylesheet, const TextPosition& startPosition)
    100     : m_contents(contents)
    101     , m_isInlineStylesheet(isInlineStylesheet)
    102     , m_isDisabled(false)
    103     , m_ownerNode(ownerNode)
    104     , m_ownerRule(0)
    105     , m_startPosition(startPosition)
    106 {
    107     ASSERT(isAcceptableCSSStyleSheetParent(ownerNode));
    108     m_contents->registerClient(this);
    109 }
    110 
    111 CSSStyleSheet::~CSSStyleSheet()
    112 {
    113     // For style rules outside the document, .parentStyleSheet can become null even if the style rule
    114     // is still observable from JavaScript. This matches the behavior of .parentNode for nodes, but
    115     // it's not ideal because it makes the CSSOM's behavior depend on the timing of garbage collection.
    116     for (unsigned i = 0; i < m_childRuleCSSOMWrappers.size(); ++i) {
    117         if (m_childRuleCSSOMWrappers[i])
    118             m_childRuleCSSOMWrappers[i]->setParentStyleSheet(0);
    119     }
    120     if (m_mediaCSSOMWrapper)
    121         m_mediaCSSOMWrapper->clearParentStyleSheet();
    122 
    123     m_contents->unregisterClient(this);
    124 }
    125 
    126 void CSSStyleSheet::willMutateRules()
    127 {
    128     // If we are the only client it is safe to mutate.
    129     if (m_contents->hasOneClient() && !m_contents->isInMemoryCache()) {
    130         m_contents->setMutable();
    131         return;
    132     }
    133     // Only cacheable stylesheets should have multiple clients.
    134     ASSERT(m_contents->isCacheable());
    135 
    136     // Copy-on-write.
    137     m_contents->unregisterClient(this);
    138     m_contents = m_contents->copy();
    139     m_contents->registerClient(this);
    140 
    141     m_contents->setMutable();
    142 
    143     // Any existing CSSOM wrappers need to be connected to the copied child rules.
    144     reattachChildRuleCSSOMWrappers();
    145 }
    146 
    147 void CSSStyleSheet::didMutateRules()
    148 {
    149     ASSERT(m_contents->isMutable());
    150     ASSERT(m_contents->hasOneClient());
    151 
    152     didMutate();
    153 }
    154 
    155 void CSSStyleSheet::didMutate()
    156 {
    157     Document* owner = ownerDocument();
    158     if (!owner)
    159         return;
    160     owner->modifiedStyleSheet(this);
    161 }
    162 
    163 void CSSStyleSheet::reattachChildRuleCSSOMWrappers()
    164 {
    165     for (unsigned i = 0; i < m_childRuleCSSOMWrappers.size(); ++i) {
    166         if (!m_childRuleCSSOMWrappers[i])
    167             continue;
    168         m_childRuleCSSOMWrappers[i]->reattach(m_contents->ruleAt(i));
    169     }
    170 }
    171 
    172 void CSSStyleSheet::setDisabled(bool disabled)
    173 {
    174     if (disabled == m_isDisabled)
    175         return;
    176     m_isDisabled = disabled;
    177 
    178     didMutate();
    179 }
    180 
    181 void CSSStyleSheet::setMediaQueries(PassRefPtr<MediaQuerySet> mediaQueries)
    182 {
    183     m_mediaQueries = mediaQueries;
    184     if (m_mediaCSSOMWrapper && m_mediaQueries)
    185         m_mediaCSSOMWrapper->reattach(m_mediaQueries.get());
    186 
    187     // Add warning message to inspector whenever dpi/dpcm values are used for "screen" media.
    188     reportMediaQueryWarningIfNeeded(ownerDocument(), m_mediaQueries.get());
    189 }
    190 
    191 unsigned CSSStyleSheet::length() const
    192 {
    193     return m_contents->ruleCount();
    194 }
    195 
    196 CSSRule* CSSStyleSheet::item(unsigned index)
    197 {
    198     unsigned ruleCount = length();
    199     if (index >= ruleCount)
    200         return 0;
    201 
    202     if (m_childRuleCSSOMWrappers.isEmpty())
    203         m_childRuleCSSOMWrappers.grow(ruleCount);
    204     ASSERT(m_childRuleCSSOMWrappers.size() == ruleCount);
    205 
    206     RefPtr<CSSRule>& cssRule = m_childRuleCSSOMWrappers[index];
    207     if (!cssRule) {
    208         if (index == 0 && m_contents->hasCharsetRule()) {
    209             ASSERT(!m_contents->ruleAt(0));
    210             cssRule = CSSCharsetRule::create(this, m_contents->encodingFromCharsetRule());
    211         } else
    212             cssRule = m_contents->ruleAt(index)->createCSSOMWrapper(this);
    213     }
    214     return cssRule.get();
    215 }
    216 
    217 bool CSSStyleSheet::canAccessRules() const
    218 {
    219     if (m_isInlineStylesheet)
    220         return true;
    221     KURL baseURL = m_contents->baseURL();
    222     if (baseURL.isEmpty())
    223         return true;
    224     Document* document = ownerDocument();
    225     if (!document)
    226         return true;
    227     if (document->securityOrigin()->canRequest(baseURL))
    228         return true;
    229     return false;
    230 }
    231 
    232 PassRefPtr<CSSRuleList> CSSStyleSheet::rules()
    233 {
    234     if (!canAccessRules())
    235         return 0;
    236     // IE behavior.
    237     RefPtr<StaticCSSRuleList> nonCharsetRules = StaticCSSRuleList::create();
    238     unsigned ruleCount = length();
    239     for (unsigned i = 0; i < ruleCount; ++i) {
    240         CSSRule* rule = item(i);
    241         if (rule->type() == CSSRule::CHARSET_RULE)
    242             continue;
    243         nonCharsetRules->rules().append(rule);
    244     }
    245     return nonCharsetRules.release();
    246 }
    247 
    248 unsigned CSSStyleSheet::insertRule(const String& ruleString, unsigned index, ExceptionState& es)
    249 {
    250     ASSERT(m_childRuleCSSOMWrappers.isEmpty() || m_childRuleCSSOMWrappers.size() == m_contents->ruleCount());
    251 
    252     if (index > length()) {
    253         es.throwDOMException(IndexSizeError);
    254         return 0;
    255     }
    256     CSSParser p(m_contents->parserContext(), UseCounter::getFrom(this));
    257     RefPtr<StyleRuleBase> rule = p.parseRule(m_contents.get(), ruleString);
    258 
    259     if (!rule) {
    260         es.throwDOMException(SyntaxError);
    261         return 0;
    262     }
    263     RuleMutationScope mutationScope(this);
    264 
    265     bool success = m_contents->wrapperInsertRule(rule, index);
    266     if (!success) {
    267         es.throwDOMException(HierarchyRequestError);
    268         return 0;
    269     }
    270     if (!m_childRuleCSSOMWrappers.isEmpty())
    271         m_childRuleCSSOMWrappers.insert(index, RefPtr<CSSRule>());
    272 
    273     return index;
    274 }
    275 
    276 void CSSStyleSheet::deleteRule(unsigned index, ExceptionState& es)
    277 {
    278     ASSERT(m_childRuleCSSOMWrappers.isEmpty() || m_childRuleCSSOMWrappers.size() == m_contents->ruleCount());
    279 
    280     if (index >= length()) {
    281         es.throwDOMException(IndexSizeError);
    282         return;
    283     }
    284     RuleMutationScope mutationScope(this);
    285 
    286     m_contents->wrapperDeleteRule(index);
    287 
    288     if (!m_childRuleCSSOMWrappers.isEmpty()) {
    289         if (m_childRuleCSSOMWrappers[index])
    290             m_childRuleCSSOMWrappers[index]->setParentStyleSheet(0);
    291         m_childRuleCSSOMWrappers.remove(index);
    292     }
    293 }
    294 
    295 int CSSStyleSheet::addRule(const String& selector, const String& style, int index, ExceptionState& es)
    296 {
    297     StringBuilder text;
    298     text.append(selector);
    299     text.appendLiteral(" { ");
    300     text.append(style);
    301     if (!style.isEmpty())
    302         text.append(' ');
    303     text.append('}');
    304     insertRule(text.toString(), index, es);
    305 
    306     // As per Microsoft documentation, always return -1.
    307     return -1;
    308 }
    309 
    310 int CSSStyleSheet::addRule(const String& selector, const String& style, ExceptionState& es)
    311 {
    312     return addRule(selector, style, length(), es);
    313 }
    314 
    315 
    316 PassRefPtr<CSSRuleList> CSSStyleSheet::cssRules()
    317 {
    318     if (!canAccessRules())
    319         return 0;
    320     if (!m_ruleListCSSOMWrapper)
    321         m_ruleListCSSOMWrapper = adoptPtr(new StyleSheetCSSRuleList(this));
    322     return m_ruleListCSSOMWrapper.get();
    323 }
    324 
    325 String CSSStyleSheet::href() const
    326 {
    327     return m_contents->originalURL();
    328 }
    329 
    330 KURL CSSStyleSheet::baseURL() const
    331 {
    332     return m_contents->baseURL();
    333 }
    334 
    335 bool CSSStyleSheet::isLoading() const
    336 {
    337     return m_contents->isLoading();
    338 }
    339 
    340 MediaList* CSSStyleSheet::media() const
    341 {
    342     if (!m_mediaQueries)
    343         return 0;
    344 
    345     if (!m_mediaCSSOMWrapper)
    346         m_mediaCSSOMWrapper = MediaList::create(m_mediaQueries.get(), const_cast<CSSStyleSheet*>(this));
    347     return m_mediaCSSOMWrapper.get();
    348 }
    349 
    350 CSSStyleSheet* CSSStyleSheet::parentStyleSheet() const
    351 {
    352     return m_ownerRule ? m_ownerRule->parentStyleSheet() : 0;
    353 }
    354 
    355 Document* CSSStyleSheet::ownerDocument() const
    356 {
    357     const CSSStyleSheet* root = this;
    358     while (root->parentStyleSheet())
    359         root = root->parentStyleSheet();
    360     return root->ownerNode() ? root->ownerNode()->document() : 0;
    361 }
    362 
    363 void CSSStyleSheet::clearChildRuleCSSOMWrappers()
    364 {
    365     m_childRuleCSSOMWrappers.clear();
    366 }
    367 
    368 }
    369