Clean up ContainerNode::childrenChanged
[WebKit-https.git] / Source / WebCore / html / HTMLStyleElement.cpp
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, 2013 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 "HTMLStyleElement.h"
26
27 #include "Attribute.h"
28 #include "ContextFeatures.h"
29 #include "Document.h"
30 #include "Event.h"
31 #include "EventSender.h"
32 #include "HTMLNames.h"
33 #include "MediaList.h"
34 #include "ScriptEventListener.h"
35 #include "ScriptableDocumentParser.h"
36 #include "ShadowRoot.h"
37 #include "StyleSheetContents.h"
38
39 namespace WebCore {
40
41 using namespace HTMLNames;
42
43 static StyleEventSender& styleLoadEventSender()
44 {
45     DEFINE_STATIC_LOCAL(StyleEventSender, sharedLoadEventSender, (eventNames().loadEvent));
46     return sharedLoadEventSender;
47 }
48
49 inline HTMLStyleElement::HTMLStyleElement(const QualifiedName& tagName, Document* document, bool createdByParser)
50     : HTMLElement(tagName, document)
51     , m_styleSheetOwner(document, createdByParser)
52     , m_firedLoad(false)
53     , m_loadedSheet(false)
54     , m_scopedStyleRegistrationState(NotRegistered)
55 {
56     ASSERT(hasTagName(styleTag));
57 }
58
59 HTMLStyleElement::~HTMLStyleElement()
60 {
61     // During tear-down, willRemove isn't called, so m_scopedStyleRegistrationState may still be RegisteredAsScoped or RegisteredInShadowRoot here.
62     // Therefore we can't ASSERT(m_scopedStyleRegistrationState == NotRegistered).
63     m_styleSheetOwner.clearDocumentData(&document(), this);
64
65     styleLoadEventSender().cancelEvent(this);
66 }
67
68 PassRefPtr<HTMLStyleElement> HTMLStyleElement::create(const QualifiedName& tagName, Document* document, bool createdByParser)
69 {
70     return adoptRef(new HTMLStyleElement(tagName, document, createdByParser));
71 }
72
73 void HTMLStyleElement::parseAttribute(const QualifiedName& name, const AtomicString& value)
74 {
75     if (name == titleAttr && sheet())
76         sheet()->setTitle(value);
77     else if (name == scopedAttr && ContextFeatures::styleScopedEnabled(&document()))
78         scopedAttributeChanged(!value.isNull());
79     else if (name == mediaAttr) {
80         m_styleSheetOwner.setMedia(value);
81         if (sheet()) {
82             sheet()->setMediaQueries(MediaQuerySet::createAllowingDescriptionSyntax(value));
83             if (inDocument() && document().renderer())
84                 document().styleResolverChanged(RecalcStyleImmediately);
85         }
86     } else if (name == typeAttr)
87         m_styleSheetOwner.setContentType(value);
88     else
89         HTMLElement::parseAttribute(name, value);
90 }
91
92 void HTMLStyleElement::scopedAttributeChanged(bool scoped)
93 {
94     ASSERT(ContextFeatures::styleScopedEnabled(&document()));
95
96     if (!inDocument())
97         return;
98
99     if (scoped) {
100         // As any <style> in a shadow tree is treated as "scoped",
101         // need to remove the <style> from its shadow root.
102         if (m_scopedStyleRegistrationState == RegisteredInShadowRoot)
103             unregisterWithScopingNode(containingShadowRoot());
104
105         if (m_scopedStyleRegistrationState != RegisteredAsScoped)
106             registerWithScopingNode(true);
107         return;
108     }
109
110     // If the <style> was scoped, need to remove the <style> from the scoping
111     // element, i.e. the parent node.
112     if (m_scopedStyleRegistrationState == RegisteredAsScoped)
113         unregisterWithScopingNode(parentNode());
114
115     // As any <style> in a shadow tree is treated as "scoped",
116     // need to add the <style> to its shadow root.
117     if (isInShadowTree() && m_scopedStyleRegistrationState != RegisteredInShadowRoot)
118         registerWithScopingNode(false);
119 }
120
121 void HTMLStyleElement::finishParsingChildren()
122 {
123     m_styleSheetOwner.finishParsingChildren(this);
124     HTMLElement::finishParsingChildren();
125 }
126
127 void HTMLStyleElement::registerWithScopingNode(bool scoped)
128 {
129     // Note: We cannot rely on the 'scoped' element already being present when this method is invoked.
130     // Therefore we cannot rely on scoped()!
131     ASSERT(m_scopedStyleRegistrationState == NotRegistered);
132     ASSERT(inDocument());
133     if (m_scopedStyleRegistrationState != NotRegistered)
134         return;
135
136     ContainerNode* scope = scoped ? parentNode() : containingShadowRoot();
137     if (!scope)
138         return;
139     if (!scope->isElementNode() && !scope->isShadowRoot()) {
140         // DocumentFragment nodes should never be inDocument,
141         // <style> should not be a child of Document, PI or some such.
142         ASSERT_NOT_REACHED();
143         return;
144     }
145     scope->registerScopedHTMLStyleChild();
146     if (scope->isShadowRoot())
147         scope->shadowHost()->setNeedsStyleRecalc();
148     else
149         scope->setNeedsStyleRecalc();
150     if (inDocument() && !document().parsing() && document().renderer())
151         document().styleResolverChanged(DeferRecalcStyle);
152
153     m_scopedStyleRegistrationState = scoped ? RegisteredAsScoped : RegisteredInShadowRoot;
154 }
155
156 void HTMLStyleElement::unregisterWithScopingNode(ContainerNode* scope)
157 {
158     ASSERT(m_scopedStyleRegistrationState != NotRegistered || !ContextFeatures::styleScopedEnabled(&document()));
159     if (!isRegisteredAsScoped())
160         return;
161
162     ASSERT(scope);
163     if (scope) {
164         ASSERT(scope->hasScopedHTMLStyleChild());
165         scope->unregisterScopedHTMLStyleChild();
166         scope->setNeedsStyleRecalc();
167     }
168     if (inDocument() && !document().parsing() && document().renderer())
169         document().styleResolverChanged(DeferRecalcStyle);
170
171     m_scopedStyleRegistrationState = NotRegistered;
172 }
173
174 Node::InsertionNotificationRequest HTMLStyleElement::insertedInto(ContainerNode* insertionPoint)
175 {
176     HTMLElement::insertedInto(insertionPoint);
177     if (insertionPoint->inDocument()) {
178         m_styleSheetOwner.insertedIntoDocument(&document(), this);
179         if (m_scopedStyleRegistrationState == NotRegistered && (scoped() || isInShadowTree()))
180             registerWithScopingNode(scoped());
181     }
182
183     return InsertionDone;
184 }
185
186 void HTMLStyleElement::removedFrom(ContainerNode* insertionPoint)
187 {
188     HTMLElement::removedFrom(insertionPoint);
189
190     // In the current implementation, <style scoped> is only registered if the node is in the document.
191     // That is, because willRemove() is also called if an ancestor is removed from the document.
192     // Now, if we want to register <style scoped> even if it's not inDocument,
193     // we'd need to find a way to discern whether that is the case, or whether <style scoped> itself is about to be removed.
194     if (m_scopedStyleRegistrationState != NotRegistered) {
195         ContainerNode* scope;
196         if (m_scopedStyleRegistrationState == RegisteredInShadowRoot) {
197             scope = containingShadowRoot();
198             if (!scope)
199                 scope = insertionPoint->containingShadowRoot();
200         } else
201             scope = parentNode() ? parentNode() : insertionPoint;
202         unregisterWithScopingNode(scope);
203     }
204
205     if (insertionPoint->inDocument())
206         m_styleSheetOwner.removedFromDocument(&document(), this);
207 }
208
209 void HTMLStyleElement::childrenChanged(const ChildChange& change)
210 {
211     HTMLElement::childrenChanged(change);
212     m_styleSheetOwner.childrenChanged(this);
213 }
214
215 bool HTMLStyleElement::scoped() const
216 {
217     return fastHasAttribute(scopedAttr) && ContextFeatures::styleScopedEnabled(&document());
218 }
219
220 void HTMLStyleElement::setScoped(bool scopedValue)
221 {
222     setBooleanAttribute(scopedAttr, scopedValue);
223 }
224
225 Element* HTMLStyleElement::scopingElement() const
226 {
227     if (!scoped())
228         return 0;
229
230     // FIXME: This probably needs to be refined for scoped stylesheets within shadow DOM.
231     // As written, such a stylesheet could style the host element, as well as children of the host.
232     // OTOH, this paves the way for a :bound-element implementation.
233     ContainerNode* parentOrShadowHost = parentOrShadowHostNode();
234     if (!parentOrShadowHost || !parentOrShadowHost->isElementNode())
235         return 0;
236
237     return toElement(parentOrShadowHost);
238 }
239
240 void HTMLStyleElement::dispatchPendingLoadEvents()
241 {
242     styleLoadEventSender().dispatchPendingEvents();
243 }
244
245 void HTMLStyleElement::dispatchPendingEvent(StyleEventSender* eventSender)
246 {
247     ASSERT_UNUSED(eventSender, eventSender == &styleLoadEventSender());
248     if (m_loadedSheet)
249         dispatchEvent(Event::create(eventNames().loadEvent, false, false));
250     else
251         dispatchEvent(Event::create(eventNames().errorEvent, false, false));
252 }
253
254 void HTMLStyleElement::notifyLoadedSheetAndAllCriticalSubresources(bool errorOccurred)
255 {
256     if (m_firedLoad)
257         return;
258     m_loadedSheet = !errorOccurred;
259     styleLoadEventSender().dispatchEventSoon(this);
260     m_firedLoad = true;
261 }
262
263 void HTMLStyleElement::addSubresourceAttributeURLs(ListHashSet<KURL>& urls) const
264 {    
265     HTMLElement::addSubresourceAttributeURLs(urls);
266
267     if (CSSStyleSheet* styleSheet = const_cast<HTMLStyleElement*>(this)->sheet())
268         styleSheet->contents()->addSubresourceStyleURLs(urls);
269 }
270
271 bool HTMLStyleElement::disabled() const
272 {
273     if (!sheet())
274         return false;
275
276     return sheet()->disabled();
277 }
278
279 void HTMLStyleElement::setDisabled(bool setDisabled)
280 {
281     if (CSSStyleSheet* styleSheet = sheet())
282         styleSheet->setDisabled(setDisabled);
283 }
284
285 }