5bee5fd9f56c51c6a313c46726557b144c5cf4ce
[WebKit-https.git] / Source / WebCore / dom / StyledElement.cpp
1 /*
2  * Copyright (C) 1999 Lars Knoll (knoll@kde.org)
3  *           (C) 1999 Antti Koivisto (koivisto@kde.org)
4  *           (C) 2001 Peter Kelly (pmk@post.com)
5  *           (C) 2001 Dirk Mueller (mueller@kde.org)
6  * Copyright (C) 2004, 2005, 2006, 2008, 2010, 2014 Apple Inc. All rights reserved.
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 "StyledElement.h"
26
27 #include "CSSImageValue.h"
28 #include "CSSParser.h"
29 #include "CSSStyleSheet.h"
30 #include "CSSValuePool.h"
31 #include "ContentSecurityPolicy.h"
32 #include "DOMTokenList.h"
33 #include "HTMLElement.h"
34 #include "HTMLParserIdioms.h"
35 #include "InspectorInstrumentation.h"
36 #include "PropertySetCSSStyleDeclaration.h"
37 #include "ScriptableDocumentParser.h"
38 #include "StyleProperties.h"
39 #include "StyleResolver.h"
40 #include <wtf/HashFunctions.h>
41
42 namespace WebCore {
43
44 COMPILE_ASSERT(sizeof(StyledElement) == sizeof(Element), styledelement_should_remain_same_size_as_element);
45
46 using namespace HTMLNames;
47
48 struct PresentationAttributeCacheKey {
49     PresentationAttributeCacheKey() : tagName(0) { }
50     AtomicStringImpl* tagName;
51     // Only the values need refcounting.
52     Vector<std::pair<AtomicStringImpl*, AtomicString>, 3> attributesAndValues;
53 };
54
55 struct PresentationAttributeCacheEntry {
56     WTF_MAKE_FAST_ALLOCATED;
57 public:
58     PresentationAttributeCacheKey key;
59     RefPtr<StyleProperties> value;
60 };
61
62 typedef HashMap<unsigned, std::unique_ptr<PresentationAttributeCacheEntry>, AlreadyHashed> PresentationAttributeCache;
63     
64 static bool operator!=(const PresentationAttributeCacheKey& a, const PresentationAttributeCacheKey& b)
65 {
66     if (a.tagName != b.tagName)
67         return true;
68     return a.attributesAndValues != b.attributesAndValues;
69 }
70
71 static PresentationAttributeCache& presentationAttributeCache()
72 {
73     static NeverDestroyed<PresentationAttributeCache> cache;
74     return cache;
75 }
76
77 class PresentationAttributeCacheCleaner {
78     WTF_MAKE_NONCOPYABLE(PresentationAttributeCacheCleaner); WTF_MAKE_FAST_ALLOCATED;
79 public:
80     PresentationAttributeCacheCleaner()
81         : m_hitCount(0)
82         , m_cleanTimer(*this, &PresentationAttributeCacheCleaner::cleanCache)
83     {
84     }
85
86     void didHitPresentationAttributeCache()
87     {
88         if (presentationAttributeCache().size() < minimumPresentationAttributeCacheSizeForCleaning)
89             return;
90
91         m_hitCount++;
92
93         if (!m_cleanTimer.isActive())
94             m_cleanTimer.startOneShot(presentationAttributeCacheCleanTimeInSeconds);
95      }
96
97 private:
98     static const unsigned presentationAttributeCacheCleanTimeInSeconds = 60;
99     static const int minimumPresentationAttributeCacheSizeForCleaning = 100;
100     static const unsigned minimumPresentationAttributeCacheHitCountPerMinute = (100 * presentationAttributeCacheCleanTimeInSeconds) / 60;
101
102     void cleanCache()
103     {
104         unsigned hitCount = m_hitCount;
105         m_hitCount = 0;
106         if (hitCount > minimumPresentationAttributeCacheHitCountPerMinute)
107             return;
108         presentationAttributeCache().clear();
109     }
110
111     unsigned m_hitCount;
112     Timer m_cleanTimer;
113 };
114
115 static PresentationAttributeCacheCleaner& presentationAttributeCacheCleaner()
116 {
117     static NeverDestroyed<PresentationAttributeCacheCleaner> cleaner;
118     return cleaner;
119 }
120
121 void StyledElement::clearPresentationAttributeCache()
122 {
123     presentationAttributeCache().clear();
124 }
125
126 void StyledElement::synchronizeStyleAttributeInternal(StyledElement* styledElement)
127 {
128     ASSERT(styledElement->elementData());
129     ASSERT(styledElement->elementData()->styleAttributeIsDirty());
130     styledElement->elementData()->setStyleAttributeIsDirty(false);
131     if (const StyleProperties* inlineStyle = styledElement->inlineStyle())
132         styledElement->setSynchronizedLazyAttribute(styleAttr, inlineStyle->asText());
133 }
134
135 StyledElement::~StyledElement()
136 {
137     if (PropertySetCSSStyleDeclaration* cssomWrapper = inlineStyleCSSOMWrapper())
138         cssomWrapper->clearParentElement();
139 }
140
141 CSSStyleDeclaration* StyledElement::style()
142 {
143     return ensureMutableInlineStyle().ensureInlineCSSStyleDeclaration(this);
144 }
145
146 MutableStyleProperties& StyledElement::ensureMutableInlineStyle()
147 {
148     RefPtr<StyleProperties>& inlineStyle = ensureUniqueElementData().m_inlineStyle;
149     if (!inlineStyle)
150         inlineStyle = MutableStyleProperties::create(strictToCSSParserMode(isHTMLElement() && !document().inQuirksMode()));
151     else if (!is<MutableStyleProperties>(*inlineStyle))
152         inlineStyle = inlineStyle->mutableCopy();
153     return downcast<MutableStyleProperties>(*inlineStyle);
154 }
155
156 void StyledElement::attributeChanged(const QualifiedName& name, const AtomicString& oldValue, const AtomicString& newValue, AttributeModificationReason reason)
157 {
158     if (name == styleAttr)
159         styleAttributeChanged(newValue, reason);
160     else if (isPresentationAttribute(name)) {
161         elementData()->setPresentationAttributeStyleIsDirty(true);
162         setNeedsStyleRecalc(InlineStyleChange);
163     }
164
165     Element::attributeChanged(name, oldValue, newValue, reason);
166 }
167
168 PropertySetCSSStyleDeclaration* StyledElement::inlineStyleCSSOMWrapper()
169 {
170     if (!inlineStyle() || !inlineStyle()->hasCSSOMWrapper())
171         return 0;
172     PropertySetCSSStyleDeclaration* cssomWrapper = ensureMutableInlineStyle().cssStyleDeclaration();
173     ASSERT(cssomWrapper && cssomWrapper->parentElement() == this);
174     return cssomWrapper;
175 }
176
177 inline void StyledElement::setInlineStyleFromString(const AtomicString& newStyleString)
178 {
179     RefPtr<StyleProperties>& inlineStyle = elementData()->m_inlineStyle;
180
181     // Avoid redundant work if we're using shared attribute data with already parsed inline style.
182     if (inlineStyle && !elementData()->isUnique())
183         return;
184
185     // We reconstruct the property set instead of mutating if there is no CSSOM wrapper.
186     // This makes wrapperless property sets immutable and so cacheable.
187     if (inlineStyle && !is<MutableStyleProperties>(*inlineStyle))
188         inlineStyle = nullptr;
189
190     if (!inlineStyle)
191         inlineStyle = CSSParser::parseInlineStyleDeclaration(newStyleString, this);
192     else
193         downcast<MutableStyleProperties>(*inlineStyle).parseDeclaration(newStyleString, &document().elementSheet().contents());
194 }
195
196 void StyledElement::styleAttributeChanged(const AtomicString& newStyleString, AttributeModificationReason reason)
197 {
198     WTF::OrdinalNumber startLineNumber = WTF::OrdinalNumber::beforeFirst();
199     if (document().scriptableDocumentParser() && !document().isInDocumentWrite())
200         startLineNumber = document().scriptableDocumentParser()->textPosition().m_line;
201
202     if (newStyleString.isNull()) {
203         if (PropertySetCSSStyleDeclaration* cssomWrapper = inlineStyleCSSOMWrapper())
204             cssomWrapper->clearParentElement();
205         ensureUniqueElementData().m_inlineStyle = nullptr;
206     } else if (reason == ModifiedByCloning || document().contentSecurityPolicy()->allowInlineStyle(document().url(), startLineNumber, isInUserAgentShadowTree()))
207         setInlineStyleFromString(newStyleString);
208
209     elementData()->setStyleAttributeIsDirty(false);
210
211     setNeedsStyleRecalc(InlineStyleChange);
212     InspectorInstrumentation::didInvalidateStyleAttr(document(), *this);
213 }
214
215 void StyledElement::inlineStyleChanged()
216 {
217     invalidateStyleAttribute();
218     InspectorInstrumentation::didInvalidateStyleAttr(document(), *this);
219 }
220     
221 bool StyledElement::setInlineStyleProperty(CSSPropertyID propertyID, CSSValueID identifier, bool important)
222 {
223     ensureMutableInlineStyle().setProperty(propertyID, CSSValuePool::singleton().createIdentifierValue(identifier), important);
224     inlineStyleChanged();
225     return true;
226 }
227
228 bool StyledElement::setInlineStyleProperty(CSSPropertyID propertyID, CSSPropertyID identifier, bool important)
229 {
230     ensureMutableInlineStyle().setProperty(propertyID, CSSValuePool::singleton().createIdentifierValue(identifier), important);
231     inlineStyleChanged();
232     return true;
233 }
234
235 bool StyledElement::setInlineStyleProperty(CSSPropertyID propertyID, double value, CSSPrimitiveValue::UnitTypes unit, bool important)
236 {
237     ensureMutableInlineStyle().setProperty(propertyID, CSSValuePool::singleton().createValue(value, unit), important);
238     inlineStyleChanged();
239     return true;
240 }
241
242 bool StyledElement::setInlineStyleProperty(CSSPropertyID propertyID, const String& value, bool important)
243 {
244     bool changes = ensureMutableInlineStyle().setProperty(propertyID, value, important, &document().elementSheet().contents());
245     if (changes)
246         inlineStyleChanged();
247     return changes;
248 }
249
250 bool StyledElement::removeInlineStyleProperty(CSSPropertyID propertyID)
251 {
252     if (!inlineStyle())
253         return false;
254     bool changes = ensureMutableInlineStyle().removeProperty(propertyID);
255     if (changes)
256         inlineStyleChanged();
257     return changes;
258 }
259
260 void StyledElement::removeAllInlineStyleProperties()
261 {
262     if (!inlineStyle() || inlineStyle()->isEmpty())
263         return;
264     ensureMutableInlineStyle().clear();
265     inlineStyleChanged();
266 }
267
268 void StyledElement::addSubresourceAttributeURLs(ListHashSet<URL>& urls) const
269 {
270     if (const StyleProperties* inlineStyle = elementData() ? elementData()->inlineStyle() : 0)
271         inlineStyle->addSubresourceStyleURLs(urls, &document().elementSheet().contents());
272 }
273
274 static inline bool attributeNameSort(const std::pair<AtomicStringImpl*, AtomicString>& p1, const std::pair<AtomicStringImpl*, AtomicString>& p2)
275 {
276     // Sort based on the attribute name pointers. It doesn't matter what the order is as long as it is always the same. 
277     return p1.first < p2.first;
278 }
279
280 void StyledElement::makePresentationAttributeCacheKey(PresentationAttributeCacheKey& result) const
281 {    
282     // FIXME: Enable for SVG.
283     if (namespaceURI() != xhtmlNamespaceURI)
284         return;
285     // Interpretation of the size attributes on <input> depends on the type attribute.
286     if (hasTagName(inputTag))
287         return;
288     for (const Attribute& attribute : attributesIterator()) {
289         if (!isPresentationAttribute(attribute.name()))
290             continue;
291         if (!attribute.namespaceURI().isNull())
292             return;
293         // FIXME: Background URL may depend on the base URL and can't be shared. Disallow caching.
294         if (attribute.name() == backgroundAttr)
295             return;
296         result.attributesAndValues.append(std::make_pair(attribute.localName().impl(), attribute.value()));
297     }
298     if (result.attributesAndValues.isEmpty())
299         return;
300     // Attribute order doesn't matter. Sort for easy equality comparison.
301     std::sort(result.attributesAndValues.begin(), result.attributesAndValues.end(), attributeNameSort);
302     // The cache key is non-null when the tagName is set.
303     result.tagName = localName().impl();
304 }
305
306 static unsigned computePresentationAttributeCacheHash(const PresentationAttributeCacheKey& key)
307 {
308     if (!key.tagName)
309         return 0;
310     ASSERT(key.attributesAndValues.size());
311     unsigned attributeHash = StringHasher::hashMemory(key.attributesAndValues.data(), key.attributesAndValues.size() * sizeof(key.attributesAndValues[0]));
312     return WTF::pairIntHash(key.tagName->existingHash(), attributeHash);
313 }
314
315 void StyledElement::rebuildPresentationAttributeStyle()
316 {
317     PresentationAttributeCacheKey cacheKey;
318     makePresentationAttributeCacheKey(cacheKey);
319
320     unsigned cacheHash = computePresentationAttributeCacheHash(cacheKey);
321
322     PresentationAttributeCache::iterator cacheIterator;
323     if (cacheHash) {
324         cacheIterator = presentationAttributeCache().add(cacheHash, nullptr).iterator;
325         if (cacheIterator->value && cacheIterator->value->key != cacheKey)
326             cacheHash = 0;
327     } else
328         cacheIterator = presentationAttributeCache().end();
329
330     RefPtr<StyleProperties> style;
331     if (cacheHash && cacheIterator->value) {
332         style = cacheIterator->value->value;
333         presentationAttributeCacheCleaner().didHitPresentationAttributeCache();
334     } else {
335         style = MutableStyleProperties::create(isSVGElement() ? SVGAttributeMode : CSSQuirksMode);
336         for (const Attribute& attribute : attributesIterator())
337             collectStyleForPresentationAttribute(attribute.name(), attribute.value(), static_cast<MutableStyleProperties&>(*style));
338     }
339
340     // ShareableElementData doesn't store presentation attribute style, so make sure we have a UniqueElementData.
341     UniqueElementData& elementData = ensureUniqueElementData();
342
343     elementData.setPresentationAttributeStyleIsDirty(false);
344     elementData.m_presentationAttributeStyle = style->isEmpty() ? 0 : style;
345
346     if (!cacheHash || cacheIterator->value)
347         return;
348
349     std::unique_ptr<PresentationAttributeCacheEntry> newEntry = std::make_unique<PresentationAttributeCacheEntry>();
350     newEntry->key = cacheKey;
351     newEntry->value = style.release();
352
353     static const int presentationAttributeCacheMaximumSize = 4096;
354     if (presentationAttributeCache().size() > presentationAttributeCacheMaximumSize) {
355         // Start building from scratch if the cache ever gets big.
356         presentationAttributeCache().clear();
357         presentationAttributeCache().set(cacheHash, WTF::move(newEntry));
358     } else
359         cacheIterator->value = WTF::move(newEntry);
360 }
361
362 void StyledElement::addPropertyToPresentationAttributeStyle(MutableStyleProperties& style, CSSPropertyID propertyID, CSSValueID identifier)
363 {
364     style.setProperty(propertyID, CSSValuePool::singleton().createIdentifierValue(identifier));
365 }
366
367 void StyledElement::addPropertyToPresentationAttributeStyle(MutableStyleProperties& style, CSSPropertyID propertyID, double value, CSSPrimitiveValue::UnitTypes unit)
368 {
369     style.setProperty(propertyID, CSSValuePool::singleton().createValue(value, unit));
370 }
371     
372 void StyledElement::addPropertyToPresentationAttributeStyle(MutableStyleProperties& style, CSSPropertyID propertyID, const String& value)
373 {
374     style.setProperty(propertyID, value, false, &document().elementSheet().contents());
375 }
376
377 }