1/*
2 * Copyright (C) 2014-2016 Apple Inc. All rights reserved.
3 * Copyright (C) 2014 Dhi Aurrahman <diorahman@rockybars.com>
4 *
5 * Redistribution and use in source and binary forms, with or without
6 * modification, are permitted provided that the following conditions
7 * are met:
8 * 1. Redistributions of source code must retain the above copyright
9 * notice, this list of conditions and the following disclaimer.
10 * 2. Redistributions in binary form must reproduce the above copyright
11 * notice, this list of conditions and the following disclaimer in the
12 * documentation and/or other materials provided with the distribution.
13 *
14 * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS''
15 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
16 * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
17 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS
18 * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
19 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
20 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
21 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
22 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
23 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
24 * THE POSSIBILITY OF SUCH DAMAGE.
25 */
26
27#pragma once
28
29#include "FocusController.h"
30#include "FullscreenManager.h"
31#include "HTMLInputElement.h"
32#include "HTMLOptionElement.h"
33#include "RenderScrollbar.h"
34#include "ScrollableArea.h"
35#include "ScrollbarTheme.h"
36#include <wtf/Compiler.h>
37
38#if ENABLE(ATTACHMENT_ELEMENT)
39#include "HTMLAttachmentElement.h"
40#endif
41
42#if ENABLE(VIDEO_TRACK)
43#include "WebVTTElement.h"
44#endif
45
46namespace WebCore {
47
48ALWAYS_INLINE bool isAutofilled(const Element& element)
49{
50 return is<HTMLInputElement>(element) && downcast<HTMLInputElement>(element).isAutoFilled();
51}
52
53ALWAYS_INLINE bool isAutofilledStrongPassword(const Element& element)
54{
55 return is<HTMLInputElement>(element) && downcast<HTMLInputElement>(element).isAutoFilled() && downcast<HTMLInputElement>(element).hasAutoFillStrongPasswordButton();
56}
57
58ALWAYS_INLINE bool matchesDefaultPseudoClass(const Element& element)
59{
60 return element.matchesDefaultPseudoClass();
61}
62
63// https://html.spec.whatwg.org/multipage/scripting.html#selector-disabled
64ALWAYS_INLINE bool matchesDisabledPseudoClass(const Element& element)
65{
66 return is<HTMLElement>(element) && downcast<HTMLElement>(element).isActuallyDisabled();
67}
68
69// https://html.spec.whatwg.org/multipage/scripting.html#selector-enabled
70ALWAYS_INLINE bool matchesEnabledPseudoClass(const Element& element)
71{
72 return is<HTMLElement>(element) && downcast<HTMLElement>(element).canBeActuallyDisabled() && !element.isDisabledFormControl();
73}
74
75ALWAYS_INLINE bool isDefinedElement(const Element& element)
76{
77 return !element.isUndefinedCustomElement();
78}
79
80ALWAYS_INLINE bool isMediaDocument(const Element& element)
81{
82 return element.document().isMediaDocument();
83}
84
85ALWAYS_INLINE bool isChecked(const Element& element)
86{
87 // Even though WinIE allows checked and indeterminate to co-exist, the CSS selector spec says that
88 // you can't be both checked and indeterminate. We will behave like WinIE behind the scenes and just
89 // obey the CSS spec here in the test for matching the pseudo.
90 if (is<HTMLInputElement>(element)) {
91 auto& inputElement = downcast<HTMLInputElement>(element);
92 return inputElement.shouldAppearChecked() && !inputElement.shouldAppearIndeterminate();
93 }
94 if (is<HTMLOptionElement>(element))
95 return const_cast<HTMLOptionElement&>(downcast<HTMLOptionElement>(element)).selected();
96
97 return false;
98}
99
100ALWAYS_INLINE bool isInRange(const Element& element)
101{
102 return element.isInRange();
103}
104
105ALWAYS_INLINE bool isOutOfRange(const Element& element)
106{
107 return element.isOutOfRange();
108}
109
110ALWAYS_INLINE bool isInvalid(const Element& element)
111{
112 return element.matchesInvalidPseudoClass();
113}
114
115ALWAYS_INLINE bool isOptionalFormControl(const Element& element)
116{
117 return element.isOptionalFormControl();
118}
119
120ALWAYS_INLINE bool isRequiredFormControl(const Element& element)
121{
122 return element.isRequiredFormControl();
123}
124
125ALWAYS_INLINE bool isValid(const Element& element)
126{
127 return element.matchesValidPseudoClass();
128}
129
130ALWAYS_INLINE bool isWindowInactive(const Element& element)
131{
132 auto* page = element.document().page();
133 if (!page)
134 return false;
135 return !page->focusController().isActive();
136}
137
138#if ENABLE(ATTACHMENT_ELEMENT)
139ALWAYS_INLINE bool hasAttachment(const Element& element)
140{
141 return is<HTMLImageElement>(element) && downcast<HTMLImageElement>(element).attachmentElement();
142}
143#endif
144
145ALWAYS_INLINE bool containslanguageSubtagMatchingRange(StringView language, StringView range, unsigned languageLength, unsigned& position)
146{
147 unsigned languageSubtagsStartIndex = position;
148 unsigned languageSubtagsEndIndex = languageLength;
149 bool isAsteriskRange = range == "*";
150 do {
151 if (languageSubtagsStartIndex > 0)
152 languageSubtagsStartIndex += 1;
153
154 languageSubtagsEndIndex = std::min<unsigned>(language.find('-', languageSubtagsStartIndex), languageLength);
155
156 if (languageSubtagsStartIndex > languageSubtagsEndIndex)
157 return false;
158
159 StringView languageSubtag = language.substring(languageSubtagsStartIndex, languageSubtagsEndIndex - languageSubtagsStartIndex);
160 bool isEqual = equalIgnoringASCIICase(range, languageSubtag);
161 if (!isAsteriskRange) {
162 if ((!isEqual && !languageSubtagsStartIndex) || (languageSubtag.length() == 1 && languageSubtagsStartIndex > 0))
163 return false;
164 }
165 languageSubtagsStartIndex = languageSubtagsEndIndex;
166 if (isEqual || isAsteriskRange) {
167 position = languageSubtagsStartIndex;
168 return true;
169 }
170
171 } while (languageSubtagsStartIndex < languageLength);
172 return false;
173}
174
175ALWAYS_INLINE bool matchesLangPseudoClass(const Element& element, const Vector<AtomicString>& argumentList)
176{
177 AtomicString language;
178#if ENABLE(VIDEO_TRACK)
179 if (is<WebVTTElement>(element))
180 language = downcast<WebVTTElement>(element).language();
181 else
182#endif
183 language = element.computeInheritedLanguage();
184
185 if (language.isEmpty())
186 return false;
187
188 // Implement basic and extended filterings of given language tags
189 // as specified in www.ietf.org/rfc/rfc4647.txt.
190 StringView languageStringView = language.string();
191 unsigned languageLength = language.length();
192 for (const AtomicString& range : argumentList) {
193 if (range.isEmpty())
194 continue;
195
196 if (range == "*")
197 return true;
198
199 StringView rangeStringView = range.string();
200 if (equalIgnoringASCIICase(languageStringView, rangeStringView) && !languageStringView.contains('-'))
201 return true;
202
203 unsigned rangeLength = rangeStringView.length();
204 unsigned rangeSubtagsStartIndex = 0;
205 unsigned rangeSubtagsEndIndex = rangeLength;
206 unsigned lastMatchedLanguageSubtagIndex = 0;
207
208 bool matchedRange = true;
209 do {
210 if (rangeSubtagsStartIndex > 0)
211 rangeSubtagsStartIndex += 1;
212 if (rangeSubtagsStartIndex > languageLength)
213 return false;
214 rangeSubtagsEndIndex = std::min<unsigned>(rangeStringView.find('-', rangeSubtagsStartIndex), rangeLength);
215 StringView rangeSubtag = rangeStringView.substring(rangeSubtagsStartIndex, rangeSubtagsEndIndex - rangeSubtagsStartIndex);
216 if (!containslanguageSubtagMatchingRange(languageStringView, rangeSubtag, languageLength, lastMatchedLanguageSubtagIndex)) {
217 matchedRange = false;
218 break;
219 }
220 rangeSubtagsStartIndex = rangeSubtagsEndIndex;
221 } while (rangeSubtagsStartIndex < rangeLength);
222 if (matchedRange)
223 return true;
224 }
225 return false;
226}
227
228ALWAYS_INLINE bool matchesReadOnlyPseudoClass(const Element& element)
229{
230 return !element.matchesReadWritePseudoClass();
231}
232
233ALWAYS_INLINE bool matchesReadWritePseudoClass(const Element& element)
234{
235 return element.matchesReadWritePseudoClass();
236}
237
238ALWAYS_INLINE bool matchesIndeterminatePseudoClass(const Element& element)
239{
240 return element.matchesIndeterminatePseudoClass();
241}
242
243ALWAYS_INLINE bool scrollbarMatchesEnabledPseudoClass(const SelectorChecker::CheckingContext& context)
244{
245 return context.scrollbar && context.scrollbar->enabled();
246}
247
248ALWAYS_INLINE bool scrollbarMatchesDisabledPseudoClass(const SelectorChecker::CheckingContext& context)
249{
250 return context.scrollbar && !context.scrollbar->enabled();
251}
252
253ALWAYS_INLINE bool scrollbarMatchesHoverPseudoClass(const SelectorChecker::CheckingContext& context)
254{
255 if (!context.scrollbar)
256 return false;
257 ScrollbarPart hoveredPart = context.scrollbar->hoveredPart();
258 if (context.scrollbarPart == ScrollbarBGPart)
259 return hoveredPart != NoPart;
260 if (context.scrollbarPart == TrackBGPart)
261 return hoveredPart == BackTrackPart || hoveredPart == ForwardTrackPart || hoveredPart == ThumbPart;
262 return context.scrollbarPart == hoveredPart;
263}
264
265ALWAYS_INLINE bool scrollbarMatchesActivePseudoClass(const SelectorChecker::CheckingContext& context)
266{
267 if (!context.scrollbar)
268 return false;
269 ScrollbarPart pressedPart = context.scrollbar->pressedPart();
270 if (context.scrollbarPart == ScrollbarBGPart)
271 return pressedPart != NoPart;
272 if (context.scrollbarPart == TrackBGPart)
273 return pressedPart == BackTrackPart || pressedPart == ForwardTrackPart || pressedPart == ThumbPart;
274 return context.scrollbarPart == pressedPart;
275}
276
277ALWAYS_INLINE bool scrollbarMatchesHorizontalPseudoClass(const SelectorChecker::CheckingContext& context)
278{
279 return context.scrollbar && context.scrollbar->orientation() == HorizontalScrollbar;
280}
281
282ALWAYS_INLINE bool scrollbarMatchesVerticalPseudoClass(const SelectorChecker::CheckingContext& context)
283{
284 return context.scrollbar && context.scrollbar->orientation() == VerticalScrollbar;
285}
286
287ALWAYS_INLINE bool scrollbarMatchesDecrementPseudoClass(const SelectorChecker::CheckingContext& context)
288{
289 return context.scrollbarPart == BackButtonStartPart || context.scrollbarPart == BackButtonEndPart || context.scrollbarPart == BackTrackPart;
290}
291
292ALWAYS_INLINE bool scrollbarMatchesIncrementPseudoClass(const SelectorChecker::CheckingContext& context)
293{
294 return context.scrollbarPart == ForwardButtonStartPart || context.scrollbarPart == ForwardButtonEndPart || context.scrollbarPart == ForwardTrackPart;
295}
296
297ALWAYS_INLINE bool scrollbarMatchesStartPseudoClass(const SelectorChecker::CheckingContext& context)
298{
299 return context.scrollbarPart == BackButtonStartPart || context.scrollbarPart == ForwardButtonStartPart || context.scrollbarPart == BackTrackPart;
300}
301
302ALWAYS_INLINE bool scrollbarMatchesEndPseudoClass(const SelectorChecker::CheckingContext& context)
303{
304 return context.scrollbarPart == BackButtonEndPart || context.scrollbarPart == ForwardButtonEndPart || context.scrollbarPart == ForwardTrackPart;
305}
306
307ALWAYS_INLINE bool scrollbarMatchesDoubleButtonPseudoClass(const SelectorChecker::CheckingContext& context)
308{
309 if (!context.scrollbar)
310 return false;
311 ScrollbarButtonsPlacement buttonsPlacement = context.scrollbar->theme().buttonsPlacement();
312 if (context.scrollbarPart == BackButtonStartPart || context.scrollbarPart == ForwardButtonStartPart || context.scrollbarPart == BackTrackPart)
313 return buttonsPlacement == ScrollbarButtonsDoubleStart || buttonsPlacement == ScrollbarButtonsDoubleBoth;
314 if (context.scrollbarPart == BackButtonEndPart || context.scrollbarPart == ForwardButtonEndPart || context.scrollbarPart == ForwardTrackPart)
315 return buttonsPlacement == ScrollbarButtonsDoubleEnd || buttonsPlacement == ScrollbarButtonsDoubleBoth;
316 return false;
317}
318
319ALWAYS_INLINE bool scrollbarMatchesSingleButtonPseudoClass(const SelectorChecker::CheckingContext& context)
320{
321 if (!context.scrollbar)
322 return false;
323 ScrollbarButtonsPlacement buttonsPlacement = context.scrollbar->theme().buttonsPlacement();
324 if (context.scrollbarPart == BackButtonStartPart || context.scrollbarPart == ForwardButtonEndPart || context.scrollbarPart == BackTrackPart || context.scrollbarPart == ForwardTrackPart)
325 return buttonsPlacement == ScrollbarButtonsSingle;
326 return false;
327}
328
329ALWAYS_INLINE bool scrollbarMatchesNoButtonPseudoClass(const SelectorChecker::CheckingContext& context)
330{
331 if (!context.scrollbar)
332 return false;
333 ScrollbarButtonsPlacement buttonsPlacement = context.scrollbar->theme().buttonsPlacement();
334 if (context.scrollbarPart == BackTrackPart)
335 return buttonsPlacement == ScrollbarButtonsNone || buttonsPlacement == ScrollbarButtonsDoubleEnd;
336 if (context.scrollbarPart == ForwardTrackPart)
337 return buttonsPlacement == ScrollbarButtonsNone || buttonsPlacement == ScrollbarButtonsDoubleStart;
338 return false;
339}
340
341ALWAYS_INLINE bool scrollbarMatchesCornerPresentPseudoClass(const SelectorChecker::CheckingContext& context)
342{
343 return context.scrollbar && context.scrollbar->scrollableArea().isScrollCornerVisible();
344}
345
346#if ENABLE(FULLSCREEN_API)
347ALWAYS_INLINE bool matchesFullScreenPseudoClass(const Element& element)
348{
349 // While a Document is in the fullscreen state, and the document's current fullscreen
350 // element is an element in the document, the 'full-screen' pseudoclass applies to
351 // that element. Also, an <iframe>, <object> or <embed> element whose child browsing
352 // context's Document is in the fullscreen state has the 'full-screen' pseudoclass applied.
353 if (element.isFrameElementBase() && element.containsFullScreenElement())
354 return true;
355 if (!element.document().fullscreenManager().isFullscreen())
356 return false;
357 return &element == element.document().fullscreenManager().currentFullscreenElement();
358}
359
360ALWAYS_INLINE bool matchesFullScreenAnimatingFullScreenTransitionPseudoClass(const Element& element)
361{
362 if (&element != element.document().fullscreenManager().currentFullscreenElement())
363 return false;
364 return element.document().fullscreenManager().isAnimatingFullscreen();
365}
366
367ALWAYS_INLINE bool matchesFullScreenAncestorPseudoClass(const Element& element)
368{
369 return element.containsFullScreenElement();
370}
371
372ALWAYS_INLINE bool matchesFullScreenDocumentPseudoClass(const Element& element)
373{
374 // While a Document is in the fullscreen state, the 'full-screen-document' pseudoclass applies
375 // to all elements of that Document.
376 if (!element.document().fullscreenManager().isFullscreen())
377 return false;
378 return true;
379}
380
381ALWAYS_INLINE bool matchesFullScreenControlsHiddenPseudoClass(const Element& element)
382{
383 if (&element != element.document().fullscreenManager().currentFullscreenElement())
384 return false;
385 return element.document().fullscreenManager().areFullscreenControlsHidden();
386}
387#endif
388
389#if ENABLE(VIDEO_TRACK)
390ALWAYS_INLINE bool matchesFutureCuePseudoClass(const Element& element)
391{
392 return is<WebVTTElement>(element) && !downcast<WebVTTElement>(element).isPastNode();
393}
394
395ALWAYS_INLINE bool matchesPastCuePseudoClass(const Element& element)
396{
397 return is<WebVTTElement>(element) && downcast<WebVTTElement>(element).isPastNode();
398}
399#endif
400
401} // namespace WebCore
402