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