8404e3463a2df0c81fc5bf8bf99e3840afed523e
[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     bool wasLoading = styleSheetIsLoading();
369
370     if (m_sheet)
371         clearSheet();
372
373     if (wasLoading)
374         removePendingSheet();
375     
376     if (m_styleScope) {
377         m_styleScope->removeStyleSheetCandidateNode(*this);
378         m_styleScope = nullptr;
379     }
380 }
381
382 void HTMLLinkElement::finishParsingChildren()
383 {
384     m_createdByParser = false;
385     HTMLElement::finishParsingChildren();
386 }
387
388 void HTMLLinkElement::initializeStyleSheet(Ref<StyleSheetContents>&& styleSheet, const CachedCSSStyleSheet& cachedStyleSheet)
389 {
390     // FIXME: originClean should be turned to false except if fetch mode is CORS.
391     std::optional<bool> originClean;
392     if (cachedStyleSheet.options().mode == FetchOptions::Mode::Cors)
393         originClean = cachedStyleSheet.isCORSSameOrigin();
394
395     m_sheet = CSSStyleSheet::create(WTFMove(styleSheet), *this, originClean);
396     m_sheet->setMediaQueries(MediaQuerySet::create(m_media));
397     m_sheet->setTitle(title());
398 }
399
400 void HTMLLinkElement::setCSSStyleSheet(const String& href, const URL& baseURL, const String& charset, const CachedCSSStyleSheet* cachedStyleSheet)
401 {
402     if (!isConnected()) {
403         ASSERT(!m_sheet);
404         return;
405     }
406     auto frame = makeRefPtr(document().frame());
407     if (!frame)
408         return;
409
410     // Completing the sheet load may cause scripts to execute.
411     Ref<HTMLLinkElement> protectedThis(*this);
412
413     if (!cachedStyleSheet->errorOccurred() && !matchIntegrityMetadata(*cachedStyleSheet, m_integrityMetadataForPendingSheetRequest)) {
414         document().addConsoleMessage(MessageSource::Security, MessageLevel::Error, makeString("Cannot load stylesheet ", cachedStyleSheet->url().stringCenterEllipsizedToLength(), ". Failed integrity metadata check."));
415
416         m_loading = false;
417         sheetLoaded();
418         notifyLoadedSheetAndAllCriticalSubresources(true);
419         return;
420     }
421
422     CSSParserContext parserContext(document(), baseURL, charset);
423     auto cachePolicy = frame->loader().subresourceCachePolicy(baseURL);
424
425     if (auto restoredSheet = const_cast<CachedCSSStyleSheet*>(cachedStyleSheet)->restoreParsedStyleSheet(parserContext, cachePolicy, frame->loader())) {
426         ASSERT(restoredSheet->isCacheable());
427         ASSERT(!restoredSheet->isLoading());
428         initializeStyleSheet(restoredSheet.releaseNonNull(), *cachedStyleSheet);
429
430         m_loading = false;
431         sheetLoaded();
432         notifyLoadedSheetAndAllCriticalSubresources(false);
433         return;
434     }
435
436     auto styleSheet = StyleSheetContents::create(href, parserContext);
437     initializeStyleSheet(styleSheet.copyRef(), *cachedStyleSheet);
438
439     styleSheet.get().parseAuthorStyleSheet(cachedStyleSheet, &document().securityOrigin());
440
441     m_loading = false;
442     styleSheet.get().notifyLoadedSheet(cachedStyleSheet);
443     styleSheet.get().checkLoaded();
444
445     if (styleSheet.get().isCacheable())
446         const_cast<CachedCSSStyleSheet*>(cachedStyleSheet)->saveParsedStyleSheet(WTFMove(styleSheet));
447 }
448
449 bool HTMLLinkElement::styleSheetIsLoading() const
450 {
451     if (m_loading)
452         return true;
453     if (!m_sheet)
454         return false;
455     return m_sheet->contents().isLoading();
456 }
457
458 DOMTokenList& HTMLLinkElement::sizes()
459 {
460     if (!m_sizes)
461         m_sizes = std::make_unique<DOMTokenList>(*this, sizesAttr);
462     return *m_sizes;
463 }
464
465 void HTMLLinkElement::linkLoaded()
466 {
467     m_loadedResource = true;
468     linkLoadEventSender().dispatchEventSoon(*this);
469 }
470
471 void HTMLLinkElement::linkLoadingErrored()
472 {
473     linkErrorEventSender().dispatchEventSoon(*this);
474 }
475
476 bool HTMLLinkElement::sheetLoaded()
477 {
478     if (!styleSheetIsLoading()) {
479         removePendingSheet();
480         return true;
481     }
482     return false;
483 }
484
485 void HTMLLinkElement::dispatchPendingLoadEvents()
486 {
487     linkLoadEventSender().dispatchPendingEvents();
488 }
489
490 void HTMLLinkElement::dispatchPendingEvent(LinkEventSender* eventSender)
491 {
492     ASSERT_UNUSED(eventSender, eventSender == &linkLoadEventSender() || eventSender == &linkErrorEventSender());
493     if (m_loadedResource)
494         dispatchEvent(Event::create(eventNames().loadEvent, false, false));
495     else
496         dispatchEvent(Event::create(eventNames().errorEvent, false, false));
497 }
498
499 DOMTokenList& HTMLLinkElement::relList()
500 {
501     if (!m_relList) 
502         m_relList = std::make_unique<DOMTokenList>(*this, HTMLNames::relAttr, [](Document& document, StringView token) {
503             return LinkRelAttribute::isSupported(document, token);
504         });
505     return *m_relList;
506 }
507
508 void HTMLLinkElement::notifyLoadedSheetAndAllCriticalSubresources(bool errorOccurred)
509 {
510     if (m_firedLoad)
511         return;
512     m_loadedResource = !errorOccurred;
513     linkLoadEventSender().dispatchEventSoon(*this);
514     m_firedLoad = true;
515 }
516
517 void HTMLLinkElement::startLoadingDynamicSheet()
518 {
519     // We don't support multiple active sheets.
520     ASSERT(m_pendingSheetType < ActiveSheet);
521     addPendingSheet(ActiveSheet);
522 }
523
524 bool HTMLLinkElement::isURLAttribute(const Attribute& attribute) const
525 {
526     return attribute.name().localName() == hrefAttr || HTMLElement::isURLAttribute(attribute);
527 }
528
529 void HTMLLinkElement::defaultEventHandler(Event& event)
530 {
531     if (MouseEvent::canTriggerActivationBehavior(event)) {
532         handleClick(event);
533         return;
534     }
535     HTMLElement::defaultEventHandler(event);
536 }
537
538 void HTMLLinkElement::handleClick(Event& event)
539 {
540     event.setDefaultHandled();
541     URL url = href();
542     if (url.isNull())
543         return;
544     RefPtr<Frame> frame = document().frame();
545     if (!frame)
546         return;
547     frame->loader().urlSelected(url, target(), &event, LockHistory::No, LockBackForwardList::No, MaybeSendReferrer, document().shouldOpenExternalURLsPolicyToPropagate());
548 }
549
550 URL HTMLLinkElement::href() const
551 {
552     return document().completeURL(attributeWithoutSynchronization(hrefAttr));
553 }
554
555 const AtomicString& HTMLLinkElement::rel() const
556 {
557     return attributeWithoutSynchronization(relAttr);
558 }
559
560 String HTMLLinkElement::target() const
561 {
562     return attributeWithoutSynchronization(targetAttr);
563 }
564
565 const AtomicString& HTMLLinkElement::type() const
566 {
567     return attributeWithoutSynchronization(typeAttr);
568 }
569
570 std::optional<LinkIconType> HTMLLinkElement::iconType() const
571 {
572     return m_relAttribute.iconType;
573 }
574
575 void HTMLLinkElement::addSubresourceAttributeURLs(ListHashSet<URL>& urls) const
576 {
577     HTMLElement::addSubresourceAttributeURLs(urls);
578
579     // Favicons are handled by a special case in LegacyWebArchive::create()
580     if (m_relAttribute.iconType)
581         return;
582
583     if (!m_relAttribute.isStyleSheet)
584         return;
585     
586     // Append the URL of this link element.
587     addSubresourceURL(urls, href());
588
589     if (auto styleSheet = makeRefPtr(this->sheet())) {
590         styleSheet->contents().traverseSubresources([&] (auto& resource) {
591             urls.add(resource.url());
592             return false;
593         });
594     }
595 }
596
597 void HTMLLinkElement::addPendingSheet(PendingSheetType type)
598 {
599     if (type <= m_pendingSheetType)
600         return;
601     m_pendingSheetType = type;
602
603     if (m_pendingSheetType == InactiveSheet)
604         return;
605     ASSERT(m_styleScope);
606     m_styleScope->addPendingSheet(*this);
607 }
608
609 void HTMLLinkElement::removePendingSheet()
610 {
611     PendingSheetType type = m_pendingSheetType;
612     m_pendingSheetType = Unknown;
613
614     if (type == Unknown)
615         return;
616
617     ASSERT(m_styleScope);
618     if (type == InactiveSheet) {
619         // Document just needs to know about the sheet for exposure through document.styleSheets
620         m_styleScope->didChangeActiveStyleSheetCandidates();
621         return;
622     }
623
624     m_styleScope->removePendingSheet(*this);
625 }
626
627 } // namespace WebCore