1e8db1eae02e4c53c26d2ce21a69ea5b67e77d9f
[WebKit-https.git] / Source / WebCore / style / StyleSharingResolver.cpp
1 /*
2  * Copyright (C) 2016 Apple 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
6  * are met:
7  * 1. Redistributions of source code must retain the above copyright
8  *    notice, this list of conditions and the following disclaimer.
9  * 2. Redistributions in binary form must reproduce the above copyright
10  *    notice, this list of conditions and the following disclaimer in the
11  *    documentation and/or other materials provided with the distribution.
12  *
13  * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY
14  * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
15  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
16  * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL APPLE INC. OR
17  * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
18  * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
19  * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
20  * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
21  * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
22  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
23  * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
24  */
25
26 #include "config.h"
27 #include "StyleSharingResolver.h"
28
29 #include "DocumentRuleSets.h"
30 #include "ElementRuleCollector.h"
31 #include "HTMLInputElement.h"
32 #include "HTMLNames.h"
33 #include "NodeRenderStyle.h"
34 #include "RenderStyle.h"
35 #include "SVGElement.h"
36 #include "StyledElement.h"
37 #include "VisitedLinkState.h"
38 #include "WebVTTElement.h"
39 #include "XMLNames.h"
40
41 namespace WebCore {
42 namespace Style {
43
44 static const unsigned cStyleSearchThreshold = 10;
45
46 struct SharingResolver::Context {
47     const StyledElement& element;
48     bool elementAffectedByClassRules;
49     EInsideLink elementLinkState;
50 };
51
52 SharingResolver::SharingResolver(const Document& document, const DocumentRuleSets& ruleSets, const SelectorFilter& selectorFilter)
53     : m_document(document)
54     , m_ruleSets(ruleSets)
55     , m_selectorFilter(selectorFilter)
56 {
57 }
58
59 static inline bool parentElementPreventsSharing(const Element& parentElement)
60 {
61     return parentElement.hasFlagsSetDuringStylingOfChildren();
62 }
63
64 static inline bool elementHasDirectionAuto(const Element& element)
65 {
66     // FIXME: This line is surprisingly hot, we may wish to inline hasDirectionAuto into StyleResolver.
67     return is<HTMLElement>(element) && downcast<HTMLElement>(element).hasDirectionAuto();
68 }
69
70 RefPtr<RenderStyle> SharingResolver::resolve(const Element& searchElement)
71 {
72     if (!is<StyledElement>(searchElement))
73         return nullptr;
74     auto& element = downcast<StyledElement>(searchElement);
75     if (!element.parentElement())
76         return nullptr;
77     auto& parentElement = *element.parentElement();
78     if (parentElement.shadowRoot())
79         return nullptr;
80     if (!parentElement.renderStyle())
81         return nullptr;
82     // If the element has inline style it is probably unique.
83     if (element.inlineStyle())
84         return nullptr;
85     if (element.isSVGElement() && downcast<SVGElement>(element).animatedSMILStyleProperties())
86         return nullptr;
87     // Ids stop style sharing if they show up in the stylesheets.
88     if (element.hasID() && m_ruleSets.features().idsInRules.contains(element.idForStyleResolution().impl()))
89         return nullptr;
90     if (parentElementPreventsSharing(parentElement))
91         return nullptr;
92     if (&element == m_document.cssTarget())
93         return nullptr;
94     if (elementHasDirectionAuto(element))
95         return nullptr;
96
97     Context context {
98         element,
99         element.hasClass() && classNamesAffectedByRules(element.classNames()),
100         m_document.visitedLinkState().determineLinkState(element)
101     };
102
103     // Check previous siblings and their cousins.
104     unsigned count = 0;
105     StyledElement* shareElement = nullptr;
106     Node* cousinList = element.previousSibling();
107     while (cousinList) {
108         shareElement = findSibling(context, cousinList, count);
109         if (shareElement)
110             break;
111         cousinList = locateCousinList(cousinList->parentElement());
112     }
113
114     // If we have exhausted all our budget or our cousins.
115     if (!shareElement)
116         return nullptr;
117
118     // Can't share if sibling rules apply. This is checked at the end as it should rarely fail.
119     if (styleSharingCandidateMatchesRuleSet(element, m_ruleSets.sibling()))
120         return nullptr;
121     // Can't share if attribute rules apply.
122     if (styleSharingCandidateMatchesRuleSet(element, m_ruleSets.uncommonAttribute()))
123         return nullptr;
124     // Tracking child index requires unique style for each node. This may get set by the sibling rule match above.
125     if (parentElementPreventsSharing(parentElement))
126         return nullptr;
127
128     m_elementsSharingStyle.add(&element, shareElement);
129
130     return RenderStyle::clone(shareElement->renderStyle());
131 }
132
133 StyledElement* SharingResolver::findSibling(const Context& context, Node* node, unsigned& count) const
134 {
135     for (; node; node = node->previousSibling()) {
136         if (!is<StyledElement>(*node))
137             continue;
138         if (canShareStyleWithElement(context, downcast<StyledElement>(*node)))
139             break;
140         if (count++ == cStyleSearchThreshold)
141             return nullptr;
142     }
143     return downcast<StyledElement>(node);
144 }
145
146 Node* SharingResolver::locateCousinList(const Element* parent) const
147 {
148     const unsigned maximumSearchCount = 10;
149     for (unsigned count = 0; count < maximumSearchCount; ++count) {
150         auto* elementSharingParentStyle = m_elementsSharingStyle.get(parent);
151         if (!elementSharingParentStyle)
152             return nullptr;
153         if (!parentElementPreventsSharing(*elementSharingParentStyle)) {
154             if (auto* cousin = elementSharingParentStyle->lastChild())
155                 return cousin;
156         }
157         parent = elementSharingParentStyle;
158     }
159
160     return nullptr;
161 }
162
163 static bool canShareStyleWithControl(const HTMLFormControlElement& element, const HTMLFormControlElement& formElement)
164 {
165     if (!is<HTMLInputElement>(formElement) || !is<HTMLInputElement>(element))
166         return false;
167
168     auto& thisInputElement = downcast<HTMLInputElement>(formElement);
169     auto& otherInputElement = downcast<HTMLInputElement>(element);
170
171     if (thisInputElement.isAutoFilled() != otherInputElement.isAutoFilled())
172         return false;
173     if (thisInputElement.shouldAppearChecked() != otherInputElement.shouldAppearChecked())
174         return false;
175     if (thisInputElement.shouldAppearIndeterminate() != otherInputElement.shouldAppearIndeterminate())
176         return false;
177     if (thisInputElement.isRequired() != otherInputElement.isRequired())
178         return false;
179
180     if (formElement.isDisabledFormControl() != element.isDisabledFormControl())
181         return false;
182
183     if (formElement.isDefaultButtonForForm() != element.isDefaultButtonForForm())
184         return false;
185
186     if (formElement.isInRange() != element.isInRange())
187         return false;
188
189     if (formElement.isOutOfRange() != element.isOutOfRange())
190         return false;
191
192     return true;
193 }
194
195 bool SharingResolver::canShareStyleWithElement(const Context& context, const StyledElement& candidateElement) const
196 {
197     auto& element = context.element;
198     auto* style = candidateElement.renderStyle();
199     if (!style)
200         return false;
201     if (style->unique())
202         return false;
203     if (style->hasUniquePseudoStyle())
204         return false;
205     if (candidateElement.tagQName() != element.tagQName())
206         return false;
207     if (candidateElement.inlineStyle())
208         return false;
209     if (candidateElement.needsStyleRecalc())
210         return false;
211     if (candidateElement.isSVGElement() && downcast<SVGElement>(candidateElement).animatedSMILStyleProperties())
212         return false;
213     if (candidateElement.isLink() != element.isLink())
214         return false;
215     if (candidateElement.hovered() != element.hovered())
216         return false;
217     if (candidateElement.active() != element.active())
218         return false;
219     if (candidateElement.focused() != element.focused())
220         return false;
221     if (candidateElement.shadowPseudoId() != element.shadowPseudoId())
222         return false;
223     if (&candidateElement == m_document.cssTarget())
224         return false;
225     if (!sharingCandidateHasIdenticalStyleAffectingAttributes(context, candidateElement))
226         return false;
227     if (const_cast<StyledElement&>(candidateElement).additionalPresentationAttributeStyle() != const_cast<StyledElement&>(element).additionalPresentationAttributeStyle())
228         return false;
229     if (candidateElement.affectsNextSiblingElementStyle() || candidateElement.styleIsAffectedByPreviousSibling())
230         return false;
231
232     if (candidateElement.hasID() && m_ruleSets.features().idsInRules.contains(candidateElement.idForStyleResolution().impl()))
233         return false;
234
235     bool isControl = is<HTMLFormControlElement>(candidateElement);
236
237     if (isControl != is<HTMLFormControlElement>(element))
238         return false;
239
240     if (isControl && !canShareStyleWithControl(downcast<HTMLFormControlElement>(element), downcast<HTMLFormControlElement>(candidateElement)))
241         return false;
242
243     if (style->transitions() || style->animations())
244         return false;
245
246     // Turn off style sharing for elements that can gain layers for reasons outside of the style system.
247     // See comments in RenderObject::setStyle().
248     if (candidateElement.hasTagName(HTMLNames::iframeTag) || candidateElement.hasTagName(HTMLNames::frameTag))
249         return false;
250
251     if (candidateElement.hasTagName(HTMLNames::embedTag) || candidateElement.hasTagName(HTMLNames::objectTag) || candidateElement.hasTagName(HTMLNames::appletTag) || candidateElement.hasTagName(HTMLNames::canvasTag))
252         return false;
253
254     if (elementHasDirectionAuto(candidateElement))
255         return false;
256
257     if (candidateElement.isLink() && context.elementLinkState != style->insideLink())
258         return false;
259
260     if (candidateElement.elementData() != element.elementData()) {
261         if (candidateElement.fastGetAttribute(HTMLNames::readonlyAttr) != element.fastGetAttribute(HTMLNames::readonlyAttr))
262             return false;
263         if (candidateElement.isSVGElement()) {
264             if (candidateElement.getAttribute(HTMLNames::typeAttr) != element.getAttribute(HTMLNames::typeAttr))
265                 return false;
266         } else {
267             if (candidateElement.fastGetAttribute(HTMLNames::typeAttr) != element.fastGetAttribute(HTMLNames::typeAttr))
268                 return false;
269         }
270     }
271
272     if (candidateElement.matchesValidPseudoClass() != element.matchesValidPseudoClass())
273         return false;
274
275     if (element.matchesInvalidPseudoClass() != element.matchesValidPseudoClass())
276         return false;
277
278 #if ENABLE(VIDEO_TRACK)
279     // Deny sharing styles between WebVTT and non-WebVTT nodes.
280     if (is<WebVTTElement>(element))
281         return false;
282 #endif
283
284 #if ENABLE(FULLSCREEN_API)
285     if (&element == m_document.webkitCurrentFullScreenElement() || &element == m_document.webkitCurrentFullScreenElement())
286         return false;
287 #endif
288     return true;
289 }
290
291 bool SharingResolver::styleSharingCandidateMatchesRuleSet(const StyledElement& element, const RuleSet* ruleSet) const
292 {
293     if (!ruleSet)
294         return false;
295
296     ElementRuleCollector collector(const_cast<StyledElement&>(element), m_ruleSets, &m_selectorFilter);
297     return collector.hasAnyMatchingRules(ruleSet);
298 }
299
300 bool SharingResolver::sharingCandidateHasIdenticalStyleAffectingAttributes(const Context& context, const StyledElement& sharingCandidate) const
301 {
302     auto& element = context.element;
303     if (element.elementData() == sharingCandidate.elementData())
304         return true;
305     if (element.fastGetAttribute(XMLNames::langAttr) != sharingCandidate.fastGetAttribute(XMLNames::langAttr))
306         return false;
307     if (element.fastGetAttribute(HTMLNames::langAttr) != sharingCandidate.fastGetAttribute(HTMLNames::langAttr))
308         return false;
309
310     if (context.elementAffectedByClassRules) {
311         if (!sharingCandidate.hasClass())
312             return false;
313         // SVG elements require a (slow!) getAttribute comparision because "class" is an animatable attribute for SVG.
314         if (element.isSVGElement()) {
315             if (element.getAttribute(HTMLNames::classAttr) != sharingCandidate.getAttribute(HTMLNames::classAttr))
316                 return false;
317         } else {
318             if (element.classNames() != sharingCandidate.classNames())
319                 return false;
320         }
321     } else if (sharingCandidate.hasClass() && classNamesAffectedByRules(sharingCandidate.classNames()))
322         return false;
323
324     if (const_cast<StyledElement&>(element).presentationAttributeStyle() != const_cast<StyledElement&>(sharingCandidate).presentationAttributeStyle())
325         return false;
326
327     if (element.hasTagName(HTMLNames::progressTag)) {
328         if (element.shouldAppearIndeterminate() != sharingCandidate.shouldAppearIndeterminate())
329             return false;
330     }
331
332     return true;
333 }
334
335 bool SharingResolver::classNamesAffectedByRules(const SpaceSplitString& classNames) const
336 {
337     for (unsigned i = 0; i < classNames.size(); ++i) {
338         if (m_ruleSets.features().classesInRules.contains(classNames[i].impl()))
339             return true;
340     }
341     return false;
342 }
343
344
345 }
346 }