1 /*
2 * Copyright (C) 2013 Google Inc. All rights reserved.
3 *
4 * Redistribution and use in source and binary forms, with or without
5 * modification, are permitted provided that the following conditions are
6 * met:
7 *
8 * * Redistributions of source code must retain the above copyright
9 * notice, this list of conditions and the following disclaimer.
10 * * Redistributions in binary form must reproduce the above
11 * copyright notice, this list of conditions and the following disclaimer
12 * in the documentation and/or other materials provided with the
13 * distribution.
14 * * Neither the name of Google Inc. nor the names of its
15 * contributors may be used to endorse or promote products derived from
16 * this software without specific prior written permission.
17 *
18 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
19 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
20 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
21 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
22 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
23 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
24 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
25 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
26 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
27 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
28 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
29 */
30
31 #include "config.h"
32 #include "core/dom/PresentationAttributeStyle.h"
33
34 #include "core/css/StylePropertySet.h"
35 #include "core/dom/Attribute.h"
36 #include "core/dom/Element.h"
37 #include "core/html/HTMLInputElement.h"
38 #include "platform/Timer.h"
39 #include "wtf/HashFunctions.h"
40 #include "wtf/HashMap.h"
41 #include "wtf/text/CString.h"
42
43 namespace WebCore {
44
45 using namespace HTMLNames;
46
47 struct PresentationAttributeCacheKey {
PresentationAttributeCacheKeyWebCore::PresentationAttributeCacheKey48 PresentationAttributeCacheKey() : tagName(0) { }
49 StringImpl* tagName;
50 Vector<std::pair<StringImpl*, AtomicString>, 3> attributesAndValues;
51 };
52
operator !=(const PresentationAttributeCacheKey & a,const PresentationAttributeCacheKey & b)53 static bool operator!=(const PresentationAttributeCacheKey& a, const PresentationAttributeCacheKey& b)
54 {
55 if (a.tagName != b.tagName)
56 return true;
57 return a.attributesAndValues != b.attributesAndValues;
58 }
59
60 struct PresentationAttributeCacheEntry {
61 WTF_MAKE_FAST_ALLOCATED;
62 public:
63 PresentationAttributeCacheKey key;
64 RefPtr<StylePropertySet> value;
65 };
66
67 typedef HashMap<unsigned, OwnPtr<PresentationAttributeCacheEntry>, AlreadyHashed> PresentationAttributeCache;
presentationAttributeCache()68 static PresentationAttributeCache& presentationAttributeCache()
69 {
70 DEFINE_STATIC_LOCAL(PresentationAttributeCache, cache, ());
71 return cache;
72 }
73
74 class PresentationAttributeCacheCleaner {
75 WTF_MAKE_NONCOPYABLE(PresentationAttributeCacheCleaner); WTF_MAKE_FAST_ALLOCATED;
76 public:
PresentationAttributeCacheCleaner()77 PresentationAttributeCacheCleaner()
78 : m_hitCount(0)
79 , m_cleanTimer(this, &PresentationAttributeCacheCleaner::cleanCache)
80 {
81 }
82
didHitPresentationAttributeCache()83 void didHitPresentationAttributeCache()
84 {
85 if (presentationAttributeCache().size() < minimumPresentationAttributeCacheSizeForCleaning)
86 return;
87
88 m_hitCount++;
89
90 if (!m_cleanTimer.isActive())
91 m_cleanTimer.startOneShot(presentationAttributeCacheCleanTimeInSeconds, FROM_HERE);
92 }
93
94 private:
95 static const unsigned presentationAttributeCacheCleanTimeInSeconds = 60;
96 static const unsigned minimumPresentationAttributeCacheSizeForCleaning = 100;
97 static const unsigned minimumPresentationAttributeCacheHitCountPerMinute = (100 * presentationAttributeCacheCleanTimeInSeconds) / 60;
98
cleanCache(Timer<PresentationAttributeCacheCleaner> * timer)99 void cleanCache(Timer<PresentationAttributeCacheCleaner>* timer)
100 {
101 ASSERT_UNUSED(timer, timer == &m_cleanTimer);
102 unsigned hitCount = m_hitCount;
103 m_hitCount = 0;
104 if (hitCount > minimumPresentationAttributeCacheHitCountPerMinute)
105 return;
106 presentationAttributeCache().clear();
107 }
108
109 unsigned m_hitCount;
110 Timer<PresentationAttributeCacheCleaner> m_cleanTimer;
111 };
112
attributeNameSort(const pair<StringImpl *,AtomicString> & p1,const pair<StringImpl *,AtomicString> & p2)113 static bool attributeNameSort(const pair<StringImpl*, AtomicString>& p1, const pair<StringImpl*, AtomicString>& p2)
114 {
115 // Sort based on the attribute name pointers. It doesn't matter what the order is as long as it is always the same.
116 return p1.first < p2.first;
117 }
118
makePresentationAttributeCacheKey(Element & element,PresentationAttributeCacheKey & result)119 static void makePresentationAttributeCacheKey(Element& element, PresentationAttributeCacheKey& result)
120 {
121 // FIXME: Enable for SVG.
122 if (!element.isHTMLElement())
123 return;
124 // Interpretation of the size attributes on <input> depends on the type attribute.
125 if (isHTMLInputElement(element))
126 return;
127 AttributeCollection attributes = element.attributes();
128 AttributeCollection::const_iterator end = attributes.end();
129 for (AttributeCollection::const_iterator it = attributes.begin(); it != end; ++it) {
130 if (!element.isPresentationAttribute(it->name()))
131 continue;
132 if (!it->namespaceURI().isNull())
133 return;
134 // FIXME: Background URL may depend on the base URL and can't be shared. Disallow caching.
135 if (it->name() == backgroundAttr)
136 return;
137 result.attributesAndValues.append(std::make_pair(it->localName().impl(), it->value()));
138 }
139 if (result.attributesAndValues.isEmpty())
140 return;
141 // Attribute order doesn't matter. Sort for easy equality comparison.
142 std::sort(result.attributesAndValues.begin(), result.attributesAndValues.end(), attributeNameSort);
143 // The cache key is non-null when the tagName is set.
144 result.tagName = element.localName().impl();
145 }
146
computePresentationAttributeCacheHash(const PresentationAttributeCacheKey & key)147 static unsigned computePresentationAttributeCacheHash(const PresentationAttributeCacheKey& key)
148 {
149 if (!key.tagName)
150 return 0;
151 ASSERT(key.attributesAndValues.size());
152 unsigned attributeHash = StringHasher::hashMemory(key.attributesAndValues.data(), key.attributesAndValues.size() * sizeof(key.attributesAndValues[0]));
153 return WTF::pairIntHash(key.tagName->existingHash(), attributeHash);
154 }
155
computePresentationAttributeStyle(Element & element)156 PassRefPtr<StylePropertySet> computePresentationAttributeStyle(Element& element)
157 {
158 DEFINE_STATIC_LOCAL(PresentationAttributeCacheCleaner, cacheCleaner, ());
159
160 ASSERT(element.isStyledElement());
161
162 PresentationAttributeCacheKey cacheKey;
163 makePresentationAttributeCacheKey(element, cacheKey);
164
165 unsigned cacheHash = computePresentationAttributeCacheHash(cacheKey);
166
167 PresentationAttributeCache::ValueType* cacheValue;
168 if (cacheHash) {
169 cacheValue = presentationAttributeCache().add(cacheHash, nullptr).storedValue;
170 if (cacheValue->value && cacheValue->value->key != cacheKey)
171 cacheHash = 0;
172 } else {
173 cacheValue = 0;
174 }
175
176 RefPtr<StylePropertySet> style;
177 if (cacheHash && cacheValue->value) {
178 style = cacheValue->value->value;
179 cacheCleaner.didHitPresentationAttributeCache();
180 } else {
181 style = MutableStylePropertySet::create(element.isSVGElement() ? SVGAttributeMode : HTMLAttributeMode);
182 AttributeCollection attributes = element.attributes();
183 AttributeCollection::const_iterator end = attributes.end();
184 for (AttributeCollection::const_iterator it = attributes.begin(); it != end; ++it)
185 element.collectStyleForPresentationAttribute(it->name(), it->value(), toMutableStylePropertySet(style));
186 }
187
188 if (!cacheHash || cacheValue->value)
189 return style.release();
190
191 OwnPtr<PresentationAttributeCacheEntry> newEntry = adoptPtr(new PresentationAttributeCacheEntry);
192 newEntry->key = cacheKey;
193 newEntry->value = style;
194
195 static const unsigned presentationAttributeCacheMaximumSize = 4096;
196 if (presentationAttributeCache().size() > presentationAttributeCacheMaximumSize) {
197 // FIXME: Discarding the entire cache when it gets too big is probably bad
198 // since it creates a perf "cliff". Perhaps we should use an LRU?
199 presentationAttributeCache().clear();
200 presentationAttributeCache().set(cacheHash, newEntry.release());
201 } else {
202 cacheValue->value = newEntry.release();
203 }
204
205 return style.release();
206 }
207
208 } // namespace WebCore
209