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/v8/ExceptionState.h"
     25 #include "bindings/v8/V8Binding.h"
     26 #include "bindings/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/parser/BisonCSSParser.h"
     32 #include "core/css/CSSRuleList.h"
     33 #include "core/css/MediaList.h"
     34 #include "core/css/StyleRule.h"
     35 #include "core/css/StyleSheetContents.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 WebCore {
     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 ASSERT_ENABLED
     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     BisonCSSParser p(context);
    314     RefPtrWillBeRawPtr<StyleRuleBase> rule = p.parseRule(m_contents.get(), ruleString);
    315 
    316     if (!rule) {
    317         exceptionState.throwDOMException(SyntaxError, "Failed to parse the rule '" + ruleString + "'.");
    318         return 0;
    319     }
    320     RuleMutationScope mutationScope(this);
    321 
    322     bool success = m_contents->wrapperInsertRule(rule, index);
    323     if (!success) {
    324         exceptionState.throwDOMException(HierarchyRequestError, "Failed to insert the rule.");
    325         return 0;
    326     }
    327     if (!m_childRuleCSSOMWrappers.isEmpty())
    328         m_childRuleCSSOMWrappers.insert(index, RefPtrWillBeMember<CSSRule>(nullptr));
    329 
    330     return index;
    331 }
    332 
    333 unsigned CSSStyleSheet::insertRule(const String& rule, ExceptionState& exceptionState)
    334 {
    335     UseCounter::countDeprecation(callingExecutionContext(V8PerIsolateData::mainThreadIsolate()), UseCounter::CSSStyleSheetInsertRuleOptionalArg);
    336     return insertRule(rule, 0, exceptionState);
    337 }
    338 
    339 void CSSStyleSheet::deleteRule(unsigned index, ExceptionState& exceptionState)
    340 {
    341     ASSERT(m_childRuleCSSOMWrappers.isEmpty() || m_childRuleCSSOMWrappers.size() == m_contents->ruleCount());
    342 
    343     if (index >= length()) {
    344         exceptionState.throwDOMException(IndexSizeError, "The index provided (" + String::number(index) + ") is larger than the maximum index (" + String::number(length() - 1) + ").");
    345         return;
    346     }
    347     RuleMutationScope mutationScope(this);
    348 
    349     m_contents->wrapperDeleteRule(index);
    350 
    351     if (!m_childRuleCSSOMWrappers.isEmpty()) {
    352         if (m_childRuleCSSOMWrappers[index])
    353             m_childRuleCSSOMWrappers[index]->setParentStyleSheet(0);
    354         m_childRuleCSSOMWrappers.remove(index);
    355     }
    356 }
    357 
    358 int CSSStyleSheet::addRule(const String& selector, const String& style, int index, ExceptionState& exceptionState)
    359 {
    360     StringBuilder text;
    361     text.append(selector);
    362     text.appendLiteral(" { ");
    363     text.append(style);
    364     if (!style.isEmpty())
    365         text.append(' ');
    366     text.append('}');
    367     insertRule(text.toString(), index, exceptionState);
    368 
    369     // As per Microsoft documentation, always return -1.
    370     return -1;
    371 }
    372 
    373 int CSSStyleSheet::addRule(const String& selector, const String& style, ExceptionState& exceptionState)
    374 {
    375     return addRule(selector, style, length(), exceptionState);
    376 }
    377 
    378 
    379 PassRefPtrWillBeRawPtr<CSSRuleList> CSSStyleSheet::cssRules()
    380 {
    381     if (!canAccessRules())
    382         return nullptr;
    383     if (!m_ruleListCSSOMWrapper)
    384         m_ruleListCSSOMWrapper = StyleSheetCSSRuleList::create(this);
    385     return m_ruleListCSSOMWrapper.get();
    386 }
    387 
    388 String CSSStyleSheet::href() const
    389 {
    390     return m_contents->originalURL();
    391 }
    392 
    393 KURL CSSStyleSheet::baseURL() const
    394 {
    395     return m_contents->baseURL();
    396 }
    397 
    398 bool CSSStyleSheet::isLoading() const
    399 {
    400     return m_contents->isLoading();
    401 }
    402 
    403 MediaList* CSSStyleSheet::media() const
    404 {
    405     if (!m_mediaQueries)
    406         return 0;
    407 
    408     if (!m_mediaCSSOMWrapper)
    409         m_mediaCSSOMWrapper = MediaList::create(m_mediaQueries.get(), const_cast<CSSStyleSheet*>(this));
    410     return m_mediaCSSOMWrapper.get();
    411 }
    412 
    413 CSSStyleSheet* CSSStyleSheet::parentStyleSheet() const
    414 {
    415     return m_ownerRule ? m_ownerRule->parentStyleSheet() : 0;
    416 }
    417 
    418 Document* CSSStyleSheet::ownerDocument() const
    419 {
    420     const CSSStyleSheet* root = this;
    421     while (root->parentStyleSheet())
    422         root = root->parentStyleSheet();
    423     return root->ownerNode() ? &root->ownerNode()->document() : 0;
    424 }
    425 
    426 void CSSStyleSheet::clearChildRuleCSSOMWrappers()
    427 {
    428     m_childRuleCSSOMWrappers.clear();
    429 }
    430 
    431 bool CSSStyleSheet::sheetLoaded()
    432 {
    433     ASSERT(m_ownerNode);
    434     setLoadCompleted(m_ownerNode->sheetLoaded());
    435     return m_loadCompleted;
    436 }
    437 
    438 void CSSStyleSheet::startLoadingDynamicSheet()
    439 {
    440     setLoadCompleted(false);
    441     m_ownerNode->startLoadingDynamicSheet();
    442 }
    443 
    444 void CSSStyleSheet::setLoadCompleted(bool completed)
    445 {
    446     if (completed == m_loadCompleted)
    447         return;
    448 
    449     m_loadCompleted = completed;
    450 
    451     if (completed)
    452         m_contents->clientLoadCompleted(this);
    453     else
    454         m_contents->clientLoadStarted(this);
    455 }
    456 
    457 void CSSStyleSheet::trace(Visitor* visitor)
    458 {
    459     visitor->trace(m_contents);
    460     visitor->trace(m_mediaQueries);
    461     visitor->trace(m_ownerNode);
    462     visitor->trace(m_ownerRule);
    463     visitor->trace(m_mediaCSSOMWrapper);
    464     visitor->trace(m_childRuleCSSOMWrappers);
    465     visitor->trace(m_ruleListCSSOMWrapper);
    466     StyleSheet::trace(visitor);
    467 }
    468 
    469 }
    470