1/*
2 * Copyright (C) 1999 Lars Knoll (knoll@kde.org)
3 *           (C) 1999 Antti Koivisto (koivisto@kde.org)
4 *           (C) 2001 Dirk Mueller (mueller@kde.org)
5 * Copyright (C) 2003, 2010 Apple Inc. All rights reserved.
6 *           (C) 2007 Rob Buis (buis@kde.org)
7 *
8 * This library is free software; you can redistribute it and/or
9 * modify it under the terms of the GNU Library General Public
10 * License as published by the Free Software Foundation; either
11 * version 2 of the License, or (at your option) any later version.
12 *
13 * This library is distributed in the hope that it will be useful,
14 * but WITHOUT ANY WARRANTY; without even the implied warranty of
15 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
16 * Library General Public License for more details.
17 *
18 * You should have received a copy of the GNU Library General Public License
19 * along with this library; see the file COPYING.LIB.  If not, write to
20 * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
21 * Boston, MA 02110-1301, USA.
22 */
23
24#include "config.h"
25#include "core/html/HTMLStyleElement.h"
26
27#include "HTMLNames.h"
28#include "core/css/MediaList.h"
29#include "core/css/StyleSheetContents.h"
30#include "core/dom/ContextFeatures.h"
31#include "core/dom/Document.h"
32#include "core/events/Event.h"
33#include "core/events/EventSender.h"
34#include "core/dom/StyleEngine.h"
35#include "core/dom/shadow/ShadowRoot.h"
36
37namespace WebCore {
38
39using namespace HTMLNames;
40
41static StyleEventSender& styleLoadEventSender()
42{
43    DEFINE_STATIC_LOCAL(StyleEventSender, sharedLoadEventSender, (EventTypeNames::load));
44    return sharedLoadEventSender;
45}
46
47inline HTMLStyleElement::HTMLStyleElement(Document& document, bool createdByParser)
48    : HTMLElement(styleTag, document)
49    , StyleElement(&document, createdByParser)
50    , m_firedLoad(false)
51    , m_loadedSheet(false)
52    , m_scopedStyleRegistrationState(NotRegistered)
53{
54    ScriptWrappable::init(this);
55}
56
57HTMLStyleElement::~HTMLStyleElement()
58{
59    // During tear-down, willRemove isn't called, so m_scopedStyleRegistrationState may still be RegisteredAsScoped or RegisteredInShadowRoot here.
60    // Therefore we can't ASSERT(m_scopedStyleRegistrationState == NotRegistered).
61    StyleElement::clearDocumentData(document(), this);
62
63    styleLoadEventSender().cancelEvent(this);
64}
65
66PassRefPtr<HTMLStyleElement> HTMLStyleElement::create(Document& document, bool createdByParser)
67{
68    return adoptRef(new HTMLStyleElement(document, createdByParser));
69}
70
71void HTMLStyleElement::parseAttribute(const QualifiedName& name, const AtomicString& value)
72{
73    if (name == titleAttr && m_sheet) {
74        m_sheet->setTitle(value);
75    } else if (name == scopedAttr && ContextFeatures::styleScopedEnabled(&document())) {
76        scopedAttributeChanged(!value.isNull());
77    } else if (name == mediaAttr && inDocument() && document().isActive() && m_sheet) {
78        m_sheet->setMediaQueries(MediaQuerySet::create(value));
79        // FIXME: This shold be RecalcStyleDeferred.
80        document().modifiedStyleSheet(m_sheet.get(), RecalcStyleImmediately);
81    } else {
82        HTMLElement::parseAttribute(name, value);
83    }
84}
85
86void HTMLStyleElement::scopedAttributeChanged(bool scoped)
87{
88    ASSERT(ContextFeatures::styleScopedEnabled(&document()));
89
90    if (!inDocument())
91        return;
92
93    if (scoped) {
94        if (m_scopedStyleRegistrationState == RegisteredAsScoped)
95            return;
96
97        // As any <style> in a shadow tree is treated as "scoped",
98        // need to remove the <style> from its shadow root.
99        ContainerNode* scopingNode = 0;
100        if (m_scopedStyleRegistrationState == RegisteredInShadowRoot) {
101            scopingNode = containingShadowRoot();
102            unregisterWithScopingNode(scopingNode);
103        }
104        document().styleEngine()->removeStyleSheetCandidateNode(this, scopingNode);
105        registerWithScopingNode(true);
106
107        document().styleEngine()->addStyleSheetCandidateNode(this, false);
108        document().modifiedStyleSheet(sheet());
109        return;
110    }
111
112    // If the <style> was scoped, need to remove the <style> from the scoping
113    // element, i.e. the parent node.
114    if (m_scopedStyleRegistrationState != RegisteredAsScoped)
115        return;
116
117    unregisterWithScopingNode(parentNode());
118    document().styleEngine()->removeStyleSheetCandidateNode(this, parentNode());
119
120    // As any <style> in a shadow tree is treated as "scoped",
121    // need to add the <style> to its shadow root.
122    if (isInShadowTree())
123        registerWithScopingNode(false);
124
125    document().styleEngine()->addStyleSheetCandidateNode(this, false);
126    // FIXME: currently need to use FullStyleUpdate here.
127    // Because ShadowTreeStyleSheetCollection doesn't know old scoping node.
128    // So setNeedsStyleRecalc for old scoping node is not invoked.
129    document().modifiedStyleSheet(sheet());
130}
131
132void HTMLStyleElement::finishParsingChildren()
133{
134    StyleElement::finishParsingChildren(this);
135    HTMLElement::finishParsingChildren();
136}
137
138void HTMLStyleElement::registerWithScopingNode(bool scoped)
139{
140    // Note: We cannot rely on the 'scoped' element already being present when this method is invoked.
141    // Therefore we cannot rely on scoped()!
142    ASSERT(m_scopedStyleRegistrationState == NotRegistered);
143    ASSERT(inDocument());
144    if (m_scopedStyleRegistrationState != NotRegistered)
145        return;
146
147    ContainerNode* scope = scoped ? parentNode() : containingShadowRoot();
148    if (!scope)
149        return;
150    if (!scope->isElementNode() && !scope->isShadowRoot()) {
151        // DocumentFragment nodes should never be inDocument,
152        // <style> should not be a child of Document, PI or some such.
153        ASSERT_NOT_REACHED();
154        return;
155    }
156    scope->registerScopedHTMLStyleChild();
157    m_scopedStyleRegistrationState = scoped ? RegisteredAsScoped : RegisteredInShadowRoot;
158}
159
160void HTMLStyleElement::unregisterWithScopingNode(ContainerNode* scope)
161{
162    ASSERT(m_scopedStyleRegistrationState != NotRegistered || !ContextFeatures::styleScopedEnabled(&document()));
163    if (!isRegisteredAsScoped())
164        return;
165
166    ASSERT(scope);
167    if (scope) {
168        ASSERT(scope->hasScopedHTMLStyleChild());
169        scope->unregisterScopedHTMLStyleChild();
170    }
171
172    m_scopedStyleRegistrationState = NotRegistered;
173}
174
175Node::InsertionNotificationRequest HTMLStyleElement::insertedInto(ContainerNode* insertionPoint)
176{
177    HTMLElement::insertedInto(insertionPoint);
178    if (insertionPoint->inDocument()) {
179        if (m_scopedStyleRegistrationState == NotRegistered && (scoped() || isInShadowTree()))
180            registerWithScopingNode(scoped());
181    }
182    return InsertionShouldCallDidNotifySubtreeInsertions;
183}
184
185void HTMLStyleElement::removedFrom(ContainerNode* insertionPoint)
186{
187    HTMLElement::removedFrom(insertionPoint);
188
189    // In the current implementation, <style scoped> is only registered if the node is in the document.
190    // That is, because willRemove() is also called if an ancestor is removed from the document.
191    // Now, if we want to register <style scoped> even if it's not inDocument,
192    // we'd need to find a way to discern whether that is the case, or whether <style scoped> itself is about to be removed.
193    ContainerNode* scope = 0;
194    if (m_scopedStyleRegistrationState != NotRegistered) {
195        if (m_scopedStyleRegistrationState == RegisteredInShadowRoot) {
196            scope = containingShadowRoot();
197            if (!scope)
198                scope = insertionPoint->containingShadowRoot();
199        } else
200            scope = parentNode() ? parentNode() : insertionPoint;
201        unregisterWithScopingNode(scope);
202    }
203
204    if (insertionPoint->inDocument())
205        StyleElement::removedFromDocument(document(), this, scope);
206}
207
208void HTMLStyleElement::didNotifySubtreeInsertionsToDocument()
209{
210    StyleElement::processStyleSheet(document(), this);
211}
212
213void HTMLStyleElement::childrenChanged(bool changedByParser, Node* beforeChange, Node* afterChange, int childCountDelta)
214{
215    HTMLElement::childrenChanged(changedByParser, beforeChange, afterChange, childCountDelta);
216    StyleElement::childrenChanged(this);
217}
218
219const AtomicString& HTMLStyleElement::media() const
220{
221    return getAttribute(mediaAttr);
222}
223
224const AtomicString& HTMLStyleElement::type() const
225{
226    return getAttribute(typeAttr);
227}
228
229bool HTMLStyleElement::scoped() const
230{
231    return fastHasAttribute(scopedAttr) && ContextFeatures::styleScopedEnabled(&document());
232}
233
234void HTMLStyleElement::setScoped(bool scopedValue)
235{
236    setBooleanAttribute(scopedAttr, scopedValue);
237}
238
239ContainerNode* HTMLStyleElement::scopingNode()
240{
241    if (!inDocument())
242        return 0;
243
244    if (!isRegisteredAsScoped())
245        return &document();
246
247    if (isRegisteredInShadowRoot())
248        return containingShadowRoot();
249
250    return parentNode();
251}
252
253void HTMLStyleElement::dispatchPendingLoadEvents()
254{
255    styleLoadEventSender().dispatchPendingEvents();
256}
257
258void HTMLStyleElement::dispatchPendingEvent(StyleEventSender* eventSender)
259{
260    ASSERT_UNUSED(eventSender, eventSender == &styleLoadEventSender());
261    dispatchEvent(Event::create(m_loadedSheet ? EventTypeNames::load : EventTypeNames::error));
262}
263
264void HTMLStyleElement::notifyLoadedSheetAndAllCriticalSubresources(bool errorOccurred)
265{
266    if (m_firedLoad)
267        return;
268    m_loadedSheet = !errorOccurred;
269    styleLoadEventSender().dispatchEventSoon(this);
270    m_firedLoad = true;
271}
272
273void HTMLStyleElement::addSubresourceAttributeURLs(ListHashSet<KURL>& urls) const
274{
275    HTMLElement::addSubresourceAttributeURLs(urls);
276
277    if (CSSStyleSheet* styleSheet = const_cast<HTMLStyleElement*>(this)->sheet())
278        styleSheet->contents()->addSubresourceStyleURLs(urls);
279}
280
281bool HTMLStyleElement::disabled() const
282{
283    if (!m_sheet)
284        return false;
285
286    return m_sheet->disabled();
287}
288
289void HTMLStyleElement::setDisabled(bool setDisabled)
290{
291    if (CSSStyleSheet* styleSheet = sheet())
292        styleSheet->setDisabled(setDisabled);
293}
294
295}
296