/* * Copyright (C) 2011 Google Inc. All rights reserved. * Copyright (C) 2016 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 "Document.h" #include "Frame.h" #include "FrameLoaderClient.h" #include "FrameView.h" #include "LinkHeader.h" #include "LinkPreloadResourceClients.h" #include "LinkRelAttribute.h" #include "RuntimeEnabledFeatures.h" #include "Settings.h" #include "StyleResolver.h" namespace WebCore { LinkLoader::LinkLoader(LinkLoaderClient& client) : m_client(client) , m_weakPtrFactory(this) { } 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) { 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) { if (headerValue.isEmpty()) return; LinkHeaderSet headerSet(headerValue); for (auto& header : headerSet) { if (!header.valid() || header.url().isEmpty() || header.rel().isEmpty()) continue; LinkRelAttribute relAttribute(header.rel()); URL url(baseURL, header.url()); // Sanity check to avoid re-entrancy here. if (equalIgnoringFragmentIdentifier(url, baseURL)) continue; preloadIfNeeded(relAttribute, url, document, header.as(), header.crossOrigin(), nullptr, nullptr); } } std::optional LinkLoader::resourceTypeFromAsAttribute(const String& as) { if (as.isEmpty()) return CachedResource::RawResource; if (equalLettersIgnoringASCIICase(as, "image")) return CachedResource::ImageResource; if (equalLettersIgnoringASCIICase(as, "script")) return CachedResource::Script; if (equalLettersIgnoringASCIICase(as, "style")) return CachedResource::CSSStyleSheet; if (equalLettersIgnoringASCIICase(as, "media")) return CachedResource::MediaResource; if (equalLettersIgnoringASCIICase(as, "font")) return CachedResource::FontResource; #if ENABLE(VIDEO_TRACK) if (equalLettersIgnoringASCIICase(as, "track")) return CachedResource::TextTrackResource; #endif return std::nullopt; } static std::unique_ptr createLinkPreloadResourceClient(CachedResource& resource, LinkLoader& loader, CachedResource::Type type) { switch (type) { case CachedResource::ImageResource: return LinkPreloadImageResourceClient::create(loader, static_cast(resource)); case CachedResource::Script: return LinkPreloadScriptResourceClient::create(loader, static_cast(resource)); case CachedResource::CSSStyleSheet: return LinkPreloadStyleResourceClient::create(loader, static_cast(resource)); case CachedResource::FontResource: return LinkPreloadFontResourceClient::create(loader, static_cast(resource)); case CachedResource::MediaResource: #if ENABLE(VIDEO_TRACK) case CachedResource::TextTrackResource: #endif case CachedResource::RawResource: return LinkPreloadRawResourceClient::create(loader, static_cast(resource)); case CachedResource::MainResource: #if ENABLE(SVG_FONTS) case CachedResource::SVGFontResource: #endif case CachedResource::SVGDocumentResource: #if ENABLE(XSLT) case CachedResource::XSLStyleSheet: #endif #if ENABLE(LINK_PREFETCH) case CachedResource::LinkSubresource: case CachedResource::LinkPrefetch: #endif // None of these values is currently supported as an `as` value. ASSERT_NOT_REACHED(); } return nullptr; } std::unique_ptr LinkLoader::preloadIfNeeded(const LinkRelAttribute& relAttribute, const URL& href, Document& document, const String& as, const String& crossOriginMode, LinkLoader* loader, LinkLoaderClient* client) { if (!document.loader() || !relAttribute.isLinkPreload) return nullptr; ASSERT(RuntimeEnabledFeatures::sharedFeatures().linkPreloadEnabled()); if (!href.isValid()) { document.addConsoleMessage(MessageSource::Other, MessageLevel::Error, String(" has an invalid `href` value")); return nullptr; } auto type = LinkLoader::resourceTypeFromAsAttribute(as); if (!type) { document.addConsoleMessage(MessageSource::Other, MessageLevel::Error, String(" must have a valid `as` value")); if (client) client->linkLoadingErrored(); return nullptr; } ResourceRequest resourceRequest(document.completeURL(href)); resourceRequest.setIgnoreForRequestCount(true); CachedResourceRequest linkRequest(WTFMove(resourceRequest), CachedResourceLoader::defaultCachedResourceOptions(), CachedResource::defaultPriorityForResourceType(type.value())); linkRequest.setInitiator("link"); linkRequest.setIsLinkPreload(); linkRequest.setAsPotentiallyCrossOrigin(crossOriginMode, document); CachedResourceHandle cachedLinkResource = document.cachedResourceLoader().preload(type.value(), WTFMove(linkRequest)); if (cachedLinkResource && loader) return createLinkPreloadResourceClient(*cachedLinkResource, *loader, type.value()); return nullptr; } void LinkLoader::cancelLoad() { if (m_preloadResourceClient) m_preloadResourceClient->clear(); } bool LinkLoader::loadLink(const LinkRelAttribute& relAttribute, const URL& href, const String& as, const String& crossOrigin, Document& document) { if (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() && href.isValid() && !href.isEmpty() && document.frame()) document.frame()->loader().client().prefetchDNS(href.host()); } if (m_client.shouldLoadLink()) { auto resourceClient = preloadIfNeeded(relAttribute, href, document, as, crossOrigin, this, &m_client); if (resourceClient) m_preloadResourceClient = WTFMove(resourceClient); else if (m_preloadResourceClient) m_preloadResourceClient->clear(); } #if ENABLE(LINK_PREFETCH) if ((relAttribute.isLinkPrefetch || relAttribute.isLinkSubresource) && href.isValid() && document.frame()) { if (!m_client.shouldLoadLink()) return false; std::optional priority; CachedResource::Type type = CachedResource::LinkPrefetch; if (relAttribute.isLinkSubresource) { // We only make one request to the cached resource loader if multiple rel types are specified; // this is the higher priority, which should overwrite the lower priority. priority = ResourceLoadPriority::Low; type = CachedResource::LinkSubresource; } if (m_cachedLinkResource) { m_cachedLinkResource->removeClient(*this); m_cachedLinkResource = nullptr; } ResourceLoaderOptions options = CachedResourceLoader::defaultCachedResourceOptions(); options.contentSecurityPolicyImposition = ContentSecurityPolicyImposition::SkipPolicyCheck; m_cachedLinkResource = document.cachedResourceLoader().requestLinkResource(type, CachedResourceRequest(ResourceRequest(document.completeURL(href)), options, priority)); if (m_cachedLinkResource) m_cachedLinkResource->addClient(*this); } #endif return true; } }