/* * Copyright (C) 2011 Google Inc. All rights reserved. * Copyright (C) 2016-2017 Apple Inc. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are * met: * * * Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * * Redistributions in binary form must reproduce the above * copyright notice, this list of conditions and the following disclaimer * in the documentation and/or other materials provided with the * distribution. * * Neither the name of Google Inc. nor the names of its * contributors may be used to endorse or promote products derived from * this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. * */ #include "config.h" #include "LinkLoader.h" #include "CSSStyleSheet.h" #include "CachedCSSStyleSheet.h" #include "CachedResourceLoader.h" #include "CachedResourceRequest.h" #include "ContainerNode.h" #include "CrossOriginAccessControl.h" #include "DefaultResourceLoadPriority.h" #include "Document.h" #include "Frame.h" #include "FrameLoader.h" #include "FrameLoaderClient.h" #include "FrameView.h" #include "HTMLSrcsetParser.h" #include "LinkHeader.h" #include "LinkPreloadResourceClients.h" #include "LinkRelAttribute.h" #include "LoaderStrategy.h" #include "MIMETypeRegistry.h" #include "MediaList.h" #include "MediaQueryEvaluator.h" #include "PlatformStrategies.h" #include "ResourceError.h" #include "RuntimeEnabledFeatures.h" #include "Settings.h" #include "SizesAttributeParser.h" #include "StyleResolver.h" namespace WebCore { LinkLoader::LinkLoader(LinkLoaderClient& client) : m_client(client) { } LinkLoader::~LinkLoader() { if (m_cachedLinkResource) m_cachedLinkResource->removeClient(*this); if (m_preloadResourceClient) m_preloadResourceClient->clear(); } void LinkLoader::triggerEvents(const CachedResource& resource) { if (resource.errorOccurred()) m_client.linkLoadingErrored(); else m_client.linkLoaded(); } void LinkLoader::notifyFinished(CachedResource& resource, const NetworkLoadMetrics&) { ASSERT_UNUSED(resource, m_cachedLinkResource.get() == &resource); triggerEvents(*m_cachedLinkResource); m_cachedLinkResource->removeClient(*this); m_cachedLinkResource = nullptr; } void LinkLoader::loadLinksFromHeader(const String& headerValue, const URL& baseURL, Document& document, MediaAttributeCheck mediaAttributeCheck) { if (headerValue.isEmpty()) return; LinkHeaderSet headerSet(headerValue); for (auto& header : headerSet) { if (!header.valid() || header.url().isEmpty() || header.rel().isEmpty()) continue; if ((mediaAttributeCheck == MediaAttributeCheck::MediaAttributeNotEmpty && !header.isViewportDependent()) || (mediaAttributeCheck == MediaAttributeCheck::MediaAttributeEmpty && header.isViewportDependent())) { continue; } LinkRelAttribute relAttribute(document, header.rel()); URL url(baseURL, header.url()); // Sanity check to avoid re-entrancy here. if (equalIgnoringFragmentIdentifier(url, baseURL)) continue; LinkLoadParameters params { relAttribute, url, header.as(), header.media(), header.mimeType(), header.crossOrigin(), header.imageSrcSet(), header.imageSizes(), ReferrerPolicy::EmptyString }; preconnectIfNeeded(params, document); preloadIfNeeded(params, document, nullptr); } } std::optional LinkLoader::resourceTypeFromAsAttribute(const String& as, Document& document) { if (equalLettersIgnoringASCIICase(as, "fetch")) return CachedResource::Type::RawResource; if (equalLettersIgnoringASCIICase(as, "image")) return CachedResource::Type::ImageResource; if (equalLettersIgnoringASCIICase(as, "script")) return CachedResource::Type::Script; if (equalLettersIgnoringASCIICase(as, "style")) return CachedResource::Type::CSSStyleSheet; if (document.settings().mediaPreloadingEnabled() && (equalLettersIgnoringASCIICase(as, "video") || equalLettersIgnoringASCIICase(as, "audio"))) return CachedResource::Type::MediaResource; if (equalLettersIgnoringASCIICase(as, "font")) return CachedResource::Type::FontResource; if (equalLettersIgnoringASCIICase(as, "track")) return CachedResource::Type::TextTrackResource; return std::nullopt; } static std::unique_ptr createLinkPreloadResourceClient(CachedResource& resource, LinkLoader& loader, Document& document) { switch (resource.type()) { case CachedResource::Type::ImageResource: return makeUnique(loader, downcast(resource)); case CachedResource::Type::Script: return makeUnique(loader, downcast(resource)); case CachedResource::Type::CSSStyleSheet: return makeUnique(loader, downcast(resource)); case CachedResource::Type::FontResource: return makeUnique(loader, downcast(resource)); case CachedResource::Type::TextTrackResource: return makeUnique(loader, downcast(resource)); case CachedResource::Type::MediaResource: ASSERT_UNUSED(document, document.settings().mediaPreloadingEnabled()); FALLTHROUGH; case CachedResource::Type::RawResource: return makeUnique(loader, downcast(resource)); case CachedResource::Type::MainResource: case CachedResource::Type::Icon: case CachedResource::Type::SVGFontResource: case CachedResource::Type::SVGDocumentResource: #if ENABLE(XSLT) case CachedResource::Type::XSLStyleSheet: #endif case CachedResource::Type::Beacon: case CachedResource::Type::Ping: case CachedResource::Type::LinkPrefetch: #if ENABLE(APPLICATION_MANIFEST) case CachedResource::Type::ApplicationManifest: #endif #if ENABLE(MODEL_ELEMENT) case CachedResource::Type::ModelResource: #endif // None of these values is currently supported as an `as` value. ASSERT_NOT_REACHED(); } return nullptr; } bool LinkLoader::isSupportedType(CachedResource::Type resourceType, const String& mimeType, Document& document) { if (mimeType.isEmpty()) return true; switch (resourceType) { case CachedResource::Type::ImageResource: return MIMETypeRegistry::isSupportedImageVideoOrSVGMIMEType(mimeType); case CachedResource::Type::Script: return MIMETypeRegistry::isSupportedJavaScriptMIMEType(mimeType); case CachedResource::Type::CSSStyleSheet: return MIMETypeRegistry::isSupportedStyleSheetMIMEType(mimeType); case CachedResource::Type::FontResource: return MIMETypeRegistry::isSupportedFontMIMEType(mimeType); case CachedResource::Type::MediaResource: if (!document.settings().mediaPreloadingEnabled()) ASSERT_NOT_REACHED(); return MIMETypeRegistry::isSupportedMediaMIMEType(mimeType); case CachedResource::Type::TextTrackResource: return MIMETypeRegistry::isSupportedTextTrackMIMEType(mimeType); case CachedResource::Type::RawResource: #if ENABLE(APPLICATION_MANIFEST) case CachedResource::Type::ApplicationManifest: #endif return true; default: ASSERT_NOT_REACHED(); } return false; } void LinkLoader::preconnectIfNeeded(const LinkLoadParameters& params, Document& document) { const URL href = params.href; if (!params.relAttribute.isLinkPreconnect || !href.isValid() || !params.href.protocolIsInHTTPFamily() || !document.frame()) return; ASSERT(document.settings().linkPreconnectEnabled()); StoredCredentialsPolicy storageCredentialsPolicy = StoredCredentialsPolicy::Use; if (equalIgnoringASCIICase(params.crossOrigin, "anonymous") && document.securityOrigin().isSameOriginDomain(SecurityOrigin::create(href))) storageCredentialsPolicy = StoredCredentialsPolicy::DoNotUse; ASSERT(document.frame()->loader().networkingContext()); platformStrategies()->loaderStrategy()->preconnectTo(document.frame()->loader(), href, storageCredentialsPolicy, [weakDocument = makeWeakPtr(document), href](ResourceError error) { if (!weakDocument) return; if (!error.isNull()) weakDocument->addConsoleMessage(MessageSource::Network, MessageLevel::Error, makeString("Failed to preconnect to "_s, href.string(), ". Error: "_s, error.localizedDescription())); else weakDocument->addConsoleMessage(MessageSource::Network, MessageLevel::Info, makeString("Successfully preconnected to "_s, href.string())); }); } std::unique_ptr LinkLoader::preloadIfNeeded(const LinkLoadParameters& params, Document& document, LinkLoader* loader) { if (!document.loader() || !params.relAttribute.isLinkPreload) return nullptr; ASSERT(document.settings().linkPreloadEnabled()); auto type = LinkLoader::resourceTypeFromAsAttribute(params.as, document); if (!type) { document.addConsoleMessage(MessageSource::Other, MessageLevel::Error, " must have a valid `as` value"_s); return nullptr; } URL url; if (document.settings().linkPreloadResponsiveImagesEnabled() && type == CachedResource::Type::ImageResource && !params.imageSrcSet.isEmpty()) { auto sourceSize = SizesAttributeParser(params.imageSizes, document).length(); auto candidate = bestFitSourceForImageAttributes(document.deviceScaleFactor(), params.href.string(), params.imageSrcSet, sourceSize); url = document.completeURL(URL({ }, candidate.string.toString()).string()); } else url = document.completeURL(params.href.string()); if (!url.isValid()) { if (params.imageSrcSet.isEmpty()) document.addConsoleMessage(MessageSource::Other, MessageLevel::Error, " has an invalid `href` value"_s); else document.addConsoleMessage(MessageSource::Other, MessageLevel::Error, " has an invalid `imagesrcset` value"_s); return nullptr; } if (!MediaQueryEvaluator::mediaAttributeMatches(document, params.media)) return nullptr; if (!isSupportedType(type.value(), params.mimeType, document)) return nullptr; auto options = CachedResourceLoader::defaultCachedResourceOptions(); options.referrerPolicy = params.referrerPolicy; auto linkRequest = createPotentialAccessControlRequest(url, WTFMove(options), document, params.crossOrigin); linkRequest.setPriority(DefaultResourceLoadPriority::forResourceType(type.value())); linkRequest.setInitiator("link"); linkRequest.setIgnoreForRequestCount(true); linkRequest.setIsLinkPreload(); auto cachedLinkResource = document.cachedResourceLoader().preload(type.value(), WTFMove(linkRequest)).value_or(nullptr); if (cachedLinkResource && cachedLinkResource->type() != *type) return nullptr; if (cachedLinkResource && loader) return createLinkPreloadResourceClient(*cachedLinkResource, *loader, document); return nullptr; } void LinkLoader::prefetchIfNeeded(const LinkLoadParameters& params, Document& document) { if (!params.href.isValid() || !document.frame()) return; ASSERT(document.settings().linkPrefetchEnabled()); std::optional priority; CachedResource::Type type = CachedResource::Type::LinkPrefetch; if (m_cachedLinkResource) { m_cachedLinkResource->removeClient(*this); m_cachedLinkResource = nullptr; } // FIXME: Add further prefetch restrictions/limitations: // - third-party iframes cannot trigger prefetches // - Number of prefetches of a given page is limited (to 1 maybe?) ResourceLoaderOptions options = CachedResourceLoader::defaultCachedResourceOptions(); options.contentSecurityPolicyImposition = ContentSecurityPolicyImposition::SkipPolicyCheck; options.certificateInfoPolicy = CertificateInfoPolicy::IncludeCertificateInfo; options.credentials = FetchOptions::Credentials::SameOrigin; options.redirect = FetchOptions::Redirect::Manual; options.mode = FetchOptions::Mode::Navigate; options.serviceWorkersMode = ServiceWorkersMode::None; options.cachingPolicy = CachingPolicy::DisallowCaching; options.referrerPolicy = params.referrerPolicy; m_cachedLinkResource = document.cachedResourceLoader().requestLinkResource(type, CachedResourceRequest(ResourceRequest { document.completeURL(params.href.string()) }, options, priority)).value_or(nullptr); if (m_cachedLinkResource) m_cachedLinkResource->addClient(*this); } void LinkLoader::cancelLoad() { if (m_preloadResourceClient) m_preloadResourceClient->clear(); } void LinkLoader::loadLink(const LinkLoadParameters& params, Document& document) { if (params.relAttribute.isDNSPrefetch) { // FIXME: The href attribute of the link element can be in "//hostname" form, and we shouldn't attempt // to complete that as URL . if (document.settings().dnsPrefetchingEnabled() && params.href.isValid() && !params.href.isEmpty() && document.frame()) document.frame()->loader().client().prefetchDNS(params.href.host().toString()); } preconnectIfNeeded(params, document); if (params.relAttribute.isLinkPrefetch) { prefetchIfNeeded(params, document); return; } if (m_client.shouldLoadLink()) { auto resourceClient = preloadIfNeeded(params, document, this); if (m_preloadResourceClient) m_preloadResourceClient->clear(); if (resourceClient) m_preloadResourceClient = WTFMove(resourceClient); } } }