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
37namespace WebCore {
38
39static inline OptionSet<AutoplayQuirk> allowedAutoplayQuirks(Document& document)
40{
41 auto* loader = document.loader();
42 if (!loader)
43 return { };
44
45 return loader->allowedAutoplayQuirks();
46}
47
48Quirks::Quirks(Document& document)
49 : m_document(makeWeakPtr(document))
50{
51}
52
53Quirks::~Quirks() = default;
54
55inline bool Quirks::needsQuirks() const
56{
57 return m_document && m_document->settings().needsSiteSpecificQuirks();
58}
59
60bool 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
73Optional<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
91bool 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
100bool 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
113bool 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
124bool 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
133bool 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
143bool 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
152bool 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
172bool 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
194bool 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
207bool 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
235static 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
245bool 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
263bool 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)
272bool 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
309bool 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
323bool 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".
338bool 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>
371bool 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>
387bool 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