author | Honza Bambas <honzab.moz@firemni.cz> |
Mon, 11 May 2020 14:20:39 +0000 | |
changeset 529092 | 86c478d114ec223ecf3698ddbb52977bf786f10c |
parent 529091 | 5c64c1dfb318158b4d5ffffa04e48686d150d29e |
child 529093 | 49d286ded8dc4e2f5ec011605db317de729969fe |
push id | 37404 |
push user | csabou@mozilla.com |
push date | Mon, 11 May 2020 21:47:06 +0000 |
treeherder | mozilla-central@61a83cc0b74b [default view] [failures only] |
perfherder | [talos] [build metrics] [platform microbench] (compared to previous push) |
reviewers | smaug |
bugs | 1618287 |
milestone | 78.0a1 |
first release with | nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
|
last release without | nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
|
--- a/netwerk/base/nsURIHashKey.h +++ b/netwerk/base/nsURIHashKey.h @@ -16,25 +16,31 @@ /** * Hashtable key class to use with nsTHashtable/nsBaseHashtable */ class nsURIHashKey : public PLDHashEntryHdr { public: typedef nsIURI* KeyType; typedef const nsIURI* KeyTypePointer; + nsURIHashKey() { MOZ_COUNT_CTOR(nsURIHashKey); } explicit nsURIHashKey(const nsIURI* aKey) : mKey(const_cast<nsIURI*>(aKey)) { MOZ_COUNT_CTOR(nsURIHashKey); } nsURIHashKey(nsURIHashKey&& toMove) : PLDHashEntryHdr(std::move(toMove)), mKey(std::move(toMove.mKey)) { MOZ_COUNT_CTOR(nsURIHashKey); } MOZ_COUNTED_DTOR(nsURIHashKey) + nsURIHashKey& operator=(const nsURIHashKey& aOther) { + mKey = aOther.mKey; + return *this; + } + nsIURI* GetKey() const { return mKey; } bool KeyEquals(const nsIURI* aKey) const { bool eq; if (!mKey) { return !aKey; } if (NS_SUCCEEDED(mKey->Equals(const_cast<nsIURI*>(aKey), &eq))) {
--- a/uriloader/moz.build +++ b/uriloader/moz.build @@ -8,9 +8,10 @@ SPHINX_TREES['/uriloader'] = 'docs' with Files('**'): BUG_COMPONENT = ('Firefox', 'File Handling') DIRS += [ 'base', 'exthandler', 'prefetch', + 'preload', ]
new file mode 100644 --- /dev/null +++ b/uriloader/preload/PreloadHashKey.cpp @@ -0,0 +1,108 @@ +/* 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 "PreloadHashKey.h" + +#include "nsIPrincipal.h" +#include "nsIReferrerInfo.h" + +namespace mozilla { + +PreloadHashKey::PreloadHashKey(const PreloadHashKey* aKey) + : nsURIHashKey(aKey->mKey) { + *this = *aKey; +} + +PreloadHashKey::PreloadHashKey(PreloadHashKey&& aToMove) + : nsURIHashKey(std::move(aToMove)) { + mAs = std::move(aToMove.mAs); + mCORSMode = std::move(aToMove.mCORSMode); + mReferrerPolicy = std::move(aToMove.mReferrerPolicy); + + switch (mAs) { + case ResourceType::SCRIPT: + break; + case ResourceType::STYLE: + break; + case ResourceType::IMAGE: + break; + case ResourceType::FONT: + break; + case ResourceType::FETCH: + break; + case ResourceType::NONE: + break; + } +} + +PreloadHashKey& PreloadHashKey::operator=(const PreloadHashKey& aOther) { + MOZ_ASSERT(mAs == ResourceType::NONE || aOther.mAs == ResourceType::NONE, + "Assigning more than once, only reset is allowed"); + + nsURIHashKey::operator=(aOther); + + mAs = aOther.mAs; + mCORSMode = aOther.mCORSMode; + mReferrerPolicy = aOther.mReferrerPolicy; + + switch (mAs) { + case ResourceType::SCRIPT: + break; + case ResourceType::STYLE: + break; + case ResourceType::IMAGE: + break; + case ResourceType::FONT: + break; + case ResourceType::FETCH: + break; + case ResourceType::NONE: + break; + } + + return *this; +} + +bool PreloadHashKey::KeyEquals(KeyTypePointer aOther) const { + if (mAs != aOther->mAs || mCORSMode != aOther->mCORSMode || + mReferrerPolicy != aOther->mReferrerPolicy) { + return false; + } + + if (!nsURIHashKey::KeyEquals( + static_cast<const nsURIHashKey*>(aOther)->GetKey())) { + return false; + } + + switch (mAs) { + case ResourceType::SCRIPT: + break; + case ResourceType::STYLE: + break; + case ResourceType::IMAGE: + break; + case ResourceType::FONT: + break; + case ResourceType::FETCH: + break; + case ResourceType::NONE: + break; + } + + return true; +} + +// static +PLDHashNumber PreloadHashKey::HashKey(KeyTypePointer aKey) { + PLDHashNumber hash = nsURIHashKey::HashKey(aKey->mKey); + + // Enough to use the common attributes + hash = AddToHash(hash, static_cast<uint32_t>(aKey->mAs)); + hash = AddToHash(hash, static_cast<uint32_t>(aKey->mCORSMode)); + hash = AddToHash(hash, static_cast<uint32_t>(aKey->mReferrerPolicy)); + + return hash; +} + +} // namespace mozilla
new file mode 100644 --- /dev/null +++ b/uriloader/preload/PreloadHashKey.h @@ -0,0 +1,74 @@ +/* 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/. */ + +#ifndef PreloadHashKey_h__ +#define PreloadHashKey_h__ + +#include "mozilla/CORSMode.h" +#include "mozilla/dom/ReferrerPolicyBinding.h" +#include "nsURIHashKey.h" + +class nsIPrincipal; +class nsIReferrerInfo; + +namespace mozilla { + +/** + * This key is used for coalescing and lookup of preloading or regular + * speculative loads. It consists of: + * - the resource type, which is the value of the 'as' attribute + * - the URI of the resource + * - set of attributes that is common to all resource types + * - resource-type-specific attributes that we use to distinguish loads that has + * to be treated separately, some of these attributes may remain at their + * default values + */ +class PreloadHashKey : public nsURIHashKey { + public: + enum class ResourceType : uint8_t { NONE, SCRIPT, STYLE, IMAGE, FONT, FETCH }; + + typedef PreloadHashKey* KeyType; + typedef const PreloadHashKey* KeyTypePointer; + + PreloadHashKey() = default; + explicit PreloadHashKey(const PreloadHashKey* aKey); + PreloadHashKey(PreloadHashKey&& aToMove); + ~PreloadHashKey() = default; + + PreloadHashKey& operator=(const PreloadHashKey& aOther); + + // TODO + // static CreateAsScript(...); + // static CreateAsStyle(...); + // static CreateAsImage(...); + // static CreateAsFont(...); + // static CreateAsFetch(...); + + KeyType GetKey() const { return const_cast<PreloadHashKey*>(this); } + KeyTypePointer GetKeyPointer() const { return this; } + static KeyTypePointer KeyToPointer(KeyType aKey) { return aKey; } + + bool KeyEquals(KeyTypePointer aOther) const; + static PLDHashNumber HashKey(KeyTypePointer aKey); + +#ifdef MOZILLA_INTERNAL_API + size_t SizeOfExcludingThis(mozilla::MallocSizeOf aMallocSizeOf) const { + // Bug 1627752 + return 0; + } +#endif + + enum { ALLOW_MEMMOVE = true }; + + private: + // Attributes common to all resource types + ResourceType mAs = ResourceType::NONE; + + CORSMode mCORSMode = CORS_NONE; + enum dom::ReferrerPolicy mReferrerPolicy = dom::ReferrerPolicy::_empty; +}; + +} // namespace mozilla + +#endif
new file mode 100644 --- /dev/null +++ b/uriloader/preload/PreloaderBase.cpp @@ -0,0 +1,227 @@ +/* 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 "PreloaderBase.h" + +#include "mozilla/dom/Document.h" +#include "nsIAsyncVerifyRedirectCallback.h" +#include "nsIChannel.h" +#include "nsILoadGroup.h" +#include "nsIInterfaceRequestorUtils.h" + +namespace mozilla { + +PreloaderBase::RedirectSink::RedirectSink(PreloaderBase* aPreloader, + nsIInterfaceRequestor* aCallbacks) + : mPreloader(new nsMainThreadPtrHolder<PreloaderBase>( + "RedirectSink.mPreloader", aPreloader)), + mCallbacks(aCallbacks) {} + +NS_IMPL_ISUPPORTS(PreloaderBase::RedirectSink, nsIInterfaceRequestor, + nsIChannelEventSink, nsIRedirectResultListener) + +NS_IMETHODIMP PreloaderBase::RedirectSink::AsyncOnChannelRedirect( + nsIChannel* aOldChannel, nsIChannel* aNewChannel, uint32_t aFlags, + nsIAsyncVerifyRedirectCallback* aCallback) { + mRedirectChannel = aNewChannel; + + if (mCallbacks) { + nsCOMPtr<nsIChannelEventSink> sink(do_GetInterface(mCallbacks)); + if (sink) { + return sink->AsyncOnChannelRedirect(aOldChannel, aNewChannel, aFlags, + aCallback); + } + } + + aCallback->OnRedirectVerifyCallback(NS_OK); + return NS_OK; +} + +NS_IMETHODIMP PreloaderBase::RedirectSink::OnRedirectResult(bool proceeding) { + if (proceeding && mRedirectChannel) { + mPreloader->mChannel = std::move(mRedirectChannel); + } else { + mRedirectChannel = nullptr; + } + + if (mCallbacks) { + nsCOMPtr<nsIRedirectResultListener> sink(do_GetInterface(mCallbacks)); + if (sink) { + return sink->OnRedirectResult(proceeding); + } + } + + return NS_OK; +} + +NS_IMETHODIMP PreloaderBase::RedirectSink::GetInterface(const nsIID& aIID, + void** aResult) { + NS_ENSURE_ARG_POINTER(aResult); + + if (aIID.Equals(NS_GET_IID(nsIChannelEventSink)) || + aIID.Equals(NS_GET_IID(nsIRedirectResultListener))) { + return QueryInterface(aIID, aResult); + } + + if (mCallbacks) { + return mCallbacks->GetInterface(aIID, aResult); + } + + *aResult = nullptr; + return NS_ERROR_NO_INTERFACE; +} + +PreloaderBase::~PreloaderBase() { MOZ_ASSERT(NS_IsMainThread()); } + +// static +void PreloaderBase::AddLoadBackgroundFlag(nsIChannel* aChannel) { + nsLoadFlags loadFlags; + aChannel->GetLoadFlags(&loadFlags); + aChannel->SetLoadFlags(loadFlags | nsIRequest::LOAD_BACKGROUND); +} + +void PreloaderBase::NotifyOpen(PreloadHashKey* aKey, nsIChannel* aChannel, + dom::Document* aDocument, bool aIsPreload) { + // * Register this preload in document's preload service. + + mChannel = aChannel; + mKey = *aKey; + mIsUsed = !aIsPreload; + + // * Start the usage timer if aIsPreload. + + nsCOMPtr<nsIInterfaceRequestor> callbacks; + mChannel->GetNotificationCallbacks(getter_AddRefs(callbacks)); + RefPtr<RedirectSink> sink(new RedirectSink(this, callbacks)); + mChannel->SetNotificationCallbacks(sink); +} + +void PreloaderBase::NotifyUsage() { + if (!mIsUsed && mChannel) { + nsLoadFlags loadFlags; + mChannel->GetLoadFlags(&loadFlags); + + // Preloads are initially set the LOAD_BACKGROUND flag. When becoming + // regular loads by hitting its consuming tag, we need to drop that flag, + // which also means to re-add the request from/to it's loadgroup to reflect + // that flag change. + if (loadFlags & nsIRequest::LOAD_BACKGROUND) { + nsCOMPtr<nsILoadGroup> loadGroup; + mChannel->GetLoadGroup(getter_AddRefs(loadGroup)); + + if (loadGroup) { + nsresult status; + mChannel->GetStatus(&status); + + nsresult rv = loadGroup->RemoveRequest(mChannel, nullptr, status); + mChannel->SetLoadFlags(loadFlags & ~nsIRequest::LOAD_BACKGROUND); + if (NS_SUCCEEDED(rv)) { + loadGroup->AddRequest(mChannel, nullptr); + } + } + } + } + + mIsUsed = true; + + // * Cancel the usage timer. +} + +void PreloaderBase::NotifyRestart(dom::Document* aDocument, + PreloaderBase* aNewPreloader) { + // * Deregister this preload from Document's preloads + mKey = PreloadHashKey(); + + if (aNewPreloader) { + aNewPreloader->mNodes = std::move(mNodes); + } +} + +void PreloaderBase::NotifyStart(nsIRequest* aRequest) { + if (!SameCOMIdentity(aRequest, mChannel)) { + return; + } + + nsCOMPtr<nsIHttpChannel> httpChannel = do_QueryInterface(mChannel); + if (!httpChannel) { + return; + } + + // if the load is cross origin without CORS, or the CORS access is rejected, + // always fire load event to avoid leaking site information. + nsresult rv; + nsCOMPtr<nsILoadInfo> loadInfo = httpChannel->LoadInfo(); + mShouldFireLoadEvent = + loadInfo->GetTainting() == LoadTainting::Opaque || + (loadInfo->GetTainting() == LoadTainting::CORS && + (NS_FAILED(httpChannel->GetStatus(&rv)) || NS_FAILED(rv))); +} + +void PreloaderBase::NotifyStop(nsIRequest* aRequest, nsresult aStatus) { + // Filter out notifications that may be arriving from the old channel before + // restarting this request. + if (!SameCOMIdentity(aRequest, mChannel)) { + return; + } + + NotifyStop(aStatus); +} + +void PreloaderBase::NotifyStop(nsresult aStatus) { + mOnStopStatus.emplace(aStatus); + + nsTArray<nsWeakPtr> nodes; + nodes.SwapElements(mNodes); + + for (nsWeakPtr& weak : nodes) { + nsCOMPtr<nsINode> node = do_QueryReferent(weak); + if (node) { + NotifyNodeEvent(node); + } + } + + mChannel = nullptr; +} + +void PreloaderBase::NotifyValidating() { mOnStopStatus.reset(); } + +void PreloaderBase::NotifyValidated(nsresult aStatus) { + NotifyStop(nullptr, aStatus); +} + +void PreloaderBase::AddLinkPreloadNode(nsINode* aNode) { + if (mOnStopStatus) { + return NotifyNodeEvent(aNode); + } + + mNodes.AppendElement(do_GetWeakReference(aNode)); +} + +void PreloaderBase::RemoveLinkPreloadNode(nsINode* aNode) { + // Note that do_GetWeakReference returns the internal weak proxy, which is + // always the same, so we can use it to search the array using default + // comparator. + nsWeakPtr node = do_GetWeakReference(aNode); + mNodes.RemoveElement(node); + + if (mNodes.Length() == 0 && !mIsUsed) { + // * Deregister this preload from document's preloads + + if (mChannel) { + mChannel->Cancel(NS_BINDING_ABORTED); + } + } +} + +void PreloaderBase::NotifyNodeEvent(nsINode* aNode) { + // * Notify load or error event on the node +} + +nsresult PreloaderBase::AsyncConsume(nsIStreamListener* aListener) { + // We want to return an error so that consumers can't ever use a preload to + // consume data unless it's properly implemented. + return NS_ERROR_NOT_IMPLEMENTED; +} + +} // namespace mozilla
new file mode 100644 --- /dev/null +++ b/uriloader/preload/PreloaderBase.h @@ -0,0 +1,166 @@ +/* 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/. */ + +#ifndef PreloaderBase_h__ +#define PreloaderBase_h__ + +#include "mozilla/Maybe.h" +#include "mozilla/PreloadHashKey.h" +#include "mozilla/WeakPtr.h" +#include "nsIChannelEventSink.h" +#include "nsIInterfaceRequestor.h" +#include "nsIRedirectResultListener.h" +#include "nsIURI.h" +#include "nsTArray.h" +#include "nsProxyRelease.h" +#include "nsWeakReference.h" + +class nsIChannel; +class nsINode; +class nsIRequest; +class nsIStreamListener; + +namespace mozilla { + +namespace dom { + +class Document; + +} // namespace dom + +/** + * A half-abstract base class that resource loaders' respective + * channel-listening classes should derive from. Provides a unified API to + * register the load or preload in a document scoped service, links <link + * rel="preload"> DOM nodes with the load progress and provides API to possibly + * consume the data by later, dynamically discovered consumers. + * + * This class is designed to be used only on the main thread. + */ +class PreloaderBase : public SupportsWeakPtr<PreloaderBase>, + public nsISupports { + public: + MOZ_DECLARE_WEAKREFERENCE_TYPENAME(PreloaderBase) + PreloaderBase() = default; + + // Called by resource loaders to register this preload in the document's + // preload service to provide coalescing, and access to the preload when it + // should be used for an actual load. + void NotifyOpen(PreloadHashKey* aKey, nsIChannel* aChannel, + dom::Document* aDocument, bool aIsPreload); + + // Called when the load is about to be started all over again and thus this + // PreloaderBase will be registered again with the same key. This method + // taks care to deregister this preload prior to that. + // @param aNewPreloader: If there is a new request and loader created for the + // restarted load, we will pass any necessary information into it. + void NotifyRestart(dom::Document* aDocument, + PreloaderBase* aNewPreloader = nullptr); + + // Called by the loading object when the channel started to load + // (OnStartRequest or equal) and when it finished (OnStopRequest or equal) + void NotifyStart(nsIRequest* aRequest); + void NotifyStop(nsIRequest* aRequest, nsresult aStatus); + + // Called when this currently existing load has to be asynchronously + // revalidated before it can be used. This prevents link preload DOM nodes + // being notified until the validation is resolved. + void NotifyValidating(); + // Called when the validation process has been done. This will notify + // associated link DOM nodes. + void NotifyValidated(nsresult aStatus); + + // Called by resource loaders or any suitable component to notify the preload + // has been used for an actual load. This is intended to stop any usage + // timers. + void NotifyUsage(); + // Whether this preloader has been used for a regular/actual load or not. + bool IsUsed() const { return mIsUsed; } + + // When a loader starting an actual load finds a preload, the data can be + // delivered using this method. It will deliver stream listener notifications + // as if it were coming from the resource loading channel. The |request| + // argument will be the channel that loaded/loads the resource. + // This method must keep to the nsIChannel.AsyncOpen contract. A loader is + // not obligated to re-implement this method when not necessarily needed. + virtual nsresult AsyncConsume(nsIStreamListener* aListener); + + // Accessor to the resource loading channel. + nsIChannel* Channel() const { return mChannel; } + + // May change priority of the resource loading channel so that it's treated as + // preload when this was initially representing a normal speculative load but + // later <link rel="preload"> was found for this resource. + virtual void PrioritizeAsPreload() = 0; + + // Helper function to set the LOAD_BACKGROUND flag on channel initiated by + // <link rel=preload>. This MUST be used before the channel is AsyncOpen'ed. + static void AddLoadBackgroundFlag(nsIChannel* aChannel); + + // These are linking this preload to <link rel="preload"> DOM nodes. If we + // are already loaded, immediately notify events on the node, otherwise wait + // for NotifyStop() call. + void AddLinkPreloadNode(nsINode* aNode); + void RemoveLinkPreloadNode(nsINode* aNode); + + protected: + virtual ~PreloaderBase(); + + private: + void NotifyStop(nsresult aStatus); + void NotifyNodeEvent(nsINode* aNode); + + // A helper class that will update the PreloaderBase.mChannel member when a + // redirect happens, so that we can reprioritize or cancel when needed. + // Having a separate class instead of implementing this on PreloaderBase + // directly is to keep PreloaderBase as simple as possible so that derived + // classes don't have to deal with calling super when implementing these + // interfaces from some reason as well. + class RedirectSink final : public nsIInterfaceRequestor, + public nsIChannelEventSink, + public nsIRedirectResultListener { + RedirectSink() = delete; + virtual ~RedirectSink() = default; + + public: + NS_DECL_THREADSAFE_ISUPPORTS + NS_DECL_NSIINTERFACEREQUESTOR + NS_DECL_NSICHANNELEVENTSINK + NS_DECL_NSIREDIRECTRESULTLISTENER + + RedirectSink(PreloaderBase* aPreloader, nsIInterfaceRequestor* aCallbacks); + + private: + nsMainThreadPtrHandle<PreloaderBase> mPreloader; + nsCOMPtr<nsIInterfaceRequestor> mCallbacks; + nsCOMPtr<nsIChannel> mRedirectChannel; + }; + + private: + // Reference to HTMLLinkElement DOM nodes to deliver onload and onerror + // notifications to. + nsTArray<nsWeakPtr> mNodes; + + // The loading channel. This will update when a redirect occurs. + nsCOMPtr<nsIChannel> mChannel; + + // The key this preload has been registered under. We want to remember it to + // be able to deregister itself from the document's preloads. + PreloadHashKey mKey; + + // This overrides the final event we send to DOM nodes to be always 'load'. + // Modified in NotifyStart based on LoadInfo data of the loading channel. + bool mShouldFireLoadEvent = false; + + // True after call to NotifyUsage. + bool mIsUsed = false; + + // Emplaced when the data delivery has finished, in NotifyStop, holds the + // result of the load. + Maybe<nsresult> mOnStopStatus; +}; + +} // namespace mozilla + +#endif // !PreloaderBase_h__
new file mode 100644 --- /dev/null +++ b/uriloader/preload/moz.build @@ -0,0 +1,20 @@ +# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*- +# vim: set filetype=python: +# 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/. + +with Files("**"): + BUG_COMPONENT = ("Core", "Networking") + +EXPORTS.mozilla += [ + 'PreloaderBase.h', + 'PreloadHashKey.h', +] + +UNIFIED_SOURCES += [ + 'PreloaderBase.cpp', + 'PreloadHashKey.cpp', +] + +FINAL_LIBRARY = 'xul'