зеркало из https://github.com/mozilla/gecko-dev.git
Bug 1618535 - Implement preloading for as="fetch" type r=kershaw,baku
Depends on D69860 Differential Revision: https://phabricator.services.mozilla.com/D69628
This commit is contained in:
Родитель
3f250fba44
Коммит
9e027ce534
|
@ -121,6 +121,10 @@ void nsHtml5SpeculativeLoad::Perform(nsHtml5TreeOpExecutor* aExecutor) {
|
|||
case eSpeculativeLoadPreconnect:
|
||||
aExecutor->Preconnect(mUrlOrSizes, mCrossOriginOrMedia);
|
||||
break;
|
||||
case eSpeculativeLoadFetch:
|
||||
aExecutor->PreloadFetch(mUrlOrSizes, mCrossOriginOrMedia,
|
||||
mReferrerPolicyOrIntegrity);
|
||||
break;
|
||||
default:
|
||||
MOZ_ASSERT_UNREACHABLE("Bogus speculative load.");
|
||||
break;
|
||||
|
|
|
@ -30,7 +30,8 @@ enum eHtml5SpeculativeLoad {
|
|||
eSpeculativeLoadManifest,
|
||||
eSpeculativeLoadSetDocumentCharset,
|
||||
eSpeculativeLoadSetDocumentMode,
|
||||
eSpeculativeLoadPreconnect
|
||||
eSpeculativeLoadPreconnect,
|
||||
eSpeculativeLoadFetch
|
||||
};
|
||||
|
||||
class nsHtml5SpeculativeLoad {
|
||||
|
@ -91,6 +92,25 @@ class nsHtml5SpeculativeLoad {
|
|||
mIsLinkPreload = aLinkPreload;
|
||||
}
|
||||
|
||||
inline void InitFetch(nsHtml5String aUrl, nsHtml5String aCrossOrigin,
|
||||
nsHtml5String aReferrerPolicy) {
|
||||
MOZ_ASSERT(mOpCode == eSpeculativeLoadUninitialized,
|
||||
"Trying to reinitialize a speculative load!");
|
||||
mOpCode = eSpeculativeLoadFetch;
|
||||
aUrl.ToString(mUrlOrSizes);
|
||||
aCrossOrigin.ToString(mCrossOriginOrMedia);
|
||||
nsString
|
||||
referrerPolicy; // Not Auto, because using it to hold nsStringBuffer*
|
||||
aReferrerPolicy.ToString(referrerPolicy);
|
||||
mReferrerPolicyOrIntegrity.Assign(
|
||||
nsContentUtils::TrimWhitespace<nsContentUtils::IsHTMLWhitespace>(
|
||||
referrerPolicy));
|
||||
|
||||
// This method can be only be triggered by <link rel=preload type=fetch>,
|
||||
// hence this operation is always a preload.
|
||||
mIsLinkPreload = true;
|
||||
}
|
||||
|
||||
// <picture> elements have multiple <source> nodes followed by an <img>,
|
||||
// where we use the first valid source, which may be the img. Because we
|
||||
// can't determine validity at this point without parsing CSS and getting
|
||||
|
|
|
@ -313,6 +313,9 @@ nsIContentHandle* nsHtml5TreeBuilder::createElement(
|
|||
nsHtml5AttributeName::ATTR_IMAGESIZES);
|
||||
mSpeculativeLoadQueue.AppendElement()->InitImage(
|
||||
url, crossOrigin, referrerPolicy, srcset, sizes, true);
|
||||
} else if (as.LowerCaseEqualsASCII("fetch")) {
|
||||
mSpeculativeLoadQueue.AppendElement()->InitFetch(
|
||||
url, crossOrigin, referrerPolicy);
|
||||
}
|
||||
// Other "as" values will be supported later.
|
||||
}
|
||||
|
|
|
@ -1031,6 +1031,17 @@ void nsHtml5TreeOpExecutor::PreloadPictureSource(const nsAString& aSrcset,
|
|||
mDocument->PreloadPictureImageSource(aSrcset, aSizes, aType, aMedia);
|
||||
}
|
||||
|
||||
void nsHtml5TreeOpExecutor::PreloadFetch(const nsAString& aURL,
|
||||
const nsAString& aCrossOrigin,
|
||||
const nsAString& aReferrerPolicy) {
|
||||
nsCOMPtr<nsIURI> uri = ConvertIfNotPreloadedYet(aURL);
|
||||
if (!uri) {
|
||||
return;
|
||||
}
|
||||
|
||||
mDocument->Preloads().PreloadFetch(uri, aCrossOrigin, aReferrerPolicy);
|
||||
}
|
||||
|
||||
void nsHtml5TreeOpExecutor::PreloadOpenPicture() {
|
||||
mDocument->PreloadPictureOpened();
|
||||
}
|
||||
|
|
|
@ -252,6 +252,9 @@ class nsHtml5TreeOpExecutor final
|
|||
void PreloadPictureSource(const nsAString& aSrcset, const nsAString& aSizes,
|
||||
const nsAString& aType, const nsAString& aMedia);
|
||||
|
||||
void PreloadFetch(const nsAString& aURL, const nsAString& aCrossOrigin,
|
||||
const nsAString& aReferrerPolicy);
|
||||
|
||||
void SetSpeculationBase(const nsAString& aURL);
|
||||
|
||||
void UpdateReferrerInfoFromMeta(const nsAString& aMetaReferrer);
|
||||
|
|
|
@ -0,0 +1,327 @@
|
|||
/* -*- 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 "FetchPreloader.h"
|
||||
|
||||
#include "mozilla/DebugOnly.h"
|
||||
#include "mozilla/dom/Document.h"
|
||||
#include "mozilla/LoadInfo.h"
|
||||
#include "mozilla/ScopeExit.h"
|
||||
#include "mozilla/Unused.h"
|
||||
#include "nsContentPolicyUtils.h"
|
||||
#include "nsContentUtils.h"
|
||||
#include "nsIChannel.h"
|
||||
#include "nsIClassOfService.h"
|
||||
#include "nsIHttpChannel.h"
|
||||
#include "nsITimedChannel.h"
|
||||
#include "nsNetUtil.h"
|
||||
#include "nsStringStream.h"
|
||||
|
||||
namespace mozilla {
|
||||
|
||||
NS_IMPL_ISUPPORTS(FetchPreloader, nsIStreamListener, nsIRequestObserver)
|
||||
|
||||
FetchPreloader::FetchPreloader()
|
||||
: FetchPreloader(nsIContentPolicy::TYPE_INTERNAL_XMLHTTPREQUEST) {}
|
||||
|
||||
FetchPreloader::FetchPreloader(nsContentPolicyType aContentPolicyType)
|
||||
: mContentPolicyType(aContentPolicyType) {}
|
||||
|
||||
nsresult FetchPreloader::OpenChannel(PreloadHashKey* aKey, nsIURI* aURI,
|
||||
const CORSMode aCORSMode,
|
||||
const dom::ReferrerPolicy& aReferrerPolicy,
|
||||
dom::Document* aDocument) {
|
||||
nsresult rv;
|
||||
nsCOMPtr<nsIChannel> channel;
|
||||
|
||||
auto notify = MakeScopeExit([&]() {
|
||||
if (NS_WARN_IF(NS_FAILED(rv)) && channel) {
|
||||
// Make sure we notify any <link preload> elements when opening because of
|
||||
// various technical or security reasons fails.
|
||||
NotifyStart(channel);
|
||||
NotifyStop(channel, rv);
|
||||
}
|
||||
});
|
||||
|
||||
rv = CheckContentPolicy(aURI, aDocument);
|
||||
if (NS_FAILED(rv)) {
|
||||
return rv;
|
||||
}
|
||||
|
||||
nsCOMPtr<nsILoadGroup> loadGroup = aDocument->GetDocumentLoadGroup();
|
||||
nsCOMPtr<nsPIDOMWindowOuter> window = aDocument->GetWindow();
|
||||
nsCOMPtr<nsIInterfaceRequestor> prompter;
|
||||
if (window) {
|
||||
nsIDocShell* docshell = window->GetDocShell();
|
||||
prompter = do_QueryInterface(docshell);
|
||||
}
|
||||
|
||||
rv = CreateChannel(getter_AddRefs(channel), aURI, aCORSMode, aReferrerPolicy,
|
||||
aDocument, loadGroup, prompter);
|
||||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
|
||||
PrioritizeAsPreload(channel);
|
||||
AddLoadBackgroundFlag(channel);
|
||||
|
||||
NotifyOpen(aKey, channel, aDocument, true);
|
||||
|
||||
return mAsyncConsumeResult = rv = channel->AsyncOpen(this);
|
||||
}
|
||||
|
||||
nsresult FetchPreloader::CreateChannel(
|
||||
nsIChannel** aChannel, nsIURI* aURI, const CORSMode aCORSMode,
|
||||
const dom::ReferrerPolicy& aReferrerPolicy, dom::Document* aDocument,
|
||||
nsILoadGroup* aLoadGroup, nsIInterfaceRequestor* aCallbacks) {
|
||||
nsresult rv;
|
||||
|
||||
nsSecurityFlags securityFlags =
|
||||
aCORSMode == CORS_NONE ? nsILoadInfo::SEC_ALLOW_CROSS_ORIGIN_DATA_IS_NULL
|
||||
: nsILoadInfo::SEC_REQUIRE_CORS_DATA_INHERITS;
|
||||
if (aCORSMode == CORS_ANONYMOUS) {
|
||||
securityFlags |= nsILoadInfo::SEC_COOKIES_SAME_ORIGIN;
|
||||
} else if (aCORSMode == CORS_USE_CREDENTIALS) {
|
||||
securityFlags |= nsILoadInfo::SEC_COOKIES_INCLUDE;
|
||||
}
|
||||
|
||||
nsCOMPtr<nsIChannel> channel;
|
||||
rv = NS_NewChannelWithTriggeringPrincipal(
|
||||
getter_AddRefs(channel), aURI, aDocument, aDocument->NodePrincipal(),
|
||||
securityFlags, nsIContentPolicy::TYPE_FETCH, nullptr, aLoadGroup,
|
||||
aCallbacks);
|
||||
if (NS_FAILED(rv)) {
|
||||
return rv;
|
||||
}
|
||||
|
||||
if (nsCOMPtr<nsIHttpChannel> httpChannel = do_QueryInterface(channel)) {
|
||||
nsCOMPtr<nsIReferrerInfo> referrerInfo = new dom::ReferrerInfo(
|
||||
aDocument->GetDocumentURIAsReferrer(), aReferrerPolicy);
|
||||
rv = httpChannel->SetReferrerInfoWithoutClone(referrerInfo);
|
||||
MOZ_ASSERT(NS_SUCCEEDED(rv));
|
||||
}
|
||||
|
||||
if (nsCOMPtr<nsITimedChannel> timedChannel = do_QueryInterface(channel)) {
|
||||
timedChannel->SetInitiatorType(NS_LITERAL_STRING("link"));
|
||||
}
|
||||
|
||||
channel.forget(aChannel);
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
nsresult FetchPreloader::CheckContentPolicy(nsIURI* aURI,
|
||||
dom::Document* aDocument) {
|
||||
if (!aDocument) {
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
nsCOMPtr<nsILoadInfo> secCheckLoadInfo = new net::LoadInfo(
|
||||
aDocument->NodePrincipal(), aDocument->NodePrincipal(), aDocument,
|
||||
nsILoadInfo::SEC_ONLY_FOR_EXPLICIT_CONTENTSEC_CHECK, mContentPolicyType);
|
||||
|
||||
int16_t shouldLoad = nsIContentPolicy::ACCEPT;
|
||||
nsresult rv = NS_CheckContentLoadPolicy(aURI, secCheckLoadInfo,
|
||||
EmptyCString(), &shouldLoad,
|
||||
nsContentUtils::GetContentPolicy());
|
||||
if (NS_SUCCEEDED(rv) && NS_CP_ACCEPTED(shouldLoad)) {
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
return NS_ERROR_CONTENT_BLOCKED;
|
||||
}
|
||||
|
||||
// PreloaderBase
|
||||
|
||||
nsresult FetchPreloader::AsyncConsume(nsIStreamListener* aListener) {
|
||||
if (NS_FAILED(mAsyncConsumeResult)) {
|
||||
// Already being consumed or failed to open.
|
||||
return mAsyncConsumeResult;
|
||||
}
|
||||
|
||||
// Prevent duplicate calls.
|
||||
mAsyncConsumeResult = NS_ERROR_NOT_AVAILABLE;
|
||||
|
||||
if (!mConsumeListener) {
|
||||
// Called before we are getting response from the channel.
|
||||
mConsumeListener = aListener;
|
||||
} else {
|
||||
// Channel already started, push cached calls to this listener.
|
||||
// Can't be anything else than the `Cache`, hence a safe static_cast.
|
||||
Cache* cache = static_cast<Cache*>(mConsumeListener.get());
|
||||
cache->AsyncConsume(aListener);
|
||||
}
|
||||
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
// static
|
||||
void FetchPreloader::PrioritizeAsPreload(nsIChannel* aChannel) {
|
||||
if (nsCOMPtr<nsIClassOfService> cos = do_QueryInterface(aChannel)) {
|
||||
cos->AddClassFlags(nsIClassOfService::Unblocked);
|
||||
}
|
||||
}
|
||||
|
||||
void FetchPreloader::PrioritizeAsPreload() { PrioritizeAsPreload(Channel()); }
|
||||
|
||||
// nsIRequestObserver + nsIStreamListener
|
||||
|
||||
NS_IMETHODIMP FetchPreloader::OnStartRequest(nsIRequest* request) {
|
||||
NotifyStart(request);
|
||||
|
||||
if (!mConsumeListener) {
|
||||
// AsyncConsume not called yet.
|
||||
mConsumeListener = new Cache();
|
||||
}
|
||||
|
||||
return mConsumeListener->OnStartRequest(request);
|
||||
}
|
||||
|
||||
NS_IMETHODIMP FetchPreloader::OnDataAvailable(nsIRequest* request,
|
||||
nsIInputStream* input,
|
||||
uint64_t offset, uint32_t count) {
|
||||
return mConsumeListener->OnDataAvailable(request, input, offset, count);
|
||||
}
|
||||
|
||||
NS_IMETHODIMP FetchPreloader::OnStopRequest(nsIRequest* request,
|
||||
nsresult status) {
|
||||
mConsumeListener->OnStopRequest(request, status);
|
||||
|
||||
// We want 404 or other types of server responses to be treated as 'error'.
|
||||
if (nsCOMPtr<nsIHttpChannel> httpChannel = do_QueryInterface(request)) {
|
||||
uint32_t responseStatus = 0;
|
||||
Unused << httpChannel->GetResponseStatus(&responseStatus);
|
||||
if (responseStatus / 100 != 2) {
|
||||
status = NS_ERROR_FAILURE;
|
||||
}
|
||||
}
|
||||
|
||||
NotifyStop(request, status);
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
// FetchPreloader::Cache
|
||||
|
||||
NS_IMPL_ISUPPORTS(FetchPreloader::Cache, nsIStreamListener, nsIRequestObserver)
|
||||
|
||||
NS_IMETHODIMP FetchPreloader::Cache::OnStartRequest(nsIRequest* request) {
|
||||
mRequest = request;
|
||||
|
||||
if (mFinalListener) {
|
||||
return mFinalListener->OnStartRequest(mRequest);
|
||||
}
|
||||
|
||||
mCalls.AppendElement(Call{VariantIndex<0>{}, StartRequest{}});
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
NS_IMETHODIMP FetchPreloader::Cache::OnDataAvailable(nsIRequest* request,
|
||||
nsIInputStream* input,
|
||||
uint64_t offset,
|
||||
uint32_t count) {
|
||||
if (mFinalListener) {
|
||||
return mFinalListener->OnDataAvailable(mRequest, input, offset, count);
|
||||
}
|
||||
|
||||
DataAvailable data;
|
||||
if (!data.mData.SetLength(count, fallible)) {
|
||||
return NS_ERROR_OUT_OF_MEMORY;
|
||||
}
|
||||
|
||||
uint32_t read;
|
||||
nsresult rv = input->Read(data.mData.BeginWriting(), count, &read);
|
||||
if (NS_FAILED(rv)) {
|
||||
return rv;
|
||||
}
|
||||
|
||||
mCalls.AppendElement(Call{VariantIndex<1>{}, std::move(data)});
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
NS_IMETHODIMP FetchPreloader::Cache::OnStopRequest(nsIRequest* request,
|
||||
nsresult status) {
|
||||
if (mFinalListener) {
|
||||
return mFinalListener->OnStopRequest(mRequest, status);
|
||||
}
|
||||
|
||||
mCalls.AppendElement(Call{VariantIndex<2>{}, StopRequest{status}});
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
void FetchPreloader::Cache::AsyncConsume(nsIStreamListener* aListener) {
|
||||
// Must dispatch for two reasons:
|
||||
// 1. This is called directly from FetchLoader::AsyncConsume, which must
|
||||
// behave the same way as nsIChannel::AsyncOpen.
|
||||
// 2. In case there are already stream listener events scheduled on the main
|
||||
// thread we preserve the order - those will still end up in Cache.
|
||||
|
||||
// * The `Cache` object is fully main thread only for now, doesn't support
|
||||
// retargeting, but it can be improved to allow it.
|
||||
|
||||
nsCOMPtr<nsIStreamListener> listener(aListener);
|
||||
NS_DispatchToMainThread(NewRunnableMethod<nsCOMPtr<nsIStreamListener>>(
|
||||
"FetchPreloader::Cache::Consume", this, &FetchPreloader::Cache::Consume,
|
||||
listener));
|
||||
}
|
||||
|
||||
void FetchPreloader::Cache::Consume(nsCOMPtr<nsIStreamListener> aListener) {
|
||||
MOZ_ASSERT(!mFinalListener, "Duplicate call");
|
||||
|
||||
mFinalListener = std::move(aListener);
|
||||
|
||||
// Status of the channel read after each call.
|
||||
nsresult status = NS_OK;
|
||||
nsCOMPtr<nsIChannel> channel(do_QueryInterface(mRequest));
|
||||
|
||||
RefPtr<Cache> self(this);
|
||||
for (auto& call : mCalls) {
|
||||
nsresult rv = call.match(
|
||||
[&](const StartRequest& startRequest) mutable {
|
||||
return self->mFinalListener->OnStartRequest(self->mRequest);
|
||||
},
|
||||
[&](const DataAvailable& dataAvailable) mutable {
|
||||
if (NS_FAILED(status)) {
|
||||
// Channel has been cancelled during this mCalls loop.
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
nsCOMPtr<nsIInputStream> input;
|
||||
rv = NS_NewCStringInputStream(getter_AddRefs(input),
|
||||
dataAvailable.mData);
|
||||
if (NS_FAILED(rv)) {
|
||||
return rv;
|
||||
}
|
||||
|
||||
return self->mFinalListener->OnDataAvailable(
|
||||
self->mRequest, input, 0, dataAvailable.mData.Length());
|
||||
},
|
||||
[&](const StopRequest& stopRequest) {
|
||||
// First cancellation overrides mStatus in nsHttpChannel.
|
||||
nsresult stopStatus =
|
||||
NS_FAILED(status) ? status : stopRequest.mStatus;
|
||||
self->mFinalListener->OnStopRequest(self->mRequest, stopStatus);
|
||||
self->mFinalListener = nullptr;
|
||||
self->mRequest = nullptr;
|
||||
return NS_OK;
|
||||
});
|
||||
|
||||
if (!mRequest) {
|
||||
// We are done!
|
||||
break;
|
||||
}
|
||||
|
||||
bool isCancelled = false;
|
||||
Unused << channel->GetCanceled(&isCancelled);
|
||||
if (isCancelled) {
|
||||
mRequest->GetStatus(&status);
|
||||
} else if (NS_FAILED(rv)) {
|
||||
status = rv;
|
||||
mRequest->Cancel(status);
|
||||
}
|
||||
}
|
||||
|
||||
mCalls.Clear();
|
||||
}
|
||||
|
||||
} // namespace mozilla
|
|
@ -0,0 +1,93 @@
|
|||
/* -*- 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/. */
|
||||
|
||||
#ifndef FetchPreloader_h_
|
||||
#define FetchPreloader_h_
|
||||
|
||||
#include "mozilla/PreloaderBase.h"
|
||||
#include "nsCOMPtr.h"
|
||||
#include "nsIAsyncOutputStream.h"
|
||||
#include "nsIAsyncInputStream.h"
|
||||
#include "nsIStreamListener.h"
|
||||
|
||||
class nsIChannel;
|
||||
class nsILoadGroup;
|
||||
class nsIInterfaceRequestor;
|
||||
|
||||
namespace mozilla {
|
||||
|
||||
class FetchPreloader : public PreloaderBase, public nsIStreamListener {
|
||||
NS_DECL_ISUPPORTS
|
||||
NS_DECL_NSIREQUESTOBSERVER
|
||||
NS_DECL_NSISTREAMLISTENER
|
||||
|
||||
FetchPreloader();
|
||||
nsresult OpenChannel(PreloadHashKey* aKey, nsIURI* aURI,
|
||||
const CORSMode aCORSMode,
|
||||
const dom::ReferrerPolicy& aReferrerPolicy,
|
||||
dom::Document* aDocument);
|
||||
|
||||
// PreloaderBase
|
||||
nsresult AsyncConsume(nsIStreamListener* aListener) override;
|
||||
static void PrioritizeAsPreload(nsIChannel* aChannel);
|
||||
void PrioritizeAsPreload() override;
|
||||
|
||||
protected:
|
||||
explicit FetchPreloader(nsContentPolicyType aContentPolicyType);
|
||||
virtual ~FetchPreloader() = default;
|
||||
|
||||
// Create and setup the channel with necessary security properties. This is
|
||||
// overridable by subclasses to allow different initial conditions.
|
||||
virtual nsresult CreateChannel(nsIChannel** aChannel, nsIURI* aURI,
|
||||
const CORSMode aCORSMode,
|
||||
const dom::ReferrerPolicy& aReferrerPolicy,
|
||||
dom::Document* aDocument,
|
||||
nsILoadGroup* aLoadGroup,
|
||||
nsIInterfaceRequestor* aCallbacks);
|
||||
|
||||
private:
|
||||
nsresult CheckContentPolicy(nsIURI* aURI, dom::Document* aDocument);
|
||||
|
||||
class Cache final : public nsIStreamListener {
|
||||
NS_DECL_ISUPPORTS
|
||||
NS_DECL_NSIREQUESTOBSERVER
|
||||
NS_DECL_NSISTREAMLISTENER
|
||||
|
||||
void AsyncConsume(nsIStreamListener* aListener);
|
||||
void Consume(nsCOMPtr<nsIStreamListener> aListener);
|
||||
|
||||
private:
|
||||
virtual ~Cache() = default;
|
||||
|
||||
struct StartRequest {};
|
||||
struct DataAvailable {
|
||||
nsCString mData;
|
||||
};
|
||||
struct StopRequest {
|
||||
nsresult mStatus;
|
||||
};
|
||||
|
||||
typedef Variant<StartRequest, DataAvailable, StopRequest> Call;
|
||||
nsCOMPtr<nsIRequest> mRequest;
|
||||
nsCOMPtr<nsIStreamListener> mFinalListener;
|
||||
nsTArray<Call> mCalls;
|
||||
};
|
||||
|
||||
// The listener passed to AsyncConsume in case we haven't started getting the
|
||||
// data from the channel yet.
|
||||
nsCOMPtr<nsIStreamListener> mConsumeListener;
|
||||
|
||||
// Returned by AsyncConsume when a failure. This remembers the result of
|
||||
// opening the channel and prevents duplicate consumation.
|
||||
nsresult mAsyncConsumeResult = NS_ERROR_NOT_AVAILABLE;
|
||||
|
||||
// The CP type to check against. Derived classes have to override call to CSP
|
||||
// constructor of this class.
|
||||
nsContentPolicyType mContentPolicyType;
|
||||
};
|
||||
|
||||
} // namespace mozilla
|
||||
|
||||
#endif
|
|
@ -135,6 +135,25 @@ PreloadHashKey PreloadHashKey::CreateAsImage(
|
|||
return key;
|
||||
}
|
||||
|
||||
// static
|
||||
PreloadHashKey PreloadHashKey::CreateAsFetch(
|
||||
nsIURI* aURI, const CORSMode aCORSMode,
|
||||
const dom::ReferrerPolicy& aReferrerPolicy) {
|
||||
PreloadHashKey key(aURI, ResourceType::FETCH);
|
||||
key.mReferrerPolicy = aReferrerPolicy;
|
||||
key.mCORSMode = aCORSMode;
|
||||
|
||||
return key;
|
||||
}
|
||||
|
||||
// static
|
||||
PreloadHashKey PreloadHashKey::CreateAsFetch(
|
||||
nsIURI* aURI, const nsAString& aCrossOrigin,
|
||||
const dom::ReferrerPolicy& aReferrerPolicy) {
|
||||
return CreateAsFetch(aURI, dom::Element::StringToCORSMode(aCrossOrigin),
|
||||
aReferrerPolicy);
|
||||
}
|
||||
|
||||
bool PreloadHashKey::KeyEquals(KeyTypePointer aOther) const {
|
||||
if (mAs != aOther->mAs || mCORSMode != aOther->mCORSMode ||
|
||||
mReferrerPolicy != aOther->mReferrerPolicy) {
|
||||
|
@ -181,6 +200,7 @@ bool PreloadHashKey::KeyEquals(KeyTypePointer aOther) const {
|
|||
case ResourceType::FONT:
|
||||
break;
|
||||
case ResourceType::FETCH:
|
||||
// No more checks needed.
|
||||
break;
|
||||
case ResourceType::NONE:
|
||||
break;
|
||||
|
|
|
@ -61,9 +61,16 @@ class PreloadHashKey : public nsURIHashKey {
|
|||
nsIURI* aURI, nsIPrincipal* aPrincipal, CORSMode aCORSMode,
|
||||
dom::ReferrerPolicy const& aReferrerPolicy);
|
||||
|
||||
// Construct key for "fetch"
|
||||
static PreloadHashKey CreateAsFetch(
|
||||
nsIURI* aURI, const CORSMode aCORSMode,
|
||||
const dom::ReferrerPolicy& aReferrerPolicy);
|
||||
static PreloadHashKey CreateAsFetch(
|
||||
nsIURI* aURI, const nsAString& aCrossOrigin,
|
||||
const dom::ReferrerPolicy& aReferrerPolicy);
|
||||
|
||||
// TODO
|
||||
// static CreateAsFont(...);
|
||||
// static CreateAsFetch(...);
|
||||
|
||||
KeyType GetKey() const { return const_cast<PreloadHashKey*>(this); }
|
||||
KeyTypePointer GetKeyPointer() const { return this; }
|
||||
|
|
|
@ -5,10 +5,12 @@
|
|||
|
||||
#include "PreloadService.h"
|
||||
|
||||
#include "FetchPreloader.h"
|
||||
#include "mozilla/AsyncEventDispatcher.h"
|
||||
#include "mozilla/dom/HTMLLinkElement.h"
|
||||
#include "mozilla/dom/ScriptLoader.h"
|
||||
#include "mozilla/StaticPrefs_network.h"
|
||||
#include "nsIReferrerInfo.h"
|
||||
#include "nsNetUtil.h"
|
||||
|
||||
namespace mozilla {
|
||||
|
@ -112,6 +114,9 @@ already_AddRefed<PreloaderBase> PreloadService::PreloadLinkElement(
|
|||
preloadKey = PreloadHashKey::CreateAsImage(
|
||||
uri, mDocument->NodePrincipal(),
|
||||
dom::Element::StringToCORSMode(crossOrigin), referrerPolicy);
|
||||
} else if (as.LowerCaseEqualsASCII("fetch")) {
|
||||
preloadKey = PreloadHashKey::CreateAsFetch(
|
||||
uri, dom::Element::StringToCORSMode(crossOrigin), referrerPolicy);
|
||||
} else {
|
||||
NotifyNodeEvent(aLinkElement, false);
|
||||
return nullptr;
|
||||
|
@ -126,6 +131,8 @@ already_AddRefed<PreloaderBase> PreloadService::PreloadLinkElement(
|
|||
PreloadStyle(uri, charset, crossOrigin, referrerPolicyAttr, integrity);
|
||||
} else if (as.LowerCaseEqualsASCII("image")) {
|
||||
PreloadImage(uri, crossOrigin, referrerPolicyAttr, isImgSet);
|
||||
} else if (as.LowerCaseEqualsASCII("fetch")) {
|
||||
PreloadFetch(uri, crossOrigin, referrerPolicyAttr);
|
||||
}
|
||||
|
||||
preload = LookupPreload(&preloadKey);
|
||||
|
@ -168,6 +175,19 @@ void PreloadService::PreloadImage(nsIURI* aURI, const nsAString& aCrossOrigin,
|
|||
aIsImgSet, true);
|
||||
}
|
||||
|
||||
void PreloadService::PreloadFetch(nsIURI* aURI, const nsAString& aCrossOrigin,
|
||||
const nsAString& aReferrerPolicy) {
|
||||
CORSMode cors = dom::Element::StringToCORSMode(aCrossOrigin);
|
||||
dom::ReferrerPolicy referrerPolicy = PreloadReferrerPolicy(aReferrerPolicy);
|
||||
auto key = PreloadHashKey::CreateAsFetch(aURI, cors, referrerPolicy);
|
||||
|
||||
// * Bug 1618549: Depending on where we decide to do the deduplication, we may
|
||||
// want to check if a fetch is already being preloaded here.
|
||||
|
||||
RefPtr<FetchPreloader> preloader = new FetchPreloader();
|
||||
preloader->OpenChannel(&key, aURI, cors, referrerPolicy, mDocument);
|
||||
}
|
||||
|
||||
// static
|
||||
void PreloadService::NotifyNodeEvent(nsINode* aNode, bool aSuccess) {
|
||||
if (!aNode->IsInComposedDoc()) {
|
||||
|
|
|
@ -75,6 +75,9 @@ class PreloadService {
|
|||
void PreloadImage(nsIURI* aURI, const nsAString& aCrossOrigin,
|
||||
const nsAString& aImageReferrerPolicy, bool aIsImgSet);
|
||||
|
||||
void PreloadFetch(nsIURI* aURI, const nsAString& aCrossOrigin,
|
||||
const nsAString& aReferrerPolicy);
|
||||
|
||||
static void NotifyNodeEvent(nsINode* aNode, bool aSuccess);
|
||||
|
||||
private:
|
||||
|
|
|
@ -0,0 +1,952 @@
|
|||
#include "gtest/gtest.h"
|
||||
|
||||
#include "mozilla/CORSMode.h"
|
||||
#include "mozilla/dom/XMLDocument.h"
|
||||
#include "mozilla/dom/ReferrerPolicyBinding.h"
|
||||
#include "mozilla/FetchPreloader.h"
|
||||
#include "mozilla/Maybe.h"
|
||||
#include "mozilla/PreloadHashKey.h"
|
||||
#include "nsNetUtil.h"
|
||||
#include "nsIChannel.h"
|
||||
#include "nsIStreamListener.h"
|
||||
#include "nsThreadUtils.h"
|
||||
#include "nsStringStream.h"
|
||||
|
||||
namespace {
|
||||
|
||||
auto const ERROR_CANCEL = NS_ERROR_ABORT;
|
||||
auto const ERROR_ONSTOP = NS_ERROR_NET_INTERRUPT;
|
||||
auto const ERROR_THROW = NS_ERROR_FAILURE;
|
||||
|
||||
class FakeChannel : public nsIChannel {
|
||||
public:
|
||||
NS_DECL_ISUPPORTS
|
||||
NS_DECL_NSICHANNEL
|
||||
NS_DECL_NSIREQUEST
|
||||
|
||||
nsresult Start() { return mListener->OnStartRequest(this); }
|
||||
nsresult Data(const nsACString& aData) {
|
||||
if (NS_FAILED(mStatus)) {
|
||||
return mStatus;
|
||||
}
|
||||
nsCOMPtr<nsIInputStream> is;
|
||||
NS_NewCStringInputStream(getter_AddRefs(is), aData);
|
||||
return mListener->OnDataAvailable(this, is, 0, aData.Length());
|
||||
}
|
||||
nsresult Stop(nsresult status) {
|
||||
if (NS_SUCCEEDED(mStatus)) {
|
||||
mStatus = status;
|
||||
}
|
||||
mListener->OnStopRequest(this, mStatus);
|
||||
mListener = nullptr;
|
||||
return mStatus;
|
||||
}
|
||||
|
||||
private:
|
||||
virtual ~FakeChannel() = default;
|
||||
bool mIsPending = false;
|
||||
bool mCanceled = false;
|
||||
nsresult mStatus = NS_OK;
|
||||
nsCOMPtr<nsIStreamListener> mListener;
|
||||
};
|
||||
|
||||
NS_IMPL_ISUPPORTS(FakeChannel, nsIChannel, nsIRequest)
|
||||
|
||||
NS_IMETHODIMP FakeChannel::GetName(nsACString& result) { return NS_OK; }
|
||||
NS_IMETHODIMP FakeChannel::IsPending(bool* result) {
|
||||
*result = mIsPending;
|
||||
return NS_OK;
|
||||
}
|
||||
NS_IMETHODIMP FakeChannel::GetStatus(nsresult* status) {
|
||||
*status = mStatus;
|
||||
return NS_OK;
|
||||
}
|
||||
NS_IMETHODIMP FakeChannel::Cancel(nsresult status) {
|
||||
if (!mCanceled) {
|
||||
mCanceled = true;
|
||||
mStatus = status;
|
||||
}
|
||||
return NS_OK;
|
||||
}
|
||||
NS_IMETHODIMP FakeChannel::Suspend() { return NS_OK; }
|
||||
NS_IMETHODIMP FakeChannel::Resume() { return NS_OK; }
|
||||
NS_IMETHODIMP FakeChannel::GetLoadFlags(nsLoadFlags* aLoadFlags) {
|
||||
*aLoadFlags = 0;
|
||||
return NS_OK;
|
||||
}
|
||||
NS_IMETHODIMP FakeChannel::SetLoadFlags(nsLoadFlags aLoadFlags) {
|
||||
return NS_OK;
|
||||
}
|
||||
NS_IMETHODIMP FakeChannel::GetTRRMode(nsIRequest::TRRMode* aTRRMode) {
|
||||
return NS_ERROR_NOT_IMPLEMENTED;
|
||||
}
|
||||
NS_IMETHODIMP FakeChannel::SetTRRMode(nsIRequest::TRRMode aTRRMode) {
|
||||
return NS_ERROR_NOT_IMPLEMENTED;
|
||||
}
|
||||
NS_IMETHODIMP FakeChannel::GetLoadGroup(nsILoadGroup** aLoadGroup) {
|
||||
return NS_OK;
|
||||
}
|
||||
NS_IMETHODIMP FakeChannel::SetLoadGroup(nsILoadGroup* aLoadGroup) {
|
||||
return NS_OK;
|
||||
}
|
||||
NS_IMETHODIMP FakeChannel::GetOriginalURI(nsIURI** aURI) { return NS_OK; }
|
||||
NS_IMETHODIMP FakeChannel::SetOriginalURI(nsIURI* aURI) { return NS_OK; }
|
||||
NS_IMETHODIMP FakeChannel::GetURI(nsIURI** aURI) { return NS_OK; }
|
||||
NS_IMETHODIMP FakeChannel::GetOwner(nsISupports** aOwner) { return NS_OK; }
|
||||
NS_IMETHODIMP FakeChannel::SetOwner(nsISupports* aOwner) { return NS_OK; }
|
||||
NS_IMETHODIMP FakeChannel::SetLoadInfo(nsILoadInfo* aLoadInfo) { return NS_OK; }
|
||||
NS_IMETHODIMP FakeChannel::GetLoadInfo(nsILoadInfo** aLoadInfo) {
|
||||
return NS_OK;
|
||||
}
|
||||
NS_IMETHODIMP FakeChannel::GetIsDocument(bool* aIsDocument) {
|
||||
*aIsDocument = false;
|
||||
return NS_OK;
|
||||
}
|
||||
NS_IMETHODIMP FakeChannel::GetNotificationCallbacks(
|
||||
nsIInterfaceRequestor** aCallbacks) {
|
||||
return NS_OK;
|
||||
}
|
||||
NS_IMETHODIMP FakeChannel::SetNotificationCallbacks(
|
||||
nsIInterfaceRequestor* aCallbacks) {
|
||||
return NS_OK;
|
||||
}
|
||||
NS_IMETHODIMP FakeChannel::GetSecurityInfo(nsISupports** aSecurityInfo) {
|
||||
return NS_OK;
|
||||
}
|
||||
NS_IMETHODIMP FakeChannel::GetContentType(nsACString& aContentType) {
|
||||
return NS_OK;
|
||||
}
|
||||
NS_IMETHODIMP FakeChannel::SetContentType(const nsACString& aContentType) {
|
||||
return NS_OK;
|
||||
}
|
||||
NS_IMETHODIMP FakeChannel::GetContentCharset(nsACString& aContentCharset) {
|
||||
return NS_OK;
|
||||
}
|
||||
NS_IMETHODIMP FakeChannel::SetContentCharset(
|
||||
const nsACString& aContentCharset) {
|
||||
return NS_OK;
|
||||
}
|
||||
NS_IMETHODIMP FakeChannel::GetContentDisposition(
|
||||
uint32_t* aContentDisposition) {
|
||||
return NS_OK;
|
||||
}
|
||||
NS_IMETHODIMP FakeChannel::SetContentDisposition(uint32_t aContentDisposition) {
|
||||
return NS_OK;
|
||||
}
|
||||
NS_IMETHODIMP FakeChannel::GetContentDispositionFilename(
|
||||
nsAString& aContentDispositionFilename) {
|
||||
return NS_OK;
|
||||
}
|
||||
NS_IMETHODIMP FakeChannel::SetContentDispositionFilename(
|
||||
const nsAString& aContentDispositionFilename) {
|
||||
return NS_OK;
|
||||
}
|
||||
NS_IMETHODIMP FakeChannel::GetContentDispositionHeader(
|
||||
nsACString& aContentDispositionHeader) {
|
||||
return NS_ERROR_NOT_AVAILABLE;
|
||||
}
|
||||
NS_IMETHODIMP FakeChannel::GetContentLength(int64_t* aContentLength) {
|
||||
return NS_OK;
|
||||
}
|
||||
NS_IMETHODIMP FakeChannel::SetContentLength(int64_t aContentLength) {
|
||||
return NS_OK;
|
||||
}
|
||||
NS_IMETHODIMP FakeChannel::GetCanceled(bool* aCanceled) {
|
||||
*aCanceled = mCanceled;
|
||||
return NS_OK;
|
||||
}
|
||||
NS_IMETHODIMP FakeChannel::Open(nsIInputStream** aStream) {
|
||||
return NS_ERROR_NOT_IMPLEMENTED;
|
||||
}
|
||||
NS_IMETHODIMP
|
||||
FakeChannel::AsyncOpen(nsIStreamListener* aListener) {
|
||||
mIsPending = true;
|
||||
mListener = aListener;
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
class FakePreloader : public mozilla::FetchPreloader {
|
||||
public:
|
||||
explicit FakePreloader(FakeChannel* aChannel) : mDrivingChannel(aChannel) {}
|
||||
|
||||
private:
|
||||
RefPtr<FakeChannel> mDrivingChannel;
|
||||
|
||||
virtual nsresult CreateChannel(
|
||||
nsIChannel** aChannel, nsIURI* aURI, const mozilla::CORSMode aCORSMode,
|
||||
const mozilla::dom::ReferrerPolicy& aReferrerPolicy,
|
||||
mozilla::dom::Document* aDocument, nsILoadGroup* aLoadGroup,
|
||||
nsIInterfaceRequestor* aCallbacks) override {
|
||||
mDrivingChannel.forget(aChannel);
|
||||
return NS_OK;
|
||||
}
|
||||
};
|
||||
|
||||
class FakeListener : public nsIStreamListener {
|
||||
NS_DECL_ISUPPORTS
|
||||
NS_DECL_NSIREQUESTOBSERVER
|
||||
NS_DECL_NSISTREAMLISTENER
|
||||
|
||||
enum { Never, OnStart, OnData, OnStop } mCancelIn = Never;
|
||||
|
||||
nsresult mOnStartResult = NS_OK;
|
||||
nsresult mOnDataResult = NS_OK;
|
||||
nsresult mOnStopResult = NS_OK;
|
||||
|
||||
bool mOnStart = false;
|
||||
nsCString mOnData;
|
||||
Maybe<nsresult> mOnStop;
|
||||
|
||||
private:
|
||||
virtual ~FakeListener() = default;
|
||||
};
|
||||
|
||||
NS_IMPL_ISUPPORTS(FakeListener, nsIStreamListener, nsIRequestObserver)
|
||||
|
||||
NS_IMETHODIMP FakeListener::OnStartRequest(nsIRequest* request) {
|
||||
EXPECT_FALSE(mOnStart);
|
||||
mOnStart = true;
|
||||
|
||||
if (mCancelIn == OnStart) {
|
||||
request->Cancel(ERROR_CANCEL);
|
||||
}
|
||||
|
||||
return mOnStartResult;
|
||||
}
|
||||
NS_IMETHODIMP FakeListener::OnDataAvailable(nsIRequest* request,
|
||||
nsIInputStream* input,
|
||||
uint64_t offset, uint32_t count) {
|
||||
nsAutoCString data;
|
||||
data.SetLength(count);
|
||||
|
||||
uint32_t read;
|
||||
input->Read(data.BeginWriting(), count, &read);
|
||||
mOnData += data;
|
||||
|
||||
if (mCancelIn == OnData) {
|
||||
request->Cancel(ERROR_CANCEL);
|
||||
}
|
||||
|
||||
return mOnDataResult;
|
||||
}
|
||||
NS_IMETHODIMP FakeListener::OnStopRequest(nsIRequest* request,
|
||||
nsresult status) {
|
||||
EXPECT_FALSE(mOnStop);
|
||||
mOnStop.emplace(status);
|
||||
|
||||
if (mCancelIn == OnStop) {
|
||||
request->Cancel(ERROR_CANCEL);
|
||||
}
|
||||
|
||||
return mOnStopResult;
|
||||
}
|
||||
|
||||
bool eventInProgress = true;
|
||||
|
||||
void Await() {
|
||||
MOZ_ALWAYS_TRUE(mozilla::SpinEventLoopUntil([&]() {
|
||||
bool yield = !eventInProgress;
|
||||
eventInProgress = true; // Just for convenience
|
||||
return yield;
|
||||
}));
|
||||
}
|
||||
|
||||
void Yield() { eventInProgress = false; }
|
||||
|
||||
} // namespace
|
||||
|
||||
// ****************************************************************************
|
||||
// Test body
|
||||
// ****************************************************************************
|
||||
|
||||
// Caching with all good results (data + NS_OK)
|
||||
TEST(TestFetchPreloader, CacheNoneBeforeConsume)
|
||||
{
|
||||
nsCOMPtr<nsIURI> uri;
|
||||
NS_NewURI(getter_AddRefs(uri), NS_LITERAL_CSTRING("https://example.com"));
|
||||
auto key = mozilla::PreloadHashKey::CreateAsFetch(
|
||||
uri, mozilla::CORS_NONE, mozilla::dom::ReferrerPolicy::_empty);
|
||||
|
||||
RefPtr<FakeChannel> channel = new FakeChannel();
|
||||
RefPtr<FakePreloader> preloader = new FakePreloader(channel);
|
||||
RefPtr<mozilla::dom::Document> doc;
|
||||
NS_NewXMLDocument(getter_AddRefs(doc));
|
||||
|
||||
EXPECT_TRUE(NS_SUCCEEDED(
|
||||
preloader->OpenChannel(&key, uri, mozilla::CORS_NONE,
|
||||
mozilla::dom::ReferrerPolicy::_empty, doc)));
|
||||
|
||||
RefPtr<FakeListener> listener = new FakeListener();
|
||||
EXPECT_TRUE(NS_SUCCEEDED(preloader->AsyncConsume(listener)));
|
||||
EXPECT_FALSE(NS_SUCCEEDED(preloader->AsyncConsume(listener)));
|
||||
|
||||
EXPECT_TRUE(NS_SUCCEEDED(channel->Start()));
|
||||
EXPECT_TRUE(NS_SUCCEEDED(channel->Data(NS_LITERAL_CSTRING("one"))));
|
||||
EXPECT_TRUE(NS_SUCCEEDED(channel->Data(NS_LITERAL_CSTRING("two"))));
|
||||
EXPECT_TRUE(NS_SUCCEEDED(channel->Data(NS_LITERAL_CSTRING("three"))));
|
||||
EXPECT_TRUE(NS_SUCCEEDED(channel->Stop(NS_OK)));
|
||||
|
||||
NS_DispatchToMainThread(NS_NewRunnableFunction("test", [&]() {
|
||||
EXPECT_TRUE(listener->mOnStart);
|
||||
EXPECT_TRUE(listener->mOnData.EqualsLiteral("onetwothree"));
|
||||
EXPECT_TRUE(listener->mOnStop && *listener->mOnStop == NS_OK);
|
||||
|
||||
Yield();
|
||||
}));
|
||||
|
||||
Await();
|
||||
|
||||
EXPECT_FALSE(NS_SUCCEEDED(preloader->AsyncConsume(listener)));
|
||||
}
|
||||
|
||||
TEST(TestFetchPreloader, CacheStartBeforeConsume)
|
||||
{
|
||||
nsCOMPtr<nsIURI> uri;
|
||||
NS_NewURI(getter_AddRefs(uri), NS_LITERAL_CSTRING("https://example.com"));
|
||||
auto key = mozilla::PreloadHashKey::CreateAsFetch(
|
||||
uri, mozilla::CORS_NONE, mozilla::dom::ReferrerPolicy::_empty);
|
||||
|
||||
RefPtr<FakeChannel> channel = new FakeChannel();
|
||||
RefPtr<FakePreloader> preloader = new FakePreloader(channel);
|
||||
RefPtr<mozilla::dom::Document> doc;
|
||||
NS_NewXMLDocument(getter_AddRefs(doc));
|
||||
|
||||
EXPECT_TRUE(NS_SUCCEEDED(
|
||||
preloader->OpenChannel(&key, uri, mozilla::CORS_NONE,
|
||||
mozilla::dom::ReferrerPolicy::_empty, doc)));
|
||||
|
||||
EXPECT_TRUE(NS_SUCCEEDED(channel->Start()));
|
||||
|
||||
RefPtr<FakeListener> listener = new FakeListener();
|
||||
EXPECT_TRUE(NS_SUCCEEDED(preloader->AsyncConsume(listener)));
|
||||
EXPECT_FALSE(NS_SUCCEEDED(preloader->AsyncConsume(listener)));
|
||||
|
||||
NS_DispatchToMainThread(NS_NewRunnableFunction("test", [&]() {
|
||||
EXPECT_TRUE(listener->mOnStart);
|
||||
|
||||
EXPECT_TRUE(NS_SUCCEEDED(channel->Data(NS_LITERAL_CSTRING("one"))));
|
||||
EXPECT_TRUE(NS_SUCCEEDED(channel->Data(NS_LITERAL_CSTRING("two"))));
|
||||
EXPECT_TRUE(NS_SUCCEEDED(channel->Data(NS_LITERAL_CSTRING("three"))));
|
||||
EXPECT_TRUE(listener->mOnData.EqualsLiteral("onetwothree"));
|
||||
|
||||
EXPECT_TRUE(NS_SUCCEEDED(channel->Stop(NS_OK)));
|
||||
EXPECT_TRUE(listener->mOnStop && *listener->mOnStop == NS_OK);
|
||||
|
||||
Yield();
|
||||
}));
|
||||
|
||||
Await();
|
||||
|
||||
EXPECT_FALSE(NS_SUCCEEDED(preloader->AsyncConsume(listener)));
|
||||
}
|
||||
|
||||
TEST(TestFetchPreloader, CachePartOfDataBeforeConsume)
|
||||
{
|
||||
nsCOMPtr<nsIURI> uri;
|
||||
NS_NewURI(getter_AddRefs(uri), NS_LITERAL_CSTRING("https://example.com"));
|
||||
auto key = mozilla::PreloadHashKey::CreateAsFetch(
|
||||
uri, mozilla::CORS_NONE, mozilla::dom::ReferrerPolicy::_empty);
|
||||
|
||||
RefPtr<FakeChannel> channel = new FakeChannel();
|
||||
RefPtr<FakePreloader> preloader = new FakePreloader(channel);
|
||||
RefPtr<mozilla::dom::Document> doc;
|
||||
NS_NewXMLDocument(getter_AddRefs(doc));
|
||||
|
||||
EXPECT_TRUE(NS_SUCCEEDED(
|
||||
preloader->OpenChannel(&key, uri, mozilla::CORS_NONE,
|
||||
mozilla::dom::ReferrerPolicy::_empty, doc)));
|
||||
|
||||
EXPECT_TRUE(NS_SUCCEEDED(channel->Start()));
|
||||
EXPECT_TRUE(NS_SUCCEEDED(channel->Data(NS_LITERAL_CSTRING("one"))));
|
||||
EXPECT_TRUE(NS_SUCCEEDED(channel->Data(NS_LITERAL_CSTRING("two"))));
|
||||
|
||||
RefPtr<FakeListener> listener = new FakeListener();
|
||||
EXPECT_TRUE(NS_SUCCEEDED(preloader->AsyncConsume(listener)));
|
||||
EXPECT_FALSE(NS_SUCCEEDED(preloader->AsyncConsume(listener)));
|
||||
|
||||
NS_DispatchToMainThread(NS_NewRunnableFunction("test", [&]() {
|
||||
EXPECT_TRUE(listener->mOnStart);
|
||||
|
||||
EXPECT_TRUE(NS_SUCCEEDED(channel->Data(NS_LITERAL_CSTRING("three"))));
|
||||
EXPECT_TRUE(listener->mOnData.EqualsLiteral("onetwothree"));
|
||||
|
||||
EXPECT_TRUE(NS_SUCCEEDED(channel->Stop(NS_OK)));
|
||||
EXPECT_TRUE(listener->mOnStop && *listener->mOnStop == NS_OK);
|
||||
|
||||
Yield();
|
||||
}));
|
||||
|
||||
Await();
|
||||
|
||||
EXPECT_FALSE(NS_SUCCEEDED(preloader->AsyncConsume(listener)));
|
||||
}
|
||||
|
||||
TEST(TestFetchPreloader, CacheAllDataBeforeConsume)
|
||||
{
|
||||
nsCOMPtr<nsIURI> uri;
|
||||
NS_NewURI(getter_AddRefs(uri), NS_LITERAL_CSTRING("https://example.com"));
|
||||
auto key = mozilla::PreloadHashKey::CreateAsFetch(
|
||||
uri, mozilla::CORS_NONE, mozilla::dom::ReferrerPolicy::_empty);
|
||||
|
||||
RefPtr<FakeChannel> channel = new FakeChannel();
|
||||
RefPtr<FakePreloader> preloader = new FakePreloader(channel);
|
||||
RefPtr<mozilla::dom::Document> doc;
|
||||
NS_NewXMLDocument(getter_AddRefs(doc));
|
||||
|
||||
EXPECT_TRUE(NS_SUCCEEDED(
|
||||
preloader->OpenChannel(&key, uri, mozilla::CORS_NONE,
|
||||
mozilla::dom::ReferrerPolicy::_empty, doc)));
|
||||
|
||||
EXPECT_TRUE(NS_SUCCEEDED(channel->Start()));
|
||||
EXPECT_TRUE(NS_SUCCEEDED(channel->Data(NS_LITERAL_CSTRING("one"))));
|
||||
EXPECT_TRUE(NS_SUCCEEDED(channel->Data(NS_LITERAL_CSTRING("two"))));
|
||||
EXPECT_TRUE(NS_SUCCEEDED(channel->Data(NS_LITERAL_CSTRING("three"))));
|
||||
|
||||
// Request consumation of the preload...
|
||||
RefPtr<FakeListener> listener = new FakeListener();
|
||||
EXPECT_TRUE(NS_SUCCEEDED(preloader->AsyncConsume(listener)));
|
||||
EXPECT_FALSE(NS_SUCCEEDED(preloader->AsyncConsume(listener)));
|
||||
|
||||
NS_DispatchToMainThread(NS_NewRunnableFunction("test", [&]() {
|
||||
EXPECT_TRUE(listener->mOnStart);
|
||||
EXPECT_TRUE(listener->mOnData.EqualsLiteral("onetwothree"));
|
||||
|
||||
EXPECT_TRUE(NS_SUCCEEDED(channel->Stop(NS_OK)));
|
||||
EXPECT_TRUE(listener->mOnStop && *listener->mOnStop == NS_OK);
|
||||
|
||||
Yield();
|
||||
}));
|
||||
|
||||
Await();
|
||||
|
||||
EXPECT_FALSE(NS_SUCCEEDED(preloader->AsyncConsume(listener)));
|
||||
}
|
||||
|
||||
TEST(TestFetchPreloader, CacheAllBeforeConsume)
|
||||
{
|
||||
nsCOMPtr<nsIURI> uri;
|
||||
NS_NewURI(getter_AddRefs(uri), NS_LITERAL_CSTRING("https://example.com"));
|
||||
auto key = mozilla::PreloadHashKey::CreateAsFetch(
|
||||
uri, mozilla::CORS_NONE, mozilla::dom::ReferrerPolicy::_empty);
|
||||
|
||||
RefPtr<FakeChannel> channel = new FakeChannel();
|
||||
RefPtr<FakePreloader> preloader = new FakePreloader(channel);
|
||||
RefPtr<mozilla::dom::Document> doc;
|
||||
NS_NewXMLDocument(getter_AddRefs(doc));
|
||||
|
||||
EXPECT_TRUE(NS_SUCCEEDED(
|
||||
preloader->OpenChannel(&key, uri, mozilla::CORS_NONE,
|
||||
mozilla::dom::ReferrerPolicy::_empty, doc)));
|
||||
|
||||
EXPECT_TRUE(NS_SUCCEEDED(channel->Start()));
|
||||
EXPECT_TRUE(NS_SUCCEEDED(channel->Data(NS_LITERAL_CSTRING("one"))));
|
||||
EXPECT_TRUE(NS_SUCCEEDED(channel->Data(NS_LITERAL_CSTRING("two"))));
|
||||
EXPECT_TRUE(NS_SUCCEEDED(channel->Data(NS_LITERAL_CSTRING("three"))));
|
||||
EXPECT_TRUE(NS_SUCCEEDED(channel->Stop(NS_OK)));
|
||||
|
||||
RefPtr<FakeListener> listener = new FakeListener();
|
||||
EXPECT_TRUE(NS_SUCCEEDED(preloader->AsyncConsume(listener)));
|
||||
EXPECT_FALSE(NS_SUCCEEDED(preloader->AsyncConsume(listener)));
|
||||
|
||||
NS_DispatchToMainThread(NS_NewRunnableFunction("test", [&]() {
|
||||
EXPECT_TRUE(listener->mOnStart);
|
||||
EXPECT_TRUE(listener->mOnData.EqualsLiteral("onetwothree"));
|
||||
EXPECT_TRUE(listener->mOnStop && *listener->mOnStop == NS_OK);
|
||||
|
||||
Yield();
|
||||
}));
|
||||
|
||||
Await();
|
||||
|
||||
EXPECT_FALSE(NS_SUCCEEDED(preloader->AsyncConsume(listener)));
|
||||
}
|
||||
|
||||
// Get data before the channel fails
|
||||
TEST(TestFetchPreloader, CacheAllBeforeConsumeWithChannelError)
|
||||
{
|
||||
nsCOMPtr<nsIURI> uri;
|
||||
NS_NewURI(getter_AddRefs(uri), NS_LITERAL_CSTRING("https://example.com"));
|
||||
auto key = mozilla::PreloadHashKey::CreateAsFetch(
|
||||
uri, mozilla::CORS_NONE, mozilla::dom::ReferrerPolicy::_empty);
|
||||
|
||||
RefPtr<FakeChannel> channel = new FakeChannel();
|
||||
RefPtr<FakePreloader> preloader = new FakePreloader(channel);
|
||||
RefPtr<mozilla::dom::Document> doc;
|
||||
NS_NewXMLDocument(getter_AddRefs(doc));
|
||||
|
||||
EXPECT_TRUE(NS_SUCCEEDED(
|
||||
preloader->OpenChannel(&key, uri, mozilla::CORS_NONE,
|
||||
mozilla::dom::ReferrerPolicy::_empty, doc)));
|
||||
|
||||
EXPECT_TRUE(NS_SUCCEEDED(channel->Start()));
|
||||
EXPECT_TRUE(NS_SUCCEEDED(channel->Data(NS_LITERAL_CSTRING("one"))));
|
||||
EXPECT_TRUE(NS_SUCCEEDED(channel->Data(NS_LITERAL_CSTRING("two"))));
|
||||
EXPECT_TRUE(NS_SUCCEEDED(channel->Data(NS_LITERAL_CSTRING("three"))));
|
||||
EXPECT_TRUE(NS_FAILED(channel->Stop(ERROR_ONSTOP)));
|
||||
|
||||
RefPtr<FakeListener> listener = new FakeListener();
|
||||
EXPECT_TRUE(NS_SUCCEEDED(preloader->AsyncConsume(listener)));
|
||||
EXPECT_FALSE(NS_SUCCEEDED(preloader->AsyncConsume(listener)));
|
||||
|
||||
NS_DispatchToMainThread(NS_NewRunnableFunction("test", [&]() {
|
||||
EXPECT_TRUE(listener->mOnStart);
|
||||
EXPECT_TRUE(listener->mOnData.EqualsLiteral("onetwothree"));
|
||||
EXPECT_TRUE(listener->mOnStop && *listener->mOnStop == ERROR_ONSTOP);
|
||||
|
||||
Yield();
|
||||
}));
|
||||
|
||||
Await();
|
||||
|
||||
EXPECT_FALSE(NS_SUCCEEDED(preloader->AsyncConsume(listener)));
|
||||
}
|
||||
|
||||
// Cancel the channel between caching and consuming
|
||||
TEST(TestFetchPreloader, CacheAllBeforeConsumeWithChannelCancel)
|
||||
{
|
||||
nsCOMPtr<nsIURI> uri;
|
||||
NS_NewURI(getter_AddRefs(uri), NS_LITERAL_CSTRING("https://example.com"));
|
||||
auto key = mozilla::PreloadHashKey::CreateAsFetch(
|
||||
uri, mozilla::CORS_NONE, mozilla::dom::ReferrerPolicy::_empty);
|
||||
|
||||
RefPtr<FakeChannel> channel = new FakeChannel();
|
||||
RefPtr<FakePreloader> preloader = new FakePreloader(channel);
|
||||
RefPtr<mozilla::dom::Document> doc;
|
||||
NS_NewXMLDocument(getter_AddRefs(doc));
|
||||
|
||||
EXPECT_TRUE(NS_SUCCEEDED(
|
||||
preloader->OpenChannel(&key, uri, mozilla::CORS_NONE,
|
||||
mozilla::dom::ReferrerPolicy::_empty, doc)));
|
||||
|
||||
EXPECT_TRUE(NS_SUCCEEDED(channel->Start()));
|
||||
EXPECT_TRUE(NS_SUCCEEDED(channel->Data(NS_LITERAL_CSTRING("one"))));
|
||||
EXPECT_TRUE(NS_SUCCEEDED(channel->Data(NS_LITERAL_CSTRING("two"))));
|
||||
channel->Cancel(ERROR_CANCEL);
|
||||
EXPECT_TRUE(NS_FAILED(channel->Stop(ERROR_CANCEL)));
|
||||
|
||||
RefPtr<FakeListener> listener = new FakeListener();
|
||||
EXPECT_TRUE(NS_SUCCEEDED(preloader->AsyncConsume(listener)));
|
||||
EXPECT_FALSE(NS_SUCCEEDED(preloader->AsyncConsume(listener)));
|
||||
|
||||
NS_DispatchToMainThread(NS_NewRunnableFunction("test", [&]() {
|
||||
EXPECT_TRUE(listener->mOnStart);
|
||||
// XXX - This is hard to solve; the data is there but we won't deliver it.
|
||||
// This is a bit different case than e.g. a network error. We want to
|
||||
// deliver some data in that case. Cancellation probably happens because of
|
||||
// navigation or a demand to not consume the channel anyway.
|
||||
EXPECT_TRUE(listener->mOnData.IsEmpty());
|
||||
EXPECT_TRUE(listener->mOnStop && *listener->mOnStop == ERROR_CANCEL);
|
||||
|
||||
Yield();
|
||||
}));
|
||||
|
||||
Await();
|
||||
|
||||
EXPECT_FALSE(NS_SUCCEEDED(preloader->AsyncConsume(listener)));
|
||||
}
|
||||
|
||||
// Let the listener throw while data is already cached
|
||||
TEST(TestFetchPreloader, CacheAllBeforeConsumeThrowFromOnStartRequest)
|
||||
{
|
||||
nsCOMPtr<nsIURI> uri;
|
||||
NS_NewURI(getter_AddRefs(uri), NS_LITERAL_CSTRING("https://example.com"));
|
||||
auto key = mozilla::PreloadHashKey::CreateAsFetch(
|
||||
uri, mozilla::CORS_NONE, mozilla::dom::ReferrerPolicy::_empty);
|
||||
|
||||
RefPtr<FakeChannel> channel = new FakeChannel();
|
||||
RefPtr<FakePreloader> preloader = new FakePreloader(channel);
|
||||
RefPtr<mozilla::dom::Document> doc;
|
||||
NS_NewXMLDocument(getter_AddRefs(doc));
|
||||
|
||||
EXPECT_TRUE(NS_SUCCEEDED(
|
||||
preloader->OpenChannel(&key, uri, mozilla::CORS_NONE,
|
||||
mozilla::dom::ReferrerPolicy::_empty, doc)));
|
||||
|
||||
EXPECT_TRUE(NS_SUCCEEDED(channel->Start()));
|
||||
EXPECT_TRUE(NS_SUCCEEDED(channel->Data(NS_LITERAL_CSTRING("one"))));
|
||||
EXPECT_TRUE(NS_SUCCEEDED(channel->Data(NS_LITERAL_CSTRING("two"))));
|
||||
EXPECT_TRUE(NS_SUCCEEDED(channel->Data(NS_LITERAL_CSTRING("three"))));
|
||||
EXPECT_TRUE(NS_SUCCEEDED(channel->Stop(NS_OK)));
|
||||
|
||||
RefPtr<FakeListener> listener = new FakeListener();
|
||||
listener->mOnStartResult = ERROR_THROW;
|
||||
|
||||
EXPECT_TRUE(NS_SUCCEEDED(preloader->AsyncConsume(listener)));
|
||||
EXPECT_FALSE(NS_SUCCEEDED(preloader->AsyncConsume(listener)));
|
||||
|
||||
NS_DispatchToMainThread(NS_NewRunnableFunction("test", [&]() {
|
||||
EXPECT_TRUE(listener->mOnStart);
|
||||
EXPECT_TRUE(listener->mOnData.IsEmpty());
|
||||
EXPECT_TRUE(listener->mOnStop && *listener->mOnStop == ERROR_THROW);
|
||||
|
||||
Yield();
|
||||
}));
|
||||
|
||||
Await();
|
||||
|
||||
EXPECT_FALSE(NS_SUCCEEDED(preloader->AsyncConsume(listener)));
|
||||
}
|
||||
|
||||
TEST(TestFetchPreloader, CacheAllBeforeConsumeThrowFromOnDataAvailable)
|
||||
{
|
||||
nsCOMPtr<nsIURI> uri;
|
||||
NS_NewURI(getter_AddRefs(uri), NS_LITERAL_CSTRING("https://example.com"));
|
||||
auto key = mozilla::PreloadHashKey::CreateAsFetch(
|
||||
uri, mozilla::CORS_NONE, mozilla::dom::ReferrerPolicy::_empty);
|
||||
|
||||
RefPtr<FakeChannel> channel = new FakeChannel();
|
||||
RefPtr<FakePreloader> preloader = new FakePreloader(channel);
|
||||
RefPtr<mozilla::dom::Document> doc;
|
||||
NS_NewXMLDocument(getter_AddRefs(doc));
|
||||
|
||||
EXPECT_TRUE(NS_SUCCEEDED(
|
||||
preloader->OpenChannel(&key, uri, mozilla::CORS_NONE,
|
||||
mozilla::dom::ReferrerPolicy::_empty, doc)));
|
||||
|
||||
EXPECT_TRUE(NS_SUCCEEDED(channel->Start()));
|
||||
EXPECT_TRUE(NS_SUCCEEDED(channel->Data(NS_LITERAL_CSTRING("one"))));
|
||||
EXPECT_TRUE(NS_SUCCEEDED(channel->Data(NS_LITERAL_CSTRING("two"))));
|
||||
EXPECT_TRUE(NS_SUCCEEDED(channel->Data(NS_LITERAL_CSTRING("three"))));
|
||||
EXPECT_TRUE(NS_SUCCEEDED(channel->Stop(NS_OK)));
|
||||
|
||||
RefPtr<FakeListener> listener = new FakeListener();
|
||||
listener->mOnDataResult = ERROR_THROW;
|
||||
|
||||
EXPECT_TRUE(NS_SUCCEEDED(preloader->AsyncConsume(listener)));
|
||||
EXPECT_FALSE(NS_SUCCEEDED(preloader->AsyncConsume(listener)));
|
||||
|
||||
NS_DispatchToMainThread(NS_NewRunnableFunction("test", [&]() {
|
||||
EXPECT_TRUE(listener->mOnStart);
|
||||
EXPECT_TRUE(listener->mOnData.EqualsLiteral("one"));
|
||||
EXPECT_TRUE(listener->mOnStop && *listener->mOnStop == ERROR_THROW);
|
||||
|
||||
Yield();
|
||||
}));
|
||||
|
||||
Await();
|
||||
|
||||
EXPECT_FALSE(NS_SUCCEEDED(preloader->AsyncConsume(listener)));
|
||||
}
|
||||
|
||||
TEST(TestFetchPreloader, CacheAllBeforeConsumeThrowFromOnStopRequest)
|
||||
{
|
||||
nsCOMPtr<nsIURI> uri;
|
||||
NS_NewURI(getter_AddRefs(uri), NS_LITERAL_CSTRING("https://example.com"));
|
||||
auto key = mozilla::PreloadHashKey::CreateAsFetch(
|
||||
uri, mozilla::CORS_NONE, mozilla::dom::ReferrerPolicy::_empty);
|
||||
|
||||
RefPtr<FakeChannel> channel = new FakeChannel();
|
||||
RefPtr<FakePreloader> preloader = new FakePreloader(channel);
|
||||
RefPtr<mozilla::dom::Document> doc;
|
||||
NS_NewXMLDocument(getter_AddRefs(doc));
|
||||
|
||||
EXPECT_TRUE(NS_SUCCEEDED(
|
||||
preloader->OpenChannel(&key, uri, mozilla::CORS_NONE,
|
||||
mozilla::dom::ReferrerPolicy::_empty, doc)));
|
||||
|
||||
EXPECT_TRUE(NS_SUCCEEDED(channel->Start()));
|
||||
EXPECT_TRUE(NS_SUCCEEDED(channel->Data(NS_LITERAL_CSTRING("one"))));
|
||||
EXPECT_TRUE(NS_SUCCEEDED(channel->Data(NS_LITERAL_CSTRING("two"))));
|
||||
EXPECT_TRUE(NS_SUCCEEDED(channel->Data(NS_LITERAL_CSTRING("three"))));
|
||||
EXPECT_TRUE(NS_SUCCEEDED(channel->Stop(NS_OK)));
|
||||
|
||||
RefPtr<FakeListener> listener = new FakeListener();
|
||||
listener->mOnStopResult = ERROR_THROW;
|
||||
|
||||
EXPECT_TRUE(NS_SUCCEEDED(preloader->AsyncConsume(listener)));
|
||||
EXPECT_FALSE(NS_SUCCEEDED(preloader->AsyncConsume(listener)));
|
||||
|
||||
NS_DispatchToMainThread(NS_NewRunnableFunction("test", [&]() {
|
||||
EXPECT_TRUE(listener->mOnStart);
|
||||
EXPECT_TRUE(listener->mOnData.EqualsLiteral("onetwothree"));
|
||||
// Throwing from OnStopRequest is generally ignored.
|
||||
EXPECT_TRUE(listener->mOnStop && *listener->mOnStop == NS_OK);
|
||||
|
||||
Yield();
|
||||
}));
|
||||
|
||||
Await();
|
||||
|
||||
EXPECT_FALSE(NS_SUCCEEDED(preloader->AsyncConsume(listener)));
|
||||
}
|
||||
|
||||
// Cancel the channel in various callbacks
|
||||
TEST(TestFetchPreloader, CacheAllBeforeConsumeCancelInOnStartRequest)
|
||||
{
|
||||
nsCOMPtr<nsIURI> uri;
|
||||
NS_NewURI(getter_AddRefs(uri), NS_LITERAL_CSTRING("https://example.com"));
|
||||
auto key = mozilla::PreloadHashKey::CreateAsFetch(
|
||||
uri, mozilla::CORS_NONE, mozilla::dom::ReferrerPolicy::_empty);
|
||||
|
||||
RefPtr<FakeChannel> channel = new FakeChannel();
|
||||
RefPtr<FakePreloader> preloader = new FakePreloader(channel);
|
||||
RefPtr<mozilla::dom::Document> doc;
|
||||
NS_NewXMLDocument(getter_AddRefs(doc));
|
||||
|
||||
EXPECT_TRUE(NS_SUCCEEDED(
|
||||
preloader->OpenChannel(&key, uri, mozilla::CORS_NONE,
|
||||
mozilla::dom::ReferrerPolicy::_empty, doc)));
|
||||
|
||||
EXPECT_TRUE(NS_SUCCEEDED(channel->Start()));
|
||||
EXPECT_TRUE(NS_SUCCEEDED(channel->Data(NS_LITERAL_CSTRING("one"))));
|
||||
EXPECT_TRUE(NS_SUCCEEDED(channel->Data(NS_LITERAL_CSTRING("two"))));
|
||||
EXPECT_TRUE(NS_SUCCEEDED(channel->Data(NS_LITERAL_CSTRING("three"))));
|
||||
EXPECT_TRUE(NS_SUCCEEDED(channel->Stop(NS_OK)));
|
||||
|
||||
RefPtr<FakeListener> listener = new FakeListener();
|
||||
listener->mCancelIn = FakeListener::OnStart;
|
||||
// check that throwing from OnStartRequest doesn't affect the cancellation
|
||||
// status.
|
||||
listener->mOnStartResult = ERROR_THROW;
|
||||
|
||||
EXPECT_TRUE(NS_SUCCEEDED(preloader->AsyncConsume(listener)));
|
||||
EXPECT_FALSE(NS_SUCCEEDED(preloader->AsyncConsume(listener)));
|
||||
|
||||
NS_DispatchToMainThread(NS_NewRunnableFunction("test", [&]() {
|
||||
EXPECT_TRUE(listener->mOnStart);
|
||||
EXPECT_TRUE(listener->mOnData.IsEmpty());
|
||||
EXPECT_TRUE(listener->mOnStop && *listener->mOnStop == ERROR_CANCEL);
|
||||
|
||||
Yield();
|
||||
}));
|
||||
|
||||
Await();
|
||||
|
||||
EXPECT_FALSE(NS_SUCCEEDED(preloader->AsyncConsume(listener)));
|
||||
}
|
||||
|
||||
TEST(TestFetchPreloader, CacheAllBeforeConsumeCancelInOnDataAvailable)
|
||||
{
|
||||
nsCOMPtr<nsIURI> uri;
|
||||
NS_NewURI(getter_AddRefs(uri), NS_LITERAL_CSTRING("https://example.com"));
|
||||
auto key = mozilla::PreloadHashKey::CreateAsFetch(
|
||||
uri, mozilla::CORS_NONE, mozilla::dom::ReferrerPolicy::_empty);
|
||||
|
||||
RefPtr<FakeChannel> channel = new FakeChannel();
|
||||
RefPtr<FakePreloader> preloader = new FakePreloader(channel);
|
||||
RefPtr<mozilla::dom::Document> doc;
|
||||
NS_NewXMLDocument(getter_AddRefs(doc));
|
||||
|
||||
EXPECT_TRUE(NS_SUCCEEDED(
|
||||
preloader->OpenChannel(&key, uri, mozilla::CORS_NONE,
|
||||
mozilla::dom::ReferrerPolicy::_empty, doc)));
|
||||
|
||||
EXPECT_TRUE(NS_SUCCEEDED(channel->Start()));
|
||||
EXPECT_TRUE(NS_SUCCEEDED(channel->Data(NS_LITERAL_CSTRING("one"))));
|
||||
EXPECT_TRUE(NS_SUCCEEDED(channel->Data(NS_LITERAL_CSTRING("two"))));
|
||||
EXPECT_TRUE(NS_SUCCEEDED(channel->Data(NS_LITERAL_CSTRING("three"))));
|
||||
EXPECT_TRUE(NS_SUCCEEDED(channel->Stop(NS_OK)));
|
||||
|
||||
RefPtr<FakeListener> listener = new FakeListener();
|
||||
listener->mCancelIn = FakeListener::OnData;
|
||||
// check that throwing from OnStartRequest doesn't affect the cancellation
|
||||
// status.
|
||||
listener->mOnDataResult = ERROR_THROW;
|
||||
|
||||
EXPECT_TRUE(NS_SUCCEEDED(preloader->AsyncConsume(listener)));
|
||||
EXPECT_FALSE(NS_SUCCEEDED(preloader->AsyncConsume(listener)));
|
||||
|
||||
NS_DispatchToMainThread(NS_NewRunnableFunction("test", [&]() {
|
||||
EXPECT_TRUE(listener->mOnStart);
|
||||
EXPECT_TRUE(listener->mOnData.EqualsLiteral("one"));
|
||||
EXPECT_TRUE(listener->mOnStop && *listener->mOnStop == ERROR_CANCEL);
|
||||
|
||||
Yield();
|
||||
}));
|
||||
|
||||
Await();
|
||||
|
||||
EXPECT_FALSE(NS_SUCCEEDED(preloader->AsyncConsume(listener)));
|
||||
}
|
||||
|
||||
// Corner cases
|
||||
TEST(TestFetchPreloader, CachePartlyBeforeConsumeCancelInOnDataAvailable)
|
||||
{
|
||||
nsCOMPtr<nsIURI> uri;
|
||||
NS_NewURI(getter_AddRefs(uri), NS_LITERAL_CSTRING("https://example.com"));
|
||||
auto key = mozilla::PreloadHashKey::CreateAsFetch(
|
||||
uri, mozilla::CORS_NONE, mozilla::dom::ReferrerPolicy::_empty);
|
||||
|
||||
RefPtr<FakeChannel> channel = new FakeChannel();
|
||||
RefPtr<FakePreloader> preloader = new FakePreloader(channel);
|
||||
RefPtr<mozilla::dom::Document> doc;
|
||||
NS_NewXMLDocument(getter_AddRefs(doc));
|
||||
|
||||
EXPECT_TRUE(NS_SUCCEEDED(
|
||||
preloader->OpenChannel(&key, uri, mozilla::CORS_NONE,
|
||||
mozilla::dom::ReferrerPolicy::_empty, doc)));
|
||||
|
||||
EXPECT_TRUE(NS_SUCCEEDED(channel->Start()));
|
||||
EXPECT_TRUE(NS_SUCCEEDED(channel->Data(NS_LITERAL_CSTRING("one"))));
|
||||
EXPECT_TRUE(NS_SUCCEEDED(channel->Data(NS_LITERAL_CSTRING("two"))));
|
||||
|
||||
RefPtr<FakeListener> listener = new FakeListener();
|
||||
listener->mCancelIn = FakeListener::OnData;
|
||||
|
||||
EXPECT_TRUE(NS_SUCCEEDED(preloader->AsyncConsume(listener)));
|
||||
EXPECT_FALSE(NS_SUCCEEDED(preloader->AsyncConsume(listener)));
|
||||
|
||||
NS_DispatchToMainThread(NS_NewRunnableFunction("test", [&]() {
|
||||
EXPECT_TRUE(NS_FAILED(channel->Data(NS_LITERAL_CSTRING("three"))));
|
||||
EXPECT_TRUE(NS_FAILED(channel->Stop(NS_OK)));
|
||||
|
||||
EXPECT_TRUE(listener->mOnStart);
|
||||
EXPECT_TRUE(listener->mOnData.EqualsLiteral("one"));
|
||||
EXPECT_TRUE(listener->mOnStop && *listener->mOnStop == ERROR_CANCEL);
|
||||
|
||||
Yield();
|
||||
}));
|
||||
|
||||
Await();
|
||||
|
||||
EXPECT_FALSE(NS_SUCCEEDED(preloader->AsyncConsume(listener)));
|
||||
}
|
||||
|
||||
TEST(TestFetchPreloader, CachePartlyBeforeConsumeCancelInOnStartRequestAndRace)
|
||||
{
|
||||
nsCOMPtr<nsIURI> uri;
|
||||
NS_NewURI(getter_AddRefs(uri), NS_LITERAL_CSTRING("https://example.com"));
|
||||
auto key = mozilla::PreloadHashKey::CreateAsFetch(
|
||||
uri, mozilla::CORS_NONE, mozilla::dom::ReferrerPolicy::_empty);
|
||||
|
||||
RefPtr<FakeChannel> channel = new FakeChannel();
|
||||
RefPtr<FakePreloader> preloader = new FakePreloader(channel);
|
||||
RefPtr<mozilla::dom::Document> doc;
|
||||
NS_NewXMLDocument(getter_AddRefs(doc));
|
||||
|
||||
EXPECT_TRUE(NS_SUCCEEDED(
|
||||
preloader->OpenChannel(&key, uri, mozilla::CORS_NONE,
|
||||
mozilla::dom::ReferrerPolicy::_empty, doc)));
|
||||
|
||||
EXPECT_TRUE(NS_SUCCEEDED(channel->Start()));
|
||||
EXPECT_TRUE(NS_SUCCEEDED(channel->Data(NS_LITERAL_CSTRING("one"))));
|
||||
EXPECT_TRUE(NS_SUCCEEDED(channel->Data(NS_LITERAL_CSTRING("two"))));
|
||||
|
||||
// This has to simulate a possibiilty when stream listener notifications from
|
||||
// the channel are already pending in the queue while AsyncConsume is called.
|
||||
// At this moment the listener has not been notified yet.
|
||||
NS_DispatchToMainThread(NS_NewRunnableFunction("test", [&]() {
|
||||
EXPECT_TRUE(NS_SUCCEEDED(channel->Data(NS_LITERAL_CSTRING("three"))));
|
||||
EXPECT_TRUE(NS_SUCCEEDED(channel->Stop(NS_OK)));
|
||||
}));
|
||||
|
||||
RefPtr<FakeListener> listener = new FakeListener();
|
||||
listener->mCancelIn = FakeListener::OnStart;
|
||||
|
||||
EXPECT_TRUE(NS_SUCCEEDED(preloader->AsyncConsume(listener)));
|
||||
EXPECT_FALSE(NS_SUCCEEDED(preloader->AsyncConsume(listener)));
|
||||
|
||||
// Check listener's been fed properly. Expected is to NOT get any data and
|
||||
// propagate the cancellation code and not being called duplicated
|
||||
// OnStopRequest.
|
||||
NS_DispatchToMainThread(NS_NewRunnableFunction("test", [&]() {
|
||||
EXPECT_TRUE(listener->mOnStart);
|
||||
EXPECT_TRUE(listener->mOnData.IsEmpty());
|
||||
EXPECT_TRUE(listener->mOnStop && *listener->mOnStop == ERROR_CANCEL);
|
||||
|
||||
Yield();
|
||||
}));
|
||||
|
||||
Await();
|
||||
|
||||
EXPECT_FALSE(NS_SUCCEEDED(preloader->AsyncConsume(listener)));
|
||||
}
|
||||
|
||||
TEST(TestFetchPreloader, CachePartlyBeforeConsumeCancelInOnDataAvailableAndRace)
|
||||
{
|
||||
nsCOMPtr<nsIURI> uri;
|
||||
NS_NewURI(getter_AddRefs(uri), NS_LITERAL_CSTRING("https://example.com"));
|
||||
auto key = mozilla::PreloadHashKey::CreateAsFetch(
|
||||
uri, mozilla::CORS_NONE, mozilla::dom::ReferrerPolicy::_empty);
|
||||
|
||||
RefPtr<FakeChannel> channel = new FakeChannel();
|
||||
RefPtr<FakePreloader> preloader = new FakePreloader(channel);
|
||||
RefPtr<mozilla::dom::Document> doc;
|
||||
NS_NewXMLDocument(getter_AddRefs(doc));
|
||||
|
||||
EXPECT_TRUE(NS_SUCCEEDED(
|
||||
preloader->OpenChannel(&key, uri, mozilla::CORS_NONE,
|
||||
mozilla::dom::ReferrerPolicy::_empty, doc)));
|
||||
|
||||
EXPECT_TRUE(NS_SUCCEEDED(channel->Start()));
|
||||
EXPECT_TRUE(NS_SUCCEEDED(channel->Data(NS_LITERAL_CSTRING("one"))));
|
||||
EXPECT_TRUE(NS_SUCCEEDED(channel->Data(NS_LITERAL_CSTRING("two"))));
|
||||
|
||||
// This has to simulate a possibiilty when stream listener notifications from
|
||||
// the channel are already pending in the queue while AsyncConsume is called.
|
||||
// At this moment the listener has not been notified yet.
|
||||
NS_DispatchToMainThread(NS_NewRunnableFunction("test", [&]() {
|
||||
EXPECT_TRUE(NS_SUCCEEDED(channel->Data(NS_LITERAL_CSTRING("three"))));
|
||||
EXPECT_TRUE(NS_SUCCEEDED(channel->Stop(NS_OK)));
|
||||
}));
|
||||
|
||||
RefPtr<FakeListener> listener = new FakeListener();
|
||||
listener->mCancelIn = FakeListener::OnData;
|
||||
|
||||
EXPECT_TRUE(NS_SUCCEEDED(preloader->AsyncConsume(listener)));
|
||||
EXPECT_FALSE(NS_SUCCEEDED(preloader->AsyncConsume(listener)));
|
||||
|
||||
// Check listener's been fed properly. Expected is to NOT get anything after
|
||||
// the first OnData and propagate the cancellation code and not being called
|
||||
// duplicated OnStopRequest.
|
||||
NS_DispatchToMainThread(NS_NewRunnableFunction("test", [&]() {
|
||||
EXPECT_TRUE(listener->mOnStart);
|
||||
EXPECT_TRUE(listener->mOnData.EqualsLiteral("one"));
|
||||
EXPECT_TRUE(listener->mOnStop && *listener->mOnStop == ERROR_CANCEL);
|
||||
|
||||
Yield();
|
||||
}));
|
||||
|
||||
Await();
|
||||
|
||||
EXPECT_FALSE(NS_SUCCEEDED(preloader->AsyncConsume(listener)));
|
||||
}
|
||||
|
||||
TEST(TestFetchPreloader, CachePartlyBeforeConsumeThrowFromOnStartRequestAndRace)
|
||||
{
|
||||
nsCOMPtr<nsIURI> uri;
|
||||
NS_NewURI(getter_AddRefs(uri), NS_LITERAL_CSTRING("https://example.com"));
|
||||
auto key = mozilla::PreloadHashKey::CreateAsFetch(
|
||||
uri, mozilla::CORS_NONE, mozilla::dom::ReferrerPolicy::_empty);
|
||||
|
||||
RefPtr<FakeChannel> channel = new FakeChannel();
|
||||
RefPtr<FakePreloader> preloader = new FakePreloader(channel);
|
||||
RefPtr<mozilla::dom::Document> doc;
|
||||
NS_NewXMLDocument(getter_AddRefs(doc));
|
||||
|
||||
EXPECT_TRUE(NS_SUCCEEDED(
|
||||
preloader->OpenChannel(&key, uri, mozilla::CORS_NONE,
|
||||
mozilla::dom::ReferrerPolicy::_empty, doc)));
|
||||
|
||||
EXPECT_TRUE(NS_SUCCEEDED(channel->Start()));
|
||||
EXPECT_TRUE(NS_SUCCEEDED(channel->Data(NS_LITERAL_CSTRING("one"))));
|
||||
EXPECT_TRUE(NS_SUCCEEDED(channel->Data(NS_LITERAL_CSTRING("two"))));
|
||||
|
||||
// This has to simulate a possibiilty when stream listener notifications from
|
||||
// the channel are already pending in the queue while AsyncConsume is called.
|
||||
// At this moment the listener has not been notified yet.
|
||||
NS_DispatchToMainThread(NS_NewRunnableFunction("test", [&]() {
|
||||
EXPECT_TRUE(NS_SUCCEEDED(channel->Data(NS_LITERAL_CSTRING("three"))));
|
||||
EXPECT_TRUE(NS_SUCCEEDED(channel->Stop(NS_OK)));
|
||||
}));
|
||||
|
||||
RefPtr<FakeListener> listener = new FakeListener();
|
||||
listener->mOnStartResult = ERROR_THROW;
|
||||
|
||||
EXPECT_TRUE(NS_SUCCEEDED(preloader->AsyncConsume(listener)));
|
||||
EXPECT_FALSE(NS_SUCCEEDED(preloader->AsyncConsume(listener)));
|
||||
|
||||
// Check listener's been fed properly. Expected is to NOT get any data and
|
||||
// propagate the throwing code and not being called duplicated OnStopRequest.
|
||||
NS_DispatchToMainThread(NS_NewRunnableFunction("test", [&]() {
|
||||
EXPECT_TRUE(listener->mOnStart);
|
||||
EXPECT_TRUE(listener->mOnData.IsEmpty());
|
||||
EXPECT_TRUE(listener->mOnStop && *listener->mOnStop == ERROR_THROW);
|
||||
|
||||
Yield();
|
||||
}));
|
||||
|
||||
Await();
|
||||
|
||||
EXPECT_FALSE(NS_SUCCEEDED(preloader->AsyncConsume(listener)));
|
||||
}
|
|
@ -0,0 +1,21 @@
|
|||
# -*- Mode: python; c-basic-offset: 4; 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/.
|
||||
|
||||
UNIFIED_SOURCES += [
|
||||
'TestFetchPreloader.cpp',
|
||||
]
|
||||
|
||||
LOCAL_INCLUDES += [
|
||||
'/netwerk/base',
|
||||
'/xpcom/tests/gtest',
|
||||
]
|
||||
|
||||
FINAL_LIBRARY = 'xul-gtest'
|
||||
|
||||
LOCAL_INCLUDES += [
|
||||
'!/xpcom',
|
||||
'/xpcom/components'
|
||||
]
|
|
@ -7,13 +7,17 @@
|
|||
with Files("**"):
|
||||
BUG_COMPONENT = ("Core", "Networking")
|
||||
|
||||
TEST_DIRS += ['gtest']
|
||||
|
||||
EXPORTS.mozilla += [
|
||||
'FetchPreloader.h',
|
||||
'PreloaderBase.h',
|
||||
'PreloadHashKey.h',
|
||||
'PreloadService.h',
|
||||
]
|
||||
|
||||
UNIFIED_SOURCES += [
|
||||
'FetchPreloader.cpp',
|
||||
'PreloaderBase.cpp',
|
||||
'PreloadHashKey.cpp',
|
||||
'PreloadService.cpp',
|
||||
|
|
Загрузка…
Ссылка в новой задаче