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