Separate render tree updating from style resolve
[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 "StyleUpdate.h"
37 #include "StyledElement.h"
38 #include "VisitedLinkState.h"
39 #include "WebVTTElement.h"
40 #include "XMLNames.h"
41
42 namespace WebCore {
43 namespace Style {
44
45 static const unsigned cStyleSearchThreshold = 10;
46
47 struct SharingResolver::Context {
48     const Update& update;
49     const StyledElement& element;
50     bool elementAffectedByClassRules;
51     EInsideLink elementLinkState;
52 };
53
54 SharingResolver::SharingResolver(const Document& document, const DocumentRuleSets& ruleSets, const SelectorFilter& selectorFilter)
55     : m_document(document)
56     , m_ruleSets(ruleSets)
57     , m_selectorFilter(selectorFilter)
58 {
59 }
60
61 static inline bool parentElementPreventsSharing(const Element& parentElement)
62 {
63     return parentElement.hasFlagsSetDuringStylingOfChildren();
64 }
65
66 static inline bool elementHasDirectionAuto(const Element& element)
67 {
68     // FIXME: This line is surprisingly hot, we may wish to inline hasDirectionAuto into StyleResolver.
69     return is<HTMLElement>(element) && downcast<HTMLElement>(element).hasDirectionAuto();
70 }
71
72 RefPtr<RenderStyle> SharingResolver::resolve(const Element& searchElement, const Update& update)
73 {
74     if (!is<StyledElement>(searchElement))
75         return nullptr;
76     auto& element = downcast<StyledElement>(searchElement);
77     if (!element.parentElement())
78         return nullptr;
79     auto& parentElement = *element.parentElement();
80     if (parentElement.shadowRoot())
81         return nullptr;
82     if (!update.elementStyle(parentElement))
83         return nullptr;
84     // If the element has inline style it is probably unique.
85     if (element.inlineStyle())
86         return nullptr;
87     if (element.isSVGElement() && downcast<SVGElement>(element).animatedSMILStyleProperties())
88         return nullptr;
89     // Ids stop style sharing if they show up in the stylesheets.
90     if (element.hasID() && m_ruleSets.features().idsInRules.contains(element.idForStyleResolution().impl()))
91         return nullptr;
92     if (parentElementPreventsSharing(parentElement))
93         return nullptr;
94     if (&element == m_document.cssTarget())
95         return nullptr;
96     if (elementHasDirectionAuto(element))
97         return nullptr;
98
99     Context context {
100         update,
101         element,
102         element.hasClass() && classNamesAffectedByRules(element.classNames()),
103         m_document.visitedLinkState().determineLinkState(element)
104     };
105
106     // Check previous siblings and their cousins.
107     unsigned count = 0;
108     StyledElement* shareElement = nullptr;
109     Node* cousinList = element.previousSibling();
110     while (cousinList) {
111         shareElement = findSibling(context, cousinList, count);
112         if (shareElement)
113             break;
114         cousinList = locateCousinList(cousinList->parentElement());
115     }
116
117     // If we have exhausted all our budget or our cousins.
118     if (!shareElement)
119         return nullptr;
120
121     // Can't share if sibling rules apply. This is checked at the end as it should rarely fail.
122     if (styleSharingCandidateMatchesRuleSet(element, m_ruleSets.sibling()))
123         return nullptr;
124     // Can't share if attribute rules apply.
125     if (styleSharingCandidateMatchesRuleSet(element, m_ruleSets.uncommonAttribute()))
126         return nullptr;
127     // Tracking child index requires unique style for each node. This may get set by the sibling rule match above.
128     if (parentElementPreventsSharing(parentElement))
129         return nullptr;
130
131     m_elementsSharingStyle.add(&element, shareElement);
132
133     return RenderStyle::clone(update.elementStyle(*shareElement));
134 }
135
136 StyledElement* SharingResolver::findSibling(const Context& context, Node* node, unsigned& count) const
137 {
138     for (; node; node = node->previousSibling()) {
139         if (!is<StyledElement>(*node))
140             continue;
141         if (canShareStyleWithElement(context, downcast<StyledElement>(*node)))
142             break;
143         if (count++ == cStyleSearchThreshold)
144             return nullptr;
145     }
146     return downcast<StyledElement>(node);
147 }
148
149 Node* SharingResolver::locateCousinList(const Element* parent) const
150 {
151     const unsigned maximumSearchCount = 10;
152     for (unsigned count = 0; count < maximumSearchCount; ++count) {
153         auto* elementSharingParentStyle = m_elementsSharingStyle.get(parent);
154         if (!elementSharingParentStyle)
155             return nullptr;
156         if (!parentElementPreventsSharing(*elementSharingParentStyle)) {
157             if (auto* cousin = elementSharingParentStyle->lastChild())
158                 return cousin;
159         }
160         parent = elementSharingParentStyle;
161     }
162
163     return nullptr;
164 }
165
166 static bool canShareStyleWithControl(const HTMLFormControlElement& element, const HTMLFormControlElement& formElement)
167 {
168     if (!is<HTMLInputElement>(formElement) || !is<HTMLInputElement>(element))
169         return false;
170
171     auto& thisInputElement = downcast<HTMLInputElement>(formElement);
172     auto& otherInputElement = downcast<HTMLInputElement>(element);
173
174     if (thisInputElement.isAutoFilled() != otherInputElement.isAutoFilled())
175         return false;
176     if (thisInputElement.shouldAppearChecked() != otherInputElement.shouldAppearChecked())
177         return false;
178     if (thisInputElement.shouldAppearIndeterminate() != otherInputElement.shouldAppearIndeterminate())
179         return false;
180     if (thisInputElement.isRequired() != otherInputElement.isRequired())
181         return false;
182
183     if (formElement.isDisabledFormControl() != element.isDisabledFormControl())
184         return false;
185
186     if (formElement.isDefaultButtonForForm() != element.isDefaultButtonForForm())
187         return false;
188
189     if (formElement.isInRange() != element.isInRange())
190         return false;
191
192     if (formElement.isOutOfRange() != element.isOutOfRange())
193         return false;
194
195     return true;
196 }
197
198 bool SharingResolver::canShareStyleWithElement(const Context& context, const StyledElement& candidateElement) const
199 {
200     auto& element = context.element;
201     auto* style = context.update.elementStyle(candidateElement);
202     if (!style)
203         return false;
204     if (style->unique())
205         return false;
206     if (style->hasUniquePseudoStyle())
207         return false;
208     if (candidateElement.tagQName() != element.tagQName())
209         return false;
210     if (candidateElement.inlineStyle())
211         return false;
212     if (candidateElement.needsStyleRecalc())
213         return false;
214     if (candidateElement.isSVGElement() && downcast<SVGElement>(candidateElement).animatedSMILStyleProperties())
215         return false;
216     if (candidateElement.isLink() != element.isLink())
217         return false;
218     if (candidateElement.hovered() != element.hovered())
219         return false;
220     if (candidateElement.active() != element.active())
221         return false;
222     if (candidateElement.focused() != element.focused())
223         return false;
224     if (candidateElement.shadowPseudoId() != element.shadowPseudoId())
225         return false;
226     if (&candidateElement == m_document.cssTarget())
227         return false;
228     if (!sharingCandidateHasIdenticalStyleAffectingAttributes(context, candidateElement))
229         return false;
230     if (const_cast<StyledElement&>(candidateElement).additionalPresentationAttributeStyle() != const_cast<StyledElement&>(element).additionalPresentationAttributeStyle())
231         return false;
232     if (candidateElement.affectsNextSiblingElementStyle() || candidateElement.styleIsAffectedByPreviousSibling())
233         return false;
234
235     if (candidateElement.hasID() && m_ruleSets.features().idsInRules.contains(candidateElement.idForStyleResolution().impl()))
236         return false;
237
238     bool isControl = is<HTMLFormControlElement>(candidateElement);
239
240     if (isControl != is<HTMLFormControlElement>(element))
241         return false;
242
243     if (isControl && !canShareStyleWithControl(downcast<HTMLFormControlElement>(element), downcast<HTMLFormControlElement>(candidateElement)))
244         return false;
245
246     if (style->transitions() || style->animations())
247         return false;
248
249     // Turn off style sharing for elements that can gain layers for reasons outside of the style system.
250     // See comments in RenderObject::setStyle().
251     if (candidateElement.hasTagName(HTMLNames::iframeTag) || candidateElement.hasTagName(HTMLNames::frameTag))
252         return false;
253
254     if (candidateElement.hasTagName(HTMLNames::embedTag) || candidateElement.hasTagName(HTMLNames::objectTag) || candidateElement.hasTagName(HTMLNames::appletTag) || candidateElement.hasTagName(HTMLNames::canvasTag))
255         return false;
256
257     if (elementHasDirectionAuto(candidateElement))
258         return false;
259
260     if (candidateElement.isLink() && context.elementLinkState != style->insideLink())
261         return false;
262
263     if (candidateElement.elementData() != element.elementData()) {
264         if (candidateElement.fastGetAttribute(HTMLNames::readonlyAttr) != element.fastGetAttribute(HTMLNames::readonlyAttr))
265             return false;
266         if (candidateElement.isSVGElement()) {
267             if (candidateElement.getAttribute(HTMLNames::typeAttr) != element.getAttribute(HTMLNames::typeAttr))
268                 return false;
269         } else {
270             if (candidateElement.fastGetAttribute(HTMLNames::typeAttr) != element.fastGetAttribute(HTMLNames::typeAttr))
271                 return false;
272         }
273     }
274
275     if (candidateElement.matchesValidPseudoClass() != element.matchesValidPseudoClass())
276         return false;
277
278     if (element.matchesInvalidPseudoClass() != element.matchesValidPseudoClass())
279         return false;
280
281 #if ENABLE(VIDEO_TRACK)
282     // Deny sharing styles between WebVTT and non-WebVTT nodes.
283     if (is<WebVTTElement>(element))
284         return false;
285 #endif
286
287 #if ENABLE(FULLSCREEN_API)
288     if (&element == m_document.webkitCurrentFullScreenElement() || &element == m_document.webkitCurrentFullScreenElement())
289         return false;
290 #endif
291     return true;
292 }
293
294 bool SharingResolver::styleSharingCandidateMatchesRuleSet(const StyledElement& element, const RuleSet* ruleSet) const
295 {
296     if (!ruleSet)
297         return false;
298
299     ElementRuleCollector collector(const_cast<StyledElement&>(element), m_ruleSets, &m_selectorFilter);
300     return collector.hasAnyMatchingRules(ruleSet);
301 }
302
303 bool SharingResolver::sharingCandidateHasIdenticalStyleAffectingAttributes(const Context& context, const StyledElement& sharingCandidate) const
304 {
305     auto& element = context.element;
306     if (element.elementData() == sharingCandidate.elementData())
307         return true;
308     if (element.fastGetAttribute(XMLNames::langAttr) != sharingCandidate.fastGetAttribute(XMLNames::langAttr))
309         return false;
310     if (element.fastGetAttribute(HTMLNames::langAttr) != sharingCandidate.fastGetAttribute(HTMLNames::langAttr))
311         return false;
312
313     if (context.elementAffectedByClassRules) {
314         if (!sharingCandidate.hasClass())
315             return false;
316         // SVG elements require a (slow!) getAttribute comparision because "class" is an animatable attribute for SVG.
317         if (element.isSVGElement()) {
318             if (element.getAttribute(HTMLNames::classAttr) != sharingCandidate.getAttribute(HTMLNames::classAttr))
319                 return false;
320         } else {
321             if (element.classNames() != sharingCandidate.classNames())
322                 return false;
323         }
324     } else if (sharingCandidate.hasClass() && classNamesAffectedByRules(sharingCandidate.classNames()))
325         return false;
326
327     if (const_cast<StyledElement&>(element).presentationAttributeStyle() != const_cast<StyledElement&>(sharingCandidate).presentationAttributeStyle())
328         return false;
329
330     if (element.hasTagName(HTMLNames::progressTag)) {
331         if (element.shouldAppearIndeterminate() != sharingCandidate.shouldAppearIndeterminate())
332             return false;
333     }
334
335     return true;
336 }
337
338 bool SharingResolver::classNamesAffectedByRules(const SpaceSplitString& classNames) const
339 {
340     for (unsigned i = 0; i < classNames.size(); ++i) {
341         if (m_ruleSets.features().classesInRules.contains(classNames[i].impl()))
342             return true;
343     }
344     return false;
345 }
346
347
348 }
349 }