Add a pseudoclass that matches img elements that are backed by an attachment
[WebKit-https.git] / Source / WebCore / css / SelectorCheckerTestFunctions.h
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 "HTMLInputElement.h"
31 #include "HTMLOptionElement.h"
32 #include "RenderScrollbar.h"
33 #include "ScrollableArea.h"
34 #include "ScrollbarTheme.h"
35 #include <wtf/Compiler.h>
36
37 #if ENABLE(ATTACHMENT_ELEMENT)
38 #include "HTMLAttachmentElement.h"
39 #endif
40
41 #if ENABLE(VIDEO_TRACK)
42 #include "WebVTTElement.h"
43 #endif
44
45 namespace WebCore {
46
47 ALWAYS_INLINE bool isAutofilled(const Element& element)
48 {
49     return is<HTMLInputElement>(element) && downcast<HTMLInputElement>(element).isAutoFilled();
50 }
51
52 ALWAYS_INLINE bool isAutofilledStrongPassword(const Element& element)
53 {
54     return is<HTMLInputElement>(element) && downcast<HTMLInputElement>(element).isAutoFilled() && downcast<HTMLInputElement>(element).hasAutoFillStrongPasswordButton();
55 }
56
57 ALWAYS_INLINE bool matchesDefaultPseudoClass(const Element& element)
58 {
59     return element.matchesDefaultPseudoClass();
60 }
61
62 // https://html.spec.whatwg.org/multipage/scripting.html#selector-disabled
63 ALWAYS_INLINE bool matchesDisabledPseudoClass(const Element& element)
64 {
65     return is<HTMLElement>(element) && downcast<HTMLElement>(element).isActuallyDisabled();
66 }
67
68 // https://html.spec.whatwg.org/multipage/scripting.html#selector-enabled
69 ALWAYS_INLINE bool matchesEnabledPseudoClass(const Element& element)
70 {
71     return is<HTMLElement>(element) && downcast<HTMLElement>(element).canBeActuallyDisabled() && !element.isDisabledFormControl();
72 }
73
74 ALWAYS_INLINE bool isDefinedElement(const Element& element)
75 {
76     return !element.isUndefinedCustomElement();
77 }
78
79 ALWAYS_INLINE bool isMediaDocument(const Element& element)
80 {
81     return element.document().isMediaDocument();
82 }
83
84 ALWAYS_INLINE bool isChecked(const Element& element)
85 {
86     // Even though WinIE allows checked and indeterminate to co-exist, the CSS selector spec says that
87     // you can't be both checked and indeterminate. We will behave like WinIE behind the scenes and just
88     // obey the CSS spec here in the test for matching the pseudo.
89     if (is<HTMLInputElement>(element)) {
90         auto& inputElement = downcast<HTMLInputElement>(element);
91         return inputElement.shouldAppearChecked() && !inputElement.shouldAppearIndeterminate();
92     }
93     if (is<HTMLOptionElement>(element))
94         return const_cast<HTMLOptionElement&>(downcast<HTMLOptionElement>(element)).selected();
95
96     return false;
97 }
98
99 ALWAYS_INLINE bool isInRange(const Element& element)
100 {
101     return element.isInRange();
102 }
103
104 ALWAYS_INLINE bool isOutOfRange(const Element& element)
105 {
106     return element.isOutOfRange();
107 }
108
109 ALWAYS_INLINE bool isInvalid(const Element& element)
110 {
111     return element.matchesInvalidPseudoClass();
112 }
113
114 ALWAYS_INLINE bool isOptionalFormControl(const Element& element)
115 {
116     return element.isOptionalFormControl();
117 }
118
119 ALWAYS_INLINE bool isRequiredFormControl(const Element& element)
120 {
121     return element.isRequiredFormControl();
122 }
123
124 ALWAYS_INLINE bool isValid(const Element& element)
125 {
126     return element.matchesValidPseudoClass();
127 }
128
129 ALWAYS_INLINE bool isWindowInactive(const Element& element)
130 {
131     auto* page = element.document().page();
132     if (!page)
133         return false;
134     return !page->focusController().isActive();
135 }
136
137 #if ENABLE(ATTACHMENT_ELEMENT)
138 ALWAYS_INLINE bool hasAttachment(const Element& element)
139 {
140     return is<HTMLImageElement>(element) && downcast<HTMLImageElement>(element).attachmentElement();
141 }
142 #endif
143
144 ALWAYS_INLINE bool containslanguageSubtagMatchingRange(StringView language, StringView range, unsigned languageLength, unsigned& position)
145 {
146     unsigned languageSubtagsStartIndex = position;
147     unsigned languageSubtagsEndIndex = languageLength;
148     bool isAsteriskRange = range == "*";
149     do {
150         if (languageSubtagsStartIndex > 0)
151             languageSubtagsStartIndex += 1;
152         
153         languageSubtagsEndIndex = std::min<unsigned>(language.find('-', languageSubtagsStartIndex), languageLength);
154
155         if (languageSubtagsStartIndex > languageSubtagsEndIndex)
156             return false;
157
158         StringView languageSubtag = language.substring(languageSubtagsStartIndex, languageSubtagsEndIndex - languageSubtagsStartIndex);
159         bool isEqual = equalIgnoringASCIICase(range, languageSubtag);
160         if (!isAsteriskRange) {
161             if ((!isEqual && !languageSubtagsStartIndex) || (languageSubtag.length() == 1 && languageSubtagsStartIndex > 0))
162                 return false;
163         }
164         languageSubtagsStartIndex = languageSubtagsEndIndex;
165         if (isEqual || isAsteriskRange) {
166             position = languageSubtagsStartIndex;
167             return true;
168         }
169
170     } while (languageSubtagsStartIndex < languageLength);
171     return false;
172 }
173
174 ALWAYS_INLINE bool matchesLangPseudoClass(const Element& element, const Vector<AtomicString>& argumentList)
175 {
176     AtomicString language;
177 #if ENABLE(VIDEO_TRACK)
178     if (is<WebVTTElement>(element))
179         language = downcast<WebVTTElement>(element).language();
180     else
181 #endif
182         language = element.computeInheritedLanguage();
183
184     if (language.isEmpty())
185         return false;
186
187     // Implement basic and extended filterings of given language tags
188     // as specified in www.ietf.org/rfc/rfc4647.txt.
189     StringView languageStringView = language.string();
190     unsigned languageLength = language.length();
191     for (const AtomicString& range : argumentList) {
192         if (range.isEmpty())
193             continue;
194
195         if (range == "*")
196             return true;
197
198         StringView rangeStringView = range.string();
199         if (equalIgnoringASCIICase(languageStringView, rangeStringView) && !languageStringView.contains('-'))
200             return true;
201         
202         unsigned rangeLength = rangeStringView.length();
203         unsigned rangeSubtagsStartIndex = 0;
204         unsigned rangeSubtagsEndIndex = rangeLength;
205         unsigned lastMatchedLanguageSubtagIndex = 0;
206
207         bool matchedRange = true;
208         do {
209             if (rangeSubtagsStartIndex > 0)
210                 rangeSubtagsStartIndex += 1;
211             if (rangeSubtagsStartIndex > languageLength)
212                 return false;
213             rangeSubtagsEndIndex = std::min<unsigned>(rangeStringView.find('-', rangeSubtagsStartIndex), rangeLength);
214             StringView rangeSubtag = rangeStringView.substring(rangeSubtagsStartIndex, rangeSubtagsEndIndex - rangeSubtagsStartIndex);
215             if (!containslanguageSubtagMatchingRange(languageStringView, rangeSubtag, languageLength, lastMatchedLanguageSubtagIndex)) {
216                 matchedRange = false;
217                 break;
218             }
219             rangeSubtagsStartIndex = rangeSubtagsEndIndex;
220         } while (rangeSubtagsStartIndex < rangeLength);
221         if (matchedRange)
222             return true;
223     }
224     return false;
225 }
226
227 ALWAYS_INLINE bool matchesReadOnlyPseudoClass(const Element& element)
228 {
229     return !element.matchesReadWritePseudoClass();
230 }
231
232 ALWAYS_INLINE bool matchesReadWritePseudoClass(const Element& element)
233 {
234     return element.matchesReadWritePseudoClass();
235 }
236
237 ALWAYS_INLINE bool matchesIndeterminatePseudoClass(const Element& element)
238 {
239     return element.matchesIndeterminatePseudoClass();
240 }
241
242 ALWAYS_INLINE bool scrollbarMatchesEnabledPseudoClass(const SelectorChecker::CheckingContext& context)
243 {
244     return context.scrollbar && context.scrollbar->enabled();
245 }
246
247 ALWAYS_INLINE bool scrollbarMatchesDisabledPseudoClass(const SelectorChecker::CheckingContext& context)
248 {
249     return context.scrollbar && !context.scrollbar->enabled();
250 }
251
252 ALWAYS_INLINE bool scrollbarMatchesHoverPseudoClass(const SelectorChecker::CheckingContext& context)
253 {
254     if (!context.scrollbar)
255         return false;
256     ScrollbarPart hoveredPart = context.scrollbar->hoveredPart();
257     if (context.scrollbarPart == ScrollbarBGPart)
258         return hoveredPart != NoPart;
259     if (context.scrollbarPart == TrackBGPart)
260         return hoveredPart == BackTrackPart || hoveredPart == ForwardTrackPart || hoveredPart == ThumbPart;
261     return context.scrollbarPart == hoveredPart;
262 }
263
264 ALWAYS_INLINE bool scrollbarMatchesActivePseudoClass(const SelectorChecker::CheckingContext& context)
265 {
266     if (!context.scrollbar)
267         return false;
268     ScrollbarPart pressedPart = context.scrollbar->pressedPart();
269     if (context.scrollbarPart == ScrollbarBGPart)
270         return pressedPart != NoPart;
271     if (context.scrollbarPart == TrackBGPart)
272         return pressedPart == BackTrackPart || pressedPart == ForwardTrackPart || pressedPart == ThumbPart;
273     return context.scrollbarPart == pressedPart;
274 }
275
276 ALWAYS_INLINE bool scrollbarMatchesHorizontalPseudoClass(const SelectorChecker::CheckingContext& context)
277 {
278     return context.scrollbar && context.scrollbar->orientation() == HorizontalScrollbar;
279 }
280
281 ALWAYS_INLINE bool scrollbarMatchesVerticalPseudoClass(const SelectorChecker::CheckingContext& context)
282 {
283     return context.scrollbar && context.scrollbar->orientation() == VerticalScrollbar;
284 }
285
286 ALWAYS_INLINE bool scrollbarMatchesDecrementPseudoClass(const SelectorChecker::CheckingContext& context)
287 {
288     return context.scrollbarPart == BackButtonStartPart || context.scrollbarPart == BackButtonEndPart || context.scrollbarPart == BackTrackPart;
289 }
290
291 ALWAYS_INLINE bool scrollbarMatchesIncrementPseudoClass(const SelectorChecker::CheckingContext& context)
292 {
293     return context.scrollbarPart == ForwardButtonStartPart || context.scrollbarPart == ForwardButtonEndPart || context.scrollbarPart == ForwardTrackPart;
294 }
295
296 ALWAYS_INLINE bool scrollbarMatchesStartPseudoClass(const SelectorChecker::CheckingContext& context)
297 {
298     return context.scrollbarPart == BackButtonStartPart || context.scrollbarPart == ForwardButtonStartPart || context.scrollbarPart == BackTrackPart;
299 }
300
301 ALWAYS_INLINE bool scrollbarMatchesEndPseudoClass(const SelectorChecker::CheckingContext& context)
302 {
303     return context.scrollbarPart == BackButtonEndPart || context.scrollbarPart == ForwardButtonEndPart || context.scrollbarPart == ForwardTrackPart;
304 }
305
306 ALWAYS_INLINE bool scrollbarMatchesDoubleButtonPseudoClass(const SelectorChecker::CheckingContext& context)
307 {
308     if (!context.scrollbar)
309         return false;
310     ScrollbarButtonsPlacement buttonsPlacement = context.scrollbar->theme().buttonsPlacement();
311     if (context.scrollbarPart == BackButtonStartPart || context.scrollbarPart == ForwardButtonStartPart || context.scrollbarPart == BackTrackPart)
312         return buttonsPlacement == ScrollbarButtonsDoubleStart || buttonsPlacement == ScrollbarButtonsDoubleBoth;
313     if (context.scrollbarPart == BackButtonEndPart || context.scrollbarPart == ForwardButtonEndPart || context.scrollbarPart == ForwardTrackPart)
314         return buttonsPlacement == ScrollbarButtonsDoubleEnd || buttonsPlacement == ScrollbarButtonsDoubleBoth;
315     return false;
316 }
317
318 ALWAYS_INLINE bool scrollbarMatchesSingleButtonPseudoClass(const SelectorChecker::CheckingContext& context)
319 {
320     if (!context.scrollbar)
321         return false;
322     ScrollbarButtonsPlacement buttonsPlacement = context.scrollbar->theme().buttonsPlacement();
323     if (context.scrollbarPart == BackButtonStartPart || context.scrollbarPart == ForwardButtonEndPart || context.scrollbarPart == BackTrackPart || context.scrollbarPart == ForwardTrackPart)
324         return buttonsPlacement == ScrollbarButtonsSingle;
325     return false;
326 }
327
328 ALWAYS_INLINE bool scrollbarMatchesNoButtonPseudoClass(const SelectorChecker::CheckingContext& context)
329 {
330     if (!context.scrollbar)
331         return false;
332     ScrollbarButtonsPlacement buttonsPlacement = context.scrollbar->theme().buttonsPlacement();
333     if (context.scrollbarPart == BackTrackPart)
334         return buttonsPlacement == ScrollbarButtonsNone || buttonsPlacement == ScrollbarButtonsDoubleEnd;
335     if (context.scrollbarPart == ForwardTrackPart)
336         return buttonsPlacement == ScrollbarButtonsNone || buttonsPlacement == ScrollbarButtonsDoubleStart;
337     return false;
338 }
339
340 ALWAYS_INLINE bool scrollbarMatchesCornerPresentPseudoClass(const SelectorChecker::CheckingContext& context)
341 {
342     return context.scrollbar && context.scrollbar->scrollableArea().isScrollCornerVisible();
343 }
344
345 #if ENABLE(FULLSCREEN_API)
346 ALWAYS_INLINE bool matchesFullScreenPseudoClass(const Element& element)
347 {
348     // While a Document is in the fullscreen state, and the document's current fullscreen
349     // element is an element in the document, the 'full-screen' pseudoclass applies to
350     // that element. Also, an <iframe>, <object> or <embed> element whose child browsing
351     // context's Document is in the fullscreen state has the 'full-screen' pseudoclass applied.
352     if (element.isFrameElementBase() && element.containsFullScreenElement())
353         return true;
354     if (!element.document().webkitIsFullScreen())
355         return false;
356     return &element == element.document().webkitCurrentFullScreenElement();
357 }
358
359 ALWAYS_INLINE bool matchesFullScreenAnimatingFullScreenTransitionPseudoClass(const Element& element)
360 {
361     if (&element != element.document().webkitCurrentFullScreenElement())
362         return false;
363     return element.document().isAnimatingFullScreen();
364 }
365
366 ALWAYS_INLINE bool matchesFullScreenAncestorPseudoClass(const Element& element)
367 {
368     return element.containsFullScreenElement();
369 }
370
371 ALWAYS_INLINE bool matchesFullScreenDocumentPseudoClass(const Element& element)
372 {
373     // While a Document is in the fullscreen state, the 'full-screen-document' pseudoclass applies
374     // to all elements of that Document.
375     if (!element.document().webkitIsFullScreen())
376         return false;
377     return true;
378 }
379
380 ALWAYS_INLINE bool matchesFullScreenControlsHiddenPseudoClass(const Element& element)
381 {
382     if (&element != element.document().webkitCurrentFullScreenElement())
383         return false;
384     return element.document().areFullscreenControlsHidden();
385 }
386 #endif
387
388 #if ENABLE(VIDEO_TRACK)
389 ALWAYS_INLINE bool matchesFutureCuePseudoClass(const Element& element)
390 {
391     return is<WebVTTElement>(element) && !downcast<WebVTTElement>(element).isPastNode();
392 }
393
394 ALWAYS_INLINE bool matchesPastCuePseudoClass(const Element& element)
395 {
396     return is<WebVTTElement>(element) && downcast<WebVTTElement>(element).isPastNode();
397 }
398 #endif
399
400 } // namespace WebCore