f7cc5799800fcfc4580cc656558626f59cb5559a
[WebKit-https.git] / Source / WebCore / loader / LinkLoader.cpp
1 /*
2  * Copyright (C) 2011 Google Inc. All rights reserved.
3  * Copyright (C) 2016 Apple Inc. All rights reserved.
4  *
5  * Redistribution and use in source and binary forms, with or without
6  * modification, are permitted provided that the following conditions are
7  * met:
8  *
9  *     * Redistributions of source code must retain the above copyright
10  * notice, this list of conditions and the following disclaimer.
11  *     * Redistributions in binary form must reproduce the above
12  * copyright notice, this list of conditions and the following disclaimer
13  * in the documentation and/or other materials provided with the
14  * distribution.
15  *     * Neither the name of Google Inc. nor the names of its
16  * contributors may be used to endorse or promote products derived from
17  * this software without specific prior written permission.
18  *
19  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
20  * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
21  * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
22  * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
23  * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
24  * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
25  * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
26  * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
27  * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
28  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
29  * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
30  *
31  */
32
33 #include "config.h"
34 #include "LinkLoader.h"
35
36 #include "CSSStyleSheet.h"
37 #include "CachedCSSStyleSheet.h"
38 #include "CachedResourceLoader.h"
39 #include "CachedResourceRequest.h"
40 #include "ContainerNode.h"
41 #include "CrossOriginAccessControl.h"
42 #include "Document.h"
43 #include "Frame.h"
44 #include "FrameLoader.h"
45 #include "FrameLoaderClient.h"
46 #include "FrameView.h"
47 #include "LinkHeader.h"
48 #include "LinkPreloadResourceClients.h"
49 #include "LinkRelAttribute.h"
50 #include "LoaderStrategy.h"
51 #include "MIMETypeRegistry.h"
52 #include "MediaQueryEvaluator.h"
53 #include "PlatformStrategies.h"
54 #include "ResourceError.h"
55 #include "RuntimeEnabledFeatures.h"
56 #include "Settings.h"
57 #include "StyleResolver.h"
58
59 namespace WebCore {
60
61 LinkLoader::LinkLoader(LinkLoaderClient& client)
62     : m_client(client)
63 {
64 }
65
66 LinkLoader::~LinkLoader()
67 {
68     if (m_cachedLinkResource)
69         m_cachedLinkResource->removeClient(*this);
70     if (m_preloadResourceClient)
71         m_preloadResourceClient->clear();
72 }
73
74 void LinkLoader::triggerEvents(const CachedResource& resource)
75 {
76     if (resource.errorOccurred())
77         m_client.linkLoadingErrored();
78     else
79         m_client.linkLoaded();
80 }
81
82 void LinkLoader::notifyFinished(CachedResource& resource)
83 {
84     ASSERT_UNUSED(resource, m_cachedLinkResource.get() == &resource);
85
86     triggerEvents(*m_cachedLinkResource);
87
88     m_cachedLinkResource->removeClient(*this);
89     m_cachedLinkResource = nullptr;
90 }
91
92 void LinkLoader::loadLinksFromHeader(const String& headerValue, const URL& baseURL, Document& document, MediaAttributeCheck mediaAttributeCheck)
93 {
94     if (headerValue.isEmpty())
95         return;
96     LinkHeaderSet headerSet(headerValue);
97     for (auto& header : headerSet) {
98         if (!header.valid() || header.url().isEmpty() || header.rel().isEmpty())
99             continue;
100         if (mediaAttributeCheck == MediaAttributeCheck::MediaAttributeNotEmpty) {
101             if (header.media().isEmpty())
102                 continue;
103         } else {
104             if (!header.media().isEmpty())
105                 continue;
106         }
107
108         LinkRelAttribute relAttribute(document, header.rel());
109         URL url(baseURL, header.url());
110         // Sanity check to avoid re-entrancy here.
111         if (equalIgnoringFragmentIdentifier(url, baseURL))
112             continue;
113         preloadIfNeeded(relAttribute, url, document, header.as(), header.media(), header.mimeType(), header.crossOrigin(), nullptr);
114     }
115 }
116
117 std::optional<CachedResource::Type> LinkLoader::resourceTypeFromAsAttribute(const String& as)
118 {
119     if (equalLettersIgnoringASCIICase(as, "fetch"))
120         return CachedResource::RawResource;
121     if (equalLettersIgnoringASCIICase(as, "image"))
122         return CachedResource::ImageResource;
123     if (equalLettersIgnoringASCIICase(as, "script"))
124         return CachedResource::Script;
125     if (equalLettersIgnoringASCIICase(as, "style"))
126         return CachedResource::CSSStyleSheet;
127     if (RuntimeEnabledFeatures::sharedFeatures().mediaPreloadingEnabled() && (equalLettersIgnoringASCIICase(as, "video") || equalLettersIgnoringASCIICase(as, "audio")))
128         return CachedResource::MediaResource;
129     if (equalLettersIgnoringASCIICase(as, "font"))
130         return CachedResource::FontResource;
131 #if ENABLE(VIDEO_TRACK)
132     if (equalLettersIgnoringASCIICase(as, "track"))
133         return CachedResource::TextTrackResource;
134 #endif
135     return std::nullopt;
136 }
137
138 static std::unique_ptr<LinkPreloadResourceClient> createLinkPreloadResourceClient(CachedResource& resource, LinkLoader& loader, CachedResource::Type type)
139 {
140     switch (type) {
141     case CachedResource::ImageResource:
142         return LinkPreloadImageResourceClient::create(loader, static_cast<CachedImage&>(resource));
143     case CachedResource::Script:
144         return LinkPreloadScriptResourceClient::create(loader, static_cast<CachedScript&>(resource));
145     case CachedResource::CSSStyleSheet:
146         return LinkPreloadStyleResourceClient::create(loader, static_cast<CachedCSSStyleSheet&>(resource));
147     case CachedResource::FontResource:
148         return LinkPreloadFontResourceClient::create(loader, static_cast<CachedFont&>(resource));
149     case CachedResource::MediaResource:
150         if (!RuntimeEnabledFeatures::sharedFeatures().mediaPreloadingEnabled())
151             ASSERT_NOT_REACHED();
152         FALLTHROUGH;
153 #if ENABLE(VIDEO_TRACK)
154     case CachedResource::TextTrackResource:
155 #endif
156     case CachedResource::RawResource:
157         return LinkPreloadRawResourceClient::create(loader, static_cast<CachedRawResource&>(resource));
158     case CachedResource::MainResource:
159     case CachedResource::Icon:
160 #if ENABLE(SVG_FONTS)
161     case CachedResource::SVGFontResource:
162 #endif
163     case CachedResource::SVGDocumentResource:
164 #if ENABLE(XSLT)
165     case CachedResource::XSLStyleSheet:
166 #endif
167     case CachedResource::Beacon:
168 #if ENABLE(LINK_PREFETCH)
169     case CachedResource::LinkSubresource:
170     case CachedResource::LinkPrefetch:
171 #endif
172         // None of these values is currently supported as an `as` value.
173         ASSERT_NOT_REACHED();
174     }
175     return nullptr;
176 }
177
178 bool LinkLoader::isSupportedType(CachedResource::Type resourceType, const String& mimeType)
179 {
180     if (mimeType.isEmpty())
181         return true;
182     switch (resourceType) {
183     case CachedResource::ImageResource:
184         return MIMETypeRegistry::isSupportedImageOrSVGMIMEType(mimeType);
185     case CachedResource::Script:
186         return MIMETypeRegistry::isSupportedJavaScriptMIMEType(mimeType);
187     case CachedResource::CSSStyleSheet:
188         return MIMETypeRegistry::isSupportedStyleSheetMIMEType(mimeType);
189     case CachedResource::FontResource:
190         return MIMETypeRegistry::isSupportedFontMIMEType(mimeType);
191     case CachedResource::MediaResource:
192         if (!RuntimeEnabledFeatures::sharedFeatures().mediaPreloadingEnabled())
193             ASSERT_NOT_REACHED();
194         return MIMETypeRegistry::isSupportedMediaMIMEType(mimeType);
195
196 #if ENABLE(VIDEO_TRACK)
197     case CachedResource::TextTrackResource:
198         return MIMETypeRegistry::isSupportedTextTrackMIMEType(mimeType);
199 #endif
200     case CachedResource::RawResource:
201         return true;
202     default:
203         ASSERT_NOT_REACHED();
204     }
205     return false;
206 }
207
208 std::unique_ptr<LinkPreloadResourceClient> LinkLoader::preloadIfNeeded(const LinkRelAttribute& relAttribute, const URL& href, Document& document, const String& as, const String& media, const String& mimeType, const String& crossOriginMode, LinkLoader* loader)
209 {
210     if (!document.loader() || !relAttribute.isLinkPreload)
211         return nullptr;
212
213     ASSERT(RuntimeEnabledFeatures::sharedFeatures().linkPreloadEnabled());
214     if (!href.isValid()) {
215         document.addConsoleMessage(MessageSource::Other, MessageLevel::Error, String("<link rel=preload> has an invalid `href` value"));
216         return nullptr;
217     }
218     auto type = LinkLoader::resourceTypeFromAsAttribute(as);
219     if (!type) {
220         document.addConsoleMessage(MessageSource::Other, MessageLevel::Error, String("<link rel=preload> must have a valid `as` value"));
221         return nullptr;
222     }
223     if (!MediaQueryEvaluator::mediaAttributeMatches(document, media))
224         return nullptr;
225     if (!isSupportedType(type.value(), mimeType))
226         return nullptr;
227
228     CachedResourceRequest linkRequest(document.completeURL(href), CachedResourceLoader::defaultCachedResourceOptions(), CachedResource::defaultPriorityForResourceType(type.value()));
229     linkRequest.setInitiator("link");
230     linkRequest.setIgnoreForRequestCount(true);
231     linkRequest.setIsLinkPreload();
232
233     linkRequest.setAsPotentiallyCrossOrigin(crossOriginMode, document);
234     auto cachedLinkResource = document.cachedResourceLoader().preload(type.value(), WTFMove(linkRequest)).valueOr(nullptr);
235
236     if (cachedLinkResource && loader)
237         return createLinkPreloadResourceClient(*cachedLinkResource, *loader, type.value());
238     return nullptr;
239 }
240
241 void LinkLoader::cancelLoad()
242 {
243     if (m_preloadResourceClient)
244         m_preloadResourceClient->clear();
245 }
246
247 bool LinkLoader::loadLink(const LinkRelAttribute& relAttribute, const URL& href, const String& as, const String& media, const String& mimeType, const String& crossOrigin, Document& document)
248 {
249     if (relAttribute.isDNSPrefetch) {
250         // FIXME: The href attribute of the link element can be in "//hostname" form, and we shouldn't attempt
251         // to complete that as URL <https://bugs.webkit.org/show_bug.cgi?id=48857>.
252         if (document.settings().dnsPrefetchingEnabled() && href.isValid() && !href.isEmpty() && document.frame())
253             document.frame()->loader().client().prefetchDNS(href.host());
254     }
255
256     if (relAttribute.isLinkPreconnect && href.isValid() && href.protocolIsInHTTPFamily() && document.frame()) {
257         ASSERT(document.settings().linkPreconnectEnabled());
258         StoredCredentialsPolicy storageCredentialsPolicy = StoredCredentialsPolicy::Use;
259         if (equalIgnoringASCIICase(crossOrigin, "anonymous") && document.securityOrigin().canAccess(SecurityOrigin::create(href)))
260             storageCredentialsPolicy = StoredCredentialsPolicy::DoNotUse;
261         ASSERT(document.frame()->loader().networkingContext());
262         platformStrategies()->loaderStrategy()->preconnectTo(*document.frame()->loader().networkingContext(), href, storageCredentialsPolicy, [weakDocument = document.createWeakPtr(), href](ResourceError error) {
263             if (!weakDocument)
264                 return;
265
266             if (!error.isNull())
267                 weakDocument->addConsoleMessage(MessageSource::Network, MessageLevel::Error, makeString(ASCIILiteral("Failed to preconnect to "), href.string(), ASCIILiteral(". Error: "), error.localizedDescription()));
268             else
269                 weakDocument->addConsoleMessage(MessageSource::Network, MessageLevel::Info, makeString(ASCIILiteral("Successfuly preconnected to "), href.string()));
270         });
271     }
272
273     if (m_client.shouldLoadLink()) {
274         auto resourceClient = preloadIfNeeded(relAttribute, href, document, as, media, mimeType, crossOrigin, this);
275         if (resourceClient)
276             m_preloadResourceClient = WTFMove(resourceClient);
277         else if (m_preloadResourceClient)
278             m_preloadResourceClient->clear();
279     }
280
281 #if ENABLE(LINK_PREFETCH)
282     if ((relAttribute.isLinkPrefetch || relAttribute.isLinkSubresource) && href.isValid() && document.frame()) {
283         if (!m_client.shouldLoadLink())
284             return false;
285
286         std::optional<ResourceLoadPriority> priority;
287         CachedResource::Type type = CachedResource::LinkPrefetch;
288         if (relAttribute.isLinkSubresource) {
289             // We only make one request to the cached resource loader if multiple rel types are specified;
290             // this is the higher priority, which should overwrite the lower priority.
291             priority = ResourceLoadPriority::Low;
292             type = CachedResource::LinkSubresource;
293         }
294
295         if (m_cachedLinkResource) {
296             m_cachedLinkResource->removeClient(*this);
297             m_cachedLinkResource = nullptr;
298         }
299         ResourceLoaderOptions options = CachedResourceLoader::defaultCachedResourceOptions();
300         options.contentSecurityPolicyImposition = ContentSecurityPolicyImposition::SkipPolicyCheck;
301         m_cachedLinkResource = document.cachedResourceLoader().requestLinkResource(type, CachedResourceRequest(ResourceRequest(document.completeURL(href)), options, priority)).valueOr(nullptr);
302         if (m_cachedLinkResource)
303             m_cachedLinkResource->addClient(*this);
304     }
305 #endif
306
307     return true;
308 }
309
310 }