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 blink { 44 45 using namespace HTMLNames; 46 47 struct PresentationAttributeCacheKey { 48 PresentationAttributeCacheKey() : tagName(0) { } 49 StringImpl* tagName; 50 Vector<std::pair<StringImpl*, AtomicString>, 3> attributesAndValues; 51 }; 52 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 FINAL : public NoBaseWillBeGarbageCollectedFinalized<PresentationAttributeCacheEntry> { 61 WTF_MAKE_FAST_ALLOCATED_WILL_BE_REMOVED; 62 public: 63 void trace(Visitor* visitor) { visitor->trace(value); } 64 65 PresentationAttributeCacheKey key; 66 RefPtrWillBeMember<StylePropertySet> value; 67 }; 68 69 typedef WillBeHeapHashMap<unsigned, OwnPtrWillBeMember<PresentationAttributeCacheEntry>, AlreadyHashed> PresentationAttributeCache; 70 static PresentationAttributeCache& presentationAttributeCache() 71 { 72 DEFINE_STATIC_LOCAL(OwnPtrWillBePersistent<PresentationAttributeCache>, cache, (adoptPtrWillBeNoop(new PresentationAttributeCache()))); 73 return *cache; 74 } 75 76 class PresentationAttributeCacheCleaner { 77 WTF_MAKE_NONCOPYABLE(PresentationAttributeCacheCleaner); WTF_MAKE_FAST_ALLOCATED; 78 public: 79 PresentationAttributeCacheCleaner() 80 : m_hitCount(0) 81 , m_cleanTimer(this, &PresentationAttributeCacheCleaner::cleanCache) 82 { 83 } 84 85 void didHitPresentationAttributeCache() 86 { 87 if (presentationAttributeCache().size() < minimumPresentationAttributeCacheSizeForCleaning) 88 return; 89 90 m_hitCount++; 91 92 if (!m_cleanTimer.isActive()) 93 m_cleanTimer.startOneShot(presentationAttributeCacheCleanTimeInSeconds, FROM_HERE); 94 } 95 96 private: 97 static const unsigned presentationAttributeCacheCleanTimeInSeconds = 60; 98 static const unsigned minimumPresentationAttributeCacheSizeForCleaning = 100; 99 static const unsigned minimumPresentationAttributeCacheHitCountPerMinute = (100 * presentationAttributeCacheCleanTimeInSeconds) / 60; 100 101 void cleanCache(Timer<PresentationAttributeCacheCleaner>* timer) 102 { 103 ASSERT_UNUSED(timer, timer == &m_cleanTimer); 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<PresentationAttributeCacheCleaner> m_cleanTimer; 113 }; 114 115 static bool attributeNameSort(const pair<StringImpl*, AtomicString>& p1, const pair<StringImpl*, AtomicString>& p2) 116 { 117 // Sort based on the attribute name pointers. It doesn't matter what the order is as long as it is always the same. 118 return p1.first < p2.first; 119 } 120 121 static void makePresentationAttributeCacheKey(Element& element, PresentationAttributeCacheKey& result) 122 { 123 // FIXME: Enable for SVG. 124 if (!element.isHTMLElement()) 125 return; 126 // Interpretation of the size attributes on <input> depends on the type attribute. 127 if (isHTMLInputElement(element)) 128 return; 129 AttributeCollection attributes = element.attributesWithoutUpdate(); 130 AttributeCollection::iterator end = attributes.end(); 131 for (AttributeCollection::iterator it = attributes.begin(); it != end; ++it) { 132 if (!element.isPresentationAttribute(it->name())) 133 continue; 134 if (!it->namespaceURI().isNull()) 135 return; 136 // FIXME: Background URL may depend on the base URL and can't be shared. Disallow caching. 137 if (it->name() == backgroundAttr) 138 return; 139 result.attributesAndValues.append(std::make_pair(it->localName().impl(), it->value())); 140 } 141 if (result.attributesAndValues.isEmpty()) 142 return; 143 // Attribute order doesn't matter. Sort for easy equality comparison. 144 std::sort(result.attributesAndValues.begin(), result.attributesAndValues.end(), attributeNameSort); 145 // The cache key is non-null when the tagName is set. 146 result.tagName = element.localName().impl(); 147 } 148 149 static unsigned computePresentationAttributeCacheHash(const PresentationAttributeCacheKey& key) 150 { 151 if (!key.tagName) 152 return 0; 153 ASSERT(key.attributesAndValues.size()); 154 unsigned attributeHash = StringHasher::hashMemory(key.attributesAndValues.data(), key.attributesAndValues.size() * sizeof(key.attributesAndValues[0])); 155 return WTF::pairIntHash(key.tagName->existingHash(), attributeHash); 156 } 157 158 PassRefPtrWillBeRawPtr<StylePropertySet> computePresentationAttributeStyle(Element& element) 159 { 160 DEFINE_STATIC_LOCAL(PresentationAttributeCacheCleaner, cacheCleaner, ()); 161 162 ASSERT(element.isStyledElement()); 163 164 PresentationAttributeCacheKey cacheKey; 165 makePresentationAttributeCacheKey(element, cacheKey); 166 167 unsigned cacheHash = computePresentationAttributeCacheHash(cacheKey); 168 169 PresentationAttributeCache::ValueType* cacheValue; 170 if (cacheHash) { 171 cacheValue = presentationAttributeCache().add(cacheHash, nullptr).storedValue; 172 if (cacheValue->value && cacheValue->value->key != cacheKey) 173 cacheHash = 0; 174 } else { 175 cacheValue = 0; 176 } 177 178 RefPtrWillBeRawPtr<StylePropertySet> style = nullptr; 179 if (cacheHash && cacheValue->value) { 180 style = cacheValue->value->value; 181 cacheCleaner.didHitPresentationAttributeCache(); 182 } else { 183 style = MutableStylePropertySet::create(element.isSVGElement() ? SVGAttributeMode : HTMLAttributeMode); 184 AttributeCollection attributes = element.attributesWithoutUpdate(); 185 AttributeCollection::iterator end = attributes.end(); 186 for (AttributeCollection::iterator it = attributes.begin(); it != end; ++it) 187 element.collectStyleForPresentationAttribute(it->name(), it->value(), toMutableStylePropertySet(style)); 188 } 189 190 if (!cacheHash || cacheValue->value) 191 return style.release(); 192 193 OwnPtrWillBeRawPtr<PresentationAttributeCacheEntry> newEntry = adoptPtrWillBeNoop(new PresentationAttributeCacheEntry); 194 newEntry->key = cacheKey; 195 newEntry->value = style; 196 197 static const unsigned presentationAttributeCacheMaximumSize = 4096; 198 if (presentationAttributeCache().size() > presentationAttributeCacheMaximumSize) { 199 // FIXME: Discarding the entire cache when it gets too big is probably bad 200 // since it creates a perf "cliff". Perhaps we should use an LRU? 201 presentationAttributeCache().clear(); 202 presentationAttributeCache().set(cacheHash, newEntry.release()); 203 } else { 204 cacheValue->value = newEntry.release(); 205 } 206 207 return style.release(); 208 } 209 210 } // namespace blink 211