Update std::expected to match libc++ coding style
[WebKit-https.git] / Source / WebCore / html / HTMLLinkElement.cpp
1 /*
2  * Copyright (C) 1999 Lars Knoll (knoll@kde.org)
3  *           (C) 1999 Antti Koivisto (koivisto@kde.org)
4  *           (C) 2001 Dirk Mueller (mueller@kde.org)
5  * Copyright (C) 2003-2017 Apple Inc. All rights reserved.
6  * Copyright (C) 2009 Rob Buis (rwlbuis@gmail.com)
7  * Copyright (C) 2011 Google Inc. All rights reserved.
8  *
9  * This library is free software; you can redistribute it and/or
10  * modify it under the terms of the GNU Library General Public
11  * License as published by the Free Software Foundation; either
12  * version 2 of the License, or (at your option) any later version.
13  *
14  * This library is distributed in the hope that it will be useful,
15  * but WITHOUT ANY WARRANTY; without even the implied warranty of
16  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
17  * Library General Public License for more details.
18  *
19  * You should have received a copy of the GNU Library General Public License
20  * along with this library; see the file COPYING.LIB.  If not, write to
21  * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
22  * Boston, MA 02110-1301, USA.
23  */
24
25 #include "config.h"
26 #include "HTMLLinkElement.h"
27
28 #include "Attribute.h"
29 #include "CachedCSSStyleSheet.h"
30 #include "CachedResource.h"
31 #include "CachedResourceLoader.h"
32 #include "CachedResourceRequest.h"
33 #include "ContentSecurityPolicy.h"
34 #include "DOMTokenList.h"
35 #include "Document.h"
36 #include "Event.h"
37 #include "EventNames.h"
38 #include "EventSender.h"
39 #include "Frame.h"
40 #include "FrameLoader.h"
41 #include "FrameLoaderClient.h"
42 #include "FrameTree.h"
43 #include "FrameView.h"
44 #include "HTMLAnchorElement.h"
45 #include "HTMLNames.h"
46 #include "HTMLParserIdioms.h"
47 #include "Logging.h"
48 #include "MediaList.h"
49 #include "MediaQueryEvaluator.h"
50 #include "MouseEvent.h"
51 #include "RenderStyle.h"
52 #include "RuntimeEnabledFeatures.h"
53 #include "SecurityOrigin.h"
54 #include "Settings.h"
55 #include "StyleInheritedData.h"
56 #include "StyleResolveForDocument.h"
57 #include "StyleScope.h"
58 #include "StyleSheetContents.h"
59 #include "SubresourceIntegrity.h"
60 #include <wtf/Ref.h>
61 #include <wtf/SetForScope.h>
62 #include <wtf/StdLibExtras.h>
63
64 namespace WebCore {
65
66 using namespace HTMLNames;
67
68 static LinkEventSender& linkLoadEventSender()
69 {
70     static NeverDestroyed<LinkEventSender> sharedLoadEventSender(eventNames().loadEvent);
71     return sharedLoadEventSender;
72 }
73
74 static LinkEventSender& linkErrorEventSender()
75 {
76     static NeverDestroyed<LinkEventSender> sharedErrorEventSender(eventNames().errorEvent);
77     return sharedErrorEventSender;
78 }
79
80 inline HTMLLinkElement::HTMLLinkElement(const QualifiedName& tagName, Document& document, bool createdByParser)
81     : HTMLElement(tagName, document)
82     , m_linkLoader(*this)
83     , m_disabledState(Unset)
84     , m_loading(false)
85     , m_createdByParser(createdByParser)
86     , m_firedLoad(false)
87     , m_loadedResource(false)
88     , m_pendingSheetType(Unknown)
89 {
90     ASSERT(hasTagName(linkTag));
91 }
92
93 Ref<HTMLLinkElement> HTMLLinkElement::create(const QualifiedName& tagName, Document& document, bool createdByParser)
94 {
95     return adoptRef(*new HTMLLinkElement(tagName, document, createdByParser));
96 }
97
98 HTMLLinkElement::~HTMLLinkElement()
99 {
100     if (m_sheet)
101         m_sheet->clearOwnerNode();
102
103     if (m_cachedSheet)
104         m_cachedSheet->removeClient(*this);
105
106     if (m_styleScope)
107         m_styleScope->removeStyleSheetCandidateNode(*this);
108
109     linkLoadEventSender().cancelEvent(*this);
110     linkErrorEventSender().cancelEvent(*this);
111 }
112
113 void HTMLLinkElement::setDisabledState(bool disabled)
114 {
115     DisabledState oldDisabledState = m_disabledState;
116     m_disabledState = disabled ? Disabled : EnabledViaScript;
117     if (oldDisabledState == m_disabledState)
118         return;
119
120     ASSERT(isConnected() || !styleSheetIsLoading());
121     if (!isConnected())
122         return;
123
124     // If we change the disabled state while the sheet is still loading, then we have to
125     // perform three checks:
126     if (styleSheetIsLoading()) {
127         // Check #1: The sheet becomes disabled while loading.
128         if (m_disabledState == Disabled)
129             removePendingSheet();
130
131         // Check #2: An alternate sheet becomes enabled while it is still loading.
132         if (m_relAttribute.isAlternate && m_disabledState == EnabledViaScript)
133             addPendingSheet(ActiveSheet);
134
135         // Check #3: A main sheet becomes enabled while it was still loading and
136         // after it was disabled via script. It takes really terrible code to make this
137         // happen (a double toggle for no reason essentially). This happens on
138         // virtualplastic.net, which manages to do about 12 enable/disables on only 3
139         // sheets. :)
140         if (!m_relAttribute.isAlternate && m_disabledState == EnabledViaScript && oldDisabledState == Disabled)
141             addPendingSheet(ActiveSheet);
142
143         // If the sheet is already loading just bail.
144         return;
145     }
146
147     // Load the sheet, since it's never been loaded before.
148     if (!m_sheet && m_disabledState == EnabledViaScript)
149         process();
150     else {
151         ASSERT(m_styleScope);
152         m_styleScope->didChangeActiveStyleSheetCandidates();
153     }
154 }
155
156 void HTMLLinkElement::parseAttribute(const QualifiedName& name, const AtomicString& value)
157 {
158     if (name == relAttr) {
159         m_relAttribute = LinkRelAttribute(document(), value);
160         if (m_relList)
161             m_relList->associatedAttributeValueChanged(value);
162         process();
163         return;
164     }
165     if (name == hrefAttr) {
166         bool wasLink = isLink();
167         setIsLink(!value.isNull() && !shouldProhibitLinks(this));
168         if (wasLink != isLink())
169             invalidateStyleForSubtree();
170         process();
171         return;
172     }
173     if (name == typeAttr) {
174         m_type = value;
175         process();
176         return;
177     }
178     if (name == sizesAttr) {
179         if (m_sizes)
180             m_sizes->associatedAttributeValueChanged(value);
181         process();
182         return;
183     }
184     if (name == mediaAttr) {
185         m_media = value.string().convertToASCIILowercase();
186         process();
187         if (m_sheet && !isDisabled())
188             m_styleScope->didChangeActiveStyleSheetCandidates();
189         return;
190     }
191     if (name == disabledAttr) {
192         setDisabledState(!value.isNull());
193         return;
194     }
195     if (name == titleAttr) {
196         if (m_sheet)
197             m_sheet->setTitle(value);
198         return;
199     }
200     HTMLElement::parseAttribute(name, value);
201 }
202
203 bool HTMLLinkElement::shouldLoadLink()
204 {
205     Ref<Document> originalDocument = document();
206     if (!dispatchBeforeLoadEvent(getNonEmptyURLAttribute(hrefAttr)))
207         return false;
208     // A beforeload handler might have removed us from the document or changed the document.
209     if (!isConnected() || &document() != originalDocument.ptr())
210         return false;
211     return true;
212 }
213
214 void HTMLLinkElement::setCrossOrigin(const AtomicString& value)
215 {
216     setAttributeWithoutSynchronization(crossoriginAttr, value);
217 }
218
219 String HTMLLinkElement::crossOrigin() const
220 {
221     return parseCORSSettingsAttribute(attributeWithoutSynchronization(crossoriginAttr));
222 }
223
224 void HTMLLinkElement::setAs(const AtomicString& value)
225 {
226     setAttributeWithoutSynchronization(asAttr, value);
227 }
228
229 String HTMLLinkElement::as() const
230 {
231     String as = attributeWithoutSynchronization(asAttr);
232     if (equalLettersIgnoringASCIICase(as, "fetch")
233         || equalLettersIgnoringASCIICase(as, "image")
234         || equalLettersIgnoringASCIICase(as, "script")
235         || equalLettersIgnoringASCIICase(as, "style")
236         || (RuntimeEnabledFeatures::sharedFeatures().mediaPreloadingEnabled()
237             && (equalLettersIgnoringASCIICase(as, "video")
238                 || equalLettersIgnoringASCIICase(as, "audio")))
239 #if ENABLE(VIDEO_TRACK)
240         || equalLettersIgnoringASCIICase(as, "track")
241 #endif
242         || equalLettersIgnoringASCIICase(as, "font"))
243         return as;
244     return String();
245 }
246
247 void HTMLLinkElement::process()
248 {
249     if (!isConnected()) {
250         ASSERT(!m_sheet);
251         return;
252     }
253
254     // Prevent recursive loading of link.
255     if (m_isHandlingBeforeLoad)
256         return;
257
258     URL url = getNonEmptyURLAttribute(hrefAttr);
259
260     if (!m_linkLoader.loadLink(m_relAttribute, url, attributeWithoutSynchronization(asAttr), attributeWithoutSynchronization(mediaAttr), attributeWithoutSynchronization(typeAttr), attributeWithoutSynchronization(crossoriginAttr), document()))
261         return;
262
263     bool treatAsStyleSheet = m_relAttribute.isStyleSheet
264         || (document().settings().treatsAnyTextCSSLinkAsStylesheet() && m_type.containsIgnoringASCIICase("text/css"));
265
266     if (m_disabledState != Disabled && treatAsStyleSheet && document().frame() && url.isValid()) {
267         String charset = attributeWithoutSynchronization(charsetAttr);
268         if (charset.isEmpty())
269             charset = document().charset();
270
271         if (m_cachedSheet) {
272             removePendingSheet();
273             m_cachedSheet->removeClient(*this);
274             m_cachedSheet = nullptr;
275         }
276
277         {
278         SetForScope<bool> change(m_isHandlingBeforeLoad, true);
279         if (!shouldLoadLink())
280             return;
281         }
282
283         m_loading = true;
284
285         bool mediaQueryMatches = true;
286         if (!m_media.isEmpty()) {
287             std::optional<RenderStyle> documentStyle;
288             if (document().hasLivingRenderTree())
289                 documentStyle = Style::resolveForDocument(document());
290             auto media = MediaQuerySet::create(m_media);
291             LOG(MediaQueries, "HTMLLinkElement::process evaluating queries");
292             mediaQueryMatches = MediaQueryEvaluator { document().frame()->view()->mediaType(), document(), documentStyle ? &*documentStyle : nullptr }.evaluate(media.get());
293         }
294
295         // Don't hold up render tree construction and script execution on stylesheets
296         // that are not needed for the rendering at the moment.
297         bool isActive = mediaQueryMatches && !isAlternate();
298         addPendingSheet(isActive ? ActiveSheet : InactiveSheet);
299
300         // Load stylesheets that are not needed for the rendering immediately with low priority.
301         std::optional<ResourceLoadPriority> priority;
302         if (!isActive)
303             priority = ResourceLoadPriority::VeryLow;
304
305         if (document().settings().subresourceIntegrityEnabled())
306             m_integrityMetadataForPendingSheetRequest = attributeWithoutSynchronization(HTMLNames::integrityAttr);
307
308         ResourceLoaderOptions options = CachedResourceLoader::defaultCachedResourceOptions();
309         options.sameOriginDataURLFlag = SameOriginDataURLFlag::Set;
310         if (document().contentSecurityPolicy()->allowStyleWithNonce(attributeWithoutSynchronization(HTMLNames::nonceAttr)))
311             options.contentSecurityPolicyImposition = ContentSecurityPolicyImposition::SkipPolicyCheck;
312
313         CachedResourceRequest request(url, options, priority, WTFMove(charset));
314         request.setInitiator(*this);
315         request.setAsPotentiallyCrossOrigin(crossOrigin(), document());
316
317         ASSERT_WITH_SECURITY_IMPLICATION(!m_cachedSheet);
318         m_cachedSheet = document().cachedResourceLoader().requestCSSStyleSheet(WTFMove(request)).value_or(nullptr);
319
320         if (m_cachedSheet)
321             m_cachedSheet->addClient(*this);
322         else {
323             // The request may have been denied if (for example) the stylesheet is local and the document is remote.
324             m_loading = false;
325             sheetLoaded();
326             notifyLoadedSheetAndAllCriticalSubresources(false);
327         }
328     } else if (m_sheet) {
329         // we no longer contain a stylesheet, e.g. perhaps rel or type was changed
330         clearSheet();
331         m_styleScope->didChangeActiveStyleSheetCandidates();
332     }
333 }
334
335 void HTMLLinkElement::clearSheet()
336 {
337     ASSERT(m_sheet);
338     ASSERT(m_sheet->ownerNode() == this);
339     m_sheet->clearOwnerNode();
340     m_sheet = nullptr;
341 }
342
343 Node::InsertedIntoAncestorResult HTMLLinkElement::insertedIntoAncestor(InsertionType insertionType, ContainerNode& parentOfInsertedTree)
344 {
345     HTMLElement::insertedIntoAncestor(insertionType, parentOfInsertedTree);
346     if (!insertionType.connectedToDocument)
347         return InsertedIntoAncestorResult::Done;
348
349     m_styleScope = &Style::Scope::forNode(*this);
350     m_styleScope->addStyleSheetCandidateNode(*this, m_createdByParser);
351
352     return InsertedIntoAncestorResult::NeedsPostInsertionCallback;
353 }
354
355 void HTMLLinkElement::didFinishInsertingNode()
356 {
357     process();
358 }
359
360 void HTMLLinkElement::removedFromAncestor(RemovalType removalType, ContainerNode& oldParentOfRemovedTree)
361 {
362     HTMLElement::removedFromAncestor(removalType, oldParentOfRemovedTree);
363     if (!removalType.disconnectedFromDocument)
364         return;
365
366     m_linkLoader.cancelLoad();
367
368     if (m_sheet)
369         clearSheet();
370
371     if (styleSheetIsLoading())
372         removePendingSheet();
373     
374     if (m_styleScope) {
375         m_styleScope->removeStyleSheetCandidateNode(*this);
376         m_styleScope = nullptr;
377     }
378 }
379
380 void HTMLLinkElement::finishParsingChildren()
381 {
382     m_createdByParser = false;
383     HTMLElement::finishParsingChildren();
384 }
385
386 void HTMLLinkElement::initializeStyleSheet(Ref<StyleSheetContents>&& styleSheet, const CachedCSSStyleSheet& cachedStyleSheet)
387 {
388     // FIXME: originClean should be turned to false except if fetch mode is CORS.
389     std::optional<bool> originClean;
390     if (cachedStyleSheet.options().mode == FetchOptions::Mode::Cors)
391         originClean = cachedStyleSheet.isCORSSameOrigin();
392
393     m_sheet = CSSStyleSheet::create(WTFMove(styleSheet), *this, originClean);
394     m_sheet->setMediaQueries(MediaQuerySet::create(m_media));
395     m_sheet->setTitle(title());
396 }
397
398 void HTMLLinkElement::setCSSStyleSheet(const String& href, const URL& baseURL, const String& charset, const CachedCSSStyleSheet* cachedStyleSheet)
399 {
400     if (!isConnected()) {
401         ASSERT(!m_sheet);
402         return;
403     }
404     auto frame = makeRefPtr(document().frame());
405     if (!frame)
406         return;
407
408     // Completing the sheet load may cause scripts to execute.
409     Ref<HTMLLinkElement> protectedThis(*this);
410
411     if (!cachedStyleSheet->errorOccurred() && !matchIntegrityMetadata(*cachedStyleSheet, m_integrityMetadataForPendingSheetRequest)) {
412         document().addConsoleMessage(MessageSource::Security, MessageLevel::Error, makeString("Cannot load stylesheet ", cachedStyleSheet->url().stringCenterEllipsizedToLength(), ". Failed integrity metadata check."));
413
414         m_loading = false;
415         sheetLoaded();
416         notifyLoadedSheetAndAllCriticalSubresources(true);
417         return;
418     }
419
420     CSSParserContext parserContext(document(), baseURL, charset);
421     auto cachePolicy = frame->loader().subresourceCachePolicy(baseURL);
422
423     if (auto restoredSheet = const_cast<CachedCSSStyleSheet*>(cachedStyleSheet)->restoreParsedStyleSheet(parserContext, cachePolicy, frame->loader())) {
424         ASSERT(restoredSheet->isCacheable());
425         ASSERT(!restoredSheet->isLoading());
426         initializeStyleSheet(restoredSheet.releaseNonNull(), *cachedStyleSheet);
427
428         m_loading = false;
429         sheetLoaded();
430         notifyLoadedSheetAndAllCriticalSubresources(false);
431         return;
432     }
433
434     auto styleSheet = StyleSheetContents::create(href, parserContext);
435     initializeStyleSheet(styleSheet.copyRef(), *cachedStyleSheet);
436
437     styleSheet.get().parseAuthorStyleSheet(cachedStyleSheet, &document().securityOrigin());
438
439     m_loading = false;
440     styleSheet.get().notifyLoadedSheet(cachedStyleSheet);
441     styleSheet.get().checkLoaded();
442
443     if (styleSheet.get().isCacheable())
444         const_cast<CachedCSSStyleSheet*>(cachedStyleSheet)->saveParsedStyleSheet(WTFMove(styleSheet));
445 }
446
447 bool HTMLLinkElement::styleSheetIsLoading() const
448 {
449     if (m_loading)
450         return true;
451     if (!m_sheet)
452         return false;
453     return m_sheet->contents().isLoading();
454 }
455
456 DOMTokenList& HTMLLinkElement::sizes()
457 {
458     if (!m_sizes)
459         m_sizes = std::make_unique<DOMTokenList>(*this, sizesAttr);
460     return *m_sizes;
461 }
462
463 void HTMLLinkElement::linkLoaded()
464 {
465     m_loadedResource = true;
466     linkLoadEventSender().dispatchEventSoon(*this);
467 }
468
469 void HTMLLinkElement::linkLoadingErrored()
470 {
471     linkErrorEventSender().dispatchEventSoon(*this);
472 }
473
474 bool HTMLLinkElement::sheetLoaded()
475 {
476     if (!styleSheetIsLoading()) {
477         removePendingSheet();
478         return true;
479     }
480     return false;
481 }
482
483 void HTMLLinkElement::dispatchPendingLoadEvents()
484 {
485     linkLoadEventSender().dispatchPendingEvents();
486 }
487
488 void HTMLLinkElement::dispatchPendingEvent(LinkEventSender* eventSender)
489 {
490     ASSERT_UNUSED(eventSender, eventSender == &linkLoadEventSender() || eventSender == &linkErrorEventSender());
491     if (m_loadedResource)
492         dispatchEvent(Event::create(eventNames().loadEvent, false, false));
493     else
494         dispatchEvent(Event::create(eventNames().errorEvent, false, false));
495 }
496
497 DOMTokenList& HTMLLinkElement::relList()
498 {
499     if (!m_relList) 
500         m_relList = std::make_unique<DOMTokenList>(*this, HTMLNames::relAttr, [](Document& document, StringView token) {
501             return LinkRelAttribute::isSupported(document, token);
502         });
503     return *m_relList;
504 }
505
506 void HTMLLinkElement::notifyLoadedSheetAndAllCriticalSubresources(bool errorOccurred)
507 {
508     if (m_firedLoad)
509         return;
510     m_loadedResource = !errorOccurred;
511     linkLoadEventSender().dispatchEventSoon(*this);
512     m_firedLoad = true;
513 }
514
515 void HTMLLinkElement::startLoadingDynamicSheet()
516 {
517     // We don't support multiple active sheets.
518     ASSERT(m_pendingSheetType < ActiveSheet);
519     addPendingSheet(ActiveSheet);
520 }
521
522 bool HTMLLinkElement::isURLAttribute(const Attribute& attribute) const
523 {
524     return attribute.name().localName() == hrefAttr || HTMLElement::isURLAttribute(attribute);
525 }
526
527 void HTMLLinkElement::defaultEventHandler(Event& event)
528 {
529     if (MouseEvent::canTriggerActivationBehavior(event)) {
530         handleClick(event);
531         return;
532     }
533     HTMLElement::defaultEventHandler(event);
534 }
535
536 void HTMLLinkElement::handleClick(Event& event)
537 {
538     event.setDefaultHandled();
539     URL url = href();
540     if (url.isNull())
541         return;
542     RefPtr<Frame> frame = document().frame();
543     if (!frame)
544         return;
545     frame->loader().urlSelected(url, target(), &event, LockHistory::No, LockBackForwardList::No, MaybeSendReferrer, document().shouldOpenExternalURLsPolicyToPropagate());
546 }
547
548 URL HTMLLinkElement::href() const
549 {
550     return document().completeURL(attributeWithoutSynchronization(hrefAttr));
551 }
552
553 const AtomicString& HTMLLinkElement::rel() const
554 {
555     return attributeWithoutSynchronization(relAttr);
556 }
557
558 String HTMLLinkElement::target() const
559 {
560     return attributeWithoutSynchronization(targetAttr);
561 }
562
563 const AtomicString& HTMLLinkElement::type() const
564 {
565     return attributeWithoutSynchronization(typeAttr);
566 }
567
568 std::optional<LinkIconType> HTMLLinkElement::iconType() const
569 {
570     return m_relAttribute.iconType;
571 }
572
573 void HTMLLinkElement::addSubresourceAttributeURLs(ListHashSet<URL>& urls) const
574 {
575     HTMLElement::addSubresourceAttributeURLs(urls);
576
577     // Favicons are handled by a special case in LegacyWebArchive::create()
578     if (m_relAttribute.iconType)
579         return;
580
581     if (!m_relAttribute.isStyleSheet)
582         return;
583     
584     // Append the URL of this link element.
585     addSubresourceURL(urls, href());
586
587     if (auto styleSheet = makeRefPtr(this->sheet())) {
588         styleSheet->contents().traverseSubresources([&] (auto& resource) {
589             urls.add(resource.url());
590             return false;
591         });
592     }
593 }
594
595 void HTMLLinkElement::addPendingSheet(PendingSheetType type)
596 {
597     if (type <= m_pendingSheetType)
598         return;
599     m_pendingSheetType = type;
600
601     if (m_pendingSheetType == InactiveSheet)
602         return;
603     ASSERT(m_styleScope);
604     m_styleScope->addPendingSheet(*this);
605 }
606
607 void HTMLLinkElement::removePendingSheet()
608 {
609     PendingSheetType type = m_pendingSheetType;
610     m_pendingSheetType = Unknown;
611
612     if (type == Unknown)
613         return;
614
615     ASSERT(m_styleScope);
616     if (type == InactiveSheet) {
617         // Document just needs to know about the sheet for exposure through document.styleSheets
618         m_styleScope->didChangeActiveStyleSheetCandidates();
619         return;
620     }
621
622     m_styleScope->removePendingSheet(*this);
623 }
624
625 } // namespace WebCore