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