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