summaryrefslogtreecommitdiffstats
path: root/uriloader/preload/PreloadService.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'uriloader/preload/PreloadService.cpp')
-rw-r--r--uriloader/preload/PreloadService.cpp371
1 files changed, 371 insertions, 0 deletions
diff --git a/uriloader/preload/PreloadService.cpp b/uriloader/preload/PreloadService.cpp
new file mode 100644
index 0000000000..5fc59760f0
--- /dev/null
+++ b/uriloader/preload/PreloadService.cpp
@@ -0,0 +1,371 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "PreloadService.h"
+
+#include "FetchPreloader.h"
+#include "PreloaderBase.h"
+#include "mozilla/Assertions.h"
+#include "mozilla/AsyncEventDispatcher.h"
+#include "mozilla/Maybe.h"
+#include "mozilla/dom/FetchPriority.h"
+#include "mozilla/dom/HTMLLinkElement.h"
+#include "mozilla/dom/ScriptLoader.h"
+#include "mozilla/dom/ReferrerInfo.h"
+#include "mozilla/Encoding.h"
+#include "mozilla/FontPreloader.h"
+#include "mozilla/StaticPrefs_network.h"
+#include "nsGenericHTMLElement.h"
+#include "nsNetUtil.h"
+
+namespace mozilla {
+
+using namespace dom;
+
+static LazyLogModule sPreloadServiceLog{"PreloadService"};
+
+PreloadService::PreloadService(dom::Document* aDoc) : mDocument(aDoc) {}
+PreloadService::~PreloadService() = default;
+
+bool PreloadService::RegisterPreload(const PreloadHashKey& aKey,
+ PreloaderBase* aPreload) {
+ return mPreloads.WithEntryHandle(aKey, [&](auto&& lookup) {
+ if (lookup) {
+ lookup.Data() = aPreload;
+ return true;
+ }
+ lookup.Insert(aPreload);
+ return false;
+ });
+}
+
+void PreloadService::DeregisterPreload(const PreloadHashKey& aKey) {
+ mPreloads.Remove(aKey);
+}
+
+void PreloadService::ClearAllPreloads() { mPreloads.Clear(); }
+
+bool PreloadService::PreloadExists(const PreloadHashKey& aKey) {
+ return mPreloads.Contains(aKey);
+}
+
+already_AddRefed<PreloaderBase> PreloadService::LookupPreload(
+ const PreloadHashKey& aKey) const {
+ return mPreloads.Get(aKey);
+}
+
+already_AddRefed<nsIURI> PreloadService::GetPreloadURI(const nsAString& aURL) {
+ nsIURI* base = BaseURIForPreload();
+ auto encoding = mDocument->GetDocumentCharacterSet();
+
+ nsCOMPtr<nsIURI> uri;
+ nsresult rv = NS_NewURI(getter_AddRefs(uri), aURL, encoding, base);
+ if (NS_FAILED(rv)) {
+ return nullptr;
+ }
+
+ return uri.forget();
+}
+
+already_AddRefed<PreloaderBase> PreloadService::PreloadLinkElement(
+ dom::HTMLLinkElement* aLinkElement, nsContentPolicyType aPolicyType) {
+ if (aPolicyType == nsIContentPolicy::TYPE_INVALID) {
+ MOZ_ASSERT_UNREACHABLE("Caller should check");
+ return nullptr;
+ }
+
+ nsAutoString as, charset, crossOrigin, integrity, referrerPolicy,
+ fetchPriority, rel, srcset, sizes, type, url;
+
+ nsCOMPtr<nsIURI> uri = aLinkElement->GetURI();
+ aLinkElement->GetCharset(charset);
+ aLinkElement->GetImageSrcset(srcset);
+ aLinkElement->GetImageSizes(sizes);
+ aLinkElement->GetHref(url);
+ aLinkElement->GetCrossOrigin(crossOrigin);
+ aLinkElement->GetIntegrity(integrity);
+ aLinkElement->GetReferrerPolicy(referrerPolicy);
+ aLinkElement->GetFetchPriority(fetchPriority);
+ aLinkElement->GetRel(rel);
+
+ nsAutoString nonce;
+ if (nsString* cspNonce =
+ static_cast<nsString*>(aLinkElement->GetProperty(nsGkAtoms::nonce))) {
+ nonce = *cspNonce;
+ }
+
+ if (rel.LowerCaseEqualsASCII("modulepreload")) {
+ as = u"script"_ns;
+ type = u"module"_ns;
+ } else {
+ aLinkElement->GetAs(as);
+ aLinkElement->GetType(type);
+ }
+
+ auto result = PreloadOrCoalesce(uri, url, aPolicyType, as, type, charset,
+ srcset, sizes, nonce, integrity, crossOrigin,
+ referrerPolicy, fetchPriority,
+ /* aFromHeader = */ false, 0);
+
+ if (!result.mPreloader) {
+ NotifyNodeEvent(aLinkElement, result.mAlreadyComplete);
+ return nullptr;
+ }
+
+ result.mPreloader->AddLinkPreloadNode(aLinkElement);
+ return result.mPreloader.forget();
+}
+
+void PreloadService::PreloadLinkHeader(
+ nsIURI* aURI, const nsAString& aURL, nsContentPolicyType aPolicyType,
+ const nsAString& aAs, const nsAString& aType, const nsAString& aNonce,
+ const nsAString& aIntegrity, const nsAString& aSrcset,
+ const nsAString& aSizes, const nsAString& aCORS,
+ const nsAString& aReferrerPolicy, uint64_t aEarlyHintPreloaderId,
+ const nsAString& aFetchPriority) {
+ if (aPolicyType == nsIContentPolicy::TYPE_INVALID) {
+ MOZ_ASSERT_UNREACHABLE("Caller should check");
+ return;
+ }
+
+ PreloadOrCoalesce(aURI, aURL, aPolicyType, aAs, aType, u""_ns, aSrcset,
+ aSizes, aNonce, aIntegrity, aCORS, aReferrerPolicy,
+ aFetchPriority,
+ /* aFromHeader = */ true, aEarlyHintPreloaderId);
+}
+
+// The mapping is specified as implementation-defined, see step 15 of
+// <https://fetch.spec.whatwg.org/#concept-fetch>. For web-compatibility,
+// Chromium's mapping described at
+// <https://web.dev/articles/fetch-priority#browser_priority_and_fetchpriority>
+// is chosen.
+class SupportsPriorityValueFor {
+ public:
+ static int32_t LinkRelPreloadFont(const FetchPriority aFetchPriority) {
+ if (!StaticPrefs::network_fetchpriority_enabled()) {
+ return nsISupportsPriority::PRIORITY_HIGH;
+ }
+
+ switch (aFetchPriority) {
+ case FetchPriority::Auto:
+ return nsISupportsPriority::PRIORITY_HIGH;
+ case FetchPriority::High:
+ return nsISupportsPriority::PRIORITY_HIGH;
+ case FetchPriority::Low:
+ return nsISupportsPriority::PRIORITY_LOW;
+ }
+
+ MOZ_ASSERT_UNREACHABLE();
+ return nsISupportsPriority::PRIORITY_HIGH;
+ }
+
+ static int32_t LinkRelPreloadFetch(const FetchPriority aFetchPriority) {
+ if (!StaticPrefs::network_fetchpriority_enabled()) {
+ return nsISupportsPriority::PRIORITY_NORMAL;
+ }
+
+ switch (aFetchPriority) {
+ case FetchPriority::Auto:
+ return nsISupportsPriority::PRIORITY_NORMAL;
+ case FetchPriority::High:
+ return nsISupportsPriority::PRIORITY_HIGH;
+ case FetchPriority::Low:
+ return nsISupportsPriority::PRIORITY_LOW;
+ }
+
+ MOZ_ASSERT_UNREACHABLE();
+ return nsISupportsPriority::PRIORITY_NORMAL;
+ }
+};
+
+PreloadService::PreloadOrCoalesceResult PreloadService::PreloadOrCoalesce(
+ nsIURI* aURI, const nsAString& aURL, nsContentPolicyType aPolicyType,
+ const nsAString& aAs, const nsAString& aType, const nsAString& aCharset,
+ const nsAString& aSrcset, const nsAString& aSizes, const nsAString& aNonce,
+ const nsAString& aIntegrity, const nsAString& aCORS,
+ const nsAString& aReferrerPolicy, const nsAString& aFetchPriority,
+ bool aFromHeader, uint64_t aEarlyHintPreloaderId) {
+ if (!aURI) {
+ MOZ_ASSERT_UNREACHABLE("Should not pass null nsIURI");
+ return {nullptr, false};
+ }
+
+ bool isImgSet = false;
+ PreloadHashKey preloadKey;
+ nsCOMPtr<nsIURI> uri = aURI;
+
+ if (aAs.LowerCaseEqualsASCII("script")) {
+ preloadKey = PreloadHashKey::CreateAsScript(uri, aCORS, aType);
+ } else if (aAs.LowerCaseEqualsASCII("style")) {
+ preloadKey = PreloadHashKey::CreateAsStyle(
+ uri, mDocument->NodePrincipal(), dom::Element::StringToCORSMode(aCORS),
+ css::eAuthorSheetFeatures /* see Loader::LoadSheet */);
+ } else if (aAs.LowerCaseEqualsASCII("image")) {
+ uri = mDocument->ResolvePreloadImage(BaseURIForPreload(), aURL, aSrcset,
+ aSizes, &isImgSet);
+ if (!uri) {
+ return {nullptr, false};
+ }
+
+ preloadKey = PreloadHashKey::CreateAsImage(
+ uri, mDocument->NodePrincipal(), dom::Element::StringToCORSMode(aCORS));
+ } else if (aAs.LowerCaseEqualsASCII("font")) {
+ preloadKey = PreloadHashKey::CreateAsFont(
+ uri, dom::Element::StringToCORSMode(aCORS));
+ } else if (aAs.LowerCaseEqualsASCII("fetch")) {
+ preloadKey = PreloadHashKey::CreateAsFetch(
+ uri, dom::Element::StringToCORSMode(aCORS));
+ } else {
+ return {nullptr, false};
+ }
+
+ if (RefPtr<PreloaderBase> preload = LookupPreload(preloadKey)) {
+ return {std::move(preload), false};
+ }
+
+ if (aAs.LowerCaseEqualsASCII("script")) {
+ PreloadScript(uri, aType, aCharset, aCORS, aReferrerPolicy, aNonce,
+ aFetchPriority, aIntegrity, true /* isInHead - TODO */,
+ aEarlyHintPreloaderId);
+ } else if (aAs.LowerCaseEqualsASCII("style")) {
+ auto status = mDocument->PreloadStyle(
+ aURI, Encoding::ForLabel(aCharset), aCORS,
+ PreloadReferrerPolicy(aReferrerPolicy), aNonce, aIntegrity,
+ aFromHeader ? css::StylePreloadKind::FromLinkRelPreloadHeader
+ : css::StylePreloadKind::FromLinkRelPreloadElement,
+ aEarlyHintPreloaderId, aFetchPriority);
+ switch (status) {
+ case dom::SheetPreloadStatus::AlreadyComplete:
+ return {nullptr, /* already_complete = */ true};
+ case dom::SheetPreloadStatus::Errored:
+ case dom::SheetPreloadStatus::InProgress:
+ break;
+ }
+ } else if (aAs.LowerCaseEqualsASCII("image")) {
+ PreloadImage(uri, aCORS, aReferrerPolicy, isImgSet, aEarlyHintPreloaderId);
+ } else if (aAs.LowerCaseEqualsASCII("font")) {
+ PreloadFont(uri, aCORS, aReferrerPolicy, aEarlyHintPreloaderId,
+ aFetchPriority);
+ } else if (aAs.LowerCaseEqualsASCII("fetch")) {
+ PreloadFetch(uri, aCORS, aReferrerPolicy, aEarlyHintPreloaderId,
+ aFetchPriority);
+ }
+
+ RefPtr<PreloaderBase> preload = LookupPreload(preloadKey);
+ if (preload && aEarlyHintPreloaderId) {
+ preload->SetForEarlyHints();
+ }
+
+ return {preload, false};
+}
+
+void PreloadService::PreloadScript(
+ nsIURI* aURI, const nsAString& aType, const nsAString& aCharset,
+ const nsAString& aCrossOrigin, const nsAString& aReferrerPolicy,
+ const nsAString& aNonce, const nsAString& aFetchPriority,
+ const nsAString& aIntegrity, bool aScriptFromHead,
+ uint64_t aEarlyHintPreloaderId) {
+ mDocument->ScriptLoader()->PreloadURI(
+ aURI, aCharset, aType, aCrossOrigin, aNonce, aFetchPriority, aIntegrity,
+ aScriptFromHead, false, false, true,
+ PreloadReferrerPolicy(aReferrerPolicy), aEarlyHintPreloaderId);
+}
+
+void PreloadService::PreloadImage(nsIURI* aURI, const nsAString& aCrossOrigin,
+ const nsAString& aImageReferrerPolicy,
+ bool aIsImgSet,
+ uint64_t aEarlyHintPreloaderId) {
+ mDocument->PreLoadImage(aURI, aCrossOrigin,
+ PreloadReferrerPolicy(aImageReferrerPolicy),
+ aIsImgSet, true, aEarlyHintPreloaderId);
+}
+
+void PreloadService::PreloadFont(nsIURI* aURI, const nsAString& aCrossOrigin,
+ const nsAString& aReferrerPolicy,
+ uint64_t aEarlyHintPreloaderId,
+ const nsAString& aFetchPriority) {
+ CORSMode cors = dom::Element::StringToCORSMode(aCrossOrigin);
+ auto key = PreloadHashKey::CreateAsFont(aURI, cors);
+
+ if (PreloadExists(key)) {
+ return;
+ }
+
+ const auto fetchPriority =
+ nsGenericHTMLElement::ToFetchPriority(aFetchPriority);
+ const auto supportsPriorityValue =
+ SupportsPriorityValueFor::LinkRelPreloadFont(fetchPriority);
+ LogPriorityMapping(sPreloadServiceLog, fetchPriority, supportsPriorityValue);
+
+ RefPtr<FontPreloader> preloader = new FontPreloader();
+ dom::ReferrerPolicy referrerPolicy = PreloadReferrerPolicy(aReferrerPolicy);
+ preloader->OpenChannel(key, aURI, cors, referrerPolicy, mDocument,
+ aEarlyHintPreloaderId, supportsPriorityValue);
+}
+
+void PreloadService::PreloadFetch(nsIURI* aURI, const nsAString& aCrossOrigin,
+ const nsAString& aReferrerPolicy,
+ uint64_t aEarlyHintPreloaderId,
+ const nsAString& aFetchPriority) {
+ CORSMode cors = dom::Element::StringToCORSMode(aCrossOrigin);
+ auto key = PreloadHashKey::CreateAsFetch(aURI, cors);
+
+ if (PreloadExists(key)) {
+ return;
+ }
+
+ RefPtr<FetchPreloader> preloader = new FetchPreloader();
+ dom::ReferrerPolicy referrerPolicy = PreloadReferrerPolicy(aReferrerPolicy);
+
+ const auto fetchPriority =
+ nsGenericHTMLElement::ToFetchPriority(aFetchPriority);
+ const int32_t supportsPriorityValue =
+ SupportsPriorityValueFor::LinkRelPreloadFetch(fetchPriority);
+ if (supportsPriorityValue) {
+ LogPriorityMapping(sPreloadServiceLog, fetchPriority,
+ supportsPriorityValue);
+ }
+
+ preloader->OpenChannel(key, aURI, cors, referrerPolicy, mDocument,
+ aEarlyHintPreloaderId, supportsPriorityValue);
+}
+
+// static
+void PreloadService::NotifyNodeEvent(nsINode* aNode, bool aSuccess) {
+ if (!aNode->IsInComposedDoc()) {
+ return;
+ }
+
+ // We don't dispatch synchronously since |node| might be in a DocGroup
+ // that we're not allowed to touch. (Our network request happens in the
+ // DocGroup of one of the mSources nodes--not necessarily this one).
+
+ RefPtr<AsyncEventDispatcher> dispatcher = new AsyncEventDispatcher(
+ aNode, aSuccess ? u"load"_ns : u"error"_ns, CanBubble::eNo);
+
+ dispatcher->RequireNodeInDocument();
+ dispatcher->PostDOMEvent();
+}
+
+dom::ReferrerPolicy PreloadService::PreloadReferrerPolicy(
+ const nsAString& aReferrerPolicy) {
+ dom::ReferrerPolicy referrerPolicy =
+ dom::ReferrerInfo::ReferrerPolicyAttributeFromString(aReferrerPolicy);
+ if (referrerPolicy == dom::ReferrerPolicy::_empty) {
+ referrerPolicy = mDocument->GetPreloadReferrerInfo()->ReferrerPolicy();
+ }
+
+ return referrerPolicy;
+}
+
+nsIURI* PreloadService::BaseURIForPreload() {
+ nsIURI* documentURI = mDocument->GetDocumentURI();
+ nsIURI* documentBaseURI = mDocument->GetDocBaseURI();
+ return (documentURI == documentBaseURI)
+ ? (mSpeculationBaseURI ? mSpeculationBaseURI.get() : documentURI)
+ : documentBaseURI;
+}
+
+} // namespace mozilla