OLD | NEW |
(Empty) | |
| 1 /* |
| 2 * Copyright (C) 2014 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/css/analyzer/RuleSetAnalyzer.h" |
| 33 |
| 34 #include "core/css/CSSSelector.h" |
| 35 #include "core/css/CSSSelectorList.h" |
| 36 #include "core/css/RuleFeature.h" |
| 37 #include "core/css/RuleSet.h" |
| 38 #include "core/css/analyzer/DescendantInvalidationSet.h" |
| 39 #include "core/dom/Document.h" |
| 40 #include "core/dom/Element.h" |
| 41 #include "core/dom/Node.h" |
| 42 #include "core/dom/shadow/ElementShadow.h" |
| 43 #include "core/dom/shadow/ShadowRoot.h" |
| 44 #include "wtf/Forward.h" |
| 45 #include "wtf/HashSet.h" |
| 46 #include "wtf/text/AtomicString.h" |
| 47 #include "wtf/text/StringHash.h" |
| 48 |
| 49 |
| 50 namespace WebCore { |
| 51 |
| 52 static bool isSkippableComponentForInvalidation(const CSSSelector* selector) |
| 53 { |
| 54 if (selector->matchesPseudoElement() || selector->pseudoType() == CSSSelecto
r::PseudoHost) |
| 55 return false; |
| 56 return true; |
| 57 } |
| 58 |
| 59 // This method is somewhat conservative in what it acceptss. |
| 60 static bool supportsClassDescendantInvalidation(const CSSSelector* selector) |
| 61 { |
| 62 bool foundDescendantRelation = false; |
| 63 bool foundAncestorIdent = false; |
| 64 bool foundIdent = false; |
| 65 for (const CSSSelector* component = selector; component; component = compone
nt->tagHistory()) { |
| 66 |
| 67 // FIXME: We should allow pseudo elements, but we need to change how the
y hook |
| 68 // into recalcStyle by moving them to recalcOwnStyle instead of recalcCh
ildStyle. |
| 69 |
| 70 // FIXME: next up is adding ids and tags. |
| 71 if (component->m_match == CSSSelector::Class) { |
| 72 if (!foundDescendantRelation) |
| 73 foundIdent = true; |
| 74 else |
| 75 foundAncestorIdent = true; |
| 76 } else if (!isSkippableComponentForInvalidation(component)) { |
| 77 return false; |
| 78 } |
| 79 // FIXME: We can probably support ChildTree and DescendantTree. |
| 80 switch (component->relation()) { |
| 81 case CSSSelector::Descendant: |
| 82 case CSSSelector::Child: |
| 83 foundDescendantRelation = true; |
| 84 // Fall through! |
| 85 case CSSSelector::SubSelector: |
| 86 continue; |
| 87 default: |
| 88 return false; |
| 89 } |
| 90 } |
| 91 return foundDescendantRelation && foundAncestorIdent && foundIdent; |
| 92 } |
| 93 |
| 94 void extractClassIdOrTag(const CSSSelector& selector, HashSet<AtomicString>& cla
sses, AtomicString& id, AtomicString& tagName) |
| 95 { |
| 96 if (selector.m_match == CSSSelector::Tag) |
| 97 tagName = selector.tagQName().localName(); |
| 98 else if (selector.m_match == CSSSelector::Id) |
| 99 id = selector.value(); |
| 100 else if (selector.m_match == CSSSelector::Class) |
| 101 classes.add(selector.value()); |
| 102 } |
| 103 |
| 104 RuleSetAnalyzer::RuleSetAnalyzer() |
| 105 { |
| 106 } |
| 107 |
| 108 bool RuleSetAnalyzer::updateClassInvalidationSets(const CSSSelector* selector) |
| 109 { |
| 110 if (!selector) |
| 111 return false; |
| 112 bool supported = supportsClassDescendantInvalidation(selector); |
| 113 // if (supported) |
| 114 // printf("desc. analysis supported for selector: %d %s\n", supported, s
elector->selectorText().ascii().data()); |
| 115 if (!supported) |
| 116 return false; |
| 117 |
| 118 HashSet<AtomicString> classes; |
| 119 AtomicString id; |
| 120 AtomicString tagName; |
| 121 |
| 122 const CSSSelector* lastSelector = selector; |
| 123 for (; lastSelector->relation() == CSSSelector::SubSelector; lastSelector =
lastSelector->tagHistory()) { |
| 124 extractClassIdOrTag(*selector, classes, id, tagName); |
| 125 } |
| 126 extractClassIdOrTag(*lastSelector, classes, id, tagName); |
| 127 |
| 128 for ( ; selector; selector = selector->tagHistory()) { |
| 129 if (selector->m_match == CSSSelector::Class) { |
| 130 DescendantInvalidationSet* invalidationSet = ensureClassInvalidation
Set(selector->value()); |
| 131 if (!id.isEmpty()) |
| 132 invalidationSet->addId(id); |
| 133 if (!tagName.isEmpty()) |
| 134 invalidationSet->addTagName(tagName); |
| 135 for (HashSet<AtomicString>::const_iterator it = classes.begin(); it
!= classes.end(); ++it) { |
| 136 invalidationSet->addClass(*it); |
| 137 } |
| 138 } |
| 139 } |
| 140 return true; |
| 141 } |
| 142 |
| 143 PassRefPtr<RuleSetAnalyzer> RuleSetAnalyzer::create() |
| 144 { |
| 145 return adoptRef(new RuleSetAnalyzer); |
| 146 } |
| 147 static bool debug = false; |
| 148 |
| 149 |
| 150 void RuleSetAnalyzer::collectFeaturesFromRuleData(RuleFeatureSet& features, cons
t RuleData& ruleData) |
| 151 { |
| 152 bool foundSiblingSelector = false; |
| 153 unsigned maxDirectAdjacentSelectors = 0; |
| 154 bool selectorUsesClassInvalidationSet= updateClassInvalidationSets(ruleData.
selector()); |
| 155 |
| 156 HashSet<AtomicString> addedClasses; |
| 157 for (const CSSSelector* selector = ruleData.selector(); selector; selector =
selector->tagHistory()) { |
| 158 features.collectFeaturesFromSelector(selector, &addedClasses); |
| 159 |
| 160 if (const CSSSelectorList* selectorList = selector->selectorList()) { |
| 161 for (const CSSSelector* subSelector = selectorList->first(); subSele
ctor; subSelector = CSSSelectorList::next(subSelector)) { |
| 162 // FIXME: Shouldn't this be checking subSelector->isSiblingSelec
tor()? |
| 163 if (!foundSiblingSelector && selector->isSiblingSelector()) |
| 164 foundSiblingSelector = true; |
| 165 if (subSelector->isDirectAdjacentSelector()) |
| 166 maxDirectAdjacentSelectors++; |
| 167 features.collectFeaturesFromSelector(subSelector, &addedClasses)
; |
| 168 } |
| 169 } else { |
| 170 if (!foundSiblingSelector && selector->isSiblingSelector()) |
| 171 foundSiblingSelector = true; |
| 172 if (selector->isDirectAdjacentSelector()) |
| 173 maxDirectAdjacentSelectors++; |
| 174 } |
| 175 } |
| 176 if (!selectorUsesClassInvalidationSet) { |
| 177 for (HashSet<AtomicString>::const_iterator it = addedClasses.begin(); it
!= addedClasses.end(); ++it) { |
| 178 if (debug) { |
| 179 printf("recalc whole subtree for class=%s selector=%s\n", it->as
cii().data(), ruleData.selector()->selectorText().ascii().data()); |
| 180 } |
| 181 DescendantInvalidationSet* invalidationSet = ensureClassInvalidation
Set(*it); |
| 182 invalidationSet->setWholeSubtreeInvalid(); |
| 183 } |
| 184 } |
| 185 // } else { |
| 186 // printf("desc analysis supported: %s\n", ruleData.selector()->selector
Text().ascii().data()); |
| 187 // } |
| 188 features.setMaxDirectAdjacentSelectors(maxDirectAdjacentSelectors); |
| 189 if (foundSiblingSelector) |
| 190 features.siblingRules.append(RuleFeature(ruleData.rule(), ruleData.selec
torIndex(), ruleData.hasDocumentSecurityOrigin())); |
| 191 if (ruleData.containsUncommonAttributeSelector()) |
| 192 features.uncommonAttributeRules.append(RuleFeature(ruleData.rule(), rule
Data.selectorIndex(), ruleData.hasDocumentSecurityOrigin())); |
| 193 } |
| 194 |
| 195 DescendantInvalidationSet* RuleSetAnalyzer::ensureClassInvalidationSet(const Ato
micString& className) |
| 196 { |
| 197 InvalidationSetMap::AddResult addResult = classInvalidationSets.add(classNam
e, 0); |
| 198 if (addResult.isNewEntry) |
| 199 addResult.iterator->value = DescendantInvalidationSet::create(); |
| 200 return addResult.iterator->value.get(); |
| 201 } |
| 202 |
| 203 void RuleSetAnalyzer::combine(const RuleSetAnalyzer& other) |
| 204 { |
| 205 for (InvalidationSetMap::const_iterator it = other.classInvalidationSets.beg
in(); it != other.classInvalidationSets.end(); ++it) { |
| 206 ensureClassInvalidationSet(it->key)->combine(*it->value); |
| 207 } |
| 208 } |
| 209 |
| 210 RuleSetAnalyzer::InvalidationVec* RuleSetAnalyzer::ensurePendingInvalidationVect
or(Element* element) |
| 211 { |
| 212 PendingInvalidationMap::AddResult addResult = m_pendingInvalidationMap.add(e
lement, 0); |
| 213 if (addResult.isNewEntry) |
| 214 addResult.iterator->value = new InvalidationVec; |
| 215 return addResult.iterator->value; |
| 216 } |
| 217 |
| 218 |
| 219 bool RuleSetAnalyzer::scheduleClassInvalidationForElement(const AtomicString& cl
assName, Element* element) |
| 220 { |
| 221 if (DescendantInvalidationSet* invalidationSet = classInvalidationSets.get(c
lassName)) { |
| 222 ensurePendingInvalidationVector(element)->append(invalidationSet); |
| 223 element->setNeedsInvalidation(); |
| 224 if (debug) |
| 225 printf("element invalidation recorded for class=%s outerHTML=%s\n",
className.ascii().data(), element->outerHTML().ascii().data()); |
| 226 return true; |
| 227 } |
| 228 return false; |
| 229 } |
| 230 |
| 231 bool RuleSetAnalyzer::needsInvalidation(const Element* element) const |
| 232 { |
| 233 return m_pendingInvalidationMap.get(const_cast<Element*>(element)); |
| 234 } |
| 235 |
| 236 void RuleSetAnalyzer::recalcInvalidation(Document* document) |
| 237 { |
| 238 if (debug) |
| 239 printf("RuleSetAnalyzer::recalcInvalidation\n"); |
| 240 Vector<AtomicString> invalidationClasses; |
| 241 for (Node* child = document->firstChild(); child; child = child->nextSibling
()) { |
| 242 if (child->isElementNode() && child->childNeedsInvalidation()) { |
| 243 Element* childElement = toElement(child); |
| 244 recalcInvalidationInternal(childElement, invalidationClasses, false)
; |
| 245 } |
| 246 } |
| 247 document->clearChildNeedsInvalidation(); |
| 248 m_pendingInvalidationMap.clear(); |
| 249 } |
| 250 |
| 251 bool RuleSetAnalyzer::recalcInvalidationInternalForShadowRoot(ShadowRoot* root,
Vector<AtomicString>& invalidationClasses, bool foundInvalidationSet) |
| 252 { |
| 253 bool someElementNeedsStyleRecalc = false; |
| 254 for (Node* child = root->firstChild(); child; child = child->nextSibling())
{ |
| 255 if (child->isElementNode()) { |
| 256 Element* childElement = toElement(child); |
| 257 someElementNeedsStyleRecalc = someElementNeedsStyleRecalc || recalcI
nvalidationInternal(childElement, invalidationClasses, foundInvalidationSet); |
| 258 } |
| 259 } |
| 260 return someElementNeedsStyleRecalc; |
| 261 } |
| 262 |
| 263 bool RuleSetAnalyzer::recalcInvalidationInternal(Element* element, Vector<Atomic
String>& invalidationClasses, bool foundInvalidationSet) |
| 264 { |
| 265 int oldSize = invalidationClasses.size(); |
| 266 if (InvalidationVec* invalidationVec = m_pendingInvalidationMap.get(element)
) { |
| 267 foundInvalidationSet = true; |
| 268 if (debug) |
| 269 printf("found an invalidation vec for element with outerHTML=%s\n",
element->outerHTML().ascii().data()); |
| 270 bool recalcWholeSubtree = false; |
| 271 for (InvalidationVec::const_iterator it = invalidationVec->begin(); it !
= invalidationVec->end(); ++it) { |
| 272 if ((*it)->wholeSubtreeInvalid()) { |
| 273 recalcWholeSubtree = true; |
| 274 break; |
| 275 } else { |
| 276 (*it)->appendClasses(invalidationClasses); |
| 277 } |
| 278 } |
| 279 if (recalcWholeSubtree) { |
| 280 if (debug) |
| 281 printf("setNeedsStyleRecalc(SubtreeStyleChange): element outerHT
ML=%s\n", element->outerHTML().ascii().data()); |
| 282 element->setNeedsStyleRecalc(SubtreeStyleChange); |
| 283 |
| 284 // share code!! |
| 285 invalidationClasses.remove(oldSize, invalidationClasses.size() - old
Size); |
| 286 element->clearChildNeedsInvalidation(); |
| 287 return true; |
| 288 } |
| 289 } |
| 290 |
| 291 bool thisElementNeedsStyleRecalc = false; |
| 292 |
| 293 if (element->hasClass()) { |
| 294 const SpaceSplitString& classNames = element->classNames(); |
| 295 if (debug) |
| 296 printf("element class: %s\n", element->getClassAttribute().ascii().d
ata()); |
| 297 for (Vector<AtomicString>::const_iterator it = invalidationClasses.begin
(); it != invalidationClasses.end(); ++it) { |
| 298 if (debug) |
| 299 printf("checking class for invalidation: %s\n", it->ascii().data
()); |
| 300 if (classNames.contains(*it)) { |
| 301 thisElementNeedsStyleRecalc = true; |
| 302 break; |
| 303 } |
| 304 } |
| 305 } |
| 306 if (foundInvalidationSet || element->childNeedsInvalidation()) { |
| 307 for (Node* child = element->firstChild(); child; child = child->nextSibl
ing()) { |
| 308 if (child->isElementNode()) { |
| 309 Element* childElement = toElement(child); |
| 310 bool childAddedNeedsRecalc = |
| 311 recalcInvalidationInternal(childElement, invalidationClasses
, foundInvalidationSet); |
| 312 thisElementNeedsStyleRecalc = thisElementNeedsStyleRecalc || (ch
ildAddedNeedsRecalc && foundInvalidationSet); |
| 313 } |
| 314 } |
| 315 for (ShadowRoot* root = element->youngestShadowRoot(); root; root = root
->olderShadowRoot()) { |
| 316 thisElementNeedsStyleRecalc = thisElementNeedsStyleRecalc || recalcI
nvalidationInternalForShadowRoot(root, invalidationClasses, foundInvalidationSet
); |
| 317 } |
| 318 } |
| 319 |
| 320 if (thisElementNeedsStyleRecalc) { |
| 321 if (debug) |
| 322 printf("setNeedsStyleRecalc(LocalStyleChange): element outerHTML=%s\
n", element->outerHTML().ascii().data()); |
| 323 element->setNeedsStyleRecalc(LocalStyleChange); |
| 324 } |
| 325 |
| 326 invalidationClasses.remove(oldSize, invalidationClasses.size() - oldSize); |
| 327 element->clearChildNeedsInvalidation(); |
| 328 return thisElementNeedsStyleRecalc; |
| 329 } |
| 330 |
| 331 } // namespace WebCore |
OLD | NEW |