1 | /* |
2 | * Copyright (C) 2018-2019 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. AND ITS CONTRIBUTORS ``AS IS'' |
14 | * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, |
15 | * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR |
16 | * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS |
17 | * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR |
18 | * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF |
19 | * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS |
20 | * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN |
21 | * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) |
22 | * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF |
23 | * THE POSSIBILITY OF SUCH DAMAGE. |
24 | */ |
25 | |
26 | #include "config.h" |
27 | #include "Quirks.h" |
28 | |
29 | #include "CustomHeaderFields.h" |
30 | #include "Document.h" |
31 | #include "DocumentLoader.h" |
32 | #include "HTMLMetaElement.h" |
33 | #include "HTMLObjectElement.h" |
34 | #include "LayoutUnit.h" |
35 | #include "Settings.h" |
36 | |
37 | namespace WebCore { |
38 | |
39 | static inline OptionSet<AutoplayQuirk> allowedAutoplayQuirks(Document& document) |
40 | { |
41 | auto* loader = document.loader(); |
42 | if (!loader) |
43 | return { }; |
44 | |
45 | return loader->allowedAutoplayQuirks(); |
46 | } |
47 | |
48 | Quirks::Quirks(Document& document) |
49 | : m_document(makeWeakPtr(document)) |
50 | { |
51 | } |
52 | |
53 | Quirks::~Quirks() = default; |
54 | |
55 | inline bool Quirks::needsQuirks() const |
56 | { |
57 | return m_document && m_document->settings().needsSiteSpecificQuirks(); |
58 | } |
59 | |
60 | bool Quirks::shouldIgnoreShrinkToFitContent() const |
61 | { |
62 | #if PLATFORM(IOS_FAMILY) |
63 | if (!needsQuirks()) |
64 | return false; |
65 | |
66 | auto host = m_document->topDocument().url().host(); |
67 | if (equalLettersIgnoringASCIICase(host, "outlook.live.com" )) |
68 | return true; |
69 | #endif |
70 | return false; |
71 | } |
72 | |
73 | Optional<LayoutUnit> Quirks::overriddenViewLayoutWidth(LayoutUnit currentViewLayoutWidth) const |
74 | { |
75 | #if PLATFORM(IOS_FAMILY) |
76 | if (!needsQuirks()) |
77 | return { }; |
78 | |
79 | auto host = m_document->topDocument().url().host(); |
80 | if (equalLettersIgnoringASCIICase(host, "outlook.live.com" )) { |
81 | if (currentViewLayoutWidth <= 989 || currentViewLayoutWidth >= 1132) |
82 | return { }; |
83 | return { 989 }; |
84 | } |
85 | #else |
86 | UNUSED_PARAM(currentViewLayoutWidth); |
87 | #endif |
88 | return { }; |
89 | } |
90 | |
91 | bool Quirks::shouldIgnoreInvalidSignal() const |
92 | { |
93 | if (!needsQuirks()) |
94 | return false; |
95 | |
96 | auto host = m_document->topDocument().url().host(); |
97 | return equalLettersIgnoringASCIICase(host, "www.thrivepatientportal.com" ); |
98 | } |
99 | |
100 | bool Quirks::needsFormControlToBeMouseFocusable() const |
101 | { |
102 | #if PLATFORM(MAC) |
103 | if (!needsQuirks()) |
104 | return false; |
105 | |
106 | auto host = m_document->url().host(); |
107 | return equalLettersIgnoringASCIICase(host, "ceac.state.gov" ) || host.endsWithIgnoringASCIICase(".ceac.state.gov" ); |
108 | #else |
109 | return false; |
110 | #endif |
111 | } |
112 | |
113 | bool Quirks::needsAutoplayPlayPauseEvents() const |
114 | { |
115 | if (!needsQuirks()) |
116 | return false; |
117 | |
118 | if (allowedAutoplayQuirks(*m_document).contains(AutoplayQuirk::SynthesizedPauseEvents)) |
119 | return true; |
120 | |
121 | return allowedAutoplayQuirks(m_document->topDocument()).contains(AutoplayQuirk::SynthesizedPauseEvents); |
122 | } |
123 | |
124 | bool Quirks::needsSeekingSupportDisabled() const |
125 | { |
126 | if (!needsQuirks()) |
127 | return false; |
128 | |
129 | auto host = m_document->topDocument().url().host(); |
130 | return equalLettersIgnoringASCIICase(host, "netflix.com" ) || host.endsWithIgnoringASCIICase(".netflix.com" ); |
131 | } |
132 | |
133 | bool Quirks::needsPerDocumentAutoplayBehavior() const |
134 | { |
135 | #if PLATFORM(MAC) |
136 | ASSERT(m_document == &m_document->topDocument()); |
137 | return needsQuirks() && allowedAutoplayQuirks(*m_document).contains(AutoplayQuirk::PerDocumentAutoplayBehavior); |
138 | #else |
139 | return false; |
140 | #endif |
141 | } |
142 | |
143 | bool Quirks::shouldAutoplayForArbitraryUserGesture() const |
144 | { |
145 | #if PLATFORM(MAC) |
146 | return needsQuirks() && allowedAutoplayQuirks(*m_document).contains(AutoplayQuirk::ArbitraryUserGestures); |
147 | #else |
148 | return false; |
149 | #endif |
150 | } |
151 | |
152 | bool Quirks::hasBrokenEncryptedMediaAPISupportQuirk() const |
153 | { |
154 | if (!needsQuirks()) |
155 | return false; |
156 | |
157 | if (m_hasBrokenEncryptedMediaAPISupportQuirk) |
158 | return m_hasBrokenEncryptedMediaAPISupportQuirk.value(); |
159 | |
160 | auto domain = m_document->securityOrigin().domain().convertToASCIILowercase(); |
161 | |
162 | m_hasBrokenEncryptedMediaAPISupportQuirk = domain == "starz.com" |
163 | || domain.endsWith(".starz.com" ) |
164 | || domain == "youtube.com" |
165 | || domain.endsWith(".youtube.com" ) |
166 | || domain == "hulu.com" |
167 | || domain.endsWith("hulu.com" ); |
168 | |
169 | return m_hasBrokenEncryptedMediaAPISupportQuirk.value(); |
170 | } |
171 | |
172 | bool Quirks::hasWebSQLSupportQuirk() const |
173 | { |
174 | if (!needsQuirks()) |
175 | return false; |
176 | |
177 | if (m_hasWebSQLSupportQuirk) |
178 | return m_hasWebSQLSupportQuirk.value(); |
179 | |
180 | auto domain = m_document->securityOrigin().domain().convertToASCIILowercase(); |
181 | |
182 | m_hasWebSQLSupportQuirk = domain == "bostonglobe.com" |
183 | || domain.endsWith(".bostonglobe.com" ) |
184 | || domain == "latimes.com" |
185 | || domain.endsWith(".latimes.com" ) |
186 | || domain == "washingtonpost.com" |
187 | || domain.endsWith(".washingtonpost.com" ) |
188 | || domain == "nytimes.com" |
189 | || domain.endsWith(".nytimes.com" ); |
190 | |
191 | return m_hasWebSQLSupportQuirk.value(); |
192 | } |
193 | |
194 | bool Quirks::isTouchBarUpdateSupressedForHiddenContentEditable() const |
195 | { |
196 | #if PLATFORM(MAC) |
197 | if (!needsQuirks()) |
198 | return false; |
199 | |
200 | auto host = m_document->topDocument().url().host(); |
201 | return equalLettersIgnoringASCIICase(host, "docs.google.com" ); |
202 | #else |
203 | return false; |
204 | #endif |
205 | } |
206 | |
207 | bool Quirks::isNeverRichlyEditableForTouchBar() const |
208 | { |
209 | #if PLATFORM(MAC) |
210 | if (!needsQuirks()) |
211 | return false; |
212 | |
213 | auto& url = m_document->topDocument().url(); |
214 | auto host = url.host(); |
215 | |
216 | if (equalLettersIgnoringASCIICase(host, "twitter.com" )) |
217 | return true; |
218 | |
219 | if (equalLettersIgnoringASCIICase(host, "onedrive.live.com" )) |
220 | return true; |
221 | |
222 | if (equalLettersIgnoringASCIICase(host, "trix-editor.org" )) |
223 | return true; |
224 | |
225 | if (equalLettersIgnoringASCIICase(host, "www.icloud.com" )) { |
226 | auto path = url.path(); |
227 | if (path.contains("notes" ) || url.fragmentIdentifier().contains("notes" )) |
228 | return true; |
229 | } |
230 | #endif |
231 | |
232 | return false; |
233 | } |
234 | |
235 | static bool shouldSuppressAutocorrectionAndAutocaptializationInHiddenEditableAreasForHost(const StringView& host) |
236 | { |
237 | #if PLATFORM(IOS_FAMILY) |
238 | return equalLettersIgnoringASCIICase(host, "docs.google.com" ); |
239 | #else |
240 | UNUSED_PARAM(host); |
241 | return false; |
242 | #endif |
243 | } |
244 | |
245 | bool Quirks::shouldDispatchSyntheticMouseEventsWhenModifyingSelection() const |
246 | { |
247 | if (m_document->settings().shouldDispatchSyntheticMouseEventsWhenModifyingSelection()) |
248 | return true; |
249 | |
250 | if (!needsQuirks()) |
251 | return false; |
252 | |
253 | auto host = m_document->topDocument().url().host(); |
254 | if (equalLettersIgnoringASCIICase(host, "medium.com" ) || host.endsWithIgnoringASCIICase(".medium.com" )) |
255 | return true; |
256 | |
257 | if (equalLettersIgnoringASCIICase(host, "weebly.com" ) || host.endsWithIgnoringASCIICase(".weebly.com" )) |
258 | return true; |
259 | |
260 | return false; |
261 | } |
262 | |
263 | bool Quirks::shouldSuppressAutocorrectionAndAutocaptializationInHiddenEditableAreas() const |
264 | { |
265 | if (!needsQuirks()) |
266 | return false; |
267 | |
268 | return shouldSuppressAutocorrectionAndAutocaptializationInHiddenEditableAreasForHost(m_document->topDocument().url().host()); |
269 | } |
270 | |
271 | #if ENABLE(TOUCH_EVENTS) |
272 | bool Quirks::shouldDispatchSimulatedMouseEvents() const |
273 | { |
274 | if (!needsQuirks()) |
275 | return false; |
276 | |
277 | auto* loader = m_document->loader(); |
278 | if (!loader || loader->simulatedMouseEventsDispatchPolicy() != SimulatedMouseEventsDispatchPolicy::Allow) |
279 | return false; |
280 | |
281 | auto& url = m_document->topDocument().url(); |
282 | auto host = url.host(); |
283 | |
284 | if (equalLettersIgnoringASCIICase(host, "amazon.com" ) || host.endsWithIgnoringASCIICase(".amazon.com" )) |
285 | return true; |
286 | if (equalLettersIgnoringASCIICase(host, "wix.com" ) || host.endsWithIgnoringASCIICase(".wix.com" )) |
287 | return true; |
288 | if ((equalLettersIgnoringASCIICase(host, "desmos.com" ) || host.endsWithIgnoringASCIICase(".desmos.com" )) && url.path().startsWithIgnoringASCIICase("/calculator/" )) |
289 | return true; |
290 | if (equalLettersIgnoringASCIICase(host, "figma.com" ) || host.endsWithIgnoringASCIICase(".figma.com" )) |
291 | return true; |
292 | if (equalLettersIgnoringASCIICase(host, "trello.com" ) || host.endsWithIgnoringASCIICase(".trello.com" )) |
293 | return true; |
294 | if (equalLettersIgnoringASCIICase(host, "airtable.com" ) || host.endsWithIgnoringASCIICase(".airtable.com" )) |
295 | return true; |
296 | if (equalLettersIgnoringASCIICase(host, "msn.com" ) || host.endsWithIgnoringASCIICase(".msn.com" )) |
297 | return true; |
298 | if (equalLettersIgnoringASCIICase(host, "flipkart.com" ) || host.endsWithIgnoringASCIICase(".flipkart.com" )) |
299 | return true; |
300 | if (equalLettersIgnoringASCIICase(host, "www.google.com" ) && url.path().startsWithIgnoringASCIICase("/maps/" )) |
301 | return true; |
302 | if (equalLettersIgnoringASCIICase(host, "trailers.apple.com" )) |
303 | return true; |
304 | if (equalLettersIgnoringASCIICase(host, "naver.com" ) || host.endsWithIgnoringASCIICase(".naver.com" )) |
305 | return true; |
306 | return false; |
307 | } |
308 | |
309 | bool Quirks::shouldDispatchSimulatedMouseEventsOnTarget(EventTarget* target) const |
310 | { |
311 | if (!needsQuirks() || !shouldDispatchSimulatedMouseEvents()) |
312 | return false; |
313 | |
314 | // On Google Maps, we want to limit simulated mouse events to dragging the little man that allows entering into Street View. |
315 | auto& url = m_document->topDocument().url(); |
316 | auto host = url.host(); |
317 | if (equalLettersIgnoringASCIICase(host, "www.google.com" ) && url.path().startsWithIgnoringASCIICase("/maps/" )) |
318 | return is<Element>(target) && downcast<Element>(target)->getAttribute("class" ) == "widget-expand-button-pegman-icon" ; |
319 | return true; |
320 | } |
321 | #endif |
322 | |
323 | bool Quirks::shouldDisablePointerEventsQuirk() const |
324 | { |
325 | #if PLATFORM(IOS_FAMILY) |
326 | if (!needsQuirks()) |
327 | return false; |
328 | |
329 | auto& url = m_document->topDocument().url(); |
330 | auto host = url.host(); |
331 | if (equalLettersIgnoringASCIICase(host, "mailchimp.com" ) || host.endsWithIgnoringASCIICase(".mailchimp.com" )) |
332 | return true; |
333 | #endif |
334 | return false; |
335 | } |
336 | |
337 | // FIXME(<rdar://problem/50394969>): Remove after desmos.com adopts inputmode="none". |
338 | bool Quirks::needsInputModeNoneImplicitly(const HTMLElement& element) const |
339 | { |
340 | #if PLATFORM(IOS_FAMILY) |
341 | if (!needsQuirks()) |
342 | return false; |
343 | |
344 | if (element.hasTagName(HTMLNames::inputTag)) { |
345 | if (!equalLettersIgnoringASCIICase(m_document->url().host(), "calendar.google.com" )) |
346 | return false; |
347 | static NeverDestroyed<QualifiedName> dataInitialValueAttr(nullAtom(), "data-initial-value" , nullAtom()); |
348 | static NeverDestroyed<QualifiedName> dataPreviousValueAttr(nullAtom(), "data-previous-value" , nullAtom()); |
349 | |
350 | return equalLettersIgnoringASCIICase(element.attributeWithoutSynchronization(HTMLNames::autocompleteAttr), "off" ) |
351 | && element.hasAttributeWithoutSynchronization(dataInitialValueAttr) |
352 | && element.hasAttributeWithoutSynchronization(dataPreviousValueAttr); |
353 | } |
354 | |
355 | if (!element.hasTagName(HTMLNames::textareaTag)) |
356 | return false; |
357 | |
358 | auto& url = m_document->url(); |
359 | auto host = url.host(); |
360 | if (!host.endsWithIgnoringASCIICase(".desmos.com" )) |
361 | return false; |
362 | |
363 | return element.parentElement() && element.parentElement()->classNames().contains("dcg-mq-textarea" ); |
364 | #else |
365 | UNUSED_PARAM(element); |
366 | return false; |
367 | #endif |
368 | } |
369 | |
370 | // FIXME: Remove after the site is fixed, <rdar://problem/50374200> |
371 | bool Quirks::needsGMailOverflowScrollQuirk() const |
372 | { |
373 | #if PLATFORM(IOS_FAMILY) |
374 | if (!needsQuirks()) |
375 | return false; |
376 | |
377 | if (!m_needsGMailOverflowScrollQuirk) |
378 | m_needsGMailOverflowScrollQuirk = equalLettersIgnoringASCIICase(m_document->url().host(), "mail.google.com" ); |
379 | |
380 | return *m_needsGMailOverflowScrollQuirk; |
381 | #else |
382 | return false; |
383 | #endif |
384 | } |
385 | |
386 | // FIXME: Remove after the site is fixed, <rdar://problem/50374311> |
387 | bool Quirks::needsYouTubeOverflowScrollQuirk() const |
388 | { |
389 | #if PLATFORM(IOS_FAMILY) |
390 | if (!needsQuirks()) |
391 | return false; |
392 | |
393 | if (!m_needsYouTubeOverflowScrollQuirk) |
394 | m_needsYouTubeOverflowScrollQuirk = equalLettersIgnoringASCIICase(m_document->url().host(), "www.youtube.com" ); |
395 | |
396 | return *m_needsYouTubeOverflowScrollQuirk; |
397 | #else |
398 | return false; |
399 | #endif |
400 | } |
401 | |
402 | |
403 | } |
404 | |